proper error reporting for concepts and the introduction of the {.explain.} pragma

This commit is contained in:
Zahary Karadjov 2016-08-14 02:45:29 +03:00
commit 74a80988d9
13 changed files with 302 additions and 139 deletions

View file

@ -252,6 +252,7 @@ type
sfProcvar, # proc can be passed to a proc var sfProcvar, # proc can be passed to a proc var
sfDiscriminant, # field is a discriminant in a record/object sfDiscriminant, # field is a discriminant in a record/object
sfDeprecated, # symbol is deprecated sfDeprecated, # symbol is deprecated
sfExplain, # provide more diagnostics when this symbol is used
sfError, # usage of symbol should trigger a compile-time error sfError, # usage of symbol should trigger a compile-time error
sfShadowed, # a symbol that was shadowed in some inner scope sfShadowed, # a symbol that was shadowed in some inner scope
sfThread, # proc will run as a thread sfThread, # proc will run as a thread

View file

@ -499,7 +499,6 @@ type
TErrorOutput* = enum TErrorOutput* = enum
eStdOut eStdOut
eStdErr eStdErr
eInMemory
TErrorOutputs* = set[TErrorOutput] TErrorOutputs* = set[TErrorOutput]
@ -653,6 +652,15 @@ var
writelnHook*: proc (output: string) {.closure.} writelnHook*: proc (output: string) {.closure.}
structuredErrorHook*: proc (info: TLineInfo; msg: string; severity: Severity) {.closure.} structuredErrorHook*: proc (info: TLineInfo; msg: string; severity: Severity) {.closure.}
proc concat(strings: openarray[string]): string =
var totalLen = 0
for s in strings: totalLen += s.len
result = newStringOfCap totalLen
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):
@ -806,10 +814,7 @@ macro callStyledWriteLineStderr(args: varargs[typed]): untyped =
result.add(arg) result.add(arg)
template callWritelnHook(args: varargs[string, `$`]) = template callWritelnHook(args: varargs[string, `$`]) =
var s = "" writelnHook concat(args)
for arg in args:
s.add arg
writelnHook s
template styledMsgWriteln*(args: varargs[typed]) = template styledMsgWriteln*(args: varargs[typed]) =
if not isNil(writelnHook): if not isNil(writelnHook):

View file

@ -66,6 +66,7 @@ proc parseSymbol*(p: var TParser, allowNil = false): PNode
proc parseTry(p: var TParser; isExpr: bool): PNode proc parseTry(p: var TParser; isExpr: bool): PNode
proc parseCase(p: var TParser): PNode proc parseCase(p: var TParser): PNode
proc parseStmtPragma(p: var TParser): PNode proc parseStmtPragma(p: var TParser): PNode
proc parsePragma(p: var TParser): PNode
# implementation # implementation
proc getTok(p: var TParser) = proc getTok(p: var TParser) =
@ -770,6 +771,13 @@ proc parseOperators(p: var TParser, headNode: PNode,
proc simpleExprAux(p: var TParser, limit: int, mode: TPrimaryMode): PNode = proc simpleExprAux(p: var TParser, limit: int, mode: TPrimaryMode): PNode =
result = primary(p, mode) result = primary(p, mode)
if p.tok.tokType == tkCurlyDotLe and
p.lex.lineNumber == result.info.line and
mode == pmNormal:
var pragmaExp = newNodeP(nkPragmaExpr, p)
pragmaExp.addSon result
pragmaExp.addSon p.parsePragma
result = pragmaExp
result = parseOperators(p, result, limit, mode) result = parseOperators(p, result, limit, mode)
proc simpleExpr(p: var TParser, mode = pmNormal): PNode = proc simpleExpr(p: var TParser, mode = pmNormal): PNode =

View file

@ -55,7 +55,7 @@ const
wPure, wHeader, wCompilerproc, wFinal, wSize, wExtern, wShallow, wPure, wHeader, wCompilerproc, wFinal, wSize, wExtern, wShallow,
wImportCpp, wImportObjC, wError, wIncompleteStruct, wByCopy, wByRef, wImportCpp, wImportObjC, wError, wIncompleteStruct, wByCopy, wByRef,
wInheritable, wGensym, wInject, wRequiresInit, wUnchecked, wUnion, wPacked, wInheritable, wGensym, wInject, wRequiresInit, wUnchecked, wUnion, wPacked,
wBorrow, wGcSafe, wExportNims, wPartial, wUsed} wBorrow, wGcSafe, wExportNims, wPartial, wUsed, wExplain}
fieldPragmas* = {wImportc, wExportc, wDeprecated, wExtern, fieldPragmas* = {wImportc, wExportc, wDeprecated, wExtern,
wImportCpp, wImportObjC, wError, wGuard, wBitsize, wUsed} wImportCpp, wImportObjC, wError, wGuard, wBitsize, wUsed}
varPragmas* = {wImportc, wExportc, wVolatile, wRegister, wThreadVar, wNodecl, varPragmas* = {wImportc, wExportc, wVolatile, wRegister, wThreadVar, wNodecl,
@ -73,7 +73,7 @@ const
proc pragma*(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords) proc pragma*(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords)
# implementation # implementation
proc invalidPragma(n: PNode) = proc invalidPragma*(n: PNode) =
localError(n.info, errInvalidPragmaX, renderTree(n, {renderNoComments})) localError(n.info, errInvalidPragmaX, renderTree(n, {renderNoComments}))
proc pragmaAsm*(c: PContext, n: PNode): char = proc pragmaAsm*(c: PContext, n: PNode): char =
@ -773,6 +773,8 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int,
of wProcVar: of wProcVar:
noVal(it) noVal(it)
incl(sym.flags, sfProcvar) incl(sym.flags, sfProcvar)
of wExplain:
sym.flags.incl sfExplain
of wDeprecated: of wDeprecated:
if it.kind == nkExprColonExpr: deprecatedStmt(c, it) if it.kind == nkExprColonExpr: deprecatedStmt(c, it)
elif sym != nil: incl(sym.flags, sfDeprecated) elif sym != nil: incl(sym.flags, sfDeprecated)

View file

@ -55,76 +55,60 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode,
initialBinding: PNode, initialBinding: PNode,
filter: TSymKinds, filter: TSymKinds,
best, alt: var TCandidate, best, alt: var TCandidate,
errors: var CandidateErrors) = errors: var CandidateErrors,
diagnostics = false) =
var o: TOverloadIter var o: TOverloadIter
var sym = initOverloadIter(o, c, headSymbol) # thanks to the lazy semchecking for operands, we need to iterate over the
var scope = o.lastOverloadScope # symbol table *before* any call to 'initCandidate' which might invoke
# Thanks to the lazy semchecking for operands, we need to check whether # semExpr which might modify the symbol table in cases like
# 'initCandidate' modifies the symbol table (via semExpr). # 'init(a, 1, (var b = new(Type2); b))'.
# This can occur in cases like 'init(a, 1, (var b = new(Type2); b))' var symx = initOverloadIter(o, c, headSymbol)
let counterInitial = c.currentScope.symbols.counter let symScope = o.lastOverloadScope
var syms: seq[tuple[s: PSym, scope: int]]
var nextSymIndex = 0 var syms: seq[tuple[a: PSym, b: int]] = @[]
while sym != nil: while symx != nil:
if sym.kind in filter: if symx.kind in filter:
# Initialise 'best' and 'alt' with the first available symbol syms.add((symx, o.lastOverloadScope))
initCandidate(c, best, sym, initialBinding, scope) symx = nextOverloadIter(o, c, headSymbol)
initCandidate(c, alt, sym, initialBinding, scope) if syms.len == 0: return
best.state = csNoMatch
break
else:
sym = nextOverloadIter(o, c, headSymbol)
scope = o.lastOverloadScope
var z: TCandidate var z: TCandidate
while sym != nil: initCandidate(c, best, syms[0][0], initialBinding,
if sym.kind notin filter: symScope, diagnostics = diagnostics)
sym = nextOverloadIter(o, c, headSymbol) initCandidate(c, alt, syms[0][0], initialBinding,
scope = o.lastOverloadScope symScope, diagnostics = diagnostics)
continue best.state = csNoMatch
for i in 0 .. <syms.len:
let sym = syms[i][0]
determineType(c, sym) determineType(c, sym)
initCandidate(c, z, sym, initialBinding, scope) initCandidate(c, z, sym, initialBinding,
if c.currentScope.symbols.counter == counterInitial or syms != nil: syms[i][1], diagnostics = diagnostics)
matches(c, n, orig, z) #if sym.name.s == "*" and (n.info ?? "temp5.nim") and n.info.line == 140:
if errors != nil: # gDebug = true
errors.safeAdd((sym, int z.mutabilityProblem)) matches(c, n, orig, z)
if z.errors != nil: if z.state == csMatch:
for err in z.errors: # little hack so that iterators are preferred over everything else:
errors.add(err) if sym.kind == skIterator: inc(z.exactMatches, 200)
if z.state == csMatch: case best.state
# little hack so that iterators are preferred over everything else: of csEmpty, csNoMatch: best = z
if sym.kind == skIterator: inc(z.exactMatches, 200) of csMatch:
case best.state var cmp = cmpCandidates(best, z)
of csEmpty, csNoMatch: best = z if cmp < 0: best = z # x is better than the best so far
of csMatch: elif cmp == 0: alt = z # x is as good as the best so far
var cmp = cmpCandidates(best, z) else: discard
if cmp < 0: best = z # x is better than the best so far #if sym.name.s == "cmp" and (n.info ?? "rstgen.nim") and n.info.line == 516:
elif cmp == 0: alt = z # x is as good as the best so far # echo "Matches ", n.info, " ", typeToString(sym.typ)
else: # debug sym
# Symbol table has been modified. Restart and pre-calculate all syms # writeMatches(z)
# before any further candidate init and compare. SLOW, but rare case. # for i in 1 .. <len(z.call):
syms = initCandidateSymbols(c, headSymbol, initialBinding, filter, best, alt, o) # z.call[i].typ.debug
if syms == nil: # quit 1
sym = nextOverloadIter(o, c, headSymbol) elif errors != nil or z.diagnostics != nil:
scope = o.lastOverloadScope errors.safeAdd((sym, int z.mutabilityProblem, z.diagnostics))
elif nextSymIndex < syms.len:
# rare case: retrieve the next pre-calculated symbol
sym = syms[nextSymIndex].s
scope = syms[nextSymIndex].scope
nextSymIndex += 1
else:
break
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:
# fail fast:
globalError(n.info, errTypeMismatch, "")
if errors.isNil or errors.len == 0:
localError(n.info, errExprXCannotBeCalled, n[0].renderTree)
return
proc presentFailedCandidates(c: PContext, n: PNode, errors: CandidateErrors):
(TPreferedDesc, string) =
var prefer = preferName
# to avoid confusing errors like: # to avoid confusing errors like:
# got (SslPtr, SocketHandle) # got (SslPtr, SocketHandle)
# but expected one of: # but expected one of:
@ -132,9 +116,7 @@ proc notFoundError*(c: PContext, n: PNode, errors: CandidateErrors) =
# we do a pre-analysis. If all types produce the same string, we will add # we do a pre-analysis. If all types produce the same string, we will add
# module information. # module information.
let proto = describeArgs(c, n, 1, preferName) let proto = describeArgs(c, n, 1, preferName)
for err, mut, diagnostics in items(errors):
var prefer = preferName
for err, mut in items(errors):
var errProto = "" var errProto = ""
let n = err.typ.n let n = err.typ.n
for i in countup(1, n.len - 1): for i in countup(1, n.len - 1):
@ -147,20 +129,36 @@ proc notFoundError*(c: PContext, n: PNode, errors: CandidateErrors) =
prefer = preferModuleInfo prefer = preferModuleInfo
break break
# now use the information stored in 'prefer' to produce a nice error message:
var result = msgKindToString(errTypeMismatch)
add(result, describeArgs(c, n, 1, prefer))
add(result, ')')
var candidates = "" var candidates = ""
for err, mut in items(errors): for err, mut, diagnostics in items(errors):
if err.kind in routineKinds and err.ast != nil: if err.kind in routineKinds and err.ast != nil:
add(candidates, renderTree(err.ast, add(candidates, renderTree(err.ast,
{renderNoBody, renderNoComments,renderNoPragmas})) {renderNoBody, renderNoComments, renderNoPragmas}))
else: else:
add(candidates, err.getProcHeader(prefer)) add(candidates, err.getProcHeader(prefer))
add(candidates, "\n") add(candidates, "\n")
if mut != 0 and mut < n.len: if mut != 0 and mut < n.len:
add(candidates, "for a 'var' type a variable needs to be passed, but '" & renderTree(n[mut]) & "' is immutable\n") add(candidates, "for a 'var' type a variable needs to be passed, but '" & renderTree(n[mut]) & "' is immutable\n")
for diag in diagnostics:
add(candidates, diag & "\n")
result = (prefer, candidates)
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:
# fail fast:
globalError(n.info, errTypeMismatch, "")
if errors.isNil or errors.len == 0:
localError(n.info, errExprXCannotBeCalled, n[0].renderTree)
return
let (prefer, candidates) = presentFailedCandidates(c, n, errors)
var result = msgKindToString(errTypeMismatch)
add(result, describeArgs(c, n, 1, prefer))
add(result, ')')
if candidates != "": if candidates != "":
add(result, "\n" & msgKindToString(errButExpected) & "\n" & candidates) add(result, "\n" & msgKindToString(errButExpected) & "\n" & candidates)
localError(n.info, errGenerated, result) localError(n.info, errGenerated, result)
@ -172,7 +170,7 @@ proc bracketNotFoundError(c: PContext; n: PNode) =
var symx = initOverloadIter(o, c, headSymbol) var symx = initOverloadIter(o, c, headSymbol)
while symx != nil: while symx != nil:
if symx.kind in routineKinds: if symx.kind in routineKinds:
errors.add((symx, 0)) errors.add((symx, 0, nil))
symx = nextOverloadIter(o, c, headSymbol) symx = nextOverloadIter(o, c, headSymbol)
if errors.len == 0: if errors.len == 0:
localError(n.info, "could not resolve: " & $n) localError(n.info, "could not resolve: " & $n)
@ -180,7 +178,7 @@ proc bracketNotFoundError(c: PContext; n: PNode) =
notFoundError(c, n, errors) notFoundError(c, n, errors)
proc resolveOverloads(c: PContext, n, orig: PNode, proc resolveOverloads(c: PContext, n, orig: PNode,
filter: TSymKinds; filter: TSymKinds, flags: TExprFlags,
errors: var CandidateErrors): TCandidate = errors: var CandidateErrors): TCandidate =
var initialBinding: PNode var initialBinding: PNode
var alt: TCandidate var alt: TCandidate
@ -194,7 +192,7 @@ proc resolveOverloads(c: PContext, n, orig: PNode,
template pickBest(headSymbol) = template pickBest(headSymbol) =
pickBestCandidate(c, headSymbol, n, orig, initialBinding, pickBestCandidate(c, headSymbol, n, orig, initialBinding,
filter, result, alt, errors) filter, result, alt, errors, efExplain in flags)
pickBest(f) pickBest(f)
let overloadsState = result.state let overloadsState = result.state
@ -212,7 +210,6 @@ proc resolveOverloads(c: PContext, n, orig: PNode,
if result.state != csMatch: if result.state != csMatch:
n.sons.delete(1) n.sons.delete(1)
orig.sons.delete(1) orig.sons.delete(1)
excl n.flags, nfExprCall
else: return else: return
if nfDotField in n.flags: if nfDotField in n.flags:
@ -260,11 +257,6 @@ proc resolveOverloads(c: PContext, n, orig: PNode,
# clean up the inserted ops # clean up the inserted ops
n.sons.delete(2) n.sons.delete(2)
n.sons[0] = f n.sons[0] = f
errors = @[]
pickBest(f)
#notFoundError(c, n, errors)
return return
if alt.state == csMatch and cmpCandidates(result, alt) == 0 and if alt.state == csMatch and cmpCandidates(result, alt) == 0 and
not sameMethodDispatcher(result.calleeSym, alt.calleeSym): not sameMethodDispatcher(result.calleeSym, alt.calleeSym):
@ -286,7 +278,6 @@ proc resolveOverloads(c: PContext, n, orig: PNode,
getProcHeader(result.calleeSym), getProcHeader(alt.calleeSym), getProcHeader(result.calleeSym), getProcHeader(alt.calleeSym),
args]) args])
proc instGenericConvertersArg*(c: PContext, a: PNode, x: TCandidate) = proc instGenericConvertersArg*(c: PContext, a: PNode, x: TCandidate) =
if a.kind == nkHiddenCallConv and a.sons[0].kind == nkSym: if a.kind == nkHiddenCallConv and a.sons[0].kind == nkSym:
let s = a.sons[0].sym let s = a.sons[0].sym
@ -367,7 +358,7 @@ proc semResolvedCall(c: PContext, n: PNode, x: TCandidate): PNode =
proc canDeref(n: PNode): bool {.inline.} = proc canDeref(n: PNode): bool {.inline.} =
result = n.len >= 2 and (let t = n[1].typ; result = n.len >= 2 and (let t = n[1].typ;
t != nil and t.skipTypes({tyGenericInst, tyAlias}).kind in {tyPtr, tyRef}) t != nil and t.skipTypes({tyGenericInst}).kind in {tyPtr, tyRef})
proc tryDeref(n: PNode): PNode = proc tryDeref(n: PNode): PNode =
result = newNodeI(nkHiddenDeref, n.info) result = newNodeI(nkHiddenDeref, n.info)
@ -375,23 +366,40 @@ proc tryDeref(n: PNode): PNode =
result.addSon(n) result.addSon(n)
proc semOverloadedCall(c: PContext, n, nOrig: PNode, proc semOverloadedCall(c: PContext, n, nOrig: PNode,
filter: TSymKinds): PNode = filter: TSymKinds, flags: TExprFlags): PNode =
var errors: CandidateErrors var errors: CandidateErrors = if efExplain in flags: @[]
else: nil
var r = resolveOverloads(c, n, nOrig, filter, errors) var r = resolveOverloads(c, n, nOrig, filter, flags, errors)
if r.state == csMatch: result = semResolvedCall(c, n, r) if r.state == csMatch:
# this may be triggered, when the explain pragma is used
if errors.len > 0:
let (_, candidates) = presentFailedCandidates(c, n, errors)
message(n.info, hintUser,
"Non-matching candidates for " & renderTree(n) & "\n" &
candidates)
result = semResolvedCall(c, n, r)
elif experimentalMode(c) and canDeref(n): elif experimentalMode(c) and canDeref(n):
# try to deref the first argument and then try overloading resolution again: # try to deref the first argument and then try overloading resolution again:
#
# XXX: why is this here?
# it could be added to the long list of alternatives tried
# inside `resolveOverloads` or it could be moved all the way
# into sigmatch with hidden conversion produced there
#
n.sons[1] = n.sons[1].tryDeref n.sons[1] = n.sons[1].tryDeref
var r = resolveOverloads(c, n, nOrig, filter, errors) var r = resolveOverloads(c, n, nOrig, filter, flags, errors)
if r.state == csMatch: result = semResolvedCall(c, n, r) if r.state == csMatch: result = semResolvedCall(c, n, r)
else: else:
# get rid of the deref again for a better error message: # get rid of the deref again for a better error message:
n.sons[1] = n.sons[1].sons[0] n.sons[1] = n.sons[1].sons[0]
notFoundError(c, n, errors) notFoundError(c, n, errors)
else: else:
notFoundError(c, n, errors) if efExplain notin flags:
# else: result = errorNode(c, n) # repeat the overload resolution,
# this time enabling all the diagnostic output (this should fail again)
discard semOverloadedCall(c, n, nOrig, filter, flags + {efExplain})
else:
notFoundError(c, n, errors)
proc explicitGenericInstError(n: PNode): PNode = proc explicitGenericInstError(n: PNode): PNode =
localError(n.info, errCannotInstantiateX, renderTree(n)) localError(n.info, errCannotInstantiateX, renderTree(n))
@ -406,7 +414,12 @@ proc explicitGenericSym(c: PContext, n: PNode, s: PSym): PNode =
let formal = s.ast.sons[genericParamsPos].sons[i-1].typ let formal = s.ast.sons[genericParamsPos].sons[i-1].typ
let arg = n[i].typ let arg = n[i].typ
let tm = typeRel(m, formal, arg, true) let tm = typeRel(m, formal, arg, true)
if tm in {isNone, isConvertible}: return nil if tm in {isNone, isConvertible}:
if formal.sonsLen > 0 and formal.sons[0].kind != tyNone:
typeMismatch(n, formal.sons[0], arg)
else:
typeMismatch(n, formal, arg)
break
var newInst = generateInstance(c, s, m.bindings, n.info) var newInst = generateInstance(c, s, m.bindings, n.info)
newInst.typ.flags.excl tfUnresolved newInst.typ.flags.excl tfUnresolved
markUsed(n.info, s, c.graph.usageSym) markUsed(n.info, s, c.graph.usageSym)
@ -428,7 +441,6 @@ proc explicitGenericInstantiation(c: PContext, n: PNode, s: PSym): PNode =
"; got " & $(n.len-1) & " type(s) but expected " & $expected) "; got " & $(n.len-1) & " type(s) but expected " & $expected)
return n return n
result = explicitGenericSym(c, n, s) result = explicitGenericSym(c, n, s)
if result == nil: result = explicitGenericInstError(n)
elif a.kind in {nkClosedSymChoice, nkOpenSymChoice}: elif a.kind in {nkClosedSymChoice, nkOpenSymChoice}:
# choose the generic proc with the proper number of type parameters. # choose the generic proc with the proper number of type parameters.
# XXX I think this could be improved by reusing sigmatch.paramTypesMatch. # XXX I think this could be improved by reusing sigmatch.paramTypesMatch.
@ -441,12 +453,11 @@ proc explicitGenericInstantiation(c: PContext, n: PNode, s: PSym): PNode =
# it suffices that the candidate has the proper number of generic # it suffices that the candidate has the proper number of generic
# type parameters: # type parameters:
if safeLen(candidate.ast.sons[genericParamsPos]) == n.len-1: if safeLen(candidate.ast.sons[genericParamsPos]) == n.len-1:
let x = explicitGenericSym(c, n, candidate) result.add(explicitGenericSym(c, n, candidate))
if x != nil: result.add(x)
# get rid of nkClosedSymChoice if not ambiguous: # get rid of nkClosedSymChoice if not ambiguous:
if result.len == 1 and a.kind == nkClosedSymChoice: if result.len == 1 and a.kind == nkClosedSymChoice:
result = result[0] result = result[0]
elif result.len == 0: result = explicitGenericInstError(n) # candidateCount != 1: return explicitGenericInstError(n)
else: else:
result = explicitGenericInstError(n) result = explicitGenericInstError(n)
@ -460,7 +471,7 @@ proc searchForBorrowProc(c: PContext, startScope: PScope, fn: PSym): PSym =
call.add(newIdentNode(fn.name, fn.info)) call.add(newIdentNode(fn.name, fn.info))
for i in 1.. <fn.typ.n.len: for i in 1.. <fn.typ.n.len:
let param = fn.typ.n.sons[i] let param = fn.typ.n.sons[i]
let t = skipTypes(param.typ, abstractVar-{tyTypeDesc, tyDistinct}) let t = skipTypes(param.typ, abstractVar-{tyTypeDesc})
if t.kind == tyDistinct or param.typ.kind == tyDistinct: hasDistinct = true if t.kind == tyDistinct or param.typ.kind == tyDistinct: hasDistinct = true
var x: PType var x: PType
if param.typ.kind == tyVar: if param.typ.kind == tyVar:
@ -470,12 +481,6 @@ proc searchForBorrowProc(c: PContext, startScope: PScope, fn: PSym): PSym =
x = t.baseOfDistinct x = t.baseOfDistinct
call.add(newNodeIT(nkEmpty, fn.info, x)) call.add(newNodeIT(nkEmpty, fn.info, x))
if hasDistinct: if hasDistinct:
var resolved = semOverloadedCall(c, call, call, {fn.kind}) var resolved = semOverloadedCall(c, call, call, {fn.kind}, {})
if resolved != nil: if resolved != nil:
result = resolved.sons[0].sym result = resolved.sons[0].sym
if not compareTypes(result.typ.sons[0], fn.typ.sons[0], dcEqIgnoreDistinct):
result = nil
elif result.magic in {mArrPut, mArrGet}:
# cannot borrow these magics for now
result = nil

