Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 1 commit into from Dec 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 9 additions & 47 deletions compiler/semobjconstr.nim
Original file line number Diff line number Diff line change
Expand Up @@ -84,55 +84,17 @@ proc caseBranchMatchesExpr(branch, matched: PNode): bool =

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,
isStmtBranch: bool): IntSet =
if caseNode[caseIdx].kind == nkOfBranch:
result = initIntSet()
processBranchVals(caseNode[caseIdx], incl)
for val in processBranchVals(caseNode[caseIdx]):
result.incl(val)
else:
result = allPossibleValues(c, caseNode[0].typ)
result = c.getIntSetOfType(caseNode[0].typ)
for i in 1..<caseNode.len-1:
processBranchVals(caseNode[i], excl)

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(", ") & "} "
for val in processBranchVals(caseNode[i]):
result.excl(val)

proc findUsefulCaseContext(c: PContext, discrimator: PNode): (PNode, int) =
for i in countdown(c.p.caseContext.high, 0):
Expand Down Expand Up @@ -250,9 +212,9 @@ proc semConstructFields(c: PContext, recNode: PNode,

template valuesInConflictError(valsDiff) =
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,
formatUnsafeBranchVals(recNode[0].typ, valsDiff)])
valsDiff.renderAsType(recNode[0].typ)])

let branchNode = recNode[selectedBranch]
let flags = flags*{efAllowDestructor} + {efPreferStatic,
Expand All @@ -275,7 +237,7 @@ proc semConstructFields(c: PContext, recNode: PNode,
let (ctorCase, ctorIdx) = findUsefulCaseContext(c, discriminatorVal)
if ctorCase == nil:
if discriminatorVal.typ.kind == tyRange:
let rangeVals = rangeTypVals(discriminatorVal.typ)
let rangeVals = c.getIntSetOfType(discriminatorVal.typ)
let recBranchVals = branchVals(c, recNode, selectedBranch, false)
let diff = rangeVals - recBranchVals
if diff.len != 0:
Expand Down Expand Up @@ -311,7 +273,7 @@ proc semConstructFields(c: PContext, recNode: PNode,
break
if failedBranch != -1:
if discriminatorVal.typ.kind == tyRange:
let rangeVals = rangeTypVals(discriminatorVal.typ)
let rangeVals = c.getIntSetOfType(discriminatorVal.typ)
let recBranchVals = branchVals(c, recNode, selectedBranch, false)
let diff = rangeVals - recBranchVals
if diff.len != 0:
Expand Down
6 changes: 3 additions & 3 deletions compiler/semstmts.nim
Original file line number Diff line number Diff line change
Expand Up @@ -953,9 +953,9 @@ proc semCase(c: PContext, n: PNode; flags: TExprFlags): PNode =
if chckCovered:
if covered == toCover(c, n[0].typ):
hasElse = true
elif n[0].typ.kind == tyEnum:
localError(c.config, n.info, "not all cases are covered; missing: {$1}" %
formatMissingEnums(n))
elif n[0].typ.skipTypes(abstractRange).kind == tyEnum:
localError(c.config, n.info, "not all cases are covered; missing: $1" %
formatMissingEnums(c, n))
else:
localError(c.config, n.info, "not all cases are covered")
popCaseContext(c)
Expand Down
65 changes: 48 additions & 17 deletions compiler/semtypes.nim
Original file line number Diff line number Diff line change
Expand Up @@ -592,22 +592,53 @@ proc toCover(c: PContext, t: PType): Int128 =
proc semRecordNodeAux(c: PContext, n: PNode, check: var IntSet, pos: var int,
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()
for i in 1..<n.len:
let ofBranch = n[i]
for j in 0..<ofBranch.len - 1:
let child = ofBranch[j]
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
for val in processBranchVals(n[i]):
coveredCases.incl val
result = (c.getIntSetOfType(n[0].typ) - coveredCases).renderAsType(n[0].typ)

proc semRecordCase(c: PContext, n: PNode, check: var IntSet, pos: var int,
father: PNode, rectype: PType) =
Expand Down Expand Up @@ -656,9 +687,9 @@ proc semRecordCase(c: PContext, n: PNode, check: var IntSet, pos: var int,
delSon(b, b.len - 1)
semRecordNodeAux(c, lastSon(n[i]), check, pos, b, rectype, hasCaseFields = true)
if chckCovered and covered != toCover(c, a[0].typ):
if a[0].typ.kind == tyEnum:
localError(c.config, a.info, "not all cases are covered; missing: {$1}" %
formatMissingEnums(a))
if a[0].typ.skipTypes(abstractRange).kind == tyEnum:
localError(c.config, a.info, "not all cases are covered; missing: $1" %
formatMissingEnums(c, a))
else:
localError(c.config, a.info, "not all cases are covered")
father.add a
Expand Down
23 changes: 23 additions & 0 deletions tests/casestmt/tincompletecaseobject2.nim
Original file line number Diff line number Diff line change
@@ -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
14 changes: 14 additions & 0 deletions tests/objvariant/tnon_zero_discrim_err.nim
Original file line number Diff line number Diff line change
@@ -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
6 changes: 3 additions & 3 deletions tests/objvariant/trt_discrim_err2.nim
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
discard """
errormsg: "low(kind) must be 0 for discriminant"
line: 7
errormsg: " branch initialization with a runtime discriminator only supports ordinal types with 2^16 elements or less."
line: 13
"""
type
HoledObj = object
case kind: int
case kind: range[0 .. 20000]
of 0: a: int
else: discard

Expand Down