summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--compiler/semstmts.nim286
-rw-r--r--compiler/semtempl.nim5
-rw-r--r--doc/manual.rst28
-rw-r--r--doc/manual_experimental.rst72
-rw-r--r--lib/std/decls.nim39
-rw-r--r--tests/pragmas/tpragmas_misc.nim24
-rw-r--r--tests/pragmas/tvar_macro.nim128
-rw-r--r--tests/stdlib/tdecls.nim66
8 files changed, 439 insertions, 209 deletions
diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim
index f302dd4c3..33e304ab2 100644
--- a/compiler/semstmts.nim
+++ b/compiler/semstmts.nim
@@ -341,8 +341,12 @@ proc checkNilable(c: PContext; v: PSym) =
 
 #include liftdestructors
 
-proc addToVarSection(c: PContext; result: PNode; orig, identDefs: PNode) =
-  let value = identDefs[^1]
+proc addToVarSection(c: PContext; result: var PNode; n: PNode) =
+  if result.kind != nkStmtList:
+    result = makeStmtList(result)
+  result.add n
+
+proc addToVarSection(c: PContext; result: var PNode; orig, identDefs: PNode) =
   if result.kind == nkStmtList:
     let o = copyNode(orig)
     o.add identDefs
@@ -445,55 +449,115 @@ proc setVarType(c: PContext; v: PSym, typ: PType) =
         "; new type is: " & typeToString(typ, preferDesc))
   v.typ = typ
 
