Skip to content

Commit

Permalink
implements nim-lang/RFCs#258
Browse files Browse the repository at this point in the history
  • Loading branch information
Araq committed Oct 6, 2020
1 parent 3775882 commit 4712ba4
Show file tree
Hide file tree
Showing 11 changed files with 160 additions and 53 deletions.
1 change: 1 addition & 0 deletions compiler/condsyms.nim
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,4 @@ proc initDefines*(symbols: StringTableRef) =
defineSymbol("nimHasLentIterators")
defineSymbol("nimHasDeclaredMagic")
defineSymbol("nimHasStacktracesModule")
defineSymbol("nimHasCastPragmaBlocks")
25 changes: 16 additions & 9 deletions compiler/parser.nim
Original file line number Diff line number Diff line change
Expand Up @@ -485,17 +485,24 @@ proc setOrTableConstr(p: var Parser): PNode =
eat(p, tkCurlyRi) # skip '}'

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)
getTok(p)
eat(p, tkBracketLe)
optInd(p, result)
result.add(parseTypeDesc(p))
optPar(p)
eat(p, tkBracketRi)
eat(p, tkParLe)
optInd(p, result)
result.add(parseExpr(p))
if p.tok.tokType == tkBracketLe:
getTok(p)
optInd(p, result)
result.add(parseTypeDesc(p))
optPar(p)
eat(p, tkBracketRi)
eat(p, tkParLe)
optInd(p, result)
result.add(parseExpr(p))
else:
result.add p.emptyNode
eat(p, tkParLe)
optInd(p, result)
result.add(exprColonEqExpr(p))
optPar(p)
eat(p, tkParRi)

Expand Down
9 changes: 9 additions & 0 deletions compiler/pragmas.nim
Original file line number Diff line number Diff line change
Expand Up @@ -771,6 +771,15 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
if key.kind == nkBracketExpr:
processNote(c, it)
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:
n[i] = semCustomPragma(c, it)
return
Expand Down
7 changes: 4 additions & 3 deletions compiler/renderer.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1057,9 +1057,10 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
put(g, tkSymbol, "(wrong conv)")
of nkCast:
put(g, tkCast, "cast")
put(g, tkBracketLe, "[")
gsub(g, n, 0)
put(g, tkBracketRi, "]")
if n.len > 0 and n[0].kind != nkEmpty:
put(g, tkBracketLe, "[")
gsub(g, n, 0)
put(g, tkBracketRi, "]")
put(g, tkParLe, "(")
gsub(g, n, 1)
put(g, tkParRi, ")")
Expand Down
109 changes: 84 additions & 25 deletions compiler/sempass2.nim
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ proc createTag(g: ModuleGraph; n: PNode): PNode =
result.typ = g.sysTypeFromName(n.info, "RootEffect")
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
var aa = a.exc
for i in a.bottom..<aa.len:
Expand All @@ -345,11 +345,11 @@ proc addTag(a: PEffects, e, comesFrom: PNode) =
if sameType(aa[i].typ.skipTypes(skipPtrs), e.typ.skipTypes(skipPtrs)): return
throws(a.tags, e, comesFrom)

proc mergeEffects(a: PEffects, b, comesFrom: PNode) =
proc mergeRaises(a: PEffects, b, comesFrom: PNode) =
if b.isNil:
addEffect(a, createRaise(a.graph, comesFrom), comesFrom)
addRaiseEffect(a, createRaise(a.graph, comesFrom), comesFrom)
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) =
if b.isNil:
Expand Down Expand Up @@ -489,7 +489,7 @@ proc mergeLockLevels(tracked: PEffects, n: PNode, lockLevel: TLockLevel) =
proc propagateEffects(tracked: PEffects, n: PNode, s: PSym) =
let pragma = s.ast[pragmasPos]
let spec = effectSpec(pragma, wRaises)
mergeEffects(tracked, spec, n)
mergeRaises(tracked, spec, n)

