further improvements to the error messages produced by concepts

This commit is contained in:
Zahary Karadjov 2016-08-24 01:55:45 +03:00
commit fe48dd1cbe
8 changed files with 85 additions and 54 deletions

View file

@ -133,7 +133,7 @@ type
hintConditionAlwaysTrue, hintName, hintPattern, hintConditionAlwaysTrue, hintName, hintPattern,
hintExecuting, hintLinking, hintDependency, hintExecuting, hintLinking, hintDependency,
hintSource, hintStackTrace, hintGCStats, hintSource, hintStackTrace, hintGCStats,
hintUser hintUser, hintUserRaw
const const
MsgKindToStr*: array[TMsgKind, string] = [ MsgKindToStr*: array[TMsgKind, string] = [
@ -434,10 +434,11 @@ const
hintSource: "$1", hintSource: "$1",
hintStackTrace: "$1", hintStackTrace: "$1",
hintGCStats: "$1", hintGCStats: "$1",
hintUser: "$1"] hintUser: "$1",
hintUserRaw: "$1"]
const const
WarningsToStr*: array[0..30, string] = ["CannotOpenFile", "OctalEscape", WarningsToStr* = ["CannotOpenFile", "OctalEscape",
"XIsNeverRead", "XmightNotBeenInit", "XIsNeverRead", "XmightNotBeenInit",
"Deprecated", "ConfigDeprecated", "Deprecated", "ConfigDeprecated",
"SmallLshouldNotBeUsed", "UnknownMagic", "SmallLshouldNotBeUsed", "UnknownMagic",
@ -449,12 +450,12 @@ const
"ProveInit", "ProveField", "ProveIndex", "GcUnsafe", "GcUnsafe2", "Uninit", "ProveInit", "ProveField", "ProveIndex", "GcUnsafe", "GcUnsafe2", "Uninit",
"GcMem", "Destructor", "LockLevel", "ResultShadowed", "User"] "GcMem", "Destructor", "LockLevel", "ResultShadowed", "User"]
HintsToStr*: array[0..22, string] = ["Success", "SuccessX", "LineTooLong", HintsToStr* = ["Success", "SuccessX", "LineTooLong",
"XDeclaredButNotUsed", "ConvToBaseNotNeeded", "ConvFromXtoItselfNotNeeded", "XDeclaredButNotUsed", "ConvToBaseNotNeeded", "ConvFromXtoItselfNotNeeded",
"ExprAlwaysX", "QuitCalled", "Processing", "CodeBegin", "CodeEnd", "Conf", "ExprAlwaysX", "QuitCalled", "Processing", "CodeBegin", "CodeEnd", "Conf",
"Path", "CondTrue", "Name", "Pattern", "Exec", "Link", "Dependency", "Path", "CondTrue", "Name", "Pattern", "Exec", "Link", "Dependency",
"Source", "StackTrace", "GCStats", "Source", "StackTrace", "GCStats",
"User"] "User", "UserRaw"]
const const
fatalMin* = errUnknown fatalMin* = errUnknown
@ -658,9 +659,6 @@ proc concat(strings: openarray[string]): string =
result = newStringOfCap totalLen result = newStringOfCap totalLen
for s in strings: result.add s for s in strings: result.add s
template writeBufferedMsg(args: varargs[string, `$`]) =
bufferedMsgs.safeAdd concat(args)
proc suggestWriteln*(s: string) = proc suggestWriteln*(s: string) =
if eStdOut in errorOutputs: if eStdOut in errorOutputs:
if isNil(writelnHook): if isNil(writelnHook):
@ -929,7 +927,7 @@ proc rawMessage*(msg: TMsgKind, args: openArray[string]) =
if msg notin gNotes: return if msg notin gNotes: return
title = HintTitle title = HintTitle
color = HintColor color = HintColor
kind = HintsToStr[ord(msg) - ord(hintMin)] if msg != hintUserRaw: kind = HintsToStr[ord(msg) - ord(hintMin)]
inc(gHintCounter) inc(gHintCounter)
let s = msgKindToString(msg) % args let s = msgKindToString(msg) % args
@ -997,7 +995,7 @@ proc liMessage(info: TLineInfo, msg: TMsgKind, arg: string,
ignoreMsg = optHints notin gOptions or msg notin gNotes ignoreMsg = optHints notin gOptions or msg notin gNotes
title = HintTitle title = HintTitle
color = HintColor color = HintColor
kind = HintsToStr[ord(msg) - ord(hintMin)] if msg != hintUserRaw: kind = HintsToStr[ord(msg) - ord(hintMin)]
inc(gHintCounter) inc(gHintCounter)
# NOTE: currently line info line numbers start with 1, # NOTE: currently line info line numbers start with 1,
# but column numbers start with 0, however most editors expect # but column numbers start with 0, however most editors expect

View file

@ -215,7 +215,6 @@ proc paramsTypeCheck(c: PContext, typ: PType) {.inline.} =
proc expectMacroOrTemplateCall(c: PContext, n: PNode): PSym proc expectMacroOrTemplateCall(c: PContext, n: PNode): PSym
proc semDirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode proc semDirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode
proc semWhen(c: PContext, n: PNode, semCheck: bool = true): PNode proc semWhen(c: PContext, n: PNode, semCheck: bool = true): PNode
proc isOpImpl(c: PContext, n: PNode): PNode
proc semTemplateExpr(c: PContext, n: PNode, s: PSym, proc semTemplateExpr(c: PContext, n: PNode, s: PSym,
flags: TExprFlags = {}): PNode flags: TExprFlags = {}): PNode
proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym, proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym,

View file

@ -148,7 +148,7 @@ proc notFoundError*(c: PContext, n: PNode, errors: CandidateErrors) =
# Gives a detailed error message; this is separated from semOverloadedCall, # Gives a detailed error message; this is separated from semOverloadedCall,
# as semOverlodedCall is already pretty slow (and we need this information # as semOverlodedCall is already pretty slow (and we need this information
# only in case of an error). # only in case of an error).
if c.compilesContextId > 0: if errorOutputs == {}:
# fail fast: # fail fast:
globalError(n.info, errTypeMismatch, "") globalError(n.info, errTypeMismatch, "")
if errors.isNil or errors.len == 0: if errors.isNil or errors.len == 0:
@ -263,7 +263,7 @@ proc resolveOverloads(c: PContext, n, orig: PNode,
internalAssert result.state == csMatch internalAssert result.state == csMatch
#writeMatches(result) #writeMatches(result)
#writeMatches(alt) #writeMatches(alt)
if c.compilesContextId > 0: if errorOutputs == {}:
# quick error message for performance of 'compiles' built-in: # quick error message for performance of 'compiles' built-in:
globalError(n.info, errGenerated, "ambiguous call") globalError(n.info, errGenerated, "ambiguous call")
elif gErrorCounter == 0: elif gErrorCounter == 0:
@ -374,7 +374,7 @@ proc semOverloadedCall(c: PContext, n, nOrig: PNode,
# this may be triggered, when the explain pragma is used # this may be triggered, when the explain pragma is used
if errors.len > 0: if errors.len > 0:
let (_, candidates) = presentFailedCandidates(c, n, errors) let (_, candidates) = presentFailedCandidates(c, n, errors)
message(n.info, hintUser, message(n.info, hintUserRaw,
"Non-matching candidates for " & renderTree(n) & "\n" & "Non-matching candidates for " & renderTree(n) & "\n" &
candidates) candidates)
result = semResolvedCall(c, n, r) result = semResolvedCall(c, n, r)

View file

@ -302,7 +302,7 @@ proc semOf(c: PContext, n: PNode): PNode =
n.typ = getSysType(tyBool) n.typ = getSysType(tyBool)
result = n result = n
proc isOpImpl(c: PContext, n: PNode): PNode = proc isOpImpl(c: PContext, n: PNode, flags: TExprFlags): PNode =
internalAssert n.sonsLen == 3 and internalAssert n.sonsLen == 3 and
n[1].typ != nil and n[1].typ.kind == tyTypeDesc and n[1].typ != nil and n[1].typ.kind == tyTypeDesc and
n[2].kind in {nkStrLit..nkTripleStrLit, nkType} n[2].kind in {nkStrLit..nkTripleStrLit, nkType}
@ -324,12 +324,13 @@ proc isOpImpl(c: PContext, n: PNode): PNode =
maybeLiftType(t2, c, n.info) maybeLiftType(t2, c, n.info)
var m: TCandidate var m: TCandidate
initCandidate(c, m, t2) initCandidate(c, m, t2)
if efExplain in flags: m.diagnostics = @[]
let match = typeRel(m, t2, t1) >= isSubtype # isNone let match = typeRel(m, t2, t1) >= isSubtype # isNone
result = newIntNode(nkIntLit, ord(match)) result = newIntNode(nkIntLit, ord(match))
result.typ = n.typ result.typ = n.typ
proc semIs(c: PContext, n: PNode): PNode = proc semIs(c: PContext, n: PNode, flags: TExprFlags): PNode =
if sonsLen(n) != 3: if sonsLen(n) != 3:
localError(n.info, errXExpectsTwoArguments, "is") localError(n.info, errXExpectsTwoArguments, "is")
@ -349,7 +350,7 @@ proc semIs(c: PContext, n: PNode): PNode =
return return
# BUGFIX: don't evaluate this too early: ``T is void`` # BUGFIX: don't evaluate this too early: ``T is void``
if not n[1].typ.base.containsGenericType: result = isOpImpl(c, n) if not n[1].typ.base.containsGenericType: result = isOpImpl(c, n, flags)
proc semOpAux(c: PContext, n: PNode) = proc semOpAux(c: PContext, n: PNode) =
const flags = {efDetermineType} const flags = {efDetermineType}
@ -754,7 +755,7 @@ proc semIndirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode =
# This is a proc variable, apply normal overload resolution # This is a proc variable, apply normal overload resolution
let m = resolveIndirectCall(c, n, nOrig, t) let m = resolveIndirectCall(c, n, nOrig, t)
if m.state != csMatch: if m.state != csMatch:
if c.compilesContextId > 0: if errorOutputs == {}:
# speed up error generation: # speed up error generation:
globalError(n.info, errTypeMismatch, "") globalError(n.info, errTypeMismatch, "")
return emptyNode return emptyNode
@ -1837,7 +1838,7 @@ proc semMagic(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode =
of mLow: result = semLowHigh(c, setMs(n, s), mLow) of mLow: result = semLowHigh(c, setMs(n, s), mLow)
of mHigh: result = semLowHigh(c, setMs(n, s), mHigh) of mHigh: result = semLowHigh(c, setMs(n, s), mHigh)
of mSizeOf: result = semSizeof(c, setMs(n, s)) of mSizeOf: result = semSizeof(c, setMs(n, s))
of mIs: result = semIs(c, setMs(n, s)) of mIs: result = semIs(c, setMs(n, s), flags)
of mOf: result = semOf(c, setMs(n, s)) of mOf: result = semOf(c, setMs(n, s))
of mShallowCopy: result = semShallowCopy(c, n, flags) of mShallowCopy: result = semShallowCopy(c, n, flags)
of mExpandToAst: result = semExpandToAst(c, n, s, flags) of mExpandToAst: result = semExpandToAst(c, n, s, flags)

View file

@ -1609,7 +1609,7 @@ proc semStmtList(c: PContext, n: PNode, flags: TExprFlags): PNode =
n.typ = n.sons[i].typ n.typ = n.sons[i].typ
return return
else: else:
var expr = semExpr(c, n.sons[i]) var expr = semExpr(c, n.sons[i], flags)
n.sons[i] = expr n.sons[i] = expr
if c.inTypeClass > 0 and expr.typ != nil: if c.inTypeClass > 0 and expr.typ != nil:
case expr.typ.kind case expr.typ.kind

View file

@ -664,6 +664,7 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate,
var var
oldWriteHook: type(writelnHook) oldWriteHook: type(writelnHook)
diagnostics: seq[string] diagnostics: seq[string]
errorPrefix: string
flags: TExprFlags = {} flags: TExprFlags = {}
collectDiagnostics = m.diagnostics != nil or collectDiagnostics = m.diagnostics != nil or
sfExplain in Concept.sym.flags sfExplain in Concept.sym.flags
@ -673,9 +674,13 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate,
# XXX: we can't write to m.diagnostics directly, because # XXX: we can't write to m.diagnostics directly, because
# Nim doesn't support capturing var params in closures # Nim doesn't support capturing var params in closures
diagnostics = @[] diagnostics = @[]
writelnHook = proc (s: string) = diagnostics.add(s)
flags = {efExplain} flags = {efExplain}
writelnHook = proc (s: string) =
if errorPrefix == nil: errorPrefix = Concept.sym.name.s & ":"
let msg = s.replace("Error:", errorPrefix)
if oldWriteHook != nil: oldWriteHook msg
diagnostics.add msg
var checkedBody = c.semTryExpr(c, body.copyTree, flags) var checkedBody = c.semTryExpr(c, body.copyTree, flags)
if collectDiagnostics: if collectDiagnostics:

View file

@ -1,36 +1,63 @@
discard """ discard """
cmd: "nim c --verbosity:0 --colors:off $file" cmd: "nim c --verbosity:0 --colors:off $file"
nimout: ''' nimout: '''
tests/concepts/texplain.nim(71, 10) Hint: Non-matching candidates for e(y) tests/concepts/texplain.nim(99, 10) Hint: Non-matching candidates for e(y)
proc e(i: int): int proc e(i: int): int
[User]
tests/concepts/texplain.nim(74, 7) Hint: Non-matching candidates for e(10) tests/concepts/texplain.nim(102, 7) Hint: Non-matching candidates for e(10)
proc e[ExplainedConcept](o: ExplainedConcept): int proc e(o: ExplainedConcept): int
tests/concepts/texplain.nim(38, 6) Error: undeclared field: 'foo' tests/concepts/texplain.nim(65, 6) ExplainedConcept: undeclared field: 'foo'
tests/concepts/texplain.nim(38, 6) Error: undeclared field: '.' tests/concepts/texplain.nim(65, 6) ExplainedConcept: undeclared field: '.'
tests/concepts/texplain.nim(38, 6) Error: type mismatch: got ( tests/concepts/texplain.nim(65, 6) ExplainedConcept: expression '.' cannot be called
[User] tests/concepts/texplain.nim(65, 5) ExplainedConcept: type class predicate failed
tests/concepts/texplain.nim(77, 10) Hint: Non-matching candidates for e(10) tests/concepts/texplain.nim(66, 6) ExplainedConcept: undeclared field: 'bar'
proc e[ExplainedConcept](o: ExplainedConcept): int tests/concepts/texplain.nim(66, 6) ExplainedConcept: undeclared field: '.'
tests/concepts/texplain.nim(38, 6) Error: undeclared field: 'foo' tests/concepts/texplain.nim(66, 6) ExplainedConcept: expression '.' cannot be called
tests/concepts/texplain.nim(38, 6) Error: undeclared field: '.' tests/concepts/texplain.nim(65, 5) ExplainedConcept: type class predicate failed
tests/concepts/texplain.nim(38, 6) Error: type mismatch: got (
[User] tests/concepts/texplain.nim(105, 10) Hint: Non-matching candidates for e(10)
tests/concepts/texplain.nim(81, 20) Error: type mismatch: got ( proc e(o: ExplainedConcept): int
tests/concepts/texplain.nim(82, 20) Error: type mismatch: got ( tests/concepts/texplain.nim(65, 6) ExplainedConcept: undeclared field: 'foo'
tests/concepts/texplain.nim(83, 20) Hint: Non-matching candidates for r(y) tests/concepts/texplain.nim(65, 6) ExplainedConcept: undeclared field: '.'
proc r(i: string): int tests/concepts/texplain.nim(65, 6) ExplainedConcept: expression '.' cannot be called
[User] tests/concepts/texplain.nim(65, 5) ExplainedConcept: type class predicate failed
tests/concepts/texplain.nim(91, 2) Error: type mismatch: got (MatchingType) tests/concepts/texplain.nim(66, 6) ExplainedConcept: undeclared field: 'bar'
tests/concepts/texplain.nim(66, 6) ExplainedConcept: undeclared field: '.'
tests/concepts/texplain.nim(66, 6) ExplainedConcept: expression '.' cannot be called
tests/concepts/texplain.nim(65, 5) ExplainedConcept: type class predicate failed
tests/concepts/texplain.nim(109, 20) Error: type mismatch: got (NonMatchingType)
but expected one of: but expected one of:
proc f[NestedConcept](o: NestedConcept) proc e(o: ExplainedConcept): int
tests/concepts/texplain.nim(42, 6) Error: undeclared field: 'foo' tests/concepts/texplain.nim(65, 5) ExplainedConcept: type class predicate failed
tests/concepts/texplain.nim(42, 6) Error: undeclared field: '.' proc e(i: int): int
tests/concepts/texplain.nim(42, 6) Error: type mismatch: got (
tests/concepts/texplain.nim(46, 5) Error: type class predicate failed tests/concepts/texplain.nim(110, 20) Error: type mismatch: got (NonMatchingType)
but expected one of:
proc r(o: RegularConcept): int
tests/concepts/texplain.nim(69, 5) RegularConcept: type class predicate failed
proc r[T](a: SomeNumber; b: T; c: auto)
proc r(i: string): int
tests/concepts/texplain.nim(111, 20) Hint: Non-matching candidates for r(y)
proc r[T](a: SomeNumber; b: T; c: auto)
proc r(i: string): int
tests/concepts/texplain.nim(119, 2) Error: type mismatch: got (MatchingType)
but expected one of:
proc f(o: NestedConcept)
tests/concepts/texplain.nim(69, 6) RegularConcept: undeclared field: 'foo'
tests/concepts/texplain.nim(69, 6) RegularConcept: undeclared field: '.'
tests/concepts/texplain.nim(69, 6) RegularConcept: expression '.' cannot be called
tests/concepts/texplain.nim(69, 5) RegularConcept: type class predicate failed
tests/concepts/texplain.nim(70, 6) RegularConcept: undeclared field: 'bar'
tests/concepts/texplain.nim(70, 6) RegularConcept: undeclared field: '.'
tests/concepts/texplain.nim(70, 6) RegularConcept: expression '.' cannot be called
tests/concepts/texplain.nim(69, 5) RegularConcept: type class predicate failed
tests/concepts/texplain.nim(73, 5) NestedConcept: type class predicate failed
''' '''
line: 46 line: 119
errormsg: "type class predicate failed" errormsg: "type mismatch: got (MatchingType)"
""" """
type type
@ -56,6 +83,7 @@ type
proc e(o: ExplainedConcept): int = 1 proc e(o: ExplainedConcept): int = 1
proc e(i: int): int = i proc e(i: int): int = i
proc r[T](a: SomeNumber, b: T, c: auto) = discard
proc r(o: RegularConcept): int = 1 proc r(o: RegularConcept): int = 1
proc r(i: string): int = 1 proc r(i: string): int = 1
@ -77,11 +105,11 @@ echo(e(10) {.explain.}, 20)
discard e(10) discard e(10)
static: static:
# provide diagnostics why the compile block failed # provide diagnostics why the compile block failed
assert(compiles(e(n)) {.explain.} == false) assert(compiles(e(n)) {.explain.} == false)
assert(compiles(r(n)) {.explain.} == false) assert(compiles(r(n)) {.explain.} == false)
assert(compiles(r(y)) {.explain.} == true) assert(compiles(r(y)) {.explain.} == true)
# these should not produce any output # these should not produce any output
assert(compiles(r(10)) == false) assert(compiles(r(10)) == false)
assert(compiles(e(10)) == true) assert(compiles(e(10)) == true)

View file

@ -63,6 +63,8 @@ let
var targets = {low(TTarget)..high(TTarget)} var targets = {low(TTarget)..high(TTarget)}
proc normalizeMsg(s: string): string = s.strip.replace("\C\L", "\L")
proc callCompiler(cmdTemplate, filename, options: string, proc callCompiler(cmdTemplate, filename, options: string,
target: TTarget): TSpec = target: TTarget): TSpec =
let c = parseCmdLine(cmdTemplate % ["target", targetToCmd[target], let c = parseCmdLine(cmdTemplate % ["target", targetToCmd[target],
@ -184,7 +186,7 @@ proc addResult(r: var TResults, test: TTest,
proc cmpMsgs(r: var TResults, expected, given: TSpec, test: TTest) = proc cmpMsgs(r: var TResults, expected, given: TSpec, test: TTest) =
if strip(expected.msg) notin strip(given.msg): if strip(expected.msg) notin strip(given.msg):
r.addResult(test, expected.msg, given.msg, reMsgsDiffer) r.addResult(test, expected.msg, given.msg, reMsgsDiffer)
elif expected.nimout.len > 0 and expected.nimout.normalize notin given.nimout.normalize: elif expected.nimout.len > 0 and expected.nimout.normalizeMsg notin given.nimout.normalizeMsg:
r.addResult(test, expected.nimout, given.nimout, reMsgsDiffer) r.addResult(test, expected.nimout, given.nimout, reMsgsDiffer)
elif expected.tfile == "" and extractFilename(expected.file) != extractFilename(given.file) and elif expected.tfile == "" and extractFilename(expected.file) != extractFilename(given.file) and
"internal error:" notin expected.msg: "internal error:" notin expected.msg:
@ -235,8 +237,6 @@ proc nimoutCheck(test: TTest; expectedNimout: string; given: var TSpec) =
if exp notin giv: if exp notin giv:
given.err = reMsgsDiffer given.err = reMsgsDiffer
proc normalize(s: string): string = s.strip.replace("\C\L", "\L")
proc makeDeterministic(s: string): string = proc makeDeterministic(s: string): string =
var x = splitLines(s) var x = splitLines(s)
sort(x, system.cmp) sort(x, system.cmp)