-proc semLowerLetVarCustomPragma(c: PContext, a: PNode, n: PNode): PNode =
-  var b = a[0]
-  if b.kind == nkPragmaExpr:
-    if b[1].len != 1:
-      # we could in future support pragmas w args e.g.: `var foo {.bar:"goo".} = expr`
-      return nil
-    let nodePragma = b[1][0]
-    # see: `singlePragma`
-
-    var amb = false
-    var sym: PSym = nil
-    case nodePragma.kind
-    of nkIdent, nkAccQuoted:
-      let ident = considerQuotedIdent(c, nodePragma)
-      var userPragma = strTableGet(c.userPragmas, ident)
-      if userPragma != nil: return nil
-      let w = nodePragma.whichPragma
-      if n.kind == nkVarSection and w in varPragmas or
-        n.kind == nkLetSection and w in letPragmas or
-        n.kind == nkConstSection and w in constPragmas:
-        return nil
-      sym = searchInScopes(c, ident, amb)
-      # XXX what if amb is true?
-      # CHECKME: should that test also apply to `nkSym` case?
-      if sym == nil or sfCustomPragma in sym.flags: return nil
-    of nkSym:
-      sym = nodePragma.sym
-    else:
-      return nil
-      # skip if not in scope; skip `template myAttr() {.pragma.}`
-
-    let lhs = b[0]
-    let clash = strTableGet(c.currentScope.symbols, lhs.ident)
-    if clash != nil:
-      # refs https://github.com/nim-lang/Nim/issues/8275
-      wrongRedefinition(c, lhs.info, lhs.ident.s, clash.info)
-
-    result = newTree(nkCall)
-    result.add nodePragma
-    result.add lhs
-    if a[1].kind != nkEmpty:
-      result.add a[1]
-    else:
-      result.add newNodeIT(nkNilLit, a.info, c.graph.sysTypes[tyNil])
-    result.add a[2]
-    result.info = a.info
-    let ret = newNodeI(nkStmtList, a.info)
-    ret.add result
-    result = semExprNoType(c, ret)
+proc isPossibleMacroPragma(c: PContext, it: PNode, key: PNode): bool =
+  # make sure it's not a normal pragma, and calls an identifier
+  # considerQuotedIdent below will fail on non-identifiers
+  result = whichPragma(it) == wInvalid and key.kind in nkIdentKinds
+  if result:
+    # make sure it's not a user pragma
+    let ident = considerQuotedIdent(c, key)
+    result = strTableGet(c.userPragmas, ident) == nil
+    if result:
+      # make sure it's not a custom pragma
+      var amb = false
+      let sym = searchInScopes(c, ident, amb)
+      result = sym == nil or sfCustomPragma notin sym.flags
+
+proc copyExcept(n: PNode, i: int): PNode =
+  result = copyNode(n)
+  for j in 0..<n.len:
+    if j != i: result.add(n[j])
+
+proc semVarMacroPragma(c: PContext, a: PNode, n: PNode): PNode =
+  # Mirrored with semProcAnnotation
+  result = nil
+  # a, b {.prag.}: int = 3 not allowed
+  const lhsPos = 0
+  if a.len == 3 and a[lhsPos].kind == nkPragmaExpr:
+    var b = a[lhsPos]
+    const
+      namePos = 0
+      pragmaPos = 1
+    let pragmas = b[pragmaPos]
+    for i in 0 ..< pragmas.len:
+      let it = pragmas[i]
+      let key = if it.kind in nkPragmaCallKinds and it.len >= 1: it[0] else: it
+      
+      when false:
+        let lhs = b[0]
+        let clash = strTableGet(c.currentScope.symbols, lhs.ident)
+        if clash != nil:
+          # refs https://github.com/nim-lang/Nim/issues/8275
+          wrongRedefinition(c, lhs.info, lhs.ident.s, clash.info)
+
+      if isPossibleMacroPragma(c, it, key):
+        # we transform ``var p {.m, rest.}`` into ``m(do: var p {.rest.})`` and
+        # let the semantic checker deal with it:
+        var x = newNodeI(nkCall, key.info)
+        x.add(key)
+
+        if it.kind in nkPragmaCallKinds and it.len > 1:
+          # pass pragma arguments to the macro too:
+          for i in 1..<it.len:
+            x.add(it[i])
+
+        # Drop the pragma from the list, this prevents getting caught in endless
+        # recursion when the nkCall is semanticized
+        let oldExpr = a[lhsPos]
+        let newPragmas = copyExcept(pragmas, i)
+        if newPragmas.kind != nkEmpty and newPragmas.len == 0:
+          a[lhsPos] = oldExpr[namePos]
+        else:
+          a[lhsPos] = copyNode(oldExpr)
+          a[lhsPos].add(oldExpr[namePos])
+          a[lhsPos].add(newPragmas)
+
+        var unarySection = newNodeI(n.kind, a.info)
+        unarySection.add(a)
+        x.add(unarySection)
+
+        # recursion assures that this works for multiple macro annotations too:
+        var r = semOverloadedCall(c, x, x, {skMacro, skTemplate}, {efNoUndeclared})
+        if r == nil:
+          # Restore the old list of pragmas since we couldn't process this
+          a[lhsPos] = oldExpr
+          # No matching macro was found but there's always the possibility this may
+          # be a .pragma. template instead
+          continue
+
+        doAssert r[0].kind == nkSym
+        let m = r[0].sym
+        case m.kind
+        of skMacro: result = semMacroExpr(c, r, r, m, {})
+        of skTemplate: result = semTemplateExpr(c, r, m, {})
+        else:
+          a[lhsPos] = oldExpr
+          continue
+
+        doAssert result != nil
+
+        # since a macro pragma can set pragmas, we process these here again.
+        # This is required for SqueakNim-like export pragmas.
+        if false and result.kind in {nkVarSection, nkLetSection, nkConstSection}:
+          var validPragmas: TSpecialWords
+          case result.kind
+          of nkVarSection:
+            validPragmas = varPragmas
+          of nkLetSection:
+            validPragmas = letPragmas
+          of nkConstSection:
+            validPragmas = constPragmas
+          else:
+            # unreachable
+            discard
+          for defs in result:
+            for i in 0 ..< defs.len - 2:
+              let ex = defs[i]
+              if ex.kind == nkPragmaExpr and
+                  ex[namePos].kind == nkSym and
+                  ex[pragmaPos].kind != nkEmpty:
+                pragma(c, defs[lhsPos][namePos].sym, defs[lhsPos][pragmaPos], validPragmas)
+        return result
 
 proc errorSymChoiceUseQualifier(c: PContext; n: PNode) =
   assert n.kind in nkSymChoices
