Better case coverage error message for alias and range enum (#12913)

This commit is contained in:
Jasper Jenkins 2019-12-18 01:29:01 -08:00 • committed by Clyybber
commit 3c38edf563
6 changed files with 100 additions and 70 deletions

View file

@ -84,55 +84,17 @@ proc caseBranchMatchesExpr(branch, matched: PNode): bool =
return false return false
template processBranchVals(b, op) =
if b.kind != nkElifBranch:
for i in 0..<b.len-1:
if b[i].kind == nkIntLit:
result.op(b[i].intVal.int)
elif b[i].kind == nkRange:
for i in b[i][0].intVal..b[i][1].intVal:
result.op(i.int)
proc allPossibleValues(c: PContext, t: PType): IntSet =
result = initIntSet()
if t.enumHasHoles:
let t = t.skipTypes(abstractRange)
for field in t.n.sons:
result.incl(field.sym.position)
else:
for i in toInt64(firstOrd(c.config, t))..toInt64(lastOrd(c.config, t)):
result.incl(i.int)
proc branchVals(c: PContext, caseNode: PNode, caseIdx: int, proc branchVals(c: PContext, caseNode: PNode, caseIdx: int,
isStmtBranch: bool): IntSet = isStmtBranch: bool): IntSet =
if caseNode[caseIdx].kind == nkOfBranch: if caseNode[caseIdx].kind == nkOfBranch:
result = initIntSet() result = initIntSet()
processBranchVals(caseNode[caseIdx], incl) for val in processBranchVals(caseNode[caseIdx]):
result.incl(val)
else: else:
result = allPossibleValues(c, caseNode[0].typ) result = c.getIntSetOfType(caseNode[0].typ)
for i in 1..<caseNode.len-1: for i in 1..<caseNode.len-1:
processBranchVals(caseNode[i], excl) for val in processBranchVals(caseNode[i]):
result.excl(val)
proc rangeTypVals(rangeTyp: PType): IntSet =
assert rangeTyp.kind == tyRange
let (a, b) = (rangeTyp.n[0].intVal, rangeTyp.n[1].intVal)
result = initIntSet()
for it in a..b:
result.incl(it.int)
proc formatUnsafeBranchVals(t: PType, diffVals: IntSet): string =
if diffVals.len <= 32:
var strs: seq[string]
let t = t.skipTypes(abstractRange)
if t.kind in {tyEnum, tyBool}:
var i = 0
for val in diffVals:
while t.n[i].sym.position < val: inc(i)
strs.add(t.n[i].sym.name.s)
else:
for val in diffVals:
strs.add($val)
result = "{" & strs.join(", ") & "} "
proc findUsefulCaseContext(c: PContext, discrimator: PNode): (PNode, int) = proc findUsefulCaseContext(c: PContext, discrimator: PNode): (PNode, int) =
for i in countdown(c.p.caseContext.high, 0): for i in countdown(c.p.caseContext.high, 0):
@ -250,9 +212,9 @@ proc semConstructFields(c: PContext, recNode: PNode,
template valuesInConflictError(valsDiff) = template valuesInConflictError(valsDiff) =
localError(c.config, discriminatorVal.info, ("possible values " & localError(c.config, discriminatorVal.info, ("possible values " &
"$2are in conflict with discriminator values for " & "$2 are in conflict with discriminator values for " &
"selected object branch $1.") % [$selectedBranch, "selected object branch $1.") % [$selectedBranch,
formatUnsafeBranchVals(recNode[0].typ, valsDiff)]) valsDiff.renderAsType(recNode[0].typ)])
let branchNode = recNode[selectedBranch] let branchNode = recNode[selectedBranch]
let flags = flags*{efAllowDestructor} + {efPreferStatic, let flags = flags*{efAllowDestructor} + {efPreferStatic,
@ -275,7 +237,7 @@ proc semConstructFields(c: PContext, recNode: PNode,
let (ctorCase, ctorIdx) = findUsefulCaseContext(c, discriminatorVal) let (ctorCase, ctorIdx) = findUsefulCaseContext(c, discriminatorVal)
if ctorCase == nil: if ctorCase == nil:
if discriminatorVal.typ.kind == tyRange: if discriminatorVal.typ.kind == tyRange:
let rangeVals = rangeTypVals(discriminatorVal.typ) let rangeVals = c.getIntSetOfType(discriminatorVal.typ)
let recBranchVals = branchVals(c, recNode, selectedBranch, false) let recBranchVals = branchVals(c, recNode, selectedBranch, false)
let diff = rangeVals - recBranchVals let diff = rangeVals - recBranchVals
if diff.len != 0: if diff.len != 0:
@ -311,7 +273,7 @@ proc semConstructFields(c: PContext, recNode: PNode,
break break
if failedBranch != -1: if failedBranch != -1:
if discriminatorVal.typ.kind == tyRange: if discriminatorVal.typ.kind == tyRange:
let rangeVals = rangeTypVals(discriminatorVal.typ) let rangeVals = c.getIntSetOfType(discriminatorVal.typ)
let recBranchVals = branchVals(c, recNode, selectedBranch, false) let recBranchVals = branchVals(c, recNode, selectedBranch, false)
let diff = rangeVals - recBranchVals let diff = rangeVals - recBranchVals
if diff.len != 0: if diff.len != 0:

View file

@ -953,9 +953,9 @@ proc semCase(c: PContext, n: PNode; flags: TExprFlags): PNode =
if chckCovered: if chckCovered:
if covered == toCover(c, n[0].typ): if covered == toCover(c, n[0].typ):
hasElse = true hasElse = true
elif n[0].typ.kind == tyEnum: elif n[0].typ.skipTypes(abstractRange).kind == tyEnum:
localError(c.config, n.info, "not all cases are covered; missing: {$1}" % localError(c.config, n.info, "not all cases are covered; missing: $1" %
formatMissingEnums(n)) formatMissingEnums(c, n))
else: else:
localError(c.config, n.info, "not all cases are covered") localError(c.config, n.info, "not all cases are covered")
popCaseContext(c) popCaseContext(c)

View file

@ -592,22 +592,53 @@ proc toCover(c: PContext, t: PType): Int128 =
proc semRecordNodeAux(c: PContext, n: PNode, check: var IntSet, pos: var int, proc semRecordNodeAux(c: PContext, n: PNode, check: var IntSet, pos: var int,
father: PNode, rectype: PType, hasCaseFields = false) father: PNode, rectype: PType, hasCaseFields = false)
proc formatMissingEnums(n: PNode): string = proc getIntSetOfType(c: PContext, t: PType): IntSet =
result = initIntSet()
if t.enumHasHoles:
let t = t.skipTypes(abstractRange)
for field in t.n.sons:
result.incl(field.sym.position)
else:
assert(lengthOrd(c.config, t) <= BiggestInt(MaxSetElements))
for i in toInt64(firstOrd(c.config, t))..toInt64(lastOrd(c.config, t)):
result.incl(i.int)
iterator processBranchVals(b: PNode): int =
assert b.kind in {nkOfBranch, nkElifBranch, nkElse}
if b.kind == nkOfBranch:
for i in 0..<b.len-1:
if b[i].kind == nkIntLit:
yield b[i].intVal.int
elif b[i].kind == nkRange:
for i in b[i][0].intVal..b[i][1].intVal:
yield i.int
proc renderAsType(vals: IntSet, t: PType): string =
result = "{"
let t = t.skipTypes(abstractRange)
var enumSymOffset = 0
var i = 0
for val in vals:
if result.len > 1:
result &= ", "
if t.kind in {tyEnum, tyBool}:
while t.n[enumSymOffset].sym.position < val: inc(enumSymOffset)
result &= t.n[enumSymOffset].sym.name.s
else:
if i == 64:
result &= "omitted $1 values..." % $(vals.len - i)
break
else:
result &= $val
inc(i)
result &= "}"
proc formatMissingEnums(c: PContext, n: PNode): string =
var coveredCases = initIntSet() var coveredCases = initIntSet()
for i in 1..<n.len: for i in 1..<n.len:
let ofBranch = n[i] for val in processBranchVals(n[i]):
for j in 0..<ofBranch.len - 1: coveredCases.incl val
let child = ofBranch[j] result = (c.getIntSetOfType(n[0].typ) - coveredCases).renderAsType(n[0].typ)
if child.kind == nkIntLit:
coveredCases.incl(child.intVal.int)
elif child.kind == nkRange:
for k in child[0].intVal.int..child[1].intVal.int:
coveredCases.incl k
for child in n[0].typ.n.sons:
if child.sym.position notin coveredCases:
if result.len > 0:
result.add ", "
result.add child.sym.name.s
proc semRecordCase(c: PContext, n: PNode, check: var IntSet, pos: var int, proc semRecordCase(c: PContext, n: PNode, check: var IntSet, pos: var int,
father: PNode, rectype: PType) = father: PNode, rectype: PType) =
@ -656,9 +687,9 @@ proc semRecordCase(c: PContext, n: PNode, check: var IntSet, pos: var int,
delSon(b, b.len - 1) delSon(b, b.len - 1)
semRecordNodeAux(c, lastSon(n[i]), check, pos, b, rectype, hasCaseFields = true) semRecordNodeAux(c, lastSon(n[i]), check, pos, b, rectype, hasCaseFields = true)
if chckCovered and covered != toCover(c, a[0].typ): if chckCovered and covered != toCover(c, a[0].typ):
if a[0].typ.kind == tyEnum: if a[0].typ.skipTypes(abstractRange).kind == tyEnum:
localError(c.config, a.info, "not all cases are covered; missing: {$1}" % localError(c.config, a.info, "not all cases are covered; missing: $1" %
formatMissingEnums(a)) formatMissingEnums(c, a))
else: else:
localError(c.config, a.info, "not all cases are covered") localError(c.config, a.info, "not all cases are covered")
father.add a father.add a

View file

@ -0,0 +1,23 @@
discard """
cmd: "nim check $file"
errormsg: "not all cases are covered; missing: {A, B}"
nimout: '''
tincompletecaseobject2.nim(16, 1) Error: not all cases are covered; missing: {B, C, D}
tincompletecaseobject2.nim(19, 1) Error: not all cases are covered; missing: {A, C}
tincompletecaseobject2.nim(22, 1) Error: not all cases are covered; missing: {A, B}
'''
"""
type
ABCD = enum A, B, C, D
AliasABCD = ABCD
RangeABC = range[A .. C]
AliasRangeABC = RangeABC
case AliasABCD A:
of A: discard
case RangeABC A:
of B: discard
case AliasRangeABC A:
of C: discard

View file

@ -0,0 +1,14 @@
discard """
errormsg: "low(kind) must be 0 for discriminant"
line: 7
"""
type
HoledObj = object
case kind: int
of 0: a: int
else: discard
let someInt = low(int)
case someInt
of 938: echo HoledObj(kind: someInt, a: 1)
else: discard

View file

@ -1,10 +1,10 @@
discard """ discard """
errormsg: "low(kind) must be 0 for discriminant" errormsg: " branch initialization with a runtime discriminator only supports ordinal types with 2^16 elements or less."
line: 7 line: 13
""" """
type type
HoledObj = object HoledObj = object
case kind: int case kind: range[0 .. 20000]
of 0: a: int of 0: a: int
else: discard else: discard