diff --git a/frontend/include/chpl/resolution/resolution-error-classes-list.h b/frontend/include/chpl/resolution/resolution-error-classes-list.h index b9ef31241201..1c358bd63a08 100644 --- a/frontend/include/chpl/resolution/resolution-error-classes-list.h +++ b/frontend/include/chpl/resolution/resolution-error-classes-list.h @@ -70,7 +70,7 @@ ERROR_CLASS(MultipleEnumElems, const uast::AstNode*, chpl::UniqueString, const u ERROR_CLASS(MultipleInheritance, const uast::Class*, const uast::AstNode*, const uast::AstNode*) ERROR_CLASS(MultipleQuestionArgs, const uast::FnCall*, const uast::AstNode*, const uast::AstNode*) ERROR_CLASS(NestedClassFieldRef, const uast::TypeDecl*, const uast::TypeDecl*, const uast::AstNode*, ID) -ERROR_CLASS(NoMatchingCandidates, const uast::AstNode*, resolution::CallInfo, std::vector) +ERROR_CLASS(NoMatchingCandidates, const uast::AstNode*, resolution::CallInfo, std::vector, std::vector, std::vector) ERROR_CLASS(NonIterable, const uast::AstNode*, const uast::AstNode*, types::QualifiedType, std::vector>) ERROR_CLASS(NoMatchingEnumValue, const uast::AstNode*, const types::EnumType*, types::QualifiedType) ERROR_CLASS(NotInModule, const uast::Dot*, ID, UniqueString, ID, bool) diff --git a/frontend/lib/resolution/Resolver.cpp b/frontend/lib/resolution/Resolver.cpp index 6aa855e386c1..44a58a8b05aa 100644 --- a/frontend/lib/resolution/Resolver.cpp +++ b/frontend/lib/resolution/Resolver.cpp @@ -944,6 +944,54 @@ static bool isCallToPtr(const AstNode* formalTypeExpr) { return false; } +// helper to gather bad actuals and report NoMatchingCandidates error +static void +handleRejectedCandidates(Context* context, + ResolutionResultByPostorderID& byPostorder, + std::vector& rejected, + const resolution::CallInfo& ci, + const uast::Call*& call, + const std::vector& actualAsts) { + // By performing some processing in the resolver, we can issue a nicer error + // explaining why each candidate was rejected. + std::vector badPasses; + std::vector actualDecls; + badPasses.resize(rejected.size()); + actualDecls.resize(rejected.size()); + // check each rejected candidate for uninitialized actuals + for (size_t i = 0; i < rejected.size(); i++) { + auto &candidate = rejected[i]; + if (candidate.reason() == resolution::FAIL_CANNOT_PASS && + /* skip computing the formal-actual map because it will go poorly + with an unknown formal. */ + candidate.formalReason() != resolution::FAIL_UNKNOWN_FORMAL_TYPE) { + auto fn = candidate.initialForErr(); + resolution::FormalActualMap fa(fn, ci); + auto& badPass = fa.byFormalIdx(candidate.formalIdx()); + badPasses[i] = badPass; + const uast::AstNode *actualExpr = nullptr; + const uast::VarLikeDecl *actualDecl = nullptr; + if (call && 0 <= badPass.actualIdx() && + (size_t)badPass.actualIdx() < actualAsts.size()) { + actualExpr = actualAsts[badPass.actualIdx()]; + } + // look for a definition point of the actual for error reporting of + // uninitialized vars typically in the case of bad split-initialization + if (actualExpr && actualExpr->isIdentifier()) { + auto &resolvedExpr = byPostorder.byAst(actualExpr->toIdentifier()); + if (auto id = resolvedExpr.toId()) { + auto var = parsing::idToAst(context, id); + // should put a nullptr if not a VarLikeDecl + actualDecl = var->toVarLikeDecl(); + } + } + actualDecls[i] = actualDecl; + } + } + CHPL_ASSERT(badPasses.size() == rejected.size() && rejected.size() == actualDecls.size()); + CHPL_REPORT(context, NoMatchingCandidates, call, ci, rejected, badPasses, actualDecls); +} + static void varArgTypeQueryError(Context* context, const AstNode* node, ResolvedExpression& result) { @@ -1804,9 +1852,11 @@ Resolver::issueErrorForFailedCallResolution(const uast::AstNode* astForErr, context->error(astForErr, "Cannot resolve call to '%s': ambiguity", ci.name().c_str()); } else { + std::vector uninitializedActuals; + std::vector faPairs; // could not find a most specific candidate std::vector rejected; - CHPL_REPORT(context, NoMatchingCandidates, astForErr, ci, rejected); + CHPL_REPORT(context, NoMatchingCandidates, astForErr, ci, rejected, faPairs, uninitializedActuals); } } else { context->error(astForErr, "Cannot establish type for call expression"); @@ -1928,6 +1978,7 @@ void Resolver::handleResolvedCallPrintCandidates(ResolvedExpression& r, const CallScopeInfo& inScopes, const QualifiedType& receiverType, const CallResolutionResult& c, + std::vector& actualAsts, optional actionAndId) { bool wasCallGenerated = (bool) actionAndId; CHPL_ASSERT(!wasCallGenerated || receiverType.isUnknown()); @@ -1947,9 +1998,8 @@ void Resolver::handleResolvedCallPrintCandidates(ResolvedExpression& r, } if (!rejected.empty()) { - // There were candidates but we threw them out. We can issue a nicer - // error explaining why each candidate was rejected. - CHPL_REPORT(context, NoMatchingCandidates, call, ci, rejected); + // There were candidates but we threw them out. Report on those. + handleRejectedCandidates(context, byPostorder, rejected, ci, call, actualAsts); return; } } @@ -2295,13 +2345,14 @@ bool Resolver::resolveSpecialNewCall(const Call* call) { bool isMethodCall = true; const AstNode* questionArg = nullptr; std::vector actuals; + std::vector actualAsts; // Prepare receiver. auto receiverInfo = CallInfoActual(calledType, USTR("this")); actuals.push_back(std::move(receiverInfo)); // Remaining actuals. - prepareCallInfoActuals(call, actuals, questionArg); + prepareCallInfoActuals(call, actuals, questionArg, &actualAsts); CHPL_ASSERT(!questionArg); // The 'new' will produce an 'init' call as a side effect. @@ -2315,8 +2366,9 @@ bool Resolver::resolveSpecialNewCall(const Call* call) { // note: the resolution machinery will get compiler generated candidates auto crr = resolveGeneratedCall(context, call, ci, inScopes); - handleResolvedCallPrintCandidates(re, call, ci, inScopes, QualifiedType(), crr, - { { AssociatedAction::NEW_INIT, call->id() } }); + optional action = { { AssociatedAction::NEW_INIT, call->id() } }; + handleResolvedCallPrintCandidates(re, call, ci, inScopes, QualifiedType(), + crr, actualAsts, action); // there should be one or zero applicable candidates @@ -2471,7 +2523,7 @@ bool Resolver::resolveSpecialKeywordCall(const Call* call) { DomainType::getDefaultDistType(context), UniqueString()); actuals.push_back(std::move(defaultDistArg)); // Remaining given args from domain() call as written - prepareCallInfoActuals(call, actuals, questionArg); + prepareCallInfoActuals(call, actuals, questionArg, /*actualAsts*/ nullptr); CHPL_ASSERT(!questionArg); auto ci = @@ -4195,11 +4247,12 @@ bool Resolver::enter(const Call* call) { void Resolver::prepareCallInfoActuals(const Call* call, std::vector& actuals, - const AstNode*& questionArg) { + const AstNode*& questionArg, + std::vector* actualAsts) { CallInfo::prepareActuals(context, call, byPostorder, /* raiseErrors */ true, actuals, questionArg, - /* actualAsts */ nullptr); + actualAsts); } static const Type* getGenericType(Context* context, const Type* recv) { @@ -4465,7 +4518,7 @@ void Resolver::handleCallExpr(const uast::Call* call) { rejected); // save the most specific candidates in the resolution result for the id - handleResolvedCallPrintCandidates(r, call, ci, inScopes, receiverType, c); + handleResolvedCallPrintCandidates(r, call, ci, inScopes, receiverType, c, actualAsts); // handle type inference for variables split-inited by 'out' formals adjustTypesForOutFormals(ci, actualAsts, c.mostSpecific()); diff --git a/frontend/lib/resolution/Resolver.h b/frontend/lib/resolution/Resolver.h index 2bba06eeeb40..d1e1a40570b8 100644 --- a/frontend/lib/resolution/Resolver.h +++ b/frontend/lib/resolution/Resolver.h @@ -471,6 +471,7 @@ struct Resolver { const CallScopeInfo& inScopes, const types::QualifiedType& receiverType, const CallResolutionResult& c, + std::vector& actualAsts, optional associatedActionAndId = {}); // If the variable with the passed ID has unknown or generic type, @@ -580,7 +581,8 @@ struct Resolver { // includes special handling for operators and tuple literals void prepareCallInfoActuals(const uast::Call* call, std::vector& actuals, - const uast::AstNode*& questionArg); + const uast::AstNode*& questionArg, + std::vector* actualAsts); // prepare a CallInfo by inspecting the called expression and actuals CallInfo prepareCallInfoNormalCall(const uast::Call* call); diff --git a/frontend/lib/resolution/resolution-error-classes-list.cpp b/frontend/lib/resolution/resolution-error-classes-list.cpp index efe9fbc7489f..5bc5c4a1512c 100644 --- a/frontend/lib/resolution/resolution-error-classes-list.cpp +++ b/frontend/lib/resolution/resolution-error-classes-list.cpp @@ -1010,11 +1010,14 @@ void ErrorNoMatchingCandidates::write(ErrorWriterBase& wr) const { auto call = node->toCall(); auto& ci = std::get(info_); auto& rejected = std::get>(info_); + auto& formalActuals = std::get>(info_); + auto& actualDecls = std::get>(info_); wr.heading(kind_, type_, node, "unable to resolve call to '", ci.name(), "': no matching candidates."); wr.code(node); unsigned int printCount = 0; + unsigned int iterCount = 0; static const unsigned int maxPrintCount = 2; for (auto& candidate : rejected) { if (printCount == maxPrintCount) break; @@ -1022,13 +1025,9 @@ void ErrorNoMatchingCandidates::write(ErrorWriterBase& wr) const { auto reason = candidate.reason(); wr.message(""); - if (reason == resolution::FAIL_CANNOT_PASS && - /* skip printing detailed info_ here because computing the formal-actual - map will go poorly with an unknown formal. */ - candidate.formalReason() != resolution::FAIL_UNKNOWN_FORMAL_TYPE) { + if (formalActuals[iterCount].formalIdx() != -1) { auto fn = candidate.initialForErr(); - resolution::FormalActualMap fa(fn, ci); - auto badPass = fa.byFormalIdx(candidate.formalIdx()); + auto badPass = formalActuals[iterCount]; auto formalDecl = badPass.formal(); const uast::AstNode* actualExpr = nullptr; if (call && 0 <= badPass.actualIdx() && badPass.actualIdx() < call->numActuals()) { @@ -1044,19 +1043,37 @@ void ErrorNoMatchingCandidates::write(ErrorWriterBase& wr) const { } else if (formalDecl->isTupleDecl()) { formalName = "'" + buildTupleDeclName(formalDecl->toTupleDecl()) + "'"; } - + bool actualPrinted = false; + auto offendingActual = actualDecls[iterCount]; if (badPass.formalType().isUnknown()) { // The formal type can be unknown in an initial instantiation if it // depends on the previous formals' types. In that case, don't print it // and say something nicer. wr.message("The instantiated type of formal ", formalName, " does not allow actuals of type '", badPass.actualType().type(), "'."); + } else if (badPass.actualType().isUnknown() && + offendingActual && + !offendingActual->initExpression() && + !offendingActual->typeExpression()) { + auto formalKind = badPass.formalType().kind(); + auto actualName = "'" + actualExpr->toIdentifier()->name().str() + "'"; + wr.note(actualExpr->id(), "The actual ", actualName, + " expects to be split-initialized because it is declared without a type or initialization expression here:"); + wr.code(offendingActual, { offendingActual }); + wr.message("The call to '", ci.name() ,"' occurs before any valid initialization points:"); + wr.code(actualExpr, { actualExpr }); + actualPrinted =true; + wr.message("The call to '", ci.name(), "' cannot initialize ", + actualName, + " because only 'out' formals can be used to split-initialize. However, ", + actualName, " is passed to formal ", formalName, " which has intent '", formalKind, "'."); + } else { wr.message("The formal ", formalName, " expects ", badPass.formalType(), ", but the actual was ", badPass.actualType(), "."); } - if (actualExpr) { + if (!actualPrinted && actualExpr) { wr.code(actualExpr, { actualExpr }); } @@ -1117,6 +1134,7 @@ void ErrorNoMatchingCandidates::write(ErrorWriterBase& wr) const { } wr.code(candidate.idForErr()); } + iterCount++; } if (printCount < rejected.size()) { diff --git a/test/errors/resolution/noMatchingCandidates.1.good b/test/errors/resolution/noMatchingCandidates.1.good new file mode 100644 index 000000000000..5ae81eba4436 --- /dev/null +++ b/test/errors/resolution/noMatchingCandidates.1.good @@ -0,0 +1,5 @@ +noMatchingCandidates.chpl:6: error: unable to resolve call to 'fn': no matching candidates +noMatchingCandidates.chpl:2: note: the following candidate didn't match because an actual couldn't be passed to a formal +noMatchingCandidates.chpl:6: note: The actual 'x' expects to be split-initialized because it is declared without a type or initialization expression here +noMatchingCandidates.chpl:14: error: unable to resolve call to 'fn': no matching candidates +noMatchingCandidates.chpl:10: note: the following candidate didn't match because an actual couldn't be passed to a formal diff --git a/test/errors/resolution/noMatchingCandidates.2.good b/test/errors/resolution/noMatchingCandidates.2.good new file mode 100644 index 000000000000..325549c7d648 --- /dev/null +++ b/test/errors/resolution/noMatchingCandidates.2.good @@ -0,0 +1,45 @@ +─── error in noMatchingCandidates.chpl:6 [NoMatchingCandidates] ─── + Unable to resolve call to 'fn': no matching candidates. + | + 6 | fn(x); + | + + The following candidate didn't match because an actual couldn't be passed to a formal: + | + 2 | proc fn(const arg) { + | ⎺⎺⎺⎺⎺⎺⎺⎺⎺ + 3 | arg; + 4 | } + | + The actual 'x' expects to be split-initialized because it is declared without a type or initialization expression here: + | + 5 | var x; + | ⎺ + | + The call to 'fn' occurs before any valid initialization points: + | + 6 | fn(x); + | ⎺ + | + The call to 'fn' cannot initialize 'x' because only 'out' formals can be used to split-initialize. However, 'x' is passed to formal 'arg' which has intent 'const'. + +─── error in noMatchingCandidates.chpl:14 [NoMatchingCandidates] ─── + Unable to resolve call to 'fn': no matching candidates. + | + 14 | fn(x); + | + + The following candidate didn't match because an actual couldn't be passed to a formal: + | + 10 | proc fn(arg:string) { + | ⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺ + 11 | arg; + 12 | } + | + The formal 'arg' expects a value of type 'string', but the actual was a value of type 'int(64)'. + | + 14 | fn(x); + | ⎺ + | + Formals with kind 'const ref' expect the actual to be a subtype, but 'int(64)' is not a subtype of 'string'. + diff --git a/test/errors/resolution/noMatchingCandidates.chpl b/test/errors/resolution/noMatchingCandidates.chpl new file mode 100644 index 000000000000..e2487a499cce --- /dev/null +++ b/test/errors/resolution/noMatchingCandidates.chpl @@ -0,0 +1,35 @@ +{ // expects split-init + proc fn(const arg) { + arg; + } + var x; + fn(x); + x=5; +} +{ // type mismatch + proc fn(arg:string) { + arg; + } + var x=5; + fn(x); +} +// Tests needed: +// call was parenful but method is parenless + +// call was parenless but method was parenful + +// bad where clause + +// bad vararg count + +// star tuple mismatches + +// tuple size mismatches + +// The 'ref' intent requires the formal and actual types to match exactly + +// bad subtype + +// incompatible manager + +// incompatible nilability