View file

@ -46,9 +46,10 @@ type
TExprFlag* = enum TExprFlag* = enum
efLValue, efWantIterator, efInTypeof, efLValue, efWantIterator, efInTypeof,
efWantStmt, efAllowStmt, efDetermineType, efWantStmt, efAllowStmt, efDetermineType, efExplain,
efAllowDestructor, efWantValue, efOperand, efNoSemCheck, efAllowDestructor, efWantValue, efOperand, efNoSemCheck,
efNoProcvarCheck, efNoEvaluateGeneric, efInCall, efFromHlo efNoProcvarCheck, efNoEvaluateGeneric, efInCall, efFromHlo,
TExprFlags* = set[TExprFlag] TExprFlags* = set[TExprFlag]
TTypeAttachedOp* = enum TTypeAttachedOp* = enum
@ -84,12 +85,12 @@ type
libs*: seq[PLib] # all libs used by this module libs*: seq[PLib] # all libs used by this module
semConstExpr*: proc (c: PContext, n: PNode): PNode {.nimcall.} # for the pragmas semConstExpr*: proc (c: PContext, n: PNode): PNode {.nimcall.} # for the pragmas
semExpr*: proc (c: PContext, n: PNode, flags: TExprFlags = {}): PNode {.nimcall.} semExpr*: proc (c: PContext, n: PNode, flags: TExprFlags = {}): PNode {.nimcall.}
semTryExpr*: proc (c: PContext, n: PNode,flags: TExprFlags = {}): PNode {.nimcall.} semTryExpr*: proc (c: PContext, n: PNode, flags: TExprFlags = {}): PNode {.nimcall.}
semTryConstExpr*: proc (c: PContext, n: PNode): PNode {.nimcall.} semTryConstExpr*: proc (c: PContext, n: PNode): PNode {.nimcall.}
semOperand*: proc (c: PContext, n: PNode, flags: TExprFlags = {}): PNode {.nimcall.} semOperand*: proc (c: PContext, n: PNode, flags: TExprFlags = {}): PNode {.nimcall.}
semConstBoolExpr*: proc (c: PContext, n: PNode): PNode {.nimcall.} # XXX bite the bullet semConstBoolExpr*: proc (c: PContext, n: PNode): PNode {.nimcall.} # XXX bite the bullet
semOverloadedCall*: proc (c: PContext, n, nOrig: PNode, semOverloadedCall*: proc (c: PContext, n, nOrig: PNode,
filter: TSymKinds): PNode {.nimcall.} filter: TSymKinds, flags: TExprFlags): PNode {.nimcall.}
semTypeNode*: proc(c: PContext, n: PNode, prev: PType): PType {.nimcall.} semTypeNode*: proc(c: PContext, n: PNode, prev: PType): PType {.nimcall.}
semInferredLambda*: proc(c: PContext, pt: TIdTable, n: PNode): PNode semInferredLambda*: proc(c: PContext, pt: TIdTable, n: PNode): PNode
semGenerateInstance*: proc (c: PContext, fn: PSym, pt: TIdTable, semGenerateInstance*: proc (c: PContext, fn: PSym, pt: TIdTable,

View file

@ -643,10 +643,10 @@ proc semOverloadedCallAnalyseEffects(c: PContext, n: PNode, nOrig: PNode,
# for typeof support. # for typeof support.
# for ``type(countup(1,3))``, see ``tests/ttoseq``. # for ``type(countup(1,3))``, see ``tests/ttoseq``.
result = semOverloadedCall(c, n, nOrig, result = semOverloadedCall(c, n, nOrig,
{skProc, skMethod, skConverter, skMacro, skTemplate, skIterator}) {skProc, skMethod, skConverter, skMacro, skTemplate, skIterator}, flags)
else: else:
result = semOverloadedCall(c, n, nOrig, result = semOverloadedCall(c, n, nOrig,
{skProc, skMethod, skConverter, skMacro, skTemplate}) {skProc, skMethod, skConverter, skMacro, skTemplate}, flags)
if result != nil: if result != nil:
if result.sons[0].kind != nkSym: if result.sons[0].kind != nkSym:
@ -1744,7 +1744,7 @@ proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
let oldOwnerLen = len(c.graph.owners) let oldOwnerLen = len(c.graph.owners)
let oldGenerics = c.generics let oldGenerics = c.generics
let oldErrorOutputs = errorOutputs let oldErrorOutputs = errorOutputs
errorOutputs = {} if efExplain notin flags: errorOutputs = {}
let oldContextLen = msgs.getInfoContextLen() let oldContextLen = msgs.getInfoContextLen()
let oldInGenericContext = c.inGenericContext let oldInGenericContext = c.inGenericContext
@ -2350,8 +2350,20 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
of nkCurlyExpr: of nkCurlyExpr:
result = semExpr(c, buildOverloadedSubscripts(n, getIdent"{}"), flags) result = semExpr(c, buildOverloadedSubscripts(n, getIdent"{}"), flags)
of nkPragmaExpr: of nkPragmaExpr:
# which pragmas are allowed for expressions? `likely`, `unlikely` var
internalError(n.info, "semExpr() to implement") # XXX: to implement expr = n[0]
pragma = n[1]
pragmaName = considerQuotedIdent(pragma[0])
flags = flags
case whichKeyword(pragmaName)
of wExplain:
flags.incl efExplain
else:
# what other pragmas are allowed for expressions? `likely`, `unlikely`
invalidPragma(n)
result = semExpr(c, n[0], flags)
of nkPar: of nkPar:
case checkPar(n) case checkPar(n)
of paNone: result = errorNode(c, n) of paNone: result = errorNode(c, n)

View file

@ -766,6 +766,7 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
let owner = if typeClass.sym != nil: typeClass.sym let owner = if typeClass.sym != nil: typeClass.sym
else: getCurrOwner(c) else: getCurrOwner(c)
var s = newSym(skType, finalTypId, owner, info) var s = newSym(skType, finalTypId, owner, info)
if sfExplain in owner.flags: s.flags.incl sfExplain
if typId == nil: s.flags.incl(sfAnon) if typId == nil: s.flags.incl(sfAnon)
s.linkTo(typeClass) s.linkTo(typeClass)
typeClass.flags.incl tfImplicitTypeParam typeClass.flags.incl tfImplicitTypeParam

View file

@ -145,7 +145,7 @@ proc reResolveCallsWithTypedescParams(cl: var TReplTypeVars, n: PNode): PNode =
if isTypeParam(n[i]): needsFixing = true if isTypeParam(n[i]): needsFixing = true
if needsFixing: if needsFixing:
n.sons[0] = newSymNode(n.sons[0].sym.owner) n.sons[0] = newSymNode(n.sons[0].sym.owner)
return cl.c.semOverloadedCall(cl.c, n, n, {skProc}) return cl.c.semOverloadedCall(cl.c, n, n, {skProc}, {})
for i in 0 .. <n.safeLen: for i in 0 .. <n.safeLen:
n.sons[i] = reResolveCallsWithTypedescParams(cl, n[i]) n.sons[i] = reResolveCallsWithTypedescParams(cl, n[i])

View file

@ -22,7 +22,13 @@ type
TCandidateState* = enum TCandidateState* = enum
csEmpty, csMatch, csNoMatch csEmpty, csMatch, csNoMatch
CandidateErrors* = seq[(PSym,int)] CandidateError = tuple
sym: PSym
unmatchedVarParam: int
diagnostics: seq[string]
CandidateErrors* = seq[CandidateError]
TCandidate* = object TCandidate* = object
c*: PContext c*: PContext
exactMatches*: int # also misused to prefer iters over procs exactMatches*: int # also misused to prefer iters over procs
@ -53,11 +59,16 @@ type
# matching. they will be reset if the matching # matching. they will be reset if the matching
# is not successful. may replace the bindings # is not successful. may replace the bindings
# table in the future. # table in the future.
diagnostics*: seq[string] # when this is not nil, the matching process
# will collect extra diagnostics that will be
# displayed to the user.
# triggered when overload resolution fails
# or when the explain pragma is used. may be
# triggered with an idetools command in the
# future.
mutabilityProblem*: uint8 # tyVar mismatch mutabilityProblem*: uint8 # tyVar mismatch
inheritancePenalty: int # to prefer closest father object type inheritancePenalty: int # to prefer closest father object type
errors*: CandidateErrors # additional clarifications to be displayed to the
# user if overload resolution fails
TTypeRelation* = enum # order is important! TTypeRelation* = enum # order is important!
isNone, isConvertible, isNone, isConvertible,
isIntConv, isIntConv,
@ -105,7 +116,7 @@ proc put(c: var TCandidate, key, val: PType) {.inline.} =
idTablePut(c.bindings, key, val.skipIntLit) idTablePut(c.bindings, key, val.skipIntLit)
proc initCandidate*(ctx: PContext, c: var TCandidate, callee: PSym, proc initCandidate*(ctx: PContext, c: var TCandidate, callee: PSym,
binding: PNode, calleeScope = -1) = binding: PNode, calleeScope = -1, diagnostics = false) =
initCandidateAux(ctx, c, callee.typ) initCandidateAux(ctx, c, callee.typ)
c.calleeSym = callee c.calleeSym = callee
if callee.kind in skProcKinds and calleeScope == -1: if callee.kind in skProcKinds and calleeScope == -1:
@ -120,9 +131,9 @@ proc initCandidate*(ctx: PContext, c: var TCandidate, callee: PSym,
c.calleeScope = 1 c.calleeScope = 1
else: else:
c.calleeScope = calleeScope c.calleeScope = calleeScope
c.diagnostics = if diagnostics: @[] else: nil
c.magic = c.calleeSym.magic c.magic = c.calleeSym.magic
initIdTable(c.bindings) initIdTable(c.bindings)
c.errors = nil
if binding != nil and callee.kind in routineKinds: if binding != nil and callee.kind in routineKinds:
var typeParams = callee.ast[genericParamsPos] var typeParams = callee.ast[genericParamsPos]
for i in 1..min(sonsLen(typeParams), sonsLen(binding)-1): for i in 1..min(sonsLen(typeParams), sonsLen(binding)-1):
@ -577,14 +588,15 @@ proc typeRangeRel(f, a: PType): TTypeRelation {.noinline.} =
proc matchUserTypeClass*(c: PContext, m: var TCandidate, proc matchUserTypeClass*(c: PContext, m: var TCandidate,
ff, a: PType): PType = ff, a: PType): PType =
var body = ff.skipTypes({tyUserTypeClassInst}) var
Concept = ff.skipTypes({tyUserTypeClassInst})
body = Concept.n[3]
if c.inTypeClass > 4: if c.inTypeClass > 4:
localError(body.n[3].info, $body.n[3] & " too nested for type matching") localError(body.info, $body & " too nested for type matching")
return nil return nil
openScope(c) openScope(c)
inc c.inTypeClass inc c.inTypeClass
defer: defer:
dec c.inTypeClass dec c.inTypeClass
closeScope(c) closeScope(c)
@ -599,7 +611,7 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate,
param: PSym param: PSym
template paramSym(kind): untyped = template paramSym(kind): untyped =
newSym(kind, typeParamName, body.sym, body.sym.info) newSym(kind, typeParamName, Concept.sym, Concept.sym.info)
case typ.kind case typ.kind
of tyStatic: of tyStatic:
@ -622,7 +634,7 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate,
addDecl(c, param) addDecl(c, param)
typeParams.safeAdd((param, typ)) typeParams.safeAdd((param, typ))
for param in body.n[0]: for param in Concept.n[0]:
var var
dummyName: PNode dummyName: PNode
dummyType: PType dummyType: PType
@ -645,11 +657,31 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate,
internalAssert dummyName.kind == nkIdent internalAssert dummyName.kind == nkIdent
var dummyParam = newSym(if modifier == tyTypeDesc: skType else: skVar, var dummyParam = newSym(if modifier == tyTypeDesc: skType else: skVar,
dummyName.ident, body.sym, body.sym.info) dummyName.ident, Concept.sym, Concept.sym.info)
dummyParam.typ = dummyType dummyParam.typ = dummyType
addDecl(c, dummyParam) addDecl(c, dummyParam)
var checkedBody = c.semTryExpr(c, body.n[3].copyTree) var
oldWriteHook: type(writelnHook)
diagnostics: seq[string]
flags: TExprFlags = {}
collectDiagnostics = m.diagnostics != nil or
sfExplain in Concept.sym.flags
if collectDiagnostics:
oldWriteHook = writelnHook
# 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}
var checkedBody = c.semTryExpr(c, body.copyTree, flags)
if collectDiagnostics:
writelnHook = oldWriteHook
for msg in diagnostics: m.diagnostics.safeAdd msg
if checkedBody == nil: return nil if checkedBody == nil: return nil
# The inferrable type params have been identified during the semTryExpr above. # The inferrable type params have been identified during the semTryExpr above.

View file

@ -66,7 +66,7 @@ type
wWrite, wGensym, wInject, wDirty, wInheritable, wThreadVar, wEmit, wWrite, wGensym, wInject, wDirty, wInheritable, wThreadVar, wEmit,
wAsmNoStackFrame, wAsmNoStackFrame,
wImplicitStatic, wGlobal, wCodegenDecl, wUnchecked, wGuard, wLocks, wImplicitStatic, wGlobal, wCodegenDecl, wUnchecked, wGuard, wLocks,
wPartial, wPartial, wExplain,
wAuto, wBool, wCatch, wChar, wClass, wAuto, wBool, wCatch, wChar, wClass,
wConst_cast, wDefault, wDelete, wDouble, wDynamic_cast, wConst_cast, wDefault, wDelete, wDouble, wDynamic_cast,
@ -152,7 +152,7 @@ const
"computedgoto", "injectstmt", "experimental", "computedgoto", "injectstmt", "experimental",
"write", "gensym", "inject", "dirty", "inheritable", "threadvar", "emit", "write", "gensym", "inject", "dirty", "inheritable", "threadvar", "emit",
"asmnostackframe", "implicitstatic", "global", "codegendecl", "unchecked", "asmnostackframe", "implicitstatic", "global", "codegendecl", "unchecked",
"guard", "locks", "partial", "guard", "locks", "partial", "explain",
"auto", "bool", "catch", "char", "class", "auto", "bool", "catch", "char", "class",
"const_cast", "default", "delete", "double", "const_cast", "default", "delete", "double",

View file

@ -0,0 +1,92 @@
discard """
cmd: "nim c --verbosity:0 --colors:off $file"
nimout: '''
tests/concepts/texplain.nim(71, 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)
proc r(i: string): int
[User]
tests/concepts/texplain.nim(91, 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
'''
line: 46
errormsg: "type class predicate failed"
"""
type
ExplainedConcept {.explain.} = concept o
o.foo is int
o.bar is string
RegularConcept = concept o
o.foo is int
o.bar is string
NestedConcept = concept o
o.foo is RegularConcept
NonMatchingType = object
foo: int
bar: int
MatchingType = object
foo: int
bar: string
proc e(o: ExplainedConcept): int = 1
proc e(i: int): int = i
proc r(o: RegularConcept): int = 1
proc r(i: string): int = 1
proc f(o: NestedConcept) = discard
var n = NonMatchingType(foo: 10, bar: 20)
var y = MatchingType(foo: 10, bar: "bar")
# no diagnostic here:
discard e(y)
# explain that e(int) doesn't match
discard e(y) {.explain.}
# explain that e(ExplainedConcept) doesn't match
echo(e(10) {.explain.}, 20)
# explain that e(ExplainedConcept) doesn't again
discard e(10)
static:
# 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)
# finally, provide multiple nested explanations for failed matching
# of regular concepts, even when the explain pragma is not used
f(y)

View file

@ -184,6 +184,8 @@ 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:
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:
r.addResult(test, expected.file, given.file, reFilesDiffer) r.addResult(test, expected.file, given.file, reFilesDiffer)
@ -233,6 +235,8 @@ 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)