new .redefine pragma for templates, warn on redefinition without it (#20211)
* test CI for template redefinitions * adapt asyncmacro * fix quote * fix again * try something else * revert * fix ioselectors_select, disable packages CI * adapt more tests & simplify * more * more * more * rename to redefine, warn on implicit redefinition * basic documentation [skip ci] * Update compiler/lineinfos.nim Co-authored-by: konsumlamm <44230978+konsumlamm@users.noreply.github.com> Co-authored-by: konsumlamm <44230978+konsumlamm@users.noreply.github.com>
This commit is contained in:
parent
14656154ef
commit
d1d141b135
19 changed files with 85 additions and 25 deletions
|
|
@ -123,6 +123,12 @@
|
||||||
Baz = object
|
Baz = object
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- Redefining templates with the same signature implicitly was previously
|
||||||
|
allowed to support certain macro code. A `{.redefine.}` pragma has been
|
||||||
|
added to make this work explicitly, and a warning is generated in the case
|
||||||
|
where it is implicit. This behavior only applies to templates, redefinition
|
||||||
|
is generally disallowed for other symbols.
|
||||||
|
|
||||||
## Compiler changes
|
## Compiler changes
|
||||||
|
|
||||||
- The `gc` switch has been renamed to `mm` ("memory management") in order to reflect the
|
- The `gc` switch has been renamed to `mm` ("memory management") in order to reflect the
|
||||||
|
|
|
||||||
|
|
@ -334,6 +334,7 @@ const
|
||||||
sfEscapes* = sfProcvar # param escapes
|
sfEscapes* = sfProcvar # param escapes
|
||||||
sfBase* = sfDiscriminant
|
sfBase* = sfDiscriminant
|
||||||
sfCustomPragma* = sfRegister # symbol is custom pragma template
|
sfCustomPragma* = sfRegister # symbol is custom pragma template
|
||||||
|
sfTemplateRedefinition* = sfExportc # symbol is a redefinition of an earlier template
|
||||||
|
|
||||||
const
|
const
|
||||||
# getting ready for the future expr/stmt merge
|
# getting ready for the future expr/stmt merge
|
||||||
|
|
|
||||||
|
|
@ -140,3 +140,4 @@ proc initDefines*(symbols: StringTableRef) =
|
||||||
defineSymbol("nimHasEffectsOf")
|
defineSymbol("nimHasEffectsOf")
|
||||||
|
|
||||||
defineSymbol("nimHasEnforceNoRaises")
|
defineSymbol("nimHasEnforceNoRaises")
|
||||||
|
defineSymbol("nimHasTemplateRedefinitionPragma")
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,7 @@ type
|
||||||
warnCstringConv = "CStringConv",
|
warnCstringConv = "CStringConv",
|
||||||
warnEffect = "Effect",
|
warnEffect = "Effect",
|
||||||
warnCastSizes = "CastSizes"
|
warnCastSizes = "CastSizes"
|
||||||
|
warnTemplateRedefinition = "TemplateRedefinition",
|
||||||
warnUser = "User",
|
warnUser = "User",
|
||||||
# hints
|
# hints
|
||||||
hintSuccess = "Success", hintSuccessX = "SuccessX",
|
hintSuccess = "Success", hintSuccessX = "SuccessX",
|
||||||
|
|
@ -175,6 +176,7 @@ const
|
||||||
warnCstringConv: "$1",
|
warnCstringConv: "$1",
|
||||||
warnEffect: "$1",
|
warnEffect: "$1",
|
||||||
warnCastSizes: "$1",
|
warnCastSizes: "$1",
|
||||||
|
warnTemplateRedefinition: "template '$1' is implicitly redefined, consider adding an explicit .redefine pragma",
|
||||||
warnUser: "$1",
|
warnUser: "$1",
|
||||||
hintSuccess: "operation successful: $#",
|
hintSuccess: "operation successful: $#",
|
||||||
# keep in sync with `testament.isSuccess`
|
# keep in sync with `testament.isSuccess`
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ const
|
||||||
converterPragmas* = procPragmas
|
converterPragmas* = procPragmas
|
||||||
methodPragmas* = procPragmas+{wBase}-{wImportCpp}
|
methodPragmas* = procPragmas+{wBase}-{wImportCpp}
|
||||||
templatePragmas* = {wDeprecated, wError, wGensym, wInject, wDirty,
|
templatePragmas* = {wDeprecated, wError, wGensym, wInject, wDirty,
|
||||||
wDelegator, wExportNims, wUsed, wPragma}
|
wDelegator, wExportNims, wUsed, wPragma, wRedefine}
|
||||||
macroPragmas* = declPragmas + {FirstCallConv..LastCallConv,
|
macroPragmas* = declPragmas + {FirstCallConv..LastCallConv,
|
||||||
wMagic, wNoSideEffect, wCompilerProc, wNonReloadable, wCore,
|
wMagic, wNoSideEffect, wCompilerProc, wNonReloadable, wCore,
|
||||||
wDiscardable, wGensym, wInject, wDelegator}
|
wDiscardable, wGensym, wInject, wDelegator}
|
||||||
|
|
@ -870,6 +870,9 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
|
||||||
of wDirty:
|
of wDirty:
|
||||||
if sym.kind == skTemplate: incl(sym.flags, sfDirty)
|
if sym.kind == skTemplate: incl(sym.flags, sfDirty)
|
||||||
else: invalidPragma(c, it)
|
else: invalidPragma(c, it)
|
||||||
|
of wRedefine:
|
||||||
|
if sym.kind == skTemplate: incl(sym.flags, sfTemplateRedefinition)
|
||||||
|
else: invalidPragma(c, it)
|
||||||
of wImportCpp:
|
of wImportCpp:
|
||||||
processImportCpp(c, sym, getOptionalStr(c, it, "$1"), it.info)
|
processImportCpp(c, sym, getOptionalStr(c, it, "$1"), it.info)
|
||||||
of wCppNonPod:
|
of wCppNonPod:
|
||||||
|
|
|
||||||
|
|
@ -2112,10 +2112,12 @@ proc semQuoteAst(c: PContext, n: PNode): PNode =
|
||||||
ids[0] = newAnonSym(c, skParam, n.info).newSymNode
|
ids[0] = newAnonSym(c, skParam, n.info).newSymNode
|
||||||
processQuotations(c, quotedBlock, op, quotes, ids)
|
processQuotations(c, quotedBlock, op, quotes, ids)
|
||||||
|
|
||||||
|
let dummyTemplateSym = newAnonSym(c, skTemplate, n.info)
|
||||||
|
incl(dummyTemplateSym.flags, sfTemplateRedefinition)
|
||||||
var dummyTemplate = newProcNode(
|
var dummyTemplate = newProcNode(
|
||||||
nkTemplateDef, quotedBlock.info, body = quotedBlock,
|
nkTemplateDef, quotedBlock.info, body = quotedBlock,
|
||||||
params = c.graph.emptyNode,
|
params = c.graph.emptyNode,
|
||||||
name = newAnonSym(c, skTemplate, n.info).newSymNode,
|
name = dummyTemplateSym.newSymNode,
|
||||||
pattern = c.graph.emptyNode, genericParams = c.graph.emptyNode,
|
pattern = c.graph.emptyNode, genericParams = c.graph.emptyNode,
|
||||||
pragmas = c.graph.emptyNode, exceptions = c.graph.emptyNode)
|
pragmas = c.graph.emptyNode, exceptions = c.graph.emptyNode)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -691,6 +691,9 @@ proc semTemplateDef(c: PContext, n: PNode): PNode =
|
||||||
if proto == nil:
|
if proto == nil:
|
||||||
addInterfaceOverloadableSymAt(c, c.currentScope, s)
|
addInterfaceOverloadableSymAt(c, c.currentScope, s)
|
||||||
elif not comesFromShadowscope:
|
elif not comesFromShadowscope:
|
||||||
|
if {sfTemplateRedefinition, sfGenSym} * s.flags == {}:
|
||||||
|
#wrongRedefinition(c, n.info, proto.name.s, proto.info)
|
||||||
|
message(c.config, n.info, warnTemplateRedefinition, s.name.s)
|
||||||
symTabReplace(c.currentScope.symbols, proto, s)
|
symTabReplace(c.currentScope.symbols, proto, s)
|
||||||
if n[patternPos].kind != nkEmpty:
|
if n[patternPos].kind != nkEmpty:
|
||||||
c.patterns.add(s)
|
c.patterns.add(s)
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,7 @@ type
|
||||||
wGlobal = "global", wCodegenDecl = "codegenDecl", wUnchecked = "unchecked",
|
wGlobal = "global", wCodegenDecl = "codegenDecl", wUnchecked = "unchecked",
|
||||||
wGuard = "guard", wLocks = "locks", wPartial = "partial", wExplain = "explain",
|
wGuard = "guard", wLocks = "locks", wPartial = "partial", wExplain = "explain",
|
||||||
wLiftLocals = "liftlocals", wEnforceNoRaises = "enforceNoRaises",
|
wLiftLocals = "liftlocals", wEnforceNoRaises = "enforceNoRaises",
|
||||||
|
wRedefine = "redefine",
|
||||||
|
|
||||||
wAuto = "auto", wBool = "bool", wCatch = "catch", wChar = "char",
|
wAuto = "auto", wBool = "bool", wCatch = "catch", wChar = "char",
|
||||||
wClass = "class", wCompl = "compl", wConstCast = "const_cast", wDefault = "default",
|
wClass = "class", wCompl = "compl", wConstCast = "const_cast", wDefault = "default",
|
||||||
|
|
|
||||||
|
|
@ -7050,6 +7050,22 @@ immediate pragma
|
||||||
The immediate pragma is obsolete. See `Typed vs untyped parameters
|
The immediate pragma is obsolete. See `Typed vs untyped parameters
|
||||||
<#templates-typed-vs-untyped-parameters>`_.
|
<#templates-typed-vs-untyped-parameters>`_.
|
||||||
|
|
||||||
|
redefine pragma
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Redefinition of template symbols with the same signature is allowed.
|
||||||
|
This can be made explicit with the `redefine` pragma:
|
||||||
|
|
||||||
|
```nim
|
||||||
|
template foo: int = 1
|
||||||
|
echo foo() # 1
|
||||||
|
template foo: int {.redefine.} = 2
|
||||||
|
echo foo() # 2
|
||||||
|
# warning: implicit redefinition of template
|
||||||
|
template foo: int = 3
|
||||||
|
```
|
||||||
|
|
||||||
|
This is mostly intended for macro generated code.
|
||||||
|
|
||||||
compilation option pragmas
|
compilation option pragmas
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,9 @@ template await*(f: typed): untyped {.used.} =
|
||||||
error "await expects Future[T], got " & $typeof(f)
|
error "await expects Future[T], got " & $typeof(f)
|
||||||
|
|
||||||
template await*[T](f: Future[T]): auto {.used.} =
|
template await*[T](f: Future[T]): auto {.used.} =
|
||||||
template yieldFuture = yield FutureBase()
|
when not defined(nimHasTemplateRedefinitionPragma):
|
||||||
|
{.pragma: redefine.}
|
||||||
|
template yieldFuture {.redefine.} = yield FutureBase()
|
||||||
|
|
||||||
when compiles(yieldFuture):
|
when compiles(yieldFuture):
|
||||||
var internalTmpFuture: FutureBase = f
|
var internalTmpFuture: FutureBase = f
|
||||||
|
|
|
||||||
|
|
@ -398,18 +398,6 @@ proc contains*[T](s: Selector[T], fd: SocketHandle|int): bool {.inline.} =
|
||||||
if s.fds[i].ident == fdi:
|
if s.fds[i].ident == fdi:
|
||||||
return true
|
return true
|
||||||
|
|
||||||
when hasThreadSupport:
|
|
||||||
template withSelectLock[T](s: Selector[T], body: untyped) =
|
|
||||||
acquire(s.lock)
|
|
||||||
{.locks: [s.lock].}:
|
|
||||||
try:
|
|
||||||
body
|
|
||||||
finally:
|
|
||||||
release(s.lock)
|
|
||||||
else:
|
|
||||||
template withSelectLock[T](s: Selector[T], body: untyped) =
|
|
||||||
body
|
|
||||||
|
|
||||||
proc getData*[T](s: Selector[T], fd: SocketHandle|int): var T =
|
proc getData*[T](s: Selector[T], fd: SocketHandle|int): var T =
|
||||||
s.withSelectLock():
|
s.withSelectLock():
|
||||||
let fdi = int(fd)
|
let fdi = int(fd)
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,9 @@ template onFailedAssert*(msg, code: untyped): untyped {.dirty.} =
|
||||||
onFailedAssert(msg):
|
onFailedAssert(msg):
|
||||||
raise (ref MyError)(msg: msg, lineinfo: instantiationInfo(-2))
|
raise (ref MyError)(msg: msg, lineinfo: instantiationInfo(-2))
|
||||||
doAssertRaises(MyError): doAssert false
|
doAssertRaises(MyError): doAssert false
|
||||||
template failedAssertImpl(msgIMPL: string): untyped {.dirty.} =
|
when not defined(nimHasTemplateRedefinitionPragma):
|
||||||
|
{.pragma: redefine.}
|
||||||
|
template failedAssertImpl(msgIMPL: string): untyped {.dirty, redefine.} =
|
||||||
let msg = msgIMPL
|
let msg = msgIMPL
|
||||||
code
|
code
|
||||||
|
|
||||||
|
|
@ -98,7 +100,7 @@ template doAssertRaises*(exception: typedesc, code: untyped) =
|
||||||
var wrong = false
|
var wrong = false
|
||||||
const begin = "expected raising '" & astToStr(exception) & "', instead"
|
const begin = "expected raising '" & astToStr(exception) & "', instead"
|
||||||
const msgEnd = " by: " & astToStr(code)
|
const msgEnd = " by: " & astToStr(code)
|
||||||
template raisedForeign = raiseAssert(begin & " raised foreign exception" & msgEnd)
|
template raisedForeign {.gensym.} = raiseAssert(begin & " raised foreign exception" & msgEnd)
|
||||||
when Exception is exception:
|
when Exception is exception:
|
||||||
try:
|
try:
|
||||||
if true:
|
if true:
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ foo(1, "test")
|
||||||
proc bar(a: int, b: string) = discard
|
proc bar(a: int, b: string) = discard
|
||||||
bar(1, "test")
|
bar(1, "test")
|
||||||
|
|
||||||
template foo(a: int, b: string) = bar(a, b)
|
template foo(a: int, b: string) {.redefine.} = bar(a, b)
|
||||||
foo(1, "test")
|
foo(1, "test")
|
||||||
|
|
||||||
block:
|
block:
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ macro mergeScopes(scopeHolders: typed, newBindings: untyped): untyped =
|
||||||
newScopeDefinition.add newAssignment(newIdentNode(k), v)
|
newScopeDefinition.add newAssignment(newIdentNode(k), v)
|
||||||
|
|
||||||
result = quote:
|
result = quote:
|
||||||
template scopeHolder = `newScopeDefinition`
|
template scopeHolder {.redefine.} = `newScopeDefinition`
|
||||||
|
|
||||||
template scope(newBindings: untyped) {.dirty.} =
|
template scope(newBindings: untyped) {.dirty.} =
|
||||||
mergeScopes(bindSym"scopeHolder", newBindings)
|
mergeScopes(bindSym"scopeHolder", newBindings)
|
||||||
|
|
|
||||||
|
|
@ -158,7 +158,7 @@ template dim2: int =
|
||||||
else:
|
else:
|
||||||
int.high)
|
int.high)
|
||||||
|
|
||||||
template dim: int =
|
template dim3: int =
|
||||||
(
|
(
|
||||||
if int.high == 0:
|
if int.high == 0:
|
||||||
int.high
|
int.high
|
||||||
|
|
|
||||||
|
|
@ -274,10 +274,10 @@ parse9:
|
||||||
block gensym1:
|
block gensym1:
|
||||||
template x: untyped = -1
|
template x: untyped = -1
|
||||||
template t1() =
|
template t1() =
|
||||||
template x: untyped {.gensym.} = 1
|
template x: untyped {.gensym, redefine.} = 1
|
||||||
echo x() # 1
|
echo x() # 1
|
||||||
template t2() =
|
template t2() =
|
||||||
template x: untyped = 1 # defaults to {.inject.}
|
template x: untyped {.redefine.} = 1 # defaults to {.inject.}
|
||||||
echo x() # -1 injected x not available during template definition
|
echo x() # -1 injected x not available during template definition
|
||||||
t1()
|
t1()
|
||||||
t2()
|
t2()
|
||||||
|
|
|
||||||
33
tests/template/tredefinition_override.nim
Normal file
33
tests/template/tredefinition_override.nim
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
{.push warningAsError[TemplateRedefinition]: on.}
|
||||||
|
|
||||||
|
doAssert not (compiles do:
|
||||||
|
template foo(): int = 1
|
||||||
|
template foo(): int = 2)
|
||||||
|
doAssert (compiles do:
|
||||||
|
template foo(): int = 1
|
||||||
|
template foo(): int {.redefine.} = 2)
|
||||||
|
doAssert not (compiles do:
|
||||||
|
block:
|
||||||
|
template foo() =
|
||||||
|
template bar: string {.gensym.} = "a"
|
||||||
|
template bar: string {.gensym.} = "b"
|
||||||
|
foo())
|
||||||
|
doAssert (compiles do:
|
||||||
|
block:
|
||||||
|
template foo() =
|
||||||
|
template bar: string {.gensym.} = "a"
|
||||||
|
template bar: string {.gensym, redefine.} = "b"
|
||||||
|
foo())
|
||||||
|
|
||||||
|
block:
|
||||||
|
template foo(): int = 1
|
||||||
|
template foo(): int {.redefine.} = 2
|
||||||
|
doAssert foo() == 2
|
||||||
|
block:
|
||||||
|
template foo(): string =
|
||||||
|
template bar: string {.gensym.} = "a"
|
||||||
|
template bar: string {.gensym, redefine.} = "b"
|
||||||
|
bar()
|
||||||
|
doAssert foo() == "b"
|
||||||
|
|
||||||
|
{.pop.}
|
||||||
|
|
@ -22,7 +22,7 @@ block: # templates can be redefined multiple times
|
||||||
if not cond: fail(msg)
|
if not cond: fail(msg)
|
||||||
|
|
||||||
template assertionFailed(body: untyped) {.dirty.} =
|
template assertionFailed(body: untyped) {.dirty.} =
|
||||||
template fail(msg: string): typed =
|
template fail(msg: string): typed {.redefine.} =
|
||||||
body
|
body
|
||||||
|
|
||||||
assertionFailed:
|
assertionFailed:
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,8 @@ block tpartial:
|
||||||
proc p(x, y: int; cond: bool): int =
|
proc p(x, y: int; cond: bool): int =
|
||||||
result = if cond: x + y else: x - y
|
result = if cond: x + y else: x - y
|
||||||
|
|
||||||
template optP{p(x, y, true)}(x, y): untyped = x - y
|
template optPTrue{p(x, y, true)}(x, y): untyped = x - y
|
||||||
template optP{p(x, y, false)}(x, y): untyped = x + y
|
template optPFalse{p(x, y, false)}(x, y): untyped = x + y
|
||||||
|
|
||||||
echo p(2, 4, true)
|
echo p(2, 4, true)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue