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
|
||||
|
||||
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`
|
||||
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
|
||||
|
||||
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.}`
|
||||
proc copyExcept(n: PNode, i: int): PNode =
|
||||
result = copyNode(n)
|
||||
for j in 0..<n.len:
|
||||
if j != i: result.add(n[j])
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
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)
|
||||
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
|
||||
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:
|
||||
var amb = false
|
||||
let sym = searchInScopes(c, ident, amb)
|
||||
if sym != nil and sfCustomPragma in sym.flags:
|
||||
continue # User custom pragma
|
||||
prc[pragmasPos] = n
|
||||
continue
|
||||
|
||||
# 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)
|
||||
doAssert result != nil
|
||||
|
||||
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])
|
||||
# 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)
|
||||
|
||||
# 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 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
|
||||
return result
|
||||
|
||||
proc semInferredLambda(c: PContext, pt: TIdTable, n: PNode): PNode {.nosinks.} =
|
||||
## used for resolving 'auto' in lambdas based on their callsite
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue