summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--changelog.md56
-rw-r--r--compiler/ast.nim7
-rw-r--r--compiler/lineinfos.nim4
-rw-r--r--compiler/lookups.nim4
-rw-r--r--compiler/options.nim4
-rw-r--r--compiler/semexprs.nim62
-rw-r--r--compiler/semgnrc.nim20
-rw-r--r--compiler/semtempl.nim71
-rw-r--r--compiler/semtypes.nim12
-rw-r--r--doc/manual_experimental.md57
-rw-r--r--lib/pure/pegs.nim4
-rw-r--r--lib/pure/sugar.nim4
-rw-r--r--tests/compileoption/texperimental.nim4
-rw-r--r--tests/compileoption/texperimental.nims4
-rw-r--r--tests/config.nims2
-rw-r--r--tests/generics/tmacroinjectedsymwarning.nim4
-rw-r--r--tests/template/topensym.nim209
-rw-r--r--tests/template/topensymwarning.nim60
18 files changed, 468 insertions, 120 deletions
diff --git a/changelog.md b/changelog.md
index d98c34474..c5f30bbe2 100644
--- a/changelog.md
+++ b/changelog.md
@@ -82,30 +82,38 @@ is often an easy workaround.
     let (a, (b, c)): (byte, (float, cstring)) = (1, (2, "abc"))
     ```
 
-- 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):
@@ -117,14 +125,28 @@ is often an easy workaround.
     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
 
diff --git a/compiler/ast.nim b/compiler/ast.nim
index db6cd9761..eac4bf387 100644
--- a/compiler/ast.nim
+++ b/compiler/ast.nim
@@ -323,7 +323,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]
@@ -895,7 +895,7 @@ const
   nfAllFieldsSet* = nfBase2
 
   nkIdentKinds* = {nkIdent, nkSym, nkAccQuoted, nkOpenSymChoice,
-                   nkClosedSymChoice}
+                   nkClosedSymChoice, nkOpenSym}
 
   nkPragmaCallKinds* = {nkExprColonExpr, nkCall, nkCallStrLit}
   nkLiterals* = {nkCharLit..nkTripleStrLit}
@@ -1284,6 +1284,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
diff --git a/compiler/lineinfos.nim b/compiler/lineinfos.nim
index b48252ace..5535dc109 100644
--- a/compiler/lineinfos.nim
+++ b/compiler/lineinfos.nim
@@ -92,7 +92,7 @@ type
     warnStmtListLambda = "StmtListLambda",
     warnBareExcept = "BareExcept",
     warnImplicitDefaultValue = "ImplicitDefaultValue",
-    warnGenericsIgnoredInjection = "GenericsIgnoredInjection",
+    warnIgnoredSymbolInjection = "IgnoredSymbolInjection",
     warnStdPrefix = "StdPrefix"
     warnUser = "User",
     warnGlobalVarConstructorTemporary = "GlobalVarConstructorTemporary",
@@ -198,7 +198,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",
     warnStdPrefix: "$1 needs the 'std' prefix",
     warnUser: "$1",
     warnGlobalVarConstructorTemporary: "global variable '$1' initialization requires a temporary variable",
diff --git a/compiler/lookups.nim b/compiler/lookups.nim
index c6940a4dc..0e576d765 100644
--- a/compiler/lookups.nim
+++ b/compiler/lookups.nim
@@ -50,7 +50,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:
@@ -668,6 +668,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})
diff --git a/compiler/options.nim b/compiler/options.nim
index de412979f..34268259b 100644
--- a/compiler/options.nim
+++ b/compiler/options.nim
@@ -226,7 +226,9 @@ type
     strictDefs,
     strictCaseObjects,
     inferGenericTypes,
-    genericsOpenSym, # remove nfDisabledOpenSym when this switch is default
+    openSym, # remove nfDisabledOpenSym when this is default
+    # separated alternatives to above:
+    genericsOpenSym, templateOpenSym,
     vtables
 
   LegacyFeature* = enum
diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim
index f52de6750..0c3691362 100644
--- a/compiler/semexprs.nim
+++ b/compiler/semexprs.nim
@@ -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:
@@ -1780,7 +1785,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
@@ -3230,9 +3235,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
diff --git a/compiler/semgnrc.nim b/compiler/semgnrc.nim
index 8efc8a94e..e3a8daf99 100644
--- a/compiler/semgnrc.nim
+++ b/compiler/semgnrc.nim
@@ -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,
                           isAmbiguous: bool,
@@ -77,8 +74,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
@@ -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 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,
     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
@@ -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 genericsOpenSym in c.features:
+        if {openSym, genericsOpenSym} * c.features != {}:
           result = newOpenSym(result)
         else:
           result.flags.incl nfDisabledOpenSym
@@ -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 genericsOpenSym in c.features:
+        if {openSym, genericsOpenSym} * c.features != {}:
           result = newOpenSym(result)
         else:
           result.flags.incl nfDisabledOpenSym
@@ -164,7 +164,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
diff --git a/compiler/semtempl.nim b/compiler/semtempl.nim
index f7653a890..aef0ce9b3 100644
--- a/compiler/semtempl.nim
+++ b/compiler/semtempl.nim
@@ -218,47 +218,76 @@ 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, isAmbiguous: bool): PNode =
+proc semTemplSymbol(c: var TemplCtx, n: PNode, s: PSym; isField, isAmbiguous: 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
-    elif isAmbiguous:
-      # ambiguous types should be symchoices since lookup behaves
-      # differently for them in regular expressions
-      result = symChoice(c, n, s, scOpen, isField)
-    else: result = newSymNodeTypeDesc(s, c.idgen, n.info)
+    else:
+      if isAmbiguous:
+        # ambiguous types should be symchoices since lookup behaves
+        # differently for them in regular expressions
+        result = symChoice(c.c, n, s, scOpen, isField)
+      else: result = newSymNodeTypeDesc(s, c.c.idgen, n.info)
+      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 result.kind == nkSym:
+            result = newOpenSym(result)
+          else:
+            result.typ = nil
+        else:
+          result.flags.incl nfDisabledOpenSym
+          result.typ = nil
   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
@@ -369,7 +398,7 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode =
       else:
         if s.kind in {skVar, skLet, skConst}:
           discard qualifiedLookUp(c.c, n, {checkAmbiguity, checkModule})
-        result = semTemplSymbol(c.c, n, s, c.noGenSym > 0, c.c.isAmbiguous)
+        result = semTemplSymbol(c, n, s, c.noGenSym > 0, c.c.isAmbiguous)
   of nkBind:
     result = semTemplBody(c, n[0])
   of nkBindStmt:
@@ -580,7 +609,7 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode =
       else:
         if s.kind in {skVar, skLet, skConst}:
           discard qualifiedLookUp(c.c, n, {checkAmbiguity, checkModule})
-        return semTemplSymbol(c.c, n, s, c.noGenSym > 0, c.c.isAmbiguous)
+        return semTemplSymbol(c, n, s, c.noGenSym > 0, c.c.isAmbiguous)
     if n.kind == nkDotExpr:
       result = n
       result[0] = semTemplBody(c, n[0])
diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim
index 292558b5b..2b6a7b6a7 100644
--- a/compiler/semtypes.nim
+++ b/compiler/semtypes.nim
@@ -1282,7 +1282,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)
@@ -1965,11 +1965,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])
@@ -2059,7 +2055,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)
diff --git a/doc/manual_experimental.md b/doc/manual_experimental.md
index 34399e6f9..6998e313d 100644
--- a/doc/manual_experimental.md
+++ b/doc/manual_experimental.md
@@ -2522,33 +2522,40 @@ Notice we use the overload of `()` to have the same semantics in Nim, but on the
 This allows to easy interop with functions that accepts for example a `const` operator in its signature. 
 
 
-Injected symbols in generic procs
-=================================
+Injected symbols in generic procs and templates
+===============================================
 
-With the experimental option `genericsOpenSym`, captured symbols in generic
-routine 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 option, but other methods like
+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
-`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):
@@ -2560,14 +2567,28 @@ proc baz[T](): string =
   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.
 
 
 VTable for methods
