summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--changelog.md6
-rw-r--r--compiler/ast.nim1
-rw-r--r--compiler/condsyms.nim1
-rw-r--r--compiler/lineinfos.nim2
-rw-r--r--compiler/pragmas.nim5
-rw-r--r--compiler/semexprs.nim4
-rw-r--r--compiler/semtempl.nim3
-rw-r--r--compiler/wordrecg.nim1
-rw-r--r--doc/manual.md16
-rw-r--r--lib/pure/asyncmacro.nim4
-rw-r--r--lib/pure/ioselects/ioselectors_select.nim12
-rw-r--r--lib/std/assertions.nim6
-rw-r--r--tests/lookups/tredef.nim2
-rw-r--r--tests/macros/tstructuredlogging.nim2
-rw-r--r--tests/parser/tstmtlists.nim2
-rw-r--r--tests/template/template_various.nim4
-rw-r--r--tests/template/tredefinition_override.nim33
-rw-r--r--tests/template/utemplates.nim2
-rw-r--r--tests/trmacros/trmacros_various2.nim4
19 files changed, 85 insertions, 25 deletions
diff --git a/changelog.md b/changelog.md
index 30bfc5c77..80692e996 100644
--- a/changelog.md
+++ b/changelog.md
@@ -123,6 +123,12 @@
       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
 
 - The `gc` switch has been renamed to `mm` ("memory management") in order to reflect the
diff --git a/compiler/ast.nim b/compiler/ast.nim
index 910c95451..c720a76fb 100644
--- a/compiler/ast.nim
+++ b/compiler/ast.nim
@@ -334,6 +334,7 @@ const
   sfEscapes* = sfProcvar              # param escapes
   sfBase* = sfDiscriminant
   sfCustomPragma* = sfRegister        # symbol is custom pragma template
+  sfTemplateRedefinition* = sfExportc # symbol is a redefinition of an earlier template
 
 const
   # getting ready for the future expr/stmt merge
diff --git a/compiler/condsyms.nim b/compiler/condsyms.nim
index 5a7e78d5f..4d4358b16 100644
--- a/compiler/condsyms.nim
+++ b/compiler/condsyms.nim
@@ -140,3 +140,4 @@ proc initDefines*(symbols: StringTableRef) =
   defineSymbol("nimHasEffectsOf")
 
   defineSymbol("nimHasEnforceNoRaises")
+  defineSymbol("nimHasTemplateRedefinitionPragma")
diff --git a/compiler/lineinfos.nim b/compiler/lineinfos.nim
index 07cc988a9..ec1643960 100644
--- a/compiler/lineinfos.nim
+++ b/compiler/lineinfos.nim
@@ -81,6 +81,7 @@ type
     warnCstringConv = "CStringConv",
     warnEffect = "Effect",
     warnCastSizes = "CastSizes"
+    warnTemplateRedefinition = "TemplateRedefinition",
     warnUser = "User",
     # hints
     hintSuccess = "Success", hintSuccessX = "SuccessX",
@@ -175,6 +176,7 @@ const
     warnCstringConv: "$1",
     warnEffect: "$1",
     warnCastSizes: "$1",
+    warnTemplateRedefinition: "template '$1' is implicitly redefined, consider adding an explicit .redefine pragma",
     warnUser: "$1",
     hintSuccess: "operation successful: $#",
     # keep in sync with `testament.isSuccess`
diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim
index d5c728418..b662e09c5 100644
--- a/compiler/pragmas.nim
+++ b/compiler/pragmas.nim
@@ -38,7 +38,7 @@ const
   converterPragmas* = procPragmas
   methodPragmas* = procPragmas+{wBase}-{wImportCpp}
   templatePragmas* = {wDeprecated, wError, wGensym, wInject, wDirty,
-    wDelegator, wExportNims, wUsed, wPragma}
+    wDelegator, wExportNims, wUsed, wPragma, wRedefine}
   macroPragmas* = declPragmas + {FirstCallConv..LastCallConv,
     wMagic, wNoSideEffect, wCompilerProc, wNonReloadable, wCore,
     wDiscardable, wGensym, wInject, wDelegator}
