New/better macro pragmas, mark some as experimental (#19406)
* New/better macro pragmas, make some experimental fix #15920, close #18212, close #14781, close #6696, close https://github.com/nim-lang/RFCs/issues/220 Variable macro pragmas have been changed to only take a unary section node. They can now also be applied in sections with multiple variables, as well as `const` sections. They also accept arguments. Templates now support macro pragmas, mirroring other routine types. Type and variable macro pragmas have been made experimental. Symbols without parentheses instatiating nullary macros or templates has also been documented in the experimental manual. A check for a redefinition error based on the left hand side of variable definitions when using variable macro pragmas was disabled. This nerfs `byaddr` specifically, however this has been documented as a consequence of the experimental features `byaddr` uses. Given how simple these changes are I'm worried if I'm missing something. * accomodate compiler boot * allow weird pragmas * add test for #10994 * remove some control flow, try remove some logic
This commit is contained in:
parent
1563cb2f6e
commit
2bd1aa186e
8 changed files with 435 additions and 205 deletions
|
|
@ -341,8 +341,12 @@ proc checkNilable(c: PContext; v: PSym) =
|
||||||
|
|
||||||
#include liftdestructors
|
#include liftdestructors
|
||||||
|
|
||||||
proc addToVarSection(c: PContext; result: PNode; orig, identDefs: PNode) =
|
proc addToVarSection(c: PContext; result: var PNode; n: PNode) =
|
||||||
let value = identDefs[^1]
|
if result.kind != nkStmtList:
|
||||||
|
result = makeStmtList(result)
|
||||||
|
result.add n
|
||||||
|
|
||||||
|
proc addToVarSection(c: PContext; result: var PNode; orig, identDefs: PNode) =
|
||||||
if result.kind == nkStmtList:
|
if result.kind == nkStmtList:
|
||||||
let o = copyNode(orig)
|
let o = copyNode(orig)
|
||||||
o.add identDefs
|
o.add identDefs
|
||||||
|
|
@ -445,55 +449,115 @@ proc setVarType(c: PContext; v: PSym, typ: PType) =
|
||||||
"; new type is: " & typeToString(typ, preferDesc))
|
"; new type is: " & typeToString(typ, preferDesc))
|
||||||
v.typ = typ
|
v.typ = typ
|
||||||
|
|
||||||
proc semLowerLetVarCustomPragma(c: PContext, a: PNode, n: PNode): PNode =
|
proc isPossibleMacroPragma(c: PContext, it: PNode, key: PNode): bool =
|
||||||
var b = a[0]
|
# make sure it's not a normal pragma, and calls an identifier
|
||||||
if b.kind == nkPragmaExpr:
|
# considerQuotedIdent below will fail on non-identifiers
|
||||||
if b[1].len != 1:
|
result = whichPragma(it) == wInvalid and key.kind in nkIdentKinds
|
||||||
# we could in future support pragmas w args e.g.: `var foo {.bar:"goo".} = expr`
|
if result:
|
||||||
return nil
|
# make sure it's not a user pragma
|
||||||
let nodePragma = b[1][0]
|
let ident = considerQuotedIdent(c, key)
|
||||||
# see: `singlePragma`
|
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
|
||||||
|
|
||||||
var amb = false
|
proc copyExcept(n: PNode, i: int): PNode =
|
||||||
var sym: PSym = nil
|
result = copyNode(n)
|
||||||
case nodePragma.kind
|
for j in 0..<n.len:
|
||||||
of nkIdent, nkAccQuoted:
|
if j != i: result.add(n[j])
|
||||||
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]
|
proc semVarMacroPragma(c: PContext, a: PNode, n: PNode): PNode =
|
||||||
let clash = strTableGet(c.currentScope.symbols, lhs.ident)
|
# Mirrored with semProcAnnotation
|
||||||
if clash != nil:
|
result = nil
|
||||||
# refs https://github.com/nim-lang/Nim/issues/8275
|
# a, b {.prag.}: int = 3 not allowed
|
||||||
wrongRedefinition(c, lhs.info, lhs.ident.s, clash.info)
|
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
|
||||||
|
|
||||||
result = newTree(nkCall)
|
when false:
|
||||||
result.add nodePragma
|
let lhs = b[0]
|
||||||
result.add lhs
|
let clash = strTableGet(c.currentScope.symbols, lhs.ident)
|
||||||
if a[1].kind != nkEmpty:
|
if clash != nil:
|
||||||
result.add a[1]
|
# refs https://github.com/nim-lang/Nim/issues/8275
|
||||||
else:
|
wrongRedefinition(c, lhs.info, lhs.ident.s, clash.info)
|
||||||
result.add newNodeIT(nkNilLit, a.info, c.graph.sysTypes[tyNil])
|
|
||||||
result.add a[2]
|
if isPossibleMacroPragma(c, it, key):
|
||||||
result.info = a.info
|
# we transform ``var p {.m, rest.}`` into ``m(do: var p {.rest.})`` and
|
||||||
let ret = newNodeI(nkStmtList, a.info)
|
# let the semantic checker deal with it:
|
||||||
ret.add result
|
var x = newNodeI(nkCall, key.info)
|
||||||
result = semExprNoType(c, ret)
|
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) =
|
proc errorSymChoiceUseQualifier(c: PContext; n: PNode) =
|
||||||
assert n.kind in nkSymChoices
|
assert n.kind in nkSymChoices
|
||||||
|
|
@ -508,10 +572,6 @@ proc errorSymChoiceUseQualifier(c: PContext; n: PNode) =
|
||||||
localError(c.config, n.info, errGenerated, err)
|
localError(c.config, n.info, errGenerated, err)
|
||||||
|
|
||||||
proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
|
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
|
var b: PNode
|
||||||
result = copyNode(n)
|
result = copyNode(n)
|
||||||
for i in 0..<n.len:
|
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)
|
if a.kind notin {nkIdentDefs, nkVarTuple}: illFormedAst(a, c.config)
|
||||||
checkMinSonsLen(a, 3, c.config)
|
checkMinSonsLen(a, 3, c.config)
|
||||||
|
|
||||||
|
b = semVarMacroPragma(c, a, n)
|
||||||
|
if b != nil:
|
||||||
|
addToVarSection(c, result, b)
|
||||||
|
continue
|
||||||
|
|
||||||
var typ: PType = nil
|
var typ: PType = nil
|
||||||
if a[^2].kind != nkEmpty:
|
if a[^2].kind != nkEmpty:
|
||||||
typ = semTypeNode(c, a[^2], nil)
|
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 =
|
proc semConst(c: PContext, n: PNode): PNode =
|
||||||
result = copyNode(n)
|
result = copyNode(n)
|
||||||
inc c.inStaticContext
|
inc c.inStaticContext
|
||||||
|
var b: PNode
|
||||||
for i in 0..<n.len:
|
for i in 0..<n.len:
|
||||||
var a = n[i]
|
var a = n[i]
|
||||||
if c.config.cmd == cmdIdeTools: suggestStmt(c, a)
|
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)
|
if a.kind notin {nkConstDef, nkVarTuple}: illFormedAst(a, c.config)
|
||||||
checkMinSonsLen(a, 3, c.config)
|
checkMinSonsLen(a, 3, c.config)
|
||||||
|
|
||||||
|
b = semVarMacroPragma(c, a, n)
|
||||||
|
if b != nil:
|
||||||
|
addToVarSection(c, result, b)
|
||||||
|
continue
|
||||||
|
|
||||||
var typ: PType = nil
|
var typ: PType = nil
|
||||||
if a[^2].kind != nkEmpty:
|
if a[^2].kind != nkEmpty:
|
||||||
typ = semTypeNode(c, a[^2], nil)
|
typ = semTypeNode(c, a[^2], nil)
|
||||||
|
|
@ -704,7 +775,6 @@ proc semConst(c: PContext, n: PNode): PNode =
|
||||||
typFlags.incl taConcept
|
typFlags.incl taConcept
|
||||||
typeAllowedCheck(c, a.info, typ, skConst, typFlags)
|
typeAllowedCheck(c, a.info, typ, skConst, typFlags)
|
||||||
|
|
||||||
var b: PNode
|
|
||||||
if a.kind == nkVarTuple:
|
if a.kind == nkVarTuple:
|
||||||
if typ.kind != tyTuple:
|
if typ.kind != tyTuple:
|
||||||
localError(c.config, a.info, errXExpected, "tuple")
|
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]
|
v.ast = if def[j].kind != nkExprColonExpr: def[j]
|
||||||
else: def[j][1]
|
else: def[j][1]
|
||||||
b[j] = newSymNode(v)
|
b[j] = newSymNode(v)
|
||||||
result.add b
|
addToVarSection(c, result, n, b)
|
||||||
dec c.inStaticContext
|
dec c.inStaticContext
|
||||||
|
|
||||||
include semfields
|
include semfields
|
||||||
|
|
@ -1519,77 +1589,61 @@ proc addResult(c: PContext, n: PNode, t: PType, owner: TSymKind) =
|
||||||
n.add newSymNode(c.p.resultSym)
|
n.add newSymNode(c.p.resultSym)
|
||||||
addParamOrResult(c, c.p.resultSym, owner)
|
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;
|
proc semProcAnnotation(c: PContext, prc: PNode;
|
||||||
validPragmas: TSpecialWords): PNode =
|
validPragmas: TSpecialWords): PNode =
|
||||||
|
# Mirrored with semVarMacroPragma
|
||||||
var n = prc[pragmasPos]
|
var n = prc[pragmasPos]
|
||||||
if n == nil or n.kind == nkEmpty: return
|
if n == nil or n.kind == nkEmpty: return
|
||||||
for i in 0..<n.len:
|
for i in 0..<n.len:
|
||||||
let it = n[i]
|
let it = n[i]
|
||||||
let key = if it.kind in nkPragmaCallKinds and it.len >= 1: it[0] else: it
|
let key = if it.kind in nkPragmaCallKinds and it.len >= 1: it[0] else: it
|
||||||
|
|
||||||
if whichPragma(it) != wInvalid:
|
if isPossibleMacroPragma(c, it, key):
|
||||||
# Not a custom pragma
|
# we transform ``proc p {.m, rest.}`` into ``m(do: proc p {.rest.})`` and
|
||||||
continue
|
# let the semantic checker deal with it:
|
||||||
else:
|
var x = newNodeI(nkCall, key.info)
|
||||||
let ident = considerQuotedIdent(c, key)
|
x.add(key)
|
||||||
if strTableGet(c.userPragmas, ident) != nil:
|
|
||||||
continue # User defined pragma
|
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:
|
else:
|
||||||
var amb = false
|
prc[pragmasPos] = n
|
||||||
let sym = searchInScopes(c, ident, amb)
|
continue
|
||||||
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
|
doAssert result != nil
|
||||||
# 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:
|
# since a proc annotation can set pragmas, we process these here again.
|
||||||
# pass pragma arguments to the macro too:
|
# This is required for SqueakNim-like export pragmas.
|
||||||
for i in 1..<it.len:
|
if false and result.kind in procDefs and result[namePos].kind == nkSym and
|
||||||
x.add(it[i])
|
result[pragmasPos].kind != nkEmpty:
|
||||||
|
pragma(c, result[namePos].sym, result[pragmasPos], validPragmas)
|
||||||
|
|
||||||
# Drop the pragma from the list, this prevents getting caught in endless
|
return result
|
||||||
# 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 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)
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
proc semInferredLambda(c: PContext, pt: TIdTable, n: PNode): PNode {.nosinks.} =
|
proc semInferredLambda(c: PContext, pt: TIdTable, n: PNode): PNode {.nosinks.} =
|
||||||
## used for resolving 'auto' in lambdas based on their callsite
|
## used for resolving 'auto' in lambdas based on their callsite
|
||||||
|
|
|
||||||
|
|
@ -603,7 +603,12 @@ proc semTemplBodyDirty(c: var TemplCtx, n: PNode): PNode =
|
||||||
for i in 0..<n.len:
|
for i in 0..<n.len:
|
||||||
result[i] = semTemplBodyDirty(c, n[i])
|
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 =
|
proc semTemplateDef(c: PContext, n: PNode): PNode =
|
||||||
|
result = semProcAnnotation(c, n, templatePragmas)
|
||||||
|
if result != nil: return result
|
||||||
result = n
|
result = n
|
||||||
var s: PSym
|
var s: PSym
|
||||||
if isTopLevel(c):
|
if isTopLevel(c):
|
||||||
|
|
|
||||||
|
|
@ -7781,9 +7781,10 @@ More examples with custom pragmas:
|
||||||
Macro pragmas
|
Macro pragmas
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
All macros and templates can also be used as pragmas. They can be attached
|
Macros and templates can sometimes be called with the pragma syntax. Cases
|
||||||
to routines (procs, iterators, etc), type names, or type expressions. The
|
where this is possible include when attached to routine (procs, iterators, etc)
|
||||||
compiler will perform the following simple syntactic transformations:
|
declarations or routine type expressions. The compiler will perform the
|
||||||
|
following simple syntactic transformations:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
template command(name: string, def: untyped) = discard
|
template command(name: string, def: untyped) = discard
|
||||||
|
|
@ -7810,20 +7811,15 @@ This is translated to:
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
.. code-block:: nim
|
When multiple macro pragmas are applied to the same definition, the first one
|
||||||
type
|
from left to right will be evaluated. This macro can then choose to keep
|
||||||
MyObject {.schema: "schema.protobuf".} = object
|
the remaining macro pragmas in its output, and those will be evaluated in
|
||||||
|
the same way.
|
||||||
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.
|
|
||||||
|
|
||||||
|
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
|
Foreign function interface
|
||||||
|
|
|
||||||
|
|
@ -404,6 +404,76 @@ to use this operator.
|
||||||
doAssert (a.b)(c) == `()`(a.b, c)
|
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
|
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
|
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.
|
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`.
|
for details about how this is done for `var T`.
|
||||||
|
|
||||||
A mutable view can borrow from a mutable location, an immutable view can borrow
|
A mutable view can borrow from a mutable location, an immutable view can borrow
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,32 @@
|
||||||
# see `semLowerLetVarCustomPragma` for compiler support that enables these
|
# see `semLowerLetVarCustomPragma` for compiler support that enables these
|
||||||
# lowerings
|
# lowerings
|
||||||
|
|
||||||
template byaddr*(lhs, typ, ex) =
|
import macros
|
||||||
## Allows a syntax for lvalue reference, exact analog to
|
|
||||||
## `auto& a = ex;` in C++
|
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:
|
runnableExamples:
|
||||||
var s = @[10,11,12]
|
var s = @[10, 11, 12]
|
||||||
var a {.byaddr.} = s[0]
|
var a {.byaddr.} = s[0]
|
||||||
a+=100
|
a += 100
|
||||||
doAssert s == @[110,11,12]
|
assert s == @[110, 11, 12]
|
||||||
doAssert a is int
|
assert a is int
|
||||||
var b {.byaddr.}: int = s[0]
|
var b {.byaddr.}: int = s[0]
|
||||||
doAssert a.addr == b.addr
|
assert a.addr == b.addr
|
||||||
when typ is typeof(nil):
|
expectLen sect, 1
|
||||||
let tmp = addr(ex)
|
let def = sect[0]
|
||||||
else:
|
let
|
||||||
let tmp: ptr typ = addr(ex)
|
lhs = def[0]
|
||||||
template lhs: untyped = tmp[]
|
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)
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@ block:
|
||||||
|
|
||||||
block: # (partial fix) bug #15920
|
block: # (partial fix) bug #15920
|
||||||
block: # var template pragmas don't work in templates
|
block: # var template pragmas don't work in templates
|
||||||
template foo(lhs, typ, expr) =
|
template foo(expr) =
|
||||||
let lhs = expr
|
expr
|
||||||
proc fun1()=
|
proc fun1()=
|
||||||
let a {.foo.} = 1
|
let a {.foo.} = 1
|
||||||
template fun2()=
|
template fun2()=
|
||||||
|
|
@ -24,23 +24,22 @@ block: # (partial fix) bug #15920
|
||||||
|
|
||||||
template foo2() = discard # distractor (template or other symbol kind)
|
template foo2() = discard # distractor (template or other symbol kind)
|
||||||
block:
|
block:
|
||||||
template foo2(lhs, typ, expr) =
|
template foo2(expr) =
|
||||||
let lhs = expr
|
expr
|
||||||
proc fun1()=
|
proc fun1()=
|
||||||
let a {.foo2.} = 1
|
let a {.foo2.} = 1
|
||||||
template fun2()=
|
template fun2()=
|
||||||
let a {.foo2.} = 1
|
let a {.foo2.} = 1
|
||||||
fun1() # ok
|
fun1() # ok
|
||||||
when false: # bug: Error: invalid pragma: foo2
|
fun2() # bug: Error: invalid pragma: foo2
|
||||||
fun2()
|
|
||||||
|
|
||||||
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
|
# adapted from $nim/lib/std/private/since.nim
|
||||||
# case without overload
|
# case without overload
|
||||||
template since3(version: (int, int), body: untyped) {.dirty.} =
|
template since3(version: (int, int), body: untyped) {.dirty.} =
|
||||||
when (NimMajor, NimMinor) >= version:
|
when (NimMajor, NimMinor) >= version:
|
||||||
body
|
body
|
||||||
when false: # bug
|
when true: # bug
|
||||||
template fun3(): int {.since3: (1, 3).} = 12
|
template fun3(): int {.since3: (1, 3).} = 12
|
||||||
|
|
||||||
block: # ditto, w
|
block: # ditto, w
|
||||||
|
|
@ -51,7 +50,7 @@ block: # (partial fix) bug #15920
|
||||||
template since2(version: (int, int, int), body: untyped) {.dirty.} =
|
template since2(version: (int, int, int), body: untyped) {.dirty.} =
|
||||||
when (NimMajor, NimMinor, NimPatch) >= version:
|
when (NimMajor, NimMinor, NimPatch) >= version:
|
||||||
body
|
body
|
||||||
when false: # bug
|
when true: # bug
|
||||||
template fun3(): int {.since2: (1, 3).} = 12
|
template fun3(): int {.since2: (1, 3).} = 12
|
||||||
|
|
||||||
when true: # D20210801T100514:here
|
when true: # D20210801T100514:here
|
||||||
|
|
@ -62,3 +61,10 @@ when true: # D20210801T100514:here
|
||||||
discard ret
|
discard ret
|
||||||
fn()
|
fn()
|
||||||
static: discard genSym()
|
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
|
||||||
|
|
|
||||||
128
tests/pragmas/tvar_macro.nim
Normal file
128
tests/pragmas/tvar_macro.nim
Normal file
|
|
@ -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
|
||||||
|
|
@ -13,15 +13,18 @@ template fun() =
|
||||||
var b {.byaddr.}: int = s[0]
|
var b {.byaddr.}: int = s[0]
|
||||||
doAssert a.addr == b.addr
|
doAssert a.addr == b.addr
|
||||||
|
|
||||||
doAssert not compiles(block:
|
when false:
|
||||||
# redeclaration not allowed
|
# template specific redeclaration issue
|
||||||
var foo = 0
|
# see https://github.com/nim-lang/Nim/issues/8275
|
||||||
var foo {.byaddr.} = s[0])
|
doAssert not compiles(block:
|
||||||
|
# redeclaration not allowed
|
||||||
|
var foo = 0
|
||||||
|
var foo {.byaddr.} = s[0])
|
||||||
|
|
||||||
doAssert not compiles(block:
|
doAssert not compiles(block:
|
||||||
# ditto
|
# ditto
|
||||||
var foo {.byaddr.} = s[0]
|
var foo {.byaddr.} = s[0]
|
||||||
var foo {.byaddr.} = s[0])
|
var foo {.byaddr.} = s[0])
|
||||||
|
|
||||||
block:
|
block:
|
||||||
var b {.byaddr.} = s[1] # redeclaration ok in sub scope
|
var b {.byaddr.} = s[1] # redeclaration ok in sub scope
|
||||||
|
|
@ -44,48 +47,3 @@ fun2()
|
||||||
static: fun2()
|
static: fun2()
|
||||||
when false: # pending bug #13887
|
when false: # pending bug #13887
|
||||||
static: fun()
|
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
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue