opensym for templates + move behavior of opensymchoice to itself (#24007)

fixes #15314, fixes #24002

The OpenSym behavior first added to generics in #23091 now also applies
to templates, since templates can also capture symbols that are meant to
be replaced by local symbols if the context imports symbols with the
same name, as in the issue #24002. The experimental switch
`templateOpenSym` is added to enable this behavior for templates only,
and the experimental switch `openSym` is added to enable it for both
templates and generics, and the documentation now mainly mentions this
switch.

Additionally the logic for `nkOpenSymChoice` nodes that were previously
wrapped in `nkOpenSym` now apply to all `nkOpenSymChoice` nodes, and so
these nodes aren't wrapped in `nkOpenSym` anymore. This means
`nkOpenSym` can only have children of kind `nkSym` again, so it is more
in line with the structure of symchoice nodes. As for why they aren't
merged with `nkOpenSymChoice` nodes yet, we need some way to signal that
the node shouldn't become ambiguous if other options exist at
instantiation time, we already captured a symbol at the beginning and
another symbol can only replace it if it's closer in scope and
unambiguous.

(cherry picked from commit 770f8d5513)
This commit is contained in:
metagn
2024-08-28 21:51:13 +03:00
committed by narimiran
parent 8bb9823fde
commit 3214174f06
16 changed files with 475 additions and 92 deletions

View File

@@ -30,30 +30,38 @@ slots when enlarging a sequence.
- 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,
but other methods like renaming the captured symbols should be used instead
so that the code is not affected by context changes.
- The experimental option `--experimental:openSym` has been added to allow
captured symbols in generic routine and template bodies respectively 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, 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
where an injected symbol would replace a captured symbol not bound by `bind`
and the experimental switch isn't enabled.
`openSym`, or `genericsOpenSym` and `templateOpenSym` for only the respective
routines, needs to be enabled; and a warning is given in the case where an
injected symbol would replace a captured symbol not bound by `bind` and
the experimental switch isn't enabled.
```nim
const value = "captured"
template foo(x: int, body: untyped) =
template foo(x: int, body: untyped): untyped =
let value {.inject.} = "injected"
body
proc old[T](): string =
foo(123):
return value # warning: a new `value` has been injected, use `bind` or turn on `experimental:genericsOpenSym`
return value # warning: a new `value` has been injected, use `bind` or turn on `experimental:openSym`
echo old[int]() # "captured"
{.experimental: "genericsOpenSym".}
template oldTempl(): string =
block:
foo(123):
value # warning: a new `value` has been injected, use `bind` or turn on `experimental:openSym`
echo oldTempl() # "captured"
{.experimental: "openSym".} # or {.experimental: "genericsOpenSym".} for just generic procs
proc bar[T](): string =
foo(123):
@@ -65,14 +73,28 @@ slots when enlarging a sequence.
foo(123):
return value
assert baz[int]() == "captured"
# {.experimental: "templateOpenSym".} would be needed here if genericsOpenSym was used
template barTempl(): string =
block:
foo(123):
value
assert barTempl() == "injected" # previously it would be "captured"
template bazTempl(): string =
bind value
block:
foo(123):
value
assert bazTempl() == "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.
exactly 1 `nnkSym` 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

View File

@@ -519,7 +519,7 @@ type
nfHasComment # node has a comment
nfSkipFieldChecking # node skips field visable checking
nfDisabledOpenSym # temporary: node should be nkOpenSym but cannot
# because genericsOpenSym experimental switch is disabled
# because openSym experimental switch is disabled
# gives warning instead
TNodeFlags* = set[TNodeFlag]
@@ -1110,7 +1110,7 @@ const
nkCallKinds* = {nkCall, nkInfix, nkPrefix, nkPostfix,
nkCommand, nkCallStrLit, nkHiddenCallConv}
nkIdentKinds* = {nkIdent, nkSym, nkAccQuoted, nkOpenSymChoice,
nkClosedSymChoice}
nkClosedSymChoice, nkOpenSym}
nkPragmaCallKinds* = {nkExprColonExpr, nkCall, nkCallStrLit}
nkLiterals* = {nkCharLit..nkTripleStrLit}
@@ -1457,6 +1457,9 @@ proc newSymNode*(sym: PSym, info: TLineInfo): PNode =
result.typ = sym.typ
result.info = info
proc newOpenSym*(n: PNode): PNode {.inline.} =
result = newTreeI(nkOpenSym, n.info, n)
proc newIntNode*(kind: TNodeKind, intVal: BiggestInt): PNode =
result = newNode(kind)
result.intVal = intVal

View File

@@ -91,7 +91,7 @@ type
warnStmtListLambda = "StmtListLambda",
warnBareExcept = "BareExcept",
warnImplicitDefaultValue = "ImplicitDefaultValue",
warnGenericsIgnoredInjection = "GenericsIgnoredInjection",
warnIgnoredSymbolInjection = "IgnoredSymbolInjection",
warnUser = "User",
# hints
hintSuccess = "Success", hintSuccessX = "SuccessX",
@@ -195,7 +195,7 @@ const
warnStmtListLambda: "statement list expression assumed to be anonymous proc; this is deprecated, use `do (): ...` or `proc () = ...` instead",
warnBareExcept: "$1",
warnImplicitDefaultValue: "$1",
warnGenericsIgnoredInjection: "$1",
warnIgnoredSymbolInjection: "$1",
warnUser: "$1",
hintSuccess: "operation successful: $#",
# keep in sync with `testament.isSuccess`

View File

@@ -48,7 +48,7 @@ proc considerQuotedIdent*(c: PContext; n: PNode, origin: PNode = nil): PIdent =
case x.kind
of nkIdent: id.add(x.ident.s)
of nkSym: id.add(x.sym.name.s)
of nkSymChoices:
of nkSymChoices, nkOpenSym:
if x[0].kind == nkSym:
id.add(x[0].sym.name.s)
else:
@@ -664,6 +664,8 @@ proc qualifiedLookUp*(c: PContext, n: PNode, flags: set[TLookupFlag]): PSym =
c.isAmbiguous = amb
of nkSym:
result = n.sym
of nkOpenSym:
result = qualifiedLookUp(c, n[0], flags)
of nkDotExpr:
result = nil
var m = qualifiedLookUp(c, n[0], (flags * {checkUndeclared}) + {checkModule})

View File

@@ -221,7 +221,9 @@ type
flexibleOptionalParams,
strictDefs,
strictCaseObjects,
genericsOpenSym # remove nfDisabledOpenSym when this switch is default
openSym, # remove nfDisabledOpenSym when this is default
# separated alternatives to above:
genericsOpenSym, templateOpenSym
LegacyFeature* = enum
allowSemcheckedAstModification,

View File

@@ -138,25 +138,6 @@ proc resolveSymChoice(c: PContext, n: var PNode, flags: TExprFlags = {}, expecte
# to mirror behavior before overloadable enums
n = n[0]
proc semSymChoice(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType = nil): PNode =
result = n
resolveSymChoice(c, result, flags, expectedType)
if isSymChoice(result) and result.len == 1:
# resolveSymChoice can leave 1 sym
result = result[0]
if isSymChoice(result) and efAllowSymChoice notin flags:
var err = "ambiguous identifier: '" & result[0].sym.name.s &
"' -- use one of the following:\n"
for child in n:
let candidate = child.sym
err.add " " & candidate.owner.name.s & "." & candidate.name.s
err.add ": " & typeToString(candidate.typ) & "\n"
localError(c.config, n.info, err)
n.typ = errorType(c)
result = n
if result.kind == nkSym:
result = semSym(c, result, result.sym, flags)
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
@@ -189,23 +170,24 @@ proc semOpenSym(c: PContext, n: PNode, flags: TExprFlags, expectedType: PType,
else:
var msg =
"a new symbol '" & ident.s & "' has been injected during " &
"instantiation of " & c.p.owner.name.s & ", however "
# msgContext should show what is being instantiated:
"template or generic instantiation, however "
if isSym:
msg.add(
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 captured symbol explicitly")
"either enable --experimental:openSym to use the injected symbol, " &
"or `bind` this captured symbol explicitly")
else:
msg.add(
"overloads of " & ident.s & " will be used instead; " &
"either enable --experimental:genericsOpenSym to use the " &
"injected symbol or `bind` this symbol explicitly")
message(c.config, n.info, warnGenericsIgnoredInjection, msg)
"either enable --experimental:openSym to use the injected symbol, " &
"or `bind` this symbol explicitly")
message(c.config, n.info, warnIgnoredSymbolInjection, msg)
break
o = o.owner
# nothing found
if not warnDisabled:
if not warnDisabled and isSym:
result = semExpr(c, n, flags, expectedType)
else:
result = nil
@@ -213,6 +195,29 @@ proc semOpenSym(c: PContext, n: PNode, flags: TExprFlags, expectedType: PType,
# set symchoice node type back to None
n.typ = newTypeS(tyNone, c)
proc semSymChoice(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType = nil): PNode =
if n.kind == nkOpenSymChoice:
result = semOpenSym(c, n, flags, expectedType, warnDisabled = nfDisabledOpenSym in n.flags)
if result != nil:
return
result = n
resolveSymChoice(c, result, flags, expectedType)
if isSymChoice(result) and result.len == 1:
# resolveSymChoice can leave 1 sym
result = result[0]
if isSymChoice(result) and efAllowSymChoice notin flags:
var err = "ambiguous identifier: '" & result[0].sym.name.s &
"' -- use one of the following:\n"
for child in n:
let candidate = child.sym
err.add " " & candidate.owner.name.s & "." & candidate.name.s
err.add ": " & typeToString(candidate.typ) & "\n"
localError(c.config, n.info, err)
n.typ = errorType(c)
result = n
if result.kind == nkSym:
result = semSym(c, result, result.sym, flags)
proc inlineConst(c: PContext, n: PNode, s: PSym): PNode {.inline.} =
result = copyTree(s.astdef)
if result.isNil:
@@ -1758,7 +1763,7 @@ proc semSubscript(c: PContext, n: PNode, flags: TExprFlags): PNode =
result = nil
else:
let s = if n[0].kind == nkSym: n[0].sym
elif n[0].kind in nkSymChoices: n[0][0].sym
elif n[0].kind in nkSymChoices + {nkOpenSym}: n[0][0].sym
else: nil
if s != nil:
case s.kind
@@ -3201,9 +3206,6 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType
if isSymChoice(result):
result = semSymChoice(c, result, flags, expectedType)
of nkClosedSymChoice, nkOpenSymChoice:
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

View File

@@ -59,9 +59,6 @@ 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 =
@@ -75,8 +72,11 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym,
else:
result = symChoice(c, n, s, scOpen)
if canOpenSym(s):
if genericsOpenSym in c.features:
result = newOpenSym(result)
if {openSym, genericsOpenSym} * c.features != {}:
if result.kind == nkSym:
result = newOpenSym(result)
else:
result.typ = nil
else:
result.flags.incl nfDisabledOpenSym
result.typ = nil
@@ -108,7 +108,7 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym,
else:
result = newSymNodeTypeDesc(s, c.idgen, n.info)
if canOpenSym(result.sym):
if genericsOpenSym in c.features:
if {openSym, genericsOpenSym} * c.features != {}:
result = newOpenSym(result)
else:
result.flags.incl nfDisabledOpenSym
@@ -122,7 +122,7 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym,
(s.typ.flags * {tfGenericTypeParam, tfImplicitTypeParam} == {}):
result = newSymNodeTypeDesc(s, c.idgen, n.info)
if canOpenSym(result.sym):
if genericsOpenSym in c.features:
if {openSym, genericsOpenSym} * c.features != {}:
result = newOpenSym(result)
else:
result.flags.incl nfDisabledOpenSym
@@ -133,7 +133,7 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym,
else:
result = newSymNode(s, n.info)
if canOpenSym(result.sym):
if genericsOpenSym in c.features:
if {openSym, genericsOpenSym} * c.features != {}:
result = newOpenSym(result)
else:
result.flags.incl nfDisabledOpenSym

View File

@@ -217,43 +217,60 @@ proc addLocalDecl(c: var TemplCtx, n: var PNode, k: TSymKind) =
if k == skParam and c.inTemplateHeader > 0:
local.flags.incl sfTemplateParam
proc semTemplSymbol(c: PContext, n: PNode, s: PSym; isField: bool): PNode =
proc semTemplSymbol(c: var TemplCtx, n: PNode, s: PSym; isField: bool): PNode =
incl(s.flags, sfUsed)
# bug #12885; ideally sem'checking is performed again afterwards marking
# the symbol as used properly, but the nfSem mechanism currently prevents
# that from happening, so we mark the module as used here already:
markOwnerModuleAsUsed(c, s)
markOwnerModuleAsUsed(c.c, s)
# we do not call onUse here, as the identifier is not really
# resolved here. We will fixup the used identifiers later.
case s.kind
of skUnknown:
# Introduced in this pass! Leave it as an identifier.
result = n
of OverloadableSyms-{skTemplate,skMacro}:
result = symChoice(c, n, s, scOpen, isField)
of skTemplate, skMacro:
result = symChoice(c, n, s, scOpen, isField)
if result.kind == nkSym:
# template/macro symbols might need to be semchecked again
# prepareOperand etc don't do this without setting the type to nil
result.typ = nil
of OverloadableSyms:
result = symChoice(c.c, n, s, scOpen, isField)
if not isField and result.kind in {nkSym, nkOpenSymChoice}:
if {openSym, templateOpenSym} * c.c.features != {}:
if result.kind == nkSym:
result = newOpenSym(result)
else:
result.typ = nil
else:
result.flags.incl nfDisabledOpenSym
result.typ = nil
of skGenericParam:
if isField and sfGenSym in s.flags: result = n
else: result = newSymNodeTypeDesc(s, c.idgen, n.info)
else:
result = newSymNodeTypeDesc(s, c.c.idgen, n.info)
if not isField and s.owner != c.owner:
if {openSym, templateOpenSym} * c.c.features != {}:
result = newOpenSym(result)
else:
result.flags.incl nfDisabledOpenSym
result.typ = nil
of skParam:
result = n
of skType:
if isField and sfGenSym in s.flags: result = n
else: result = newSymNodeTypeDesc(s, c.idgen, n.info)
else: result = newSymNodeTypeDesc(s, c.c.idgen, n.info)
else:
if isField and sfGenSym in s.flags: result = n
else: result = newSymNode(s, n.info)
else:
result = newSymNode(s, n.info)
if not isField:
if {openSym, templateOpenSym} * c.c.features != {}:
result = newOpenSym(result)
else:
result.flags.incl nfDisabledOpenSym
result.typ = nil
# Issue #12832
when defined(nimsuggest):
suggestSym(c.graph, n.info, s, c.graph.usageSym, false)
suggestSym(c.c.graph, n.info, s, c.c.graph.usageSym, false)
# field access (dot expr) will be handled by builtinFieldAccess
if not isField:
styleCheckUse(c, n.info, s)
styleCheckUse(c.c, n.info, s)
proc semRoutineInTemplName(c: var TemplCtx, n: PNode, explicitInject: bool): PNode =
result = n
@@ -363,7 +380,7 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode =
else:
if s.kind in {skType, skVar, skLet, skConst}:
discard qualifiedLookUp(c.c, n, {checkAmbiguity, checkModule})
result = semTemplSymbol(c.c, n, s, c.noGenSym > 0)
result = semTemplSymbol(c, n, s, c.noGenSym > 0)
of nkBind:
result = semTemplBody(c, n[0])
of nkBindStmt:
@@ -573,7 +590,7 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode =
else:
if s.kind in {skType, skVar, skLet, skConst}:
discard qualifiedLookUp(c.c, n, {checkAmbiguity, checkModule})
return semTemplSymbol(c.c, n, s, c.noGenSym > 0)
return semTemplSymbol(c, n, s, c.noGenSym > 0)
if n.kind == nkDotExpr:
result = n
result[0] = semTemplBody(c, n[0])

View File

@@ -1251,7 +1251,7 @@ proc semParamType(c: PContext, n: PNode, constraint: var PNode): PType =
result = semTypeNode(c, n[0], nil)
constraint = semNodeKindConstraints(n, c.config, 1)
elif n.kind == nkCall and
n[0].kind in {nkIdent, nkSym, nkOpenSymChoice, nkClosedSymChoice} and
n[0].kind in {nkIdent, nkSym, nkOpenSymChoice, nkClosedSymChoice, nkOpenSym} and
considerQuotedIdent(c, n[0]).s == "{}":
result = semTypeNode(c, n[1], nil)
constraint = semNodeKindConstraints(n, c.config, 2)
@@ -1916,11 +1916,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
of nkTupleConstr: result = semAnonTuple(c, n, prev)
of nkCallKinds:
let x = n[0]
let ident = case x.kind
of nkIdent: x.ident
of nkSym: x.sym.name
of nkClosedSymChoice, nkOpenSymChoice: x[0].sym.name
else: nil
let ident = x.getPIdent
if ident != nil and ident.s == "[]":
let b = newNodeI(nkBracketExpr, n.info)
for i in 1..<n.len: b.add(n[i])
@@ -2010,7 +2006,9 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
elif op.id == ord(wType):
checkSonsLen(n, 2, c.config)
result = semTypeOf(c, n[1], prev)
elif op.s == "typeof" and n[0].kind == nkSym and n[0].sym.magic == mTypeOf:
elif op.s == "typeof" and (
(n[0].kind == nkSym and n[0].sym.magic == mTypeOf) or
(n[0].kind == nkOpenSym and n[0][0].sym.magic == mTypeOf)):
result = semTypeOf2(c, n, prev)
elif op.s == "owned" and optOwnedRefs notin c.config.globalOptions and n.len == 2:
result = semTypeExpr(c, n[1], prev)

View File

@@ -2324,3 +2324,71 @@ proc makeCppClass(): NimClass {.constructor: "NimClass() : CppClass(0, 0)".} =
In the example above `CppClass` has a deleted default constructor. Notice how by using the constructor syntax, one can call the appropiate constructor.
Notice when calling a constructor in the section of a global variable initialization, it will be called before `NimMain` meaning Nim is not fully initialized.
Injected symbols in generic procs and templates
===============================================
With the experimental option `openSym`, captured symbols in generic routine and
template 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 options, 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
`openSym`, or `genericsOpenSym` and `templateOpenSym` for only the respective
routines, needs to be enabled; and a warning is given in the case where an
injected symbol would replace a captured symbol not bound by `bind` and
the experimental switch isn't enabled.
```nim
const value = "captured"
template foo(x: int, body: untyped): untyped =
let value {.inject.} = "injected"
body
proc old[T](): string =
foo(123):
return value # warning: a new `value` has been injected, use `bind` or turn on `experimental:openSym`
echo old[int]() # "captured"
template oldTempl(): string =
block:
foo(123):
value # warning: a new `value` has been injected, use `bind` or turn on `experimental:openSym`
echo oldTempl() # "captured"
{.experimental: "openSym".} # or {.experimental: "genericsOpenSym".} for just generic procs
proc bar[T](): string =
foo(123):
return value
assert bar[int]() == "injected" # previously it would be "captured"
proc baz[T](): string =
bind value
foo(123):
return value
assert baz[int]() == "captured"
# {.experimental: "templateOpenSym".} would be needed here if genericsOpenSym was used
template barTempl(): string =
block:
foo(123):
value
assert barTempl() == "injected" # previously it would be "captured"
template bazTempl(): string =
bind value
block:
foo(123):
value
assert bazTempl() == "captured"
```
This option also generates a new node kind `nnkOpenSym` which contains
exactly 1 `nnkSym` 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.

View File

@@ -1045,7 +1045,7 @@ template eventParser*(pegAst, handlers: untyped): (proc(s: string): int) =
`enter hdPostf`(s, pegNode, start)
else:
discard
let hdPostf = ident(substr(strVal(pegKind), 2))
let hdPostf = ident(substr($pegKind, 2))
getAst(mkDoEnter(hdPostf, s, pegNode, start))
macro leave(pegKind, s, pegNode, start, length: untyped): untyped =
@@ -1056,7 +1056,7 @@ template eventParser*(pegAst, handlers: untyped): (proc(s: string): int) =
`leave hdPostf`(s, pegNode, start, length)
else:
discard
let hdPostf = ident(substr(strVal(pegKind), 2))
let hdPostf = ident(substr($pegKind, 2))
getAst(mkDoLeave(hdPostf, s, pegNode, start, length))
matchOrParse(parseIt)

View File

@@ -203,7 +203,7 @@ proc freshIdentNodes(ast: NimNode): NimNode =
# see also https://github.com/nim-lang/Nim/pull/8531#issuecomment-410436458
proc inspect(node: NimNode): NimNode =
case node.kind:
of nnkIdent, nnkSym:
of nnkIdent, nnkSym, nnkOpenSymChoice, nnkClosedSymChoice, nnkOpenSym:
result = ident($node)
of nnkEmpty, nnkLiterals:
result = node
@@ -347,7 +347,7 @@ proc collectImpl(init, body: NimNode): NimNode {.since: (1, 1).} =
if init != nil:
expectKind init, {nnkCall, nnkIdent, nnkSym, nnkClosedSymChoice, nnkOpenSymChoice}
bracketExpr = newTree(nnkBracketExpr,
if init.kind in {nnkCall, nnkClosedSymChoice, nnkOpenSymChoice}:
if init.kind in {nnkCall, nnkClosedSymChoice, nnkOpenSymChoice, nnkOpenSym}:
freshIdentNodes(init[0]) else: freshIdentNodes(init))
else:
bracketExpr = newTree(nnkBracketExpr)

View File

@@ -44,4 +44,4 @@ switch("define", "nimPreviewNonVarDestructor")
switch("warningAserror", "UnnamedBreak")
switch("legacy", "verboseTypeMismatch")
switch("experimental", "genericsOpenSym")
switch("experimental", "openSym")

View File

@@ -46,13 +46,13 @@ proc f(): Result[int, cstring] =
proc g(T: type): string =
let x = f().valueOr:
{.push warningAsError[GenericsIgnoredInjection]: on.}
{.push warningAsError[IgnoredSymbolInjection]: 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]]#
^ a new symbol 'error' has been injected during template or generic instantiation, however 'error' [enumField declared in tmacroinjectedsymwarning.nim(6, 3)] captured at the proc declaration will be used instead; either enable --experimental:openSym to use the injected symbol, or `bind` this captured symbol explicitly [IgnoredSymbolInjection]]#
"ok"

209
tests/template/topensym.nim Normal file
View File

@@ -0,0 +1,209 @@
{.experimental: "templateOpenSym".}
block: # issue #24002
type Result[T, E] = object
func value[T, E](self: Result[T, E]): T {.inline.} =
discard
func value[T: not void, E](self: var Result[T, E]): var T {.inline.} =
discard
template unrecognizedFieldWarning =
doAssert value == 123
let x = value
doAssert value == x
proc readValue(value: var int) =
unrecognizedFieldWarning()
var foo: int = 123
readValue(foo)
block: # issue #22605 for templates, normal call syntax
const error = "bad"
template valueOr(self: int, def: untyped): untyped =
case false
of true: ""
of false:
template error: untyped {.used, inject.} = "good"
def
template g(T: type): string =
var res = "ok"
let x = valueOr 123:
res = $error
"dummy"
res
doAssert g(int) == "good"
template g2(T: type): string =
bind error # use the bad version on purpose
var res = "ok"
let x = valueOr 123:
res = $error
"dummy"
res
doAssert g2(int) == "bad"
block: # issue #22605 for templates, method call syntax
const error = "bad"
template valueOr(self: int, def: untyped): untyped =
case false
of true: ""
of false:
template error: untyped {.used, inject.} = "good"
def
template g(T: type): string =
var res = "ok"
let x = 123.valueOr:
res = $error
"dummy"
res
doAssert g(int) == "good"
template g2(T: type): string =
bind error # use the bad version on purpose
var res = "ok"
let x = 123.valueOr:
res = $error
"dummy"
res
doAssert g2(int) == "bad"
block: # issue #22605 for templates, original complex example
type Xxx = enum
error
value
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
proc f(): Result[int, cstring] =
Result[int, cstring](oResultPrivate: false, eResultPrivate: "f")
template g(T: type): string =
var res = "ok"
let x = f().valueOr:
res = $error
123
res
doAssert g(int) == "f"
template g2(T: type): string =
bind error # use the bad version on purpose
var res = "ok"
let x = f().valueOr:
res = $error
123
res
doAssert g2(int) == "error"
block: # issue #23865 for templates
type Xxx = enum
error
value
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
func error[T, E](self: Result[T, E]): E =
## Fetch error of result if set, or raise Defect
case self.oResultPrivate
of true:
when T isnot void:
raiseResultDefect("Trying to access error when value is set", self.vResultPrivate)
else:
raiseResultDefect("Trying to access error when value is set")
of false:
when E isnot void:
self.eResultPrivate
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
proc f(): Result[int, cstring] =
Result[int, cstring](oResultPrivate: false, eResultPrivate: "f")
template g(T: type): string =
var res = "ok"
let x = f().valueOr:
res = $error
123
res
doAssert g(int) == "f"
import std/sequtils
block: # issue #15314
var it: string
var nums = @[1,2,3]
template doubleNums() =
nums.applyIt(it * 2)
doubleNums()
doAssert nums == @[2, 4, 6]

View File

@@ -0,0 +1,60 @@
discard """
matrix: "--skipParentCfg --filenames:legacyRelProj"
"""
type Xxx = enum
error
value
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
proc f(): Result[int, cstring] =
Result[int, cstring](oResultPrivate: false, eResultPrivate: "f")
template g(T: type): string =
var res = "ok"
let x = f().valueOr:
{.push warningAsError[IgnoredSymbolInjection]: on.}
# test spurious error
discard true
let _ = f
{.pop.}
res = $error #[tt.Warning
^ a new symbol 'error' has been injected during template or generic instantiation, however 'error' [enumField declared in topensymwarning.nim(6, 3)] captured at the proc declaration will be used instead; either enable --experimental:openSym to use the injected symbol, or `bind` this captured symbol explicitly [IgnoredSymbolInjection]]#
123
res
discard g(int)