Skip to content

[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

Merged
merged 12 commits into from
Jun 14, 2019

Conversation

sl
Copy link
Contributor

@sl sl commented May 25, 2019

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:

struct A<X, Y, Z> {}

let a: A<Int, Int, Int> = A<Int, Int, Int>()
let b: A<Bool, Int, Float> = a

Which produces the following diagnostics:

example.swift:4:30: error: cannot convert value of type 'A<Int, Int, Int>' to 'A<Bool, Int, Float>' in assignment, arguments to generic parameter 'X' ('Int' and 'Bool') are expected to be equal
let b: A<Bool, Int, Float> = a
                             ^
example.swift:1:10: note: generic parameter 'X' declared here
struct A<X, Y, Z> {}
         ^
example.swift:4:30: error: cannot convert value of type 'A<Int, Int, Int>' to 'A<Bool, Int, Float>' in assignment, arguments to generic parameter 'Z' ('Int' and 'Float') are expected to be equal
let b: A<Bool, Int, Float> = a
                             ^
example.swift:1:16: note: generic parameter 'Z' declared here
struct A<X, Y, Z> {}

Resolves: SR-10790

@sl sl requested a review from xedin May 25, 2019 06:57
@sl sl force-pushed the generic-arguments-mismatch-diagnostic branch from 545be8c to 5347bb7 Compare May 25, 2019 06:59
@sl
Copy link
Contributor Author

sl commented May 25, 2019

@swift-ci please smoke test

@sl
Copy link
Contributor Author

sl commented May 25, 2019

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, "
Copy link
Collaborator

@theblixguy theblixguy May 25, 2019

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.

Copy link
Contributor Author

@sl sl May 28, 2019

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.

@sl
Copy link
Contributor Author

sl commented May 28, 2019

@swift-ci please smoke test

@sl sl force-pushed the generic-arguments-mismatch-diagnostic branch from da49966 to 7b92eb9 Compare May 28, 2019 22:16
@sl
Copy link
Contributor Author

sl commented May 28, 2019

@swift-ci please smoke test

@sl sl force-pushed the generic-arguments-mismatch-diagnostic branch from 7b92eb9 to 71fdd37 Compare May 28, 2019 23:15
@sl
Copy link
Contributor Author

sl commented May 28, 2019

@swift-ci please smoke test

1 similar comment
@sl
Copy link
Contributor Author

sl commented May 29, 2019

@swift-ci please smoke test

Copy link
Contributor

@xedin xedin left a 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: "each other".

ConstraintLocatorBuilder locator) {
static ConstraintSystem::TypeMatchResult matchDeepTypeArguments(
ConstraintSystem &cs, ConstraintSystem::TypeMatchOptions subflags,
BoundGenericType *base1, BoundGenericType *base2, ArrayRef<Type> args1,
Copy link
Contributor

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?

@@ -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(
Copy link
Contributor

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.

@@ -6837,7 +6902,7 @@ void ConstraintSystem::addConstraint(Requirement req,
kind = ConstraintKind::Subtype;
break;
case RequirementKind::SameType:
kind = ConstraintKind::Bind;
kind = ConstraintKind::Equal;
Copy link
Contributor

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.

@@ -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)
Copy link
Contributor

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}}
Copy link
Contributor

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.

@@ -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}}
Copy link
Contributor

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",
Copy link
Contributor

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?

Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

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
         ^

Copy link
Contributor Author

@sl sl May 30, 2019

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!

Copy link
Contributor

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.

@@ -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}}
Copy link
Contributor

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 ...`

Copy link
Contributor Author

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}}
Copy link
Contributor

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.

@sl sl force-pushed the generic-arguments-mismatch-diagnostic branch from f711de5 to e593791 Compare June 11, 2019 01:48
@sl
Copy link
Contributor Author

sl commented Jun 11, 2019

@swift-ci please smoke test

@sl sl force-pushed the generic-arguments-mismatch-diagnostic branch from e593791 to 4772c1f Compare June 11, 2019 12:57
@sl
Copy link
Contributor Author

sl commented Jun 11, 2019

@swift-ci please smoke test

1 similar comment
@sl
Copy link
Contributor Author

sl commented Jun 11, 2019

@swift-ci please smoke test

@sl
Copy link
Contributor Author

sl commented Jun 13, 2019

@swift-ci please smoke test

1 similar comment
@sl
Copy link
Contributor Author

sl commented Jun 13, 2019

@swift-ci please smoke test

@@ -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
Copy link
Contributor Author

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?

Copy link
Contributor

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.

@sl sl force-pushed the generic-arguments-mismatch-diagnostic branch from 30594da to e32654e Compare June 13, 2019 05:09
@sl
Copy link
Contributor Author

sl commented Jun 13, 2019

@swift-ci please smoke test

1 similar comment
@sl
Copy link
Contributor Author

sl commented Jun 13, 2019

@swift-ci please smoke test

Copy link
Contributor

@xedin xedin left a 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!

*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);
Copy link
Contributor

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());

Copy link
Contributor

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();
}

ConstraintSystem &cs, ConstraintSystem::TypeMatchOptions subflags,
ArrayRef<Type> args1, ArrayRef<Type> args2,
ConstraintLocatorBuilder locator,
llvm::function_ref<bool(unsigned)> recordMismatch = [](unsigned) { return false; }) {
Copy link
Contributor

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.

auto result =
matchDeepTypeArguments(*this, subflags, args1, args2, locator,
[&](unsigned position) {
if (forRequirement || isOptional || isArray)
Copy link
Contributor

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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extraneous semi-colon here.

@sl
Copy link
Contributor Author

sl commented Jun 14, 2019

@swift-ci please smoke test

@sl sl force-pushed the generic-arguments-mismatch-diagnostic branch from 714ad3f to 4d56dff Compare June 14, 2019 02:06
@sl
Copy link
Contributor Author

sl commented Jun 14, 2019

@swift-ci please smoke test

1 similar comment
@sl
Copy link
Contributor Author

sl commented Jun 14, 2019

@swift-ci please smoke test

@sl sl force-pushed the generic-arguments-mismatch-diagnostic branch from 4d56dff to 798b81b Compare June 14, 2019 03:17
@sl
Copy link
Contributor Author

sl commented Jun 14, 2019

@swift-ci please smoke test

@sl sl force-pushed the generic-arguments-mismatch-diagnostic branch from 798b81b to 9111ea2 Compare June 14, 2019 16:34
@sl
Copy link
Contributor Author

sl commented Jun 14, 2019

@swift-ci please smoke test

@sl sl force-pushed the generic-arguments-mismatch-diagnostic branch from 9111ea2 to ff950a4 Compare June 14, 2019 16:37
@sl
Copy link
Contributor Author

sl commented Jun 14, 2019

@swift-ci please smoke test

1 similar comment
@sl
Copy link
Contributor Author

sl commented Jun 14, 2019

@swift-ci please smoke test

@xedin
Copy link
Contributor

xedin commented Jun 14, 2019

:shipit:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants