Skip to content

Un-fold the executable's ghc-options and warn on the test suite too - #66

Open
kogai wants to merge 3 commits into
claude/haskell-resolver-lts23from
claude/fix-ghc-options
Open

kogai wants to merge 3 commits into
claude/haskell-resolver-lts23from
claude/fix-ghc-options

Conversation

@kogai

@kogai kogai commented Sep 13, 2026

Copy link
Copy Markdown
Owner

#60 のレビューで指摘された、package.yaml の警告フラグの事故を直します。base は #65(resolver 更新)。

何が起きていたか

executables の ghc-options はこう書かれていました。

    ghc-options:
      - -threaded
      - -rtsopts
      - -with-rtsopts=-N
        -Wall
        -fwarn-incomplete-patterns
        -fwarn-incomplete-uni-patterns

これは 4 つのリスト要素ではなく、4 行に折り畳まれた 1 つのスカラ値です。結果的には動いています(cabal が空白で分割するため、生成された onix.cabal には 4 つのフラグが並びます)が、成立しているのは偶然です。このブロックを次に編集した人が、気づかないまま警告フラグを落とす形になります。

テストスイートには -Wall が無かった

tests スタンザにはそもそも警告フラグがなく、test/ 配下は不完全パターンも未使用束縛も一切チェックされていませんでした。追加します。

これを #65(GHC 8.8.3 → 9.8.4)と一緒に入れるのには理由があります。5 世代分の跳躍で何が変わったかを教えてくれるのは警告であり、テストコードだけが黙っている状態は避けたいためです。

確認

  • 生成される onix.cabal の 3 スタンザすべてで ghc-options が期待どおりになることを確認
    42:  ghc-options: -Wall -fwarn-incomplete-patterns -fwarn-incomplete-uni-patterns
    66:  ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall -fwarn-incomplete-patterns -fwarn-incomplete-uni-patterns
    85:  ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall
    
  • hpack ハッシュの自己整合性を確認
  • ビルドは未検証(この環境に GHC / stack が無いため)。-Wall を足したことで新しい警告が出る可能性はありますが、警告はエラーではないので CI は緑のままのはずです

🤖 Generated with Claude Code

https://claude.ai/code/session_01P8rZakwAiz1jpViUX34x1Z


Generated by Claude Code

In package.yaml the executable's flags were written as

    - -with-rtsopts=-N
      -Wall
      -fwarn-incomplete-patterns
      -fwarn-incomplete-uni-patterns

which is one YAML scalar continued over four lines, not four list items.
It happens to work — cabal splits the value on whitespace, and the
generated onix.cabal shows all four flags — but only by accident, and the
next edit to that block can silently drop the warning flags. Write them
as separate entries.

The test suite had no -Wall at all, so nothing under test/ was checked
for incomplete patterns or unused bindings. Add it. This is worth doing
alongside the GHC 9.8 move in the base branch: warnings are how a jump of
that size tells you what it changed, and the tests were the part not
saying anything.

Raised in review of #60.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8rZakwAiz1jpViUX34x1Z

kogai commented Sep 14, 2026

Copy link
Copy Markdown
Owner Author

レビュー

差分そのものは正しく、PR 本文の主張も裏が取れました。変更前の executables.onix-exe.ghc-options は確かに「4 要素のリスト」ではなく 3 要素のリストで、3 番目が 4 行に折り畳まれた 1 個のスカラ でした(PyYAML でパースして確認、下記)。生成された onix.cabal の行が結果的に正しかったのは、hpack が要素を空白で join するので折り畳まれたスカラ内の空白と区別がつかなかったためで、まさに「偶然」です。onix.cabal の手編集も test-suite の ghc-options に -Wall を末尾追加しただけで hpack の unwords 出力と一致し、hpack ハッシュも自己整合しています(sha256 を再計算して一致、アルゴリズムは hpack 本体のソースから起こして既知の hpack 生成済みファイルで検証済み)。必須の指摘はありません。ただし -Wall を tests に足したことで新規に 30 件の警告が出ており(CI ログで実測)、これを放置すると「毎ビルド流れるが誰も直さないノイズ」になるので、この PR か直後の後続で潰すことを強く勧めます。あわせて -fwarn-incomplete-* は GHC 9.8 では -Wall に完全に含まれており、今回リストとして「本当に効くフラグ」になった結果、冗長であることがはっきりしました。


