From b993379c941c7584594583fa31ace0a3462b0d20 Mon Sep 17 00:00:00 2001 From: Araq Date: Thu, 25 Jun 2026 17:00:49 +0200 Subject: [PATCH] progress --- compiler/cgen.nim | 22 +++++++++++++++++++++- compiler/cgmeth.nim | 12 +++++++++++- compiler/sempass2.nim | 22 +++++++++++++++++++++- 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/compiler/cgen.nim b/compiler/cgen.nim index 4cf77d12a4..4dcefe12a2 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -139,11 +139,31 @@ proc emitsBodyInThisModule(m: BModule, prc: PSym): bool = ## prototype and its body lands in no TU → undefined at link. if not (m.config.cmd == cmdNifC and m.config.icBackendStage == "cg"): return true + # The symbol may ITSELF be content-addressed (a synthesized hook or a generic + # instance carries `Hook/InstanceDisambBit` on its OWN `disamb`): then it has no + # single owning module and every demander emits it (merge dedups by C name), + # regardless of what it is nested under. This must be checked on `prc` directly, + # not on `top`: a `=destroy`/`=sink` lifted while compiling some enclosing proc + # (e.g. system's `isZeroMemory` destroying a `ptr array`) has that PROC as its + # `skipGenericOwner`, so `top` walks up to a plain routine whose own disamb has + # no bit — gating the hook to that routine's owner module, which mints it + # on demand and emits it nowhere → undefined at link. + if (prc.disamb and (InstanceDisambBit or HookDisambBit)) != 0'i32: + return true var top = prc while top.skipGenericOwner != nil and top.skipGenericOwner.kind != skModule: top = top.skipGenericOwner result = top.itemId.module == m.module.position or - (top.disamb and (InstanceDisambBit or HookDisambBit)) != 0'i32 + (top.disamb and (InstanceDisambBit or HookDisambBit)) != 0'i32 or + # An INLINE iterator has no standalone body — it is expanded at each + # call site — so it is materialized in every module that iterates over + # it, never in its owner. A proc nested in one (e.g. std/uri's + # `parseData` inside `iterator decodeQuery`) is lambda-lifted into each + # of those consumer TUs and must be emitted there (its stable + # owner-suffixed name + `'u'` flag let the merge stage keep one); gating + # it to the iterator's owner module leaves it in no TU → undefined. + (top.kind == skIterator and top.typ != nil and + top.typ.callConv != ccClosure) proc initLoc(k: TLocKind, lode: PNode, s: TStorageLoc, flags: TLocFlags = {}): TLoc = result = TLoc(k: k, storage: s, lode: lode, diff --git a/compiler/cgmeth.nim b/compiler/cgmeth.nim index a646b04746..e106a674b6 100644 --- a/compiler/cgmeth.nim +++ b/compiler/cgmeth.nim @@ -160,7 +160,17 @@ proc fixupDispatcher(meth, disp: PSym; conf: ConfigRef) = proc methodDef*(g: ModuleGraph; idgen: IdGenerator; s: PSym) = var witness: PSym = nil if s.typ.firstParamType.owner.getModule != s.getModule and vtables in g.config.features and not - g.config.isDefined("nimInternalNonVtablesTesting"): + g.config.isDefined("nimInternalNonVtablesTesting") and sfFromGeneric notin s.flags: + # `sfFromGeneric` excepted: this is the same-module restriction for vtable + # slot placement, and it must be judged on the GENERIC method, not on an + # instance. The generic `method skip[T](x: Input[T])` never reaches here + # (`semMethodPrototype` registers generic methods via `addMethodToGeneric`, + # bypassing `methodDef`); only its instance `skip[string]` does, and that + # instance's first-param type `Input[string]` is owned by whichever module + # first instantiated it (`tparsecombnum`, which `import parsecomb`s and uses + # it), NOT by `Input[T]`'s defining module — so the comparison spuriously + # fails for a method that is perfectly legal at the generic level. (Concrete + # methods, `sfFromGeneric notin flags`, are still checked.) localError(g.config, s.info, errGenerated, "method `" & s.name.s & "` can be defined only in the same module with its type (" & s.typ.firstParamType.typeToString() & ")") if sfImportc in s.flags: diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index f72548cf72..8c740f54bf 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -1135,7 +1135,27 @@ proc trackCall(tracked: PEffects; n: PNode) = # global `unameRes` → "cannot evaluate at compile time: unameRes". In the # normal nimscript run (emRepl) the module's var section runs first and the # slot exists, so the marking is both unnecessary and harmful here. - incl(tracked.owner, sfCompileTime) + # + # ...and NOT if the routine is — or is nested inside — a macro/template: + # those are VM-only (never code-generated), so the per-module IC backend has + # nothing to protect there, while `sfCompileTime` on a macro-internal nested + # closure breaks its captured-variable access in the VM ("cannot evaluate at + # compile time: n" — `tests/macros/tmacros1`'s `innerProc` reading the + # macro-local `n`). Walk the owner chain and bail on the first + # skMacro/skTemplate. NB mark `tracked.owner` (the routine that directly + # reaches the magic), NOT its outermost enclosing: a runtime proc may legally + # nest a compile-time helper — `tests/generics/tunique_type`'s `[]` proc + # contains a nested `buildResult` macro — and marking the proc would wrongly + # make IT compile-time ("request to generate code for .compileTime proc: []"). + var encl = tracked.owner + var insideMeta = false + while encl != nil and encl.kind != skModule: + if encl.kind in {skMacro, skTemplate}: + insideMeta = true + break + encl = encl.skipGenericOwner + if not insideMeta: + incl(tracked.owner, sfCompileTime) if n.typ != nil: if tracked.owner.kind != skMacro and n.typ.skipTypes(abstractVar).kind != tyOpenArray: createTypeBoundOps(tracked, n.typ, n.info)