Skip to content

[SR-12689] Diagnose arg mismatch with exact same fixes across multiple solutions #31359

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,10 @@ ERROR(no_candidates_match_result_type,none,
"no '%0' candidates produce the expected contextual result type %1",
(StringRef, Type))

ERROR(no_candidates_match_argument_type,none,
"no '%0' candidates produce the expected parameter type %1",
(StringRef, Type))

ERROR(cannot_infer_closure_type,none,
"unable to infer closure type in the current context", ())
ERROR(cannot_infer_closure_result_type,none,
Expand Down
26 changes: 26 additions & 0 deletions lib/Sema/CSFix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,32 @@ bool AllowFunctionTypeMismatch::coalesceAndDiagnose(
return failure.diagnose(asNote);
}

bool AllowFunctionTypeMismatch::diagnoseForAmbiguity(CommonFixesArray commonFixes) const {
const Solution *solution;
const ConstraintFix *fix;
std::tie(solution, fix) = commonFixes.front();
auto applyInfo = solution->getFunctionArgApplyInfo(fix->getLocator());
if (!applyInfo)
return false;

if (auto *ODRE = dyn_cast<OverloadedDeclRefExpr>(applyInfo->getArgExpr())) {
auto &DE = getConstraintSystem().getASTContext().Diags;

DE.diagnose(ODRE->getLoc(), diag::no_candidates_match_argument_type,
ODRE->getDecls()[0]->getName().getBaseName().userFacingName(),
applyInfo->getParamType());

for (auto decl : ODRE->getDecls()) {
if (decl->getLoc().isValid())
DE.diagnose(decl->getLoc(), diag::found_candidate_type,
decl->getInterfaceType());
}
return true;
}

return false;
}

bool AllowFunctionTypeMismatch::diagnose(const Solution &solution,
bool asNote) const {
return coalesceAndDiagnose(solution, {}, asNote);
Expand Down
4 changes: 3 additions & 1 deletion lib/Sema/CSFix.h
Original file line number Diff line number Diff line change
Expand Up @@ -1062,7 +1062,9 @@ class AllowFunctionTypeMismatch final : public ContextualMismatch {
bool coalesceAndDiagnose(const Solution &solution,
ArrayRef<ConstraintFix *> secondaryFixes,
bool asNote = false) const override;


bool diagnoseForAmbiguity(CommonFixesArray commonFixes) const override;

bool diagnose(const Solution &solution, bool asNote = false) const override;
};

Expand Down
257 changes: 150 additions & 107 deletions lib/Sema/ConstraintSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2958,6 +2958,8 @@ bool ConstraintSystem::diagnoseAmbiguityWithFixes(
// Collect aggregated fixes from all solutions
using LocatorAndKind = std::pair<ConstraintLocator *, FixKind>;
using SolutionAndFix = std::pair<const Solution *, const ConstraintFix *>;
using AggregatedEntry = std::pair<LocatorAndKind,
llvm::SmallVector<SolutionAndFix, 4>>;
llvm::SmallMapVector<LocatorAndKind, llvm::SmallVector<SolutionAndFix, 4>, 4>
aggregatedFixes;
for (const auto &solution : solutions) {
Expand All @@ -2966,134 +2968,175 @@ bool ConstraintSystem::diagnoseAmbiguityWithFixes(
aggregatedFixes[key].emplace_back(&solution, fix);
}
}

auto getAggregatedFixAnchor = [](const AggregatedEntry &aggregatedFix) {
auto *locator = aggregatedFix.first.first;
auto anchor = locator->getAnchor();
// Assignment failures are all about the source expression, because
// they treat destination as a contextual type.
if (auto *assignExpr = getAsExpr<AssignExpr>(anchor))
anchor = assignExpr->getSrc();

if (auto *callExpr = getAsExpr<CallExpr>(anchor)) {
if (!isa<TypeExpr>(callExpr->getDirectCallee()))
anchor = callExpr->getDirectCallee();
} else if (auto *applyExpr = getAsExpr<ApplyExpr>(anchor)) {
anchor = applyExpr->getFn();
}
return anchor;
};

SmallPtrSet<SolutionDiff::OverloadDiff* , 4> ambiguosOverloads;

// If there is an overload difference, let's see if there's a common callee
// locator for all of the fixes.
auto ambiguousOverload = llvm::find_if(solutionDiff.overloads,
[&](const auto &overloadDiff) {
return llvm::all_of(aggregatedFixes, [&](const auto &aggregatedFix) {
auto *locator = aggregatedFix.first.first;
auto anchor = locator->getAnchor();
// Assignment failures are all about the source expression, because
// they treat destination as a contextual type.
if (auto *assignExpr = getAsExpr<AssignExpr>(anchor))
anchor = assignExpr->getSrc();

if (auto *callExpr = getAsExpr<CallExpr>(anchor)) {
if (!isa<TypeExpr>(callExpr->getDirectCallee()))
anchor = callExpr->getDirectCallee();
} else if (auto *applyExpr = getAsExpr<ApplyExpr>(anchor)) {
anchor = applyExpr->getFn();
}

return overloadDiff.locator->getAnchor() == anchor;
bool diagnosed = false;
for (const AggregatedEntry &aggregatedFix : aggregatedFixes) {
auto ambiguousOverload =
llvm::find_if(solutionDiff.overloads, [&](const auto &overloadDiff) {
return overloadDiff.locator->getAnchor() ==
getAggregatedFixAnchor(aggregatedFix);
});
});

// If we didn't find an ambiguous overload, diagnose the common fixes.
if (ambiguousOverload == solutionDiff.overloads.end()) {
bool diagnosed = false;
for (auto fixes: aggregatedFixes) {
if (ambiguousOverload == solutionDiff.overloads.end()) {
// A common fix must appear in all solutions
auto &commonFixes = fixes.second;
if (commonFixes.size() != solutions.size()) continue;
auto &commonFixes = aggregatedFix.second;
if (commonFixes.size() != solutions.size())
continue;

auto *firstFix = commonFixes.front().second;
diagnosed |= firstFix->diagnoseForAmbiguity(commonFixes);
} else {
ambiguosOverloads.insert(ambiguousOverload);
}
return diagnosed;
}

auto *commonCalleeLocator = ambiguousOverload->locator;
auto *decl = ambiguousOverload->choices.front().getDecl();
assert(solverState);
// If any of the non-overloaded common fixes appear in all solutions
// and can be diagnosed by the diagnoseForAmbiguity implementation,
// we diagnose and be done.
if (diagnosed)
return true;

bool diagnosed = true;
{
DiagnosticTransaction transaction(getASTContext().Diags);
// Otherwise let's try to fall back to overload common anchor
// diagnostic if possible.
if (ambiguosOverloads.empty())
return false;

auto commonAnchor = commonCalleeLocator->getAnchor();
if (auto *callExpr = getAsExpr<CallExpr>(commonAnchor))
commonAnchor = callExpr->getDirectCallee();
auto &DE = getASTContext().Diags;
const auto name = decl->getName();

// Emit an error message for the ambiguity.
if (aggregatedFixes.size() == 1 &&
aggregatedFixes.front().first.first->isForContextualType()) {
auto anchor = aggregatedFixes.front().first.first->getAnchor();
auto baseName = name.getBaseName();
DE.diagnose(getLoc(commonAnchor), diag::no_candidates_match_result_type,
baseName.userFacingName(), getContextualType(anchor));
} else if (name.isOperator()) {
auto *anchor = castToExpr(commonCalleeLocator->getAnchor());

// If operator is "applied" e.g. `1 + 2` there are tailored
// diagnostics in case of ambiguity, but if it's referenced
// e.g. `arr.sort(by: <)` it's better to produce generic error
// and a note per candidate.
if (auto *parentExpr = getParentExpr(anchor)) {
if (isa<ApplyExpr>(parentExpr)) {
diagnoseOperatorAmbiguity(*this, name.getBaseIdentifier(), solutions,
commonCalleeLocator);
return true;
diagnosed = true;
for (auto ambiguousOverload : ambiguosOverloads) {
auto *commonCalleeLocator = ambiguousOverload->locator;
auto *decl = ambiguousOverload->choices.front().getDecl();
assert(solverState);

{
DiagnosticTransaction transaction(getASTContext().Diags);

auto commonAnchor = commonCalleeLocator->getAnchor();
if (auto *callExpr = getAsExpr<CallExpr>(commonAnchor))
commonAnchor = callExpr->getDirectCallee();
auto &DE = getASTContext().Diags;
const auto name = decl->getName();

// Emit an error message for the ambiguity.
if (aggregatedFixes.size() == 1 &&
aggregatedFixes.front().first.first->isForContextualType()) {
auto anchor = aggregatedFixes.front().first.first->getAnchor();
auto baseName = name.getBaseName();
DE.diagnose(getLoc(commonAnchor), diag::no_candidates_match_result_type,
baseName.userFacingName(), getContextualType(anchor));
} else if (name.isOperator()) {
auto *anchor = castToExpr(commonCalleeLocator->getAnchor());

// If operator is "applied" e.g. `1 + 2` there are tailored
// diagnostics in case of ambiguity, but if it's referenced
// e.g. `arr.sort(by: <)` it's better to produce generic error
// and a note per candidate.
if (auto *parentExpr = getParentExpr(anchor)) {
if (isa<ApplyExpr>(parentExpr)) {
diagnoseOperatorAmbiguity(*this, name.getBaseIdentifier(),
solutions, commonCalleeLocator);
return true;
}
}
}

DE.diagnose(anchor->getLoc(), diag::no_overloads_match_exactly_in_call,
/*isApplication=*/false, decl->getDescriptiveKind(),
name.isSpecial(), name.getBaseName());
} else {
bool isApplication =
llvm::any_of(ArgumentInfos, [&](const auto &argInfo) {
return argInfo.first->getAnchor() == commonAnchor;
});
DE.diagnose(anchor->getLoc(), diag::no_overloads_match_exactly_in_call,
/*isApplication=*/false, decl->getDescriptiveKind(),
name.isSpecial(), name.getBaseName());
} else {
bool isApplication =
llvm::any_of(ArgumentInfos, [&](const auto &argInfo) {
return argInfo.first->getAnchor() == commonAnchor;
});

DE.diagnose(getLoc(commonAnchor),
diag::no_overloads_match_exactly_in_call, isApplication,
decl->getDescriptiveKind(), name.isSpecial(),
name.getBaseName());
}
DE.diagnose(getLoc(commonAnchor),
diag::no_overloads_match_exactly_in_call, isApplication,
decl->getDescriptiveKind(), name.isSpecial(),
name.getBaseName());
}

// Produce candidate notes
SmallPtrSet<ValueDecl *, 4> distinctChoices;
llvm::SmallSet<CanType, 4> candidateTypes;
for (const auto &solution: solutions) {
auto overload = solution.getOverloadChoice(commonCalleeLocator);
auto *decl = overload.choice.getDecl();
auto type = solution.simplifyType(overload.openedType);
// Skip if we've already produced a note for this overload
if (!distinctChoices.insert(decl).second)
continue;
// Produce candidate notes
SmallPtrSet<ValueDecl *, 4> distinctChoices;
llvm::SmallSet<CanType, 4> candidateTypes;
for (const auto &solution : solutions) {
auto overload = solution.getOverloadChoice(commonCalleeLocator);
auto *decl = overload.choice.getDecl();
auto type = solution.simplifyType(overload.openedType);
// Skip if we've already produced a note for this overload
if (!distinctChoices.insert(decl).second)
continue;

if (solution.Fixes.size() == 1) {
diagnosed &=
solution.Fixes.front()->diagnose(solution, /*asNote*/ true);
} else if (llvm::all_of(solution.Fixes,
[&](ConstraintFix *fix) {
return fix->getLocator()
->findLast<LocatorPathElt::ApplyArgument>().hasValue();
})) {
// All fixes have to do with arguments, so let's show the parameter lists.
auto *fn = type->getAs<AnyFunctionType>();
assert(fn);
DE.diagnose(decl->getLoc(),
diag::candidate_partial_match,
fn->getParamListAsString(fn->getParams()));
} else {
// Emit a general "found candidate" note
if (decl->getLoc().isInvalid()) {
if (candidateTypes.insert(type->getCanonicalType()).second)
DE.diagnose(getLoc(commonAnchor), diag::found_candidate_type, type);
if (solution.Fixes.size() == 1) {
diagnosed &=
solution.Fixes.front()->diagnose(solution, /*asNote*/ true);
} else {
DE.diagnose(decl->getLoc(), diag::found_candidate);
auto emitGeneralFoundCandidate = [&]() {
// Emit a general "found candidate" note
if (decl->getLoc().isInvalid()) {
if (candidateTypes.insert(type->getCanonicalType()).second)
DE.diagnose(getLoc(commonAnchor), diag::found_candidate_type,
type);
} else {
DE.diagnose(decl->getLoc(), diag::found_candidate);
}
};

if (llvm::all_of(solution.Fixes, [](ConstraintFix *fix) {
return fix->getLocator()
->findLast<LocatorPathElt::ApplyArgument>()
.hasValue();
})) {

// All fixes have to do with arguments, so let's show the parameter
// lists.
auto *fn = type->getAs<AnyFunctionType>();
assert(fn);

if (fn->getNumParams() == 1) {
// For single param functions if we have a re-label fix,
// let's prefer note that instead of a general mismatch.
auto relabelFix =
llvm::find_if(solution.Fixes, [](ConstraintFix *fix) {
return fix->getKind() == FixKind::RelabelArguments;
});
if (relabelFix != solution.Fixes.end()) {
(*relabelFix)->diagnose(solution, /*asNote*/ true);
} else {
emitGeneralFoundCandidate();
}
continue;
}

DE.diagnose(decl->getLoc(), diag::candidate_partial_match,
fn->getParamListAsString(fn->getParams()));

} else {
emitGeneralFoundCandidate();
}
}
}
}

// If not all of the fixes produced a note, we can't diagnose this.
if (!diagnosed)
transaction.abort();
// If not all of the fixes produced a note, we can't diagnose this.
if (!diagnosed)
transaction.abort();
}
}

return diagnosed;
Expand Down
6 changes: 2 additions & 4 deletions test/Constraints/diagnostics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -496,10 +496,8 @@ enum Color {

static func rainbow() -> Color {}

static func overload(a : Int) -> Color {} // expected-note {{incorrect labels for candidate (have: '(_:)', expected: '(a:)')}}
// expected-note@-1 {{candidate has partially matching parameter list (a: Int)}}
static func overload(b : Int) -> Color {} // expected-note {{incorrect labels for candidate (have: '(_:)', expected: '(b:)')}}
// expected-note@-1 {{candidate has partially matching parameter list (b: Int)}}
static func overload(a : Int) -> Color {} // expected-note 2 {{incorrect labels for candidate (have: '(_:)', expected: '(a:)')}}
static func overload(b : Int) -> Color {} // expected-note 2 {{incorrect labels for candidate (have: '(_:)', expected: '(b:)')}}

static func frob(_ a : Int, b : inout Int) -> Color {}
static var svar: Color { return .Red }
Expand Down
8 changes: 4 additions & 4 deletions test/Constraints/super_constructor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,15 @@ class B {
init() {
}

init(x:Int) { // expected-note{{candidate has partially matching parameter list (x: Int)}}
init(x:Int) { // expected-note{{incorrect labels for candidate (have: '(_:)', expected: '(x:)')}}
}

init(a:UnicodeScalar) { // expected-note {{candidate has partially matching parameter list (a: UnicodeScalar)}}
init(a:UnicodeScalar) { // expected-note {{incorrect labels for candidate (have: '(_:)', expected: '(a:)')}}
}
init(b:UnicodeScalar) { // expected-note{{candidate has partially matching parameter list (b: UnicodeScalar)}}
init(b:UnicodeScalar) { // expected-note{{incorrect labels for candidate (have: '(_:)', expected: '(b:)')}}
}

init(z:Float) { // expected-note{{candidate has partially matching parameter list (z: Float)}}
init(z:Float) { // expected-note {{incorrect labels for candidate (have: '(_:)', expected: '(z:)')}}
super.init() // expected-error{{'super' members cannot be referenced in a root class}}
}
}
Expand Down
Loading