必須

なし。


推奨

1. -fwarn-incomplete-patterns / -fwarn-incomplete-uni-patterns は GHC 9.8 の -Wall に完全包含されるので削除する

  • 場所: package.yaml L29-30(library)、L55-56(executables.onix-exe)

  • 問題: GHC 9.8 のユーザーズガイドでは

    • -Wincomplete-patterns は -W(= -Wextra)に含まれ、-Wall は「-Wextra の全部 + α」と定義されている
    • -Wincomplete-uni-patterns は -Wall の「+ α」側に明記されている(GHC 9.2 で -Wall に入りました)

    つまり 2 つとも -Wall の後ろに書く意味がありません。加えて -fwarn-⟨wflag⟩ という綴り自体が GHC 8 以降 deprecated です("This spelling is deprecated, but still accepted for backwards compatibility.")。

  • なぜ重要か: 今回の修正で「折り畳まれていて効いていないのでは」という疑いは晴れましたが、代わりに「効いてはいるが 1 つも新しい警告を増やしていない行」が 6 行残りました。次にこのブロックを触る人が「この 2 つは何のためにあるのか」で止まります。

  • 修正案: library と executable の ghc-options からこの 2 行ずつを削除し、-Wall だけにする。挙動は一切変わりません(onix.cabal とハッシュの再生成は必要)。

  • なお deprecation 警告は出ません: CI ログ全文を fwarn / deprecat で grep しましたが、GHC 由来の deprecation メッセージはゼロでした(引っかかったのは Actions 側の Node 20 deprecation のみ)。なので「毎ビルド deprecation が出続ける」という実害は今のところありません。あくまで冗長性の除去です。

2. tests への -Wall で新規に 30 件の警告が出ている。内訳はすべて機械的に潰せるものなので、この PR で潰すか直後に潰す

CI ログ(run 34746314621 / job 103694787643)から実測した、この PR で新規に増えた警告(ユニーク箇所):

