# # # The Nim Compiler # (c) Copyright 2025 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. # ## Based on sighashes.nim but works on astdef directly as we need it in ast2nif.nim. ## Also produces more readable names thanks to treemangler. import std/[assertions, sets] import "../dist/nimony/src/lib" / [treemangler] import "../dist/nimony/src/gear2" / modnames import astdef, idents, options, lineinfos, msgs import ic / [enum2nif] # -------------- Module name handling -------------------------------------------- proc cachedModuleSuffix*(config: ConfigRef; fileIdx: FileIndex): string = ## Gets or computes the module suffix for a FileIndex. ## For NIF modules, the suffix is already stored in the file info. ## For source files, computes it from the path. let fullPath = toFullPath(config, fileIdx) if fileInfoKind(config, fileIdx) == fikNifModule: result = fullPath # Already a suffix else: result = moduleSuffix(fullPath, cast[seq[string]](config.searchPaths)) proc modname*(module: int; conf: ConfigRef): string = cachedModuleSuffix(conf, module.FileIndex) proc modname*(module: PSym; conf: ConfigRef): string = assert module.kindImpl == skModule modname(module.positionImpl, conf) # --------------- Type key generation -------------------------------------------- type ConsiderFlag = enum CoProc CoType CoIgnoreRange CoConsiderOwned CoDistinct CoHashTypeInsideNode TypeLoader* = proc (t: PType) {.nimcall.} SymLoader* = proc (s: PSym) {.nimcall.} Context = object m: Mangler tl: TypeLoader sl: SymLoader visited: HashSet[ItemId] # anonymous object types whose fields are currently # being hashed — a non-mutating guard against endless # recursion when a field references the type itself. proc typeKey(c: var Context; t: PType; flags: set[ConsiderFlag]; conf: ConfigRef) proc symKey(c: var Context; s: PSym; conf: ConfigRef) = if s.state == Partial: assert c.sl != nil c.sl(s) if sfAnon in s.flagsImpl or s.kindImpl == skGenericParam: c.m.addIdent("´anon") else: var name = s.name.s name.add '.' name.addInt s.disamb # The owner may still be an unloaded stub (kind `skStub`): force it in # before inspecting its kind, otherwise the module suffix is silently # dropped from the key and def-vs-use keys diverge — e.g. `Lexer`'s base # class keyed as `TBaseLexer.0.` at nifc vs `TBaseLexer.0.nimqydn3y` at # sem time, making `getAttachedOp` miss ("'=destroy' operator not found"). template forceLoaded(x: PSym): PSym = let tmp = x if tmp != nil and tmp.state == Partial and c.sl != nil: c.sl(tmp) tmp let owner = forceLoaded(s.ownerFieldImpl) let it = if s.kindImpl == skModule: s elif s.kindImpl in skProcKinds and sfFromGeneric in s.flagsImpl and owner != nil and owner.kindImpl != skModule: forceLoaded(owner.ownerFieldImpl) else: owner if it != nil and it.kindImpl == skModule: name.add '.' name.add modname(it, conf) c.m.addSymbol(name) proc treeKey(c: var Context; n: PNode; flags: set[ConsiderFlag]; conf: ConfigRef) = if n == nil: c.m.addEmpty() return let k = n.kind case k of nkEmpty, nkNilLit, nkType: discard of nkIdent: c.m.addIdent(n.ident.s) of nkSym: symKey(c, n.sym, conf) if CoHashTypeInsideNode in flags and n.sym.typImpl != nil: typeKey(c, n.sym.typImpl, flags, conf) of nkCharLit..nkUInt64Lit: let v = n.intVal c.m.addIntLit v of nkFloatLit..nkFloat64Lit: let v = n.floatVal c.m.addFloatLit v of nkStrLit..nkTripleStrLit: c.m.addStrLit n.strVal else: withTree c.m, toNifTag(k): for i in 0.. 0: # Guard against endless recursion when a field references this type # itself. Unlike sighashes (which temporarily clears `sfAnon`/`sfGenSym` # on the symbol), do NOT mutate: `typeKey` runs during sem — it is # called unconditionally from `modulegraphs.setAttachedOp` — so a # mutation that an assertion deeper in `treeKey` left unrestored would # corrupt the type. `symKey` above already emitted the type's identity, # so on a back-reference we simply stop. if not containsOrIncl(c.visited, t.itemId): c.treeKey(t.nImpl, flags + {CoHashTypeInsideNode}, conf) c.visited.excl t.itemId else: c.m.addIdent "´empty" # Object inheritance is part of identity: key the base class too. if t.kind == tyObject and t.sonsImpl.len > 0 and t.sonsImpl[0] != nil: c.typeKey t.sonsImpl[0], flags, conf else: c.m.addIdent "`bug" of tyFromExpr: withTree c.m, toNifTag(t.kind): c.treeKey(t.nImpl, flags, conf) of tyTuple: withTree c.m, toNifTag(t.kind): if t.nImpl != nil and CoType notin flags: for i in 0.. 0: c.typeKey(t.skipModifierB, flags, conf) of tyProc: withTree c.m, (if tfIterator in t.flagsImpl: "itertype" else: "proctype"): # Proc parameter *types* are part of the type's identity. Under IC the # parameters live in `nImpl` (`sonsImpl` holds only the return type), so a # loaded proc type has an empty `sonsImpl[1..]`; reading params from there # would silently drop them and collide every same-return/same-callconv # closure onto one key (e.g. `proc(cb: proc())` onto bare `proc()`), # which made hook lookup resolve to the wrong `=copy`. Prefer `nImpl` # (consistent in-memory and after load); hash param types only, not their # symbols — parameter names do not affect type identity. if t.nImpl != nil and t.nImpl.kind == nkFormalParams: let params = t.nImpl for i in 1.. 0: c.typeKey(t.sonsImpl[0], flags, conf) c.m.addIdent toNifTag(t.callConvImpl) if tfVarargs in t.flagsImpl: c.m.addIdent "´varargs" of tyArray: withTree c.m, toNifTag(t.kind): c.typeKey(t.sonsImpl[^1], flags-{CoIgnoreRange}, conf) c.typeKey(t.sonsImpl[0], flags-{CoIgnoreRange}, conf) else: withTree c.m, toNifTag(t.kind): for i in 0..