strict func: much better error messages (#15068)

* strict func: much better error messages
* documented the 'strict funcs' mode
This commit is contained in:
Andreas Rumpf 2020-07-25 19:36:53 +02:00 • committed by GitHub
commit 2aca748ddd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 133 additions and 46 deletions

View file

@ -11,7 +11,7 @@
## Nim's write tracking. The used algorithm is "union find"
## with path compression.
import ast, types
import ast, types, lineinfos, options, msgs
from trees import getMagic
from isolation_check import canAlias
@ -31,14 +31,36 @@ type
of dependsOn: parent: int
of isRootOf: graphIndex: int
MutationInfo* = object
param: PSym
mutatedHere, connectedVia: TLineInfo
flags: set[SubgraphFlag]
Partitions = object
symToId: seq[PSym]
s: seq[VarIndex]
graphs: seq[set[SubgraphFlag]]
graphs: seq[MutationInfo]
proc hasSideEffect(p: Partitions): bool =
for g in p.graphs:
if g == {isMutated, connectsConstParam}: return true
proc `$`*(config: ConfigRef; g: MutationInfo): string =
result = ""
if g.flags == {isMutated, connectsConstParam}:
result.add "\nan object reachable from '"
result.add g.param.name.s
result.add "' is potentially mutated"
if g.mutatedHere != unknownLineInfo:
result.add "\n"
result.add config $ g.mutatedHere
result.add " the mutation is here"
if g.connectedVia != unknownLineInfo:
result.add "\n"
result.add config $ g.connectedVia
result.add " is the statement that connected the mutation to the parameter"
proc hasSideEffect(p: var Partitions; info: var MutationInfo): bool =
for g in mitems p.graphs:
if g.flags == {isMutated, connectsConstParam}:
info = g
return true
return false
template isConstParam(a): bool = a.kind == skParam and a.typ.kind != tyVar
@ -48,7 +70,8 @@ proc registerVariable(p: var Partitions; n: PNode) =
p.symToId.add n.sym
if isConstParam(n.sym):
p.s.add VarIndex(kind: isRootOf, graphIndex: p.graphs.len)
p.graphs.add({connectsConstParam})
p.graphs.add MutationInfo(param: n.sym, mutatedHere: unknownLineInfo,
connectedVia: unknownLineInfo, flags: {connectsConstParam})
else:
p.s.add VarIndex(kind: isEmptyRoot)
@ -56,31 +79,40 @@ proc variableId(p: Partitions; x: PSym): int {.inline.} = system.find(p.symToId,
proc root(v: var Partitions; start: int): int =
result = start
var depth = 0
while v.s[result].kind == dependsOn:
result = v.s[result].parent
# path compression:
var it = start
while v.s[it].kind == dependsOn:
let next = v.s[it].parent
v.s[it] = VarIndex(kind: dependsOn, parent: result)
it = next
inc depth
if depth > 0:
# path compression:
var it = start
while v.s[it].kind == dependsOn:
let next = v.s[it].parent
v.s[it] = VarIndex(kind: dependsOn, parent: result)
it = next
proc potentialMutation(v: var Partitions; s: PSym) =
proc potentialMutation(v: var Partitions; s: PSym; info: TLineInfo) =
let id = variableId(v, s)
if id >= 0:
let r = root(v, id)
case v.s[r].kind
of isEmptyRoot:
v.s[r] = VarIndex(kind: isRootOf, graphIndex: v.graphs.len)
v.graphs.add({isMutated})
v.graphs.add MutationInfo(param: if isConstParam(s): s else: nil, mutatedHere: info,
connectedVia: unknownLineInfo, flags: {isMutated})
of isRootOf:
v.graphs[v.s[r].graphIndex].incl isMutated
let g = addr v.graphs[v.s[r].graphIndex]
if g.param == nil and isConstParam(s):
g.param = s
if g.mutatedHere == unknownLineInfo:
g.mutatedHere = info
g.flags.incl isMutated
else:
assert false, "cannot happen"
else:
discard "we are not interested in the mutation"
proc connect(v: var Partitions; a, b: PSym) =
proc connect(v: var Partitions; a, b: PSym; info: TLineInfo) =
let aid = variableId(v, a)
if aid < 0:
return
@ -91,26 +123,37 @@ proc connect(v: var Partitions; a, b: PSym) =
let ra = root(v, aid)
let rb = root(v, bid)
if ra != rb:
var param = PSym(nil)
if isConstParam(a): param = a
elif isConstParam(b): param = b
let paramFlags =
if isConstParam(a) or isConstParam(b):
if param != nil:
{connectsConstParam}
else:
{}
# for now we always make 'rb' the slave and 'ra' the master:
let rbFlags =
if v.s[rb].kind == isRootOf:
v.graphs[v.s[rb].graphIndex]
else:
{}
var rbFlags: set[SubgraphFlag] = {}
var mutatedHere = unknownLineInfo
if v.s[rb].kind == isRootOf:
var gb = addr v.graphs[v.s[rb].graphIndex]
if param == nil: param = gb.param
mutatedHere = gb.mutatedHere
rbFlags = gb.flags
v.s[rb] = VarIndex(kind: dependsOn, parent: ra)
case v.s[ra].kind
of isEmptyRoot:
v.s[ra] = VarIndex(kind: isRootOf, graphIndex: v.graphs.len)
v.graphs.add(paramFlags + rbFlags)
v.graphs.add MutationInfo(param: param, mutatedHere: mutatedHere,
connectedVia: info, flags: paramFlags + rbFlags)
of isRootOf:
v.graphs[v.s[ra].graphIndex].incl paramFlags + rbFlags
var g = addr v.graphs[v.s[ra].graphIndex]
if g.param == nil: g.param = param
if g.mutatedHere == unknownLineInfo: g.mutatedHere = mutatedHere
g.connectedVia = info
g.flags.incl paramFlags + rbFlags
else:
assert false, "cannot happen"
@ -144,8 +187,6 @@ proc allRoots(n: PNode; result: var seq[PSym]) =
let m = getMagic(n)
case m
of mNone:
# we do significantly better here by using the available escape
# information:
if n[0].typ.isNil: return
var typ = n[0].typ
if typ != nil:
@ -177,12 +218,12 @@ proc deps(p: var Partitions; dest, src: PNode) =
allRoots(src, sources)
for t in targets:
if dest.kind != nkSym:
potentialMutation(p, t)
potentialMutation(p, t, dest.info)
proc wrap(t: PType): bool {.nimcall.} = t.kind in {tyRef, tyPtr}
if types.searchTypeFor(t.typ, wrap):
for s in sources:
connect(p, t, s)
connect(p, t, s, dest.info)
proc traverse(p: var Partitions; n: PNode) =
case n.kind
@ -224,13 +265,12 @@ proc traverse(p: var Partitions; n: PNode) =
if paramType.typ.kind == tyVar:
var roots: seq[PSym]
allRoots(n, roots)
for r in roots: potentialMutation(p, r)
for r in roots: potentialMutation(p, r, n.info)
else:
for child in n: traverse(p, child)
proc mutatesNonVarParameters*(s: PSym; n: PNode): bool =
proc mutatesNonVarParameters*(s: PSym; n: PNode; info: var MutationInfo): bool =
var par = Partitions()
if s.kind != skMacro:
let params = s.typ.n
@ -240,4 +280,4 @@ proc mutatesNonVarParameters*(s: PSym; n: PNode): bool =
registerVariable(par, s.ast[resultPos])
traverse(par, n)
result = hasSideEffect(par)
result = hasSideEffect(par, info)