Skip to content

Commit e01fa1c

Browse files
committed
[ConstraintSystem] Handle ambiguities caused by requirement failures
Aggregate all requirement failures (regardless of kind) that belong to the same locator and diagnose them as an ambiguity (if there is more than one overload) or as the singular failure if all solutions point to the same overload. (cherry picked from commit c7ba055)
1 parent 6dc20a7 commit e01fa1c

File tree

2 files changed

+162
-2
lines changed

2 files changed

+162
-2
lines changed

lib/Sema/ConstraintSystem.cpp

Lines changed: 127 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4373,6 +4373,67 @@ static bool diagnoseAmbiguityWithContextualType(
43734373
return true;
43744374
}
43754375

4376+
/// Diagnose problems with generic requirement fixes that are anchored on
4377+
/// one callee location. The list could contain different kinds of fixes
4378+
/// i.e. missing protocol conformances at different positions,
4379+
/// same-type requirement mismatches, etc.
4380+
static bool diagnoseAmbiguityWithGenericRequirements(
4381+
ConstraintSystem &cs,
4382+
ArrayRef<std::pair<const Solution *, const ConstraintFix *>> aggregate) {
4383+
// If all of the fixes point to the same overload choice,
4384+
// we can diagnose this an a single error.
4385+
bool hasNonDeclOverloads = false;
4386+
4387+
llvm::SmallSet<ValueDecl *, 4> overloadChoices;
4388+
for (const auto &entry : aggregate) {
4389+
const auto &solution = *entry.first;
4390+
auto *calleeLocator = solution.getCalleeLocator(entry.second->getLocator());
4391+
4392+
if (auto overload = solution.getOverloadChoiceIfAvailable(calleeLocator)) {
4393+
if (auto *D = overload->choice.getDeclOrNull()) {
4394+
overloadChoices.insert(D);
4395+
} else {
4396+
hasNonDeclOverloads = true;
4397+
}
4398+
}
4399+
}
4400+
4401+
auto &primaryFix = aggregate.front();
4402+
{
4403+
if (overloadChoices.size() > 0) {
4404+
// Some of the choices are non-declaration,
4405+
// let's delegate that to ambiguity diagnostics.
4406+
if (hasNonDeclOverloads)
4407+
return false;
4408+
4409+
if (overloadChoices.size() == 1)
4410+
return primaryFix.second->diagnose(*primaryFix.first);
4411+
4412+
// fall through to the tailored ambiguity diagnostic.
4413+
} else {
4414+
// If there are no overload choices it means that
4415+
// the issue is with types, delegate that to the primary fix.
4416+
return primaryFix.second->diagnoseForAmbiguity(aggregate);
4417+
}
4418+
}
4419+
4420+
// Produce "no exact matches" diagnostic.
4421+
auto &ctx = cs.getASTContext();
4422+
auto *choice = *overloadChoices.begin();
4423+
auto name = choice->getName();
4424+
4425+
ctx.Diags.diagnose(getLoc(primaryFix.second->getLocator()->getAnchor()),
4426+
diag::no_overloads_match_exactly_in_call,
4427+
/*isApplication=*/false, choice->getDescriptiveKind(),
4428+
name.isSpecial(), name.getBaseName());
4429+
4430+
for (const auto &entry : aggregate) {
4431+
entry.second->diagnose(*entry.first, /*asNote=*/true);
4432+
}
4433+
4434+
return true;
4435+
}
4436+
43764437
static bool diagnoseAmbiguity(
43774438
ConstraintSystem &cs, const SolutionDiff::OverloadDiff &ambiguity,
43784439
ArrayRef<std::pair<const Solution *, const ConstraintFix *>> aggregateFix,
@@ -4796,14 +4857,78 @@ bool ConstraintSystem::diagnoseAmbiguityWithFixes(
47964857
// overload choices.
47974858
fixes.set_subtract(consideredFixes);
47984859

4860+
// Aggregate all requirement fixes that belong to the same callee
4861+
// and attempt to diagnose possible ambiguities.
4862+
{
4863+
auto isResultBuilderRef = [&](ASTNode node) {
4864+
auto *UDE = getAsExpr<UnresolvedDotExpr>(node);
4865+
if (!UDE)
4866+
return false;
4867+
4868+
auto &ctx = getASTContext();
4869+
return UDE->isImplicit() &&
4870+
UDE->getName().compare(DeclNameRef(ctx.Id_buildBlock)) == 0;
4871+
};
4872+
4873+
llvm::MapVector<SourceLoc,
4874+
SmallVector<FixInContext, 4>>
4875+
builderFixes;
4876+
4877+
llvm::MapVector<ConstraintLocator *, SmallVector<FixInContext, 4>>
4878+
requirementFixes;
4879+
4880+
// TODO(diagnostics): This approach doesn't work for synthesized code
4881+
// because i.e. result builders would inject a new `buildBlock` or
4882+
// `buildExpression` for every kind of builder. We need to come up
4883+
// with the strategy to unify locators in such cases.
4884+
for (const auto &entry : fixes) {
4885+
auto *fix = entry.second;
4886+
if (!fix->getLocator()->isLastElement<LocatorPathElt::AnyRequirement>())
4887+
continue;
4888+
4889+
auto *calleeLoc = entry.first->getCalleeLocator(fix->getLocator());
4890+
4891+
if (isResultBuilderRef(calleeLoc->getAnchor())) {
4892+
auto *anchor = castToExpr<Expr>(calleeLoc->getAnchor());
4893+
builderFixes[anchor->getLoc()].push_back(entry);
4894+
} else {
4895+
requirementFixes[calleeLoc].push_back(entry);
4896+
}
4897+
}
4898+
4899+
SmallVector<FixInContext, 4> diagnosedFixes;
4900+
4901+
for (auto &entry : builderFixes) {
4902+
auto &aggregate = entry.second;
4903+
4904+
if (diagnoseAmbiguityWithGenericRequirements(*this, aggregate))
4905+
diagnosedFixes.append(aggregate.begin(), aggregate.end());
4906+
}
4907+
4908+
for (auto &entry : requirementFixes) {
4909+
auto &aggregate = entry.second;
4910+
4911+
// Ambiguity only if all of the solutions have a requirement
4912+
// fix at the given location.
4913+
if (aggregate.size() != solutions.size())
4914+
continue;
4915+
4916+
if (diagnoseAmbiguityWithGenericRequirements(*this, aggregate))
4917+
diagnosedFixes.append(aggregate.begin(), aggregate.end());
4918+
}
4919+
4920+
diagnosed |= !diagnosedFixes.empty();
4921+
// Remove any diagnosed fixes.
4922+
fixes.set_subtract(diagnosedFixes);
4923+
}
4924+
47994925
llvm::MapVector<std::pair<FixKind, ConstraintLocator *>,
48004926
SmallVector<FixInContext, 4>>
48014927
fixesByKind;
48024928

48034929
for (const auto &entry : fixes) {
48044930
const auto *fix = entry.second;
4805-
fixesByKind[{fix->getKind(), fix->getLocator()}].push_back(
4806-
{entry.first, fix});
4931+
fixesByKind[{fix->getKind(), fix->getLocator()}].push_back(entry);
48074932
}
48084933

48094934
// If leftover fix is contained in all of the solutions let's

test/Constraints/generics.swift

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -996,3 +996,38 @@ do {
996996
// expected-error@-3 {{local function 'foo' requires the types 'Set<Int>.Type.Element' and 'Array<String>.Type.Element' be equivalent}}
997997
// expected-note@-4 2 {{only concrete types such as structs, enums and classes can conform to protocols}}
998998
}
999+
1000+
// https://github.com/apple/swift/issues/56173
1001+
protocol P_56173 {
1002+
associatedtype Element
1003+
}
1004+
protocol Q_56173 {
1005+
associatedtype Element
1006+
}
1007+
1008+
func test_requirement_failures_in_ambiguous_context() {
1009+
struct A : P_56173 {
1010+
typealias Element = String
1011+
}
1012+
struct B : Q_56173 {
1013+
typealias Element = Int
1014+
}
1015+
1016+
func f1<T: Equatable>(_: T, _: T) {} // expected-note {{where 'T' = 'A'}}
1017+
1018+
f1(A(), B()) // expected-error {{local function 'f1' requires that 'A' conform to 'Equatable'}}
1019+
1020+
func f2<T: P_56173, U: P_56173>(_: T, _: U) {}
1021+
// expected-note@-1 {{candidate requires that 'B' conform to 'P_56173' (requirement specified as 'U' : 'P_56173')}}
1022+
func f2<T: Q_56173, U: Q_56173>(_: T, _: U) {}
1023+
// expected-note@-1 {{candidate requires that 'A' conform to 'Q_56173' (requirement specified as 'T' : 'Q_56173')}}
1024+
1025+
f2(A(), B()) // expected-error {{no exact matches in call to local function 'f2'}}
1026+
1027+
func f3<T: P_56173>(_: T) where T.Element == Int {}
1028+
// expected-note@-1 {{candidate requires that the types 'A.Element' (aka 'String') and 'Int' be equivalent (requirement specified as 'T.Element' == 'Int')}}
1029+
func f3<U: Q_56173>(_: U) where U.Element == String {}
1030+
// expected-note@-1 {{candidate requires that 'A' conform to 'Q_56173' (requirement specified as 'U' : 'Q_56173')}}
1031+
1032+
f3(A()) // expected-error {{no exact matches in call to local function 'f3'}}
1033+
}

0 commit comments

Comments
 (0)