Skip to content

Commit 770f8d5

Browse files
authored
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.
1 parent d3af51e commit 770f8d5

18 files changed

+468
-120
lines changed

changelog.md

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -82,30 +82,38 @@ is often an easy workaround.
8282
let (a, (b, c)): (byte, (float, cstring)) = (1, (2, "abc"))
8383
```
8484
85-
- An experimental option `genericsOpenSym` has been added to allow captured
86-
symbols in generic routine bodies to be replaced by symbols injected locally
87-
by templates/macros at instantiation time. `bind` may be used to keep the
88-
captured symbols over the injected ones regardless of enabling the option,
89-
but other methods like renaming the captured symbols should be used instead
90-
so that the code is not affected by context changes.
85+
- The experimental option `--experimental:openSym` has been added to allow
86+
captured symbols in generic routine and template bodies respectively to be
87+
replaced by symbols injected locally by templates/macros at instantiation
88+
time. `bind` may be used to keep the captured symbols over the injected ones
89+
regardless of enabling the option, but other methods like renaming the
90+
captured symbols should be used instead so that the code is not affected by
91+
context changes.
9192
9293
Since this change may affect runtime behavior, the experimental switch
93-
`genericsOpenSym` needs to be enabled, and a warning is given in the case
94-
where an injected symbol would replace a captured symbol not bound by `bind`
95-
and the experimental switch isn't enabled.
94+
`openSym`, or `genericsOpenSym` and `templateOpenSym` for only the respective
95+
routines, needs to be enabled; and a warning is given in the case where an
96+
injected symbol would replace a captured symbol not bound by `bind` and
97+
the experimental switch isn't enabled.
9698
9799
```nim
98100
const value = "captured"
99-
template foo(x: int, body: untyped) =
101+
template foo(x: int, body: untyped): untyped =
100102
let value {.inject.} = "injected"
101103
body
102104
103105
proc old[T](): string =
104106
foo(123):
105-
return value # warning: a new `value` has been injected, use `bind` or turn on `experimental:genericsOpenSym`
107+
return value # warning: a new `value` has been injected, use `bind` or turn on `experimental:openSym`
106108
echo old[int]() # "captured"
107109
108-
{.experimental: "genericsOpenSym".}
110+
template oldTempl(): string =
111+
block:
112+
foo(123):
113+
value # warning: a new `value` has been injected, use `bind` or turn on `experimental:openSym`
114+
echo oldTempl() # "captured"
115+
116+
{.experimental: "openSym".} # or {.experimental: "genericsOpenSym".} for just generic procs
109117
110118
proc bar[T](): string =
111119
foo(123):
@@ -117,14 +125,28 @@ is often an easy workaround.
117125
foo(123):
118126
return value
119127
assert baz[int]() == "captured"
128+
129+
# {.experimental: "templateOpenSym".} would be needed here if genericsOpenSym was used
130+
131+
template barTempl(): string =
132+
block:
133+
foo(123):
134+
value
135+
assert barTempl() == "injected" # previously it would be "captured"
136+
137+
template bazTempl(): string =
138+
bind value
139+
block:
140+
foo(123):
141+
value
142+
assert bazTempl() == "captured"
120143
```
121144

122145
This option also generates a new node kind `nnkOpenSym` which contains
123-
exactly 1 of either an `nnkSym` or an `nnkOpenSymChoice` node. In the future
124-
this might be merged with a slightly modified `nnkOpenSymChoice` node but
125-
macros that want to support the experimental feature should still handle
126-
`nnkOpenSym`, as the node kind would simply not be generated as opposed to
127-
being removed.
146+
exactly 1 `nnkSym` node. In the future this might be merged with a slightly
147+
modified `nnkOpenSymChoice` node but macros that want to support the
148+
experimental feature should still handle `nnkOpenSym`, as the node kind would
149+
simply not be generated as opposed to being removed.
128150

129151
## Compiler changes
130152

compiler/ast.nim

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ type
323323
nfHasComment # node has a comment
324324
nfSkipFieldChecking # node skips field visable checking
325325
nfDisabledOpenSym # temporary: node should be nkOpenSym but cannot
326-
# because genericsOpenSym experimental switch is disabled
326+
# because openSym experimental switch is disabled
327327
# gives warning instead
328328

329329
TNodeFlags* = set[TNodeFlag]
@@ -895,7 +895,7 @@ const
895895
nfAllFieldsSet* = nfBase2
896896

897897
nkIdentKinds* = {nkIdent, nkSym, nkAccQuoted, nkOpenSymChoice,
898-
nkClosedSymChoice}
898+
nkClosedSymChoice, nkOpenSym}
899899

900900
nkPragmaCallKinds* = {nkExprColonExpr, nkCall, nkCallStrLit}
901901
nkLiterals* = {nkCharLit..nkTripleStrLit}
@@ -1284,6 +1284,9 @@ proc newSymNode*(sym: PSym, info: TLineInfo): PNode =
12841284
result.typ = sym.typ
12851285
result.info = info
12861286

1287+
proc newOpenSym*(n: PNode): PNode {.inline.} =
1288+
result = newTreeI(nkOpenSym, n.info, n)
1289+
12871290
proc newIntNode*(kind: TNodeKind, intVal: BiggestInt): PNode =
12881291
result = newNode(kind)
12891292
result.intVal = intVal

compiler/lineinfos.nim

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ type
9292
warnStmtListLambda = "StmtListLambda",
9393
warnBareExcept = "BareExcept",
9494
warnImplicitDefaultValue = "ImplicitDefaultValue",
95-
warnGenericsIgnoredInjection = "GenericsIgnoredInjection",
95+
warnIgnoredSymbolInjection = "IgnoredSymbolInjection",
9696
warnStdPrefix = "StdPrefix"
9797
warnUser = "User",
9898
warnGlobalVarConstructorTemporary = "GlobalVarConstructorTemporary",
@@ -198,7 +198,7 @@ const
198198
warnStmtListLambda: "statement list expression assumed to be anonymous proc; this is deprecated, use `do (): ...` or `proc () = ...` instead",
199199
warnBareExcept: "$1",
200200
warnImplicitDefaultValue: "$1",
201-
warnGenericsIgnoredInjection: "$1",
201+
warnIgnoredSymbolInjection: "$1",
202202
warnStdPrefix: "$1 needs the 'std' prefix",
203203
warnUser: "$1",
204204
warnGlobalVarConstructorTemporary: "global variable '$1' initialization requires a temporary variable",

compiler/lookups.nim

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ proc considerQuotedIdent*(c: PContext; n: PNode, origin: PNode = nil): PIdent =
5050
case x.kind
5151
of nkIdent: id.add(x.ident.s)
5252
of nkSym: id.add(x.sym.name.s)
53-
of nkSymChoices:
53+
of nkSymChoices, nkOpenSym:
5454
if x[0].kind == nkSym:
5555
id.add(x[0].sym.name.s)
5656
else:
@@ -668,6 +668,8 @@ proc qualifiedLookUp*(c: PContext, n: PNode, flags: set[TLookupFlag]): PSym =
668668
c.isAmbiguous = amb
669669
of nkSym:
670670
result = n.sym
671+
of nkOpenSym:
672+
result = qualifiedLookUp(c, n[0], flags)
671673
of nkDotExpr:
672674
result = nil
673675
var m = qualifiedLookUp(c, n[0], (flags * {checkUndeclared}) + {checkModule})

compiler/options.nim

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,9 @@ type
226226
strictDefs,
227227
strictCaseObjects,
228228
inferGenericTypes,
229-
genericsOpenSym, # remove nfDisabledOpenSym when this switch is default
229+
openSym, # remove nfDisabledOpenSym when this is default
230+
# separated alternatives to above:
231+
genericsOpenSym, templateOpenSym,
230232
vtables
231233

232234
LegacyFeature* = enum

compiler/semexprs.nim

Lines changed: 32 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -138,25 +138,6 @@ proc resolveSymChoice(c: PContext, n: var PNode, flags: TExprFlags = {}, expecte
138138
# to mirror behavior before overloadable enums
139139
n = n[0]
140140

141-
proc semSymChoice(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType = nil): PNode =
142-
result = n
143-
resolveSymChoice(c, result, flags, expectedType)
144-
if isSymChoice(result) and result.len == 1:
145-
# resolveSymChoice can leave 1 sym
146-
result = result[0]
147-
if isSymChoice(result) and efAllowSymChoice notin flags:
148-
var err = "ambiguous identifier: '" & result[0].sym.name.s &
149-
"' -- use one of the following:\n"
150-
for child in n:
151-
let candidate = child.sym
152-
err.add " " & candidate.owner.name.s & "." & candidate.name.s
153-
err.add ": " & typeToString(candidate.typ) & "\n"
154-
localError(c.config, n.info, err)
155-
n.typ = errorType(c)
156-
result = n
157-
if result.kind == nkSym:
158-
result = semSym(c, result, result.sym, flags)
159-
160141
proc semOpenSym(c: PContext, n: PNode, flags: TExprFlags, expectedType: PType,
161142
warnDisabled = false): PNode =
162143
## sem the child of an `nkOpenSym` node, that is, captured symbols that can be
@@ -189,30 +170,54 @@ proc semOpenSym(c: PContext, n: PNode, flags: TExprFlags, expectedType: PType,
189170
else:
190171
var msg =
191172
"a new symbol '" & ident.s & "' has been injected during " &
192-
"instantiation of " & c.p.owner.name.s & ", however "
173+
# msgContext should show what is being instantiated:
174+
"template or generic instantiation, however "
193175
if isSym:
194176
msg.add(
195177
getSymRepr(c.config, n.sym) & " captured at " &
196178
"the proc declaration will be used instead; " &
197-
"either enable --experimental:genericsOpenSym to use the " &
198-
"injected symbol or `bind` this captured symbol explicitly")
179+
"either enable --experimental:openSym to use the injected symbol, " &
180+
"or `bind` this captured symbol explicitly")
199181
else:
200182
msg.add(
201183
"overloads of " & ident.s & " will be used instead; " &
202-
"either enable --experimental:genericsOpenSym to use the " &
203-
"injected symbol or `bind` this symbol explicitly")
204-
message(c.config, n.info, warnGenericsIgnoredInjection, msg)
184+
"either enable --experimental:openSym to use the injected symbol, " &
185+
"or `bind` this symbol explicitly")
186+
message(c.config, n.info, warnIgnoredSymbolInjection, msg)
205187
break
206188
o = o.owner
207189
# nothing found
208-
if not warnDisabled:
190+
if not warnDisabled and isSym:
209191
result = semExpr(c, n, flags, expectedType)
210192
else:
211193
result = nil
212194
if not isSym:
213195
# set symchoice node type back to None
214196
n.typ = newTypeS(tyNone, c)
215197

198+
proc semSymChoice(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType = nil): PNode =
199+
if n.kind == nkOpenSymChoice:
200+
result = semOpenSym(c, n, flags, expectedType, warnDisabled = nfDisabledOpenSym in n.flags)
201+
if result != nil:
202+
return
203+
result = n
204+
resolveSymChoice(c, result, flags, expectedType)
205+
if isSymChoice(result) and result.len == 1:
206+
# resolveSymChoice can leave 1 sym
207+
result = result[0]
208+
if isSymChoice(result) and efAllowSymChoice notin flags:
209+
var err = "ambiguous identifier: '" & result[0].sym.name.s &
210+
"' -- use one of the following:\n"
211+
for child in n:
212+
let candidate = child.sym
213+
err.add " " & candidate.owner.name.s & "." & candidate.name.s
214+
err.add ": " & typeToString(candidate.typ) & "\n"
215+
localError(c.config, n.info, err)
216+
n.typ = errorType(c)
217+
result = n
218+
if result.kind == nkSym:
219+
result = semSym(c, result, result.sym, flags)
220+
216221
proc inlineConst(c: PContext, n: PNode, s: PSym): PNode {.inline.} =
217222
result = copyTree(s.astdef)
218223
if result.isNil:
@@ -1780,7 +1785,7 @@ proc semSubscript(c: PContext, n: PNode, flags: TExprFlags): PNode =
17801785
result = nil
17811786
else:
17821787
let s = if n[0].kind == nkSym: n[0].sym
1783-
elif n[0].kind in nkSymChoices: n[0][0].sym
1788+
elif n[0].kind in nkSymChoices + {nkOpenSym}: n[0][0].sym
17841789
else: nil
17851790
if s != nil:
17861791
case s.kind
@@ -3230,9 +3235,6 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType
32303235
if isSymChoice(result):
32313236
result = semSymChoice(c, result, flags, expectedType)
32323237
of nkClosedSymChoice, nkOpenSymChoice:
3233-
if nfDisabledOpenSym in n.flags:
3234-
let res = semOpenSym(c, n, flags, expectedType, warnDisabled = true)
3235-
assert res == nil
32363238
result = semSymChoice(c, n, flags, expectedType)
32373239
of nkSym:
32383240
let s = n.sym

compiler/semgnrc.nim

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,6 @@ template isMixedIn(sym): bool =
5959
template canOpenSym(s): bool =
6060
{withinMixin, withinConcept} * flags == {withinMixin} and s.id notin ctx.toBind
6161

62-
proc newOpenSym*(n: PNode): PNode {.inline.} =
63-
result = newTreeI(nkOpenSym, n.info, n)
64-
6562
proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym,
6663
ctx: var GenericCtx; flags: TSemGenericFlags,
6764
isAmbiguous: bool,
@@ -77,8 +74,11 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym,
7774
else:
7875
result = symChoice(c, n, s, scOpen)
7976
if canOpenSym(s):
80-
if genericsOpenSym in c.features:
81-
result = newOpenSym(result)
77+
if {openSym, genericsOpenSym} * c.features != {}:
78+
if result.kind == nkSym:
79+
result = newOpenSym(result)
80+
else:
81+
result.typ = nil
8282
else:
8383
result.flags.incl nfDisabledOpenSym
8484
result.typ = nil
@@ -112,7 +112,7 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym,
112112
# we are in a generic context and `prepareNode` will be called
113113
result = newSymNodeTypeDesc(s, c.idgen, n.info)
114114
if canOpenSym(result.sym):
115-
if genericsOpenSym in c.features:
115+
if {openSym, genericsOpenSym} * c.features != {}:
116116
result = newOpenSym(result)
117117
else:
118118
result.flags.incl nfDisabledOpenSym
@@ -122,7 +122,7 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym,
122122
else:
123123
result = newSymNodeTypeDesc(s, c.idgen, n.info)
124124
if canOpenSym(result.sym):
125-
if genericsOpenSym in c.features:
125+
if {openSym, genericsOpenSym} * c.features != {}:
126126
result = newOpenSym(result)
127127
else:
128128
result.flags.incl nfDisabledOpenSym
@@ -141,7 +141,7 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym,
141141
return
142142
result = newSymNodeTypeDesc(s, c.idgen, n.info)
143143
if canOpenSym(result.sym):
144-
if genericsOpenSym in c.features:
144+
if {openSym, genericsOpenSym} * c.features != {}:
145145
result = newOpenSym(result)
146146
else:
147147
result.flags.incl nfDisabledOpenSym
@@ -153,7 +153,7 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym,
153153
# we are in a generic context and `prepareNode` will be called
154154
result = newSymNodeTypeDesc(s, c.idgen, n.info)
155155
if canOpenSym(result.sym):
156-
if genericsOpenSym in c.features:
156+
if {openSym, genericsOpenSym} * c.features != {}:
157157
result = newOpenSym(result)
158158
else:
159159
result.flags.incl nfDisabledOpenSym
@@ -164,7 +164,7 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym,
164164
else:
165165
result = newSymNode(s, n.info)
166166
if canOpenSym(result.sym):
167-
if genericsOpenSym in c.features:
167+
if {openSym, genericsOpenSym} * c.features != {}:
168168
result = newOpenSym(result)
169169
else:
170170
result.flags.incl nfDisabledOpenSym

0 commit comments

Comments
 (0)