@@ -508,10 +572,6 @@ proc errorSymChoiceUseQualifier(c: PContext; n: PNode) =
   localError(c.config, n.info, errGenerated, err)
 
 proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
-  if n.len == 1:
-    result = semLowerLetVarCustomPragma(c, n[0], n)
-    if result != nil: return result
-
   var b: PNode
   result = copyNode(n)
   for i in 0..<n.len:
@@ -521,6 +581,11 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
     if a.kind notin {nkIdentDefs, nkVarTuple}: illFormedAst(a, c.config)
     checkMinSonsLen(a, 3, c.config)
 
+    b = semVarMacroPragma(c, a, n)
+    if b != nil:
+      addToVarSection(c, result, b)
+      continue
+
     var typ: PType = nil
     if a[^2].kind != nkEmpty:
       typ = semTypeNode(c, a[^2], nil)
@@ -663,6 +728,7 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
 proc semConst(c: PContext, n: PNode): PNode =
   result = copyNode(n)
   inc c.inStaticContext
+  var b: PNode
   for i in 0..<n.len:
     var a = n[i]
     if c.config.cmd == cmdIdeTools: suggestStmt(c, a)
@@ -670,6 +736,11 @@ proc semConst(c: PContext, n: PNode): PNode =
     if a.kind notin {nkConstDef, nkVarTuple}: illFormedAst(a, c.config)
     checkMinSonsLen(a, 3, c.config)
 
+    b = semVarMacroPragma(c, a, n)
+    if b != nil:
+      addToVarSection(c, result, b)
+      continue
+
     var typ: PType = nil
     if a[^2].kind != nkEmpty:
       typ = semTypeNode(c, a[^2], nil)
@@ -704,7 +775,6 @@ proc semConst(c: PContext, n: PNode): PNode =
         typFlags.incl taConcept
       typeAllowedCheck(c, a.info, typ, skConst, typFlags)
 
-    var b: PNode
     if a.kind == nkVarTuple:
       if typ.kind != tyTuple:
         localError(c.config, a.info, errXExpected, "tuple")
@@ -735,7 +805,7 @@ proc semConst(c: PContext, n: PNode): PNode =
         v.ast = if def[j].kind != nkExprColonExpr: def[j]
                 else: def[j][1]
         b[j] = newSymNode(v)
-    result.add b
+    addToVarSection(c, result, n, b)
   dec c.inStaticContext
 
 include semfields
@@ -1519,77 +1589,61 @@ proc addResult(c: PContext, n: PNode, t: PType, owner: TSymKind) =
       n.add newSymNode(c.p.resultSym)
     addParamOrResult(c, c.p.resultSym, owner)
 
-proc copyExcept(n: PNode, i: int): PNode =
-  result = copyNode(n)
-  for j in 0..<n.len:
-    if j != i: result.add(n[j])
-
 proc semProcAnnotation(c: PContext, prc: PNode;
                        validPragmas: TSpecialWords): PNode =
+  # Mirrored with semVarMacroPragma
   var n = prc[pragmasPos]
   if n == nil or n.kind == nkEmpty: return
   for i in 0..<n.len:
     let it = n[i]
     let key = if it.kind in nkPragmaCallKinds and it.len >= 1: it[0] else: it
 
-    if whichPragma(it) != wInvalid:
-      # Not a custom pragma
-      continue
-    else:
-      let ident = considerQuotedIdent(c, key)
-      if strTableGet(c.userPragmas, ident) != nil:
-        continue # User defined pragma
-      else:
-        var amb = false
-        let sym = searchInScopes(c, ident, amb)
-        if sym != nil and sfCustomPragma in sym.flags:
-          continue # User custom pragma
-
-    # we transform ``proc p {.m, rest.}`` into ``m(do: proc p {.rest.})`` and
-    # let the semantic checker deal with it:
-    var x = newNodeI(nkCall, key.info)
-    x.add(key)
-
-    if it.kind in nkPragmaCallKinds and it.len > 1:
-      # pass pragma arguments to the macro too:
-      for i in 1..<it.len:
-        x.add(it[i])
-
-    # Drop the pragma from the list, this prevents getting caught in endless
-    # recursion when the nkCall is semanticized
-    prc[pragmasPos] = copyExcept(n, i)
-    if prc[pragmasPos].kind != nkEmpty and prc[pragmasPos].len == 0:
-      prc[pragmasPos] = c.graph.emptyNode
-
-    x.add(prc)
-
-    # recursion assures that this works for multiple macro annotations too:
-    var r = semOverloadedCall(c, x, x, {skMacro, skTemplate}, {efNoUndeclared})
-    if r == nil:
-      # Restore the old list of pragmas since we couldn't process this
-      prc[pragmasPos] = n
-      # No matching macro was found but there's always the possibility this may
-      # be a .pragma. template instead
-      continue
+    if isPossibleMacroPragma(c, it, key):
+      # we transform ``proc p {.m, rest.}`` into ``m(do: proc p {.rest.})`` and
+      # let the semantic checker deal with it:
+      var x = newNodeI(nkCall, key.info)
+      x.add(key)
+
+      if it.kind in nkPragmaCallKinds and it.len > 1:
+        # pass pragma arguments to the macro too:
+        for i in 1..<it.len:
+          x.add(it[i])
+
+      # Drop the pragma from the list, this prevents getting caught in endless
+      # recursion when the nkCall is semanticized
+      prc[pragmasPos] = copyExcept(n, i)
+      if prc[pragmasPos].kind != nkEmpty and prc[pragmasPos].len == 0:
+        prc[pragmasPos] = c.graph.emptyNode
+
+      x.add(prc)
+
+      # recursion assures that this works for multiple macro annotations too:
+      var r = semOverloadedCall(c, x, x, {skMacro, skTemplate}, {efNoUndeclared})
+      if r == nil:
+        # Restore the old list of pragmas since we couldn't process this
+        prc[pragmasPos] = n
+        # No matching macro was found but there's always the possibility this may
+        # be a .pragma. template instead
+        continue
 
-    doAssert r[0].kind == nkSym
-    let m = r[0].sym
-    case m.kind
-    of skMacro: result = semMacroExpr(c, r, r, m, {})
-    of skTemplate: result = semTemplateExpr(c, r, m, {})
-    else:
-      prc[pragmasPos] = n
-      continue
+      doAssert r[0].kind == nkSym
+      let m = r[0].sym
+      case m.kind
+      of skMacro: result = semMacroExpr(c, r, r, m, {})
+      of skTemplate: result = semTemplateExpr(c, r, m, {})
+      else:
+        prc[pragmasPos] = n
+        continue
 
-    doAssert result != nil
+      doAssert result != nil
 
-    # since a proc annotation can set pragmas, we process these here again.
-    # This is required for SqueakNim-like export pragmas.
-    if result.kind in procDefs and result[namePos].kind == nkSym and
-        result[pragmasPos].kind != nkEmpty:
-      pragma(c, result[namePos].sym, result[pragmasPos], validPragmas)
+      # since a proc annotation can set pragmas, we process these here again.
+      # This is required for SqueakNim-like export pragmas.
+      if false and result.kind in procDefs and result[namePos].kind == nkSym and
+          result[pragmasPos].kind != nkEmpty:
+        pragma(c, result[namePos].sym, result[pragmasPos], validPragmas)
 
-    return
+      return result
 
 proc semInferredLambda(c: PContext, pt: TIdTable, n: PNode): PNode {.nosinks.} =
   ## used for resolving 'auto' in lambdas based on their callsite
diff --git a/compiler/semtempl.nim b/compiler/semtempl.nim
index b921fda2c..e7c74a767 100644
--- a/compiler/semtempl.nim
+++ b/compiler/semtempl.nim
@@ -603,7 +603,12 @@ proc semTemplBodyDirty(c: var TemplCtx, n: PNode): PNode =
     for i in 0..<n.len:
       result[i] = semTemplBodyDirty(c, n[i])
 
+# in semstmts.nim:
+proc semProcAnnotation(c: PContext, prc: PNode; validPragmas: TSpecialWords): PNode
+
 proc semTemplateDef(c: PContext, n: PNode): PNode =
