diff options
author | metagn <10591326+metagn@users.noreply.github.com> | 2022-01-20 22:57:50 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-01-20 20:57:50 +0100 |
commit | 2bd1aa186e09565b2103394bd281478fa1b10ef1 (patch) | |
tree | 3c1d9b68565f1c2f98fb3691fc4da1a81045460f /compiler | |
parent | 1563cb2f6e37f07c303d095dabde74955be1e523 (diff) | |
download | Nim-2bd1aa186e09565b2103394bd281478fa1b10ef1.tar.gz |
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
Diffstat (limited to 'compiler')
-rw-r--r-- | compiler/semstmts.nim | 286 | ||||
-rw-r--r-- | compiler/semtempl.nim | 5 |
2 files changed, 175 insertions, 116 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): |