Skip to content

Commit

Permalink
make genericsOpenSym work at instantiation time, new behavior in `o…
Browse files Browse the repository at this point in the history
…penSym` (#24111)

alternative to #24101

#23892 changed the opensym experimental switch so that it has to be
enabled in the context of the generic/template declarations capturing
the symbols, not the context of the instantiation of the
generics/templates. This was to be in line with where the compiler gives
the warnings and changes behavior in a potentially breaking way.

However `results` [depends on the old
behavior](https://github.com/arnetheduck/nim-results/blob/71d404b314479a6205bfd050f4fe5fe49cdafc69/results.nim#L1428),
so that the callers of the macros provided by results always take
advantage of the opensym behavior. To accomodate this, we change the
behavior of the old experimental option that `results` uses,
`genericsOpenSym`, so that ignores the information of whether or not
symbols are intentionally opened and always gives the opensym behavior
as long as it's enabled at instantiation time. This should keep
`results` working as is. However this differs from the normal opensym
switch in that it doesn't generate `nnkOpenSym`.

Before it was just a generics-only version of `openSym` along with
`templateOpenSym` which was only for templates. So `templateOpenSym` is
removed along with this change, but no one appears to have used it.
  • Loading branch information
metagn authored Sep 18, 2024
1 parent 79b17b7 commit 0c3573e
Show file tree
Hide file tree
Showing 11 changed files with 125 additions and 28 deletions.
35 changes: 30 additions & 5 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,7 @@ is often an easy workaround.
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
`openSym` 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.
Expand All @@ -150,7 +149,7 @@ is often an easy workaround.
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
{.experimental: "openSym".}
proc bar[T](): string =
foo(123):
Expand All @@ -163,8 +162,6 @@ is often an easy workaround.
return value
assert baz[int]() == "captured"
# {.experimental: "templateOpenSym".} would be needed here if genericsOpenSym was used
template barTempl(): string =
block:
foo(123):
Expand All @@ -185,6 +182,34 @@ is often an easy workaround.
experimental feature should still handle `nnkOpenSym`, as the node kind would
simply not be generated as opposed to being removed.

Another experimental switch `genericsOpenSym` exists that enables this behavior
at instantiation time, meaning templates etc can enable it specifically when
they are being called. However this does not generate `nnkOpenSym` nodes
(unless the other switch is enabled) and so doesn't reflect the regular
behavior of the switch.

```nim
const value = "captured"
template foo(x: int, body: untyped): untyped =
let value {.inject.} = "injected"
{.push experimental: "genericsOpenSym".}
body
{.pop.}
proc bar[T](): string =
foo(123):
return value
echo bar[int]() # "injected"
template barTempl(): string =
block:
var res: string
foo(123):
res = value
res
assert barTempl() == "injected"
```

## 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.
Expand Down
1 change: 1 addition & 0 deletions compiler/condsyms.nim
Original file line number Diff line number Diff line change
Expand Up @@ -167,4 +167,5 @@ proc initDefines*(symbols: StringTableRef) =

defineSymbol("nimHasVtables")
defineSymbol("nimHasGenericsOpenSym2")
defineSymbol("nimHasGenericsOpenSym3")
defineSymbol("nimHasJsNoLambdaLifting")
4 changes: 2 additions & 2 deletions compiler/options.nim
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,8 @@ type
strictCaseObjects,
inferGenericTypes,
openSym, # remove nfDisabledOpenSym when this is default
# separated alternatives to above:
genericsOpenSym, templateOpenSym,
# alternative to above:
genericsOpenSym
vtables

LegacyFeature* = enum
Expand Down
13 changes: 10 additions & 3 deletions compiler/semexprs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ proc semOpenSym(c: PContext, n: PNode, flags: TExprFlags, expectedType: PType,
break
o = o.owner
# nothing found
n.flags.excl nfDisabledOpenSym
if not warnDisabled and isSym:
result = semExpr(c, n, flags, expectedType)
else:
Expand All @@ -197,7 +198,9 @@ proc semOpenSym(c: PContext, n: PNode, flags: TExprFlags, expectedType: PType,

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)
result = semOpenSym(c, n, flags, expectedType,
warnDisabled = nfDisabledOpenSym in n.flags and
genericsOpenSym notin c.features)
if result != nil:
return
result = n
Expand Down Expand Up @@ -3293,8 +3296,12 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType
of nkSym:
let s = n.sym
if nfDisabledOpenSym in n.flags:
let res = semOpenSym(c, n, flags, expectedType, warnDisabled = true)
assert res == nil
let override = genericsOpenSym in c.features
let res = semOpenSym(c, n, flags, expectedType,
warnDisabled = not override)
if res != nil:
assert override
return res
# 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)
Expand Down
12 changes: 6 additions & 6 deletions compiler/semgnrc.nim
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym,
else:
result = symChoice(c, n, s, scOpen)
if canOpenSym(s):
if {openSym, genericsOpenSym} * c.features != {}:
if openSym in c.features:
if result.kind == nkSym:
result = newOpenSym(result)
else:
Expand Down Expand Up @@ -112,7 +112,7 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym,
# we are in a generic context and `prepareNode` will be called
result = newSymNodeTypeDesc(s, c.idgen, n.info)
if canOpenSym(result.sym):
if {openSym, genericsOpenSym} * c.features != {}:
if openSym in c.features:
result = newOpenSym(result)
else:
result.flags.incl nfDisabledOpenSym
Expand All @@ -122,7 +122,7 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym,
else:
result = newSymNodeTypeDesc(s, c.idgen, n.info)
if canOpenSym(result.sym):
if {openSym, genericsOpenSym} * c.features != {}:
if openSym in c.features:
result = newOpenSym(result)
else:
result.flags.incl nfDisabledOpenSym
Expand All @@ -141,7 +141,7 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym,
return
result = newSymNodeTypeDesc(s, c.idgen, n.info)
if canOpenSym(result.sym):
if {openSym, genericsOpenSym} * c.features != {}:
if openSym in c.features:
result = newOpenSym(result)
else:
result.flags.incl nfDisabledOpenSym
Expand All @@ -153,7 +153,7 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym,
# we are in a generic context and `prepareNode` will be called
result = newSymNodeTypeDesc(s, c.idgen, n.info)
if canOpenSym(result.sym):
if {openSym, genericsOpenSym} * c.features != {}:
if openSym in c.features:
result = newOpenSym(result)
else:
result.flags.incl nfDisabledOpenSym
Expand All @@ -164,7 +164,7 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym,
else:
result = newSymNode(s, n.info)
if canOpenSym(result.sym):
if {openSym, genericsOpenSym} * c.features != {}:
if openSym in c.features:
result = newOpenSym(result)
else:
result.flags.incl nfDisabledOpenSym
Expand Down
8 changes: 4 additions & 4 deletions compiler/semtempl.nim
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ proc semTemplSymbol(c: var TemplCtx, n: PNode, s: PSym; isField, isAmbiguous: bo
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 openSym in c.c.features:
if result.kind == nkSym:
result = newOpenSym(result)
else:
Expand All @@ -246,7 +246,7 @@ proc semTemplSymbol(c: var TemplCtx, n: PNode, s: PSym; isField, isAmbiguous: bo
else:
result = newSymNodeTypeDesc(s, c.c.idgen, n.info)
if not isField and s.owner != c.owner:
if {openSym, templateOpenSym} * c.c.features != {}:
if openSym in c.c.features:
result = newOpenSym(result)
else:
result.flags.incl nfDisabledOpenSym
Expand All @@ -264,7 +264,7 @@ proc semTemplSymbol(c: var TemplCtx, n: PNode, s: PSym; isField, isAmbiguous: bo
if not isField and not (s.owner == c.owner and
s.typ != nil and s.typ.kind == tyGenericParam) and
result.kind in {nkSym, nkOpenSymChoice}:
if {openSym, templateOpenSym} * c.c.features != {}:
if openSym in c.c.features:
if result.kind == nkSym:
result = newOpenSym(result)
else:
Expand All @@ -277,7 +277,7 @@ proc semTemplSymbol(c: var TemplCtx, n: PNode, s: PSym; isField, isAmbiguous: bo
else:
result = newSymNode(s, n.info)
if not isField:
if {openSym, templateOpenSym} * c.c.features != {}:
if openSym in c.c.features:
result = newOpenSym(result)
else:
result.flags.incl nfDisabledOpenSym
Expand Down
35 changes: 30 additions & 5 deletions doc/manual_experimental.md
Original file line number Diff line number Diff line change
Expand Up @@ -2533,8 +2533,7 @@ 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
`openSym` 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.
Expand All @@ -2555,7 +2554,7 @@ template oldTempl(): string =
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
{.experimental: "openSym".}
proc bar[T](): string =
foo(123):
Expand All @@ -2568,8 +2567,6 @@ proc baz[T](): string =
return value
assert baz[int]() == "captured"
# {.experimental: "templateOpenSym".} would be needed here if genericsOpenSym was used
template barTempl(): string =
block:
foo(123):
Expand All @@ -2590,6 +2587,34 @@ 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.
Another experimental switch `genericsOpenSym` exists that enables this behavior
at instantiation time, meaning templates etc can enable it specifically when
they are being called. However this does not generate `nnkOpenSym` nodes
(unless the other switch is enabled) and so doesn't reflect the regular
behavior of the switch.

```nim
const value = "captured"
template foo(x: int, body: untyped): untyped =
let value {.inject.} = "injected"
{.push experimental: "genericsOpenSym".}
body
{.pop.}

proc bar[T](): string =
foo(123):
return value
echo bar[int]() # "injected"

template barTempl(): string =
block:
var res: string
foo(123):
res = value
res
assert barTempl() == "injected"
```
VTable for methods
==================
Expand Down
2 changes: 1 addition & 1 deletion tests/generics/mopensymimport2.nim
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{.experimental: "genericsOpenSym".}
{.experimental: "openSym".}

import mopensymimport1

Expand Down
2 changes: 1 addition & 1 deletion tests/generics/tmacroinjectedsym.nim
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{.experimental: "genericsOpenSym".}
{.experimental: "openSym".}

block: # issue #22605, normal call syntax
const error = "bad"
Expand Down
2 changes: 1 addition & 1 deletion tests/template/topensym.nim
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{.experimental: "templateOpenSym".}
{.experimental: "openSym".}

block: # issue #24002
type Result[T, E] = object
Expand Down
39 changes: 39 additions & 0 deletions tests/template/topensymoverride.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
discard """
matrix: "--skipParentCfg --filenames:legacyRelProj"
"""

const value = "captured"
template fooOld(x: int, body: untyped): untyped =
let value {.inject.} = "injected"
body
template foo(x: int, body: untyped): untyped =
let value {.inject.} = "injected"
{.push experimental: "genericsOpenSym".}
body
{.pop.}

proc old[T](): string =
fooOld(123):
return value
doAssert old[int]() == "captured"

template oldTempl(): string =
block:
var res: string
fooOld(123):
res = value
res
doAssert oldTempl() == "captured"

proc bar[T](): string =
foo(123):
return value
doAssert bar[int]() == "injected"

template barTempl(): string =
block:
var res: string
foo(123):
res = value
res
doAssert barTempl() == "injected"

0 comments on commit 0c3573e

Please sign in to comment.