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
parent 644d645ea7
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
sfDiscriminant, # field is a discriminant in a record/object
sfDeprecated, # symbol is deprecated
sfExplain, # provide more diagnostics when this symbol is used
sfError, # usage of symbol should trigger a compile-time error
sfShadowed, # a symbol that was shadowed in some inner scope
sfThread, # proc will run as a thread

View File

@@ -499,7 +499,6 @@ type
TErrorOutput* = enum
eStdOut
eStdErr
eInMemory
TErrorOutputs* = set[TErrorOutput]
@@ -653,6 +652,15 @@ var
writelnHook*: proc (output: string) {.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) =
if eStdOut in errorOutputs:
if isNil(writelnHook):
@@ -806,10 +814,7 @@ macro callStyledWriteLineStderr(args: varargs[typed]): untyped =
result.add(arg)
template callWritelnHook(args: varargs[string, `$`]) =
var s = ""
for arg in args:
s.add arg
writelnHook s
writelnHook concat(args)
template styledMsgWriteln*(args: varargs[typed]) =
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 parseCase(p: var TParser): PNode
proc parseStmtPragma(p: var TParser): PNode
proc parsePragma(p: var TParser): PNode
# implementation
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 =
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)
proc simpleExpr(p: var TParser, mode = pmNormal): PNode =

View File

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

View File

@@ -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

View File

@@ -46,9 +46,10 @@ type
TExprFlag* = enum
efLValue, efWantIterator, efInTypeof,
efWantStmt, efAllowStmt, efDetermineType,
efWantStmt, efAllowStmt, efDetermineType, efExplain,
efAllowDestructor, efWantValue, efOperand, efNoSemCheck,
efNoProcvarCheck, efNoEvaluateGeneric, efInCall, efFromHlo
efNoProcvarCheck, efNoEvaluateGeneric, efInCall, efFromHlo,
TExprFlags* = set[TExprFlag]
TTypeAttachedOp* = enum
@@ -84,12 +85,12 @@ type
libs*: seq[PLib] # all libs used by this module
semConstExpr*: proc (c: PContext, n: PNode): PNode {.nimcall.} # for the pragmas
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.}
semOperand*: proc (c: PContext, n: PNode, flags: TExprFlags = {}): PNode {.nimcall.}
semConstBoolExpr*: proc (c: PContext, n: PNode): PNode {.nimcall.} # XXX bite the bullet
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.}
semInferredLambda*: proc(c: PContext, pt: TIdTable, n: PNode): PNode
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 ``type(countup(1,3))``, see ``tests/ttoseq``.
result = semOverloadedCall(c, n, nOrig,
{skProc, skMethod, skConverter, skMacro, skTemplate, skIterator})
{skProc, skMethod, skConverter, skMacro, skTemplate, skIterator}, flags)
else:
result = semOverloadedCall(c, n, nOrig,
{skProc, skMethod, skConverter, skMacro, skTemplate})
{skProc, skMethod, skConverter, skMacro, skTemplate}, flags)
if result != nil:
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 oldGenerics = c.generics
let oldErrorOutputs = errorOutputs
errorOutputs = {}
if efExplain notin flags: errorOutputs = {}
let oldContextLen = msgs.getInfoContextLen()
let oldInGenericContext = c.inGenericContext
@@ -2350,8 +2350,20 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
of nkCurlyExpr:
result = semExpr(c, buildOverloadedSubscripts(n, getIdent"{}"), flags)
of nkPragmaExpr:
# which pragmas are allowed for expressions? `likely`, `unlikely`
internalError(n.info, "semExpr() to implement") # XXX: to implement
var
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:
case checkPar(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
else: getCurrOwner(c)
var s = newSym(skType, finalTypId, owner, info)
if sfExplain in owner.flags: s.flags.incl sfExplain
if typId == nil: s.flags.incl(sfAnon)
s.linkTo(typeClass)
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 needsFixing:
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:
n.sons[i] = reResolveCallsWithTypedescParams(cl, n[i])

View File

@@ -22,7 +22,13 @@ type
TCandidateState* = enum
csEmpty, csMatch, csNoMatch
CandidateErrors* = seq[(PSym,int)]
CandidateError = tuple
sym: PSym
unmatchedVarParam: int
diagnostics: seq[string]
CandidateErrors* = seq[CandidateError]
TCandidate* = object
c*: PContext
exactMatches*: int # also misused to prefer iters over procs
@@ -53,11 +59,16 @@ type
# matching. they will be reset if the matching
# is not successful. may replace the bindings
# 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
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!
isNone, isConvertible,
isIntConv,
@@ -105,7 +116,7 @@ proc put(c: var TCandidate, key, val: PType) {.inline.} =
idTablePut(c.bindings, key, val.skipIntLit)
proc initCandidate*(ctx: PContext, c: var TCandidate, callee: PSym,
binding: PNode, calleeScope = -1) =
binding: PNode, calleeScope = -1, diagnostics = false) =
initCandidateAux(ctx, c, callee.typ)
c.calleeSym = callee
if callee.kind in skProcKinds and calleeScope == -1:
@@ -120,9 +131,9 @@ proc initCandidate*(ctx: PContext, c: var TCandidate, callee: PSym,
c.calleeScope = 1
else:
c.calleeScope = calleeScope
c.diagnostics = if diagnostics: @[] else: nil
c.magic = c.calleeSym.magic
initIdTable(c.bindings)
c.errors = nil
if binding != nil and callee.kind in routineKinds:
var typeParams = callee.ast[genericParamsPos]
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,
ff, a: PType): PType =
var body = ff.skipTypes({tyUserTypeClassInst})
var
Concept = ff.skipTypes({tyUserTypeClassInst})
body = Concept.n[3]
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
openScope(c)
inc c.inTypeClass
defer:
dec c.inTypeClass
closeScope(c)
@@ -599,7 +611,7 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate,
param: PSym
template paramSym(kind): untyped =
newSym(kind, typeParamName, body.sym, body.sym.info)
newSym(kind, typeParamName, Concept.sym, Concept.sym.info)
case typ.kind
of tyStatic:
@@ -622,7 +634,7 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate,
addDecl(c, param)
typeParams.safeAdd((param, typ))
for param in body.n[0]:
for param in Concept.n[0]:
var
dummyName: PNode
dummyType: PType
@@ -645,11 +657,31 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate,
internalAssert dummyName.kind == nkIdent
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
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
# 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,
wAsmNoStackFrame,
wImplicitStatic, wGlobal, wCodegenDecl, wUnchecked, wGuard, wLocks,
wPartial,
wPartial, wExplain,
wAuto, wBool, wCatch, wChar, wClass,
wConst_cast, wDefault, wDelete, wDouble, wDynamic_cast,
@@ -152,7 +152,7 @@ const
"computedgoto", "injectstmt", "experimental",
"write", "gensym", "inject", "dirty", "inheritable", "threadvar", "emit",
"asmnostackframe", "implicitstatic", "global", "codegendecl", "unchecked",
"guard", "locks", "partial",
"guard", "locks", "partial", "explain",
"auto", "bool", "catch", "char", "class",
"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) =
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:
r.addResult(test, expected.nimout, given.nimout, reMsgsDiffer)
elif expected.tfile == "" and extractFilename(expected.file) != extractFilename(given.file) and
"internal error:" notin expected.msg:
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:
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)