Better support for treating templates and macros as symbols.
This allows you to pass a template or a macro to another macro which can then inspect the implementation of the former template/macro using `getImpl`. Since templates can be freely redefined, this allows you to treat their symbols as compile-time variables that have lexical scope. A motivating PoC example for a logging library taking advantage of this will be provided in the next commit. Implementation details: * The name of a template or a macro will be consider a symbol if the template/macro requires parameters * For parameterless templates/macros, you can use `bindSym`, which was extended to also work outside of compile-time procs.
This commit is contained in:
parent
7897026e57
commit
bdcb729597
7 changed files with 202 additions and 8 deletions
|
|
@ -1615,6 +1615,19 @@ proc originatingModule*(s: PSym): PSym =
|
||||||
proc isRoutine*(s: PSym): bool {.inline.} =
|
proc isRoutine*(s: PSym): bool {.inline.} =
|
||||||
result = s.kind in skProcKinds
|
result = s.kind in skProcKinds
|
||||||
|
|
||||||
|
proc isCompileTimeProc*(s: PSym): bool {.inline.} =
|
||||||
|
result = s.kind == skMacro or
|
||||||
|
s.kind == skProc and sfCompileTime in s.flags
|
||||||
|
|
||||||
|
proc requiredParams*(s: PSym): int =
|
||||||
|
# Returns the number of required params (without default values)
|
||||||
|
# XXX: Perhaps we can store this in the `offset` field of the
|
||||||
|
# symbol instead?
|
||||||
|
for i in 1 ..< s.typ.len:
|
||||||
|
if s.typ.n[i].sym.ast != nil:
|
||||||
|
return i - 1
|
||||||
|
return s.typ.len - 1
|
||||||
|
|
||||||
proc hasPattern*(s: PSym): bool {.inline.} =
|
proc hasPattern*(s: PSym): bool {.inline.} =
|
||||||
result = isRoutine(s) and s.ast.sons[patternPos].kind != nkEmpty
|
result = isRoutine(s) and s.ast.sons[patternPos].kind != nkEmpty
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ proc evalTemplateArgs(n: PNode, s: PSym; fromHlo: bool): PNode =
|
||||||
# if the template has zero arguments, it can be called without ``()``
|
# if the template has zero arguments, it can be called without ``()``
|
||||||
# `n` is then a nkSym or something similar
|
# `n` is then a nkSym or something similar
|
||||||
var totalParams = case n.kind
|
var totalParams = case n.kind
|
||||||
of nkCall, nkInfix, nkPrefix, nkPostfix, nkCommand, nkCallStrLit: n.len-1
|
of nkCallKinds: n.len-1
|
||||||
else: 0
|
else: 0
|
||||||
|
|
||||||
var
|
var
|
||||||
|
|
|
||||||
|
|
@ -960,18 +960,20 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode =
|
||||||
else:
|
else:
|
||||||
result = newSymNode(s, n.info)
|
result = newSymNode(s, n.info)
|
||||||
of skMacro:
|
of skMacro:
|
||||||
if efNoEvaluateGeneric in flags and s.ast[genericParamsPos].len > 0:
|
if efNoEvaluateGeneric in flags and s.ast[genericParamsPos].len > 0 or
|
||||||
|
(n.kind notin nkCallKinds and s.requiredParams > 0):
|
||||||
markUsed(n.info, s, c.graph.usageSym)
|
markUsed(n.info, s, c.graph.usageSym)
|
||||||
styleCheckUse(n.info, s)
|
styleCheckUse(n.info, s)
|
||||||
result = newSymNode(s, n.info)
|
result = symChoice(c, n, s, scClosed)
|
||||||
else:
|
else:
|
||||||
result = semMacroExpr(c, n, n, s, flags)
|
result = semMacroExpr(c, n, n, s, flags)
|
||||||
of skTemplate:
|
of skTemplate:
|
||||||
if efNoEvaluateGeneric in flags and s.ast[genericParamsPos].len > 0 or
|
if efNoEvaluateGeneric in flags and s.ast[genericParamsPos].len > 0 or
|
||||||
|
(n.kind notin nkCallKinds and s.requiredParams > 0) or
|
||||||
sfCustomPragma in sym.flags:
|
sfCustomPragma in sym.flags:
|
||||||
markUsed(n.info, s, c.graph.usageSym)
|
markUsed(n.info, s, c.graph.usageSym)
|
||||||
styleCheckUse(n.info, s)
|
styleCheckUse(n.info, s)
|
||||||
result = newSymNode(s, n.info)
|
result = symChoice(c, n, s, scClosed)
|
||||||
else:
|
else:
|
||||||
result = semTemplateExpr(c, n, s, flags)
|
result = semTemplateExpr(c, n, s, flags)
|
||||||
of skParam:
|
of skParam:
|
||||||
|
|
|
||||||
|
|
@ -199,6 +199,10 @@ proc semBindSym(c: PContext, n: PNode): PNode =
|
||||||
if s != nil:
|
if s != nil:
|
||||||
# we need to mark all symbols:
|
# we need to mark all symbols:
|
||||||
var sc = symChoice(c, id, s, TSymChoiceRule(isMixin.intVal))
|
var sc = symChoice(c, id, s, TSymChoiceRule(isMixin.intVal))
|
||||||
|
if not getCurrOwner(c).isCompileTimeProc:
|
||||||
|
# inside regular code, bindSym resolves to the sym-choice
|
||||||
|
# nodes (see tinspectsymbol)
|
||||||
|
return sc
|
||||||
result.add(sc)
|
result.add(sc)
|
||||||
else:
|
else:
|
||||||
errorUndeclaredIdentifier(c, n.sons[1].info, sl.strVal)
|
errorUndeclaredIdentifier(c, n.sons[1].info, sl.strVal)
|
||||||
|
|
|
||||||
|
|
@ -2032,8 +2032,9 @@ proc paramTypesMatch*(m: var TCandidate, f, a: PType,
|
||||||
y.calleeSym = m.calleeSym
|
y.calleeSym = m.calleeSym
|
||||||
z.calleeSym = m.calleeSym
|
z.calleeSym = m.calleeSym
|
||||||
var best = -1
|
var best = -1
|
||||||
for i in countup(0, sonsLen(arg) - 1):
|
for i in 0 ..< arg.len:
|
||||||
if arg.sons[i].sym.kind in {skProc, skFunc, skMethod, skConverter, skIterator}:
|
if arg.sons[i].sym.kind in {skProc, skFunc, skMethod, skConverter,
|
||||||
|
skIterator, skMacro, skTemplate}:
|
||||||
copyCandidate(z, m)
|
copyCandidate(z, m)
|
||||||
z.callee = arg.sons[i].typ
|
z.callee = arg.sons[i].typ
|
||||||
if tfUnresolved in z.callee.flags: continue
|
if tfUnresolved in z.callee.flags: continue
|
||||||
|
|
@ -2062,6 +2063,7 @@ proc paramTypesMatch*(m: var TCandidate, f, a: PType,
|
||||||
x = z
|
x = z
|
||||||
elif cmp == 0:
|
elif cmp == 0:
|
||||||
y = z # z is as good as x
|
y = z # z is as good as x
|
||||||
|
|
||||||
if x.state == csEmpty:
|
if x.state == csEmpty:
|
||||||
result = nil
|
result = nil
|
||||||
elif y.state == csMatch and cmpCandidates(x, y) == 0:
|
elif y.state == csMatch and cmpCandidates(x, y) == 0:
|
||||||
|
|
@ -2070,7 +2072,7 @@ proc paramTypesMatch*(m: var TCandidate, f, a: PType,
|
||||||
# ambiguous: more than one symbol fits!
|
# ambiguous: more than one symbol fits!
|
||||||
# See tsymchoice_for_expr as an example. 'f.kind == tyExpr' should match
|
# See tsymchoice_for_expr as an example. 'f.kind == tyExpr' should match
|
||||||
# anyway:
|
# anyway:
|
||||||
if f.kind == tyExpr: result = arg
|
if f.kind in {tyExpr, tyStmt}: result = arg
|
||||||
else: result = nil
|
else: result = nil
|
||||||
else:
|
else:
|
||||||
# only one valid interpretation found:
|
# only one valid interpretation found:
|
||||||
|
|
|
||||||
|
|
@ -385,7 +385,7 @@ type
|
||||||
|
|
||||||
{.deprecated: [TBindSymRule: BindSymRule].}
|
{.deprecated: [TBindSymRule: BindSymRule].}
|
||||||
|
|
||||||
proc bindSym*(ident: string, rule: BindSymRule = brClosed): NimNode {.
|
proc bindSym*(ident: static[string], rule: BindSymRule = brClosed): NimNode {.
|
||||||
magic: "NBindSym", noSideEffect.}
|
magic: "NBindSym", noSideEffect.}
|
||||||
## creates a node that binds `ident` to a symbol node. The bound symbol
|
## creates a node that binds `ident` to a symbol node. The bound symbol
|
||||||
## may be an overloaded symbol.
|
## may be an overloaded symbol.
|
||||||
|
|
|
||||||
173
tests/macros/ttemplatesymbols.nim
Normal file
173
tests/macros/ttemplatesymbols.nim
Normal file
|
|
@ -0,0 +1,173 @@
|
||||||
|
import
|
||||||
|
macros, algorithm, strutils
|
||||||
|
|
||||||
|
proc normalProc(x: int) =
|
||||||
|
echo x
|
||||||
|
|
||||||
|
template templateWithtouParams =
|
||||||
|
echo 10
|
||||||
|
|
||||||
|
proc overloadedProc(x: int) =
|
||||||
|
echo x
|
||||||
|
|
||||||
|
proc overloadedProc(x: string) =
|
||||||
|
echo x
|
||||||
|
|
||||||
|
proc overloadedProc[T](x: T) =
|
||||||
|
echo x
|
||||||
|
|
||||||
|
template normalTemplate(x: int) =
|
||||||
|
echo x
|
||||||
|
|
||||||
|
template overloadedTemplate(x: int) =
|
||||||
|
echo x
|
||||||
|
|
||||||
|
template overloadedTemplate(x: string) =
|
||||||
|
echo x
|
||||||
|
|
||||||
|
macro normalMacro(x: int): untyped =
|
||||||
|
discard
|
||||||
|
|
||||||
|
macro macroWithoutParams: untyped =
|
||||||
|
discard
|
||||||
|
|
||||||
|
macro inspectSymbol(sym: typed, expected: static[string]): untyped =
|
||||||
|
if sym.kind == nnkSym:
|
||||||
|
echo "Symbol node:"
|
||||||
|
let res = sym.getImpl.repr & "\n"
|
||||||
|
echo res
|
||||||
|
# echo "|", res, "|"
|
||||||
|
# echo "|", expected, "|"
|
||||||
|
if expected.len > 0: assert res == expected
|
||||||
|
elif sym.kind in {nnkClosedSymChoice, nnkOpenSymChoice}:
|
||||||
|
echo "Begin sym choice:"
|
||||||
|
var results = newSeq[string](0)
|
||||||
|
for innerSym in sym:
|
||||||
|
results.add innerSym.getImpl.repr
|
||||||
|
sort(results, cmp[string])
|
||||||
|
let res = results.join("\n") & "\n"
|
||||||
|
echo res
|
||||||
|
if expected.len > 0: assert res == expected
|
||||||
|
echo "End symchoice."
|
||||||
|
else:
|
||||||
|
echo "Non-symbol node: ", sym.kind
|
||||||
|
if expected.len > 0: assert $sym.kind == expected
|
||||||
|
|
||||||
|
macro inspectUntyped(sym: untyped, expected: static[string]): untyped =
|
||||||
|
let res = sym.repr
|
||||||
|
echo "Untyped node: ", res
|
||||||
|
assert res == expected
|
||||||
|
|
||||||
|
inspectSymbol templateWithtouParams, "nnkCommand"
|
||||||
|
# this template is expanded, because bindSym was not used
|
||||||
|
# the end result is the template body (nnkCommand)
|
||||||
|
|
||||||
|
inspectSymbol bindSym("templateWithtouParams"), """
|
||||||
|
template templateWithtouParams() =
|
||||||
|
echo 10
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
inspectSymbol macroWithoutParams, "nnkEmpty"
|
||||||
|
# Just like the template above, the macro was expanded
|
||||||
|
|
||||||
|
inspectSymbol bindSym("macroWithoutParams"), """
|
||||||
|
macro macroWithoutParams(): untyped =
|
||||||
|
discard
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
inspectSymbol normalMacro, """
|
||||||
|
macro normalMacro(x: int): untyped =
|
||||||
|
discard
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Since the normalMacro has params, it's automatically
|
||||||
|
# treated as a symbol here (no need for `bindSym`)
|
||||||
|
|
||||||
|
inspectSymbol bindSym("normalMacro"), """
|
||||||
|
macro normalMacro(x: int): untyped =
|
||||||
|
discard
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
inspectSymbol normalTemplate, """
|
||||||
|
template normalTemplate(x: int) =
|
||||||
|
echo x
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
inspectSymbol bindSym("normalTemplate"), """
|
||||||
|
template normalTemplate(x: int) =
|
||||||
|
echo x
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
inspectSymbol overloadedTemplate, """
|
||||||
|
template overloadedTemplate(x: int) =
|
||||||
|
echo x
|
||||||
|
|
||||||
|
template overloadedTemplate(x: string) =
|
||||||
|
echo x
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
inspectSymbol bindSym("overloadedTemplate"), """
|
||||||
|
template overloadedTemplate(x: int) =
|
||||||
|
echo x
|
||||||
|
|
||||||
|
template overloadedTemplate(x: string) =
|
||||||
|
echo x
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
inspectUntyped bindSym("overloadedTemplate"), """bindSym("overloadedTemplate")"""
|
||||||
|
# binSym is active only in the presense of `typed` params.
|
||||||
|
# `untyped` params still get the raw AST
|
||||||
|
|
||||||
|
inspectSymbol normalProc, """
|
||||||
|
proc normalProc(x: int) =
|
||||||
|
echo [x]
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
inspectSymbol bindSym("normalProc"), """
|
||||||
|
proc normalProc(x: int) =
|
||||||
|
echo [x]
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
inspectSymbol overloadedProc, """
|
||||||
|
proc overloadedProc(x: int) =
|
||||||
|
echo [x]
|
||||||
|
|
||||||
|
proc overloadedProc(x: string) =
|
||||||
|
echo [x]
|
||||||
|
|
||||||
|
proc overloadedProc[T](x: T) =
|
||||||
|
echo x
|
||||||
|
|
||||||
|
"""
|
||||||
|
# XXX: There seems to be a repr rendering problem above.
|
||||||
|
# Notice that `echo [x]`
|
||||||
|
|
||||||
|
inspectSymbol overloadedProc[float], """
|
||||||
|
proc overloadedProc(x: T) =
|
||||||
|
echo [x]
|
||||||
|
|
||||||
|
"""
|
||||||
|
# As expected, when we select a specific generic, the
|
||||||
|
# AST is no longer a symChoice
|
||||||
|
|
||||||
|
inspectSymbol bindSym("overloadedProc"), """
|
||||||
|
proc overloadedProc(x: int) =
|
||||||
|
echo [x]
|
||||||
|
|
||||||
|
proc overloadedProc(x: string) =
|
||||||
|
echo [x]
|
||||||
|
|
||||||
|
proc overloadedProc[T](x: T) =
|
||||||
|
echo x
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue