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:
parent
72fa5833ad
commit
e0ef859130
35 changed files with 482 additions and 203 deletions
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -137,3 +137,4 @@ proc initDefines*(symbols: StringTableRef) =
|
|||
defineSymbol("nimHasDragonBox")
|
||||
defineSymbol("nimHasHintAll")
|
||||
defineSymbol("nimHasTrace")
|
||||
defineSymbol("nimHasEffectsOf")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -25,3 +25,8 @@ define:useStdoutAsStdmsg
|
|||
@if nimHasWarningObservableStores:
|
||||
warning:ObservableStores: off
|
||||
@end
|
||||
|
||||
@if nimHasEffectsOf:
|
||||
experimental:strictEffects
|
||||
warningAsError:Effect:on
|
||||
@end
|
||||
|
|
|
|||
|
|
@ -204,7 +204,8 @@ type
|
|||
strictFuncs,
|
||||
views,
|
||||
strictNotNil,
|
||||
overloadableEnums
|
||||
overloadableEnums,
|
||||
strictEffects
|
||||
|
||||
LegacyFeature* = enum
|
||||
allowSemcheckedAstModification,
|
||||
|
|
|
|||
|
|
@ -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})
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -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."
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue