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
This commit is contained in:
jcosborn 2019-01-07 05:36:06 -06:00 • committed by Andreas Rumpf
commit 044cef152f
9 changed files with 77 additions and 20 deletions

View file

@ -23,7 +23,8 @@
- The undocumented ``#? strongSpaces`` parsing mode has been removed. - The undocumented ``#? strongSpaces`` parsing mode has been removed.
- The `not` operator is now always a unary operator, this means that code like - The `not` operator is now always a unary operator, this means that code like
``assert not isFalse(3)`` compiles. ``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 #### Breaking changes in the standard library
@ -133,8 +134,9 @@ proc enumToString*(enums: openArray[enum]): string =
the `gcsafe` pragma block. the `gcsafe` pragma block.
- added os.getCurrentProcessId() - added os.getCurrentProcessId()
- User defined pragmas are now allowed in the pragma blocks - 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 pragmas for further analysis by macros
- Custom pragmas are now supported for `var` and `let` symbols.
### Language changes ### Language changes

View file

@ -1087,6 +1087,13 @@ proc newSym*(symKind: TSymKind, name: PIdent, owner: PSym,
when debugIds: when debugIds:
registerId(result) registerId(result)
proc astdef*(s: PSym): PNode =
# get only the definition (initializer) portion of the ast
if s.ast != nil and s.ast.kind == nkIdentDefs:
s.ast[2]
else:
s.ast
proc isMetaType*(t: PType): bool = proc isMetaType*(t: PType): bool =
return t.kind in tyMetaTypes or return t.kind in tyMetaTypes or
(t.kind == tyStatic and t.n == nil) or (t.kind == tyStatic and t.n == nil) or

View file

@ -291,6 +291,7 @@ proc testCompileOption*(conf: ConfigRef; switch: string, info: TLineInfo): bool
of "patterns": result = contains(conf.options, optPatterns) of "patterns": result = contains(conf.options, optPatterns)
of "excessivestacktrace": result = contains(conf.globalOptions, optExcessiveStackTrace) of "excessivestacktrace": result = contains(conf.globalOptions, optExcessiveStackTrace)
of "nilseqs": result = contains(conf.options, optNilSeqs) of "nilseqs": result = contains(conf.options, optNilSeqs)
of "oldast": result = contains(conf.options, optOldAst)
else: invalidCmdLineOption(conf, passCmd1, switch, info) else: invalidCmdLineOption(conf, passCmd1, switch, info)
proc processPath(conf: ConfigRef; path: string, info: TLineInfo, 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) localError(conf, info, errOnOrOffExpectedButXFound % arg)
of "laxstrings": processOnOffSwitch(conf, {optLaxStrings}, arg, pass, info) of "laxstrings": processOnOffSwitch(conf, {optLaxStrings}, arg, pass, info)
of "nilseqs": processOnOffSwitch(conf, {optNilSeqs}, 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 "checks", "x": processOnOffSwitch(conf, ChecksOptions, arg, pass, info)
of "floatchecks": of "floatchecks":
processOnOffSwitch(conf, {optNaNCheck, optInfCheck}, arg, pass, info) processOnOffSwitch(conf, {optNaNCheck, optInfCheck}, arg, pass, info)

View file

@ -257,9 +257,9 @@ proc canon*(n: PNode; o: Operators): PNode =
for i in 0 ..< n.len: for i in 0 ..< n.len:
result.sons[i] = canon(n.sons[i], o) result.sons[i] = canon(n.sons[i], o)
elif n.kind == nkSym and n.sym.kind == skLet and 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): someMax + someHigh + {mUnaryLt} + someSub + someLen + someDiv):
result = n.sym.ast.copyTree result = n.sym.astdef.copyTree
else: else:
result = n result = n
case result.getMagic case result.getMagic
@ -395,8 +395,8 @@ proc usefulFact(n: PNode; o: Operators): PNode =
# if a: # if a:
# ... # ...
# We make can easily replace 'a' by '2 < x' here: # We make can easily replace 'a' by '2 < x' here:
if n.sym.ast != nil: if n.sym.astdef != nil:
result = usefulFact(n.sym.ast, o) result = usefulFact(n.sym.astdef, o)
elif n.kind == nkStmtListExpr: elif n.kind == nkStmtListExpr:
result = usefulFact(n.lastSon, o) result = usefulFact(n.lastSon, o)

View file

@ -40,7 +40,8 @@ type # please make sure we have under 32 options
optMemTracker, optMemTracker,
optHotCodeReloading, optHotCodeReloading,
optLaxStrings, optLaxStrings,
optNilSeqs optNilSeqs,
optOldAst
TOptions* = set[TOption] TOptions* = set[TOption]
TGlobalOption* = enum # **keep binary compatible** TGlobalOption* = enum # **keep binary compatible**

View file

