Repro (on master, from the repo root)
daslang modules/dasPEG/peg/peg.das
error[20605]: several modules with the same name 'detail/colors'
namely 'modules/dasPEG/peg/detail/colors.das' and 'D:/Work/daScript/modules/dasPEG/peg/detail/colors.das'
from modules/dasPEG/peg/peg.das
The same command with an absolute path to peg.das compiles fine. Same file, two spellings.
Mechanism
Require resolution inherits the parent file's spelled name instead of canonicalizing, and the module registry compares files by spelled path string:
daslang modules/dasPEG/peg/peg.das — the root file registers under the relative string as typed.
peg.das:10 require peg/parse_macro — not a same-dir file, resolves through the module search path (das_root scan) → absolute <root>/modules/dasPEG/peg/parse_macro.das.
parse_macro.das:19 require detail/colors — same-dir relative to parse_macro's spelled name → registers colors.das under the absolute path.
peg.das:11 require detail/colors — same-dir relative to peg.das's spelled name → resolves the same file under the relative path.
Two "distinct" files now both claim module name detail/colors → 20605.
General shape: any root file compiled by a relative path whose same-directory sibling is also reachable through a mount-routed require chain trips this.
How it surfaced
The CI changed-files lint gate feeds git diff relative paths; the moment peg.das entered a PR's changed set the gate went red (#3420). Worked around at the tool boundary — utils/lint/main.das now absolutizes its positional inputs (2f52e76) — but the compiler bug remains for any embedder/CLI passing relative paths.
Fix direction
Canonicalize for module identity (a normalizeFileName at require-resolution time, used only for the duplicate check / registry key). The blunt alternative — canonicalizing every registered name — would flip error messages and LineInfo to absolute paths tree-wide and could churn AOT hashes, so identity-only comparison looks like the safer shape.
🤖 Generated with Claude Code
Repro (on master, from the repo root)
The same command with an absolute path to
peg.dascompiles fine. Same file, two spellings.Mechanism
Require resolution inherits the parent file's spelled name instead of canonicalizing, and the module registry compares files by spelled path string:
daslang modules/dasPEG/peg/peg.das— the root file registers under the relative string as typed.peg.das:10require peg/parse_macro— not a same-dir file, resolves through the module search path (das_root scan) → absolute<root>/modules/dasPEG/peg/parse_macro.das.parse_macro.das:19require detail/colors— same-dir relative to parse_macro's spelled name → registerscolors.dasunder the absolute path.peg.das:11require detail/colors— same-dir relative to peg.das's spelled name → resolves the same file under the relative path.Two "distinct" files now both claim module name
detail/colors→ 20605.General shape: any root file compiled by a relative path whose same-directory sibling is also reachable through a mount-routed require chain trips this.
How it surfaced
The CI changed-files lint gate feeds
git diffrelative paths; the momentpeg.dasentered a PR's changed set the gate went red (#3420). Worked around at the tool boundary —utils/lint/main.dasnow absolutizes its positional inputs (2f52e76) — but the compiler bug remains for any embedder/CLI passing relative paths.Fix direction
Canonicalize for module identity (a
normalizeFileNameat require-resolution time, used only for the duplicate check / registry key). The blunt alternative — canonicalizing every registered name — would flip error messages and LineInfo to absolute paths tree-wide and could churn AOT hashes, so identity-only comparison looks like the safer shape.🤖 Generated with Claude Code