mirror of
https://github.com/nim-lang/Nim.git
synced 2026-02-14 23:33:28 +00:00
proper error reporting for concepts and the introduction of the {.explain.} pragma
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
|
||||
92
tests/concepts/texplain.nim
Normal file
92
tests/concepts/texplain.nim
Normal 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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user