Conversation
src/Code.hs には `\(X.Enumeration v docs) -> Code {...}` という同じラムダが
4 回書かれていた。うち 1 つは `constraintToCode` という名前の付いた
トップレベル関数で、`Enumeration v []` を最初の等式で処理していて全域。
もう 1 つは `if not (null docs_)` でガードしてあり、これも全域。残る 2 つ
(Code.hs:129 と Code.hs:178) は **ガード無しで `head docs_` と
`last docs_` を呼んでいた**。
in Code {value = v, codeDescription = head docs_, notes = last docs_}
`<xs:enumeration value="02" />` のようにドキュメントを持たない列挙値が
1 つでもこの経路に来ると、生成器が `Prelude.head: empty list` で落ちる。
空の docs が実在することは、同じファイルの `constraintToCode` がわざわざ
`Enumeration v []` を明示的に処理していることが示している。
3 つのインラインのラムダをすべて `constraintToCode` の呼び出しに置き換える。
同じ式は既にこのファイルの 2 箇所 (`map constraintToCode (typeConstraints t)`
と `map constraintToCode simpleRestrictionConstraints`) で使われている。
ガード済みのものと `constraintToCode` は完全に等価なので、現在パースできて
いるスキーマに対する生成結果は変わらない。変わるのは、これまで落ちていた
入力に対して `""` を返すようになる点だけ。
回帰テストを足す。fixtures/test_code_undocumented*.xsd は
test_code_description*.xsd のコピーで、"02" の列挙から `<xs:annotation>` を
取り除いただけ。この差分だけで、修正前は `head` が空リストで落ちる。
parseConstrains は `value` 属性しか要求せず、annotation が無ければ
parseAnnotations が `[]` を返すので、`Enumeration "02" []` になる。
-Wx-partial が src/ で報告する 5 箇所のうち、残る 3 箇所
(Code.hs:108 のガード済み、Code.hs:200 の `constraintToCode` 本体、
Model.hs:150 の `tail ys`) はいずれも偽陽性なので触っていない。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8rZakwAiz1jpViUX34x1Z
レビュー結果マージを止める指摘(必須)はありません。 3 箇所の置き換えはいずれも等価で、回帰テストは修正前に確実に落ちることをコードを追って確認しました。以下は検証の内訳と、推奨/任意の指摘です。 検証したこと1. 等価性 — 3 箇所すべて OK
L108 / L109(ガード付き版)vs
strictness も一致します。旧 L108 は L129 / L178(無ガード版): 2. 「生成結果は変わらない」 — 結論は正しく、本文より強い論証が立ちます本文は「現在パースできているスキーマではこの経路に空の docs が来ていない」という経験的な前提に寄りかかっていますが、そこに依存する必要はありません。上の表から、任意のスキーマ S について
が成り立ちます。つまり 「これまで生成できていた入力の出力は、この PR では絶対に変わらない」 は前提抜きで言えます。変わりうるのは「これまで生成器が落ちていた入力」だけで、それは定義上 遅延評価の抜け穴( 補足として、注釈なし enumeration が実在することの裏付けは //
case "ltr":
*c = ``となっており、 → 3. 回帰テストは L178 を確かに通ります
修正前は この経路が実際に動くことは、同一構造の既存ケース( 4. フィクスチャの妥当性 — OK
5. 残した 3 箇所は本当に偽陽性 — OK
6. 他の無ガードな部分関数
推奨推奨 1. L129(
|
レビュー指摘。落ちうる 2 箇所のうち、先の回帰テストは L178 (topLevelElementToCode の経路) しか通っていなかった。L129 (topLevelTypeToCode の ListType 分岐) は無防備なまま。 fixtures/test_code_territorycodelist*.xsd が既にその経路を踏んでいるので、 その undocumented 版を足して 1 ケースで塞ぐ。差分は "02" の列挙から <xs:annotation> を取り除いただけ。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P8rZakwAiz1jpViUX34x1Z
|
レビューありがとうございます。推奨 3 件すべて対応しました。 推奨 1: L129 側の回帰テストが無い → 足しました(
|
| ブランチ | resolver | GHC |
|---|---|---|
main(この PR の base) |
lts/16/27 |
8.8.4 |
claude/test-warnings(#72 系列) |
lts/23/25 |
9.8.4 |
-Wx-partial は GHC 9.8 で追加された警告なので、この PR の CI ログには 1 行も出ていません。 本文に書いた「26 行 / 21 箇所」「library-profiling で src が二重計上」という内訳は、#72 の CI ログ(lts-23.25)で観測したものです。数字自体は正しいものの、どこで観測したかを書かずに載せたのは不正確でした。本文を直します。
なお、この PR の修正の妥当性はその観測に依存していません(head が部分関数であることは GHC の警告の有無と無関係)。
推奨 3: 「触っていないもの」に Code.hs:108 があるのは自己矛盾 → そのとおり
Code.hs:108 はこの PR で constraintToCode に置き換わって消えます。「触っていない」リストに入れたのは誤りでした。PR 後に残る偽陽性は Code.hs:200(constraintToCode 本体)と Model.hs:150(tail ys)の 2 箇所です。本文を直します。
任意 2: Util.unwrap(実質 fromJust)
これは別途対応します。 ご指摘の非対称が特に効いています。
Code.hs:118は今回直した L129 の 11 行上、同じ分岐の中- 兄弟関数
topLevelElementToCodeは同じ lookup をMaybeで流している
同じクラスのバグが Model.hs に 7 箇所・Code.hs に 2 箇所残るとのことなので、この PR に混ぜず(「無ガードの head / last を潰す」という単位が崩れるため)、課題として登録しました。unwrap は head と違って -Wx-partial が何も言わないので、洗い出しは手作業になります。
検証していただいた点について
等価性 3 箇所(1 要素で head == last になる点まで)、回帰テストが L178 に到達する経路、Model.hs:150 の findIndex p acc == Just i ⇒ i < length acc ⇒ ys が長さ ≥ 1、src/ に他の無ガード部分関数が無いこと — いずれも独立に裏を取っていただけて助かりました。
特に 「生成結果は変わらない」の論証を『before-output は ⊥ か after-output のどちらか』に整理していただいたのは、私の書き方(「現在パースできているスキーマでは空の docs が来ていない」という経験的前提)より強い形になっています。本文をその形に書き換えます。generated/go/v3/code.go:33049 の Dir に *c = ``` `` が実在するという指摘も、注釈なし enumeration が現実に存在することと ("", "") が既存出力と整合することの両方の裏付けになっていて有用でした。
Generated by Claude Code
base は
main。#72 のレビューで-Wx-partialの 5 箇所を個別に見てもらった結果、2 箇所が実際に落ちうると分かったので、その修正です。同じラムダが 4 回書かれていて、2 つだけガードが無い
src/Code.hsには\(X.Enumeration v docs) -> Code {...}という同じ変換が 4 回あります。docsの扱いconstraintToCode(L196)Enumeration v []を処理if not (null docs_) then head docs_ else ""落ちる方はこうなっています。
<xs:enumeration value="02" />のようにドキュメントを持たない列挙値が 1 つでもこの経路に来ると、生成器がPrelude.head: empty listで落ちます。空の
docsが実在する証拠は、同じファイルの中にあります。constraintToCodeがわざわざ第 1 等式でEnumeration v []を書き分けているのは、それが起こるからです。実際の生成物にもgenerated/go/v3/code.goのDirに空文字列のcaseが存在します。同じ行のlast docs_も同罪ですが、-Wx-partialはlastを報告しないので警告には出ません。修正
3 つのインラインのラムダを、すべて
constraintToCodeの呼び出しに置き換えます。同じ式は既にこのファイルの 2 箇所で使われています(L221 の
map constraintToCode (typeConstraints t)と L225 のmap constraintToCode simpleRestrictionConstraints)。新しい書き方を持ち込んでいるのではなく、既にある書き方に寄せるだけです。型も既存の使用箇所が保証しています(
typeConstraints :: X.Type -> [X.Constraint]、X.Constraintの構築子はEnumeration Text [Annotation]のみなのでmap constraintToCodeは全域)。生成結果は変わりません
置き換えた各箇所について、修正前の出力は「⊥(クラッシュ)」か「修正後と同じ値」のどちらかにしかなりません。
constraintToCodeは完全に等価です。docs = []ならどちらも("", "")、1 要素ならhead == lastでどちらも(d, d)、複数要素ならどちらも(head docs_, last docs_)。("", "")のみです。generated/は現に存在してビルドも通っているので、修正前の出力は ⊥ ではありません。したがって修正後と同じです。回帰テスト
落ちうる 2 箇所の両方にテストを付けました。フィクスチャはいずれも既存のものをコピーして、
"02"の列挙から<xs:annotation>を取り除いただけです。topLevelElementToCode)test_code_undocumented{,_codelists}.xsdtopLevelTypeToCodeのListType分岐)test_code_territorycodelist_undocumented{,_codelists}.xsd期待値は元のテストと同じ
CodeTypeで、2 番目のCodeだけがCode "02" "" ""になります。修正前はどちらもPrelude.head: empty listで落ちます。触っていないもの
この PR の後、
-Wx-partialの偽陽性として残るのは 2 箇所です(Code.hs:108はこの PR でconstraintToCodeに置き換わって消えます)。Code.hs:200—constraintToCode本体。第 1 等式が[]を処理しているが GHC には見通せないModel.hs:150—tail ys。findIndex p acc == Just i⇒i < length acc⇒splitAt iのysは長さ ≥ 1Util.unwrap(実質fromJust)の無ガードな使用がModel.hsに 7 箇所・Code.hsに 2 箇所残っていますが、「無ガードのhead/lastを潰す」という単位が崩れるので別 PRにします。警告の数について(観測元の注記)
-Wx-partialは GHC 9.8 で追加された警告です。main(この PR の base)はlts/16/27= GHC 8.8.4 なので、この PR の CI ログには 1 行も出ません。本文中の箇所の特定は、
lts/23/25(GHC 9.8.4)に上げている #72 の CI ログで観測したものです(ログ 26 行 → 実際は 21 箇所、src/が 5 /test/が 16。src/が倍になるのはstack.yamlのlibrary-profiling: trueで library が 2 way コンパイルされるため)。この修正の妥当性は、その観測に依存していません(
headが部分関数であることは警告の有無と無関係です)。確認したこと
src/Xsd/Parser.hsのparseConstrainsを読み、value属性しか要求しないこと、annotation が無ければparseAnnotationsが[]を返すことを確認(したがって<xs:enumeration value="02" />はEnumeration "02" []になります)xml.etree.ElementTreeでパースして整形式であることを確認schemaLocationの 1 行と"02"の annotation だけであることをdiffで確認🤖 Generated with Claude Code
https://claude.ai/code/session_01P8rZakwAiz1jpViUX34x1Z
Generated by Claude Code