.forbids pragma: defining forbidden tags (#20050)

* .forbids pragma: defining illegal effects for proc types

This patch intends to define the opposite of the .tags pragma: a way to define effects which are not allowed in a proc.

* updated documentation and changelogs for the forbids pragma

* renamed notTagEffects to forbiddenEffects

* corrected issues of forbids pragma

the forbids pragma didn't handle simple restrictions properly and it also had issues with subtyping

* removed incorrect character from changelog

* added test to cover the interaction of methods and the forbids pragma

* covering the interaction of the tags and forbids pragmas

* updated manual about the forbids pragma

* removed useless statement

* corrected the subtyping of proc types using the forbids pragma

* updated manual for the forbids pragma

* updated documentations for forbids pragma

* updated nim docs

* updated docs with rsttester.nim

* regenerated documentation

* updated rst docs

* Update changelog.md

Co-authored-by: ringabout <43030857+ringabout@users.noreply.github.com>

* updated changelog

* corrected typo

Co-authored-by: ringabout <43030857+ringabout@users.noreply.github.com>
This commit is contained in:
Lancer11211 2022-07-26 07:40:49 +02:00 • committed by GitHub
commit efd5c571bf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 405 additions and 71 deletions

View file

@ -66,6 +66,7 @@ type
TEffects = object
exc: PNode # stack of exceptions
tags: PNode # list of tags
forbids: PNode # list of tags
bottom, inTryStmt, inExceptOrFinallyStmt, leftPartOfAsgn: int
owner: PSym
ownerModule: PSym
@ -388,6 +389,12 @@ 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 addNotTag(a: PEffects, e, comesFrom: PNode) =
var aa = a.forbids
for i in 0..<aa.len:
if sameType(aa[i].typ.skipTypes(skipPtrs), e.typ.skipTypes(skipPtrs)): return
throws(a.forbids, e, comesFrom)
proc mergeRaises(a: PEffects, b, comesFrom: PNode) =
if b.isNil:
addRaiseEffect(a, createRaise(a.graph, comesFrom), comesFrom)
@ -403,6 +410,7 @@ proc mergeTags(a: PEffects, b, comesFrom: PNode) =
proc listEffects(a: PEffects) =
for e in items(a.exc): message(a.config, e.info, hintUser, typeToString(e.typ))
for e in items(a.tags): message(a.config, e.info, hintUser, typeToString(e.typ))
for e in items(a.forbids): message(a.config, e.info, hintUser, typeToString(e.typ))
#if a.maxLockLevel != 0:
# message(e.info, hintUser, "lockLevel: " & a.maxLockLevel)
@ -604,7 +612,7 @@ proc isOwnedProcVar(tracked: PEffects; n: PNode): bool =
proc isNoEffectList(n: PNode): bool {.inline.} =
assert n.kind == nkEffectList
n.len == 0 or (n[tagEffects] == nil and n[exceptionEffects] == nil)
n.len == 0 or (n[tagEffects] == nil and n[exceptionEffects] == nil and n[forbiddenEffects] == nil)
proc isTrival(caller: PNode): bool {.inline.} =
result = caller.kind == nkSym and caller.sym.magic in {mEqProc, mIsNil, mMove, mWasMoved, mSwap}
@ -924,14 +932,17 @@ type
oldLocked: int
oldLockLevel: TLockLevel
enforcedGcSafety, enforceNoSideEffects: bool
oldExc, oldTags: int
exc, tags: PNode
oldExc, oldTags, oldForbids: int
exc, tags, forbids: PNode
proc createBlockContext(tracked: PEffects): PragmaBlockContext =
var oldForbidsLen = 0
if tracked.forbids != nil: oldForbidsLen = tracked.forbids.len
result = PragmaBlockContext(oldLocked: tracked.locked.len,
oldLockLevel: tracked.currLockLevel,
enforcedGcSafety: false, enforceNoSideEffects: false,
oldExc: tracked.exc.len, oldTags: tracked.tags.len)
oldExc: tracked.exc.len, oldTags: tracked.tags.len,
oldForbids: oldForbidsLen)
proc applyBlockContext(tracked: PEffects, bc: PragmaBlockContext) =
if bc.enforcedGcSafety: tracked.inEnforcedGcSafe = true
@ -952,6 +963,10 @@ proc unapplyBlockContext(tracked: PEffects; bc: PragmaBlockContext) =
setLen(tracked.tags.sons, bc.oldTags)
for t in bc.tags:
addTag(tracked, t, t)
if bc.forbids != nil:
setLen(tracked.forbids.sons, bc.oldForbids)
for t in bc.forbids:
addNotTag(tracked, t, t)
proc castBlock(tracked: PEffects, pragma: PNode, bc: var PragmaBlockContext) =
case whichPragma(pragma)
@ -966,6 +981,13 @@ proc castBlock(tracked: PEffects, pragma: PNode, bc: var PragmaBlockContext) =
else:
bc.tags = newNodeI(nkArgList, pragma.info)
bc.tags.add n
of wForbids:
let n = pragma[1]
if n.kind in {nkCurly, nkBracket}:
bc.forbids = n
else:
bc.forbids = newNodeI(nkArgList, pragma.info)
bc.forbids.add n
of wRaises:
let n = pragma[1]
if n.kind in {nkCurly, nkBracket}:
@ -1274,7 +1296,7 @@ proc subtypeRelation(g: ModuleGraph; spec, real: PNode): bool =
proc checkRaisesSpec(g: ModuleGraph; emitWarnings: bool; spec, real: PNode, msg: string, hints: bool;
effectPredicate: proc (g: ModuleGraph; a, b: PNode): bool {.nimcall.};
hintsArg: PNode = nil) =
hintsArg: PNode = nil; isForbids: bool = false) =
# check that any real exception is listed in 'spec'; mark those as used;
# report any unused exception
var used = initIntSet()
@ -1282,8 +1304,11 @@ proc checkRaisesSpec(g: ModuleGraph; emitWarnings: bool; spec, real: PNode, msg:
block search:
for s in 0..<spec.len:
if effectPredicate(g, spec[s], r):
if isForbids: break
used.incl(s)
break search
if isForbids:
break search
# XXX call graph analysis would be nice here!
pushInfoContext(g.config, spec.info)
var rr = if r.kind == nkRaiseStmt: r[0] else: r
@ -1312,6 +1337,10 @@ proc checkMethodEffects*(g: ModuleGraph; disp, branch: PSym) =
if not isNil(tagsSpec):
checkRaisesSpec(g, false, tagsSpec, actual[tagEffects],
"can have an unlisted effect: ", hints=off, subtypeRelation)
let forbidsSpec = effectSpec(p, wForbids)
if not isNil(forbidsSpec):
checkRaisesSpec(g, false, forbidsSpec, actual[tagEffects],
"has an illegal effect: ", hints=off, subtypeRelation, isForbids=true)
if sfThread in disp.flags and notGcSafe(branch.typ):
localError(g.config, branch.info, "base method is GC-safe, but '$1' is not" %
branch.name.s)
@ -1349,6 +1378,12 @@ proc setEffectsForProcType*(g: ModuleGraph; t: PType, n: PNode; s: PSym = nil) =
elif s != nil and (s.magic != mNone or {sfImportc, sfExportc} * s.flags == {sfImportc}):
effects[tagEffects] = newNodeI(nkArgList, effects.info)
let forbidsSpec = effectSpec(n, wForbids)
if not isNil(forbidsSpec):
effects[forbiddenEffects] = forbidsSpec
elif s != nil and (s.magic != mNone or {sfImportc, sfExportc} * s.flags == {sfImportc}):
effects[forbiddenEffects] = newNodeI(nkArgList, effects.info)
let requiresSpec = propSpec(n, wRequires)
if not isNil(requiresSpec):
effects[requiresEffects] = requiresSpec
@ -1365,6 +1400,7 @@ proc rawInitEffects(g: ModuleGraph; effects: PNode) =
newSeq(effects.sons, effectListLen)
effects[exceptionEffects] = newNodeI(nkArgList, effects.info)
effects[tagEffects] = newNodeI(nkArgList, effects.info)
effects[forbiddenEffects] = newNodeI(nkArgList, effects.info)
effects[requiresEffects] = g.emptyNode
effects[ensuresEffects] = g.emptyNode
effects[pragmasEffects] = g.emptyNode
@ -1374,6 +1410,7 @@ proc initEffects(g: ModuleGraph; effects: PNode; s: PSym; t: var TEffects; c: PC
t.exc = effects[exceptionEffects]
t.tags = effects[tagEffects]
t.forbids = effects[forbiddenEffects]
t.owner = s
t.ownerModule = s.getModule
t.init = @[]
@ -1448,6 +1485,15 @@ proc trackProc*(c: PContext; s: PSym, body: PNode) =
else:
effects[tagEffects] = t.tags
let forbidsSpec = effectSpec(p, wForbids)
if not isNil(forbidsSpec):
checkRaisesSpec(g, false, forbidsSpec, t.tags, "has an illegal effect: ",
hints=off, subtypeRelation, isForbids=true)
# after the check, use the formal spec:
effects[forbiddenEffects] = forbidsSpec
else:
effects[forbiddenEffects] = t.forbids
let requiresSpec = propSpec(p, wRequires)
if not isNil(requiresSpec):
effects[requiresEffects] = requiresSpec