@ -330,9 +330,9 @@ proc semIdentDef(c: PContext, n: PNode, kind: TSymKind): PSym =
proc checkNilable(c: PContext; v: PSym) = proc checkNilable(c: PContext; v: PSym) =
if {sfGlobal, sfImportC} * v.flags == {sfGlobal} and if {sfGlobal, sfImportC} * v.flags == {sfGlobal} and
{tfNotNil, tfNeedsInit} * v.typ.flags != {}: {tfNotNil, tfNeedsInit} * v.typ.flags != {}:
if v.ast.isNil: if v.astdef.isNil:
message(c.config, v.info, warnProveInit, v.name.s) message(c.config, v.info, warnProveInit, v.name.s)
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, v.info, warnProveInit, v.name.s) message(c.config, v.info, warnProveInit, v.name.s)
include semasgn include semasgn
@ -518,8 +518,6 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
message(c.config, a.info, warnShadowIdent, v.name.s) message(c.config, a.info, warnShadowIdent, v.name.s)
if a.kind != nkVarTuple: if a.kind != nkVarTuple:
if def.kind != nkEmpty: 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, def.info, errThreadvarCannotInit) if sfThread in v.flags: localError(c.config, def.info, errThreadvarCannotInit)
setVarType(c, v, typ) setVarType(c, v, typ)
b = newNodeI(nkIdentDefs, a.info) b = newNodeI(nkIdentDefs, a.info)
@ -531,6 +529,23 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
addSon(b, a.sons[length-2]) addSon(b, a.sons[length-2])
addSon(b, copyTree(def)) addSon(b, copyTree(def))
addToVarSection(c, result, n, b) addToVarSection(c, result, n, b)
if optOldAst in c.config.options:
if def.kind != nkEmpty:
v.ast = def
else:
# this is needed for the evaluation pass, guard checking
# and custom pragmas:
var ast = newNodeI(nkIdentDefs, a.info)
if a[j].kind == nkPragmaExpr:
var p = newNodeI(nkPragmaExpr, a.info)
p.add newSymNode(v)
p.add a[j][1].copyTree
ast.add p
else:
ast.add newSymNode(v)
ast.add a.sons[length-2].copyTree
ast.add def
v.ast = ast
else: else:
if def.kind in {nkPar, nkTupleConstr}: v.ast = def[j] if def.kind in {nkPar, nkTupleConstr}: v.ast = def[j]
# bug #7663, for 'nim check' this can be a non-tuple: # bug #7663, for 'nim check' this can be a non-tuple:

View file

@ -76,6 +76,7 @@ Advanced options:
strings is allowed; only for backwards compatibility strings is allowed; only for backwards compatibility
--nilseqs:on|off allow 'nil' for strings/seqs for --nilseqs:on|off allow 'nil' for strings/seqs for
backwards compatibility backwards compatibility
--oldast:on|off use old AST for backwards compatibility
--skipCfg do not read the general configuration file --skipCfg do not read the general configuration file
--skipUserCfg do not read the user's configuration file --skipUserCfg do not read the user's configuration file
--skipParentCfg do not read the parent dirs' configuration files --skipParentCfg do not read the parent dirs' configuration files

View file

@ -1402,8 +1402,14 @@ proc customPragmaNode(n: NimNode): NimNode =
let impl = n.getImpl() let impl = n.getImpl()
if impl.kind in RoutineNodes: if impl.kind in RoutineNodes:
return impl.pragma return impl.pragma
elif impl.kind == nnkIdentDefs and impl[0].kind == nnkPragmaExpr:
return impl[0][1]
else: else:
return typ.getImpl()[0][1] 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}: if n.kind in {nnkDotExpr, nnkCheckedFieldExpr}:
let name = $(if n.kind == nnkCheckedFieldExpr: n[0][1] else: n[1]) let name = $(if n.kind == nnkCheckedFieldExpr: n[0][1] else: n[1])

View file

@ -175,7 +175,6 @@ var foo: Something
foo.cardinal = north foo.cardinal = north
doAssert foo.b.hasCustomPragma(thingy) == true doAssert foo.b.hasCustomPragma(thingy) == true
proc myproc(s: string): int = proc myproc(s: string): int =
{.thingy.}: {.thingy.}:
s.len s.len
@ -188,7 +187,6 @@ let xx = compiles:
s.len s.len
doAssert: xx == false doAssert: xx == false
macro checkSym(s: typed{nkSym}): untyped = macro checkSym(s: typed{nkSym}): untyped =
let body = s.getImpl.body let body = s.getImpl.body
doAssert body[1].kind == nnkPragmaBlock doAssert body[1].kind == nnkPragmaBlock
@ -196,3 +194,28 @@ macro checkSym(s: typed{nkSym}): untyped =
doAssert body[1][0][0] == bindSym"thingy" doAssert body[1][0][0] == bindSym"thingy"
checkSym(myproc) checkSym(myproc)
# var and let pragmas
block:
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"
check(a)
check(b)
check(x)
check(y)
check(z)