strict func: much better error messages (#15068)
* strict func: much better error messages * documented the 'strict funcs' mode
This commit is contained in:
parent
7ca32c86bb
commit
2aca748ddd
5 changed files with 133 additions and 46 deletions
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue