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:
Zahary Karadjov 2018-04-18 19:40:08 +03:00 • committed by Andreas Rumpf
commit bdcb729597
7 changed files with 202 additions and 8 deletions

View file

@ -1615,6 +1615,19 @@ proc originatingModule*(s: PSym): PSym =
proc isRoutine*(s: PSym): bool {.inline.} =
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.} =
result = isRoutine(s) and s.ast.sons[patternPos].kind != nkEmpty

View file

@ -63,7 +63,7 @@ proc evalTemplateArgs(n: PNode, s: PSym; fromHlo: bool): PNode =
# if the template has zero arguments, it can be called without ``()``
# `n` is then a nkSym or something similar
var totalParams = case n.kind
of nkCall, nkInfix, nkPrefix, nkPostfix, nkCommand, nkCallStrLit: n.len-1
of nkCallKinds: n.len-1
else: 0
var

View file

@ -960,18 +960,20 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode =
else:
result = newSymNode(s, n.info)
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)
styleCheckUse(n.info, s)
result = newSymNode(s, n.info)
result = symChoice(c, n, s, scClosed)
else:
result = semMacroExpr(c, n, n, s, flags)
of skTemplate:
if efNoEvaluateGeneric in flags and s.ast[genericParamsPos].len > 0 or
(n.kind notin nkCallKinds and s.requiredParams > 0) or
sfCustomPragma in sym.flags:
markUsed(n.info, s, c.graph.usageSym)
styleCheckUse(n.info, s)
result = newSymNode(s, n.info)
result = symChoice(c, n, s, scClosed)
else:
result = semTemplateExpr(c, n, s, flags)
of skParam:

View file

@ -199,6 +199,10 @@ proc semBindSym(c: PContext, n: PNode): PNode =
if s != nil:
# we need to mark all symbols:
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)
else:
errorUndeclaredIdentifier(c, n.sons[1].info, sl.strVal)

View file

@ -2032,8 +2032,9 @@ proc paramTypesMatch*(m: var TCandidate, f, a: PType,
y.calleeSym = m.calleeSym
z.calleeSym = m.calleeSym
var best = -1
for i in countup(0, sonsLen(arg) - 1):
if arg.sons[i].sym.kind in {skProc, skFunc, skMethod, skConverter, skIterator}:
for i in 0 ..< arg.len:
if arg.sons[i].sym.kind in {skProc, skFunc, skMethod, skConverter,
skIterator, skMacro, skTemplate}:
copyCandidate(z, m)
z.callee = arg.sons[i].typ
if tfUnresolved in z.callee.flags: continue
@ -2062,6 +2063,7 @@ proc paramTypesMatch*(m: var TCandidate, f, a: PType,
x = z
elif cmp == 0:
y = z # z is as good as x
if x.state == csEmpty:
result = nil
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!
# See tsymchoice_for_expr as an example. 'f.kind == tyExpr' should match
# anyway:
if f.kind == tyExpr: result = arg
if f.kind in {tyExpr, tyStmt}: result = arg
else: result = nil
else:
# only one valid interpretation found: