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
@ -143,10 +145,10 @@ proc enumToString*(enums: openArray[enum]): string =
it's more recognizable and allows tools like github to recognize it as Nim, it's more recognizable and allows tools like github to recognize it as Nim,
see [#9647](https://github.com/nim-lang/Nim/issues/9647). see [#9647](https://github.com/nim-lang/Nim/issues/9647).
The previous extension will continue to work. 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 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 list is deprecated to be consistent with how pragmas are used with a proc. See
[#8514](https://github.com/nim-lang/Nim/issues/8514) and [#8514](https://github.com/nim-lang/Nim/issues/8514) and
[#1872](https://github.com/nim-lang/Nim/issues/1872) for further details. [#1872](https://github.com/nim-lang/Nim/issues/1872) for further details.

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,24 +175,47 @@ 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
doAssert myproc("123") == 3 doAssert myproc("123") == 3
let xx = compiles: let xx = compiles:
proc myproc_bad(s: string): int = proc myproc_bad(s: string): int =
{.not_exist.}: {.not_exist.}:
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
doAssert body[1][0].kind == nnkPragma doAssert body[1][0].kind == nnkPragma
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)