let tagSpec = effectSpec(pragma, wTags)
mergeTags(tracked, tagSpec, n)
Expand Down Expand Up @@ -535,7 +535,7 @@ proc notNilCheck(tracked: PEffects, n: PNode, paramType: PType) =
of impYes: discard

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)
let lockLevel = if op.lockLevel == UnspecifiedLockLevel: UnknownLockLevel
else: op.lockLevel
Expand Down Expand Up @@ -581,7 +581,7 @@ proc trackOperandForIndirectCall(tracked: PEffects, n: PNode, paramType: PType;
elif tfNoSideEffect notin op.flags and not isOwnedProcVar(a, tracked.owner):
markSideEffect(tracked, a)
else:
mergeEffects(tracked, effectList[exceptionEffects], n)
mergeRaises(tracked, effectList[exceptionEffects], n)
mergeTags(tracked, effectList[tagEffects], n)
if notGcSafe(op):
if tracked.config.hasWarn(warnGcUnsafe): warnAboutGcUnsafe(n, tracked.config)
Expand Down Expand Up @@ -780,7 +780,7 @@ proc trackCall(tracked: PEffects; n: PNode) =
assumeTheWorst(tracked, n, op)
gcsafeAndSideeffectCheck()
else:
mergeEffects(tracked, effectList[exceptionEffects], n)
mergeRaises(tracked, effectList[exceptionEffects], n)
mergeTags(tracked, effectList[tagEffects], n)
gcsafeAndSideeffectCheck()
if a.kind != nkSym or a.sym.magic != mNBindSym:
Expand Down Expand Up @@ -837,6 +837,64 @@ proc trackCall(tracked: PEffects; n: PNode) =
# initVar(tracked, n[i].skipAddr, false)
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) =
case n.kind
of nkSym:
Expand All @@ -854,15 +912,15 @@ proc track(tracked: PEffects, n: PNode) =
if n[0].kind != nkEmpty:
n[0].info = n.info
#throws(tracked.exc, n[0])
addEffect(tracked, n[0], nil)
addRaiseEffect(tracked, n[0], nil)
for i in 0..<n.safeLen:
track(tracked, n[i])
createTypeBoundOps(tracked, n[0].typ, n.info)
else:
# 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`.
# 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:
trackCall(tracked, n)
of nkDotExpr:
Expand Down Expand Up @@ -1011,26 +1069,27 @@ proc track(tracked: PEffects, n: PNode) =
checkForSink(tracked.config, tracked.owner, n[i])
of nkPragmaBlock:
let pragmaList = n[0]
let oldLocked = tracked.locked.len
let oldLockLevel = tracked.currLockLevel
var enforcedGcSafety = false
var enforceNoSideEffects = false
var bc = createBlockContext(tracked)
for i in 0..<pragmaList.len:
let pragma = whichPragma(pragmaList[i])
if pragma == wLocks:
case pragma
of wLocks:
lockLocations(tracked, pragmaList[i])
elif pragma == wGcSafe:
enforcedGcSafety = true
elif pragma == wNoSideEffect:
enforceNoSideEffects = true
of wGcSafe:
bc.enforcedGcSafety = true
of wNoSideEffect:
bc.enforceNoSideEffects = true
of wCast:
castBlock(tracked, pragmaList[i][1], bc)
of wLine: discard
else:
localError(tracked.config, pragmaList[i].info,
"invalid pragma block: " & $pragmaList[i])

if enforcedGcSafety: tracked.inEnforcedGcSafe = true
if enforceNoSideEffects: tracked.inEnforcedNoSideEffects = true
applyBlockContext(tracked, bc)
track(tracked, n.lastSon)
if enforcedGcSafety: tracked.inEnforcedGcSafe = false
if enforceNoSideEffects: tracked.inEnforcedNoSideEffects = false
setLen(tracked.locked, oldLocked)
tracked.currLockLevel = oldLockLevel
unapplyBlockContext(tracked, bc)

of nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef, nkIteratorDef,
nkMacroDef, nkTemplateDef, nkLambda, nkDo, nkFuncDef:
discard
Expand Down
2 changes: 1 addition & 1 deletion compiler/semstmts.nim
Original file line number Diff line number Diff line change
Expand Up @@ -2169,7 +2169,7 @@ proc setLine(n: PNode, info: TLineInfo) =
proc semPragmaBlock(c: PContext, n: PNode): PNode =
checkSonsLen(n, 2, c.config)
let pragmaList = n[0]
pragma(c, nil, pragmaList, exprPragmas)
pragma(c, nil, pragmaList, exprPragmas, isStatement = true)
n[1] = semExpr(c, n[1])
result = n
result.typ = n[1].typ
Expand Down
1 change: 1 addition & 0 deletions compiler/transf.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
# in the back-end (e.g. var p: pointer = someProc)
let exprIsPointerCast = n.kind in {nkCast, nkConv, nkHiddenStdConv} and
n.typ != nil and
n.typ.kind == tyPointer
if not exprIsPointerCast:
var cnst = getConstExpr(c.module, result, c.graph)
Expand Down
14 changes: 13 additions & 1 deletion compiler/trees.nim
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,19 @@ proc isRange*(n: PNode): bool {.inline.} =

proc whichPragma*(n: PNode): TSpecialWord =
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 =
if n.kind == nkPragma:
Expand Down
5 changes: 2 additions & 3 deletions compiler/varpartitions.nim
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@
## for a high-level description of how borrow checking works.

import ast, types, lineinfos, options, msgs, renderer, typeallowed
from trees import getMagic, whichPragma, stupidStmtListExpr
from wordrecg import wNoSideEffect
from trees import getMagic, isNoSideEffectPragma, stupidStmtListExpr
from isolation_check import canAlias

type
Expand Down Expand Up @@ -713,7 +712,7 @@ proc traverse(c: var Partitions; n: PNode) =
let pragmaList = n[0]
var enforceNoSideEffects = 0
for i in 0..<pragmaList.len:
if whichPragma(pragmaList[i]) == wNoSideEffect:
if isNoSideEffectPragma(pragmaList[i]):
enforceNoSideEffects = 1
break

Expand Down
22 changes: 11 additions & 11 deletions doc/grammar.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ colon = ':' COMMENT?
colcom = ':' COMMENT?
operator = OP0 | OP1 | OP2 | OP3 | OP4 | OP5 | OP6 | OP7 | OP8 | OP9
| 'or' | 'xor' | 'and'
| 'is' | 'isnot' | 'in' | 'notin' | 'of' | 'as' | 'from' |
| 'is' | 'isnot' | 'in' | 'notin' | 'of' | 'as' | 'from'
| 'div' | 'mod' | 'shl' | 'shr' | 'not' | 'static' | '..'
prefixOperator = operator
optInd = COMMENT? IND?
Expand All @@ -31,15 +31,15 @@ dotExpr = expr '.' optInd (symbol | '[:' exprList ']')
explicitGenericInstantiation = '[:' exprList ']' ( '(' exprColonEqExpr ')' )?
qualifiedIdent = symbol ('.' optInd symbol)?
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'
| 'finally' | 'except' | 'for' | 'block' | 'const' | 'let'
| 'when' | 'var' | 'mixin'
par = '(' optInd
( &parKeyw complexOrSimpleStmt ^+ ';'
| ';' complexOrSimpleStmt ^+ ';'
( &parKeyw (ifExpr \ complexOrSimpleStmt) ^+ ';'
| ';' (ifExpr \ complexOrSimpleStmt) ^+ ';'
| pragmaStmt
| simpleExpr ( ('=' expr (';' complexOrSimpleStmt ^+ ';' )? )
| simpleExpr ( ('=' expr (';' (ifExpr \ complexOrSimpleStmt) ^+ ';' )? )
| (':' expr (',' exprColonEqExpr ^+ ',' )? ) ) )
optPar ')'
literal = | INT_LIT | INT8_LIT | INT16_LIT | INT32_LIT | INT64_LIT
Expand All @@ -59,11 +59,6 @@ primarySuffix = '(' (exprColonEqExpr comma?)* ')'
| '[' optInd exprColonEqExprList optPar ']'
| '{' optInd exprColonEqExprList optPar '}'
| &( '`'|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 ('.}' | '}')
identVis = symbol OPR? # postfix position
identVisDot = symbol '.' optInd symbol OPR?
Expand Down Expand Up @@ -130,6 +125,11 @@ condStmt = expr colcom stmt COMMENT?
(IND{=} 'else' colcom stmt)?
ifStmt = 'if' 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
ofBranch = 'of' exprList colcom stmt
ofBranches = ofBranch (IND{=} ofBranch)*
Expand Down Expand Up @@ -193,8 +193,8 @@ complexOrSimpleStmt = (ifStmt | whenStmt | whileStmt
| tryStmt | forStmt
| blockStmt | staticStmt | deferStmt | asmStmt
| 'proc' routine
| 'func' routine
| 'method' routine
| 'func' routine
| 'iterator' routine
| 'macro' routine
| 'template' routine
Expand Down
18 changes: 18 additions & 0 deletions tests/effects/tcast_as_pragma.nim
Original file line number Diff line number Diff line change
@@ -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()

0 comments on commit 4712ba4

Please sign in to comment.