* implements https://github.com/nim-lang/RFCs/issues/258

* don't be too strict with custom pragma blocks

* cast pragmas: documentation

* added most missing inference query procs to effecttraits.nim
This commit is contained in:
Andreas Rumpf 2020-10-06 16:47:15 +02:00 • committed by GitHub
commit 92163fa330
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 232 additions and 68 deletions

View file

@ -304,10 +304,13 @@ proc mydiv(a, b): int {.raises: [].} =
- `system.deepcopy` has to be enabled explicitly for `--gc:arc` and `--gc:orc` via - `system.deepcopy` has to be enabled explicitly for `--gc:arc` and `--gc:orc` via
`--deepcopy:on`. `--deepcopy:on`.
- Added a `std/effecttraits` module for introspection of the inferred `.raise` effects. - Added a `std/effecttraits` module for introspection of the inferred effects.
We hope this enables `async` macros that are precise about the possible exceptions that We hope this enables `async` macros that are precise about the possible exceptions that
can be raised. can be raised.
- Added `critbits.toCritBitTree`, similar to `tables.toTable`, creates a new `CritBitTree` with given arguments. - Added `critbits.toCritBitTree`, similar to `tables.toTable`, creates a new `CritBitTree` with given arguments.
- The pragma blocks `{.gcsafe.}: ...` and `{.noSideEffect.}: ...` can now also be
written as `{.cast(gcsafe).}: ...` and `{.cast(noSideEffect).}: ...`. This is the new
preferred way of writing these, emphasizing their unsafe nature.
## Compiler changes ## Compiler changes

View file

@ -121,3 +121,5 @@ proc initDefines*(symbols: StringTableRef) =
defineSymbol("nimHasLentIterators") defineSymbol("nimHasLentIterators")
defineSymbol("nimHasDeclaredMagic") defineSymbol("nimHasDeclaredMagic")
defineSymbol("nimHasStacktracesModule") defineSymbol("nimHasStacktracesModule")
defineSymbol("nimHasEffectTraitsModule")
defineSymbol("nimHasCastPragmaBlocks")

View file

@ -485,10 +485,12 @@ proc setOrTableConstr(p: var Parser): PNode =
eat(p, tkCurlyRi) # skip '}' eat(p, tkCurlyRi) # skip '}'
proc parseCast(p: var Parser): PNode = proc parseCast(p: var Parser): PNode =
#| castExpr = 'cast' '[' optInd typeDesc optPar ']' '(' optInd expr optPar ')' #| castExpr = 'cast' ('[' optInd typeDesc optPar ']' '(' optInd expr optPar ')') /
# ('(' optInd exprColonEqExpr optPar ')')
result = newNodeP(nkCast, p) result = newNodeP(nkCast, p)
getTok(p) getTok(p)
eat(p, tkBracketLe) if p.tok.tokType == tkBracketLe:
getTok(p)
optInd(p, result) optInd(p, result)
result.add(parseTypeDesc(p)) result.add(parseTypeDesc(p))
optPar(p) optPar(p)
@ -496,6 +498,11 @@ proc parseCast(p: var Parser): PNode =
eat(p, tkParLe) eat(p, tkParLe)
optInd(p, result) optInd(p, result)
result.add(parseExpr(p)) result.add(parseExpr(p))
else:
result.add p.emptyNode
eat(p, tkParLe)
optInd(p, result)
result.add(exprColonEqExpr(p))
optPar(p) optPar(p)
eat(p, tkParRi) eat(p, tkParRi)

View file

@ -771,6 +771,15 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
if key.kind == nkBracketExpr: if key.kind == nkBracketExpr:
processNote(c, it) processNote(c, it)
return return
elif key.kind == nkCast:
if comesFromPush:
localError(c.config, n.info, "a 'cast' pragma cannot be pushed")
elif not isStatement:
localError(c.config, n.info, "'cast' pragma only allowed in a statement context")
case whichPragma(key[1])
of wRaises, wTags: pragmaRaisesOrTags(c, key[1])
else: discard
return
elif key.kind notin nkIdentKinds: elif key.kind notin nkIdentKinds:
n[i] = semCustomPragma(c, it) n[i] = semCustomPragma(c, it)
return return

View file

@ -1057,6 +1057,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
put(g, tkSymbol, "(wrong conv)") put(g, tkSymbol, "(wrong conv)")
of nkCast: of nkCast:
put(g, tkCast, "cast") put(g, tkCast, "cast")
if n.len > 0 and n[0].kind != nkEmpty:
put(g, tkBracketLe, "[") put(g, tkBracketLe, "[")
gsub(g, n, 0) gsub(g, n, 0)
put(g, tkBracketRi, "]") put(g, tkBracketRi, "]")

View file

@ -325,7 +325,7 @@ proc createTag(g: ModuleGraph; n: PNode): PNode =
result.typ = g.sysTypeFromName(n.info, "RootEffect") result.typ = g.sysTypeFromName(n.info, "RootEffect")
if not n.isNil: result.info = n.info if not n.isNil: result.info = n.info
proc addEffect(a: PEffects, e, comesFrom: PNode) = proc addRaiseEffect(a: PEffects, e, comesFrom: PNode) =
assert e.kind != nkRaiseStmt assert e.kind != nkRaiseStmt
var aa = a.exc var aa = a.exc
for i in a.bottom..<aa.len: for i in a.bottom..<aa.len:
@ -345,11 +345,11 @@ proc addTag(a: PEffects, e, comesFrom: PNode) =
if sameType(aa[i].typ.skipTypes(skipPtrs), e.typ.skipTypes(skipPtrs)): return if sameType(aa[i].typ.skipTypes(skipPtrs), e.typ.skipTypes(skipPtrs)): return
throws(a.tags, e, comesFrom) throws(a.tags, e, comesFrom)
proc mergeEffects(a: PEffects, b, comesFrom: PNode) = proc mergeRaises(a: PEffects, b, comesFrom: PNode) =
if b.isNil: if b.isNil:
addEffect(a, createRaise(a.graph, comesFrom), comesFrom) addRaiseEffect(a, createRaise(a.graph, comesFrom), comesFrom)
else: else:
for effect in items(b): addEffect(a, effect, comesFrom) for effect in items(b): addRaiseEffect(a, effect, comesFrom)
proc mergeTags(a: PEffects, b, comesFrom: PNode) = proc mergeTags(a: PEffects, b, comesFrom: PNode) =
if b.isNil: if b.isNil:
@ -489,7 +489,7 @@ proc mergeLockLevels(tracked: PEffects, n: PNode, lockLevel: TLockLevel) =
proc propagateEffects(tracked: PEffects, n: PNode, s: PSym) = proc propagateEffects(tracked: PEffects, n: PNode, s: PSym) =
let pragma = s.ast[pragmasPos] let pragma = s.ast[pragmasPos]
let spec = effectSpec(pragma, wRaises) let spec = effectSpec(pragma, wRaises)
mergeEffects(tracked, spec, n) mergeRaises(tracked, spec, n)
let tagSpec = effectSpec(pragma, wTags) let tagSpec = effectSpec(pragma, wTags)
mergeTags(tracked, tagSpec, n) mergeTags(tracked, tagSpec, n)
@ -535,7 +535,7 @@ proc notNilCheck(tracked: PEffects, n: PNode, paramType: PType) =
of impYes: discard of impYes: discard
proc assumeTheWorst(tracked: PEffects; n: PNode; op: PType) = proc assumeTheWorst(tracked: PEffects; n: PNode; op: PType) =
addEffect(tracked, createRaise(tracked.graph, n), nil) addRaiseEffect(tracked, createRaise(tracked.graph, n), nil)
addTag(tracked, createTag(tracked.graph, n), nil) addTag(tracked, createTag(tracked.graph, n), nil)
let lockLevel = if op.lockLevel == UnspecifiedLockLevel: UnknownLockLevel let lockLevel = if op.lockLevel == UnspecifiedLockLevel: UnknownLockLevel
else: op.lockLevel else: op.lockLevel
@ -581,7 +581,7 @@ proc trackOperandForIndirectCall(tracked: PEffects, n: PNode, paramType: PType;
elif tfNoSideEffect notin op.flags and not isOwnedProcVar(a, tracked.owner): elif tfNoSideEffect notin op.flags and not isOwnedProcVar(a, tracked.owner):
markSideEffect(tracked, a) markSideEffect(tracked, a)
else: else:
mergeEffects(tracked, effectList[exceptionEffects], n) mergeRaises(tracked, effectList[exceptionEffects], n)
mergeTags(tracked, effectList[tagEffects], n) mergeTags(tracked, effectList[tagEffects], n)
if notGcSafe(op): if notGcSafe(op):
if tracked.config.hasWarn(warnGcUnsafe): warnAboutGcUnsafe(n, tracked.config) if tracked.config.hasWarn(warnGcUnsafe): warnAboutGcUnsafe(n, tracked.config)
@ -780,7 +780,7 @@ proc trackCall(tracked: PEffects; n: PNode) =
assumeTheWorst(tracked, n, op) assumeTheWorst(tracked, n, op)
gcsafeAndSideeffectCheck() gcsafeAndSideeffectCheck()
else: else:
mergeEffects(tracked, effectList[exceptionEffects], n) mergeRaises(tracked, effectList[exceptionEffects], n)
mergeTags(tracked, effectList[tagEffects], n) mergeTags(tracked, effectList[tagEffects], n)
gcsafeAndSideeffectCheck() gcsafeAndSideeffectCheck()
if a.kind != nkSym or a.sym.magic != mNBindSym: if a.kind != nkSym or a.sym.magic != mNBindSym:
@ -837,6 +837,64 @@ proc trackCall(tracked: PEffects; n: PNode) =
# initVar(tracked, n[i].skipAddr, false) # initVar(tracked, n[i].skipAddr, false)
else: discard else: discard
type
PragmaBlockContext = object
oldLocked: int
oldLockLevel: TLockLevel
enforcedGcSafety, enforceNoSideEffects: bool
oldExc, oldTags: int
exc, tags: PNode
proc createBlockContext(tracked: PEffects): PragmaBlockContext =
result = PragmaBlockContext(oldLocked: tracked.locked.len,
oldLockLevel: tracked.currLockLevel,
enforcedGcSafety: false, enforceNoSideEffects: false,
oldExc: tracked.exc.len, oldTags: tracked.tags.len)
proc applyBlockContext(tracked: PEffects, bc: PragmaBlockContext) =
if bc.enforcedGcSafety: tracked.inEnforcedGcSafe = true
if bc.enforceNoSideEffects: tracked.inEnforcedNoSideEffects = true
proc unapplyBlockContext(tracked: PEffects; bc: PragmaBlockContext) =
if bc.enforcedGcSafety: tracked.inEnforcedGcSafe = false
if bc.enforceNoSideEffects: tracked.inEnforcedNoSideEffects = false
setLen(tracked.locked, bc.oldLocked)
tracked.currLockLevel = bc.oldLockLevel
if bc.exc != nil:
# beware that 'raises: []' is very different from not saying
# anything about 'raises' in the 'cast' at all. Same applies for 'tags'.
setLen(tracked.exc.sons, bc.oldExc)
for e in bc.exc:
addRaiseEffect(tracked, e, e)
if bc.tags != nil:
setLen(tracked.tags.sons, bc.oldTags)
for t in bc.tags:
addTag(tracked, t, t)
proc castBlock(tracked: PEffects, pragma: PNode, bc: var PragmaBlockContext) =
case whichPragma(pragma)
of wGcSafe:
bc.enforcedGcSafety = true
of wNoSideEffect:
bc.enforceNoSideEffects = true
of wTags:
let n = pragma[1]
if n.kind in {nkCurly, nkBracket}:
bc.tags = n
else:
bc.tags = newNodeI(nkArgList, pragma.info)
bc.tags.add n
of wRaises:
let n = pragma[1]
if n.kind in {nkCurly, nkBracket}:
bc.exc = n
else:
bc.exc = newNodeI(nkArgList, pragma.info)
bc.exc.add n
else:
localError(tracked.config, pragma.info,
"invalid pragma block: " & $pragma)
proc track(tracked: PEffects, n: PNode) = proc track(tracked: PEffects, n: PNode) =
case n.kind case n.kind
of nkSym: of nkSym:
@ -854,7 +912,7 @@ proc track(tracked: PEffects, n: PNode) =
if n[0].kind != nkEmpty: if n[0].kind != nkEmpty:
n[0].info = n.info n[0].info = n.info
#throws(tracked.exc, n[0]) #throws(tracked.exc, n[0])
addEffect(tracked, n[0], nil) addRaiseEffect(tracked, n[0], nil)
for i in 0..<n.safeLen: for i in 0..<n.safeLen:
track(tracked, n[i]) track(tracked, n[i])
createTypeBoundOps(tracked, n[0].typ, n.info) createTypeBoundOps(tracked, n[0].typ, n.info)
@ -862,7 +920,7 @@ proc track(tracked: PEffects, n: PNode) =
# A `raise` with no arguments means we're going to re-raise the exception # A `raise` with no arguments means we're going to re-raise the exception
# being handled or, if outside of an `except` block, a `ReraiseDefect`. # being handled or, if outside of an `except` block, a `ReraiseDefect`.
# Here we add a `Exception` tag in order to cover both the cases. # Here we add a `Exception` tag in order to cover both the cases.
addEffect(tracked, createRaise(tracked.graph, n), nil) addRaiseEffect(tracked, createRaise(tracked.graph, n), nil)
of nkCallKinds: of nkCallKinds:
trackCall(tracked, n) trackCall(tracked, n)
of nkDotExpr: of nkDotExpr:
@ -1011,26 +1069,24 @@ proc track(tracked: PEffects, n: PNode) =
checkForSink(tracked.config, tracked.owner, n[i]) checkForSink(tracked.config, tracked.owner, n[i])
of nkPragmaBlock: of nkPragmaBlock:
let pragmaList = n[0] let pragmaList = n[0]
let oldLocked = tracked.locked.len var bc = createBlockContext(tracked)
let oldLockLevel = tracked.currLockLevel
var enforcedGcSafety = false
var enforceNoSideEffects = false
for i in 0..<pragmaList.len: for i in 0..<pragmaList.len:
let pragma = whichPragma(pragmaList[i]) let pragma = whichPragma(pragmaList[i])
if pragma == wLocks: case pragma
of wLocks:
lockLocations(tracked, pragmaList[i]) lockLocations(tracked, pragmaList[i])
elif pragma == wGcSafe: of wGcSafe:
enforcedGcSafety = true bc.enforcedGcSafety = true
elif pragma == wNoSideEffect: of wNoSideEffect:
enforceNoSideEffects = true bc.enforceNoSideEffects = true
of wCast:
if enforcedGcSafety: tracked.inEnforcedGcSafe = true castBlock(tracked, pragmaList[i][1], bc)
if enforceNoSideEffects: tracked.inEnforcedNoSideEffects = true else:
discard
applyBlockContext(tracked, bc)
track(tracked, n.lastSon) track(tracked, n.lastSon)
if enforcedGcSafety: tracked.inEnforcedGcSafe = false unapplyBlockContext(tracked, bc)
if enforceNoSideEffects: tracked.inEnforcedNoSideEffects = false
setLen(tracked.locked, oldLocked)
tracked.currLockLevel = oldLockLevel
of nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef, nkIteratorDef, of nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef, nkIteratorDef,
nkMacroDef, nkTemplateDef, nkLambda, nkDo, nkFuncDef: nkMacroDef, nkTemplateDef, nkLambda, nkDo, nkFuncDef:
discard discard

View file

@ -2169,7 +2169,7 @@ proc setLine(n: PNode, info: TLineInfo) =
proc semPragmaBlock(c: PContext, n: PNode): PNode = proc semPragmaBlock(c: PContext, n: PNode): PNode =
checkSonsLen(n, 2, c.config) checkSonsLen(n, 2, c.config)
let pragmaList = n[0] let pragmaList = n[0]
pragma(c, nil, pragmaList, exprPragmas) pragma(c, nil, pragmaList, exprPragmas, isStatement = true)
n[1] = semExpr(c, n[1]) n[1] = semExpr(c, n[1])
result = n result = n
result.typ = n[1].typ result.typ = n[1].typ

View file

@ -1009,6 +1009,7 @@ proc transform(c: PTransf, n: PNode): PNode =
# Constants can be inlined here, but only if they cannot result in a cast # Constants can be inlined here, but only if they cannot result in a cast
# in the back-end (e.g. var p: pointer = someProc) # in the back-end (e.g. var p: pointer = someProc)
let exprIsPointerCast = n.kind in {nkCast, nkConv, nkHiddenStdConv} and let exprIsPointerCast = n.kind in {nkCast, nkConv, nkHiddenStdConv} and
n.typ != nil and
n.typ.kind == tyPointer n.typ.kind == tyPointer
if not exprIsPointerCast: if not exprIsPointerCast:
var cnst = getConstExpr(c.module, result, c.graph) var cnst = getConstExpr(c.module, result, c.graph)

View file

@ -130,7 +130,19 @@ proc isRange*(n: PNode): bool {.inline.} =
proc whichPragma*(n: PNode): TSpecialWord = proc whichPragma*(n: PNode): TSpecialWord =
let key = if n.kind in nkPragmaCallKinds and n.len > 0: n[0] else: n let key = if n.kind in nkPragmaCallKinds and n.len > 0: n[0] else: n
if key.kind == nkIdent: result = whichKeyword(key.ident) case key.kind
of nkIdent: result = whichKeyword(key.ident)
of nkSym: result = whichKeyword(key.sym.name)
of nkCast: result = wCast
of nkClosedSymChoice, nkOpenSymChoice:
result = whichPragma(key[0])
else: result = wInvalid
proc isNoSideEffectPragma*(n: PNode): bool =
var k = whichPragma(n)
if k == wCast:
k = whichPragma(n[1])
result = k == wNoSideEffect
proc findPragma*(n: PNode, which: TSpecialWord): PNode = proc findPragma*(n: PNode, which: TSpecialWord): PNode =
if n.kind == nkPragma: if n.kind == nkPragma:

View file

@ -29,8 +29,7 @@
## for a high-level description of how borrow checking works. ## for a high-level description of how borrow checking works.
import ast, types, lineinfos, options, msgs, renderer, typeallowed import ast, types, lineinfos, options, msgs, renderer, typeallowed
from trees import getMagic, whichPragma, stupidStmtListExpr from trees import getMagic, isNoSideEffectPragma, stupidStmtListExpr
from wordrecg import wNoSideEffect
from isolation_check import canAlias from isolation_check import canAlias
type type
@ -713,7 +712,7 @@ proc traverse(c: var Partitions; n: PNode) =
let pragmaList = n[0] let pragmaList = n[0]
var enforceNoSideEffects = 0 var enforceNoSideEffects = 0
for i in 0..<pragmaList.len: for i in 0..<pragmaList.len:
if whichPragma(pragmaList[i]) == wNoSideEffect: if isNoSideEffectPragma(pragmaList[i]):
enforceNoSideEffects = 1 enforceNoSideEffects = 1
break break

View file

@ -262,11 +262,25 @@ proc registerAdditionalOps*(c: PCtx) =
registerCallback c, "stdlib.times.getTime", proc (a: VmArgs) {.nimcall.} = registerCallback c, "stdlib.times.getTime", proc (a: VmArgs) {.nimcall.} =
setResult(a, times.getTime().toLit) setResult(a, times.getTime().toLit)
registerCallback c, "stdlib.effecttraits.getRaisesListImpl", proc (a: VmArgs) = proc getEffectList(c: PCtx; a: VmArgs; effectIndex: int) =
let fn = getNode(a, 0) let fn = getNode(a, 0)
if fn.typ != nil and fn.typ.n != nil and fn.typ.n[0].len >= effectListLen and if fn.typ != nil and fn.typ.n != nil and fn.typ.n[0].len >= effectListLen and
fn.typ.n[0][exceptionEffects] != nil: fn.typ.n[0][effectIndex] != nil:
var list = newNodeI(nkBracket, fn.info) var list = newNodeI(nkBracket, fn.info)
for e in fn.typ.n[0][exceptionEffects]: for e in fn.typ.n[0][effectIndex]:
list.add opMapTypeInstToAst(c.cache, e.typ.skipTypes({tyRef}), e.info) list.add opMapTypeInstToAst(c.cache, e.typ.skipTypes({tyRef}), e.info)
setResult(a, list) setResult(a, list)
registerCallback c, "stdlib.effecttraits.getRaisesListImpl", proc (a: VmArgs) =
getEffectList(c, a, exceptionEffects)
registerCallback c, "stdlib.effecttraits.getTagsListImpl", proc (a: VmArgs) =
getEffectList(c, a, tagEffects)
registerCallback c, "stdlib.effecttraits.isGcSafeImpl", proc (a: VmArgs) =
let fn = getNode(a, 0)
setResult(a, fn.typ != nil and tfGcSafe in fn.typ.flags)
registerCallback c, "stdlib.effecttraits.hasNoSideEffectsImpl", proc (a: VmArgs) =
let fn = getNode(a, 0)
setResult(a, (fn.typ != nil and tfNoSideEffect in fn.typ.flags) or
(fn.kind == nkSym and fn.sym.kind == skFunc))

View file

@ -6,7 +6,7 @@ colon = ':' COMMENT?
colcom = ':' COMMENT? colcom = ':' COMMENT?
operator = OP0 | OP1 | OP2 | OP3 | OP4 | OP5 | OP6 | OP7 | OP8 | OP9 operator = OP0 | OP1 | OP2 | OP3 | OP4 | OP5 | OP6 | OP7 | OP8 | OP9
| 'or' | 'xor' | 'and' | 'or' | 'xor' | 'and'
| 'is' | 'isnot' | 'in' | 'notin' | 'of' | 'as' | 'from' | | 'is' | 'isnot' | 'in' | 'notin' | 'of' | 'as' | 'from'
| 'div' | 'mod' | 'shl' | 'shr' | 'not' | 'static' | '..' | 'div' | 'mod' | 'shl' | 'shr' | 'not' | 'static' | '..'
prefixOperator = operator prefixOperator = operator
optInd = COMMENT? IND? optInd = COMMENT? IND?
@ -31,15 +31,15 @@ dotExpr = expr '.' optInd (symbol | '[:' exprList ']')
explicitGenericInstantiation = '[:' exprList ']' ( '(' exprColonEqExpr ')' )? explicitGenericInstantiation = '[:' exprList ']' ( '(' exprColonEqExpr ')' )?
qualifiedIdent = symbol ('.' optInd symbol)? qualifiedIdent = symbol ('.' optInd symbol)?
setOrTableConstr = '{' ((exprColonEqExpr comma)* | ':' ) '}' setOrTableConstr = '{' ((exprColonEqExpr comma)* | ':' ) '}'
castExpr = 'cast' '[' optInd typeDesc optPar ']' '(' optInd expr optPar ')' castExpr = 'cast' ('[' optInd typeDesc optPar ']' '(' optInd expr optPar ')') /
parKeyw = 'discard' | 'include' | 'if' | 'while' | 'case' | 'try' parKeyw = 'discard' | 'include' | 'if' | 'while' | 'case' | 'try'
| 'finally' | 'except' | 'for' | 'block' | 'const' | 'let' | 'finally' | 'except' | 'for' | 'block' | 'const' | 'let'
| 'when' | 'var' | 'mixin' | 'when' | 'var' | 'mixin'
par = '(' optInd par = '(' optInd
( &parKeyw complexOrSimpleStmt ^+ ';' ( &parKeyw (ifExpr \ complexOrSimpleStmt) ^+ ';'
| ';' complexOrSimpleStmt ^+ ';' | ';' (ifExpr \ complexOrSimpleStmt) ^+ ';'
| pragmaStmt | pragmaStmt
| simpleExpr ( ('=' expr (';' complexOrSimpleStmt ^+ ';' )? ) | simpleExpr ( ('=' expr (';' (ifExpr \ complexOrSimpleStmt) ^+ ';' )? )
| (':' expr (',' exprColonEqExpr ^+ ',' )? ) ) ) | (':' expr (',' exprColonEqExpr ^+ ',' )? ) ) )
optPar ')' optPar ')'
literal = | INT_LIT | INT8_LIT | INT16_LIT | INT32_LIT | INT64_LIT literal = | INT_LIT | INT8_LIT | INT16_LIT | INT32_LIT | INT64_LIT
@ -59,11 +59,6 @@ primarySuffix = '(' (exprColonEqExpr comma?)* ')'
| '[' optInd exprColonEqExprList optPar ']' | '[' optInd exprColonEqExprList optPar ']'
| '{' optInd exprColonEqExprList optPar '}' | '{' optInd exprColonEqExprList optPar '}'
| &( '`'|IDENT|literal|'cast'|'addr'|'type') expr # command syntax | &( '`'|IDENT|literal|'cast'|'addr'|'type') expr # command syntax
condExpr = expr colcom expr optInd
('elif' expr colcom expr optInd)*
'else' colcom expr
ifExpr = 'if' condExpr
whenExpr = 'when' condExpr
pragma = '{.' optInd (exprColonEqExpr comma?)* optPar ('.}' | '}') pragma = '{.' optInd (exprColonEqExpr comma?)* optPar ('.}' | '}')
identVis = symbol OPR? # postfix position identVis = symbol OPR? # postfix position
identVisDot = symbol '.' optInd symbol OPR? identVisDot = symbol '.' optInd symbol OPR?
@ -130,6 +125,11 @@ condStmt = expr colcom stmt COMMENT?
(IND{=} 'else' colcom stmt)? (IND{=} 'else' colcom stmt)?
ifStmt = 'if' condStmt ifStmt = 'if' condStmt
whenStmt = 'when' condStmt whenStmt = 'when' condStmt
condExpr = expr colcom expr optInd
('elif' expr colcom expr optInd)*
'else' colcom expr
ifExpr = 'if' condExpr
whenExpr = 'when' condExpr
whileStmt = 'while' expr colcom stmt whileStmt = 'while' expr colcom stmt
ofBranch = 'of' exprList colcom stmt ofBranch = 'of' exprList colcom stmt
ofBranches = ofBranch (IND{=} ofBranch)* ofBranches = ofBranch (IND{=} ofBranch)*
@ -193,8 +193,8 @@ complexOrSimpleStmt = (ifStmt | whenStmt | whileStmt
| tryStmt | forStmt | tryStmt | forStmt
| blockStmt | staticStmt | deferStmt | asmStmt | blockStmt | staticStmt | deferStmt | asmStmt
| 'proc' routine | 'proc' routine
| 'func' routine
| 'method' routine | 'method' routine
| 'func' routine
| 'iterator' routine | 'iterator' routine
| 'macro' routine | 'macro' routine
| 'template' routine | 'template' routine

View file

@ -6031,12 +6031,12 @@ so that it can be used for debugging routines marked as ``noSideEffect``.
To override the compiler's side effect analysis a ``{.noSideEffect.}`` To override the compiler's side effect analysis a ``{.noSideEffect.}``
pragma block can be used: ``cast`` pragma block can be used:
.. code-block:: nim .. code-block:: nim
func f() = func f() =
{.noSideEffect.}: {.cast(noSideEffect).}:
echo "test" echo "test"
@ -7501,7 +7501,7 @@ To disable the GC-safety checking the ``--threadAnalysis:off`` command line
switch can be used. This is a temporary workaround to ease the porting effort switch can be used. This is a temporary workaround to ease the porting effort
from old code to the new threading model. from old code to the new threading model.
To override the compiler's gcsafety analysis a ``{.gcsafe.}`` pragma block can To override the compiler's gcsafety analysis a ``{.cast(gcsafe).}`` pragma block can
be used: be used:
.. code-block:: nim .. code-block:: nim
@ -7511,7 +7511,7 @@ be used:
perThread {.threadvar.}: string perThread {.threadvar.}: string
proc setPerThread() = proc setPerThread() =
{.gcsafe.}: {.cast(gcsafe).}:
deepCopy(perThread, someGlobal) deepCopy(perThread, someGlobal)

View file

@ -1,7 +1,7 @@
# #
# #
# Nim's Runtime Library # Nim's Runtime Library
# (c) Copyright 2018 Nim contributors # (c) Copyright 2020 Nim contributors
# #
# See the file "copying.txt", included in this # See the file "copying.txt", included in this
# distribution, for details about the copyright. # distribution, for details about the copyright.
@ -9,11 +9,46 @@
## This module provides access to the inferred .raises effects ## This module provides access to the inferred .raises effects
## for Nim's macro system. ## for Nim's macro system.
## **Since**: Version 1.4.
##
## One can test for the existance of this standard module
## via ``defined(nimHasEffectTraitsModule)``.
import macros import macros
proc getRaisesListImpl(n: NimNode): NimNode = discard "see compiler/vmops.nim" proc getRaisesListImpl(n: NimNode): NimNode = discard "see compiler/vmops.nim"
proc getTagsListImpl(n: NimNode): NimNode = discard "see compiler/vmops.nim"
proc isGcSafeImpl(n: NimNode): bool = discard "see compiler/vmops.nim"
proc hasNoSideEffectsImpl(n: NimNode): bool = discard "see compiler/vmops.nim"
proc getRaisesList*(call: NimNode): NimNode = proc getRaisesList*(fn: NimNode): NimNode =
expectKind call, nnkCallKinds ## Extracts the ``.raises`` list of the func/proc/etc ``fn``.
result = getRaisesListImpl(call[0]) ## ``fn`` has to be a resolved symbol of kind ``nnkSym``. This
## implies that the macro that calls this proc should accept ``typed``
## arguments and not ``untyped`` arguments.
expectKind fn, nnkSym
result = getRaisesListImpl(fn)
proc getTagsList*(fn: NimNode): NimNode =
## Extracts the ``.tags`` list of the func/proc/etc ``fn``.
## ``fn`` has to be a resolved symbol of kind ``nnkSym``. This
## implies that the macro that calls this proc should accept ``typed``
## arguments and not ``untyped`` arguments.
expectKind fn, nnkSym
result = getTagsListImpl(fn)
proc isGcSafe*(fn: NimNode): bool =
## Return true if the func/proc/etc ``fn`` is `gcsafe`.
## ``fn`` has to be a resolved symbol of kind ``nnkSym``. This
## implies that the macro that calls this proc should accept ``typed``
## arguments and not ``untyped`` arguments.
expectKind fn, nnkSym
result = isGcSafeImpl(fn)
proc hasNoSideEffects*(fn: NimNode): bool =
## Return true if the func/proc/etc ``fn`` has `noSideEffect`.
## ``fn`` has to be a resolved symbol of kind ``nnkSym``. This
## implies that the macro that calls this proc should accept ``typed``
## arguments and not ``untyped`` arguments.
expectKind fn, nnkSym
result = hasNoSideEffectsImpl(fn)

View file

@ -0,0 +1,18 @@
discard """
cmd: "nim c $file"
action: "compile"
"""
proc taggy() {.tags: RootEffect.} = discard
proc m {.raises: [], tags: [].} =
{.cast(noSideEffect).}:
echo "hi"
{.cast(raises: []).}:
raise newException(ValueError, "bah")
{.cast(tags: []).}:
taggy()
m()

View file

@ -1,5 +1,7 @@
discard """ discard """
nimout: '''##[ValueError, Gen[string]]##''' nimout: '''##[ValueError, Gen[string]]##
%%[RootEffect]%%
true true'''
""" """
import macros import macros
@ -10,13 +12,18 @@ type
x: T x: T
macro m(call: typed): untyped = macro m(call: typed): untyped =
echo "##", repr getRaisesList(call), "##" echo "##", repr getRaisesList(call[0]), "##"
echo "%%", repr getTagsList(call[0]), "%%"
echo isGcSafe(call[0]), " ", hasNoSideEffects(call[0])
result = call result = call
proc gutenTag() {.tags: RootEffect.} = discard
proc r(inp: int) = proc r(inp: int) =
if inp == 0: if inp == 0:
raise newException(ValueError, "bah") raise newException(ValueError, "bah")
elif inp == 1: elif inp == 1:
raise newException(Gen[string], "bahB") raise newException(Gen[string], "bahB")
gutenTag()
m r(2) m r(2)