mirror of
https://github.com/nim-lang/Nim.git
synced 2026-06-14 15:43:45 +00:00
IC: fix fwd-decl dup, import-except deps, and field interface leak
- ast2nif.canonicalRoutine: collapse a forward-decl's discarded impl sym onto the surviving proto so it is not serialized twice (was an ambiguous overload in importers; fixes tnewlit). - deps.nim: handle `import m except syms` (importexcept) in the dependency scanner so the build-order edge is not dropped (fixes strformat->strutils ordering). - ast2nif.writeSymDef: object fields (skField) are no longer marked bare-importable (x) in the NIF index; an exported field name leaked into importer scope and shadowed a template's open symbol (type mismatch 'T'). Together these fix tmacro8. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -314,7 +314,16 @@ proc writeLib(w: var Writer; dest: var TokenBuf; lib: PLib) =
|
||||
proc writeSymDef(w: var Writer; dest: var TokenBuf; sym: PSym) =
|
||||
dest.addParLe sdefTag, trLineInfo(w, sym.infoImpl)
|
||||
dest.addSymDef pool.syms.getOrIncl(w.toNifSymName(sym)), NoLineInfo
|
||||
if {sfExported, sfFromGeneric} * sym.flagsImpl == {sfExported}:
|
||||
# The `x` marker means "importable as a bare identifier into an importer's
|
||||
# scope". Object fields carry `sfExported` (so they are visible via `obj.field`
|
||||
# across modules) but must NOT become bare-importable: otherwise an exported
|
||||
# field name (e.g. `HSlice.a`, whose type is a generic param `T`) leaks into
|
||||
# module scope and a template's open/mixin symbol of the same name resolves to
|
||||
# the field instead of a local, producing "type mismatch: got 'T'". Fields are
|
||||
# still indexed (for `obj.field` resolution via the loaded object type); they
|
||||
# are merely not advertised as importable. `skEnumField` stays importable —
|
||||
# enum values are legitimately usable as bare identifiers.
|
||||
if sym.kindImpl != skField and {sfExported, sfFromGeneric} * sym.flagsImpl == {sfExported}:
|
||||
dest.addIdent "x"
|
||||
else:
|
||||
dest.addDotToken
|
||||
@@ -379,7 +388,29 @@ proc shouldWriteSymDef(w: var Writer; sym: PSym): bool {.inline.} =
|
||||
return true # Normal case for global symbols
|
||||
return false
|
||||
|
||||
proc canonicalRoutine(sym: PSym): PSym {.inline.} =
|
||||
## A forward declaration and its implementation are merged in sem into a single
|
||||
## surviving symbol (the prototype). The discarded impl symbol is orphaned but
|
||||
## can still be reachable from the surviving routine's AST — e.g. as the `owner`
|
||||
## of a `[T: tuple]`-style generic-param constraint type created while the impl
|
||||
## header was being processed. If we serialized that dead symbol it would get
|
||||
## its own module-global sdef, be indexed, and the importer would then see two
|
||||
## identical overloads -> "ambiguous call". The dead symbol is recognisable
|
||||
## because its `ast` is the shared routine node whose name no longer points back
|
||||
## at it (it points at the survivor). Collapse to the survivor. Writer-only /
|
||||
## IC-specific: from-source compilation never serialises, so this cannot affect
|
||||
## the non-IC path.
|
||||
result = sym
|
||||
if sym != nil and sym.kindImpl in routineKinds:
|
||||
let a = sym.astImpl
|
||||
if a != nil and a.len > namePos and a[namePos].kind == nkSym:
|
||||
let canon = a[namePos].sym
|
||||
if canon != nil and canon != sym and canon.name.id == sym.name.id and
|
||||
canon.itemId.module == sym.itemId.module:
|
||||
result = canon
|
||||
|
||||
proc writeSym(w: var Writer; dest: var TokenBuf; sym: PSym) =
|
||||
let sym = canonicalRoutine(sym)
|
||||
if sym == nil:
|
||||
dest.addDotToken()
|
||||
elif shouldWriteSymDef(w, sym):
|
||||
|
||||
@@ -368,7 +368,7 @@ proc readDepsFile(c: var DepContext; pair: FilePair; current: Node) =
|
||||
if t.kind == ParLe:
|
||||
let tag = pool.tags[t.tagId]
|
||||
case tag
|
||||
of "import", "fromimport", "include":
|
||||
of "import", "fromimport", "importexcept", "include":
|
||||
# Read first child. May be a `(when COND...)` marker — parse and
|
||||
# evaluate; if the condition is statically false, skip the import
|
||||
# entirely. Otherwise advance past the marker and parse the path.
|
||||
@@ -395,7 +395,10 @@ proc readDepsFile(c: var DepContext; pair: FilePair; current: Node) =
|
||||
# that expand to several imports. A plain `import a, b, c` lists several
|
||||
# modules as siblings; a `fromimport` has a single path followed by the
|
||||
# imported symbol list, which must not be treated as modules.
|
||||
if tag == "fromimport":
|
||||
if tag == "fromimport" or tag == "importexcept":
|
||||
# `from m import syms` / `import m except syms`: the first child is the
|
||||
# module path; the rest is the (in/ex)cluded symbol list, which must not
|
||||
# be treated as modules. Both still create a real dependency on `m`.
|
||||
for importPath in parseImportPath(s, t):
|
||||
if importPath.len > 0:
|
||||
processImport(c, importPath, current)
|
||||
|
||||
Reference in New Issue
Block a user