Skip to content

fix: extend a re-opened module instead of discarding the earlier block - #6206

Open
prql-bot wants to merge 2 commits into
mainfrom
fix/module-reopen-discards-earlier-block
Open

fix: extend a re-opened module instead of discarding the earlier block#6206
prql-bot wants to merge 2 commits into
mainfrom
fix/module-reopen-discards-earlier-block

Conversation

@prql-bot

Copy link
Copy Markdown
Collaborator

Declaring a module twice at the same level kept only the last block and dropped the earlier one with no diagnostic, so module m { let a = 5 } followed by module m { let b = 6 } left m.a unresolvable. fold_module_def_stmt was the one statement kind still inserting straight into root_mod.module, which overwrites; every other kind goes through RootModule::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, where insert_stmts_at_path already merges same-named module defs across files, and it would need a special case for the empty std placeholder that Module::new_root seeds 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 as duplicate declarations of m.a.

Behaviour, before and after
Query Before After
module m { let a = 5 }
module m { let b = 6 }
Unknown name m.a both resolve
enum m { Paid = 0 }
module m { let a = 5 }
enum silently dropped both resolve
module m { let a = 5 }
module m { let a = 6 }
last one silently wins duplicate declarations of m.a
let m = 5
module m { let a = 6 }
let silently dropped duplicate declarations of m

The last two rows are new diagnostics for what previously compiled clean.

Verification

module_reopened_is_extended in error_messages.rs covers all four rows; it fails on main with Unknown 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-parser passes (495 integration tests, +1 from this change), as do cargo clippy -p prqlc --all-targets and cargo fmt --check. task prqlc:pull-request couldn't run in the tend sandbox — cargo-insta isn't on the PATH there, which is what #6144 is about — so the snapshots here were written from actual compiler output rather than --accept.

@prql-bot prql-bot left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
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.

Duplicate module definitions silently discard the earlier declaration

1 participant