strict effects (#18777)

* fixes #17369
* megatest is green for --cpu:arm64
* docgen output includes more tags/raises
* implemented 'effectsOf' 
* algorithm.nim: uses new effectsOf annotation
* closes #18376
* closes #17475
* closes #13905
* allow effectsOf: [a, b]
* added a test case
* parameters that are not ours cannot be declared as .effectsOf
* documentation
* manual: added the 'sort' example
* bootstrap with the new better options
This commit is contained in:
Andreas Rumpf 2021-09-02 12:10:14 +02:00 • committed by GitHub
commit e0ef859130
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 482 additions and 203 deletions

View file

@ -229,7 +229,7 @@ type
TNodeKinds* = set[TNodeKind]
type
TSymFlag* = enum # 47 flags!
TSymFlag* = enum # 48 flags!
sfUsed, # read access of sym (for warnings) or simply used
sfExported, # symbol is exported from module
sfFromGeneric, # symbol is instantiation of a generic; this is needed
@ -299,6 +299,7 @@ type
sfUsedInFinallyOrExcept # symbol is used inside an 'except' or 'finally'
sfSingleUsedTemp # For temporaries that we know will only be used once
sfNoalias # 'noalias' annotation, means C's 'restrict'
sfEffectsDelayed # an 'effectsDelayed' parameter
TSymFlags* = set[TSymFlag]
@ -568,6 +569,7 @@ type
# sizeof, alignof, offsetof at CT
tfExplicitCallConv
tfIsConstructor
tfEffectSystemWorkaround
TTypeFlags* = set[TTypeFlag]
@ -1781,7 +1783,7 @@ proc containsNode*(n: PNode, kinds: TNodeKinds): bool =
proc hasSubnodeWith*(n: PNode, kind: TNodeKind): bool =
case n.kind
of nkEmpty..nkNilLit: result = n.kind == kind
of nkEmpty..nkNilLit, nkFormalParams: result = n.kind == kind
else:
for i in 0..<n.len:
if (n[i].kind == kind) or hasSubnodeWith(n[i], kind):

View file

@ -325,7 +325,7 @@ proc skipTrivialIndirections(n: PNode): PNode =
proc getPotentialWrites(n: PNode; mutate: bool; result: var seq[PNode]) =
case n.kind:
of nkLiterals, nkIdent: discard
of nkLiterals, nkIdent, nkFormalParams: discard
of nkSym:
if mutate: result.add n
of nkAsgn, nkFastAsgn:
@ -354,7 +354,7 @@ proc getPotentialWrites(n: PNode; mutate: bool; result: var seq[PNode]) =
proc getPotentialReads(n: PNode; result: var seq[PNode]) =
case n.kind:
of nkLiterals, nkIdent: discard
of nkLiterals, nkIdent, nkFormalParams: discard
of nkSym: result.add n
else:
for s in n:

View file

@ -8,7 +8,6 @@
#
# This module handles the parsing of command line arguments.
from ast import setUseIc
# We do this here before the 'import' statement so 'defined' does not get
# confused with 'TGCMode.gcMarkAndSweep' etc.
@ -30,7 +29,7 @@ import
msgs, options, nversion, condsyms, extccomp, platform,
wordrecg, nimblecmd, lineinfos, pathutils, pathnorm
from ast import eqTypeFlags, tfGcSafe, tfNoSideEffect
from ast import setUseIc, eqTypeFlags, tfGcSafe, tfNoSideEffect
# but some have deps to imported modules. Yay.
bootSwitch(usedTinyC, hasTinyCBackend, "-d:tinyc")

View file

@ -137,3 +137,4 @@ proc initDefines*(symbols: StringTableRef) =
defineSymbol("nimHasDragonBox")
defineSymbol("nimHasHintAll")
defineSymbol("nimHasTrace")
defineSymbol("nimHasEffectsOf")

View file

@ -1085,10 +1085,11 @@ proc documentEffect(cache: IdentCache; n, x: PNode, effectType: TSpecialWord, id
let actual = s.typ.n[0]
if actual.len != effectListLen: return
let real = actual[idx]
if real == nil: return
let realLen = real.len
# warning: hack ahead:
var effects = newNodeI(nkBracket, n.info, real.len)
for i in 0..<real.len:
var effects = newNodeI(nkBracket, n.info, realLen)
for i in 0..<realLen:
var t = typeToString(real[i].typ)
if t.startsWith("ref "): t = substr(t, 4)
effects[i] = newIdentNode(getIdent(cache, t), n.info)

View file

@ -75,6 +75,7 @@ type
warnAnyEnumConv = "AnyEnumConv",
warnHoleEnumConv = "HoleEnumConv",
warnCstringConv = "CStringConv",
warnEffect = "Effect",
warnUser = "User",
# hints
hintSuccess = "Success", hintSuccessX = "SuccessX",
@ -163,6 +164,7 @@ const
warnAnyEnumConv: "$1",
warnHoleEnumConv: "$1",
warnCstringConv: "$1",
warnEffect: "$1",
warnUser: "$1",
hintSuccess: "operation successful: $#",
# keep in sync with `testament.isSuccess`

View file

@ -25,3 +25,8 @@ define:useStdoutAsStdmsg
@if nimHasWarningObservableStores:
warning:ObservableStores: off
@end
@if nimHasEffectsOf:
experimental:strictEffects
warningAsError:Effect:on
@end

View file

@ -204,7 +204,8 @@ type
strictFuncs,
views,
strictNotNil,
overloadableEnums
overloadableEnums,
strictEffects
LegacyFeature* = enum
allowSemcheckedAstModification,

View file

@ -29,7 +29,7 @@ const
wCompilerProc, wNonReloadable, wCore, wProcVar, wVarargs, wCompileTime, wMerge,
wBorrow, wImportCompilerProc, wThread,
wAsmNoStackFrame, wDiscardable, wNoInit, wCodegenDecl,
wGensym, wInject, wRaises, wTags, wLocks, wDelegator, wGcSafe,
wGensym, wInject, wRaises, wEffectsOf, wTags, wLocks, wDelegator, wGcSafe,
wConstructor, wLiftLocals, wStackTrace, wLineTrace, wNoDestroy,
wRequires, wEnsures}
converterPragmas* = procPragmas
@ -41,7 +41,7 @@ const
wDiscardable, wGensym, wInject, wDelegator}
iteratorPragmas* = declPragmas + {FirstCallConv..LastCallConv, wNoSideEffect, wSideEffect,
wMagic, wBorrow,
wDiscardable, wGensym, wInject, wRaises,
wDiscardable, wGensym, wInject, wRaises, wEffectsOf,
wTags, wLocks, wGcSafe, wRequires, wEnsures}
exprPragmas* = {wLine, wLocks, wNoRewrite, wGcSafe, wNoSideEffect}
stmtPragmas* = {wChecks, wObjChecks, wFieldChecks, wRangeChecks,
@ -59,7 +59,7 @@ const
lambdaPragmas* = {FirstCallConv..LastCallConv,
wNoSideEffect, wSideEffect, wNoreturn, wNosinks, wDynlib, wHeader,
wThread, wAsmNoStackFrame,
wRaises, wLocks, wTags, wRequires, wEnsures,
wRaises, wLocks, wTags, wRequires, wEnsures, wEffectsOf,
wGcSafe, wCodegenDecl, wNoInit, wCompileTime}
typePragmas* = declPragmas + {wMagic, wAcyclic,
wPure, wHeader, wCompilerProc, wCore, wFinal, wSize, wShallow,
@ -79,7 +79,7 @@ const
paramPragmas* = {wNoalias, wInject, wGensym}
letPragmas* = varPragmas
procTypePragmas* = {FirstCallConv..LastCallConv, wVarargs, wNoSideEffect,
wThread, wRaises, wLocks, wTags, wGcSafe,
wThread, wRaises, wEffectsOf, wLocks, wTags, wGcSafe,
wRequires, wEnsures}
forVarPragmas* = {wInject, wGensym}
allRoutinePragmas* = methodPragmas + iteratorPragmas + lambdaPragmas
@ -779,6 +779,26 @@ proc semCustomPragma(c: PContext, n: PNode): PNode =
# pragma(arg) -> pragma: arg
result.transitionSonsKind(n.kind)
proc processEffectsOf(c: PContext, n: PNode; owner: PSym) =
proc processParam(c: PContext; n: PNode) =
let r = c.semExpr(c, n)
if r.kind == nkSym and r.sym.kind == skParam:
if r.sym.owner == owner:
incl r.sym.flags, sfEffectsDelayed
else:
localError(c.config, n.info, errGenerated, "parameter cannot be declared as .effectsOf")
else:
localError(c.config, n.info, errGenerated, "parameter name expected")
if n.kind notin nkPragmaCallKinds or n.len != 2:
localError(c.config, n.info, errGenerated, "parameter name expected")
else:
let it = n[1]
if it.kind in {nkCurly, nkBracket}:
for x in items(it): processParam(c, x)
else:
processParam(c, it)
proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
validPragmas: TSpecialWords,
comesFromPush, isStatement: bool): bool =
@ -895,6 +915,8 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
of wNoalias:
noVal(c, it)
incl(sym.flags, sfNoalias)
of wEffectsOf:
processEffectsOf(c, it, sym)
of wThreadVar:
noVal(c, it)
incl(sym.flags, {sfThread, sfGlobal})

View file

@ -400,9 +400,10 @@ when not defined(nimHasSinkInference):
include hlo, seminst, semcall
proc resetSemFlag(n: PNode) =
excl n.flags, nfSem
for i in 0..<n.safeLen:
resetSemFlag(n[i])
if n != nil:
excl n.flags, nfSem
for i in 0..<n.safeLen:
resetSemFlag(n[i])
proc semAfterMacroCall(c: PContext, call, macroResult: PNode,
s: PSym, flags: TExprFlags): PNode =

View file

@ -152,6 +152,8 @@ proc effectProblem(f, a: PType; result: var string; c: PContext) =
of efLockLevelsDiffer:
result.add "\n The `.locks` requirements differ. Annotate the " &
"proc with {.locks: 0.} to get extended error information."
of efEffectsDelayed:
result.add "\n The `.effectsOf` annotations differ."
when defined(drnim):
if not c.graph.compatibleProps(c.graph, f, a):
result.add "\n The `.requires` or `.ensures` properties are incompatible."

View file

@ -261,7 +261,9 @@ proc getGenSym*(c: PContext; s: PSym): PSym =
result = s
proc considerGenSyms*(c: PContext; n: PNode) =
if n.kind == nkSym:
if n == nil:
discard "can happen for nkFormalParams/nkArgList"
elif n.kind == nkSym:
let s = getGenSym(c, n.sym)
if n.sym != s:
n.sym = s

View file

@ -296,11 +296,12 @@ proc useVarNoInitCheck(a: PEffects; n: PNode; s: PSym) =
if {sfGlobal, sfThread} * s.flags != {} and s.kind in {skVar, skLet} and
s.magic != mNimvm:
if s.guard != nil: guardGlobal(a, n, s.guard)
if {sfGlobal, sfThread} * s.flags == {sfGlobal} and
(tfHasGCedMem in s.typ.flags or s.typ.isGCedMem):
#if a.config.hasWarn(warnGcUnsafe): warnAboutGcUnsafe(n)
markGcUnsafe(a, s)
markSideEffect(a, s, n.info)
if strictEffects notin a.c.features:
if {sfGlobal, sfThread} * s.flags == {sfGlobal} and
(tfHasGCedMem in s.typ.flags or s.typ.isGCedMem):
#if a.config.hasWarn(warnGcUnsafe): warnAboutGcUnsafe(n)
markGcUnsafe(a, s)
markSideEffect(a, s, n.info)
if s.owner != a.owner and s.kind in {skVar, skLet, skForVar, skResult, skParam} and
{sfGlobal, sfThread} * s.flags == {}:
a.isInnerProc = true
@ -474,14 +475,20 @@ proc trackTryStmt(tracked: PEffects, n: PNode) =
for id, count in items(inter):
if count == branches: tracked.init.add id
proc isIndirectCall(n: PNode, owner: PSym): bool =
proc isIndirectCall(tracked: PEffects; n: PNode): bool =
# we don't count f(...) as an indirect call if 'f' is an parameter.
# Instead we track expressions of type tyProc too. See the manual for
# details:
if n.kind != nkSym:
result = true
elif n.sym.kind == skParam:
result = owner != n.sym.owner or owner == nil
if strictEffects in tracked.c.features:
if tracked.owner == n.sym.owner and sfEffectsDelayed in n.sym.flags:
result = false # it is not a harmful call
else:
result = true
else:
result = tracked.owner != n.sym.owner or tracked.owner == nil
elif n.sym.kind notin routineKinds:
result = true
@ -579,9 +586,14 @@ proc assumeTheWorst(tracked: PEffects; n: PNode; op: PType) =
# message(??.config, n.info, warnUser, "had to assume the worst here")
mergeLockLevels(tracked, n, lockLevel)
proc isOwnedProcVar(n: PNode; owner: PSym): bool =
proc isOwnedProcVar(tracked: PEffects; n: PNode): bool =
# XXX prove the soundness of this effect system rule
result = n.kind == nkSym and n.sym.kind == skParam and owner == n.sym.owner
result = n.kind == nkSym and n.sym.kind == skParam and
tracked.owner == n.sym.owner
#if result and sfPolymorphic notin n.sym.flags:
# echo tracked.config $ n.info, " different here!"
if strictEffects in tracked.c.features:
result = result and sfEffectsDelayed in n.sym.flags
proc isNoEffectList(n: PNode): bool {.inline.} =
assert n.kind == nkEffectList
@ -590,11 +602,15 @@ proc isNoEffectList(n: PNode): bool {.inline.} =
proc isTrival(caller: PNode): bool {.inline.} =
result = caller.kind == nkSym and caller.sym.magic in {mEqProc, mIsNil, mMove, mWasMoved, mSwap}
proc trackOperandForIndirectCall(tracked: PEffects, n: PNode, paramType: PType; caller: PNode) =
proc trackOperandForIndirectCall(tracked: PEffects, n: PNode, formals: PType; argIndex: int; caller: PNode) =
let a = skipConvCastAndClosure(n)
let op = a.typ
let param = if formals != nil and argIndex < formals.len and formals.n != nil: formals.n[argIndex].sym else: nil
# assume indirect calls are taken here:
if op != nil and op.kind == tyProc and n.skipConv.kind != nkNilLit and not isTrival(caller):
if op != nil and op.kind == tyProc and n.skipConv.kind != nkNilLit and
not isTrival(caller) and
((param != nil and sfEffectsDelayed in param.flags) or strictEffects notin tracked.c.features):
internalAssert tracked.config, op.n[0].kind == nkEffectList
var effectList = op.n[0]
var s = n.skipConv
@ -607,14 +623,14 @@ proc trackOperandForIndirectCall(tracked: PEffects, n: PNode, paramType: PType;
# we have no explicit effects but it's a forward declaration and so it's
# stated there are no additional effects, so simply propagate them:
propagateEffects(tracked, n, n.sym)
elif not isOwnedProcVar(a, tracked.owner):
elif not isOwnedProcVar(tracked, a):
# we have no explicit effects so assume the worst:
assumeTheWorst(tracked, n, op)
# assume GcUnsafe unless in its type; 'forward' does not matter:
if notGcSafe(op) and not isOwnedProcVar(a, tracked.owner):
if notGcSafe(op) and not isOwnedProcVar(tracked, a):
if tracked.config.hasWarn(warnGcUnsafe): warnAboutGcUnsafe(n, tracked.config)
markGcUnsafe(tracked, a)
elif tfNoSideEffect notin op.flags and not isOwnedProcVar(a, tracked.owner):
elif tfNoSideEffect notin op.flags and not isOwnedProcVar(tracked, a):
markSideEffect(tracked, a, n.info)
else:
mergeRaises(tracked, effectList[exceptionEffects], n)
@ -624,6 +640,7 @@ proc trackOperandForIndirectCall(tracked: PEffects, n: PNode, paramType: PType;
markGcUnsafe(tracked, a)
elif tfNoSideEffect notin op.flags:
markSideEffect(tracked, a, n.info)
let paramType = if formals != nil and argIndex < formals.len: formals[argIndex] else: nil
if paramType != nil and paramType.kind in {tyVar}:
invalidateFacts(tracked.guards, n)
if n.kind == nkSym and isLocalVar(tracked, n.sym):
@ -724,9 +741,6 @@ proc trackBlock(tracked: PEffects, n: PNode) =
else:
track(tracked, n)
proc paramType(op: PType, i: int): PType =
if op != nil and i < op.len: result = op[i]
proc cstringCheck(tracked: PEffects; n: PNode) =
if n[0].typ.kind == tyCstring and (let a = skipConv(n[1]);
a.typ.kind == tyString and a.kind notin {nkStrLit..nkTripleStrLit}):
@ -770,6 +784,25 @@ proc checkRange(c: PEffects; value: PNode; typ: PType) =
checkLe(c, lowBound, value)
checkLe(c, value, highBound)
#[
proc passedToEffectsDelayedParam(tracked: PEffects; n: PNode) =
let t = n.typ.skipTypes(abstractInst)
if t.kind == tyProc:
if n.kind == nkSym and tracked.owner == n.sym.owner and sfEffectsDelayed in n.sym.flags:
discard "the arg is itself a delayed parameter, so do nothing"
else:
var effectList = t.n[0]
if effectList.len == effectListLen:
mergeRaises(tracked, effectList[exceptionEffects], n)
mergeTags(tracked, effectList[tagEffects], n)
if not importedFromC(n):
if notGcSafe(t):
if tracked.config.hasWarn(warnGcUnsafe): warnAboutGcUnsafe(n, tracked.config)
markGcUnsafe(tracked, n)
if tfNoSideEffect notin t.flags:
markSideEffect(tracked, n, n.info)
]#
proc trackCall(tracked: PEffects; n: PNode) =
template gcsafeAndSideeffectCheck() =
if notGcSafe(op) and not importedFromC(a):
@ -811,7 +844,7 @@ proc trackCall(tracked: PEffects; n: PNode) =
elif isNoEffectList(effectList):
if isForwardedProc(a):
propagateEffects(tracked, n, a.sym)
elif isIndirectCall(a, tracked.owner):
elif isIndirectCall(tracked, a):
assumeTheWorst(tracked, n, op)
gcsafeAndSideeffectCheck()
else:
@ -819,7 +852,8 @@ proc trackCall(tracked: PEffects; n: PNode) =
mergeTags(tracked, effectList[tagEffects], n)
gcsafeAndSideeffectCheck()
if a.kind != nkSym or a.sym.magic != mNBindSym:
for i in 1..<n.len: trackOperandForIndirectCall(tracked, n[i], paramType(op, i), a)
for i in 1..<n.len:
trackOperandForIndirectCall(tracked, n[i], op, i, a)
if a.kind == nkSym and a.sym.magic in {mNew, mNewFinalize, mNewSeq}:
# may not look like an assignment, but it is:
let arg = n[1]
@ -1225,7 +1259,7 @@ proc subtypeRelation(g: ModuleGraph; spec, real: PNode): bool =
else:
return safeInheritanceDiff(g.excType(real), spec.typ) <= 0
proc checkRaisesSpec(g: ModuleGraph; spec, real: PNode, msg: string, hints: 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) =
# check that any real exception is listed in 'spec'; mark those as used;
@ -1241,7 +1275,8 @@ proc checkRaisesSpec(g: ModuleGraph; spec, real: PNode, msg: string, hints: bool
pushInfoContext(g.config, spec.info)
var rr = if r.kind == nkRaiseStmt: r[0] else: r
while rr.kind in {nkStmtList, nkStmtListExpr} and rr.len > 0: rr = rr.lastSon
localError(g.config, r.info, errGenerated, renderTree(rr) & " " & msg & typeToString(r.typ))
message(g.config, r.info, if emitWarnings: warnEffect else: errGenerated,
renderTree(rr) & " " & msg & typeToString(r.typ))
popInfoContext(g.config)
# hint about unnecessarily listed exception types:
if hints:
@ -1258,11 +1293,11 @@ proc checkMethodEffects*(g: ModuleGraph; disp, branch: PSym) =
let p = disp.ast[pragmasPos]
let raisesSpec = effectSpec(p, wRaises)
if not isNil(raisesSpec):
checkRaisesSpec(g, raisesSpec, actual[exceptionEffects],
checkRaisesSpec(g, false, raisesSpec, actual[exceptionEffects],
"can raise an unlisted exception: ", hints=off, subtypeRelation)
let tagsSpec = effectSpec(p, wTags)
if not isNil(tagsSpec):
checkRaisesSpec(g, tagsSpec, actual[tagEffects],
checkRaisesSpec(g, false, tagsSpec, actual[tagEffects],
"can have an unlisted effect: ", hints=off, subtypeRelation)
if sfThread in disp.flags and notGcSafe(branch.typ):
localError(g.config, branch.info, "base method is GC-safe, but '$1' is not" %
@ -1283,7 +1318,7 @@ proc checkMethodEffects*(g: ModuleGraph; disp, branch: PSym) =
"base method has lock level $1, but dispatcher has $2" %
[$branch.typ.lockLevel, $disp.typ.lockLevel])
proc setEffectsForProcType*(g: ModuleGraph; t: PType, n: PNode) =
proc setEffectsForProcType*(g: ModuleGraph; t: PType, n: PNode; s: PSym = nil) =
var effects = t.n[0]
if t.kind != tyProc or effects.kind != nkEffectList: return
if n.kind != nkEmpty:
@ -1292,9 +1327,14 @@ proc setEffectsForProcType*(g: ModuleGraph; t: PType, n: PNode) =
let raisesSpec = effectSpec(n, wRaises)
if not isNil(raisesSpec):
effects[exceptionEffects] = raisesSpec
elif s != nil and (s.magic != mNone or {sfImportc, sfExportc} * s.flags == {sfImportc}):
effects[exceptionEffects] = newNodeI(nkArgList, effects.info)
let tagsSpec = effectSpec(n, wTags)
if not isNil(tagsSpec):
effects[tagEffects] = tagsSpec
elif s != nil and (s.magic != mNone or {sfImportc, sfExportc} * s.flags == {sfImportc}):
effects[tagEffects] = newNodeI(nkArgList, effects.info)
let requiresSpec = propSpec(n, wRequires)
if not isNil(requiresSpec):
@ -1304,15 +1344,21 @@ proc setEffectsForProcType*(g: ModuleGraph; t: PType, n: PNode) =
effects[ensuresEffects] = ensuresSpec
effects[pragmasEffects] = n
if s != nil and s.magic != mNone:
if s.magic != mEcho:
t.flags.incl tfNoSideEffect
proc initEffects(g: ModuleGraph; effects: PNode; s: PSym; t: var TEffects; c: PContext) =
proc rawInitEffects(g: ModuleGraph; effects: PNode) =
newSeq(effects.sons, effectListLen)
effects[exceptionEffects] = newNodeI(nkArgList, s.info)
effects[tagEffects] = newNodeI(nkArgList, s.info)
effects[exceptionEffects] = newNodeI(nkArgList, effects.info)
effects[tagEffects] = newNodeI(nkArgList, effects.info)
effects[requiresEffects] = g.emptyNode
effects[ensuresEffects] = g.emptyNode
effects[pragmasEffects] = g.emptyNode
proc initEffects(g: ModuleGraph; effects: PNode; s: PSym; t: var TEffects; c: PContext) =
rawInitEffects(g, effects)
t.exc = effects[exceptionEffects]
t.tags = effects[tagEffects]
t.owner = s
@ -1341,10 +1387,14 @@ proc trackProc*(c: PContext; s: PSym, body: PNode) =
if effects.kind != nkEffectList: return
# effects already computed?
if not s.hasRealBody: return
if effects.len == effectListLen: return
let emitWarnings = tfEffectSystemWorkaround in s.typ.flags
if effects.len == effectListLen and not emitWarnings: return
var inferredEffects = newNodeI(nkEffectList, s.info)
var t: TEffects
initEffects(g, effects, s, t, c)
initEffects(g, inferredEffects, s, t, c)
rawInitEffects g, effects
track(t, body)
if s.kind != skMacro:
@ -1369,17 +1419,21 @@ proc trackProc*(c: PContext; s: PSym, body: PNode) =
let p = s.ast[pragmasPos]
let raisesSpec = effectSpec(p, wRaises)
if not isNil(raisesSpec):
checkRaisesSpec(g, raisesSpec, t.exc, "can raise an unlisted exception: ",
checkRaisesSpec(g, emitWarnings, raisesSpec, t.exc, "can raise an unlisted exception: ",
hints=on, subtypeRelation, hintsArg=s.ast[0])
# after the check, use the formal spec:
effects[exceptionEffects] = raisesSpec
else:
effects[exceptionEffects] = t.exc
let tagsSpec = effectSpec(p, wTags)
if not isNil(tagsSpec):
checkRaisesSpec(g, tagsSpec, t.tags, "can have an unlisted effect: ",
checkRaisesSpec(g, emitWarnings, tagsSpec, t.tags, "can have an unlisted effect: ",
hints=off, subtypeRelation)
# after the check, use the formal spec:
effects[tagEffects] = tagsSpec
else:
effects[tagEffects] = t.tags
let requiresSpec = propSpec(p, wRequires)
if not isNil(requiresSpec):

View file

@ -1953,6 +1953,10 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
if not hasProto:
implicitPragmas(c, s, n.info, validPragmas)
if n[pragmasPos].kind != nkEmpty:
setEffectsForProcType(c.graph, s.typ, n[pragmasPos], s)
s.typ.flags.incl tfEffectSystemWorkaround
# To ease macro generation that produce forwarded .async procs we now
# allow a bit redundancy in the pragma declarations. The rule is
# a prototype's pragma list must be a superset of the current pragma
@ -2211,8 +2215,9 @@ proc evalInclude(c: PContext, n: PNode): PNode =
incMod(c, n, it, result)
proc setLine(n: PNode, info: TLineInfo) =
for i in 0..<n.safeLen: setLine(n[i], info)
n.info = info
if n != nil:
for i in 0..<n.safeLen: setLine(n[i], info)
n.info = info
proc semPragmaBlock(c: PContext, n: PNode): PNode =
checkSonsLen(n, 2, c.config)

View file

@ -1364,10 +1364,14 @@ type
efTagsDiffer
efTagsUnknown
efLockLevelsDiffer
efEffectsDelayed
proc compatibleEffects*(formal, actual: PType): EffectsCompat =
# for proc type compatibility checking:
assert formal.kind == tyProc and actual.kind == tyProc
#if tfEffectSystemWorkaround in actual.flags:
# return efCompat
if formal.n[0].kind != nkEffectList or
actual.n[0].kind != nkEffectList:
return efTagsUnknown
@ -1391,9 +1395,17 @@ proc compatibleEffects*(formal, actual: PType): EffectsCompat =
# spec requires some exception or tag, but we don't know anything:
if real.len == 0: return efTagsUnknown
let res = compatibleEffectsAux(st, real[tagEffects])
if not res: return efTagsDiffer
if not res:
#if tfEffectSystemWorkaround notin actual.flags:
return efTagsDiffer
if formal.lockLevel.ord < 0 or
actual.lockLevel.ord <= formal.lockLevel.ord:
for i in 1 ..< min(formal.n.len, actual.n.len):
if formal.n[i].sym.flags * {sfEffectsDelayed} != actual.n[i].sym.flags * {sfEffectsDelayed}:
result = efEffectsDelayed
break
result = efCompat
else:
result = efLockLevelsDiffer
@ -1597,7 +1609,8 @@ proc typeMismatch*(conf: ConfigRef; info: TLineInfo, formal, actual: PType, n: P
msg.add "\n.tag effect is 'any tag allowed'"
of efLockLevelsDiffer:
msg.add "\nlock levels differ"
of efEffectsDelayed:
msg.add "\n.effectsOf annotations differ"
localError(conf, info, msg)
proc isTupleRecursive(t: PType, cycleDetector: var IntSet): bool =

View file

@ -35,7 +35,7 @@ type
wMagic = "magic", wThread = "thread", wFinal = "final", wProfiler = "profiler",
wMemTracker = "memtracker", wObjChecks = "objchecks",
wIntDefine = "intdefine", wStrDefine = "strdefine", wBoolDefine = "booldefine",
wCursor = "cursor", wNoalias = "noalias",
wCursor = "cursor", wNoalias = "noalias", wEffectsOf = "effectsOf",
wImmediate = "immediate", wConstructor = "constructor", wDestructor = "destructor",
wDelegator = "delegator", wOverride = "override", wImportCpp = "importcpp",