From b922f2b75d8db3db570428ca7c7551b15b1c642e Mon Sep 17 00:00:00 2001 From: Ahmad Rezaii Date: Wed, 8 Jan 2025 14:54:34 -0700 Subject: [PATCH 01/10] add bad split-init actual errors Signed-off-by: Ahmad Rezaii --- .../resolution-error-classes-list.h | 2 +- frontend/lib/resolution/Resolver.cpp | 38 ++++++++++++++++++- .../resolution-error-classes-list.cpp | 26 +++++++++++-- 3 files changed, 59 insertions(+), 7 deletions(-) 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..25fe8e69053d 100644 --- a/frontend/lib/resolution/Resolver.cpp +++ b/frontend/lib/resolution/Resolver.cpp @@ -1804,9 +1804,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"); @@ -1949,7 +1951,39 @@ 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); + std::vector badPasses; + std::vector actualDecls; + // check each rejected candidate for uninitialized actuals + for (auto& candidate : rejected) { + auto reason = candidate.reason(); + 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) { + auto fn = candidate.initialForErr(); + resolution::FormalActualMap fa(fn, ci); + auto badPass = fa.byFormalIdx(candidate.formalIdx()); + badPasses.push_back(badPass); + const uast::AstNode* actualExpr = nullptr; + const uast::VarLikeDecl* actualDecl = nullptr; + if (call && 0 <= badPass.actualIdx() && + badPass.actualIdx() < call->numActuals()) { + actualExpr = call->actual(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.push_back(actualDecl); + } + } + CHPL_REPORT(context, NoMatchingCandidates, call, ci, rejected, badPasses, actualDecls); return; } } diff --git a/frontend/lib/resolution/resolution-error-classes-list.cpp b/frontend/lib/resolution/resolution-error-classes-list.cpp index efe9fbc7489f..da26a36a241c 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; @@ -1027,8 +1030,7 @@ void ErrorNoMatchingCandidates::write(ErrorWriterBase& wr) const { map 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()); + auto badPass = formalActuals[iterCount]; auto formalDecl = badPass.formal(); const uast::AstNode* actualExpr = nullptr; if (call && 0 <= badPass.actualIdx() && badPass.actualIdx() < call->numActuals()) { @@ -1044,19 +1046,34 @@ void ErrorNoMatchingCandidates::write(ErrorWriterBase& wr) const { } else if (formalDecl->isTupleDecl()) { formalName = "'" + buildTupleDeclName(formalDecl->toTupleDecl()) + "'"; } - + bool actualPrinted = false; 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() && actualExpr && actualExpr->isIdentifier()) { + auto offendingActual = actualDecls[iterCount]; + auto formalKind = badPass.formalType().kind(); + auto actualName = "'" + actualExpr->toIdentifier()->name().str() + "'"; + wr.message("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 }); } @@ -1094,6 +1111,7 @@ void ErrorNoMatchingCandidates::write(ErrorWriterBase& wr) const { } else if (formalReason == resolution::FAIL_NOT_EXACT_MATCH) { wr.message("The 'ref' intent requires the formal and actual types to match exactly."); } + iterCount++; } else { const char* reasonStr = nullptr; if (reason == resolution::FAIL_FORMAL_ACTUAL_MISMATCH) { From 4372a4130f94e06dae7ff8ce491b8eb68df3e215 Mon Sep 17 00:00:00 2001 From: Ahmad Rezaii Date: Wed, 8 Jan 2025 15:54:24 -0700 Subject: [PATCH 02/10] add note to error message output Signed-off-by: Ahmad Rezaii --- frontend/lib/resolution/Resolver.cpp | 11 +++++------ .../lib/resolution/resolution-error-classes-list.cpp | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/frontend/lib/resolution/Resolver.cpp b/frontend/lib/resolution/Resolver.cpp index 25fe8e69053d..4b26577ae9ce 100644 --- a/frontend/lib/resolution/Resolver.cpp +++ b/frontend/lib/resolution/Resolver.cpp @@ -1955,11 +1955,10 @@ void Resolver::handleResolvedCallPrintCandidates(ResolvedExpression& r, std::vector actualDecls; // check each rejected candidate for uninitialized actuals for (auto& candidate : rejected) { - auto reason = candidate.reason(); - if (reason == resolution::FAIL_CANNOT_PASS && - /* skip printing detailed info_ here because computing the formal-actual + if (candidate.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) { + candidate.formalReason() != resolution::FAIL_UNKNOWN_FORMAL_TYPE) { auto fn = candidate.initialForErr(); resolution::FormalActualMap fa(fn, ci); auto badPass = fa.byFormalIdx(candidate.formalIdx()); @@ -1970,8 +1969,8 @@ void Resolver::handleResolvedCallPrintCandidates(ResolvedExpression& r, badPass.actualIdx() < call->numActuals()) { actualExpr = call->actual(badPass.actualIdx()); } - // look for a definition point of the actual for error reporting of uninitialized vars - // typically in the case of bad split-initialization + // 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()) { diff --git a/frontend/lib/resolution/resolution-error-classes-list.cpp b/frontend/lib/resolution/resolution-error-classes-list.cpp index da26a36a241c..314844ba6f57 100644 --- a/frontend/lib/resolution/resolution-error-classes-list.cpp +++ b/frontend/lib/resolution/resolution-error-classes-list.cpp @@ -1057,7 +1057,7 @@ void ErrorNoMatchingCandidates::write(ErrorWriterBase& wr) const { auto offendingActual = actualDecls[iterCount]; auto formalKind = badPass.formalType().kind(); auto actualName = "'" + actualExpr->toIdentifier()->name().str() + "'"; - wr.message("The actual ", actualName, + 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:"); From 926011e1142e74d280da1461c4903dfaee6eb5ba Mon Sep 17 00:00:00 2001 From: Ahmad Rezaii Date: Wed, 8 Jan 2025 17:30:28 -0700 Subject: [PATCH 03/10] deduplicate some control flow logic Signed-off-by: Ahmad Rezaii --- frontend/lib/resolution/Resolver.cpp | 11 ++++++++--- .../lib/resolution/resolution-error-classes-list.cpp | 7 ++----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/frontend/lib/resolution/Resolver.cpp b/frontend/lib/resolution/Resolver.cpp index 4b26577ae9ce..e25720459044 100644 --- a/frontend/lib/resolution/Resolver.cpp +++ b/frontend/lib/resolution/Resolver.cpp @@ -1953,8 +1953,12 @@ void Resolver::handleResolvedCallPrintCandidates(ResolvedExpression& r, // 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 (auto& candidate : rejected) { + + for (size_t i = 0; i < rejected.size(); i++) { + auto& candidate = rejected[i]; if (candidate.reason() == resolution::FAIL_CANNOT_PASS && /* skip printing detailed info_ here because computing the formal-actual map will go poorly with an unknown formal. */ @@ -1962,7 +1966,7 @@ void Resolver::handleResolvedCallPrintCandidates(ResolvedExpression& r, auto fn = candidate.initialForErr(); resolution::FormalActualMap fa(fn, ci); auto badPass = fa.byFormalIdx(candidate.formalIdx()); - badPasses.push_back(badPass); + badPasses[i] =badPass; const uast::AstNode* actualExpr = nullptr; const uast::VarLikeDecl* actualDecl = nullptr; if (call && 0 <= badPass.actualIdx() && @@ -1979,9 +1983,10 @@ void Resolver::handleResolvedCallPrintCandidates(ResolvedExpression& r, actualDecl = var->toVarLikeDecl(); } } - actualDecls.push_back(actualDecl); + actualDecls[i] = actualDecl; } } + CHPL_ASSERT((badPasses.size() == rejected.size()) && (rejected.size() == actualDecls.size())); CHPL_REPORT(context, NoMatchingCandidates, call, ci, rejected, badPasses, actualDecls); return; } diff --git a/frontend/lib/resolution/resolution-error-classes-list.cpp b/frontend/lib/resolution/resolution-error-classes-list.cpp index 314844ba6f57..4ee7b1995153 100644 --- a/frontend/lib/resolution/resolution-error-classes-list.cpp +++ b/frontend/lib/resolution/resolution-error-classes-list.cpp @@ -1025,10 +1025,7 @@ 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(); auto badPass = formalActuals[iterCount]; auto formalDecl = badPass.formal(); @@ -1111,7 +1108,6 @@ void ErrorNoMatchingCandidates::write(ErrorWriterBase& wr) const { } else if (formalReason == resolution::FAIL_NOT_EXACT_MATCH) { wr.message("The 'ref' intent requires the formal and actual types to match exactly."); } - iterCount++; } else { const char* reasonStr = nullptr; if (reason == resolution::FAIL_FORMAL_ACTUAL_MISMATCH) { @@ -1135,6 +1131,7 @@ void ErrorNoMatchingCandidates::write(ErrorWriterBase& wr) const { } wr.code(candidate.idForErr()); } + iterCount++; } if (printCount < rejected.size()) { From d3948f4d80603752100a5a3252159d1876feba83 Mon Sep 17 00:00:00 2001 From: Ahmad Rezaii Date: Wed, 8 Jan 2025 17:35:40 -0700 Subject: [PATCH 04/10] fix some indentation Signed-off-by: Ahmad Rezaii --- frontend/lib/resolution/Resolver.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/frontend/lib/resolution/Resolver.cpp b/frontend/lib/resolution/Resolver.cpp index e25720459044..4ce4e30da067 100644 --- a/frontend/lib/resolution/Resolver.cpp +++ b/frontend/lib/resolution/Resolver.cpp @@ -1956,17 +1956,16 @@ void Resolver::handleResolvedCallPrintCandidates(ResolvedExpression& r, 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 printing detailed info_ here because computing the formal-actual - map will go poorly with an unknown formal. */ - candidate.formalReason() != resolution::FAIL_UNKNOWN_FORMAL_TYPE) { + /* 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) { auto fn = candidate.initialForErr(); resolution::FormalActualMap fa(fn, ci); auto badPass = fa.byFormalIdx(candidate.formalIdx()); - badPasses[i] =badPass; + badPasses[i] = badPass; const uast::AstNode* actualExpr = nullptr; const uast::VarLikeDecl* actualDecl = nullptr; if (call && 0 <= badPass.actualIdx() && From 1c8e5997f8efcdc8e08cf2b5ad0b92dd3ea312c7 Mon Sep 17 00:00:00 2001 From: Ahmad Rezaii Date: Wed, 8 Jan 2025 18:30:50 -0700 Subject: [PATCH 05/10] touch up error output, formatting Signed-off-by: Ahmad Rezaii --- frontend/lib/resolution/resolution-error-classes-list.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/lib/resolution/resolution-error-classes-list.cpp b/frontend/lib/resolution/resolution-error-classes-list.cpp index 4ee7b1995153..bd8cc61a76a2 100644 --- a/frontend/lib/resolution/resolution-error-classes-list.cpp +++ b/frontend/lib/resolution/resolution-error-classes-list.cpp @@ -1055,9 +1055,9 @@ void ErrorNoMatchingCandidates::write(ErrorWriterBase& wr) const { 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:"); + " 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.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 ", From 7ab602b7eba92930b28fad0a62153550aae65b90 Mon Sep 17 00:00:00 2001 From: Ahmad Rezaii Date: Wed, 8 Jan 2025 19:46:44 -0700 Subject: [PATCH 06/10] small refactor to extract helper, condition updates Signed-off-by: Ahmad Rezaii --- frontend/lib/resolution/Resolver.cpp | 86 +++++++++++-------- .../resolution-error-classes-list.cpp | 7 +- 2 files changed, 53 insertions(+), 40 deletions(-) diff --git a/frontend/lib/resolution/Resolver.cpp b/frontend/lib/resolution/Resolver.cpp index 4ce4e30da067..928039b16311 100644 --- a/frontend/lib/resolution/Resolver.cpp +++ b/frontend/lib/resolution/Resolver.cpp @@ -944,6 +944,53 @@ 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) { + // There were candidates but we threw them out. 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 printing detailed info_ here because computing the formal-actual + map 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() && + badPass.actualIdx() < call->numActuals()) { + actualExpr = call->actual(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) { @@ -1949,44 +1996,7 @@ 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. - 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 printing detailed info_ here because computing the formal-actual - map 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() && - badPass.actualIdx() < call->numActuals()) { - actualExpr = call->actual(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); + handleRejectedCandidates(context, byPostorder, rejected, ci, call); return; } } diff --git a/frontend/lib/resolution/resolution-error-classes-list.cpp b/frontend/lib/resolution/resolution-error-classes-list.cpp index bd8cc61a76a2..5bc5c4a1512c 100644 --- a/frontend/lib/resolution/resolution-error-classes-list.cpp +++ b/frontend/lib/resolution/resolution-error-classes-list.cpp @@ -1044,14 +1044,17 @@ void ErrorNoMatchingCandidates::write(ErrorWriterBase& wr) const { 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() && actualExpr && actualExpr->isIdentifier()) { - auto offendingActual = actualDecls[iterCount]; + } 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, From 7cd38687a60b7a3ad1e9d6187701457d7fd6e236 Mon Sep 17 00:00:00 2001 From: Ahmad Rezaii Date: Wed, 8 Jan 2025 19:52:54 -0700 Subject: [PATCH 07/10] update comment wording Signed-off-by: Ahmad Rezaii --- frontend/lib/resolution/Resolver.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/frontend/lib/resolution/Resolver.cpp b/frontend/lib/resolution/Resolver.cpp index 928039b16311..1dceb2e7bddc 100644 --- a/frontend/lib/resolution/Resolver.cpp +++ b/frontend/lib/resolution/Resolver.cpp @@ -951,8 +951,8 @@ handleRejectedCandidates(Context* context, std::vector& rejected, const resolution::CallInfo& ci, const uast::Call*& call) { - // There were candidates but we threw them out. We can issue a nicer - // error explaining why each candidate was rejected. + // 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()); @@ -961,8 +961,8 @@ handleRejectedCandidates(Context* context, for (size_t i = 0; i < rejected.size(); i++) { auto &candidate = rejected[i]; if (candidate.reason() == resolution::FAIL_CANNOT_PASS && - /* skip printing detailed info_ here because computing the formal-actual - map will go poorly with an unknown formal. */ + /* 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); @@ -1996,6 +1996,7 @@ void Resolver::handleResolvedCallPrintCandidates(ResolvedExpression& r, } if (!rejected.empty()) { + // There were candidates but we threw them out. Report on those. handleRejectedCandidates(context, byPostorder, rejected, ci, call); return; } From 521328816216a1dbe76614533ef03cd163ea36a5 Mon Sep 17 00:00:00 2001 From: Ahmad Rezaii Date: Tue, 14 Jan 2025 12:02:57 -0700 Subject: [PATCH 08/10] pass adjusted list of Actual Ast nodes Signed-off-by: Ahmad Rezaii --- frontend/lib/resolution/Resolver.cpp | 27 ++++++++++++++++----------- frontend/lib/resolution/Resolver.h | 4 +++- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/frontend/lib/resolution/Resolver.cpp b/frontend/lib/resolution/Resolver.cpp index 1dceb2e7bddc..0ed82554790b 100644 --- a/frontend/lib/resolution/Resolver.cpp +++ b/frontend/lib/resolution/Resolver.cpp @@ -950,7 +950,8 @@ handleRejectedCandidates(Context* context, ResolutionResultByPostorderID& byPostorder, std::vector& rejected, const resolution::CallInfo& ci, - const uast::Call*& call) { + 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; @@ -971,8 +972,8 @@ handleRejectedCandidates(Context* context, const uast::AstNode *actualExpr = nullptr; const uast::VarLikeDecl *actualDecl = nullptr; if (call && 0 <= badPass.actualIdx() && - badPass.actualIdx() < call->numActuals()) { - actualExpr = call->actual(badPass.actualIdx()); + 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 @@ -1977,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()); @@ -1997,7 +1999,7 @@ void Resolver::handleResolvedCallPrintCandidates(ResolvedExpression& r, if (!rejected.empty()) { // There were candidates but we threw them out. Report on those. - handleRejectedCandidates(context, byPostorder, rejected, ci, call); + handleRejectedCandidates(context, byPostorder, rejected, ci, call, actualAsts); return; } } @@ -2343,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. @@ -2363,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 @@ -2519,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 = @@ -4243,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) { @@ -4513,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); From 720a7eb5f4b1163a5689f1588e8d7eddc5c91d01 Mon Sep 17 00:00:00 2001 From: Ahmad Rezaii Date: Tue, 14 Jan 2025 12:25:26 -0700 Subject: [PATCH 09/10] fix size_t int compare Signed-off-by: Ahmad Rezaii --- frontend/lib/resolution/Resolver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/lib/resolution/Resolver.cpp b/frontend/lib/resolution/Resolver.cpp index 0ed82554790b..44a58a8b05aa 100644 --- a/frontend/lib/resolution/Resolver.cpp +++ b/frontend/lib/resolution/Resolver.cpp @@ -972,7 +972,7 @@ handleRejectedCandidates(Context* context, const uast::AstNode *actualExpr = nullptr; const uast::VarLikeDecl *actualDecl = nullptr; if (call && 0 <= badPass.actualIdx() && - badPass.actualIdx() < actualAsts.size()) { + (size_t)badPass.actualIdx() < actualAsts.size()) { actualExpr = actualAsts[badPass.actualIdx()]; } // look for a definition point of the actual for error reporting of From a2bd8814f9732e1040c54cd5e4d3d4edd1e12caf Mon Sep 17 00:00:00 2001 From: Ahmad Rezaii Date: Wed, 15 Jan 2025 22:17:51 -0700 Subject: [PATCH 10/10] add two tests for nomatchingcandidates Signed-off-by: Ahmad Rezaii --- .../resolution/noMatchingCandidates.1.good | 5 +++ .../resolution/noMatchingCandidates.2.good | 45 +++++++++++++++++++ .../resolution/noMatchingCandidates.chpl | 35 +++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 test/errors/resolution/noMatchingCandidates.1.good create mode 100644 test/errors/resolution/noMatchingCandidates.2.good create mode 100644 test/errors/resolution/noMatchingCandidates.chpl 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