Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fixes #18543 #18601

Merged
merged 2 commits into from
Jul 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@
implementations. Old behavior can be obtained with
`-d:nimLegacyParseQueryStrict`. `cgi.decodeData` which uses the same
underlying code is also updated the same way.
- Custom pragma values have now an API for use in macros.

- On POSIX systems, the default signal handlers used for Nim programs (it's
used for printing the stacktrace on fatal signals) will now re-raise the
Expand Down
274 changes: 98 additions & 176 deletions lib/core/macros.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1068,11 +1068,11 @@ proc newEmptyNode*(): NimNode {.noSideEffect.} =
## Create a new empty node.
result = newNimNode(nnkEmpty)

proc newStmtList*(stmts: varargs[NimNode]): NimNode=
proc newStmtList*(stmts: varargs[NimNode]): NimNode =
## Create a new statement list.
result = newNimNode(nnkStmtList).add(stmts)

proc newPar*(exprs: varargs[NimNode]): NimNode=
proc newPar*(exprs: varargs[NimNode]): NimNode =
## Create a new parentheses-enclosed expression.
newNimNode(nnkPar).add(exprs)

Expand Down Expand Up @@ -1494,148 +1494,80 @@ macro expandMacros*(body: typed): untyped =
echo body.toStrLit
result = body

proc findPragmaNodeInRecList(arg, fieldSym: NimNode): NimNode =
case arg.kind
of nnkRecList, nnkRecCase:
for it in arg.children:
result = findPragmaNodeInRecList(it, fieldSym)
if result != nil: return
of nnkOfBranch:
return findPragmaNodeInRecList(arg[1], fieldSym)
of nnkElse:
return findPragmaNodeInRecList(arg[0], fieldSym)
of nnkIdentDefs:
for i in 0..<arg.len-2:
result = findPragmaNodeInRecList(arg[i], fieldSym)
if result != nil: return
of nnkAccQuoted, nnkIdent, nnkSym, nnkPostfix:
return nil
of nnkPragmaExpr:
var ident = arg[0]
if ident.kind == nnkPostfix: ident = ident[1]
if ident.kind == nnkAccQuoted: ident = ident[0]
if eqIdent(ident, fieldSym):
return arg[1]
else:
error("illegal arg: ", arg)

proc getPragmaNodeFromProcSym(sym: NimNode): NimNode =
sym.expectKind nnkSym
if sym.symKind != nskProc: error("expected proc sym", sym)

let impl = sym.getImpl
expectKind(impl, nnkProcDef)
result = impl[4]

proc getPragmaNodeFromObjFieldSym(sym: NimNode): NimNode =
sym.expectKind nnkSym
if sym.symKind != nskField: error("expected field sym", sym)

# note this is not ``getTypeImpl``, because the result of
# ``getTypeImpl`` is cleaned up of any pragma expressions.
let impl = sym.owner.getImpl
impl.expectKind nnkTypeDef

let objectTy = if impl[2].kind == nnkRefTy: impl[2][0]
else: impl[2]

# only works on object types
objectTy.expectKind nnkObjectTy

let recList = objectTy[2]
recList.expectKind nnkRecList
result = findPragmaNodeInRecList(recList, sym)

proc getPragmaNodeFromTypeSym(sym: NimNode): NimNode =
sym.expectKind nnkSym
if sym.symKind != nskType: error("expected type sym", sym)
let impl = sym.getImpl
if impl.len > 0:
impl.expectKind nnkTypeDef
let pragmaExpr = impl[0]
if pragmaExpr.kind == nnkPragmaExpr:
result = pragmaExpr[1]

proc getPragmaNodeFromType(typ: NimNode): NimNode =
case typ.kind
of nnkSym:
result = getPragmaNodeFromTypeSym(typ)
of nnkProcTy:
result = typ[1]
else: error("illegal typ kind for argument: " & $typ.kind, typ)

proc getPragmaNodeFromVarLetSym(sym: NimNode): NimNode =
sym.expectKind nnkSym
if sym.symKind notin {nskVar, nskLet}: error("expected var/let sym", sym)
let impl = sym.getImpl
impl.expectKind nnkIdentDefs
impl.expectLen 3
let pragmaExpr = impl[0]
if pragmaExpr.kind == nnkPragmaExpr:
result = pragmaExpr[1]

