Skip to content

[Diagnostics] Generalize the "protocol type cannot conform" error to work for all types that cannot conform to protocols. #27176

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 8 commits into from
Sep 18, 2019

Conversation

hborla
Copy link
Member

@hborla hborla commented Sep 13, 2019

Add a tailored diagnostic for missing conformance failures for (all) types that cannot conform to protocols. Example:

protocol P {}

func genericFunc<T: P>(_ x: T) {}

genericFunc((1, "hello")) // error

Previously, we diagnosed argument type '(Int, String)' does not conform to expected type 'P'. Now, we diagnose type '(Int, String)' cannot conform to 'P'; only struct/enum/class types can conform to protocols. Additionally, this change adds notes pointing to the requirement declaration, which we previously didn't show for existential types.

rdar://problem/44204160

@hborla hborla requested a review from xedin September 13, 2019 22:11
@hborla
Copy link
Member Author

hborla commented Sep 13, 2019

@swift-ci please smoke test

@xwu
Copy link
Collaborator

xwu commented Sep 14, 2019

Previously, we diagnosed “argument type '(Int, String)' does not conform to expected type 'P'”. Now, we diagnose “tuple type '(Int, String)' cannot conform to 'P'; only struct/enum/class types can conform to protocols”.

I like the attempt to improve the diagnostic, but I’m not sure the revised wording is ideal:

“Argument type” is helpful here because it helps the reader figure out where the error specifically is that we’re talking about. It may not always be obvious that the type being referenced is that of the argument used, since the argument won’t always be a tuple literal. Consider a use site f(x), where x is a tuple: the diagnostic would mention the generic constraints of f and the type of x, neither of which can be seen at the location where the error is displayed.

By contrast, consider the revised wording “tuple type ‘(Int, String)’”: it is clear to anyone with a basic knowledge of Swift that “(Int, String)” is a tuple type, so that wording says the same thing twice.

Meanwhile, it is true that the tuple type “does not” conform to the protocol “P”, and it is equally true that it “cannot.”

The original diagnostic is phrased in terms of the specifics of why there is an error: the argument to this specific function must be of a type that conforms to “P”—and this in turn implies the only way to call this function correctly: by supplying an argument of a type that does conform to ‘P’. It also implies a currently impossible fix when the argument is a tuple: conforming the type of the argument being used to the protocol. This is what we need to clarify.

Put another way, there are three pieces of information that need to be conveyed here to the user:

  1. You have supplied an argument of the wrong type.
  2. The type is wrong because there is a missing protocol conformance.
  3. There is no protocol conformance because tuple types cannot conform to any protocols.

The current diagnostic only accomplishes (1) and (2).

The proposed diagnostic here does not accomplish (1) because it does not phrase itself in terms of the specific argument. In fact, not only does it remove the word “argument,” it also phrases the error as a general rule about the language (“cannot”). It is a legitimate technique to teach users in error messages about what does or does not work in the language. But in this case it is phrased as a general rule with an oddly ambiguous specificity: now, the user is being taught that this specific type cannot (as a rule) conform to protocol “P”. At least two implied fixes that a user might take away are false: another tuple type cannot conform to “P” any more than this one can, and this tuple type cannot conform to any other protocol any more than to “P”. Therefore, the proposed error message here correctly clarifies what it’s trying to say by then restating the general rule in a more accurate way. But then it does so by affirmatively mentioning structs, enums, and classes, none of which are relevant to this error in this place.

The end result is that a general rule is stated twice, accomplishing points (2) and (3) above more than once, while abandoning point (1).

One wording that might work to teach users all three points is:

“Argument type ‘(Int, String)’ does not conform to expected protocol ‘P’; tuple types cannot conform to protocols.”

“Argument type ‘P’ does not conform to expected protocol ‘P’; protocols used as types cannot conform to protocols.”

@xedin
Copy link
Contributor

xedin commented Sep 14, 2019

@xwu Thank you for such a thoughtful comment! I agree that it would be best to get an additional variant of this diagnostic which is tailored for arguments as you suggest. That would actually be as easy as adjusting existing cannot_convert_argument_value_protocol diagnostic message because we already can distinguish that type of failure happening in argument position.

@hborla
Copy link
Member Author

hborla commented Sep 16, 2019

@xwu again, thank you for the feedback! I agree that there is a lot of useful information that should be shown to the user, and this is where Swift's one-line diagnostic messages really fall short. FWIW, this is already what we show for existential types (which is arguably the most common form of this error), so we are already missing a lot of information in that case, and I'm attempting to remedy this by adding notes to this diagnostic. In the most recent version of Xcode, the error message for the example in the description actually says Global function 'genericFunc' requires that '(Int, String)' conform to 'P', along with a note pointing to the declaration of genericFunc. This is different than the current message produced on master, but it IS useful to know that the requirement comes from global function genericFunc, and this is information that the user should know. Additionally, I do think it's useful to state that only struct/enum/class types can conform to protocols because until the user changes the code so that they're sending in a value of a struct, enum, or class, they cannot fix the error.

As I mentioned, I'm currently adding notes to this diagnostic so that the user does see more of this information (and I'm removing the redundancy of tuple type, function type, etc - see the thread above). IMO, ideally we would also show the original error message as a note in addition to the note pointing back to the declaration if applicable. So, the error message would say type '(Int, String)' cannot conform to 'P'; only struct/enum/class types can conform to protocols with notes stating why it must conform, and where the expected conformance is specified. I can also adjust the message to say "argument type" if applicable, but I'll have to figure out how to adjust this message when we have an existential type. We do still need to specify when a type is an existential type, because those types look just like any nominal type.

@xwu
Copy link
Collaborator

xwu commented Sep 17, 2019

How about "type '(Int, String)' cannot satisfy expected protocol constraint because it is not a struct, enum, or class"?

Again, I think for some users it can be misleading to say that it "cannot conform to 'P'", since P has nothing to do with what can or cannot happen here.

to the declaration that requires protocol conformance.
@hborla hborla changed the title [Diagnostics] Generalize the "protocol type cannot conform" error towork for all types that cannot conform to protocols. [Diagnostics] Generalize the "protocol type cannot conform" error to work for all types that cannot conform to protocols. Sep 17, 2019
@hborla hborla force-pushed the type-cannot-conform-diag branch from cc7e95c to 7687293 Compare September 17, 2019 18:53
@hborla
Copy link
Member Author

hborla commented Sep 17, 2019

@swift-ci please smoke test

@hborla hborla merged commit 29044e5 into swiftlang:master Sep 18, 2019
@hborla hborla deleted the type-cannot-conform-diag branch September 18, 2019 17:38
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.

4 participants