Skip to content

Commit d20fdf5

Browse files
authored
Merge pull request #19920 from gregomni/8757
[Sema][QoI] Call out missing conformances in protocol witness candidates.
2 parents edb21b2 + b91e455 commit d20fdf5

16 files changed

+299
-32
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1770,9 +1770,18 @@ NOTE(bad_associated_type_deduction,none,
17701770
"unable to infer associated type %0 for protocol %1",
17711771
(DeclName, DeclName))
17721772
NOTE(associated_type_deduction_witness_failed,none,
1773-
"inferred type %1 (by matching requirement %0) is invalid: "
1774-
"does not %select{inherit from|conform to}3 %2",
1773+
"candidate would match and infer %0 = %1 if %1 "
1774+
"%select{inherited from|conformed to}3 %2",
17751775
(DeclName, Type, Type, bool))
1776+
NOTE(associated_type_witness_conform_impossible,none,
1777+
"candidate can not infer %0 = %1 because %1 "
1778+
"is not a nominal type and so can't conform to %2",
1779+
(DeclName, Type, Type))
1780+
NOTE(associated_type_witness_inherit_impossible,none,
1781+
"candidate can not infer %0 = %1 because %1 "
1782+
"is not a class type and so can't inherit from %2",
1783+
(DeclName, Type, Type))
1784+
17761785
NOTE(ambiguous_associated_type_deduction,none,
17771786
"ambiguous inference of associated type %0: %1 vs. %2",
17781787
(DeclName, Type, Type))
@@ -1793,6 +1802,9 @@ NOTE(protocol_witness_kind_conflict,none,
17931802
"a subscript}0", (RequirementKind))
17941803
NOTE(protocol_witness_type_conflict,none,
17951804
"candidate has non-matching type %0%1", (Type, StringRef))
1805+
NOTE(protocol_witness_missing_requirement,none,
1806+
"candidate would match if %0 %select{conformed to|subclassed|"
1807+
"was the same type as}2 %1", (Type, Type, unsigned))
17961808

