diff options
-rw-r--r-- | changelog.md | 11 | ||||
-rw-r--r-- | compiler/ast.nim | 9 | ||||
-rw-r--r-- | compiler/condsyms.nim | 1 | ||||
-rw-r--r-- | compiler/docgen.nim | 6 | ||||
-rw-r--r-- | compiler/ic/ic.nim | 4 | ||||
-rw-r--r-- | compiler/lookups.nim | 6 | ||||
-rw-r--r-- | compiler/nodekinds.nim | 1 | ||||
-rw-r--r-- | compiler/options.nim | 2 | ||||
-rw-r--r-- | compiler/renderer.nim | 4 | ||||
-rw-r--r-- | compiler/semexprs.nim | 60 | ||||
-rw-r--r-- | compiler/semgnrc.nim | 31 | ||||
-rw-r--r-- | compiler/semtypes.nim | 1 | ||||
-rw-r--r-- | doc/manual_experimental.md | 11 | ||||
-rw-r--r-- | lib/core/macros.nim | 4 | ||||
-rw-r--r-- | lib/pure/sugar.nim | 2 | ||||
-rw-r--r-- | tests/generics/mopensymimport1.nim | 34 | ||||
-rw-r--r-- | tests/generics/mopensymimport2.nim | 16 | ||||
-rw-r--r-- | tests/generics/tmacroinjectedsymwarning.nim | 5 | ||||
-rw-r--r-- | tests/generics/topensymimport.nim | 5 |
19 files changed, 166 insertions, 47 deletions
diff --git a/changelog.md b/changelog.md index 75b848ebd..5056d7b3d 100644 --- a/changelog.md +++ b/changelog.md @@ -78,7 +78,9 @@ is often an easy workaround. - An experimental option `genericsOpenSym` has been added to allow captured symbols in generic routine bodies to be replaced by symbols injected locally by templates/macros at instantiation time. `bind` may be used to keep the - captured symbols over the injected ones regardless of enabling the option. + captured symbols over the injected ones regardless of enabling the option, + but other methods like renaming the captured symbols should be used instead + so that the code is not affected by context changes. Since this change may affect runtime behavior, the experimental switch `genericsOpenSym` needs to be enabled, and a warning is given in the case @@ -110,6 +112,13 @@ is often an easy workaround. assert baz[int]() == "captured" ``` + This option also generates a new node kind `nnkOpenSym` which contains + exactly 1 of either an `nnkSym` or an `nnkOpenSymChoice` node. In the future + this might be merged with a slightly modified `nnkOpenSymChoice` node but + macros that want to support the experimental feature should still handle + `nnkOpenSym`, as the node kind would simply not be generated as opposed to + being removed. + ## Compiler changes - `--nimcache` using a relative path as the argument in a config file is now relative to the config file instead of the current directory. diff --git a/compiler/ast.nim b/compiler/ast.nim index 624bc32f9..648bc4392 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -325,9 +325,9 @@ type nfFirstWrite # this node is a first write nfHasComment # node has a comment nfSkipFieldChecking # node skips field visable checking - nfOpenSym # node is a captured sym but can be overriden by local symbols - # ideally a unary node containing nkSym/nkOpenSymChoice or an - # extension over nkOpenSymChoice + nfDisabledOpenSym # temporary: node should be nkOpenSym but cannot + # because genericsOpenSym experimental switch is disabled + # gives warning instead TNodeFlags* = set[TNodeFlag] TTypeFlag* = enum # keep below 32 for efficiency reasons (now: 47) @@ -882,7 +882,7 @@ const nfFromTemplate, nfDefaultRefsParam, nfExecuteOnReload, nfLastRead, nfFirstWrite, nfSkipFieldChecking, - nfOpenSym} + nfDisabledOpenSym} namePos* = 0 patternPos* = 1 # empty except for term rewriting macros genericParamsPos* = 2 @@ -924,6 +924,7 @@ proc getPIdent*(a: PNode): PIdent {.inline.} = of nkSym: a.sym.name of nkIdent: a.ident of nkOpenSymChoice, nkClosedSymChoice: a.sons[0].sym.name + of nkOpenSym: getPIdent(a.sons[0]) else: nil const diff --git a/compiler/condsyms.nim b/compiler/condsyms.nim index aba6de49d..3119e657e 100644 --- a/compiler/condsyms.nim +++ b/compiler/condsyms.nim @@ -166,4 +166,5 @@ proc initDefines*(symbols: StringTableRef) = defineSymbol("nimHasWarnStdPrefix") defineSymbol("nimHasVtables") + defineSymbol("nimHasGenericsOpenSym2") defineSymbol("nimHasJsNoLambdaLifting") diff --git a/compiler/docgen.nim b/compiler/docgen.nim index a6795be44..8e5f5e4e7 100644 --- a/compiler/docgen.nim +++ b/compiler/docgen.nim @@ -831,7 +831,7 @@ proc getName(n: PNode): string = result = "`" for i in 0..<n.len: result.add(getName(n[i])) result = "`" - of nkOpenSymChoice, nkClosedSymChoice: + of nkOpenSymChoice, nkClosedSymChoice, nkOpenSym: result = getName(n[0]) else: result = "" @@ -849,7 +849,7 @@ proc getNameIdent(cache: IdentCache; n: PNode): PIdent = var r = "" for i in 0..<n.len: r.add(getNameIdent(cache, n[i]).s) result = getIdent(cache, r) - of nkOpenSymChoice, nkClosedSymChoice: + of nkOpenSymChoice, nkClosedSymChoice, nkOpenSym: result = getNameIdent(cache, n[0]) else: result = nil @@ -863,7 +863,7 @@ proc getRstName(n: PNode): PRstNode = of nkAccQuoted: result = getRstName(n[0]) for i in 1..<n.len: result.text.add(getRstName(n[i]).text) - of nkOpenSymChoice, nkClosedSymChoice: + of nkOpenSymChoice, nkClosedSymChoice, nkOpenSym: result = getRstName(n[0]) else: result = nil diff --git a/compiler/ic/ic.nim b/compiler/ic/ic.nim index 7dcdb4f1e..8e81633ef 100644 --- a/compiler/ic/ic.nim +++ b/compiler/ic/ic.nim @@ -837,7 +837,7 @@ proc loadNodes*(c: var PackedDecoder; g: var PackedModuleGraph; thisModule: int; result.ident = getIdent(c.cache, g[thisModule].fromDisk.strings[n.litId]) of nkSym: result.sym = loadSym(c, g, thisModule, PackedItemId(module: LitId(0), item: tree[n].soperand)) - if result.typ == nil and nfOpenSym notin result.flags: + if result.typ == nil: result.typ = result.sym.typ of externIntLit: result.intVal = g[thisModule].fromDisk.numbers[n.litId] @@ -851,7 +851,7 @@ proc loadNodes*(c: var PackedDecoder; g: var PackedModuleGraph; thisModule: int; assert n2.kind == nkNone transitionNoneToSym(result) result.sym = loadSym(c, g, thisModule, PackedItemId(module: n1.litId, item: tree[n2].soperand)) - if result.typ == nil and nfOpenSym notin result.flags: + if result.typ == nil: result.typ = result.sym.typ else: for n0 in sonsReadonly(tree, n): diff --git a/compiler/lookups.nim b/compiler/lookups.nim index e6b4c8f9a..d0e114a18 100644 --- a/compiler/lookups.nim +++ b/compiler/lookups.nim @@ -63,6 +63,8 @@ proc considerQuotedIdent*(c: PContext; n: PNode, origin: PNode = nil): PIdent = result = n[0].sym.name else: handleError(n, origin) + of nkOpenSym: + result = considerQuotedIdent(c, n[0], origin) else: handleError(n, origin) @@ -701,6 +703,10 @@ proc qualifiedLookUp*(c: PContext, n: PNode, flags: set[TLookupFlag]): PSym = if result != nil and result.kind == skStub: loadStub(result) proc initOverloadIter*(o: var TOverloadIter, c: PContext, n: PNode): PSym = + if n.kind == nkOpenSym: + # maybe the logic in semexprs should be mirrored here instead + # for now it only seems this is called for `pickSym` in `getTypeIdent` + return initOverloadIter(o, c, n[0]) o.importIdx = -1 o.marked = initIntSet() case n.kind diff --git a/compiler/nodekinds.nim b/compiler/nodekinds.nim index 98ae9405d..ccdbbd26d 100644 --- a/compiler/nodekinds.nim +++ b/compiler/nodekinds.nim @@ -204,6 +204,7 @@ type nkModuleRef # for .rod file support: A (moduleId, itemId) pair nkReplayAction # for .rod file support: A replay action nkNilRodNode # for .rod file support: a 'nil' PNode + nkOpenSym # container for captured sym that can be overriden by local symbols const nkCallKinds* = {nkCall, nkInfix, nkPrefix, nkPostfix, diff --git a/compiler/options.nim b/compiler/options.nim index b442ac5b5..de412979f 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -226,7 +226,7 @@ type strictDefs, strictCaseObjects, inferGenericTypes, - genericsOpenSym, + genericsOpenSym, # remove nfDisabledOpenSym when this switch is default vtables LegacyFeature* = enum diff --git a/compiler/renderer.nim b/compiler/renderer.nim index 3a7c60953..19df22326 100644 --- a/compiler/renderer.nim +++ b/compiler/renderer.nim @@ -514,6 +514,7 @@ proc lsub(g: TSrcGen; n: PNode): int = result = if n.len > 0: lcomma(g, n) + 2 else: len("{:}") of nkClosedSymChoice, nkOpenSymChoice: if n.len > 0: result += lsub(g, n[0]) + of nkOpenSym: result = lsub(g, n[0]) of nkTupleTy: result = lcomma(g, n) + len("tuple[]") of nkTupleClassTy: result = len("tuple") of nkDotExpr: result = lsons(g, n) + 1 @@ -1013,7 +1014,7 @@ proc bracketKind*(g: TSrcGen, n: PNode): BracketKind = proc skipHiddenNodes(n: PNode): PNode = result = n while result != nil: - if result.kind in {nkHiddenStdConv, nkHiddenSubConv, nkHiddenCallConv} and result.len > 1: + if result.kind in {nkHiddenStdConv, nkHiddenSubConv, nkHiddenCallConv, nkOpenSym} and result.len > 1: result = result[1] elif result.kind in {nkCheckedFieldExpr, nkHiddenAddr, nkHiddenDeref, nkStringToCString, nkCStringToString} and result.len > 0: @@ -1275,6 +1276,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext, fromStmtList = false) = put(g, tkParRi, if n.kind == nkOpenSymChoice: "|...)" else: ")") else: gsub(g, n, 0) + of nkOpenSym: gsub(g, n, 0) of nkPar, nkClosure: put(g, tkParLe, "(") gcomma(g, n, c) diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 14d154ef4..d49f115ea 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -157,13 +157,14 @@ proc semSymChoice(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: P if result.kind == nkSym: result = semSym(c, result, result.sym, flags) -proc semOpenSym(c: PContext, n: PNode, s: PSym, flags: TExprFlags, expectedType: PType): PNode = - ## sem a node marked `nfOpenSym`, that is, captured symbols that can be +proc semOpenSym(c: PContext, n: PNode, flags: TExprFlags, expectedType: PType, + warnDisabled = false): PNode = + ## sem the child of an `nkOpenSym` node, that is, captured symbols that can be ## replaced by newly injected symbols in generics. `s` must be the captured ## symbol if the original node is an `nkSym` node; and `nil` if it is an ## `nkOpenSymChoice`, in which case only non-overloadable injected symbols ## will be considered. - result = nil + let isSym = n.kind == nkSym let ident = n.getPIdent assert ident != nil let id = newIdentNode(ident, n.info) @@ -176,36 +177,41 @@ proc semOpenSym(c: PContext, n: PNode, s: PSym, flags: TExprFlags, expectedType: # but of the overloadable sym kinds, semExpr does not handle skModule, skMacro, skTemplate # as overloaded in the case where `nkIdent` finds them first if s2 != nil and not c.isAmbiguous and - ((s == nil and s2.kind notin OverloadableSyms-{skModule, skMacro, skTemplate}) or - (s != nil and s2 != s)): + ((isSym and s2 != n.sym) or + (not isSym and s2.kind notin OverloadableSyms-{skModule, skMacro, skTemplate})): # only consider symbols defined under current proc: var o = s2.owner while o != nil: if o == c.p.owner: - if genericsOpenSym in c.features: + if not warnDisabled: result = semExpr(c, id, flags, expectedType) return else: var msg = "a new symbol '" & ident.s & "' has been injected during " & "instantiation of " & c.p.owner.name.s & ", however " - if s == nil: + if isSym: msg.add( - "overloads of " & ident.s & " will be used instead; " & + getSymRepr(c.config, n.sym) & " captured at " & + "the proc declaration will be used instead; " & "either enable --experimental:genericsOpenSym to use the " & - "injected symbol or `bind` this symbol explicitly") + "injected symbol or `bind` this captured symbol explicitly") else: msg.add( - getSymRepr(c.config, s) & " captured at " & - "the proc declaration will be used instead; " & + "overloads of " & ident.s & " will be used instead; " & "either enable --experimental:genericsOpenSym to use the " & - "injected symbol or `bind` this captured symbol explicitly") + "injected symbol or `bind` this symbol explicitly") message(c.config, n.info, warnGenericsIgnoredInjection, msg) break o = o.owner - if s == nil: - # set symchoice node type back to None - n.typ = newTypeS(tyNone, c) + # nothing found + if not warnDisabled: + result = semExpr(c, n, flags, expectedType) + else: + result = nil + if not isSym: + # set symchoice node type back to None + n.typ = newTypeS(tyNone, c) proc inlineConst(c: PContext, n: PNode, s: PSym): PNode {.inline.} = result = copyTree(s.astdef) @@ -2191,6 +2197,8 @@ proc lookUpForDeclared(c: PContext, n: PNode, onlyCurrentScope: bool): PSym = result = n.sym of nkOpenSymChoice, nkClosedSymChoice: result = n[0].sym + of nkOpenSym: + result = lookUpForDeclared(c, n[0], onlyCurrentScope) else: localError(c.config, n.info, "identifier expected, but got: " & renderTree(n)) result = nil @@ -2643,7 +2651,9 @@ proc semWhen(c: PContext, n: PNode, semCheck = true): PNode = var typ = commonTypeBegin if n.len in 1..2 and n[0].kind == nkElifBranch and ( n.len == 1 or n[1].kind == nkElse): - let exprNode = n[0][0] + var exprNode = n[0][0] + if exprNode.kind == nkOpenSym: + exprNode = exprNode[0] if exprNode.kind == nkIdent: whenNimvm = lookUp(c, exprNode).magic == mNimvm elif exprNode.kind == nkSym: @@ -3192,20 +3202,22 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType if isSymChoice(result): result = semSymChoice(c, result, flags, expectedType) of nkClosedSymChoice, nkOpenSymChoice: - if n.kind == nkOpenSymChoice and nfOpenSym in n.flags: - result = semOpenSym(c, n, nil, flags, expectedType) - if result != nil: - return + if nfDisabledOpenSym in n.flags: + let res = semOpenSym(c, n, flags, expectedType, warnDisabled = true) + assert res == nil result = semSymChoice(c, n, flags, expectedType) of nkSym: let s = n.sym - if nfOpenSym in n.flags: - result = semOpenSym(c, n, s, flags, expectedType) - if result != nil: - return + if nfDisabledOpenSym in n.flags: + let res = semOpenSym(c, n, flags, expectedType, warnDisabled = true) + assert res == nil # because of the changed symbol binding, this does not mean that we # don't have to check the symbol for semantics here again! result = semSym(c, n, s, flags) + of nkOpenSym: + assert n.len == 1 + let inner = n[0] + result = semOpenSym(c, inner, flags, expectedType) of nkEmpty, nkNone, nkCommentStmt, nkType: discard of nkNilLit: diff --git a/compiler/semgnrc.nim b/compiler/semgnrc.nim index b5faac979..e2da56c5d 100644 --- a/compiler/semgnrc.nim +++ b/compiler/semgnrc.nim @@ -59,6 +59,9 @@ template isMixedIn(sym): bool = template canOpenSym(s): bool = {withinMixin, withinConcept} * flags == {withinMixin} and s.id notin ctx.toBind +proc newOpenSym*(n: PNode): PNode {.inline.} = + result = newTreeI(nkOpenSym, n.info, n) + proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym, ctx: var GenericCtx; flags: TSemGenericFlags, fromDotExpr=false): PNode = @@ -73,8 +76,11 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym, else: result = symChoice(c, n, s, scOpen) if canOpenSym(s): - result.flags.incl nfOpenSym - result.typ = nil + if genericsOpenSym in c.features: + result = newOpenSym(result) + else: + result.flags.incl nfDisabledOpenSym + result.typ = nil case s.kind of skUnknown: # Introduced in this pass! Leave it as an identifier. @@ -103,8 +109,11 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym, else: result = newSymNodeTypeDesc(s, c.idgen, n.info) if canOpenSym(result.sym): - result.flags.incl nfOpenSym - result.typ = nil + if genericsOpenSym in c.features: + result = newOpenSym(result) + else: + result.flags.incl nfDisabledOpenSym + result.typ = nil onUse(n.info, s) of skParam: result = n @@ -114,16 +123,22 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym, (s.typ.flags * {tfGenericTypeParam, tfImplicitTypeParam} == {}): result = newSymNodeTypeDesc(s, c.idgen, n.info) if canOpenSym(result.sym): - result.flags.incl nfOpenSym - result.typ = nil + if genericsOpenSym in c.features: + result = newOpenSym(result) + else: + result.flags.incl nfDisabledOpenSym + result.typ = nil else: result = n onUse(n.info, s) else: result = newSymNode(s, n.info) if canOpenSym(result.sym): - result.flags.incl nfOpenSym - result.typ = nil + if genericsOpenSym in c.features: + result = newOpenSym(result) + else: + result.flags.incl nfDisabledOpenSym + result.typ = nil onUse(n.info, s) proc lookup(c: PContext, n: PNode, flags: TSemGenericFlags, diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 8e9e43de8..299c4a1b6 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -2207,6 +2207,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = of nkType: result = n.typ of nkStmtListType: result = semStmtListType(c, n, prev) of nkBlockType: result = semBlockType(c, n, prev) + of nkOpenSym: result = semTypeNode(c, n[0], prev) else: result = semTypeExpr(c, n, prev) when false: diff --git a/doc/manual_experimental.md b/doc/manual_experimental.md index 9b52fbd2a..34399e6f9 100644 --- a/doc/manual_experimental.md +++ b/doc/manual_experimental.md @@ -2528,7 +2528,9 @@ Injected symbols in generic procs With the experimental option `genericsOpenSym`, captured symbols in generic routine bodies may be replaced by symbols injected locally by templates/macros at instantiation time. `bind` may be used to keep the captured symbols over -the injected ones regardless of enabling the option. +the injected ones regardless of enabling the option, but other methods like +renaming the captured symbols should be used instead so that the code is not +affected by context changes. Since this change may affect runtime behavior, the experimental switch `genericsOpenSym` needs to be enabled, and a warning is given in the case @@ -2560,6 +2562,13 @@ proc baz[T](): string = assert baz[int]() == "captured" ``` +This option also generates a new node kind `nnkOpenSym` which contains +exactly 1 of either an `nnkSym` or an `nnkOpenSymChoice` node. In the future +this might be merged with a slightly modified `nnkOpenSymChoice` node but +macros that want to support the experimental feature should still handle +`nnkOpenSym`, as the node kind would simply not be generated as opposed to +being removed. + VTable for methods ================== diff --git a/lib/core/macros.nim b/lib/core/macros.nim index d391bf761..7646b165c 100644 --- a/lib/core/macros.nim +++ b/lib/core/macros.nim @@ -93,6 +93,8 @@ type nnkFuncDef, nnkTupleConstr, nnkError, ## erroneous AST node + nnkModuleRef, nnkReplayAction, nnkNilRodNode ## internal IC nodes + nnkOpenSym NimNodeKinds* = set[NimNodeKind] NimTypeKind* = enum # some types are no longer used, see ast.nim @@ -1407,7 +1409,7 @@ proc `$`*(node: NimNode): string = result = node.basename.strVal & "*" of nnkStrLit..nnkTripleStrLit, nnkCommentStmt, nnkSym, nnkIdent: result = node.strVal - of nnkOpenSymChoice, nnkClosedSymChoice: + of nnkOpenSymChoice, nnkClosedSymChoice, nnkOpenSym: result = $node[0] of nnkAccQuoted: result = "" diff --git a/lib/pure/sugar.nim b/lib/pure/sugar.nim index 7833ed063..e3ec99b0c 100644 --- a/lib/pure/sugar.nim +++ b/lib/pure/sugar.nim @@ -345,7 +345,7 @@ proc collectImpl(init, body: NimNode): NimNode {.since: (1, 1).} = let res = genSym(nskVar, "collectResult") var bracketExpr: NimNode if init != nil: - expectKind init, {nnkCall, nnkIdent, nnkSym, nnkClosedSymChoice, nnkOpenSymChoice} + expectKind init, {nnkCall, nnkIdent, nnkSym, nnkClosedSymChoice, nnkOpenSymChoice, nnkOpenSym} bracketExpr = newTree(nnkBracketExpr, if init.kind in {nnkCall, nnkClosedSymChoice, nnkOpenSymChoice}: freshIdentNodes(init[0]) else: freshIdentNodes(init)) diff --git a/tests/generics/mopensymimport1.nim b/tests/generics/mopensymimport1.nim new file mode 100644 index 000000000..912db1302 --- /dev/null +++ b/tests/generics/mopensymimport1.nim @@ -0,0 +1,34 @@ +type + Result*[T, E] = object + when T is void: + when E is void: + oResultPrivate*: bool + else: + case oResultPrivate*: bool + of false: + eResultPrivate*: E + of true: + discard + else: + when E is void: + case oResultPrivate*: bool + of false: + discard + of true: + vResultPrivate*: T + else: + case oResultPrivate*: bool + of false: + eResultPrivate*: E + of true: + vResultPrivate*: T + +template valueOr*[T: not void, E](self: Result[T, E], def: untyped): untyped = + let s = (self) # TODO avoid copy + case s.oResultPrivate + of true: + s.vResultPrivate + of false: + when E isnot void: + template error: untyped {.used, inject.} = s.eResultPrivate + def diff --git a/tests/generics/mopensymimport2.nim b/tests/generics/mopensymimport2.nim new file mode 100644 index 000000000..1e1cda301 --- /dev/null +++ b/tests/generics/mopensymimport2.nim @@ -0,0 +1,16 @@ +{.experimental: "genericsOpenSym".} + +import mopensymimport1 + +type Xxx = enum + error + value + +proc f(): Result[int, cstring] = + Result[int, cstring](oResultPrivate: false, eResultPrivate: "f") + +proc g*(T: type): string = + let x = f().valueOr: + return $error + + "ok" diff --git a/tests/generics/tmacroinjectedsymwarning.nim b/tests/generics/tmacroinjectedsymwarning.nim index 43d11aed7..4b2ae4f7e 100644 --- a/tests/generics/tmacroinjectedsymwarning.nim +++ b/tests/generics/tmacroinjectedsymwarning.nim @@ -46,6 +46,11 @@ proc f(): Result[int, cstring] = proc g(T: type): string = let x = f().valueOr: + {.push warningAsError[GenericsIgnoredInjection]: on.} + # test spurious error + discard true + let _ = f + {.pop.} return $error #[tt.Warning ^ a new symbol 'error' has been injected during instantiation of g, however 'error' [enumField declared in tmacroinjectedsymwarning.nim(6, 3)] captured at the proc declaration will be used instead; either enable --experimental:genericsOpenSym to use the injected symbol or `bind` this captured symbol explicitly [GenericsIgnoredInjection]]# diff --git a/tests/generics/topensymimport.nim b/tests/generics/topensymimport.nim new file mode 100644 index 000000000..a47496827 --- /dev/null +++ b/tests/generics/topensymimport.nim @@ -0,0 +1,5 @@ +# issue #23386 + +import mopensymimport2 + +doAssert g(int) == "f" |