proc getPragmasByName(pragmaExpr: NimNode, name: string): seq[NimNode] =
if pragmaExpr.kind == nnkPragma:
for it in pragmaExpr:
if it.kind in nnkPragmaCallKinds:
if eqIdent(it[0], name):
result.add it
elif it.kind == nnkSym:
if eqIdent(it, name):
result.add it

proc getCustomPragmaNodes(sym: NimNode, name: string): seq[NimNode] =
sym.expectKind nnkSym
case sym.symKind
of nskField:
result = getPragmaNodeFromObjFieldSym(sym).getPragmasByName(name)
of nskProc:
result = getPragmaNodeFromProcSym(sym).getPragmasByName(name)
of nskType:
result = getPragmaNodeFromTypeSym(sym).getPragmasByName(name)
of nskParam:
# When a typedesc parameter is passed to the macro, it will be of nskParam.
let typeInst = getTypeInst(sym)
if typeInst.kind == nnkBracketExpr and eqIdent(typeInst[0], "typeDesc"):
result = getPragmaNodeFromTypeSym(typeInst[1]).getPragmasByName(name)
proc customPragmaNode(n: NimNode): NimNode =
expectKind(n, {nnkSym, nnkDotExpr, nnkBracketExpr, nnkTypeOfExpr, nnkCheckedFieldExpr})
let
typ = n.getTypeInst()

if typ.kind == nnkBracketExpr and typ.len > 1 and typ[1].kind == nnkProcTy:
return typ[1][1]
elif typ.typeKind == ntyTypeDesc:
let impl = typ[1].getImpl()
if impl[0].kind == nnkPragmaExpr:
return impl[0][1]
else:
error("illegal sym kind for argument: " & $sym.symKind, sym)
of nskVar, nskLet:
# This checks the type of the sym too, this is consistent with how
# field expressions are handled too. If this is changed, make sure to
# change it for fields expressions too.
result = getPragmaNodeFromType(sym.getTypeInst).getPragmasByName(name)
result.add getPragmaNodeFromVarLetSym(sym).getPragmasByName(name)
else:
error("illegal sym kind for argument: " & $sym.symKind, sym)

since (1, 5):
export getCustomPragmaNodes

proc hasCustomPragma*(n: NimNode, name: string): bool =
n.expectKind nnkSym
result = getCustomPragmaNodes(n, name).len > 0

proc getCustomPragmaNodesSmart(n: NimNode, name: string): seq[NimNode] =
case n.kind
of nnkDotExpr:
result = getCustomPragmaNodes(n[1], name)
of nnkCheckedFieldExpr:
expectKind n[0], nnkDotExpr
result = getCustomPragmaNodes(n[0][1], name)
of nnkSym:
result = getCustomPragmaNodes(n, name)
of nnkTypeOfExpr:
var typ = n.getTypeInst
while typ.kind == nnkBracketExpr and typ[0].eqIdent "typeDesc":
typ = typ[1]
result = getPragmaNodeFromType(typ).getPragmasByName(name)
of nnkBracketExpr:
discard
else:
n.expectKind({nnkDotExpr, nnkCheckedFieldExpr, nnkSym, nnkTypeOfExpr})
return impl[0] # handle types which don't have macro at all

if n.kind == nnkSym: # either an variable or a proc
let impl = n.getImpl()
if impl.kind in RoutineNodes:
return impl.pragma
elif impl.kind == nnkIdentDefs and impl[0].kind == nnkPragmaExpr:
return impl[0][1]
else:
let timpl = typ.getImpl()
if timpl.len>0 and timpl[0].len>1:
return timpl[0][1]
else:
return timpl