+  result = semProcAnnotation(c, n, templatePragmas)
+  if result != nil: return result
   result = n
   var s: PSym
   if isTopLevel(c):
diff --git a/doc/manual.rst b/doc/manual.rst
index 4eabd0225..2469a0525 100644
--- a/doc/manual.rst
+++ b/doc/manual.rst
@@ -7781,9 +7781,10 @@ More examples with custom pragmas:
 Macro pragmas
 -------------
 
-All macros and templates can also be used as pragmas. They can be attached
-to routines (procs, iterators, etc), type names, or type expressions. The
-compiler will perform the following simple syntactic transformations:
+Macros and templates can sometimes be called with the pragma syntax. Cases
+where this is possible include when attached to routine (procs, iterators, etc)
+declarations or routine type expressions. The compiler will perform the
+following simple syntactic transformations:
 
 .. code-block:: nim
   template command(name: string, def: untyped) = discard
@@ -7810,20 +7811,15 @@ This is translated to:
 
 ------
 
-.. code-block:: nim
-  type
-    MyObject {.schema: "schema.protobuf".} = object
-
-This is translated to a call to the `schema` macro with a `nnkTypeDef`
-AST node capturing both the left-hand side and right-hand side of the
-definition. The macro can return a potentially modified `nnkTypeDef` tree
-or multiple `nnkTypeDef` trees contained in a `nnkTypeSection` node
-which will replace the original row in the type section.
-
-When multiple macro pragmas are applied to the same definition, the
-compiler will apply them consequently from left to right. Each macro
-will receive as input the output of the previous one.
+When multiple macro pragmas are applied to the same definition, the first one
+from left to right will be evaluated. This macro can then choose to keep
+the remaining macro pragmas in its output, and those will be evaluated in
+the same way.
 
+There are a few more applications of macro pragmas, such as in type,
+variable and constant declarations, but this behavior is considered to be
+experimental and is documented in the `experimental manual
+<manual_experimental.html#extended-macro-pragmas>` instead.
 
 
 Foreign function interface
diff --git a/doc/manual_experimental.rst b/doc/manual_experimental.rst
index 407924a13..3089755cb 100644
--- a/doc/manual_experimental.rst
+++ b/doc/manual_experimental.rst
@@ -404,6 +404,76 @@ to use this operator.
     doAssert (a.b)(c) == `()`(a.b, c)
 
 
+Extended macro pragmas
+======================
+
+Macro pragmas as described in `the manual <manual.html#userminusdefined-pragmas-macro-pragmas>`_
+can also be applied to type, variable and constant declarations.
+
+For types:
+
+.. code-block:: nim
+  type
+    MyObject {.schema: "schema.protobuf".} = object
+
+This is translated to a call to the `schema` macro with a `nnkTypeDef`
+AST node capturing the left-hand side, remaining pragmas and the right-hand
+side of the definition. The macro can return either a type section or
+another `nnkTypeDef` node, both of which will replace the original row
+in the type section.
+
+In the future, this `nnkTypeDef` argument may be replaced with a unary
+type section node containing the type definition, or some other node that may
+be more convenient to work with. The ability to return nodes other than type
+definitions may also be supported, however currently this is not convenient
+when dealing with mutual type recursion. For now, macros can return an unused
+type definition where the right-hand node is of kind `nnkStmtListType`.
+Declarations in this node will be attached to the same scope as
+the parent scope of the type section.
+
+------
+
+For variables and constants, it is largely the same, except a unary node with
+the same kind as the section containing a single definition is passed to macros,
+and macros can return any expression.
+
+.. code-block:: nim
+  var
+    a = ...
+    b {.importc, foo, nodecl.} = ...
+    c = ...
+
+Assuming `foo` is a macro or a template, this is roughly equivalent to:
+
+.. code-block:: nim
+  var a = ...
+  foo:
+    var b {.importc, nodecl.} = ...
+  var c = ...
+
+
+Symbols as template/macro calls
+===============================
+
+Templates and macros that take no arguments can be called as lone symbols,
+i.e. without parentheses. This is useful for repeated uses of complex
+expressions that cannot conveniently be represented as runtime values.
+
+.. code-block:: nim
+  type Foo = object
+    bar: int
+  
+  var foo = Foo(bar: 10)
+  template bar: untyped = foo.bar
+  assert bar == 10
+  bar = 15
+  assert bar == 15
+
+In the future, this may require more specific information on template or macro
+signatures to be used. Specializations for some applications of this may also
+be introduced to guarantee consistency and circumvent bugs.
+
+
 Not nil annotation
 ==================
 
@@ -613,7 +683,7 @@ has `source` as the owner. A path expression `e` is defined recursively:
 
 If a view type is used as a return type, the location must borrow from a location
 that is derived from the first parameter that is passed to the proc.
-See `the manual <https://nim-lang.org/docs/manual.html#procedures-var-return-type>`_
+See `the manual <manual.html#procedures-var-return-type>`_
 for details about how this is done for `var T`.
 
 A mutable view can borrow from a mutable location, an immutable view can borrow
diff --git a/lib/std/decls.nim b/lib/std/decls.nim
index dd7d19da7..7b907f5e1 100644
--- a/lib/std/decls.nim
+++ b/lib/std/decls.nim
@@ -1,19 +1,32 @@
 # see `semLowerLetVarCustomPragma` for compiler support that enables these
 # lowerings
 
-template byaddr*(lhs, typ, ex) =
-  ## Allows a syntax for lvalue reference, exact analog to
-  ## `auto& a = ex;` in C++
+import macros
+
+macro byaddr*(sect) =
+  ## Allows a syntax for l-value references, being an exact analog to
+  ## `auto& a = ex;` in C++.
+  ## 
+  ## Warning: This makes use of 2 experimental features, namely nullary
+  ## templates instantiated as symbols and variable macro pragmas.
+  ## For this reason, its behavior is not stable. The current implementation
+  ## allows redefinition, but this is not an intended consequence.
   runnableExamples:
-    var s = @[10,11,12]
+    var s = @[10, 11, 12]
     var a {.byaddr.} = s[0]
-    a+=100
-    doAssert s == @[110,11,12]
-    doAssert a is int
+    a += 100
+    assert s == @[110, 11, 12]
+    assert a is int
     var b {.byaddr.}: int = s[0]
-    doAssert a.addr == b.addr
-  when typ is typeof(nil):
-    let tmp = addr(ex)
-  else:
-    let tmp: ptr typ = addr(ex)
-  template lhs: untyped = tmp[]
+    assert a.addr == b.addr
+  expectLen sect, 1
+  let def = sect[0]
+  let
+    lhs = def[0]
+    typ = def[1]
+    ex = def[2]
+    addrTyp = if typ.kind == nnkEmpty: typ else: newTree(nnkPtrTy, typ)
+  result = quote do:
+    let tmp: `addrTyp` = addr(`ex`)
+    template `lhs`: untyped = tmp[]
+  result.copyLineInfo(def)
diff --git a/tests/pragmas/tpragmas_misc.nim b/tests/pragmas/tpragmas_misc.nim
index 8cab74053..6dc2e6b80 100644
--- a/tests/pragmas/tpragmas_misc.nim
+++ b/tests/pragmas/tpragmas_misc.nim
@@ -13,8 +13,8 @@ block:
 
 block: # (partial fix) bug #15920
   block: # var template pragmas don't work in templates
-    template foo(lhs, typ, expr) =
-      let lhs = expr
+    template foo(expr) =
+      expr
     proc fun1()=
       let a {.foo.} = 1
     template fun2()=
@@ -24,23 +24,22 @@ block: # (partial fix) bug #15920
 
   template foo2() = discard # distractor (template or other symbol kind)
   block:
-    template foo2(lhs, typ, expr) =
-      let lhs = expr
+    template foo2(expr) =
+      expr
     proc fun1()=
       let a {.foo2.} = 1
     template fun2()=
       let a {.foo2.} = 1
     fun1() # ok
-    when false: # bug: Error: invalid pragma: foo2
-      fun2()
+    fun2() # bug: Error: invalid pragma: foo2
 