@@ -870,6 +870,9 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
       of wDirty:
         if sym.kind == skTemplate: incl(sym.flags, sfDirty)
         else: invalidPragma(c, it)
+      of wRedefine:
+        if sym.kind == skTemplate: incl(sym.flags, sfTemplateRedefinition)
+        else: invalidPragma(c, it)
       of wImportCpp:
         processImportCpp(c, sym, getOptionalStr(c, it, "$1"), it.info)
       of wCppNonPod:
diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim
index 119daa8b3..b47aff429 100644
--- a/compiler/semexprs.nim
+++ b/compiler/semexprs.nim
@@ -2112,10 +2112,12 @@ proc semQuoteAst(c: PContext, n: PNode): PNode =
   ids[0] = newAnonSym(c, skParam, n.info).newSymNode
   processQuotations(c, quotedBlock, op, quotes, ids)
 
+  let dummyTemplateSym = newAnonSym(c, skTemplate, n.info)
+  incl(dummyTemplateSym.flags, sfTemplateRedefinition)
   var dummyTemplate = newProcNode(
     nkTemplateDef, quotedBlock.info, body = quotedBlock,
     params = c.graph.emptyNode,
-    name = newAnonSym(c, skTemplate, n.info).newSymNode,
+    name = dummyTemplateSym.newSymNode,
               pattern = c.graph.emptyNode, genericParams = c.graph.emptyNode,
               pragmas = c.graph.emptyNode, exceptions = c.graph.emptyNode)
 
diff --git a/compiler/semtempl.nim b/compiler/semtempl.nim
index 1f76aff75..c72e9db05 100644
--- a/compiler/semtempl.nim
+++ b/compiler/semtempl.nim
@@ -691,6 +691,9 @@ proc semTemplateDef(c: PContext, n: PNode): PNode =
   if proto == nil:
     addInterfaceOverloadableSymAt(c, c.currentScope, s)
   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)
   if n[patternPos].kind != nkEmpty:
     c.patterns.add(s)
diff --git a/compiler/wordrecg.nim b/compiler/wordrecg.nim
index 71b2e6384..d33982b0f 100644
--- a/compiler/wordrecg.nim
+++ b/compiler/wordrecg.nim
@@ -87,6 +87,7 @@ type
     wGlobal = "global", wCodegenDecl = "codegenDecl", wUnchecked = "unchecked",
     wGuard = "guard", wLocks = "locks", wPartial = "partial", wExplain = "explain",
     wLiftLocals = "liftlocals", wEnforceNoRaises = "enforceNoRaises",
+    wRedefine = "redefine",
 
     wAuto = "auto", wBool = "bool", wCatch = "catch", wChar = "char",
     wClass = "class", wCompl = "compl", wConstCast = "const_cast", wDefault = "default",