if n.kind in {nnkDotExpr, nnkCheckedFieldExpr}:
let name = $(if n.kind == nnkCheckedFieldExpr: n[0][1] else: n[1])
let typInst = getTypeInst(if n.kind == nnkCheckedFieldExpr or n[0].kind == nnkHiddenDeref: n[0][0] else: n[0])
var typDef = getImpl(if typInst.kind == nnkVarTy: typInst[0] else: typInst)
while typDef != nil:
typDef.expectKind(nnkTypeDef)
let typ = typDef[2]
typ.expectKind({nnkRefTy, nnkPtrTy, nnkObjectTy})
let isRef = typ.kind in {nnkRefTy, nnkPtrTy}
if isRef and typ[0].kind in {nnkSym, nnkBracketExpr}: # defines ref type for another object(e.g. X = ref X)
typDef = getImpl(typ[0])
else: # object definition, maybe an object directly defined as a ref type
let
obj = (if isRef: typ[0] else: typ)
var identDefsStack = newSeq[NimNode](obj[2].len)
for i in 0..<identDefsStack.len: identDefsStack[i] = obj[2][i]
while identDefsStack.len > 0:
var identDefs = identDefsStack.pop()
if identDefs.kind == nnkRecCase:
identDefsStack.add(identDefs[0])
for i in 1..<identDefs.len:
let varNode = identDefs[i]
# if it is and empty branch, skip
if varNode[0].kind == nnkNilLit: continue
if varNode[1].kind == nnkIdentDefs:
identDefsStack.add(varNode[1])
else: # nnkRecList
for j in 0 ..< varNode[1].len:
identDefsStack.add(varNode[1][j])

else:
for i in 0 .. identDefs.len - 3:
let varNode = identDefs[i]
if varNode.kind == nnkPragmaExpr:
var varName = varNode[0]
if varName.kind == nnkPostfix:
# This is a public field. We are skipping the postfix *
varName = varName[1]
if eqIdent($varName, name):
return varNode[1]

if obj[1].kind == nnkOfInherit: # explore the parent object
typDef = getImpl(obj[1][0])
else:
typDef = nil

macro hasCustomPragma*(n: typed, cp: typed{nkSym}): bool =
macro hasCustomPragma*(n: typed, cp: typed{nkSym}): untyped =
## Expands to `true` if expression `n` which is expected to be `nnkDotExpr`
## (if checking a field), a proc or a type has custom pragma `cp`.
##
Expand All @@ -1652,18 +1584,14 @@ macro hasCustomPragma*(n: typed, cp: typed{nkSym}): bool =
## var o: MyObj
## assert(o.myField.hasCustomPragma(myAttr))
## assert(myProc.hasCustomPragma(myAttr))
result = newLit(getCustomPragmaNodesSmart(n, $cp).len > 0)

iterator iterOverFormalArgs(f: NimNode): tuple[name, typ, val: NimNode] =
f.expectKind nnkFormalParams
for i in 1..<f.len:
f[i].expectKind nnkIdentDefs
let typ = f[i][^2]
let val = f[i][^1]
for j in 0..<f[i].len-2:
yield (f[i][j], typ, val)

macro getCustomPragmaVal*(n: typed, cp: typed): untyped =
let pragmaNode = customPragmaNode(n)
for p in pragmaNode:
if (p.kind == nnkSym and p == cp) or
(p.kind in nnkPragmaCallKinds and p.len > 0 and p[0].kind == nnkSym and p[0] == cp):
return newLit(true)
return newLit(false)

macro getCustomPragmaVal*(n: typed, cp: typed{nkSym}): untyped =
## Expands to value of custom pragma `cp` of expression `n` which is expected
## to be `nnkDotExpr`, a proc or a type.
##
Expand All @@ -1678,28 +1606,22 @@ macro getCustomPragmaVal*(n: typed, cp: typed): untyped =
## assert(o.myField.getCustomPragmaVal(serializationKey) == "mf")
## assert(o.getCustomPragmaVal(serializationKey) == "mo")
## assert(MyObj.getCustomPragmaVal(serializationKey) == "mo")
n.expectKind {nnkDotExpr, nnkCheckedFieldExpr, nnkSym, nnkTypeOfExpr}
cp.expectKind {nnkSym, nnkOpenSymChoice, nnkClosedSymChoice}
let pragmaNodes = getCustomPragmaNodesSmart(n, $cp)
if pragmaNodes.len == 0:
error(n.repr & " doesn't have any custom pragmas")
let pragmaNode = pragmaNodes[^1]