-  block: # proc template pragmas don't work in templates
+  block: # template pragmas don't work for templates, #18212
     # adapted from $nim/lib/std/private/since.nim
     # case without overload
     template since3(version: (int, int), body: untyped) {.dirty.} =
       when (NimMajor, NimMinor) >= version:
         body
-    when false: # bug
+    when true: # bug
       template fun3(): int {.since3: (1, 3).} = 12
 
   block: # ditto, w
@@ -51,7 +50,7 @@ block: # (partial fix) bug #15920
     template since2(version: (int, int, int), body: untyped) {.dirty.} =
       when (NimMajor, NimMinor, NimPatch) >= version:
         body
-    when false: # bug
+    when true: # bug
       template fun3(): int {.since2: (1, 3).} = 12
 
 when true: # D20210801T100514:here
@@ -62,3 +61,10 @@ when true: # D20210801T100514:here
       discard ret
     fn()
     static: discard genSym()
+
+block: # issue #10994
+  macro foo(x): untyped = x
+  template bar {.pragma.}
+
+  proc a {.bar.} = discard # works
+  proc b {.bar, foo.} = discard # doesn't
diff --git a/tests/pragmas/tvar_macro.nim b/tests/pragmas/tvar_macro.nim
new file mode 100644
index 000000000..d6a4ff983
--- /dev/null
+++ b/tests/pragmas/tvar_macro.nim
@@ -0,0 +1,128 @@
+import macros
+
+block: # test usage
+  macro modify(sec) =
+    result = copy sec
+    result[0][0] = ident(repr(result[0][0]) & "Modified")
+
+  block:
+    let foo {.modify.} = 3
+    doAssert fooModified == 3
+
+  block: # in section 
+    let
+      a = 1
+      b {.modify.} = 2
+      c = 3
+    doAssert (a, bModified, c) == (1, 2, 3)
+
+block: # with single argument
+  macro appendToName(name: static string, sec) =
+    result = sec
+    result[0][0] = ident(repr(result[0][0]) & name)
+
+  block:
+    let foo {.appendToName: "Bar".} = 3
+    doAssert fooBar == 3
+
+  block:
+    let
+      a = 1
+      b {.appendToName("").} = 2
+      c = 3
+    doAssert (a, b, c) == (1, 2, 3)
+
+macro appendToNameAndAdd(name: static string, incr: static int, sec) =
+  result = sec
+  result[0][0] = ident(repr(result[0][0]) & name)
+  result[0][2] = infix(result[0][2], "+", newLit(incr))
+
+block: # with multiple arguments
+  block:
+    let foo {.appendToNameAndAdd("Bar", 5).} = 3
+    doAssert fooBar == 8
+
+  block:
+    let
+      a = 1
+      b {.appendToNameAndAdd("", 15).} = 2
+      c = 3
+    doAssert (a, b, c) == (1, 17, 3)
+
+block: # in other kinds of sections
+  block:
+    const
+      a = 1
+      b {.appendToNameAndAdd("", 15).} = 2
+      c = 3
+    doAssert (a, b, c) == (1, 17, 3)
+    doAssert static(b) == b
+
+  block:
+    var
+      a = 1
+      b {.appendToNameAndAdd("", 15).} = 2
+      c = 3
+    doAssert (a, b, c) == (1, 17, 3)
+    b += a
+    c += b
+    doAssert (a, b, c) == (1, 18, 21)
+
+block: # with other pragmas
+  macro appendToNameAndAdd(name: static string, incr, sec) =
+    result = sec
+    result[0][0][0] = ident(repr(result[0][0][0]) & name)
+    result[0][0][1].add(ident"deprecated")
+    result[0][2] = infix(result[0][2], "+", incr)
+
+  var
+    a = 1
+    foo {.exportc: "exportedFooBar", appendToNameAndAdd("Bar", {'0'..'9'}), used.} = {'a'..'z', 'A'..'Z'}
+    b = 2
+  
+  doAssert (a, b) == (1, 2)
+
+  let importedFooBar {.importc: "exportedFooBar", nodecl.}: set[char]
+
+  doAssert importedFooBar == fooBar #[tt.Warning
+                            ^ fooBar is deprecated
+  ]#
+  
+
+block: # with stropping
+  macro `cast`(def) =
+    let def = def[0]
+    let
+      lhs = def[0]
+      typ = def[1]
+      ex = def[2]
+      addrTyp = if typ.kind == nnkEmpty: typ else: newTree(nnkPtrTy, typ)
+    result = quote do:
+      let tmp: `addrTyp` = unsafeAddr(`ex`)
+      template `lhs`: untyped = tmp[]
+  
+  macro assign(def) =
+    result = getAst(`cast`(def))
+
+  block:
+    let s = @["foo", "bar"]
+    let a {.`assign`.} = s[0]
+    doAssert a == "foo"
+    doAssert a[0].addr == s[0][0].addr
+
+  block:
+    let
+      s = @["foo", "bar"]
+      a {.`cast`.} = s[0]
+    doAssert a == "foo"
+    doAssert a[0].addr == s[0][0].addr
+
+block: # bug #15920
+  macro foo(def) =
+    result = def
+  proc fun1()=
+    let a {.foo.} = 1
+  template fun2()=
+    let a {.foo.} = 1
+  fun1() # ok
+  fun2() # BUG
diff --git a/tests/stdlib/tdecls.nim b/tests/stdlib/tdecls.nim
index c0d6f8a08..4e7407045 100644
--- a/tests/stdlib/tdecls.nim
+++ b/tests/stdlib/tdecls.nim
@@ -13,15 +13,18 @@ template fun() =
   var b {.byaddr.}: int = s[0]
   doAssert a.addr == b.addr
 
