diff --git a/compiler/analyzer.cpp b/compiler/analyzer.cpp index eb9dac43f..a67ad83dd 100644 --- a/compiler/analyzer.cpp +++ b/compiler/analyzer.cpp @@ -2008,6 +2008,8 @@ InvokeEntry *analyzeCallable(ObjectPtr x, llvm::ArrayRef args) { matchFailureError(failures); } + setCurrentOverload(entry->matchedOverload); + if (entry->parent->shouldLog) matchFailureLog(failures); diff --git a/compiler/ceramic.hpp b/compiler/ceramic.hpp index fa240b722..3d5e8d255 100644 --- a/compiler/ceramic.hpp +++ b/compiler/ceramic.hpp @@ -609,6 +609,7 @@ struct CompileContextEntry { Location location; ObjectPtr callable; + OverloadPtr overload; bool hasParams : 1; @@ -633,6 +634,8 @@ void pushCompileContext(ObjectPtr obj, llvm::ArrayRef params, void popCompileContext(); +void setCurrentOverload(OverloadPtr overload); + vector getCompileContext(); void setCompileContext(llvm::ArrayRef x); @@ -1190,7 +1193,15 @@ enum BindingKind { BINDING_KIND_MAP(BINDING_KIND_GEN) }; llvm::raw_ostream &operator<<(llvm::raw_ostream &os, BindingKind); -struct PatternVar; +struct PatternVar { + IdentifierPtr name; + bool isMulti : 1; + + PatternVar(bool isMulti, IdentifierPtr name) + : name(name), isMulti(isMulti) {} + + PatternVar() : isMulti(false) {} +}; struct Binding : public Statement { const BindingKind bindingKind; @@ -1506,16 +1517,6 @@ struct ReturnSpec : public ANode { : ANode(RETURN_SPEC), type(type), name(name) {} }; -struct PatternVar { - IdentifierPtr name; - bool isMulti : 1; - - PatternVar(bool isMulti, IdentifierPtr name) - : name(name), isMulti(isMulti) {} - - PatternVar() : isMulti(false) {} -}; - struct LLVMCode : ANode { const string body; @@ -1696,26 +1697,24 @@ struct Overload : public TopLevelItem { PatternPtr callablePattern; vector argPatterns; MultiPatternPtr varArgPattern; - InlineAttribute isInline : 3; - int patternsInitializedState : 2; // 0:notinit, -1:initing, +1:inited - bool callByName : 1; - bool nameIsPattern : 1; - bool hasAsConversion : 1; - bool isDefault : 1; + InlineAttribute isInline : 3 = IGNORE; + int patternsInitializedState : 2 = 0; // 0:notinit, -1:initing, +1:inited + bool callByName : 1 = false; + bool nameIsPattern : 1 = false; + bool hasAsConversion : 1 = false; + bool isDefault : 1 = false; + bool isDiagnosticTransparent : 1 = false; Overload(Module *module, ExprPtr target, CodePtr code, bool callByName, InlineAttribute isInline) : TopLevelItem(OVERLOAD, module), target(target), code(code), - isInline(isInline), patternsInitializedState(0), - callByName(callByName), nameIsPattern(false), hasAsConversion(false), - isDefault(false) {} + isInline(isInline), callByName(callByName) {} Overload(Module *module, ExprPtr target, CodePtr code, bool callByName, InlineAttribute isInline, bool hasAsConversion) : TopLevelItem(OVERLOAD, module), target(target), code(code), - isInline(isInline), patternsInitializedState(0), - callByName(callByName), nameIsPattern(false), - hasAsConversion(hasAsConversion), isDefault(false) {} + isInline(isInline), callByName(callByName), + hasAsConversion(hasAsConversion) {} }; struct Procedure : public TopLevelItem { diff --git a/compiler/error.cpp b/compiler/error.cpp index 16f9e82ec..e148e4d9d 100644 --- a/compiler/error.cpp +++ b/compiler/error.cpp @@ -6,6 +6,7 @@ #include "matchinvoke.hpp" #include "printer.hpp" +#include #include namespace ceramic { @@ -48,6 +49,11 @@ void pushCompileContext(ObjectPtr obj, llvm::ArrayRef params, void popCompileContext() { contextStack.pop_back(); } +void setCurrentOverload(OverloadPtr overload) { + if (!contextStack.empty()) + contextStack.back().overload = overload; +} + vector getCompileContext() { return contextStack; } void setCompileContext(llvm::ArrayRef x) { @@ -433,42 +439,130 @@ void matchBindingError(MatchResultPtr const &result) { error(sout.str()); } +// Rank a match-failure kind by how informative it is to the user. +// Higher = more useful (predicate names, type-pattern mismatches). +// Lower = noise (this overload was just for an unrelated callable name). +static int failureScore(MatchCode code) { + switch (code) { + case MATCH_PREDICATE_ERROR: + return 4; + case MATCH_ARGUMENT_ERROR: + case MATCH_MULTI_ARGUMENT_ERROR: + case MATCH_BINDING_ERROR: + case MATCH_MULTI_BINDING_ERROR: + return 3; + case MATCH_ARITY_ERROR: + return 2; + case MATCH_CALLABLE_ERROR: + return 1; + default: + return 0; + } +} + +static void printFailureLine(llvm::raw_ostream &sout, + const pair &failure) { + sout << "\n "; + Location location = failure.first->location; + unsigned line, column, tabColumn; + getLineCol(location, line, column, tabColumn); + sout << location.source->fileName.c_str() << "(" << line + 1 << "," + << column << ")" + << "\n "; + printMatchError(sout, failure.second); +} + static void matchFailureMessage(MatchFailureError const &err, string &outBuf) { llvm::raw_string_ostream sout(outBuf); - int hiddenPatternOverloads = 0; + // -full-match-errors preserves the original verbatim dump. + if (shouldPrintFullMatchErrors) { + for (const auto &failure : err.failures) + printFailureLine(sout, failure); + sout.flush(); + return; + } + + // Default path: hide universal pattern overloads, then rank what's left. + int hiddenPatternOverloads = 0; + vector> visible; for (const auto &failure : err.failures) { - OverloadPtr overload = failure.first; - if (!shouldPrintFullMatchErrors && overload->nameIsPattern) { + if (failure.first->nameIsPattern) { ++hiddenPatternOverloads; continue; } - sout << "\n "; - Location location = overload->location; - unsigned line, column, tabColumn; - getLineCol(location, line, column, tabColumn); - sout << location.source->fileName.c_str() << "(" << line + 1 << "," - << column << ")" - << "\n "; - printMatchError(sout, failure.second); + visible.push_back(failure); } - if (hiddenPatternOverloads > 0) + + std::stable_sort(visible.begin(), visible.end(), + [](const pair &a, + const pair &b) { + return failureScore(a.second->matchCode) > + failureScore(b.second->matchCode); + }); + + const size_t MAX_SHOW = 5; + size_t shown = std::min(visible.size(), MAX_SHOW); + size_t lessSpecific = visible.size() - shown; + + for (size_t i = 0; i < shown; ++i) + printFailureLine(sout, visible[i]); + + if (lessSpecific > 0) { + sout << "\n " << lessSpecific + << " other less-specific overloads not shown"; + if (hiddenPatternOverloads > 0) + sout << " (plus " << hiddenPatternOverloads + << " universal overloads)"; + sout << " (use -full-match-errors for all)"; + } else if (hiddenPatternOverloads > 0) { sout << "\n " << hiddenPatternOverloads - << " universal overloads not shown (show with -full-match-errors " - "option)"; + << " universal overloads not shown (use -full-match-errors for " + "all)"; + } sout.flush(); } void matchFailureError(MatchFailureError const &err) { string buf; - if (err.failedInterface) - buf = "call does not conform to function interface"; - else if (err.ambiguousMatch) - buf = "call matches ambiguous overloads"; - else - buf = "no matching overload found"; + { + llvm::raw_string_ostream sout(buf); + if (err.failedInterface) + sout << "call does not conform to function interface"; + else if (err.ambiguousMatch) + sout << "call matches ambiguous overloads"; + else { + sout << "no matching overload found"; + // Append the callable + arg types from the deepest context frame, + // e.g. "no matching overload found for +(Foo, Foo)" + if (!contextStack.empty()) { + const auto &top = contextStack.back(); + sout << " for "; + printName(sout, top.callable); + if (top.hasParams) { + sout << "("; + printNameList(sout, top.params, top.dispatchIndices); + sout << ")"; + } + } + } + sout.flush(); + } matchFailureMessage(err, buf); + + // Pick the deepest blameable frame. + for (auto it = contextStack.rbegin(); it != contextStack.rend(); ++it) { + if (!it->location.ok() || !it->location.source) + continue; + if (it->overload.ptr() == nullptr) + continue; + if (it->overload->isDiagnosticTransparent) + continue; + pushLocation(it->location); + break; + } + error(buf); } diff --git a/compiler/invoketables.cpp b/compiler/invoketables.cpp index 3a957db69..8d8d2e972 100644 --- a/compiler/invoketables.cpp +++ b/compiler/invoketables.cpp @@ -281,6 +281,7 @@ static InvokeEntry *newInvokeEntry(InvokeSet *parent, MatchSuccessPtr match, MatchSuccessPtr interfaceMatch) { InvokeEntry *entry = new InvokeEntry(parent, match->callable, match->argsKey); + entry->matchedOverload = match->overload; entry->origCode = match->overload->code; entry->code = clone(match->overload->code); entry->env = match->env; diff --git a/compiler/invoketables.hpp b/compiler/invoketables.hpp index 0d55a735d..a15ed515b 100644 --- a/compiler/invoketables.hpp +++ b/compiler/invoketables.hpp @@ -17,6 +17,7 @@ struct InvokeEntry { InvokeSet *parent; ObjectPtr callable; + OverloadPtr matchedOverload; vector argsKey; vector forwardedRValueFlags; diff --git a/compiler/parser.cpp b/compiler/parser.cpp index c0aafbb3c..bf7508968 100644 --- a/compiler/parser.cpp +++ b/compiler/parser.cpp @@ -2173,18 +2173,25 @@ static void skipOptPatternVar() { unsigned p = save(); if (!symbol("[")) { restore(p); - } else { - int bracket = 1; - while (bracket) { - unsigned i = save(); - if (symbol("[")) { - ++bracket; - continue; - } - restore(i); - if (symbol("]")) - --bracket; + return; + } + // `[[` belongs to a different grammar rule. + unsigned q = save(); + if (symbol("[")) { + restore(p); + return; + } + restore(q); + int bracket = 1; + while (bracket) { + unsigned i = save(); + if (symbol("[")) { + ++bracket; + continue; } + restore(i); + if (symbol("]")) + --bracket; } } @@ -2669,6 +2676,43 @@ static bool isOverload(bool &isDefault) { return true; } +// `[[name, ...]]` attribute list. Unknown names warn. +static bool optAttributeList(bool &isDiagnosticTransparent) { + isDiagnosticTransparent = false; + unsigned p = save(); + if (!symbol("[")) { + restore(p); + return true; + } + if (!symbol("[")) { + restore(p); + return true; + } + while (true) { + Location attrLoc = currentLocation(); + IdentifierPtr name; + if (!identifier(name)) + return false; + if (name->str == "transparent") { + isDiagnosticTransparent = true; + } else { + pushLocation(attrLoc); + warning("unknown attribute '" + name->str + "'"); + popLocation(); + } + unsigned q = save(); + if (symbol(",")) + continue; + restore(q); + break; + } + if (!symbol("]")) + return false; + if (!symbol("]")) + return false; + return true; +} + static bool optInline(InlineAttribute &isInline) { unsigned p = save(); if (keyword("inline")) @@ -2816,6 +2860,9 @@ static bool procedureWithInterface(vector &x, Module *module, static bool procedureWithBody(vector &x, Module *module, unsigned s) { + bool isDiagnosticTransparent = false; + if (!optAttributeList(isDiagnosticTransparent)) + return false; Visibility vis; if (!topLevelVisibility(vis)) return false; @@ -2865,6 +2912,7 @@ static bool procedureWithBody(vector &x, Module *module, OverloadPtr oload = new Overload(module, target, code, callByName, isInline, hasAsConversion); oload->location = location; + oload->isDiagnosticTransparent = isDiagnosticTransparent; x.emplace_back(oload.ptr()); proc->singleOverload = oload; @@ -2893,6 +2941,9 @@ static bool procedure(TopLevelItemPtr &x, Module *module) { } static bool overload(TopLevelItemPtr &x, Module *module, unsigned s) { + bool isDiagnosticTransparent = false; + if (!optAttributeList(isDiagnosticTransparent)) + return false; InlineAttribute isInline; if (!optInline(isInline)) return false; @@ -2943,6 +2994,7 @@ static bool overload(TopLevelItemPtr &x, Module *module, unsigned s) { hasAsConversion); oload->location = location; oload->isDefault = isDefault; + oload->isDiagnosticTransparent = isDiagnosticTransparent; x = oload.ptr(); return true; }