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:
parent
40898871a9
commit
9134bb9cfb
7 changed files with 303 additions and 140 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue