-
Notifications
You must be signed in to change notification settings - Fork 10.2k
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
[Sema][QoI] Call out missing conformances in protocol witness candidates. #19920
Changes from 2 commits
f2a80c4
782f620
002c04e
939de4f
723d56c
85ad4a1
b91e455
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -758,7 +758,7 @@ swift::matchWitness(TypeChecker &tc, | |
auto setup = [&]() -> std::tuple<Optional<RequirementMatch>, Type, Type> { | ||
// Construct a constraint system to use to solve the equality between | ||
// the required type and the witness type. | ||
cs.emplace(tc, dc, ConstraintSystemOptions()); | ||
cs.emplace(tc, dc, ConstraintSystemFlags::AllowFixes); | ||
|
||
auto reqGenericEnv = reqEnvironment.getSyntheticEnvironment(); | ||
auto reqSubMap = reqEnvironment.getRequirementToSyntheticMap(); | ||
|
@@ -840,8 +840,35 @@ swift::matchWitness(TypeChecker &tc, | |
// Try to solve the system disallowing free type variables, because | ||
// that would resolve in incorrect substitution matching when witness | ||
// type has free type variables present as well. | ||
auto solution = cs->solveSingle(FreeTypeVariableBinding::Disallow); | ||
if (!solution) | ||
auto solution = cs->solveSingle(FreeTypeVariableBinding::Disallow, | ||
/* allowFixes */ true); | ||
|
||
// If the types would match but for some other missing conformance, find and | ||
// call that out. | ||
if (solution && conformance && solution->Fixes.size() == 1 && | ||
solution->Fixes.front()->getKind() == FixKind::AddConformance) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If there is more than one fix, perhaps we should diagnose the first fix? Or walk through the fixes and diagnose the first one we recognize? It won't describe the whole problem, but it's probably a better clue than falling back to "type mismatch." |
||
auto fix = (MissingConformance *)solution->Fixes.front(); | ||
auto type = fix->getNonConformingType(); | ||
auto protocolType = fix->getProtocol()->getDeclaredType(); | ||
if (auto memberTy = type->getAs<DependentMemberType>()) | ||
return RequirementMatch(witness, MatchKind::MissingConformance, type, | ||
protocolType); | ||
|
||
type = type->mapTypeOutOfContext(); | ||
if (auto typeParamTy = type->getAs<GenericTypeParamType>()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if it’s a dependent member type? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Checked for just above (that's one of the things I broke and needed the second commit for) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we generalize the |
||
if (auto env = conformance->getGenericEnvironment()) | ||
if (auto assocType = env->mapTypeIntoContext(typeParamTy)) | ||
return RequirementMatch(witness, MatchKind::MissingConformance, | ||
assocType, protocolType); | ||
if (type->isEqual(conformance->getType())) { | ||
if (auto bgt = type->getAs<BoundGenericType>()) | ||
type = bgt->getDecl()->getDeclaredType(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if it’s a non-generic type nested in a generic type? Is this restriction really necessary? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nope, fix upcoming |
||
return RequirementMatch(witness, MatchKind::MissingConformance, type, | ||
protocolType); | ||
} | ||
} | ||
|
||
if (!solution || solution->Fixes.size()) | ||
return RequirementMatch(witness, MatchKind::TypeConflict, | ||
witnessType); | ||
|
||
|
@@ -2024,6 +2051,11 @@ diagnoseMatch(ModuleDecl *module, NormalProtocolConformance *conformance, | |
break; | ||
} | ||
|
||
case MatchKind::MissingConformance: | ||
diags.diagnose(match.Witness, diag::protocol_witness_missing_conformance, | ||
match.WitnessType, match.SecondType); | ||
break; | ||
|
||
case MatchKind::ThrowsConflict: | ||
diags.diagnose(match.Witness, diag::protocol_witness_throws_conflict); | ||
break; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
// RUN: %target-typecheck-verify-swift | ||
|
||
// Test candidates for witnesses that are missing conformances | ||
// in various ways. | ||
|
||
protocol LikeSetAlgebra { | ||
func onion(_ other: Self) -> Self // expected-note {{protocol requires function 'onion' with type '(X) -> X'; do you want to add a stub?}} | ||
func indifference(_ other: Self) -> Self // expected-note {{protocol requires function 'indifference' with type '(X) -> X'; do you want to add a stub?}} | ||
|
||
} | ||
protocol LikeOptionSet : LikeSetAlgebra, RawRepresentable {} | ||
extension LikeOptionSet where RawValue : FixedWidthInteger { | ||
func onion(_ other: Self) -> Self { return self } // expected-note {{candidate would match if 'X.RawValue' conformed to 'FixedWidthInteger'}} | ||
func indifference(_ other: Self) -> Self { return self } // expected-note {{candidate would match if 'X.RawValue' conformed to 'FixedWidthInteger'}} | ||
} | ||
|
||
struct X : LikeOptionSet {} | ||
// expected-error@-1 {{type 'X' does not conform to protocol 'LikeSetAlgebra'}} | ||
// expected-error@-2 {{type 'X' does not conform to protocol 'RawRepresentable'}} | ||
|
||
protocol IterProtocol {} | ||
protocol LikeSequence { | ||
associatedtype Iter : IterProtocol // expected-note {{unable to infer associated type 'Iter' for protocol 'LikeSequence'}} | ||
func makeIter() -> Iter | ||
} | ||
extension LikeSequence where Self == Self.Iter { | ||
func makeIter() -> Self { return self } // expected-note {{candidate would match and infer 'Iter'='Y' if 'Y' conformed to 'IterProtocol'}} | ||
} | ||
|
||
struct Y : LikeSequence {} // expected-error {{type 'Y' does not conform to protocol 'LikeSequence'}} | ||
|
||
protocol P1 { | ||
associatedtype Result | ||
func get() -> Result // expected-note {{protocol requires function 'get()' with type '() -> Result'; do you want to add a stub?}} | ||
func got() // expected-note {{protocol requires function 'got()' with type '() -> ()'; do you want to add a stub?}} | ||
} | ||
protocol P2 { | ||
static var singularThing: Self { get } | ||
} | ||
extension P1 where Result : P2 { | ||
func get() -> Result { return Result.singularThing } // expected-note {{candidate would match if 'Result' conformed to 'P2'}} | ||
} | ||
protocol P3 {} | ||
extension P1 where Self : P3 { | ||
func got() {} // expected-note {{candidate would match if 'Z' conformed to 'P3'}} | ||
} | ||
|
||
struct Z<T1, T2, T3, Result, T4> : P1 {} // expected-error {{type 'Z<T1, T2, T3, Result, T4>' does not conform to protocol 'P1'}} | ||
|
||
protocol P4 { | ||
func this() // expected-note {{protocol requires function 'this()' with type '() -> ()'; do you want to add a stub?}} | ||
} | ||
protocol P5 {} | ||
extension P4 where Self : P5 { | ||
func this() {} // expected-note {{candidate would match if 'W' conformed to 'P5'}} | ||
} | ||
struct W : P4 {} // expected-error {{type 'W' does not conform to protocol 'P4'}} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,7 +17,7 @@ func bad_containers_2(bc: BadContainer2) { | |
} | ||
|
||
struct BadContainer3 : Sequence { // expected-error{{type 'BadContainer3' does not conform to protocol 'Sequence'}} | ||
func makeIterator() { } // expected-note{{inferred type '()' (by matching requirement 'makeIterator()') is invalid: does not conform to 'IteratorProtocol'}} | ||
func makeIterator() { } // expected-note{{candidate would match and infer 'Iterator'='()' if '()' conformed to 'IteratorProtocol'}} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, but () is not a nominal type, so it would never conform. Can you instead just say the return type is wrong without offering a suggestion? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, more specific can't-conform-to-non-nominal and can't-inherit-from-non-class coming up. |
||
} | ||
|
||
func bad_containers_3(bc: BadContainer3) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spaces around = might be more readable