フラグ 件数
-Wunused-imports 19
-Wmissing-signatures 8
-Wunused-matches 2
-Wunused-local-binds 1
合計 30
  • -Wunused-imports 19 件: test/*.hs 5 ファイルすべてが同じ import ブロックのコピペを持っていて、その大半が使われていません。例:
    • test/TestCode.hs:6 import qualified Data.Map as M(M. の使用ゼロ)
    • test/TestCode.hs:7 import Data.Text (Text, pack, unpack)
    • test/TestCode.hs:11 import Text.XML (def, parseText, readFile) — これは Prelude.readFile を隠しかねない import なので、消えると素直に嬉しい
    • test/TestCode.hs:14 import qualified Xsd as X(X. の使用ゼロ)
    • test/TestCode.hs:10 / TestMixed.hs:9 / TestModel.hs:9 / TestParser.hs:8 / TestUtils.hs:7 は Test.HUnit の TestList が未使用
    • 他に TestMixed.hs:6,8,10、TestModel.hs:6,11、TestParser.hs:6,7、TestUtils.hs:5,6、Spec.hs:6
  • -Wmissing-signatures 8 件: TestCode.hs:16 tests、TestModel.hs:15,25,32,56 (expected1..4) と 92 tests、TestParser.hs:12 expected と 91 tests。TestMixed.hs だけは tests :: [Test] が付いています。全部 [Test] か具体型を書くだけです。
  • なぜ重要か: 19 件は削除するだけ、8 件は 1 行足すだけで、判断を要するものが 1 つもありません。ここを放置すると make test の出力が毎回 30 行の既読スルー領域になり、後から入る「本当に見るべき警告」が埋まります。-Wall を足した目的(5 世代ジャンプで何が変わったかを警告に語らせる)が、掃除しないと成立しません。逆に言うと掃除コストが極小なので、この PR で一緒にやってしまうのが一番安いです。

3. -Wunused-matches / -Wunused-local-binds の 3 件は「本物の指摘」なので個別に見てほしい

-Wall を足した価値が一番出ているのがここです。

  • test/TestModel.hs:123 scm <- getSchema "./fixtures/test_model_atomic.xsd" → その直後の actual = typeToText expected1 は scm を使っていません。このテストは fixture を読んでいるだけで、読んだ結果を一切検証していません。
  • test/TestModel.hs:147 も同様(test_model_atom_ref_bylist.xsd を読んで typeToText expected4 を検証)。
  • test/TestModel.hs:203 key = makeTargetQName "Annotation" が未使用。このケースは actual = collectElements scm が [] であることだけを assert しており、key は書きかけの痕跡に見えます。
  • 修正案: 未使用の scm <- を機械的に消すとテストの意味が変わるので、ここだけは「本当に fixture 越しに検証したかったのか、それとも純粋な typeToText の単体テストだったのか」を判断してから直してください。単体テストでよいなら getSchema の行を消す、fixture を検証したかったなら typeToText の引数を scm から取り出した型に差し替える、のどちらかです。

任意

4. -Wno-x-partial を入れるなら tests スタンザだけ。全体には反対

  • head / tail の警告([GHC-63394] [-Wx-partial])は -Wall とは無関係で、GHC 9.8.1 から default on です(-Wx-⟨category⟩: :since: 9.8.1, :default: on)。
  • 実際、この PR より前の Move to lts-23.25 (GHC 9.8.4) and stop pinning dependency versions #65 の CI ログ(job 103694322422、tests にまだ -Wall が無い状態)でも test/ に -Wx-partial が 16 件出ています。この PR が増やしたものではありません。
  • 内訳: src/ 5 箇所(src/Code.hs:108,129,178,200 と src/Model.hs:150)、test/ 16 箇所。
  • 判断: src/ の 5 箇所は本番コードの部分関数で、9.8 が正しく指摘している実バグ予備軍です。ここを黙らせるべきではありません。一方 test/ の head はすべて「このフィクスチャには必ず 1 件以上ある」前提の取り出しで、失敗すればテストが落ちて意味が分かるので、警告の価値は低い。抑制するなら tests スタンザにだけ -Wno-x-partial を足す形にしてください。package.yaml 全体やライブラリに付けるのは反対です。
  • ただしこれは Move to lts-23.25 (GHC 9.8.4) and stop pinning dependency versions #65 から続く既存事象なので、この PR のスコープ外として後続に回しても構いません。

5. -Wcompat を library / executable に足す

  • 5 世代(8.8 → 9.8)飛んだ直後なので、次の飛躍を小さくするのに一番効くのが -Wcompat です。-Wsemigroup / -Wnoncanonical-monoid-instances / -Wnoncanonical-monad-instances / -Wcompat-unqualified-imports / -Wtype-equality-out-of-scope が入ります。
  • 今の src/ は -Wall 下で -Wx-partial 以外の警告がゼロ(CI ログで確認)なので、追加コストはほぼ増分ゼロで測れます。
  • ただし警告が増える可能性はあるので、指摘 2 の掃除と同時にやらないほうが差分が読みやすいです。後続 PR 推奨。

6. -Werror は package.yaml に入れず、CI 側だけにする

  • package.yaml に -Werror を書くとローカル開発と将来の GHC アップグレードが両方壊れます。入れるなら .github/workflows/test.yml の make test を stack test --pedantic(= -Wall -Werror)相当にするか、--ghc-options=-Werror を CI からだけ渡す形。
  • 前提条件: 現状ユニークで 51 件の警告が出ているので、指摘 2・4 を片付けるまでは入れられません。掃除が終わってからの後続タスクとして。

7. -Wall は hpack のトップレベル ghc-options: に寄せてもよい

  • 今回の事故の本質は「3 スタンザに同じ警告フラグをコピペしていて、tests だけ付け忘れていた」ことです。hpack はトップレベルの ghc-options: を全セクションに継承させられるので、-Wall(と入れるなら -Wcompat)をトップレベルに置き、-threaded -rtsopts -with-rtsopts=-N だけをスタンザ側に残すと、同じ付け忘れが構造的に起きなくなります。
  • 生成される onix.cabal のフラグの並び順は変わるので、ハッシュ再計算が必要です。挙動は変わらないため完全に任意。

検証したこと / できなかったこと

検証したこと

(1) YAML の主張(PyYAML でパース)

$ python3 -c "import yaml,json; ..."   # before = origin/claude/haskell-resolver-lts23, after = origin/claude/fix-ghc-options

=== before.yaml ===
  executable onix-exe type= list len= 3
    ["-threaded", "-rtsopts", "-with-rtsopts=-N -Wall -fwarn-incomplete-patterns -fwarn-incomplete-uni-patterns"]
   joined: -threaded -rtsopts -with-rtsopts=-N -Wall -fwarn-incomplete-patterns -fwarn-incomplete-uni-patterns
  test onix-test type= list len= 3
    ["-threaded", "-rtsopts", "-with-rtsopts=-N"]

=== after.yaml ===
  executable onix-exe type= list len= 6
    ["-threaded", "-rtsopts", "-with-rtsopts=-N", "-Wall", "-fwarn-incomplete-patterns", "-fwarn-incomplete-uni-patterns"]
   joined: -threaded -rtsopts -with-rtsopts=-N -Wall -fwarn-incomplete-patterns -fwarn-incomplete-uni-patterns
  test onix-test type= list len= 4
    ["-threaded", "-rtsopts", "-with-rtsopts=-N", "-Wall"]

→ before は 3 要素で 3 番目が折り畳みスカラ、after は 6 要素。join 結果が同一なので生成 cabal 行は before でも正しかった=PR 本文の「偶然」という説明どおりです。

(2) hpack ハッシュの再計算

アルゴリズムは hpack 本体から起こしました(src/Hpack.hs: calculateHash (CabalFile cabalVersion _ _ body _) = sha256 (unlines $ cabalVersion ++ body)、src/Hpack/CabalFile.hs: span isComment <$> span (not . isComment) → 先頭の非コメント行群 + コメントヘッダ + dropWhile null した本体)。つまり L1-2 と L9-末尾を連結して sha256。

main.cabal   (hpack が実際に生成した既知のファイル。アルゴリズム検証用)
   stated   d894bbda696a3e4398796a16a928bd9923bbeaae2b4a61ad371f193c8322fa0d
   computed d894bbda696a3e4398796a16a928bd9923bbeaae2b4a61ad371f193c8322fa0d MATCH
base.cabal   (#65)
   stated   07b4003dd4819b94016dc0a6629b5b634c96677c275eb19e5e97c2a6ac30ffd5
   computed 07b4003dd4819b94016dc0a6629b5b634c96677c275eb19e5e97c2a6ac30ffd5 MATCH
after.cabal  (本 PR)
   stated   3926f8a5f904cf97dc479ae5599b082559e417ae2568fb51d2a7fd1931177343
   computed 3926f8a5f904cf97dc479ae5599b082559e417ae2568fb51d2a7fd1931177343 MATCH

(3) 3 スタンザのレンダリング一致

onix.cabal の差分はハッシュ行と L85 の 1 行のみ。L42 / L66 / L85 は package.yaml のリストを順序どおり空白 join したものと完全一致しています。

42:  ghc-options: -Wall -fwarn-incomplete-patterns -fwarn-incomplete-uni-patterns
66:  ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall -fwarn-incomplete-patterns -fwarn-incomplete-uni-patterns
85:  ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall

(4) -Wall が tests に実際に効いているか(CI ログの実測)

本 PR の run(job 103694787643, ghc-9.8.4)と base #65 の run(job 103694322422)の両方をパースして比較しました。

base #65 本 PR #66
src/ -Wx-partial 5 5
test/ -Wx-partial 16 16
test/ -Wunused-imports 0 19
test/ -Wmissing-signatures 0 8
test/ -Wunused-matches 0 2
test/ -Wunused-local-binds 0 1
app/ 0 0

→ -Wall が tests に確実に届いていること、-Wx-partial はこの PR とは無関係に default on であること、executable 側は(折り畳み時代も含め)そもそも警告ゼロだったことが確認できました。

(5) -Wall のフラグ集合(GHC 9.8 ユーザーズガイド原典)

docs/users_guide/using-warnings.rst(ghc-9.8 ブランチ)より:

  • -W に -Wincomplete-patterns が含まれる
  • -Wall は「-Wextra(= -W)の全部 + …」で、その + 側に -Wincomplete-uni-patterns と -Wmissing-signatures と -Wname-shadowing が明記されている
  • -Wx-⟨category⟩ は :since: 9.8.1 / :default: on / :reverse: -Wno-x-⟨category⟩
  • -fwarn-⟨wflag⟩ について「This spelling is deprecated, but still accepted for backwards compatibility.」

検証できなかったこと

  • hpack を実際に走らせて onix.cabal がバイト単位で一致するかは未検証です。この環境に GHC / stack / cabal / hpack がなく、haskell.org は egress ポリシーでブロックされているため入れられませんでした。代替として (a) hpack ソースから起こしたハッシュアルゴリズムを既知の hpack 生成物で検証したうえでの自己整合性確認、(b) CI ログから読み取った実効フラグの確認、の 2 つで担保しています。特に (b) は「生成 cabal がどうであれ、実際にコンパイラに渡ったフラグ」を見ているので、この PR の目的に対しては十分な検証だと考えます。
  • -Wcompat / -Wno-x-partial を足した場合に増減する警告の実数は未測定です(ビルドできないため)。指摘 5・6 を後続 PR に回すことを勧めているのはこのためです。
  • -Wname-shadowing / -Wincomplete-patterns が test/ で何も出していないのは CI ログ上の事実であって、ソース読解による推測ではありません(ログに 1 件も現れませんでした)。

Generated by Claude Code

Review checked this against GHC 9.8: -Wincomplete-patterns comes in via
-W, and -Wincomplete-uni-patterns moved into -Wall in 9.2, so both
spellings were redundant everywhere they appeared. The deprecated
-fwarn-* form is still accepted silently — the CI log carries no
deprecation output — so this costs nothing per build; it was just six
lines implying the project asks for something -Wall does not give.

ghc-options now reads as what it is:

  library     -Wall
  executable  -threaded -rtsopts -with-rtsopts=-N -Wall
  test        -threaded -rtsopts -with-rtsopts=-N -Wall

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8rZakwAiz1jpViUX34x1Z

kogai commented Sep 14, 2026

Copy link
Copy Markdown
Owner Author

レビューありがとうございます。推奨 1 を反映しました (4fd2d64)。残りは方針をお伝えします。

推奨 1: -fwarn-incomplete-* は -Wall に包含されている

削除しました。-Wincomplete-patterns は -W 経由、-Wincomplete-uni-patterns は GHC 9.2 で -Wall に取り込まれた、という確認までしていただいたおかげで安心して消せました。「CI ログに deprecation 出力が無いので毎ビルドのコストは無い、ただの混乱要因」という切り分けも助かります。

結果、3 スタンザとも素直な内容になりました。

library     -Wall
executable  -threaded -rtsopts -with-rtsopts=-N -Wall
test        -threaded -rtsopts -with-rtsopts=-N -Wall

推奨 3: 2 つのテストが何も検証していない — これが最大の収穫です

-Wall を足した目的そのものが、この発見で回収されました。確認しました。

-- test/TestModel.hs:123
scm <- getSchema "./fixtures/test_model_atomic.xsd"
let actual = typeToText expected1        -- ← scm ではなくファイル先頭のリテラル
assertEqual "can derive string type" "string" actual

expected1 / expected4 はファイル冒頭で手書きされた AST リテラルです。つまりこの 2 つのテストはフィクスチャを読み込んで捨てており、パース結果については何も表明していません。typeToText の単体テストとしては成立していますが、フィクスチャ名が示す内容は検証されていません。

これは別 PR にします。 本 PR はフラグの整理であり、テストの意味を変える変更は別の関心事だからです。「未使用束縛を消す」だけなら 1 行ですが、それだとテストが空振りしている事実を隠すことになります。フィクスチャを実際に検証する形に直すべきで、それは新しい表明を書くことになります。

推奨 2: 30 件の警告

別 PR で片付けます(19 unused-imports / 8 missing-signatures / 2 unused-matches / 1 unused-local-binds)。CI ログから実測していただいた内訳があるので、作業が確実になります。

任意

  • -Wx-partial は -Wall 由来ではなく GHC 9.8.1 以降デフォルト有効: 重要な訂正でした。抑制するとしてもテストスタンザのみ、src/ の 5 箇所(Code.hs:108,129,178,200 / Model.hs:150)は本番コードの実際の部分関数なので残すべき、という判断に同意します。
  • -Wcompat: 5 世代ジャンプ直後なので有効だと思います。警告整理の PR に含めます。
  • -Werror(CI のみ): 既存 51 件を片付けてからにします。順序はご指摘のとおりです。
  • -Wall をトップレベルに引き上げる: 「スタンザ 1 つ書き忘れる」バグの再発を構造的に防げるので、採用します。警告整理の PR で一緒に。

Generated by Claude Code

CI caught it immediately:

    Error: [S-305] Failed to generate a Cabal file using the Hpack library
           YAML parse exception at line 52, column 20:
           mapping values are not allowed in this context

My own doing. I removed the -fwarn-* lines with a list of replacement
strings that had the four-space forms before the six-space ones, and the
four-space form is a substring of the six-space form. So on the
executable's lines it matched two characters in and deleted from there,
leaving a stray "  " on each line that then ran into the following
`dependencies:` and indented it by eight spaces.

Restore the correct indentation. package.yaml now parses, and the four
stanzas read as intended:

    library     ['-Wall']
    executable  ['-threaded','-rtsopts','-with-rtsopts=-N','-Wall']
    test        ['-threaded','-rtsopts','-with-rtsopts=-N','-Wall']

with both dependency lists intact. Checked with a YAML parser this time,
which is what I should have done before pushing the previous commit —
onix.cabal was unaffected and its hash is still self-consistent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8rZakwAiz1jpViUX34x1Z
kogai pushed a commit that referenced this pull request Sep 14, 2026
#71 inherited the broken YAML through its base.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8rZakwAiz1jpViUX34x1Z

kogai commented Sep 14, 2026

Copy link
Copy Markdown
Owner Author

前のコミットで package.yaml を壊しました。修正済みです (903f72f)。

原因

Error: [S-305] Failed to generate a Cabal file using the Hpack library
       YAML parse exception at line 52, column 20:
       mapping values are not allowed in this context

-fwarn-* を削除する際、置換文字列のリストで 4 スペース版を 6 スペース版より先に並べていました。4 スペース版は 6 スペース版の部分文字列なので、executable スタンザの行では 2 文字目からマッチして削除され、各行に が残り、それが次の dependencies: に連結されて 8 スペースインデントになっていました。

      - -Wall
        dependencies:      ← 4 スペースであるべき
      - onix

修正と検証

インデントを戻し、今度は YAML パーサで検証しました。

library     ['-Wall']
executable  ['-threaded','-rtsopts','-with-rtsopts=-N','-Wall']
test        ['-threaded','-rtsopts','-with-rtsopts=-N','-Wall']

両方の dependencies リストも無傷です。onix.cabal は影響を受けておらず、ハッシュも自己整合しています。

前のコミットを push する前にこの検証をすべきでした。同じ変更でワークフローの YAML は検証していたのに、package.yaml はしていませんでした。

#71 は base 経由でこの破損を引き継いでいたので、そちらにもマージ済みです。


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant