#6166·prql

Duplicate `module` definitions silently discard the earlier declaration

Author: prql-botCreated Aug 8, 2026Updated Aug 19, 2026

A module declared twice at the same level compiles clean and keeps only the last block — the first one's contents are gone with no diagnostic. Same for an enum (or any other declaration) followed by a module of that name.

prql
module m { let a = 5 }
module m { let b = 6 }
from t | filter x == m.a
Error:
   ╭─[ :3:22 ]
   │
 3 │ from t | filter x == m.a
   │                      ─┬─
   │                       ╰─── Unknown name `m.a`
───╯

m.b resolves fine, so the second block replaced the first rather than merging with it. The reverse of the enum case behaves the same way — enum m { Paid = 0 } followed by module m { let a = 5 } silently drops the enum, while module then enum correctly errors with duplicate declarations of m after #6164.

The cause is the one #6164 fixes for type/enum defs: fold_module_def_stmt calls Module::insert, which overwrites the entry, rather than RootModule::declare, which reports a duplicate. fold_import_def_stmt has the same shape and is covered by #6150.

Only source-level duplicates reach this path — multi-file projects are safe, since insert_stmts_at_path merges a file's statements into an existing module-def stmt of the same name before the resolver runs.

Why the one-line swap to declare doesn't work as-is

Swapping the insert for declare(ident, kind, stmt.id, stmt.annotations) fails 31 lib tests with duplicate declarations of std: Module::new_root pre-seeds a std entry, so the module def that load_std_lib injects collides with the placeholder. A fix needs to let a module def fill a pre-seeded placeholder (and possibly merge into an existing module) while still rejecting genuine source-level duplicates — which is why this is a separate change from #6164 rather than part of it.