Skip to content

Commit

Permalink
New/better macro pragmas, mark some as experimental (#19406)
Browse files Browse the repository at this point in the history
* New/better macro pragmas, make some experimental

fix #15920, close #18212, close #14781, close #6696,
close nim-lang/RFCs#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
  • Loading branch information
metagn authored Jan 20, 2022
1 parent 1563cb2 commit 2bd1aa1
Show file tree
Hide file tree
Showing 8 changed files with 439 additions and 209 deletions.
286 changes: 170 additions & 116 deletions compiler/semstmts.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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)
Expand Down Expand Up @@ -663,13 +728,19 @@ 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)
if a.kind == nkCommentStmt: continue
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)
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions compiler/semtempl.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
Loading

0 comments on commit 2bd1aa1

Please sign in to comment.