Skip to content
Browse files

add custom pragma support for var and let symbols (#9582)

* add custom pragma support for var and let symbols
* updated changelog for custom pragmas on var and let symbols
* add oldast switch for backwards compatibility
  • Loading branch information...
jcosborn authored and Araq committed Jan 7, 2019
1 parent 139fa39 commit 044cef152f6006927a905d69dc527cada8206b0f
Showing with 77 additions and 20 deletions.
  1. +6 −4
  2. +7 −0 compiler/ast.nim
  3. +2 −0 compiler/commands.nim
  4. +4 −4 compiler/guards.nim
  5. +2 −1 compiler/options.nim
  6. +19 −4 compiler/semstmts.nim
  7. +1 −0 doc/advopt.txt
  8. +7 −1 lib/core/macros.nim
  9. +29 −6 tests/pragmas/tcustom_pragma.nim
@@ -23,7 +23,8 @@
- The undocumented ``#? strongSpaces`` parsing mode has been removed.
- The `not` operator is now always a unary operator, this means that code like
``assert not isFalse(3)`` compiles.

- `getImpl` on a `var` or `let` symbol will now return the full `IdentDefs`
tree from the symbol declaration instead of just the initializer portion.

#### Breaking changes in the standard library

@@ -133,8 +134,9 @@ proc enumToString*(enums: openArray[enum]): string =
the `gcsafe` pragma block.
- added os.getCurrentProcessId()
- User defined pragmas are now allowed in the pragma blocks
- Pragma blocks are now longer eliminated from the typed AST tree to preserve
- Pragma blocks are no longer eliminated from the typed AST tree to preserve
pragmas for further analysis by macros
- Custom pragmas are now supported for `var` and `let` symbols.

### Language changes

@@ -143,10 +145,10 @@ proc enumToString*(enums: openArray[enum]): string =
it's more recognizable and allows tools like github to recognize it as Nim,
see [#9647](
The previous extension will continue to work.
- Pragma syntax is now consistent. Previous syntax where type pragmas did not
- Pragma syntax is now consistent. Previous syntax where type pragmas did not
follow the type name is now deprecated. Also pragma before generic parameter
list is deprecated to be consistent with how pragmas are used with a proc. See
[#8514]( and
[#8514]( and
[#1872]( for further details.

@@ -1087,6 +1087,13 @@ proc newSym*(symKind: TSymKind, name: PIdent, owner: PSym,
when debugIds:

proc astdef*(s: PSym): PNode =

This comment has been minimized.

Copy link

cooldome Jan 9, 2019


Sorry I didn't have a change to review this PR in time.
I am suggesting slightly different approach: internally compiler always stores new ast representation. The "oldast" command line argument comes into play only when user calls getImpl from macros module. astdef helper is not required anymore, s.ast[2] or s.ast[^1] can be used everywhere instead.

# get only the definition (initializer) portion of the ast
if s.ast != nil and s.ast.kind == nkIdentDefs:

proc isMetaType*(t: PType): bool =
return t.kind in tyMetaTypes or
(t.kind == tyStatic and t.n == nil) or
@@ -291,6 +291,7 @@ proc testCompileOption*(conf: ConfigRef; switch: string, info: TLineInfo): bool
of "patterns": result = contains(conf.options, optPatterns)
of "excessivestacktrace": result = contains(conf.globalOptions, optExcessiveStackTrace)
of "nilseqs": result = contains(conf.options, optNilSeqs)
of "oldast": result = contains(conf.options, optOldAst)
else: invalidCmdLineOption(conf, passCmd1, switch, info)

proc processPath(conf: ConfigRef; path: string, info: TLineInfo,
@@ -508,6 +509,7 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
localError(conf, info, errOnOrOffExpectedButXFound % arg)
of "laxstrings": processOnOffSwitch(conf, {optLaxStrings}, arg, pass, info)
of "nilseqs": processOnOffSwitch(conf, {optNilSeqs}, arg, pass, info)
of "oldast": processOnOffSwitch(conf, {optOldAst}, arg, pass, info)
of "checks", "x": processOnOffSwitch(conf, ChecksOptions, arg, pass, info)
of "floatchecks":
processOnOffSwitch(conf, {optNaNCheck, optInfCheck}, arg, pass, info)
@@ -257,9 +257,9 @@ proc canon*(n: PNode; o: Operators): PNode =
for i in 0 ..< n.len:
result.sons[i] = canon(n.sons[i], o)
elif n.kind == nkSym and n.sym.kind == skLet and
n.sym.ast.getMagic in (someEq + someAdd + someMul + someMin +
n.sym.astdef.getMagic in (someEq + someAdd + someMul + someMin +
someMax + someHigh + {mUnaryLt} + someSub + someLen + someDiv):
result = n.sym.ast.copyTree
result = n.sym.astdef.copyTree
result = n
case result.getMagic
@@ -395,8 +395,8 @@ proc usefulFact(n: PNode; o: Operators): PNode =
# if a:
# ...
# We make can easily replace 'a' by '2 < x' here:
if n.sym.ast != nil:
result = usefulFact(n.sym.ast, o)
if n.sym.astdef != nil:
result = usefulFact(n.sym.astdef, o)
elif n.kind == nkStmtListExpr:
result = usefulFact(n.lastSon, o)

@@ -40,7 +40,8 @@ type # please make sure we have under 32 options

TOptions* = set[TOption]
TGlobalOption* = enum # **keep binary compatible**
@@ -330,9 +330,9 @@ proc semIdentDef(c: PContext, n: PNode, kind: TSymKind): PSym =
proc checkNilable(c: PContext; v: PSym) =
if {sfGlobal, sfImportC} * v.flags == {sfGlobal} and
{tfNotNil, tfNeedsInit} * v.typ.flags != {}:
if v.ast.isNil:
if v.astdef.isNil:
message(c.config,, warnProveInit,
elif tfNotNil in v.typ.flags and tfNotNil notin v.ast.typ.flags:
elif tfNotNil in v.typ.flags and tfNotNil notin v.astdef.typ.flags:
message(c.config,, warnProveInit,

include semasgn
@@ -518,8 +518,6 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
message(c.config,, warnShadowIdent,
if a.kind != nkVarTuple:
if def.kind != nkEmpty:
# this is needed for the evaluation pass and for the guard checking:
v.ast = def
if sfThread in v.flags: localError(c.config,, errThreadvarCannotInit)
setVarType(c, v, typ)
b = newNodeI(nkIdentDefs,
@@ -531,6 +529,23 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
addSon(b, a.sons[length-2])
addSon(b, copyTree(def))
addToVarSection(c, result, n, b)
if optOldAst in c.config.options:
if def.kind != nkEmpty:
v.ast = def
# this is needed for the evaluation pass, guard checking
# and custom pragmas:
var ast = newNodeI(nkIdentDefs,
if a[j].kind == nkPragmaExpr:

This comment has been minimized.

Copy link

cooldome Jan 9, 2019


ideally, not only pragmas needs to be preserved, but also export marker (postfix *)

var p = newNodeI(nkPragmaExpr,
p.add newSymNode(v)
p.add a[j][1].copyTree
ast.add p
ast.add newSymNode(v)
ast.add a.sons[length-2].copyTree
ast.add def
v.ast = ast
if def.kind in {nkPar, nkTupleConstr}: v.ast = def[j]
# bug #7663, for 'nim check' this can be a non-tuple:
@@ -76,6 +76,7 @@ Advanced options:
strings is allowed; only for backwards compatibility
--nilseqs:on|off allow 'nil' for strings/seqs for
backwards compatibility
--oldast:on|off use old AST for backwards compatibility
--skipCfg do not read the general configuration file
--skipUserCfg do not read the user's configuration file
--skipParentCfg do not read the parent dirs' configuration files
@@ -1402,8 +1402,14 @@ proc customPragmaNode(n: NimNode): NimNode =
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]
return typ.getImpl()[0][1]
let timpl = typ.getImpl()
if timpl.len>0 and timpl[0].len>1:
return timpl[0][1]
return timpl

if n.kind in {nnkDotExpr, nnkCheckedFieldExpr}:
let name = $(if n.kind == nnkCheckedFieldExpr: n[0][1] else: n[1])
@@ -175,24 +175,47 @@ var foo: Something
foo.cardinal = north
doAssert foo.b.hasCustomPragma(thingy) == true

proc myproc(s: string): int =
proc myproc(s: string): int =

doAssert myproc("123") == 3

let xx = compiles:
proc myproc_bad(s: string): int =
proc myproc_bad(s: string): int =
doAssert: xx == false

macro checkSym(s: typed{nkSym}): untyped =
macro checkSym(s: typed{nkSym}): untyped =
let body = s.getImpl.body
doAssert body[1].kind == nnkPragmaBlock
doAssert body[1][0].kind == nnkPragma
doAssert body[1][0][0] == bindSym"thingy"


# var and let pragmas
template myAttr() {.pragma.}
template myAttr2(x: int) {.pragma.}
template myAttr3(x: string) {.pragma.}

let a {.myAttr,myAttr2(2),myAttr3:"test".}: int = 0
let b {.myAttr,myAttr2(2),myAttr3:"test".} = 0
var x {.myAttr,myAttr2(2),myAttr3:"test".}: int = 0
var y {.myAttr,myAttr2(2),myAttr3:"test".}: int
var z {.myAttr,myAttr2(2),myAttr3:"test".} = 0

template check(s: untyped) =
doAssert s.hasCustomPragma(myAttr)
doAssert s.hasCustomPragma(myAttr2)
doAssert s.getCustomPragmaVal(myAttr2) == 2
doAssert s.hasCustomPragma(myAttr3)
doAssert s.getCustomPragmaVal(myAttr3) == "test"


0 comments on commit 044cef1

Please sign in to comment.
You can’t perform that action at this time.