From fe48dd1cbec500298f7edeb75f1d6fef8490346c Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Wed, 24 Aug 2016 01:55:45 +0300 Subject: [PATCH] further improvements to the error messages produced by concepts --- compiler/msgs.nim | 18 ++++---- compiler/sem.nim | 1 - compiler/semcall.nim | 6 +-- compiler/semexprs.nim | 11 ++--- compiler/semstmts.nim | 2 +- compiler/sigmatch.nim | 9 +++- tests/concepts/texplain.nim | 84 ++++++++++++++++++++++++------------- tests/testament/tester.nim | 6 +-- 8 files changed, 84 insertions(+), 53 deletions(-) diff --git a/compiler/msgs.nim b/compiler/msgs.nim index 8f7c43312151..3a97f1ed2b38 100644 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -133,7 +133,7 @@ type hintConditionAlwaysTrue, hintName, hintPattern, hintExecuting, hintLinking, hintDependency, hintSource, hintStackTrace, hintGCStats, - hintUser + hintUser, hintUserRaw const MsgKindToStr*: array[TMsgKind, string] = [ @@ -434,10 +434,11 @@ const hintSource: "$1", hintStackTrace: "$1", hintGCStats: "$1", - hintUser: "$1"] + hintUser: "$1", + hintUserRaw: "$1"] const - WarningsToStr*: array[0..30, string] = ["CannotOpenFile", "OctalEscape", + WarningsToStr* = ["CannotOpenFile", "OctalEscape", "XIsNeverRead", "XmightNotBeenInit", "Deprecated", "ConfigDeprecated", "SmallLshouldNotBeUsed", "UnknownMagic", @@ -449,12 +450,12 @@ const "ProveInit", "ProveField", "ProveIndex", "GcUnsafe", "GcUnsafe2", "Uninit", "GcMem", "Destructor", "LockLevel", "ResultShadowed", "User"] - HintsToStr*: array[0..22, string] = ["Success", "SuccessX", "LineTooLong", + HintsToStr* = ["Success", "SuccessX", "LineTooLong", "XDeclaredButNotUsed", "ConvToBaseNotNeeded", "ConvFromXtoItselfNotNeeded", "ExprAlwaysX", "QuitCalled", "Processing", "CodeBegin", "CodeEnd", "Conf", "Path", "CondTrue", "Name", "Pattern", "Exec", "Link", "Dependency", "Source", "StackTrace", "GCStats", - "User"] + "User", "UserRaw"] const fatalMin* = errUnknown @@ -658,9 +659,6 @@ proc concat(strings: openarray[string]): string = result = newStringOfCap totalLen for s in strings: result.add s -template writeBufferedMsg(args: varargs[string, `$`]) = - bufferedMsgs.safeAdd concat(args) - proc suggestWriteln*(s: string) = if eStdOut in errorOutputs: if isNil(writelnHook): @@ -929,7 +927,7 @@ proc rawMessage*(msg: TMsgKind, args: openArray[string]) = if msg notin gNotes: return title = HintTitle color = HintColor - kind = HintsToStr[ord(msg) - ord(hintMin)] + if msg != hintUserRaw: kind = HintsToStr[ord(msg) - ord(hintMin)] inc(gHintCounter) 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 title = HintTitle color = HintColor - kind = HintsToStr[ord(msg) - ord(hintMin)] + if msg != hintUserRaw: kind = HintsToStr[ord(msg) - ord(hintMin)] inc(gHintCounter) # NOTE: currently line info line numbers start with 1, # but column numbers start with 0, however most editors expect diff --git a/compiler/sem.nim b/compiler/sem.nim index 6ad77e3fb260..57b87e0bb412 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -215,7 +215,6 @@ proc paramsTypeCheck(c: PContext, typ: PType) {.inline.} = proc expectMacroOrTemplateCall(c: PContext, n: PNode): PSym proc semDirectOp(c: PContext, n: PNode, flags: TExprFlags): 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, flags: TExprFlags = {}): PNode proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym, diff --git a/compiler/semcall.nim b/compiler/semcall.nim index 291cf0c6de5d..49b4930eb862 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -148,7 +148,7 @@ proc notFoundError*(c: PContext, n: PNode, errors: CandidateErrors) = # Gives a detailed error message; this is separated from semOverloadedCall, # as semOverlodedCall is already pretty slow (and we need this information # only in case of an error). - if c.compilesContextId > 0: + if errorOutputs == {}: # fail fast: globalError(n.info, errTypeMismatch, "") if errors.isNil or errors.len == 0: @@ -263,7 +263,7 @@ proc resolveOverloads(c: PContext, n, orig: PNode, internalAssert result.state == csMatch #writeMatches(result) #writeMatches(alt) - if c.compilesContextId > 0: + if errorOutputs == {}: # quick error message for performance of 'compiles' built-in: globalError(n.info, errGenerated, "ambiguous call") elif gErrorCounter == 0: @@ -374,7 +374,7 @@ proc semOverloadedCall(c: PContext, n, nOrig: PNode, # this may be triggered, when the explain pragma is used if errors.len > 0: let (_, candidates) = presentFailedCandidates(c, n, errors) - message(n.info, hintUser, + message(n.info, hintUserRaw, "Non-matching candidates for " & renderTree(n) & "\n" & candidates) result = semResolvedCall(c, n, r) diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 59b7e7d7f327..b5e9e6dc4633 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -302,7 +302,7 @@ proc semOf(c: PContext, n: PNode): PNode = n.typ = getSysType(tyBool) result = n -proc isOpImpl(c: PContext, n: PNode): PNode = +proc isOpImpl(c: PContext, n: PNode, flags: TExprFlags): PNode = internalAssert n.sonsLen == 3 and n[1].typ != nil and n[1].typ.kind == tyTypeDesc and n[2].kind in {nkStrLit..nkTripleStrLit, nkType} @@ -324,12 +324,13 @@ proc isOpImpl(c: PContext, n: PNode): PNode = maybeLiftType(t2, c, n.info) var m: TCandidate initCandidate(c, m, t2) + if efExplain in flags: m.diagnostics = @[] let match = typeRel(m, t2, t1) >= isSubtype # isNone result = newIntNode(nkIntLit, ord(match)) result.typ = n.typ -proc semIs(c: PContext, n: PNode): PNode = +proc semIs(c: PContext, n: PNode, flags: TExprFlags): PNode = if sonsLen(n) != 3: localError(n.info, errXExpectsTwoArguments, "is") @@ -349,7 +350,7 @@ proc semIs(c: PContext, n: PNode): PNode = return # 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) = 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 let m = resolveIndirectCall(c, n, nOrig, t) if m.state != csMatch: - if c.compilesContextId > 0: + if errorOutputs == {}: # speed up error generation: globalError(n.info, errTypeMismatch, "") 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 mHigh: result = semLowHigh(c, setMs(n, s), mHigh) 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 mShallowCopy: result = semShallowCopy(c, n, flags) of mExpandToAst: result = semExpandToAst(c, n, s, flags) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 09631c7936ea..d42eb543367f 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -1609,7 +1609,7 @@ proc semStmtList(c: PContext, n: PNode, flags: TExprFlags): PNode = n.typ = n.sons[i].typ return else: - var expr = semExpr(c, n.sons[i]) + var expr = semExpr(c, n.sons[i], flags) n.sons[i] = expr if c.inTypeClass > 0 and expr.typ != nil: case expr.typ.kind diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index c76b08eb9108..5a6f4e015094 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -664,6 +664,7 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate, var oldWriteHook: type(writelnHook) diagnostics: seq[string] + errorPrefix: string flags: TExprFlags = {} collectDiagnostics = m.diagnostics != nil or 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 # Nim doesn't support capturing var params in closures diagnostics = @[] - writelnHook = proc (s: string) = diagnostics.add(s) 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) if collectDiagnostics: diff --git a/tests/concepts/texplain.nim b/tests/concepts/texplain.nim index 9b2b1f70d921..186621f5bf02 100644 --- a/tests/concepts/texplain.nim +++ b/tests/concepts/texplain.nim @@ -1,36 +1,63 @@ discard """ cmd: "nim c --verbosity:0 --colors:off $file" 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 - [User] -tests/concepts/texplain.nim(74, 7) Hint: Non-matching candidates for e(10) -proc e[ExplainedConcept](o: ExplainedConcept): int -tests/concepts/texplain.nim(38, 6) Error: undeclared field: 'foo' -tests/concepts/texplain.nim(38, 6) Error: undeclared field: '.' -tests/concepts/texplain.nim(38, 6) Error: type mismatch: got ( - [User] -tests/concepts/texplain.nim(77, 10) Hint: Non-matching candidates for e(10) -proc e[ExplainedConcept](o: ExplainedConcept): int -tests/concepts/texplain.nim(38, 6) Error: undeclared field: 'foo' -tests/concepts/texplain.nim(38, 6) Error: undeclared field: '.' -tests/concepts/texplain.nim(38, 6) Error: type mismatch: got ( - [User] -tests/concepts/texplain.nim(81, 20) Error: type mismatch: got ( -tests/concepts/texplain.nim(82, 20) Error: type mismatch: got ( -tests/concepts/texplain.nim(83, 20) Hint: Non-matching candidates for r(y) + +tests/concepts/texplain.nim(102, 7) Hint: Non-matching candidates for e(10) +proc e(o: ExplainedConcept): int +tests/concepts/texplain.nim(65, 6) ExplainedConcept: undeclared field: 'foo' +tests/concepts/texplain.nim(65, 6) ExplainedConcept: undeclared field: '.' +tests/concepts/texplain.nim(65, 6) ExplainedConcept: expression '.' cannot be called +tests/concepts/texplain.nim(65, 5) ExplainedConcept: type class predicate failed +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(105, 10) Hint: Non-matching candidates for e(10) +proc e(o: ExplainedConcept): int +tests/concepts/texplain.nim(65, 6) ExplainedConcept: undeclared field: 'foo' +tests/concepts/texplain.nim(65, 6) ExplainedConcept: undeclared field: '.' +tests/concepts/texplain.nim(65, 6) ExplainedConcept: expression '.' cannot be called +tests/concepts/texplain.nim(65, 5) ExplainedConcept: type class predicate failed +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: +proc e(o: ExplainedConcept): int +tests/concepts/texplain.nim(65, 5) ExplainedConcept: type class predicate failed +proc e(i: int): int + +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 - [User] -tests/concepts/texplain.nim(91, 2) Error: type mismatch: got (MatchingType) + +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[NestedConcept](o: NestedConcept) -tests/concepts/texplain.nim(42, 6) Error: undeclared field: 'foo' -tests/concepts/texplain.nim(42, 6) Error: undeclared field: '.' -tests/concepts/texplain.nim(42, 6) Error: type mismatch: got ( -tests/concepts/texplain.nim(46, 5) Error: type class predicate failed +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 - errormsg: "type class predicate failed" + line: 119 + errormsg: "type mismatch: got (MatchingType)" """ type @@ -56,6 +83,7 @@ type proc e(o: ExplainedConcept): int = 1 proc e(i: int): int = i +proc r[T](a: SomeNumber, b: T, c: auto) = discard proc r(o: RegularConcept): int = 1 proc r(i: string): int = 1 @@ -77,11 +105,11 @@ echo(e(10) {.explain.}, 20) discard e(10) static: - # provide diagnostics why the compile block failed + # provide diagnostics why the compile block failed assert(compiles(e(n)) {.explain.} == false) assert(compiles(r(n)) {.explain.} == false) assert(compiles(r(y)) {.explain.} == true) - + # these should not produce any output assert(compiles(r(10)) == false) assert(compiles(e(10)) == true) diff --git a/tests/testament/tester.nim b/tests/testament/tester.nim index 908eba962dbc..2d758ef0ddb4 100644 --- a/tests/testament/tester.nim +++ b/tests/testament/tester.nim @@ -63,6 +63,8 @@ let var targets = {low(TTarget)..high(TTarget)} +proc normalizeMsg(s: string): string = s.strip.replace("\C\L", "\L") + proc callCompiler(cmdTemplate, filename, options: string, target: TTarget): TSpec = 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) = if strip(expected.msg) notin strip(given.msg): 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) elif expected.tfile == "" and extractFilename(expected.file) != extractFilename(given.file) and "internal error:" notin expected.msg: @@ -235,8 +237,6 @@ proc nimoutCheck(test: TTest; expectedNimout: string; given: var TSpec) = if exp notin giv: given.err = reMsgsDiffer -proc normalize(s: string): string = s.strip.replace("\C\L", "\L") - proc makeDeterministic(s: string): string = var x = splitLines(s) sort(x, system.cmp)