macros for proc types, macros for types (#13778)

* new minor feature: macros for proc types, to be documented

* Finished the implementation and added tests

* [skip ci] Describe the new custom pragmas in the manual and the changelog

Co-authored-by: Zahary Karadjov <zahary@gmail.com>
This commit is contained in:
Andreas Rumpf 2020-03-31 21:14:05 +02:00 • committed by GitHub
commit 9134bb9cfb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 303 additions and 140 deletions

View file

@ -1597,48 +1597,43 @@ proc transitionToLet*(s: PSym) =
s.bitsize = obj.bitsize
s.alignment = obj.alignment
proc shallowCopy*(src: PNode): PNode =
# does not copy its sons, but provides space for them:
if src == nil: return nil
result = newNode(src.kind)
result.info = src.info
result.typ = src.typ
result.flags = src.flags * PersistentNodeFlags
result.comment = src.comment
template copyNodeImpl(dst, src, processSonsStmt) =
if src == nil: return
dst = newNode(src.kind)
dst.info = src.info
dst.typ = src.typ
dst.flags = src.flags * PersistentNodeFlags
dst.comment = src.comment
when defined(useNodeIds):
if result.id == nodeIdToDebug:
if dst.id == nodeIdToDebug:
echo "COMES FROM ", src.id
case src.kind
of nkCharLit..nkUInt64Lit: result.intVal = src.intVal
of nkFloatLiterals: result.floatVal = src.floatVal
of nkSym: result.sym = src.sym
of nkIdent: result.ident = src.ident
of nkStrLit..nkTripleStrLit: result.strVal = src.strVal
else: newSeq(result.sons, src.len)
of nkCharLit..nkUInt64Lit: dst.intVal = src.intVal
of nkFloatLiterals: dst.floatVal = src.floatVal
of nkSym: dst.sym = src.sym
of nkIdent: dst.ident = src.ident
of nkStrLit..nkTripleStrLit: dst.strVal = src.strVal
else: processSonsStmt
proc shallowCopy*(src: PNode): PNode =
# does not copy its sons, but provides space for them:
copyNodeImpl(result, src):
newSeq(result.sons, src.len)
proc copyTree*(src: PNode): PNode =
# copy a whole syntax tree; performs deep copying
if src == nil:
return nil
result = newNode(src.kind)
result.info = src.info
result.typ = src.typ
result.flags = src.flags * PersistentNodeFlags
result.comment = src.comment
when defined(useNodeIds):
if result.id == nodeIdToDebug:
echo "COMES FROM ", src.id
case src.kind
of nkCharLit..nkUInt64Lit: result.intVal = src.intVal
of nkFloatLiterals: result.floatVal = src.floatVal
of nkSym: result.sym = src.sym
of nkIdent: result.ident = src.ident
of nkStrLit..nkTripleStrLit: result.strVal = src.strVal
else:
copyNodeImpl(result, src):
newSeq(result.sons, src.len)
for i in 0..<src.len:
result[i] = copyTree(src[i])
proc copyTreeWithoutNode*(src, skippedNode: PNode): PNode =
copyNodeImpl(result, src):
result.sons = newSeqOfCap[PNode](src.len)
for n in src.sons:
if n != skippedNode:
result.sons.add copyTreeWithoutNode(n, skippedNode)
proc hasSonWith*(n: PNode, kind: TNodeKind): bool =
for i in 0..<n.len:
if n[i].kind == kind:

View file

@ -1051,6 +1051,68 @@ proc typeSectionTypeName(c: PContext; n: PNode): PNode =
result = n
if result.kind != nkSym: illFormedAst(n, c.config)
proc typeDefLeftSidePass(c: PContext, typeSection: PNode, i: int) =
let typeDef= typeSection[i]
checkSonsLen(typeDef, 3, c.config)
var name = typeDef[0]
var s: PSym
if name.kind == nkDotExpr and typeDef[2].kind == nkObjectTy:
let pkgName = considerQuotedIdent(c, name[0])
let typName = considerQuotedIdent(c, name[1])
let pkg = c.graph.packageSyms.strTableGet(pkgName)
if pkg.isNil or pkg.kind != skPackage:
localError(c.config, name.info, "unknown package name: " & pkgName.s)
else:
let typsym = pkg.tab.strTableGet(typName)
if typsym.isNil:
s = semIdentDef(c, name[1], skType)
onDef(name[1].info, s)
s.typ = newTypeS(tyObject, c)
s.typ.sym = s
s.flags.incl sfForward
pkg.tab.strTableAdd s
addInterfaceDecl(c, s)
elif typsym.kind == skType and sfForward in typsym.flags:
s = typsym
addInterfaceDecl(c, s)
else:
localError(c.config, name.info, typsym.name.s & " is not a type that can be forwarded")
s = typsym
else:
s = semIdentDef(c, name, skType)
onDef(name.info, s)
s.typ = newTypeS(tyForward, c)
s.typ.sym = s # process pragmas:
if name.kind == nkPragmaExpr:
let rewritten = applyTypeSectionPragmas(c, name[1], typeDef)
if rewritten != nil:
typeSection[i] = rewritten
typeDefLeftSidePass(c, typeSection, i)
return
pragma(c, s, name[1], typePragmas)
if sfForward in s.flags:
# check if the symbol already exists:
let pkg = c.module.owner
if not isTopLevel(c) or pkg.isNil:
localError(c.config, name.info, "only top level types in a package can be 'package'")
else:
let typsym = pkg.tab.strTableGet(s.name)
if typsym != nil:
if sfForward notin typsym.flags or sfNoForward notin typsym.flags:
typeCompleted(typsym)
typsym.info = s.info
else:
localError(c.config, name.info, "cannot complete type '" & s.name.s & "' twice; " &
"previous type completion was here: " & c.config$typsym.info)
s = typsym
# add it here, so that recursive types are possible:
if sfGenSym notin s.flags: addInterfaceDecl(c, s)
elif s.owner == nil: s.owner = getCurrOwner(c)
if name.kind == nkPragmaExpr:
typeDef[0][0] = newSymNode(s)
else:
typeDef[0] = newSymNode(s)
proc typeSectionLeftSidePass(c: PContext, n: PNode) =
# process the symbols on the left side for the whole type section, before
@ -1064,61 +1126,7 @@ proc typeSectionLeftSidePass(c: PContext, n: PNode) =
dec c.inTypeContext
if a.kind == nkCommentStmt: continue
if a.kind != nkTypeDef: illFormedAst(a, c.config)
checkSonsLen(a, 3, c.config)
let name = a[0]
var s: PSym
if name.kind == nkDotExpr and a[2].kind == nkObjectTy:
let pkgName = considerQuotedIdent(c, name[0])
let typName = considerQuotedIdent(c, name[1])
let pkg = c.graph.packageSyms.strTableGet(pkgName)
if pkg.isNil or pkg.kind != skPackage:
localError(c.config, name.info, "unknown package name: " & pkgName.s)
else:
let typsym = pkg.tab.strTableGet(typName)
if typsym.isNil:
s = semIdentDef(c, name[1], skType)
onDef(name[1].info, s)
s.typ = newTypeS(tyObject, c)
s.typ.sym = s
s.flags.incl sfForward
pkg.tab.strTableAdd s
addInterfaceDecl(c, s)
elif typsym.kind == skType and sfForward in typsym.flags:
s = typsym
addInterfaceDecl(c, s)
else:
localError(c.config, name.info, typsym.name.s & " is not a type that can be forwarded")
s = typsym
else:
s = semIdentDef(c, name, skType)
onDef(name.info, s)
s.typ = newTypeS(tyForward, c)
s.typ.sym = s # process pragmas:
if name.kind == nkPragmaExpr:
pragma(c, s, name[1], typePragmas)
if sfForward in s.flags:
# check if the symbol already exists:
let pkg = c.module.owner
if not isTopLevel(c) or pkg.isNil:
localError(c.config, name.info, "only top level types in a package can be 'package'")
else:
let typsym = pkg.tab.strTableGet(s.name)
if typsym != nil:
if sfForward notin typsym.flags or sfNoForward notin typsym.flags:
typeCompleted(typsym)
typsym.info = s.info
else:
localError(c.config, name.info, "cannot complete type '" & s.name.s & "' twice; " &
"previous type completion was here: " & c.config$typsym.info)
s = typsym
# add it here, so that recursive types are possible:
if sfGenSym notin s.flags: addInterfaceDecl(c, s)
elif s.owner == nil: s.owner = getCurrOwner(c)
if name.kind == nkPragmaExpr:
a[0][0] = newSymNode(s)
else:
a[0] = newSymNode(s)
typeDefLeftSidePass(c, n, i)
proc checkCovariantParamsUsages(c: PContext; genericType: PType) =
var body = genericType[^1]
@ -1453,8 +1461,8 @@ proc semProcAnnotation(c: PContext, prc: PNode;
var n = prc[pragmasPos]
if n == nil or n.kind == nkEmpty: return
for i in 0..<n.len:
var it = n[i]
var key = if it.kind in nkPragmaCallKinds and it.len >= 1: it[0] else: it
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

View file

@ -1567,9 +1567,44 @@ proc semTypeClass(c: PContext, n: PNode, prev: PType): PType =
result.n[3] = semConceptBody(c, n[3])
closeScope(c)
proc applyTypeSectionPragmas(c: PContext; pragmas, operand: PNode): PNode =
for p in pragmas:
let key = if p.kind in nkPragmaCallKinds and p.len >= 1: p[0] else: p
if p.kind == nkEmpty or whichPragma(p) != wInvalid:
discard "builtin pragma"
elif strTableGet(c.userPragmas, considerQuotedIdent(c, key)) != nil:
discard "User-defined pragma"
else:
# we transform ``(arg1, arg2: T) {.m, rest.}`` into ``m((arg1, arg2: T) {.rest.})`` and
# let the semantic checker deal with it:
var x = newNodeI(nkCall, key.info)
x.add(key)
if p.kind in nkPragmaCallKinds and p.len > 1:
# pass pragma arguments to the macro too:
for i in 1 ..< p.len:
x.add(p[i])
# Also pass the node the pragma has been applied to
x.add(operand.copyTreeWithoutNode(p))
# recursion assures that this works for multiple macro annotations too:
var r = semOverloadedCall(c, x, x, {skMacro, skTemplate}, {efNoUndeclared})
if r != nil:
doAssert r[0].kind == nkSym
let m = r[0].sym
case m.kind
of skMacro: return semMacroExpr(c, r, r, m, {efNoSemCheck})
of skTemplate: return semTemplateExpr(c, r, m, {efNoSemCheck})
else: doAssert(false, "cannot happen")
proc semProcTypeWithScope(c: PContext, n: PNode,
prev: PType, kind: TSymKind): PType =
prev: PType, kind: TSymKind): PType =
checkSonsLen(n, 2, c.config)
if n[1].kind != nkEmpty and n[1].len > 0:
let macroEval = applyTypeSectionPragmas(c, n[1], n)
if macroEval != nil:
return semTypeNode(c, macroEval, prev)
openScope(c)
result = semProcTypeNode(c, n[0], nil, prev, kind, isType=true)
# start with 'ccClosure', but of course pragmas can overwrite this:
@ -1845,11 +1880,12 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
result.addSonSkipIntLit(child)
else:
result = semProcTypeWithScope(c, n, prev, skIterator)
result.flags.incl(tfIterator)
if n.lastSon.kind == nkPragma and hasPragma(n.lastSon, wInline):
result.callConv = ccInline
else:
result.callConv = ccClosure
if result.kind == tyProc:
result.flags.incl(tfIterator)
if n.lastSon.kind == nkPragma and hasPragma(n.lastSon, wInline):
result.callConv = ccInline
else:
result.callConv = ccClosure
of nkProcTy:
if n.len == 0:
result = newConstraint(c, tyProc)