17971809
NOTE(protocol_witness_optionality_conflict,none,
17981810
"candidate %select{type has|result type has|parameter type has|"

lib/Sema/CSFix.h

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ enum class FixKind : uint8_t {
7575
/// Skip same-type generic requirement constraint,
7676
/// and assume that types are equal.
7777
SkipSameTypeRequirement,
78+
79+
/// Skip superclass generic requirement constraint,
80+
/// and assume that types are related.
81+
SkipSuperclassRequirement,
82+
7883
};
7984

8085
class ConstraintFix {
@@ -288,6 +293,10 @@ class MissingConformance final : public ConstraintFix {
288293
static MissingConformance *create(ConstraintSystem &cs, Type type,
289294
ProtocolDecl *protocol,
290295
ConstraintLocator *locator);
296+
297+
Type getNonConformingType() { return NonConformingType; }
298+
299+
ProtocolDecl *getProtocol() { return Protocol; }
291300
};
292301

293302
/// Skip same-type generic requirement constraint,
@@ -307,6 +316,9 @@ class SkipSameTypeRequirement final : public ConstraintFix {
307316

308317
bool diagnose(Expr *root, bool asNote = false) const override;
309318

319+
Type lhsType() { return LHS; }
320+
Type rhsType() { return RHS; }
321+
310322
static SkipSameTypeRequirement *create(ConstraintSystem &cs, Type lhs,
311323
Type rhs, ConstraintLocator *locator);
312324
};
@@ -318,8 +330,8 @@ class SkipSuperclassRequirement final : public ConstraintFix {
318330

319331
SkipSuperclassRequirement(ConstraintSystem &cs, Type lhs, Type rhs,
320332
ConstraintLocator *locator)
321-
: ConstraintFix(cs, FixKind::SkipSameTypeRequirement, locator), LHS(lhs),
322-
RHS(rhs) {}
333+
: ConstraintFix(cs, FixKind::SkipSuperclassRequirement, locator),
334+
LHS(lhs), RHS(rhs) {}
323335

324336
public:
325337
std::string getName() const override {
@@ -328,6 +340,9 @@ class SkipSuperclassRequirement final : public ConstraintFix {
328340

329341
bool diagnose(Expr *root, bool asNote = false) const override;
330342

343+
Type subclassType() { return LHS; }
344+
Type superclassType() { return RHS; }
345+
331346
static SkipSuperclassRequirement *
332347
create(ConstraintSystem &cs, Type lhs, Type rhs, ConstraintLocator *locator);
333348
};

lib/Sema/CSSimplify.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5046,7 +5046,8 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyFixConstraint(
50465046
return result;
50475047
}
50485048

5049-
case FixKind::SkipSameTypeRequirement: {
5049+
case FixKind::SkipSameTypeRequirement:
5050+
case FixKind::SkipSuperclassRequirement: {
50505051
return recordFix(fix) ? SolutionKind::Error : SolutionKind::Solved;
50515052
}
50525053

lib/Sema/CSSolver.cpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -560,10 +560,17 @@ ConstraintSystem::SolverScope::~SolverScope() {
560560
/// \returns a solution if a single unambiguous one could be found, or None if
561561
/// ambiguous or unsolvable.
562562
Optional<Solution>
563-
ConstraintSystem::solveSingle(FreeTypeVariableBinding allowFreeTypeVariables) {
563+
ConstraintSystem::solveSingle(FreeTypeVariableBinding allowFreeTypeVariables,
564+
bool allowFixes) {
565+
566+
SolverState state(nullptr, *this, allowFreeTypeVariables);
567+
state.recordFixes = allowFixes;
568+
564569
SmallVector<Solution, 4> solutions;
565-
if (solve(nullptr, solutions, allowFreeTypeVariables) ||
566-
solutions.size() != 1)
570+
solve(solutions);
571+
filterSolutions(solutions, state.ExprWeights);
572+
573+
if (solutions.size() != 1)
567574
return Optional<Solution>();
568575

569576
return std::move(solutions[0]);

lib/Sema/ConstraintSystem.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3053,10 +3053,13 @@ class ConstraintSystem {
30533053
/// \param allowFreeTypeVariables How to bind free type variables in
30543054
/// the solution.
30553055
///
3056+
/// \param allowFixes Whether to allow fixes in the solution.
3057+
///
30563058
/// \returns a solution if a single unambiguous one could be found, or None if
30573059
/// ambiguous or unsolvable.
30583060
Optional<Solution> solveSingle(FreeTypeVariableBinding allowFreeTypeVariables
3059-
= FreeTypeVariableBinding::Disallow);
3061+
= FreeTypeVariableBinding::Disallow,
3062+
bool allowFixes = false);
30603063

30613064
private:
30623065
/// \brief Solve the system of constraints.

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 83 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,69 @@ static const RequirementEnvironment &getOrCreateRequirementEnvironment(
686686
return cacheIter->getSecond();
687687
}
688688

689+
static Optional<RequirementMatch> findMissingGenericRequirementForSolutionFix(
690+
constraints::ConstraintFix *fix, ValueDecl *witness,
691+
ProtocolConformance *conformance,
692+
const RequirementEnvironment &reqEnvironment) {
693+
Type type, missingType;
694+
RequirementKind requirementKind;
695+
696+
using namespace constraints;
697+
698+
switch (fix->getKind()) {
699+
case FixKind::AddConformance: {
700+
auto missingConform = (MissingConformance *)fix;
701+
requirementKind = RequirementKind::Conformance;
702+
type = missingConform->getNonConformingType();
703+
missingType = missingConform->getProtocol()->getDeclaredType();
704+
break;
705+
}
706+
case FixKind::SkipSameTypeRequirement: {
707+
requirementKind = RequirementKind::SameType;
708+
auto requirementFix = (SkipSameTypeRequirement *)fix;
709+
type = requirementFix->lhsType();
710+
missingType = requirementFix->rhsType();
711+
break;
712+
}
713+
case FixKind::SkipSuperclassRequirement: {
714+
requirementKind = RequirementKind::Superclass;
715+
auto requirementFix = (SkipSuperclassRequirement *)fix;
716+
type = requirementFix->subclassType();
717+
missingType = requirementFix->superclassType();
718+
break;
719+
}
720+
default:
721+
return Optional<RequirementMatch>();
722+
}
723+
724+
auto missingRequirementMatch = [&](Type type) -> RequirementMatch {
725+
Requirement requirement(requirementKind, type, missingType);
726+
return RequirementMatch(witness, MatchKind::MissingRequirement,
727+
requirement);
728+
};
729+
730+
if (auto memberTy = type->getAs<DependentMemberType>())
731+
return missingRequirementMatch(type);
732+
733+
type = type->mapTypeOutOfContext();
734+
if (type->hasTypeParameter())
735+
if (auto env = conformance->getGenericEnvironment())
736+
if (auto assocType = env->mapTypeIntoContext(type))
737+
return missingRequirementMatch(assocType);
738+
739+
auto reqSubMap = reqEnvironment.getRequirementToSyntheticMap();
740+
auto proto = conformance->getProtocol();
741+
Type selfTy = proto->getSelfInterfaceType().subst(reqSubMap);
742+
if (type->isEqual(selfTy)) {
743+
type = conformance->getType();
744+
if (auto agt = type->getAs<AnyGenericType>())
745+
type = agt->getDecl()->getDeclaredInterfaceType();
746+
return missingRequirementMatch(type);
747+
}
748+
749+
return Optional<RequirementMatch>();
750+
}
751+
689752
RequirementMatch
690753
swift::matchWitness(TypeChecker &tc,
691754
WitnessChecker::RequirementEnvironmentCache &reqEnvCache,
@@ -766,7 +829,7 @@ swift::matchWitness(TypeChecker &tc,
766829
auto setup = [&]() -> std::tuple<Optional<RequirementMatch>, Type, Type> {
767830
// Construct a constraint system to use to solve the equality between
768831
// the required type and the witness type.
769-
cs.emplace(tc, dc, ConstraintSystemOptions());
832+
cs.emplace(tc, dc, ConstraintSystemFlags::AllowFixes);
770833

771834
auto reqGenericEnv = reqEnvironment.getSyntheticEnvironment();
772835
auto reqSubMap = reqEnvironment.getRequirementToSyntheticMap();
@@ -848,8 +911,19 @@ swift::matchWitness(TypeChecker &tc,
848911
// Try to solve the system disallowing free type variables, because
849912
// that would resolve in incorrect substitution matching when witness
850913
// type has free type variables present as well.
851-
auto solution = cs->solveSingle(FreeTypeVariableBinding::Disallow);
852-
if (!solution)
914+
auto solution = cs->solveSingle(FreeTypeVariableBinding::Disallow,
915+
/* allowFixes */ true);
916+
917+
// If the types would match but for some other missing conformance, find and
918+
// call that out.
919+
if (solution && conformance && solution->Fixes.size()) {
920+
for (auto fix : solution->Fixes) {
921+
if (auto result = findMissingGenericRequirementForSolutionFix(
922+
fix, witness, conformance, reqEnvironment))
923+
return *result;
924+
}
925+
}
926+
if (!solution || solution->Fixes.size())
853927
return RequirementMatch(witness, MatchKind::TypeConflict,
854928
witnessType);
855929

@@ -2032,6 +2106,12 @@ diagnoseMatch(ModuleDecl *module, NormalProtocolConformance *conformance,
20322106
break;
20332107
}
20342108

2109+
case MatchKind::MissingRequirement:
2110+
diags.diagnose(match.Witness, diag::protocol_witness_missing_requirement,
2111+
match.WitnessType, match.MissingRequirement->getSecondType(),
2112+
(unsigned)match.MissingRequirement->getKind());
2113+
break;
2114+
20352115
case MatchKind::ThrowsConflict:
20362116
diags.diagnose(match.Witness, diag::protocol_witness_throws_conflict);
20372117
break;

lib/Sema/TypeCheckProtocol.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ class CheckTypeWitnessResult {
7979
bool isConformanceRequirement() const {
8080
return Requirement->isExistentialType();
8181
}
82+
bool isSuperclassRequirement() const {
83+
return !isConformanceRequirement();
84+
}
8285
bool isError() const {
8386
return Requirement->is<ErrorType>();
8487
}
@@ -168,6 +171,9 @@ enum class MatchKind : uint8_t {
168171
/// \brief The types conflict.
169172
TypeConflict,
170173

174+
/// \brief The witness would match if an additional requirement were met.
175+
MissingRequirement,
176+
171177
/// The witness throws, but the requirement does not.
172178
ThrowsConflict,
173179

@@ -352,6 +358,17 @@ struct RequirementMatch {
352358
"Should (or should not) have witness type");
353359
}
354360

361+
RequirementMatch(ValueDecl *witness, MatchKind kind, Requirement requirement,
362+
Optional<RequirementEnvironment> env = None,
363+
ArrayRef<OptionalAdjustment> optionalAdjustments = {})
364+
: Witness(witness), Kind(kind), WitnessType(requirement.getFirstType()),
365+
MissingRequirement(requirement), ReqEnv(std::move(env)),
366+
OptionalAdjustments(optionalAdjustments.begin(),
367+
optionalAdjustments.end()) {
368+
assert(hasWitnessType() && hasRequirement() &&
369+
"Should have witness type and requirement");
370+
}
371+
355372
/// \brief The witness that matches the (implied) requirement.
356373
ValueDecl *Witness;
357374

@@ -361,6 +378,9 @@ struct RequirementMatch {
361378
/// \brief The type of the witness when it is referenced.
362379
Type WitnessType;
363380

381+
/// \brief Requirement not met.
382+
Optional<Requirement> MissingRequirement;
383+
364384
/// \brief The requirement environment to use for the witness thunk.
365385
Optional<RequirementEnvironment> ReqEnv;
366386

@@ -382,6 +402,7 @@ struct RequirementMatch {
382402
case MatchKind::WitnessInvalid:
383403
case MatchKind::KindConflict:
384404
case MatchKind::TypeConflict:
405+
case MatchKind::MissingRequirement:
385406
case MatchKind::StaticNonStaticConflict:
386407
case MatchKind::SettableConflict:
387408
case MatchKind::PrefixNonPrefixConflict:
@@ -404,6 +425,7 @@ struct RequirementMatch {
404425
case MatchKind::ExactMatch:
405426
case MatchKind::RenamedMatch:
406427
case MatchKind::TypeConflict:
428+
case MatchKind::MissingRequirement:
407429
case MatchKind::OptionalityConflict:
408430
return true;
409431

@@ -425,6 +447,9 @@ struct RequirementMatch {
425447
llvm_unreachable("Unhandled MatchKind in switch.");
426448
}
427449

450+
/// \brief Determine whether this requirement match has a requirement.
451+
bool hasRequirement() { return Kind == MatchKind::MissingRequirement; }
452+
428453
swift::Witness getWitness(ASTContext &ctx) const;
429454
};
430455

lib/Sema/TypeCheckProtocolInference.cpp

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1747,9 +1747,27 @@ bool AssociatedTypeInference::diagnoseNoSolutions(
17471747
if (failed.Result.isError())
17481748
continue;
17491749

1750+
if ((!failed.TypeWitness->getAnyNominal() ||
1751+
failed.TypeWitness->isExistentialType()) &&
1752+
failed.Result.isConformanceRequirement()) {
1753+
diags.diagnose(failed.Witness,
1754+
diag::associated_type_witness_conform_impossible,
1755+
assocType->getName(), failed.TypeWitness,
1756+
failed.Result.getRequirement());
1757+
continue;
1758+
}
1759+
if (!failed.TypeWitness->getClassOrBoundGenericClass() &&
1760+
failed.Result.isSuperclassRequirement()) {
1761+
diags.diagnose(failed.Witness,
1762+
diag::associated_type_witness_inherit_impossible,
1763+
assocType->getName(), failed.TypeWitness,
1764+
failed.Result.getRequirement());
1765+
continue;
1766+
}
1767+
17501768
diags.diagnose(failed.Witness,
17511769
diag::associated_type_deduction_witness_failed,
1752-
failed.Requirement->getFullName(),
1770+
assocType->getName(),
17531771
failed.TypeWitness,
17541772
failed.Result.getRequirement(),
17551773
failed.Result.isConformanceRequirement());

test/Generics/associated_types_inherit.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ struct X1 : P {
1919
}
2020

2121
struct X2 : P { // expected-error{{type 'X2' does not conform to protocol 'P'}}
22-
func getAssoc() -> E { return E() } // expected-note{{inferred type 'E' (by matching requirement 'getAssoc()') is invalid: does not inherit from 'C'}}
22+
func getAssoc() -> E { return E() } // expected-note{{candidate would match and infer 'Assoc' = 'E' if 'E' inherited from 'C'}}
2323
}
2424

2525
func testP<T:P>(_ t: T) {

test/decl/ext/protocol_as_witness.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ protocol P1 {
88
protocol Q1 {}
99

1010
extension P1 where Self : Q1 {
11-
// FIXME: Poor QoI
12-
func f() {} // expected-note{{candidate has non-matching type '<Self> () -> ()'}}
11+
func f() {} // expected-note{{candidate would match if 'X1' conformed to 'Q1'}}
1312
}
1413

1514
struct X1 : P1 {} // expected-error{{type 'X1' does not conform to protocol 'P1'}}
@@ -33,7 +32,7 @@ protocol P3 {
3332
}
3433

3534
extension P3 where Self : Equatable {
36-
func f() {} // expected-note{{candidate has non-matching type '<Self> () -> ()'}}
35+
func f() {} // expected-note{{candidate would match if 'X3' conformed to 'Equatable'}}
3736
}
3837

3938
struct X3 : P3 {} // expected-error{{type 'X3' does not conform to protocol 'P3'}}

test/decl/protocol/conforms/self.swift

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,7 @@ class SeriousClass {}
6161

6262
extension HasDefault where Self : SeriousClass {
6363
func foo() {}
64-
// expected-note@-1 {{candidate has non-matching type '<Self> () -> ()'}}
65-
66-
// FIXME: the above diangostic is from trying to check conformance for
67-
// 'SillyClass' and not 'SeriousClass'. Evidently name lookup finds members
68-
// from all constrained extensions, and then if any don't have a matching
69-
// generic signature, diagnostics doesn't really know what to do about it.
64+
// expected-note@-1 {{candidate would match if 'SillyClass' subclassed 'SeriousClass'}}
7065
}
7166

7267
extension SeriousClass : HasDefault {}

0 commit comments

Comments
 (0)