-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[Diagnostics] Add a detailed diagnostic for generic argument mismatches #25060
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
[Diagnostics] Add a detailed diagnostic for generic argument mismatches #25060
Conversation
545be8c
to
5347bb7
Compare
@swift-ci please smoke test |
Some tests will likely fail as there may be a few tests I missed adding the new diagnostic to. I will patch those up tomorrow morning and re-run tests. |
"arguments to generic parameter %2 (%3 and %4) are expected to be equal", | ||
(Type, Type, Identifier, Type, Type)) | ||
ERROR(cannot_convert_generic_type_dict_key,none, | ||
"cannot convert value of type %0 to expected dictionary key type %1, " |
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.
Some of these diagnostics could be combined using the select syntax, but I suppose the way it’s being used in the code might make it tricky to do so.
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, that's why I decided against utilizing it here. It would make the types of the diagnostic not line up so we wouldn't be able to unify the logic for actually producing the error message. As such, I think that's a trade off that's not worth making. I do wish I could make use of the select syntax here though but I think the benefits of having a single logical flow outweigh the benefits of having less diagnostics relating to this case.
@swift-ci please smoke test |
da49966
to
7b92eb9
Compare
@swift-ci please smoke test |
7b92eb9
to
71fdd37
Compare
@swift-ci please smoke test |
1 similar comment
@swift-ci please smoke test |
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.
Overall this is great! I have left some comments inline we need to address before merging.
lib/Sema/CSFix.h
Outdated
@@ -88,6 +88,9 @@ enum class FixKind : uint8_t { | |||
/// like the types are aligned. | |||
ContextualMismatch, | |||
|
|||
/// Fix up the generic arguments of two types so they match eachother. |
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.
Nit: "each other".
lib/Sema/CSSimplify.cpp
Outdated
ConstraintLocatorBuilder locator) { | ||
static ConstraintSystem::TypeMatchResult matchDeepTypeArguments( | ||
ConstraintSystem &cs, ConstraintSystem::TypeMatchOptions subflags, | ||
BoundGenericType *base1, BoundGenericType *base2, ArrayRef<Type> args1, |
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 we need to make this more flexible to e.g. accommodate opaque result types, so maybe it would be easier to add a callback e.g. llvm::function_ref<void(unsigned)>
(which does nothing by default) here to capture each mismatched argument position and then decide what to do in the call site?
lib/Sema/CSSimplify.cpp
Outdated
@@ -2163,6 +2202,18 @@ bool ConstraintSystem::repairFailures( | |||
} | |||
|
|||
case ConstraintLocator::ClosureResult: { | |||
// If we could record a generic arguments mismatch instead of this fix, | |||
// don't record a ContextualMismatch here. | |||
auto hasDeepEqualityRestriction = llvm::any_of( |
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.
Let's get this generalized for any ConversionRestrictionKind
and extracted into a separate closure it could be used in both places.
lib/Sema/CSSimplify.cpp
Outdated
@@ -6837,7 +6902,7 @@ void ConstraintSystem::addConstraint(Requirement req, | |||
kind = ConstraintKind::Subtype; | |||
break; | |||
case RequirementKind::SameType: | |||
kind = ConstraintKind::Bind; | |||
kind = ConstraintKind::Equal; |
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 this is unrelated.
lib/Sema/CSSimplify.cpp
Outdated
@@ -4745,6 +4808,7 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyMemberConstraint( | |||
|
|||
// FIXME(diagnostics): If there were no viable results, but there are | |||
// unviable ones, we'd have to introduce fix for each specific problem. | |||
// TODO(diagnostics): Remove this, see what happens (should be noop) |
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.
Looks like something committed unintentionally.
@@ -13,6 +13,12 @@ class Generic<T> : Concrete { | |||
|
|||
func genericMethod(_: GenericAlias) {} | |||
} | |||
// expected-note@-5 {{generic parameter 'T' declared here}} |
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.
Here could be simplified too.
test/decl/typealias/generic.swift
Outdated
@@ -3,6 +3,11 @@ | |||
struct MyType<TyA, TyB> { // expected-note {{generic type 'MyType' declared here}} | |||
var a : TyA, b : TyB | |||
} | |||
// expected-note@-3 {{generic parameter 'TyA' declared here}} |
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.
And here.
@@ -394,6 +394,51 @@ ERROR(cannot_convert_closure_result_protocol,none, | |||
ERROR(cannot_convert_closure_result_nil,none, | |||
"'nil' is not compatible with closure result type %0", (Type)) | |||
|
|||
ERROR(cannot_convert_generic_type_argument,none, | |||
"cannot convert %0 to expected argument type %1, " | |||
"arguments to generic parameter %2 (%3 and %4) are expected to be equal", |
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.
Since this note is the same for all diagnostics, let's maybe make it a note and attach it to the same location as error?
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.
Overall after looking at all of the diagnostic changes it seems like it might be test to have original messages with added notes about mismatched generic parameters, WDYT?
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 that probably is the best approach. My one question is would there be too many notes for this diagnostic to be useful? We'd be emitting one note pointing to the mismatch at the location itself, and one note pointing to the declaration of the generic parameter per mismatch which i feel could get confusing.
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 we can remove declared here
note in favor of just pointing new note to the parameter declaration then? So in example from description it would look like:
example.swift:4:30: error: cannot assign value of type 'A<Int, Int, Int>' to type 'A<Bool, Int, Float>'
let b: A<Bool, Int, Float> = a
^
example.swift:1:10: note: arguments to generic parameter 'X' ('Int' and 'Bool') are expected to be equal
struct A<X, Y, Z> {}
^
or to the type repr location e.g.
example.swift:4:30: error: cannot assign value of type 'A<Int, Int, Int>' to type 'A<Bool, Int, Float>'
let b: A<Bool, Int, Float> = a
^
example.swift:1:10: note: arguments to generic parameter 'X' ('Int' and 'Bool') are expected to be equal
let b: A<Bool, Int, Float> = a
^
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.
So is the idea that we attempt to put the diagnostic at the first one where possible, and fall back to the second one if that source location wasn't valid? If so, I think that seems good!
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, that sounds good to me.
test/expr/cast/set_coerce.swift
Outdated
@@ -18,4 +18,4 @@ var setD = Set<D>() | |||
|
|||
// Test set upcasts | |||
setC = setD | |||
setD = setC // expected-error{{cannot assign value of type 'Set<C>' to type 'Set<D>'}} | |||
setD = setC // expected-error{{cannot convert value of type 'Set<C>' to 'Set<D>' in assignment, arguments to generic parameter 'Element' ('C' and 'D') are expected to be equal}} |
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.
Actually it seems like for assignment it make make sense to go with original text "cannot assign value of type ... to type ...`
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, on second thought, that does look much nicer. I'll make that change in my next round of revisions 👍
_ = UnsafePointer<String>(upi) // expected-error {{cannot convert value of type 'UnsafePointer<Int>' to expected argument type 'RawPointer'}} | ||
_ = UnsafeMutablePointer<String>(umpi) // expected-error {{cannot convert value of type 'UnsafeMutablePointer<Int>' to expected argument type 'UnsafeMutablePointer<_>'}} | ||
_ = UnsafeMutablePointer<String>(umpi) // expected-error {{cannot convert 'UnsafeMutablePointer<Int>' to expected argument type 'UnsafeMutablePointer<_>', arguments to generic parameter 'Pointee' ('Int' and '_') are expected to be equal}} |
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.
Lets preserve value of type
in the message.
f711de5
to
e593791
Compare
@swift-ci please smoke test |
e593791
to
4772c1f
Compare
@swift-ci please smoke test |
1 similar comment
@swift-ci please smoke test |
@swift-ci please smoke test |
1 similar comment
@swift-ci please smoke test |
test/Constraints/keypath.swift
Outdated
@@ -31,6 +31,9 @@ class Demo { | |||
} | |||
|
|||
// FIXME: This error is better than it was, but the diagnosis should break it down more specifically to 'here's type. | |||
// TODO(diagnostics): Fix this regression, we want the following error message ideally |
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.
Not sure what the best way to address this regression is. Figured I'd post a version with the other review comments addressed and leave this in for the moment. @xedin Thoughts?
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.
It's not incorrect to produce that diagnostic really :) Let me pull your changes locally and see what could be done, I think it should be possible to default type variables found in the left-hand side to Any
which is going to take affect only if the only way they get bindings is from context.
30594da
to
e32654e
Compare
@swift-ci please smoke test |
1 similar comment
@swift-ci please smoke test |
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.
Looks great, I have left some refactoring feedback but it looks like the logic is correct now!
lib/Sema/CSSimplify.cpp
Outdated
*this, bound1, bound2, failedArguments, getConstraintLocator(locator)); | ||
// Increase the solution's score for each mismtach this fixes. | ||
for (unsigned i = 0; i < failedArguments.size(); ++i) { | ||
increaseScore(SK_Fix); |
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 could be simplified to increaseScore(SK_Fix, failedArguments.size());
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.
Also I suggest to increase score only if the fix has been recorded successfully e.g.
if (!recordFix(fix)) {
increaseScore(SK_Fix, failedArguments.size());
return getTypeMatchSuccess();
}
lib/Sema/CSSimplify.cpp
Outdated
ConstraintSystem &cs, ConstraintSystem::TypeMatchOptions subflags, | ||
ArrayRef<Type> args1, ArrayRef<Type> args2, | ||
ConstraintLocatorBuilder locator, | ||
llvm::function_ref<bool(unsigned)> recordMismatch = [](unsigned) { return false; }) { |
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.
See my later comment, this callback doesn't really have to return anything.
lib/Sema/CSSimplify.cpp
Outdated
auto result = | ||
matchDeepTypeArguments(*this, subflags, args1, args2, locator, | ||
[&](unsigned position) { | ||
if (forRequirement || isOptional || isArray) |
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 this check belongs at top level. This block could be simplified to:
if (shouldAttemptFixes()) {
// Optionals, arrays, requirements etc.
if (<check for additional conditions where fix can't be applied>)
return matchDeepTypeArguments(*this, subflags, args1, args2, locator);
SmallVector<unsigned, 4> mismatches;
auto result = matchDeepTypeArguments(*this, subflags, args1, args2, locator,
[&mismatches](unsigned position) { mismatches.push_back(mismatchPosition); });
if (mismatches.empty())
return result;
// record the fix and increase the score
return <result>;
}
return matchDeepTypeArguments(*this, subflags, args1, args2, locator);
@@ -43,8 +43,8 @@ extension ProtoRefinesClass { | |||
let _: BaseProto & Generic<Int> = self | |||
let _: BaseProto & Concrete = self | |||
|
|||
let _: Generic<String> = self | |||
// expected-error@-1 {{cannot convert value of type 'Self' to specified type 'Generic<String>'}} | |||
let _: Generic<String> = self; |
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.
Extraneous semi-colon here.
@swift-ci please smoke test |
714ad3f
to
4d56dff
Compare
@swift-ci please smoke test |
1 similar comment
@swift-ci please smoke test |
4d56dff
to
798b81b
Compare
@swift-ci please smoke test |
798b81b
to
9111ea2
Compare
@swift-ci please smoke test |
…atchFailure Additionally, fixed a crash caused by the change relating to opaque types.
… recorded anything
9111ea2
to
ff950a4
Compare
@swift-ci please smoke test |
1 similar comment
@swift-ci please smoke test |
|
Adds a detailed diagnostic for generic argument mismatches. The new diagnostic covers both existing cases that would have been diagnosed as
cannot convert X<Y> to X<Z>
, providing more information, and takes the place of some diagnostics generated by CSDiag which provided little help.The new diagnostic has a few forms depending on the context it's used in, but here's an example of it with assignment:
Which produces the following diagnostics:
Resolves: SR-10790