fix: extend a re-opened module instead of discarding the earlier block - #6206
fix: extend a re-opened module instead of discarding the earlier block#6206prql-bot wants to merge 2 commits into
module instead of discarding the earlier block#6206Conversation
prql-bot
left a comment
There was a problem hiding this comment.
Self-review, so this is a COMMENT rather than an approval.
The core change looks right — routing through RootModule::declare puts module on the same insertion path as every other statement kind, and the merge semantics match what insert_stmts_at_path already does across files. cargo test -p prqlc --test integration passes here (490 tests, 5 ignored), including every pre-existing snapshot, so the std placeholder still fills in as before.
Three things:
The residual asymmetry is worth a human call. enum m { Paid = 0 } then module m { let a = 5 } now merges, but the reverse order — module m { let a = 5 } then enum m { Paid = 0 } — still errors with duplicate declarations of m, asserted in enum_duplicate_of_existing_declaration, which this PR leaves in place. So the same two statements now compile or fail depending only on which comes first, and both outcomes are asserted a few lines apart in the same file. An enum builds a module, so if re-opening is the rule then fold_type_def_stmt wants the same treatment; if it isn't, the enum-then-module direction shouldn't merge either. This PR doesn't have to settle it, but it shouldn't stay implicit — happy to follow up whichever way you'd prefer.
Missing changelog entry. development.md asks for a line for any user-facing change, and #6150 — the same class of fix for import — got one under Language. This change adds two new compile errors for programs that previously compiled clean, so it needs one as well.
Some(false) duplicates declare's error. The arm hand-builds duplicate declarations of {ident} with stmt.span, which is byte-for-byte what declare already returns when the name is taken — two copies of one message, free to drift. Collapsing it into the None arm as _ produces the same message and span (the let m = 5 / module m snapshot is unchanged) and drops six lines.
Since this is a bot-authored PR with no human author to apply suggestions, I'm pushing the last two as a follow-up commit; the first is left for a maintainer.
`declare` already returns `duplicate declarations of {ident}` for a name
that's taken, so the explicit `Some(false)` arm was a second copy of the
same message and span.
Declaring a
moduletwice at the same level kept only the last block and dropped the earlier one with no diagnostic, somodule m { let a = 5 }followed bymodule m { let b = 6 }leftm.aunresolvable.fold_module_def_stmtwas the one statement kind still inserting straight intoroot_mod.module, which overwrites; every other kind goes throughRootModule::declare. It now extends an existing module instead of replacing it, and reports a collision when the name holds something that isn't a module. Fixes #6166.Merge rather than error is the judgement call here, and it's worth a second opinion. The alternative — rejecting the second block outright, matching
let/type/import/enum— reads as more consistent, but it would make a single file stricter than the multi-file path, whereinsert_stmts_at_pathalready merges same-named module defs across files, and it would need a special case for the emptystdplaceholder thatModule::new_rootseeds and the standard library relies on overwriting. Merging keeps both behaviours consistent with no special case. Either way the silent discard is gone: a name declared by both blocks is now reported asduplicate declarations of m.a.Behaviour, before and after
module m { let a = 5 }module m { let b = 6 }Unknown name m.aenum m { Paid = 0 }module m { let a = 5 }module m { let a = 5 }module m { let a = 6 }duplicate declarations of m.alet m = 5module m { let a = 6 }letsilently droppedduplicate declarations of mThe last two rows are new diagnostics for what previously compiled clean.
Verification
module_reopened_is_extendedinerror_messages.rscovers all four rows; it fails onmainwithUnknown name m.a, which is the symptom reported in #6166. #6164's doc comment pointed at #6166 as the uncovered direction — that note is updated to point at the new test.cargo test -p prqlc -p prqlc-parserpasses (495 integration tests, +1 from this change), as docargo clippy -p prqlc --all-targetsandcargo fmt --check.task prqlc:pull-requestcouldn't run in the tend sandbox —cargo-instaisn't on the PATH there, which is what #6144 is about — so the snapshots here were written from actual compiler output rather than--accept.