-  doAssert not compiles(block:
-    # redeclaration not allowed
-    var foo = 0
-    var foo {.byaddr.} = s[0])
-
-  doAssert not compiles(block:
-    # ditto
-    var foo {.byaddr.} = s[0]
-    var foo {.byaddr.} = s[0])
+  when false:
+    # template specific redeclaration issue
+    # see https://github.com/nim-lang/Nim/issues/8275
+    doAssert not compiles(block:
+      # redeclaration not allowed
+      var foo = 0
+      var foo {.byaddr.} = s[0])
+
+    doAssert not compiles(block:
+      # ditto
+      var foo {.byaddr.} = s[0]
+      var foo {.byaddr.} = s[0])
 
   block:
     var b {.byaddr.} = s[1] # redeclaration ok in sub scope
@@ -44,48 +47,3 @@ fun2()
 static: fun2()
 when false: # pending bug #13887
   static: fun()
-
-## We can define custom pragmas in user code
-template byUnsafeAddr(lhs, typ, expr) =
-  when typ is type(nil):
-    let tmp = addr(expr)
-  else:
-    let tmp: ptr typ = addr(expr)
-  template lhs: untyped = tmp[]
-
-block:
-  let s = @["foo", "bar"]
-  let a {.byUnsafeAddr.} = s[0]
-  doAssert a == "foo"
-  doAssert a[0].addr == s[0][0].addr
-
-block: # nkAccQuoted
-  # shows using a keyword, which requires nkAccQuoted
-  template `cast`(lhs, typ, expr) =
-    when typ is type(nil):
-      let tmp = addr(expr)
-    else:
-      let tmp: ptr typ = addr(expr)
-    template lhs: untyped = tmp[]
-
-  block:
-    let s = @["foo", "bar"]
-    let a {.`byUnsafeAddr`.} = s[0]
-    doAssert a == "foo"
-    doAssert a[0].addr == s[0][0].addr
-
-  block:
-    let s = @["foo", "bar"]
-    let a {.`cast`.} = s[0]
-    doAssert a == "foo"
-    doAssert a[0].addr == s[0][0].addr
-
-block: # bug #15920
-  template foo(lhs, typ, expr) =
-    let lhs = expr
-  proc fun1()=
-    let a {.foo.} = 1
-  template fun2()=
-    let a {.foo.} = 1
-  fun1() # ok
-  fun2() # BUG