-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[ConstraintSystem] Diagnose missing conformance requirements via "fixes" #18484
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
Conversation
If fixes are allowed let solver record missing protocol conformance requirements and assume that `conformsTo` constraint is successfully solved, this helps to diagnose such errors without involving heavy-weight expression based diagnostics. Resolves: rdar://problem/40537858
Any solution which doesn't require source changes should be considered better than any other solution which does. Otherwise solutions with fixes are always considered better than solutions with unavailable overloads, which isn't correct especially considering that different types of errors are now diagnosed via forming solutions with fixes.
…pr is available While trying to diagnose missing conformances don't attempt to lookup parameter position if the parent apply expression is not available, which could happen when fixes are produced as part of the sub-expression diagnostics.
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.
Most of the diagnostic changes look like clear improvements, nice work! I have a few small suggestions and some ideas for future work.
} | ||
|
||
var arr: [S] = [] | ||
_ = List(arr, id: \.id) // expected-error {{'List<[S], S.Id>' requires that 'S.Id' conform to 'Hashable'}} |
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.
Eventually, we should say something like
List<T, E> requires that 'E' conform to 'Hashable' (with 'E' = 'S.Id')
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.
I can definitely add requirement information to the diagnostic message, since owner is known I can get generic signature and extract the original type from requirement.
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.
Yeah. Including the name of the decl and it’s generic signature would make the diagnostic clearer, unless it becomes too long; but in that case we can split off some notes to spell out the substitutions, for example
@@ -75,18 +75,18 @@ let _ = Conforms<X>.Decl3.self | |||
let _ = Conforms<X>.Decl4<X>.self | |||
let _ = Conforms<X>.Decl5<X>.self | |||
|
|||
let _ = Conforms<Y>.TypeAlias1.self // expected-error {{type 'Y' does not conform to protocol 'P'}} | |||
let _ = Conforms<Y>.TypeAlias2.self // expected-error {{type 'Y' does not conform to protocol 'P'}} | |||
let _ = Conforms<Y>.TypeAlias1.self // expected-error {{'Conforms<Y>.TypeAlias1.Type' (aka 'Y.Type') requires that 'Y' conform to 'P'}} |
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.
The '.Type' is just noise. It might be better to refer to the declaration here -- something like
typealias 'TypeAlias1' requires that 'Y' conform to 'P'
|
||
func platypus<T>(a: [T]) { | ||
_ = elephant(a) // expected-error {{generic parameter 'U' could not be inferred}} | ||
_ = elephant(a) // expected-error {{'([T]) -> ()' requires that 'T' conform to 'Hashable'}} |
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.
Another case where we diagnose the type but not the decl -- shouldn't this say
function 'elephant' requires that 'T' conform to 'Hashable'
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.
Yes, that definitely would be great! I left that for last, but I think it shouldn't be hard to do because anchor point to either DeclRefExpr
or UnresolvedDot
which should have a kind/name attach to it.
Just trying to get protocol compositions diagnose correctly first...
test/Constraints/generics.swift
Outdated
@@ -188,7 +188,7 @@ func r22459135() { | |||
|
|||
// <rdar://problem/19710848> QoI: Friendlier error message for "[] as Set" | |||
// <rdar://problem/22326930> QoI: "argument for generic parameter 'Element' could not be inferred" lacks context | |||
_ = [] as Set // expected-error {{generic parameter 'Element' could not be inferred in cast to 'Set'}} expected-note {{explicitly specify the generic arguments to fix this issue}} {{14-14=<<#Element: Hashable#>>}} | |||
_ = [] as Set // expected-error {{using 'Any' as a concrete type conforming to protocol 'Hashable' is not supported}} |
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.
This is more accurate now, awesome!
I know this is an existing diagnostic, but in a subsequent patch, do you mind changing it to not say "not supported". Instead, I would just say "protocol type 'Any' cannot conform to 'Hashable' because only concrete types can conform to protocols" or something like that.
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.
Sure! There are currently 2 existing diagnostics - one for objc and one for swift types, should I modify both or just the one related to swift types?
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.
I think the objc one already diagnoses the correct cause - static or initializer requirements. The swift one just says “not supported” which suggests it might be supported, but we don’t know if we’re going to implement self conforming existentials yet. Instead of promising a behavior, the diagnostic should explain the language rule - only concrete types can satisfy protocol requirements.
@@ -214,7 +214,7 @@ func rangeOfIsBefore<R : IteratorProtocol>(_ range: R) where R.Element : IsBefor | |||
|
|||
func callRangeOfIsBefore(_ ia: [Int], da: [Double]) { | |||
rangeOfIsBefore(ia.makeIterator()) | |||
rangeOfIsBefore(da.makeIterator()) // expected-error{{type 'Double' does not conform to protocol 'IsBefore'}} | |||
rangeOfIsBefore(da.makeIterator()) // expected-error{{'(IndexingIterator<[Double]>) -> ()' requires that 'Double' conform to 'IsBefore'}} |
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.
Another case where the decl and not the type would be most useful
@@ -17,7 +17,7 @@ func fAOE(_ t: AnyObject) { } | |||
func fT<T>(_ t: T) { } | |||
|
|||
func testPassExistential(_ p: P, op: OP, opp: OP & P, cp: CP, sp: SP, any: Any, ao: AnyObject) { | |||
fP(p) // expected-error{{cannot invoke 'fP' with an argument list of type '(P)'}} // expected-note{{expected an argument list of type '(T)'}} | |||
fP(p) // expected-error{{using 'P' as a concrete type conforming to protocol 'P' is not supported}} |
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.
Excellent!
return foor20792596(x) // expected-error {{generic parameter 'T' could not be inferred}} | ||
return foor20792596(x) | ||
// expected-error@-1 {{missing argument label 'x:' in call}} | ||
// expected-error@-2 {{argument type 'T' does not conform to expected type 'r20792596P'}} |
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.
+1!
@@ -11,5 +11,4 @@ var a : TheBuiltinInt64 | |||
|
|||
// Check that it really is Builtin.Int64. | |||
var wrapped = Int64(a) // okay | |||
var badWrapped = Int32(a) // expected-error{{cannot invoke initializer for type 'Int32' with an argument list of type '(TheBuiltinInt64)'}} | |||
// expected-note @-1 {{overloads for 'Int32' exist with}} | |||
var badWrapped = Int32(a) // expected-error{{'Int32' requires that 'TheBuiltinInt64' conform to 'BinaryInteger'}} |
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.
Would be clearer if it said "initializer for 'Int32'"
Note that I didn’t review the code in detail, just the diagnostic changes. @rudkx should do that. But I think this and the other change to diagnose argument label mismatches as fixes are the right approach overall. Do you think soon you’ll be able to start ripping out code from CSDiag? |
Yes, I hope to start removing logic from CSDiag soon. For example, I want to get out-of-order and missing arguments diagnosed via fixes, which would give it parity with what we have in CSDiag and, in theory, would allow us to remove |
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.
These are great improvements to the diagnostics! I added a few comments/questions, but none that should block merging.
lib/Sema/CSApply.cpp
Outdated
@@ -8148,6 +8149,95 @@ bool ConstraintSystem::applySolutionFix( | |||
fix.first.getArgumentLabels(*this), | |||
isa<SubscriptExpr>(call->getFn())); | |||
} | |||
|
|||
case FixKind::AddConformace: { |
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.
Can we extract this case into a different function? It's a ton of code
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.
I was thinking about doing that in the separate PR because I think we need to refactor applySolutionFixes
into Solution
itself, add diagnoseFix
instead of applySolutionFix
, and extract this logic into fixConformance
, WDYT?
auto protocolType = conformance.second->getDeclaredType(); | ||
|
||
// Find `ApplyExpr` based on a function expression attached to it. | ||
auto findApplyExpr = [](Expr *parent, Expr *fnExpr) -> ApplyExpr * { |
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.
I'm a little surprised that we have to go searching for the ApplyExpr
and matching up the parameter. Doesn't the (pre-simplified) locator contain the ApplyExpr
, as well as information about which parameter we're talking about? Or is that still hard to get to because of the matchTypes/function call mess?
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.
Yes, unfortunately that exactly the reason, currently generic requirements are opened from function expression, which means that it would have DeclRefExpr
or UnresolvedDotExpr
or OverloadedDeclRefExpr
as its anchor and opened generic -> type requirement #
as its path...
// If this is a static, initializer or operator call, | ||
// let's not try to diagnose it here, but refer to expression | ||
// diagnostics. | ||
if (isa<BinaryExpr>(applyExpr) || isa<TypeExpr>(anchor)) |
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.
I would have expected that these diagnostics would be better for operators (at least), but okay.
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.
There are some problems with them currently, where current way of listing overload choices is better IMO, otherwise it would just say something like Int does not conform to RangeReplaceableCollection
:(
auto *argExpr = getArgumentAt(applyExpr, *atParameterPos); | ||
TC.diagnose(argExpr->getLoc(), | ||
diag::cannot_convert_argument_value_protocol, type, | ||
protocolType); |
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.
Oh! Oh! In many cases, we can probably resolveapplyExpr->getFn()
down to a declaration, in which case we can produce a note pointing to the declaration we are trying to call. Bonus points for finding the type variable bindings when that declaration is generic. It would be super amazing if we could do something like this:
note: in call to function 'foo(blah:)' [with T=Int, U=String]
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.
Indeed! I've mentioned that in reply to Slava's comment - I'm going to change the diagnostic to <decl-kind> <name> requires that 'X' conform to 'Y' [with T = X]
lib/Sema/CSSimplify.cpp
Outdated
auto typeRequirement = path.back(); | ||
std::pair<Expr *, unsigned> reqLoc = {anchor, typeRequirement.getValue()}; | ||
MissingConformances[reqLoc] = {type.getPointer(), protocol}; | ||
// Let's strip all of the uncessary information from locator, |
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.
Typo "uncessary"
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.
Thanks!
lib/Sema/CSSimplify.cpp
Outdated
// Let's strip all of the uncessary information from locator, | ||
// diagnostics only care about anchor - to lookup type, | ||
// and what was the requirement# which is not satisfied. | ||
ConstraintLocatorBuilder requirement(getConstraintLocator(anchor)); |
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.
Ah, so this is what helps us be sure we can find the missing conformance information later. Makes sense.
lib/Sema/ConstraintSystem.h
Outdated
@@ -1009,6 +1009,12 @@ class ConstraintSystem { | |||
/// Argument labels fixed by the constraint solver. | |||
SmallVector<std::vector<Identifier>, 4> FixedArgLabels; | |||
|
|||
/// Conformances which solver "fixed" to exist to help with |
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.
typo? "to exist to help", perhaps just "to help"?
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.
Thanks!
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.
LGTM - very nice improvement.
…formance failures Having `.Type` in `requires conformance` diagnostic is unnecessary.
I'll split the rest of the changes into multiple separate PR refactoring and extraction of diagnostics could be done separately. |
@swift-ci please test |
Excellent, thank you! |
If fixes are allowed let solver record missing protocol conformance
requirements and assume that
conformsTo
constraint is successfullysolved, this helps to diagnose such errors without involving
heavy-weight expression based diagnostics.
Resolves: rdar://problem/40537858