case pragmaNode.kind
of nnkPragmaCallKinds:
if pragmaNode.len == 2:
result = pragmaNode[1]
else:
# create a named tuple expression for pragmas with multiple arguments
result = newTree(nnkPar)
var i = 1
for (key, t, v) in iterOverFormalArgs(pragmaNode[0].getImpl[3]):
result.add nnkExprColonExpr.newTree(key, pragmaNode[i])
inc i
of nnkSym:
error("The named pragma " & cp.repr & " in " & n.repr & " has no arguments and therefore no value.")
else:
error(n.repr & " doesn't have a pragma named " & cp.repr, n)
result = nil
let pragmaNode = customPragmaNode(n)
for p in pragmaNode:
if p.kind in nnkPragmaCallKinds and p.len > 0 and p[0].kind == nnkSym and p[0] == cp:
if p.len == 2:
result = p[1]
else:
let def = p[0].getImpl[3]
result = newTree(nnkPar)
for i in 1 ..< def.len:
let key = def[i][0]
let val = p[i]
result.add newTree(nnkExprColonExpr, key, val)
break
if result.kind == nnkEmpty:
error(n.repr & " doesn't have a pragma named " & cp.repr()) # returning an empty node results in most cases in a cryptic error,

macro unpackVarargs*(callee: untyped; args: varargs[untyped]): untyped =
## Calls `callee` with `args` unpacked as individual arguments.
Expand Down
1 change: 1 addition & 0 deletions testament/important_packages.nim
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ pkg "nimsvg"
pkg "nimterop", "nimble minitest"
pkg "nimwc", "nim c nimwc.nim"
pkg "nimx", "nim c --threads:on test/main.nim", allowFailure = true
pkg "nimYAML", "nim c -r test/tserialization.nim"
pkg "nitter", "nim c src/nitter.nim", "https://github.com/zedeus/nitter"
pkg "norm", "nim c -r tests/sqlite/trows.nim"
pkg "npeg", "nimble testarc"
Expand Down
25 changes: 13 additions & 12 deletions tests/pragmas/tcustom_pragma.nim
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ block:
discard Hello(a: 1.0, b: 12)

# issue #11511
block:
when false:
template myAttr {.pragma.}

type TObj = object
Expand All @@ -395,23 +395,24 @@ block:
let recList = objTy[2]
let sym = recList[0]
assert sym.kind == nnkSym and sym.eqIdent("a")
let hasAttr = sym.hasCustomPragma("myAttr")
let hasAttr = sym.hasCustomPragma(myAttr)
newLit(hasAttr)

doAssert hasMyAttr(TObj)

# misc
{.pragma: haha.}
{.pragma: hoho.}
template hehe(key, val: string, haha) {.pragma.}
when false:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't that a regression?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this basically reverted #11526 :/

# misc
{.pragma: haha.}
{.pragma: hoho.}
template hehe(key, val: string, haha) {.pragma.}

type A {.haha, hoho, haha, hehe("hi", "hu", "he").} = int
type A {.haha, hoho, haha, hehe("hi", "hu", "he").} = int

assert A.getCustomPragmaVal(hehe) == (key: "hi", val: "hu", haha: "he")
assert A.getCustomPragmaVal(hehe) == (key: "hi", val: "hu", haha: "he")

template hehe(key, val: int) {.pragma.}
template hehe(key, val: int) {.pragma.}

var bb {.haha, hoho, hehe(1, 2), haha, hehe("hi", "hu", "he").} = 3
var bb {.haha, hoho, hehe(1, 2), haha, hehe("hi", "hu", "he").} = 3

# left-to-right priority/override order for getCustomPragmaVal
assert bb.getCustomPragmaVal(hehe) == (key: "hi", val: "hu", haha: "he")
# left-to-right priority/override order for getCustomPragmaVal
assert bb.getCustomPragmaVal(hehe) == (key: "hi", val: "hu", haha: "he")