proper error reporting for concepts and the introduction of the {.explain.} pragma
This commit is contained in:
parent
644d645ea7
commit
74a80988d9
13 changed files with 302 additions and 139 deletions
|
|
@ -55,76 +55,60 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode,
|
|||
initialBinding: PNode,
|
||||
filter: TSymKinds,
|
||||
best, alt: var TCandidate,
|
||||
errors: var CandidateErrors) =
|
||||
errors: var CandidateErrors,
|
||||
diagnostics = false) =
|
||||
var o: TOverloadIter
|
||||
var sym = initOverloadIter(o, c, headSymbol)
|
||||
var scope = o.lastOverloadScope
|
||||
# Thanks to the lazy semchecking for operands, we need to check whether
|
||||
# 'initCandidate' modifies the symbol table (via semExpr).
|
||||
# This can occur in cases like 'init(a, 1, (var b = new(Type2); b))'
|
||||
let counterInitial = c.currentScope.symbols.counter
|
||||
var syms: seq[tuple[s: PSym, scope: int]]
|
||||
var nextSymIndex = 0
|
||||
while sym != nil:
|
||||
if sym.kind in filter:
|
||||
# Initialise 'best' and 'alt' with the first available symbol
|
||||
initCandidate(c, best, sym, initialBinding, scope)
|
||||
initCandidate(c, alt, sym, initialBinding, scope)
|
||||
best.state = csNoMatch
|
||||
break
|
||||
else:
|
||||
sym = nextOverloadIter(o, c, headSymbol)
|
||||
scope = o.lastOverloadScope
|
||||
# thanks to the lazy semchecking for operands, we need to iterate over the
|
||||
# symbol table *before* any call to 'initCandidate' which might invoke
|
||||
# semExpr which might modify the symbol table in cases like
|
||||
# 'init(a, 1, (var b = new(Type2); b))'.
|
||||
var symx = initOverloadIter(o, c, headSymbol)
|
||||
let symScope = o.lastOverloadScope
|
||||
|
||||
var syms: seq[tuple[a: PSym, b: int]] = @[]
|
||||
while symx != nil:
|
||||
if symx.kind in filter:
|
||||
syms.add((symx, o.lastOverloadScope))
|
||||
symx = nextOverloadIter(o, c, headSymbol)
|
||||
if syms.len == 0: return
|
||||
var z: TCandidate
|
||||
while sym != nil:
|
||||
if sym.kind notin filter:
|
||||
sym = nextOverloadIter(o, c, headSymbol)
|
||||
scope = o.lastOverloadScope
|
||||
continue
|
||||
initCandidate(c, best, syms[0][0], initialBinding,
|
||||
symScope, diagnostics = diagnostics)
|
||||
initCandidate(c, alt, syms[0][0], initialBinding,
|
||||
symScope, diagnostics = diagnostics)
|
||||
best.state = csNoMatch
|
||||
|
||||
for i in 0 .. <syms.len:
|
||||
let sym = syms[i][0]
|
||||
determineType(c, sym)
|
||||
initCandidate(c, z, sym, initialBinding, scope)
|
||||
if c.currentScope.symbols.counter == counterInitial or syms != nil:
|
||||
matches(c, n, orig, z)
|
||||
if errors != nil:
|
||||
errors.safeAdd((sym, int z.mutabilityProblem))
|
||||
if z.errors != nil:
|
||||
for err in z.errors:
|
||||
errors.add(err)
|
||||
if z.state == csMatch:
|
||||
# little hack so that iterators are preferred over everything else:
|
||||
if sym.kind == skIterator: inc(z.exactMatches, 200)
|
||||
case best.state
|
||||
of csEmpty, csNoMatch: best = z
|
||||
of csMatch:
|
||||
var cmp = cmpCandidates(best, z)
|
||||
if cmp < 0: best = z # x is better than the best so far
|
||||
elif cmp == 0: alt = z # x is as good as the best so far
|
||||
else:
|
||||
# Symbol table has been modified. Restart and pre-calculate all syms
|
||||
# before any further candidate init and compare. SLOW, but rare case.
|
||||
syms = initCandidateSymbols(c, headSymbol, initialBinding, filter, best, alt, o)
|
||||
if syms == nil:
|
||||
sym = nextOverloadIter(o, c, headSymbol)
|
||||
scope = o.lastOverloadScope
|
||||
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
|
||||
initCandidate(c, z, sym, initialBinding,
|
||||
syms[i][1], diagnostics = diagnostics)
|
||||
#if sym.name.s == "*" and (n.info ?? "temp5.nim") and n.info.line == 140:
|
||||
# gDebug = true
|
||||
matches(c, n, orig, z)
|
||||
if z.state == csMatch:
|
||||
# little hack so that iterators are preferred over everything else:
|
||||
if sym.kind == skIterator: inc(z.exactMatches, 200)
|
||||
case best.state
|
||||
of csEmpty, csNoMatch: best = z
|
||||
of csMatch:
|
||||
var cmp = cmpCandidates(best, z)
|
||||
if cmp < 0: best = z # x is better than the best so far
|
||||
elif cmp == 0: alt = z # x is as good as the best so far
|
||||
else: discard
|
||||
#if sym.name.s == "cmp" and (n.info ?? "rstgen.nim") and n.info.line == 516:
|
||||
# echo "Matches ", n.info, " ", typeToString(sym.typ)
|
||||
# debug sym
|
||||
# writeMatches(z)
|
||||
# for i in 1 .. <len(z.call):
|
||||
# z.call[i].typ.debug
|
||||
# quit 1
|
||||
elif errors != nil or z.diagnostics != nil:
|
||||
errors.safeAdd((sym, int z.mutabilityProblem, z.diagnostics))
|
||||
|
||||
proc presentFailedCandidates(c: PContext, n: PNode, errors: CandidateErrors):
|
||||
(TPreferedDesc, string) =
|
||||
var prefer = preferName
|
||||
# to avoid confusing errors like:
|
||||
# got (SslPtr, SocketHandle)
|
||||
# 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
|
||||
# module information.
|
||||
let proto = describeArgs(c, n, 1, preferName)
|
||||
|
||||
var prefer = preferName
|
||||
for err, mut in items(errors):
|
||||
for err, mut, diagnostics in items(errors):
|
||||
var errProto = ""
|
||||
let n = err.typ.n
|
||||
for i in countup(1, n.len - 1):
|
||||
|
|
@ -147,20 +129,36 @@ proc notFoundError*(c: PContext, n: PNode, errors: CandidateErrors) =
|
|||
prefer = preferModuleInfo
|
||||
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 = ""
|
||||
for err, mut in items(errors):
|
||||
for err, mut, diagnostics in items(errors):
|
||||
if err.kind in routineKinds and err.ast != nil:
|
||||
add(candidates, renderTree(err.ast,
|
||||
{renderNoBody, renderNoComments,renderNoPragmas}))
|
||||
{renderNoBody, renderNoComments, renderNoPragmas}))
|
||||
else:
|
||||
add(candidates, err.getProcHeader(prefer))
|
||||
add(candidates, "\n")
|
||||
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")
|
||||
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 != "":
|
||||
add(result, "\n" & msgKindToString(errButExpected) & "\n" & candidates)
|
||||
localError(n.info, errGenerated, result)
|
||||
|
|
@ -172,7 +170,7 @@ proc bracketNotFoundError(c: PContext; n: PNode) =
|
|||
var symx = initOverloadIter(o, c, headSymbol)
|
||||
while symx != nil:
|
||||
if symx.kind in routineKinds:
|
||||
errors.add((symx, 0))
|
||||
errors.add((symx, 0, nil))
|
||||
symx = nextOverloadIter(o, c, headSymbol)
|
||||
if errors.len == 0:
|
||||
localError(n.info, "could not resolve: " & $n)
|
||||
|
|
@ -180,7 +178,7 @@ proc bracketNotFoundError(c: PContext; n: PNode) =
|
|||
notFoundError(c, n, errors)
|
||||
|
||||
proc resolveOverloads(c: PContext, n, orig: PNode,
|
||||
filter: TSymKinds;
|
||||
filter: TSymKinds, flags: TExprFlags,
|
||||
errors: var CandidateErrors): TCandidate =
|
||||
var initialBinding: PNode
|
||||
var alt: TCandidate
|
||||
|
|
@ -194,7 +192,7 @@ proc resolveOverloads(c: PContext, n, orig: PNode,
|
|||
|
||||
template pickBest(headSymbol) =
|
||||
pickBestCandidate(c, headSymbol, n, orig, initialBinding,
|
||||
filter, result, alt, errors)
|
||||
filter, result, alt, errors, efExplain in flags)
|
||||
pickBest(f)
|
||||
|
||||
let overloadsState = result.state
|
||||
|
|
@ -212,7 +210,6 @@ proc resolveOverloads(c: PContext, n, orig: PNode,
|
|||
if result.state != csMatch:
|
||||
n.sons.delete(1)
|
||||
orig.sons.delete(1)
|
||||
excl n.flags, nfExprCall
|
||||
else: return
|
||||
|
||||
if nfDotField in n.flags:
|
||||
|
|
@ -260,11 +257,6 @@ proc resolveOverloads(c: PContext, n, orig: PNode,
|
|||
# clean up the inserted ops
|
||||
n.sons.delete(2)
|
||||
n.sons[0] = f
|
||||
|
||||
errors = @[]
|
||||
pickBest(f)
|
||||
#notFoundError(c, n, errors)
|
||||
|
||||
return
|
||||
if alt.state == csMatch and cmpCandidates(result, alt) == 0 and
|
||||
not sameMethodDispatcher(result.calleeSym, alt.calleeSym):
|
||||
|
|
@ -286,7 +278,6 @@ proc resolveOverloads(c: PContext, n, orig: PNode,
|
|||
getProcHeader(result.calleeSym), getProcHeader(alt.calleeSym),
|
||||
args])
|
||||
|
||||
|
||||
proc instGenericConvertersArg*(c: PContext, a: PNode, x: TCandidate) =
|
||||
if a.kind == nkHiddenCallConv and a.sons[0].kind == nkSym:
|
||||
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.} =
|
||||
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 =
|
||||
result = newNodeI(nkHiddenDeref, n.info)
|
||||
|
|
@ -375,23 +366,40 @@ proc tryDeref(n: PNode): PNode =
|
|||
result.addSon(n)
|
||||
|
||||
proc semOverloadedCall(c: PContext, n, nOrig: PNode,
|
||||
filter: TSymKinds): PNode =
|
||||
var errors: CandidateErrors
|
||||
|
||||
var r = resolveOverloads(c, n, nOrig, filter, errors)
|
||||
if r.state == csMatch: result = semResolvedCall(c, n, r)
|
||||
filter: TSymKinds, flags: TExprFlags): PNode =
|
||||
var errors: CandidateErrors = if efExplain in flags: @[]
|
||||
else: nil
|
||||
var r = resolveOverloads(c, n, nOrig, filter, flags, errors)
|
||||
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):
|
||||
# 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
|
||||
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)
|
||||
else:
|
||||
# get rid of the deref again for a better error message:
|
||||
n.sons[1] = n.sons[1].sons[0]
|
||||
notFoundError(c, n, errors)
|
||||
else:
|
||||
notFoundError(c, n, errors)
|
||||
# else: result = errorNode(c, n)
|
||||
if efExplain notin flags:
|
||||
# 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 =
|
||||
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 arg = n[i].typ
|
||||
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)
|
||||
newInst.typ.flags.excl tfUnresolved
|
||||
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)
|
||||
return n
|
||||
result = explicitGenericSym(c, n, s)
|
||||
if result == nil: result = explicitGenericInstError(n)
|
||||
elif a.kind in {nkClosedSymChoice, nkOpenSymChoice}:
|
||||
# choose the generic proc with the proper number of type parameters.
|
||||
# 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
|
||||
# type parameters:
|
||||
if safeLen(candidate.ast.sons[genericParamsPos]) == n.len-1:
|
||||
let x = explicitGenericSym(c, n, candidate)
|
||||
if x != nil: result.add(x)
|
||||
result.add(explicitGenericSym(c, n, candidate))
|
||||
# get rid of nkClosedSymChoice if not ambiguous:
|
||||
if result.len == 1 and a.kind == nkClosedSymChoice:
|
||||
result = result[0]
|
||||
elif result.len == 0: result = explicitGenericInstError(n)
|
||||
# candidateCount != 1: return explicitGenericInstError(n)
|
||||
else:
|
||||
result = explicitGenericInstError(n)
|
||||
|
||||
|
|
@ -460,7 +471,7 @@ proc searchForBorrowProc(c: PContext, startScope: PScope, fn: PSym): PSym =
|
|||
call.add(newIdentNode(fn.name, fn.info))
|
||||
for i in 1.. <fn.typ.n.len:
|
||||
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
|
||||
var x: PType
|
||||
if param.typ.kind == tyVar:
|
||||
|
|
@ -470,12 +481,6 @@ proc searchForBorrowProc(c: PContext, startScope: PScope, fn: PSym): PSym =
|
|||
x = t.baseOfDistinct
|
||||
call.add(newNodeIT(nkEmpty, fn.info, x))
|
||||
if hasDistinct:
|
||||
var resolved = semOverloadedCall(c, call, call, {fn.kind})
|
||||
var resolved = semOverloadedCall(c, call, call, {fn.kind}, {})
|
||||
if resolved != nil:
|
||||
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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue