Skip to content

Commit 16fc311

Browse files
committed
[ConstraintSystem] Diagnose ambiguities related to solutions with fixes
If all of the solutions in the set have a single fix, which points to the common anchor, attempt to diagnose the failure as an ambiguity with a list of candidates and their related problems as notes. Having richer message like that helps to understand why something is ambiguous e.g. if there are two overloads, one requires conformance to some protocol and another has a same-type requirement on some type, but neither matched exactly, having both candidates in the diagnostic message with associated errors, instead of simplify pointing to related declarations, helps tremendously.
1 parent eefbb15 commit 16fc311

File tree

5 files changed

+124
-14
lines changed

5 files changed

+124
-14
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ NOTE(extended_type_declared_here,none,
5858

5959
ERROR(ambiguous_member_overload_set,none,
6060
"ambiguous reference to member %0", (DeclName))
61+
ERROR(ambiguous_reference_to_decl,none,
62+
"ambiguous reference to %0 %1", (DescriptiveDeclKind, DeclName))
6163

6264
ERROR(ambiguous_subscript,none,
6365
"ambiguous subscript with base type %0 and index type %1",

lib/Sema/CSDiag.cpp

Lines changed: 98 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,20 @@
1414
//
1515
//===----------------------------------------------------------------------===//
1616

17-
#include "ConstraintSystem.h"
1817
#include "CSDiag.h"
1918
#include "CalleeCandidateInfo.h"
19+
#include "ConstraintSystem.h"
2020
#include "MiscDiagnostics.h"
2121
#include "TypeCheckAvailability.h"
2222
#include "TypoCorrection.h"
2323
#include "swift/AST/ASTWalker.h"
24+
#include "swift/AST/DiagnosticEngine.h"
2425
#include "swift/AST/GenericEnvironment.h"
2526
#include "swift/AST/Initializer.h"
2627
#include "swift/AST/ParameterList.h"
2728
#include "swift/AST/ProtocolConformance.h"
28-
#include "swift/AST/TypeWalker.h"
2929
#include "swift/AST/TypeMatcher.h"
30+
#include "swift/AST/TypeWalker.h"
3031
#include "swift/Basic/Defer.h"
3132
#include "swift/Basic/StringExtras.h"
3233
#include "llvm/ADT/DenseSet.h"
@@ -455,9 +456,91 @@ tryDiagnoseTrailingClosureAmbiguity(TypeChecker &tc,
455456
return true;
456457
}
457458

458-
static bool diagnoseAmbiguity(ConstraintSystem &cs,
459-
ArrayRef<Solution> solutions,
460-
Expr *expr) {
459+
bool ConstraintSystem::diagnoseAmbiguityWithFixes(
460+
Expr *expr, ArrayRef<Solution> solutions) {
461+
if (solutions.empty())
462+
return false;
463+
464+
auto getOverloadDecl = [&](SelectedOverload &overload) -> ValueDecl * {
465+
auto &choice = overload.choice;
466+
return choice.isDecl() ? choice.getDecl() : nullptr;
467+
};
468+
469+
// Problems related to fixes forming ambiguous solution set
470+
// could only be diagnosed (at the moment), if all of the fixes
471+
// are attached to the same anchor, which means they fix
472+
// different overloads of the same declaration.
473+
Expr *commonAnchor = nullptr;
474+
SmallPtrSet<ValueDecl *, 4> distinctChoices;
475+
SmallVector<std::pair<const Solution *, const ConstraintFix *>, 4>
476+
viableSolutions;
477+
478+
bool diagnosable = llvm::all_of(solutions, [&](const Solution &solution) {
479+
ArrayRef<ConstraintFix *> fixes = solution.Fixes;
480+
481+
// Currently only support a single fix in a solution,
482+
// but ultimately should be able to deal with multiple.
483+
if (fixes.size() != 1)
484+
return false;
485+
486+
const auto *fix = fixes.front();
487+
if (commonAnchor && commonAnchor != fix->getAnchor())
488+
return false;
489+
490+
commonAnchor = fix->getAnchor();
491+
492+
SmallVector<SelectedOverload, 2> overloads;
493+
solution.getOverloadChoices(commonAnchor, overloads);
494+
// There is unfortunately no way, at the moment, to figure out
495+
// what declaration the fix is attached to, so we have to make
496+
// sure that there is only one declaration associated with common
497+
// anchor to be sure that the right problem is being diagnosed.
498+
if (overloads.size() != 1)
499+
return false;
500+
501+
auto *decl = getOverloadDecl(overloads.front());
502+
if (!decl)
503+
return false;
504+
505+
// If this declaration is distinct, let's record this solution
506+
// as viable, otherwise we'd produce the same diagnostic multiple
507+
// times, which means that actual problem is elsewhere.
508+
if (distinctChoices.insert(decl).second)
509+
viableSolutions.push_back({&solution, fix});
510+
return true;
511+
});
512+
513+
if (!diagnosable || viableSolutions.size() < 2)
514+
return false;
515+
516+
auto *decl = *distinctChoices.begin();
517+
assert(solverState);
518+
519+
bool diagnosed = true;
520+
{
521+
DiagnosticTransaction transaction(TC.Diags);
522+
523+
TC.diagnose(commonAnchor->getLoc(), diag::ambiguous_reference_to_decl,
524+
decl->getDescriptiveKind(), decl->getFullName());
525+
526+
for (const auto &viable : viableSolutions) {
527+
// Create scope so each applied solution is rolled back.
528+
ConstraintSystem::SolverScope scope(*this);
529+
applySolution(*viable.first);
530+
// All of the solutions supposed to produce a "candidate" note.
531+
diagnosed &= viable.second->diagnose(expr, /*asNote*/ true);
532+
}
533+
534+
// If not all of the fixes produced a note, we can't diagnose this.
535+
if (!diagnosed)
536+
transaction.abort();
537+
}
538+
539+
return diagnosed;
540+
}
541+
542+
bool ConstraintSystem::diagnoseAmbiguity(Expr *expr,
543+
ArrayRef<Solution> solutions) {
461544
// Produce a diff of the solutions.
462545
SolutionDiff diff(solutions);
463546

@@ -484,7 +567,7 @@ static bool diagnoseAmbiguity(ConstraintSystem &cs,
484567

485568
// If we can't resolve the locator to an anchor expression with no path,
486569
// we can't diagnose this well.
487-
auto *anchor = simplifyLocatorToAnchor(cs, overload.locator);
570+
auto *anchor = simplifyLocatorToAnchor(*this, overload.locator);
488571
if (!anchor)
489572
continue;
490573
auto it = indexMap.find(anchor);
@@ -524,10 +607,10 @@ static bool diagnoseAmbiguity(ConstraintSystem &cs,
524607
if (bestOverload) {
525608
auto &overload = diff.overloads[*bestOverload];
526609
auto name = getOverloadChoiceName(overload.choices);
527-
auto anchor = simplifyLocatorToAnchor(cs, overload.locator);
610+
auto anchor = simplifyLocatorToAnchor(*this, overload.locator);
528611

529612
// Emit the ambiguity diagnostic.
530-
auto &tc = cs.getTypeChecker();
613+
auto &tc = getTypeChecker();
531614
tc.diagnose(anchor->getLoc(),
532615
name.isOperator() ? diag::ambiguous_operator_ref
533616
: diag::ambiguous_decl_ref,
@@ -9021,6 +9104,11 @@ bool ConstraintSystem::salvage(SmallVectorImpl<Solution> &viable, Expr *expr) {
90219104
// FIXME: If we were able to actually fix things along the way,
90229105
// we may have to hunt for the best solution. For now, we don't care.
90239106

9107+
// Before removing any "fixed" solutions, let's check
9108+
// if ambiguity is caused by fixes and diagnose if possible.
9109+
if (diagnoseAmbiguityWithFixes(expr, viable))
9110+
return true;
9111+
90249112
// Remove solutions that require fixes; the fixes in those systems should
90259113
// be diagnosed rather than any ambiguity.
90269114
auto hasFixes = [](const Solution &sol) { return !sol.Fixes.empty(); };
@@ -9039,9 +9127,9 @@ bool ConstraintSystem::salvage(SmallVectorImpl<Solution> &viable, Expr *expr) {
90399127
solution.dump(log);
90409128
log << "\n";
90419129
}
9042-
}
9130+
}
90439131

9044-
if (diagnoseAmbiguity(*this, viable, expr)) {
9132+
if (diagnoseAmbiguity(expr, viable)) {
90459133
return true;
90469134
}
90479135
}

lib/Sema/CSDiagnostics.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,9 @@ bool MissingConformanceFailure::diagnose(bool asNote) {
234234
}
235235

236236
bool LabelingFailure::diagnose(bool asNote) {
237+
if (asNote)
238+
return false;
239+
237240
auto &cs = getConstraintSystem();
238241
auto *call = cast<CallExpr>(getAnchor());
239242
return diagnoseArgumentLabelError(cs.getASTContext(), call->getArg(),

lib/Sema/ConstraintSystem.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,16 @@ class Solution {
707707
return None;
708708
}
709709

710+
/// Retrieve overload choices associated with given expression.
711+
void getOverloadChoices(Expr *anchor,
712+
SmallVectorImpl<SelectedOverload> &overloads) const {
713+
for (auto &e : overloadChoices) {
714+
auto *locator = e.first;
715+
if (locator->getAnchor() == anchor)
716+
overloads.push_back(e.second);
717+
}
718+
}
719+
710720
LLVM_ATTRIBUTE_DEPRECATED(
711721
void dump() const LLVM_ATTRIBUTE_USED,
712722
"only for use within the debugger");
@@ -1778,6 +1788,9 @@ class ConstraintSystem {
17781788
/// emits an error message.
17791789
void diagnoseFailureForExpr(Expr *expr);
17801790

1791+
bool diagnoseAmbiguity(Expr *expr, ArrayRef<Solution> solutions);
1792+
bool diagnoseAmbiguityWithFixes(Expr *expr, ArrayRef<Solution> solutions);
1793+
17811794
/// \brief Give the deprecation warning for referring to a global function
17821795
/// when there's a method from a conditional conformance in a smaller/closer
17831796
/// scope.

test/decl/ext/protocol.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,9 @@ extension S1 {
180180
// Protocol extensions with additional requirements
181181
// ----------------------------------------------------------------------------
182182
extension P4 where Self.AssocP4 : P1 {
183-
func extP4a() { // expected-note 2 {{found this candidate}}
183+
// expected-note@-1 {{candidate requires that 'Int' conform to 'P1' (requirement specified as 'Self.AssocP4' == 'P1')}}
184+
// expected-note@-2 {{candidate requires that 'S4aHelper' conform to 'P1' (requirement specified as 'Self.AssocP4' == 'P1')}}
185+
func extP4a() {
184186
acceptsP1(reqP4a())
185187
}
186188
}
@@ -211,17 +213,19 @@ extension P4 where Self.AssocP4 == Int {
211213
}
212214

213215
extension P4 where Self.AssocP4 == Bool {
214-
func extP4a() -> Bool { return reqP4a() } // expected-note 2 {{found this candidate}}
216+
// expected-note@-1 {{candidate requires that the types 'Int' and 'Bool' be equivalent (requirement specified as 'Self.AssocP4' == 'Bool')}}
217+
// expected-note@-2 {{candidate requires that the types 'S4aHelper' and 'Bool' be equivalent (requirement specified as 'Self.AssocP4' == 'Bool')}}
218+
func extP4a() -> Bool { return reqP4a() }
215219
}
216220

217221
func testP4(_ s4a: S4a, s4b: S4b, s4c: S4c, s4d: S4d) {
218222
// FIXME: Both of the 'ambiguous' examples below are indeed ambiguous,
219223
// because they don't match on conformance and same-type
220224
// requirement of different overloads, but diagnostic
221225
// could be improved to point out exactly what is missing in each case.
222-
s4a.extP4a() // expected-error{{ambiguous reference to member 'extP4a()'}}
226+
s4a.extP4a() // expected-error{{ambiguous reference to instance method 'extP4a()'}}
223227
s4b.extP4a() // ok
224-
s4c.extP4a() // expected-error{{ambiguous reference to member 'extP4a()'}}
228+
s4c.extP4a() // expected-error{{ambiguous reference to instance method 'extP4a()'}}
225229
s4c.extP4Int() // okay
226230
var b1 = s4d.extP4a() // okay, "Bool" version
227231
b1 = true // checks type above

0 commit comments

Comments
 (0)