diff --git a/lib/pure/pegs.nim b/lib/pure/pegs.nim
index ca2e80021..2969fd6d7 100644
--- a/lib/pure/pegs.nim
+++ b/lib/pure/pegs.nim
@@ -1047,7 +1047,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 =
@@ -1058,7 +1058,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)
diff --git a/lib/pure/sugar.nim b/lib/pure/sugar.nim
index e3ec99b0c..90ba20c13 100644
--- a/lib/pure/sugar.nim
+++ b/lib/pure/sugar.nim
@@ -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, nnkOpenSym}
     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)
diff --git a/tests/compileoption/texperimental.nim b/tests/compileoption/texperimental.nim
index e31b0481d..be637e58a 100644
--- a/tests/compileoption/texperimental.nim
+++ b/tests/compileoption/texperimental.nim
@@ -16,5 +16,5 @@ static:
   doAssert compileOption("experimental", "strictDefs")
   doAssert compileOption("experimental", "strictCaseObjects")
   doAssert compileOption("experimental", "inferGenericTypes")
-  doAssert compileOption("experimental", "genericsOpenSym")
-  doAssert compileOption("experimental", "vtables")
\ No newline at end of file
+  doAssert compileOption("experimental", "openSym")
+  doAssert compileOption("experimental", "vtables")
diff --git a/tests/compileoption/texperimental.nims b/tests/compileoption/texperimental.nims
index a2094eb4a..c2c357caa 100644
--- a/tests/compileoption/texperimental.nims
+++ b/tests/compileoption/texperimental.nims
@@ -15,5 +15,5 @@ switch("experimental", "flexibleOptionalParams")
 switch("experimental", "strictDefs")
 switch("experimental", "strictCaseObjects")
 switch("experimental", "inferGenericTypes")
-switch("experimental", "genericsOpenSym")
-switch("experimental", "vtables")
\ No newline at end of file
+switch("experimental", "openSym")
+switch("experimental", "vtables")
diff --git a/tests/config.nims b/tests/config.nims
index 91934cc74..31610e128 100644
--- a/tests/config.nims
+++ b/tests/config.nims
@@ -45,4 +45,4 @@ switch("define", "nimPreviewNonVarDestructor")
 switch("warningAserror", "UnnamedBreak")
 switch("legacy", "verboseTypeMismatch")
 switch("experimental", "vtables")
-switch("experimental", "genericsOpenSym")
+switch("experimental", "openSym")
diff --git a/tests/generics/tmacroinjectedsymwarning.nim b/tests/generics/tmacroinjectedsymwarning.nim
index 4b2ae4f7e..77119004b 100644
--- a/tests/generics/tmacroinjectedsymwarning.nim
+++ b/tests/generics/tmacroinjectedsymwarning.nim
@@ -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"
 
diff --git a/tests/template/topensym.nim b/tests/template/topensym.nim
new file mode 100644
index 000000000..9393e1971
--- /dev/null
+++ b/tests/template/topensym.nim
@@ -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]
diff --git a/tests/template/topensymwarning.nim b/tests/template/topensymwarning.nim
new file mode 100644
index 000000000..0bbe0a9fb
--- /dev/null
+++ b/tests/template/topensymwarning.nim
@@ -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)