diff --git a/doc/manual.md b/doc/manual.md
index 0a7231d23..4642fdb5c 100644
--- a/doc/manual.md
+++ b/doc/manual.md
@@ -7050,6 +7050,22 @@ immediate pragma
 The immediate pragma is obsolete. See `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
 --------------------------
diff --git a/lib/pure/asyncmacro.nim b/lib/pure/asyncmacro.nim
index 63c8e6e5c..d85e3e621 100644
--- a/lib/pure/asyncmacro.nim
+++ b/lib/pure/asyncmacro.nim
@@ -126,7 +126,9 @@ template await*(f: typed): untyped {.used.} =
     error "await expects Future[T], got " & $typeof(f)
 
 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):
     var internalTmpFuture: FutureBase = f
diff --git a/lib/pure/ioselects/ioselectors_select.nim b/lib/pure/ioselects/ioselectors_select.nim
index 4c38ba604..34c88d85e 100644
--- a/lib/pure/ioselects/ioselectors_select.nim
+++ b/lib/pure/ioselects/ioselectors_select.nim
@@ -398,18 +398,6 @@ proc contains*[T](s: Selector[T], fd: SocketHandle|int): bool {.inline.} =
       if s.fds[i].ident == fdi:
         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 =
   s.withSelectLock():
     let fdi = int(fd)
diff --git a/lib/std/assertions.nim b/lib/std/assertions.nim
index 3d2112b1a..0bc4653f2 100644
--- a/lib/std/assertions.nim
+++ b/lib/std/assertions.nim
@@ -85,7 +85,9 @@ template onFailedAssert*(msg, code: untyped): untyped {.dirty.} =
     onFailedAssert(msg):
       raise (ref MyError)(msg: msg, lineinfo: instantiationInfo(-2))
     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
     code
 
@@ -98,7 +100,7 @@ template doAssertRaises*(exception: typedesc, code: untyped) =
   var wrong = false
   const begin = "expected raising '" & astToStr(exception) & "', instead"
   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:
     try:
       if true:
diff --git a/tests/lookups/tredef.nim b/tests/lookups/tredef.nim
index 8f1b38bd1..7c1df238e 100644
--- a/tests/lookups/tredef.nim
+++ b/tests/lookups/tredef.nim
@@ -4,7 +4,7 @@ foo(1, "test")
 proc bar(a: int, b: string) = discard
 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")
 
 block:
diff --git a/tests/macros/tstructuredlogging.nim b/tests/macros/tstructuredlogging.nim
index 05bb52a40..5e4fdea3f 100644
--- a/tests/macros/tstructuredlogging.nim
+++ b/tests/macros/tstructuredlogging.nim
@@ -66,7 +66,7 @@ macro mergeScopes(scopeHolders: typed, newBindings: untyped): untyped =
     newScopeDefinition.add newAssignment(newIdentNode(k), v)
 
   result = quote:
-    template scopeHolder = `newScopeDefinition`
+    template scopeHolder {.redefine.} = `newScopeDefinition`
 
 template scope(newBindings: untyped) {.dirty.} =
   mergeScopes(bindSym"scopeHolder", newBindings)
diff --git a/tests/parser/tstmtlists.nim b/tests/parser/tstmtlists.nim
index fa47ba364..158c93340 100644
--- a/tests/parser/tstmtlists.nim
+++ b/tests/parser/tstmtlists.nim
@@ -158,7 +158,7 @@ template dim2: int =
    else:
     int.high)
 
-template dim: int =
+template dim3: int =
   (
    if int.high == 0:
      int.high
diff --git a/tests/template/template_various.nim b/tests/template/template_various.nim
index e7a2be748..6b1290fb9 100644
--- a/tests/template/template_various.nim
+++ b/tests/template/template_various.nim
@@ -274,10 +274,10 @@ parse9:
 block gensym1:
   template x: untyped = -1
   template t1() =
-    template x: untyped {.gensym.} = 1
+    template x: untyped {.gensym, redefine.} = 1
     echo x()  # 1
   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
   t1()
   t2()
diff --git a/tests/template/tredefinition_override.nim b/tests/template/tredefinition_override.nim
new file mode 100644
index 000000000..0bda4025b
--- /dev/null
+++ b/tests/template/tredefinition_override.nim
@@ -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.}
diff --git a/tests/template/utemplates.nim b/tests/template/utemplates.nim
index 7674ba7c0..d70746578 100644
--- a/tests/template/utemplates.nim
+++ b/tests/template/utemplates.nim
@@ -22,7 +22,7 @@ block: # templates can be redefined multiple times
     if not cond: fail(msg)
 
   template assertionFailed(body: untyped) {.dirty.} =
-    template fail(msg: string): typed =
+    template fail(msg: string): typed {.redefine.} =
       body
 
   assertionFailed:
diff --git a/tests/trmacros/trmacros_various2.nim b/tests/trmacros/trmacros_various2.nim
index c1367cb1b..257ee8ba6 100644
--- a/tests/trmacros/trmacros_various2.nim
+++ b/tests/trmacros/trmacros_various2.nim
@@ -33,8 +33,8 @@ block tpartial:
   proc p(x, y: int; cond: bool): int =
     result = if cond: x + y else: x - y
 
-  template optP{p(x, y, true)}(x, y): untyped = x - y
-  template optP{p(x, y, false)}(x, y): untyped = x + y
+  template optPTrue{p(x, y, true)}(x, y): untyped = x - y
+  template optPFalse{p(x, y, false)}(x, y): untyped = x + y
 
   echo p(2, 4, true)