mirror of
https://github.com/nim-lang/Nim.git
synced 2026-01-09 22:43:34 +00:00
progress
This commit is contained in:
@@ -46,6 +46,12 @@ template loadType(t: PType) =
|
||||
when not defined(nimKochBootstrap):
|
||||
ast2nif.loadType(program, t)
|
||||
|
||||
proc loadSymCallback*(s: PSym) {.nimcall.} =
|
||||
loadSym(s)
|
||||
|
||||
proc loadTypeCallback*(t: PType) {.nimcall.} =
|
||||
loadType(t)
|
||||
|
||||
proc ensureMutable*(s: PSym) {.inline.} =
|
||||
assert s.state != Sealed
|
||||
if s.state == Partial: loadSym(s)
|
||||
|
||||
@@ -667,7 +667,7 @@ proc buildExportBuf(w: var Writer): TokenBuf =
|
||||
proc translateOpsLog(w: var Writer; opsLog: seq[LogEntry]): IndexSections =
|
||||
result = IndexSections(hooks: default array[AttachedOp, seq[HookIndexEntry]], converters: @[], classes: @[])
|
||||
for entry in opsLog:
|
||||
let key = pool.syms.getOrIncl(entry.typ.typeKey(w.infos.config))
|
||||
let key = pool.syms.getOrIncl(entry.key)
|
||||
let sym = pool.syms.getOrIncl(w.toNifSymName(entry.sym))
|
||||
case entry.kind
|
||||
of HookEntry:
|
||||
@@ -1393,24 +1393,6 @@ proc toNifIndexFilename*(conf: ConfigRef; f: FileIndex): string =
|
||||
let suffix = moduleSuffix(conf, f)
|
||||
result = toGeneratedFile(conf, AbsoluteFile(suffix), ".s.idx.nif").string
|
||||
|
||||
proc parseTypeSymIdToItemId*(c: var DecodeContext; symId: nifstreams.SymId): ItemId =
|
||||
## Parses a type SymId (format: `"`tN.modulesuffix"`) to extract ItemId.
|
||||
let s = pool.syms[symId]
|
||||
if not s.startsWith("`t"):
|
||||
return ItemId(module: -1, item: 0)
|
||||
var i = 2 # skip "`t"
|
||||
var item = 0'i32
|
||||
while i < s.len and s[i] in {'0'..'9'}:
|
||||
item = item * 10 + int32(ord(s[i]) - ord('0'))
|
||||
inc i
|
||||
if i < s.len and s[i] == '.':
|
||||
inc i
|
||||
let suffix = s.substr(i)
|
||||
let module = moduleId(c, suffix)
|
||||
result = ItemId(module: int32(module), item: item)
|
||||
else:
|
||||
result = ItemId(module: -1, item: item)
|
||||
|
||||
proc resolveSym(c: var DecodeContext; symAsStr: string; alsoConsiderPrivate: bool): PSym =
|
||||
result = c.syms.getOrDefault(symAsStr)[0]
|
||||
if result != nil:
|
||||
@@ -1463,9 +1445,22 @@ proc loadNifModule*(c: var DecodeContext; f: FileIndex; interf, interfHidden: va
|
||||
populateInterfaceTablesFromIndex(c, module, interf, interfHidden, suffix)
|
||||
|
||||
# Return hooks from the index
|
||||
hooks = move c.mods[module].index.hooks
|
||||
let hooks = move c.mods[module].index.hooks
|
||||
for typeKey, h in hooks:
|
||||
for op in AttachedOp:
|
||||
let (hookSymId, isGeneric) = h.a[op]
|
||||
let hookSym = resolveHookSym(c, hookSymId)
|
||||
if hookSym != nil:
|
||||
logOps.add LogEntry(kind: HookEntry, op: toTTypeAttachedOp(op),
|
||||
isGeneric: isGeneric, key: pool.syms[typeKey], sym: hookSym)
|
||||
# Return converters from the index
|
||||
converters = move c.mods[module].index.converters
|
||||
let converters = move c.mods[module].index.converters
|
||||
for (destType, convSym) in converters:
|
||||
let symId = pool.syms.getOrIncl(convSym)
|
||||
let convPSym = resolveHookSym(c, symId) # reuse hook resolution
|
||||
if convPSym != nil:
|
||||
logOps.add LogEntry(kind: ConverterEntry,
|
||||
isGeneric: false, key: destType, sym: convPSym)
|
||||
# Return classes/methods from the index
|
||||
classes = move c.mods[module].index.classes
|
||||
|
||||
|
||||
@@ -1001,7 +1001,7 @@ type
|
||||
kind*: LogEntryKind
|
||||
op*: TTypeAttachedOp
|
||||
isGeneric*: bool
|
||||
typ*: PType
|
||||
key*: string
|
||||
sym*: PSym
|
||||
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ when not defined(nimKochBootstrap):
|
||||
import ast2nif
|
||||
import "../dist/nimony/src/lib" / [nifstreams, bitabs]
|
||||
|
||||
import typekeys
|
||||
|
||||
when defined(nimPreviewSlimSystem):
|
||||
import std/assertions
|
||||
|
||||
@@ -79,6 +81,7 @@ type
|
||||
typeInstCache*: Table[ItemId, seq[LazyType]] # A symbol's ItemId.
|
||||
procInstCache*: Table[ItemId, seq[LazyInstantiation]] # A symbol's ItemId.
|
||||
attachedOps*: array[TTypeAttachedOp, Table[ItemId, LazySym]] # Type ID, destructors, etc.
|
||||
loadedOps: array[TTypeAttachedOp, Table[string, PSym]] # This can later by unified with `attachedOps` once it's stable
|
||||
opsLog*: seq[LogEntry]
|
||||
methodsPerGenericType*: Table[ItemId, seq[(int, LazySym)]] # Type ID, attached methods
|
||||
memberProcsPerType*: Table[ItemId, seq[PSym]] # Type ID, attached member procs (only c++, virtual,member and ctor so far).
|
||||
@@ -368,7 +371,8 @@ proc getAttachedOp*(g: ModuleGraph; t: PType; op: TTypeAttachedOp): PSym =
|
||||
proc setAttachedOp*(g: ModuleGraph; module: int; t: PType; op: TTypeAttachedOp; value: PSym) =
|
||||
## we also need to record this to the packed module.
|
||||
if not g.attachedOps[op].contains(t.itemId):
|
||||
g.opsLog.add LogEntry(kind: HookEntry, op: op, typ: t, sym: value)
|
||||
let key = typeKey(t, g.config, loadTypeCallback, loadSymCallback)
|
||||
g.opsLog.add LogEntry(kind: HookEntry, op: op, key: key, sym: value)
|
||||
g.attachedOps[op][t.itemId] = LazySym(sym: value)
|
||||
|
||||
proc setAttachedOp*(g: ModuleGraph; module: int; typeId: ItemId; op: TTypeAttachedOp; value: PSym) =
|
||||
@@ -417,7 +421,8 @@ proc getToStringProc*(g: ModuleGraph; t: PType): PSym =
|
||||
|
||||
proc setToStringProc*(g: ModuleGraph; t: PType; value: PSym) =
|
||||
g.enumToStringProcs[t.itemId] = LazySym(sym: value)
|
||||
g.opsLog.add LogEntry(kind: EnumToStrEntry, typ: t, sym: value)
|
||||
let key = typeKey(t, g.config, loadTypeCallback, loadSymCallback)
|
||||
g.opsLog.add LogEntry(kind: EnumToStrEntry, key: key, sym: value)
|
||||
|
||||
iterator methodsForGeneric*(g: ModuleGraph; t: PType): (int, PSym) =
|
||||
if g.methodsPerGenericType.contains(t.itemId):
|
||||
@@ -426,7 +431,8 @@ iterator methodsForGeneric*(g: ModuleGraph; t: PType): (int, PSym) =
|
||||
|
||||
proc addMethodToGeneric*(g: ModuleGraph; module: int; t: PType; col: int; m: PSym) =
|
||||
g.methodsPerGenericType.mgetOrPut(t.itemId, @[]).add (col, LazySym(sym: m))
|
||||
g.opsLog.add LogEntry(kind: MethodEntry, typ: t, sym: m)
|
||||
let key = typeKey(t, g.config, loadTypeCallback, loadSymCallback)
|
||||
g.opsLog.add LogEntry(kind: MethodEntry, key: key, sym: m)
|
||||
|
||||
proc hasDisabledAsgn*(g: ModuleGraph; t: PType): bool =
|
||||
let op = getAttachedOp(g, t, attachedAsgn)
|
||||
@@ -809,38 +815,23 @@ when not defined(nimKochBootstrap):
|
||||
|
||||
# Register module in graph
|
||||
registerModule(g, result)
|
||||
var hooks = initTable[nifstreams.SymId, HooksPerType]()
|
||||
var converters: seq[(string, string)] = @[]
|
||||
var opsLog: seq[LogEntry] = @[]
|
||||
var classes: seq[ClassIndexEntry] = @[]
|
||||
result.astImpl = loadNifModule(ast.program, fileIdx, g.ifaces[fileIdx.int].interf,
|
||||
g.ifaces[fileIdx.int].interfHidden, hooks, converters, classes, loadFullAst)
|
||||
g.ifaces[fileIdx.int].interfHidden, opsLog, classes, loadFullAst)
|
||||
# Register hooks from NIF index with the module graph
|
||||
for typSymId, hooksPerType in hooks:
|
||||
let typeItemId = parseTypeSymIdToItemId(ast.program, typSymId)
|
||||
if typeItemId.module >= 0:
|
||||
for op in AttachedOp:
|
||||
let (hookSymId, isGeneric) = hooksPerType.a[op]
|
||||
if hookSymId != nifstreams.SymId(0):
|
||||
let hookSym = resolveHookSym(ast.program, hookSymId)
|
||||
if hookSym != nil:
|
||||
setAttachedOp(g, int(fileIdx), typeItemId, toTTypeAttachedOp(op), hookSym)
|
||||
# Register converters from NIF index with the module's interface
|
||||
for (destType, convSym) in converters:
|
||||
let symId = pool.syms.getOrIncl(convSym)
|
||||
let convPSym = resolveHookSym(ast.program, symId) # reuse hook resolution
|
||||
if convPSym != nil:
|
||||
g.ifaces[fileIdx.int].converters.add LazySym(sym: convPSym)
|
||||
for x in opsLog:
|
||||
case x.kind
|
||||
of HookEntry:
|
||||
g.loadedOps[x.op][x.key] = x.sym
|
||||
of ConverterEntry:
|
||||
g.ifaces[fileIdx.int].converters.add LazySym(sym: x.sym)
|
||||
of MethodEntry:
|
||||
discard "todo"
|
||||
of EnumToStrEntry:
|
||||
discard "todo"
|
||||
# Register methods per type from NIF index
|
||||
for classEntry in classes:
|
||||
let typeItemId = parseTypeSymIdToItemId(ast.program, classEntry.cls)
|
||||
if typeItemId.module >= 0:
|
||||
var methodSyms: seq[LazySym] = @[]
|
||||
for methodEntry in classEntry.methods:
|
||||
let methodSym = resolveHookSym(ast.program, methodEntry.fn)
|
||||
if methodSym != nil:
|
||||
methodSyms.add LazySym(sym: methodSym)
|
||||
if methodSyms.len > 0:
|
||||
setMethodsPerType(g, typeItemId, methodSyms)
|
||||
discard "todo"
|
||||
cachedModules.add fileIdx
|
||||
|
||||
proc configComplete*(g: ModuleGraph) =
|
||||
|
||||
@@ -48,10 +48,20 @@ type
|
||||
CoDistinct
|
||||
CoHashTypeInsideNode
|
||||
|
||||
proc typeKey(c: var Mangler, t: PType; flags: set[ConsiderFlag]; conf: ConfigRef)
|
||||
proc symKey(c: var Mangler, s: PSym; conf: ConfigRef) =
|
||||
TypeLoader* = proc (t: PType) {.nimcall.}
|
||||
SymLoader* = proc (s: PSym) {.nimcall.}
|
||||
Context = object
|
||||
m: Mangler
|
||||
tl: TypeLoader
|
||||
sl: SymLoader
|
||||
|
||||
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.addIdent("´anon")
|
||||
c.m.addIdent("´anon")
|
||||
else:
|
||||
var name = s.name.s
|
||||
name.add '.'
|
||||
@@ -67,32 +77,32 @@ proc symKey(c: var Mangler, s: PSym; conf: ConfigRef) =
|
||||
if it.kindImpl == skModule:
|
||||
name.add '.'
|
||||
name.add modname(it, conf)
|
||||
c.addSymbol(name)
|
||||
c.m.addSymbol(name)
|
||||
|
||||
proc treeKey(c: var Mangler, n: PNode; flags: set[ConsiderFlag]; conf: ConfigRef) =
|
||||
proc treeKey(c: var Context; n: PNode; flags: set[ConsiderFlag]; conf: ConfigRef) =
|
||||
if n == nil:
|
||||
c.addEmpty()
|
||||
c.m.addEmpty()
|
||||
return
|
||||
|
||||
let k = n.kind
|
||||
case k
|
||||
of nkEmpty, nkNilLit, nkType: discard
|
||||
of nkIdent:
|
||||
c.addIdent(n.ident.s)
|
||||
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.addIntLit v
|
||||
c.m.addIntLit v
|
||||
of nkFloatLit..nkFloat64Lit:
|
||||
let v = n.floatVal
|
||||
c.addFloatLit v
|
||||
c.m.addFloatLit v
|
||||
of nkStrLit..nkTripleStrLit:
|
||||
c.addStrLit n.strVal
|
||||
c.m.addStrLit n.strVal
|
||||
else:
|
||||
withTree c, toNifTag(k):
|
||||
withTree c.m, toNifTag(k):
|
||||
for i in 0..<n.len: treeKey(c, n[i], flags, conf)
|
||||
|
||||
proc skipModifierB(n: PType): PType {.inline.} =
|
||||
@@ -110,15 +120,19 @@ proc skipGenericAlias(t: PType): PType =
|
||||
if result.isGenericAlias:
|
||||
result = result.skipModifierB.skipTypesB({tyAlias})
|
||||
|
||||
proc maybeImported(c: var Mangler, s: PSym; conf: ConfigRef) {.inline.} =
|
||||
proc maybeImported(c: var Context; s: PSym; conf: ConfigRef) {.inline.} =
|
||||
if s != nil and {sfImportc, sfExportc} * s.flagsImpl != {}:
|
||||
c.symKey(s, conf)
|
||||
|
||||
proc typeKey(c: var Mangler, t: PType; flags: set[ConsiderFlag]; conf: ConfigRef) =
|
||||
proc typeKey(c: var Context; t: PType; flags: set[ConsiderFlag]; conf: ConfigRef) =
|
||||
if t == nil:
|
||||
c.addEmpty()
|
||||
c.m.addEmpty()
|
||||
return
|
||||
|
||||
if t.state == Partial:
|
||||
assert c.tl != nil
|
||||
c.tl(t)
|
||||
|
||||
case t.kind
|
||||
of tyGenericInvocation:
|
||||
for a in t.sonsImpl:
|
||||
@@ -147,56 +161,56 @@ proc typeKey(c: var Mangler, t: PType; flags: set[ConsiderFlag]; conf: ConfigRef
|
||||
c.typeKey t.skipModifierB, flags, conf
|
||||
of tyOwned:
|
||||
if CoConsiderOwned in flags:
|
||||
withTree c, toNifTag(t.kind):
|
||||
withTree c.m, toNifTag(t.kind):
|
||||
c.typeKey t.skipModifierB, flags, conf
|
||||
else:
|
||||
c.typeKey t.skipModifierB, flags, conf
|
||||
of tyBool:
|
||||
withTree c, "bool":
|
||||
withTree c.m, "bool":
|
||||
maybeImported(c, t.symImpl, conf)
|
||||
of tyChar:
|
||||
withTree c, "c":
|
||||
c.addIntLit 8 # char is always 8 bits
|
||||
withTree c.m, "c":
|
||||
c.m.addIntLit 8 # char is always 8 bits
|
||||
maybeImported(c, t.symImpl, conf)
|
||||
of tyInt:
|
||||
withTree c, "i":
|
||||
c.addIntLit -1
|
||||
withTree c.m, "i":
|
||||
c.m.addIntLit -1
|
||||
maybeImported(c, t.symImpl, conf)
|
||||
of tyInt8:
|
||||
withTree c, "i":
|
||||
c.addIntLit 8
|
||||
withTree c.m, "i":
|
||||
c.m.addIntLit 8
|
||||
maybeImported(c, t.symImpl, conf)
|
||||
of tyInt16:
|
||||
withTree c, "i":
|
||||
c.addIntLit 16
|
||||
withTree c.m, "i":
|
||||
c.m.addIntLit 16
|
||||
maybeImported(c, t.symImpl, conf)
|
||||
of tyInt32:
|
||||
withTree c, "i":
|
||||
c.addIntLit 32
|
||||
withTree c.m, "i":
|
||||
c.m.addIntLit 32
|
||||
maybeImported(c, t.symImpl, conf)
|
||||
of tyInt64:
|
||||
withTree c, "i":
|
||||
c.addIntLit 64
|
||||
withTree c.m, "i":
|
||||
c.m.addIntLit 64
|
||||
maybeImported(c, t.symImpl, conf)
|
||||
of tyUInt:
|
||||
withTree c, "u":
|
||||
c.addIntLit -1
|
||||
withTree c.m, "u":
|
||||
c.m.addIntLit -1
|
||||
maybeImported(c, t.symImpl, conf)
|
||||
of tyUInt8:
|
||||
withTree c, "u":
|
||||
c.addIntLit 8
|
||||
withTree c.m, "u":
|
||||
c.m.addIntLit 8
|
||||
maybeImported(c, t.symImpl, conf)
|
||||
of tyUInt16:
|
||||
withTree c, "u":
|
||||
c.addIntLit 16
|
||||
withTree c.m, "u":
|
||||
c.m.addIntLit 16
|
||||
maybeImported(c, t.symImpl, conf)
|
||||
of tyUInt32:
|
||||
withTree c, "u":
|
||||
c.addIntLit 32
|
||||
withTree c.m, "u":
|
||||
c.m.addIntLit 32
|
||||
maybeImported(c, t.symImpl, conf)
|
||||
of tyUInt64:
|
||||
withTree c, "u":
|
||||
c.addIntLit 64
|
||||
withTree c.m, "u":
|
||||
c.m.addIntLit 64
|
||||
maybeImported(c, t.symImpl, conf)
|
||||
of tyObject, tyEnum:
|
||||
if t.typeInstImpl != nil:
|
||||
@@ -211,15 +225,15 @@ proc typeKey(c: var Mangler, t: PType; flags: set[ConsiderFlag]; conf: ConfigRef
|
||||
elif t.symImpl != nil:
|
||||
c.symKey(t.symImpl, conf)
|
||||
else:
|
||||
c.addIdent "`bug"
|
||||
c.m.addIdent "`bug"
|
||||
of tyFromExpr:
|
||||
withTree c, toNifTag(t.kind):
|
||||
withTree c.m, toNifTag(t.kind):
|
||||
c.treeKey(t.nImpl, flags, conf)
|
||||
of tyTuple:
|
||||
withTree c, toNifTag(t.kind):
|
||||
withTree c.m, toNifTag(t.kind):
|
||||
if t.nImpl != nil and CoType notin flags:
|
||||
for i in 0..<t.nImpl.len:
|
||||
withTree c, "kv":
|
||||
withTree c.m, "kv":
|
||||
assert(t.nImpl[i].kind == nkSym)
|
||||
c.symKey(t.nImpl[i].sym, conf)
|
||||
c.typeKey(t.nImpl[i].sym.typImpl, flags+{CoIgnoreRange}, conf)
|
||||
@@ -228,17 +242,17 @@ proc typeKey(c: var Mangler, t: PType; flags: set[ConsiderFlag]; conf: ConfigRef
|
||||
c.typeKey t.sonsImpl[i], flags+{CoIgnoreRange}, conf
|
||||
of tyRange:
|
||||
if CoIgnoreRange notin flags:
|
||||
withTree c, toNifTag(t.kind):
|
||||
withTree c.m, toNifTag(t.kind):
|
||||
c.treeKey(t.nImpl, {}, conf)
|
||||
c.typeKey(t.sonsImpl[^1], flags, conf)
|
||||
else:
|
||||
c.typeKey(t.sonsImpl[^1], flags, conf)
|
||||
of tyStatic:
|
||||
withTree c, toNifTag(t.kind):
|
||||
withTree c.m, toNifTag(t.kind):
|
||||
c.treeKey(t.nImpl, {}, conf)
|
||||
c.typeKey(t.skipModifierB, flags, conf)
|
||||
of tyProc:
|
||||
withTree c, (if tfIterator in t.flagsImpl: "itertype" else: "proctype"):
|
||||
withTree c.m, (if tfIterator in t.flagsImpl: "itertype" else: "proctype"):
|
||||
if CoProc in flags and t.nImpl != nil:
|
||||
let params = t.nImpl
|
||||
for i in 1..<params.len:
|
||||
@@ -251,19 +265,20 @@ proc typeKey(c: var Mangler, t: PType; flags: set[ConsiderFlag]; conf: ConfigRef
|
||||
if t.sonsImpl.len > 0:
|
||||
c.typeKey(t.sonsImpl[0], flags, conf)
|
||||
|
||||
c.addIdent toNifTag(t.callConvImpl)
|
||||
if tfVarargs in t.flagsImpl: c.addIdent "´varargs"
|
||||
c.m.addIdent toNifTag(t.callConvImpl)
|
||||
if tfVarargs in t.flagsImpl: c.m.addIdent "´varargs"
|
||||
of tyArray:
|
||||
withTree c, toNifTag(t.kind):
|
||||
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, toNifTag(t.kind):
|
||||
withTree c.m, toNifTag(t.kind):
|
||||
for i in 1..<t.sonsImpl.len:
|
||||
c.typeKey t.sonsImpl[i], flags, conf
|
||||
if tfNotNil in t.flagsImpl and CoType notin flags: c.addIdent "´notnil"
|
||||
if tfNotNil in t.flagsImpl and CoType notin flags:
|
||||
c.m.addIdent "´notnil"
|
||||
|
||||
proc typeKey*(t: PType; conf: ConfigRef): string =
|
||||
var c = createMangler(30, -1)
|
||||
proc typeKey*(t: PType; conf: ConfigRef; tl: TypeLoader; sl: SymLoader): string =
|
||||
var c: Context = Context(m: createMangler(30, -1), tl: tl, sl: sl)
|
||||
typeKey(c, t, {}, conf)
|
||||
result = c.extract()
|
||||
result = c.m.extract()
|
||||
|
||||
@@ -390,15 +390,15 @@ else:
|
||||
## Generic `destructor`:idx: implementation that can be overridden.
|
||||
discard
|
||||
|
||||
when defined(nimAllowNonVarDestructor) and arcLikeMem:
|
||||
proc `=destroy`*(x: string) {.inline, magic: "Destroy", enforceNoRaises.} =
|
||||
discard
|
||||
when defined(nimAllowNonVarDestructor) and arcLikeMem:
|
||||
proc `=destroy`*(x: string) {.inline, magic: "Destroy", enforceNoRaises.} =
|
||||
discard
|
||||
|
||||
proc `=destroy`*[T](x: seq[T]) {.inline, magic: "Destroy".} =
|
||||
discard
|
||||
proc `=destroy`*[T](x: seq[T]) {.inline, magic: "Destroy".} =
|
||||
discard
|
||||
|
||||
proc `=destroy`*[T](x: ref T) {.inline, magic: "Destroy".} =
|
||||
discard
|
||||
proc `=destroy`*[T](x: ref T) {.inline, magic: "Destroy".} =
|
||||
discard
|
||||
|
||||
when defined(nimHasDup):
|
||||
proc `=dup`*[T](x: T): T {.inline, magic: "Dup".} =
|
||||
|
||||
Reference in New Issue
Block a user