Skip to content

Make sure protocol witness errors don't leave the conformance context #16489

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 2 commits into from
May 11, 2018

Conversation

jrose-apple
Copy link
Contributor

That is, if there's a problem with a witness, and the witness comes from a different extension from the conformance (or the original type, when the conformance is on an extension), put the main diagnostic on the conformance, with a note on the witness. This involves some shuffling and rephrasing of existing diagnostics too.

There's a few reasons for this change:

  • More context. It may not be obvious why a declaration in file A.swift needs to be marked 'public' if you can't see the conformance in B.swift.

  • Better locations for imported declarations. If you're checking a conformance in a source file but the witness came from an imported module, it's better to put the diagnostic on the part you have control over. (This is especially true in Xcode, which can't display diagnostics on imported declarations in the source editor.)

  • Plays better with batch mode. Without this change, you can have diagnostics being reported in file A.swift that are tied to a conformance declared in file B.swift. Of course the contents of A.swift also affect the diagnostic, but compiling A.swift on its own wouldn't produce the diagnostic, and so putting it there is problematic. (See [Batch Mode] Suppressing diagnostics for non-primaries - rdar://40032762 #16485.)

The change does in some cases make for a worse user experience, though; if you just want to apply the changes and move on, the main diagnostic isn't in the "right place". It's the note that has the info and possible fix-it. It's also a slightly more complicated implementation.

@jrose-apple
Copy link
Contributor Author

@swift-ci Please test

@jrose-apple
Copy link
Contributor Author

@swift-ci Please test source compatibility

@jrose-apple
Copy link
Contributor Author

Still needs new tests for the new behavior in addition to the updates for the existing tests, but I want to see if anything breaks, or if reviewers have objections.

Copy link
Contributor

@davidungar davidungar left a comment

Choose a reason for hiding this comment

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

@jrose-apple What sort of review are you looking for here? Since I'm not familiar with the type-checker, I can't quickly offer thoughts on correctness. If you let me know what would be most helpful, I will do it. A very quick glance at the diffs did not result in anything jumping out at me.

@jrose-apple
Copy link
Contributor Author

For you and Graydon it was more a CC than a review request, I guess. "Here's the part I've been working on, here's the general approach I took". It's Huon and/or Doug, who've been working with conformance checking more, that I really want to look over this.

@jrose-apple jrose-apple force-pushed the to-put-it-in-context branch from f6a1586 to ba5bca6 Compare May 10, 2018 21:33
@jrose-apple
Copy link
Contributor Author

Now with tests for everything except the witness_non_objc_* diagnostics, which don't have any other tests except one lonely case that should probably be valid anyway (SR-7601). I can't figure out how to get the others to happen, so I'm leaving it for now.

@swift-ci Please smoke test

Copy link
Member

@DougGregor DougGregor left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Contributor

@huonw huonw left a comment

Choose a reason for hiding this comment

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

Minor questions other than the ctor->isImplicit which is a just slightly-more-than-minor question.

// If the main diagnostic is emitted on the conformance, we want to
// attach the fix-it to the note that shows where the initializer is
// defined.
fixItDiag.reset();
Copy link
Contributor

Choose a reason for hiding this comment

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

The destructor-based emission of diagnostics makes this if kinda... magical.

Referencing the instances below, it seems this could almost be factored out, maybe?

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 don't think there's enough commonality to factor it out, but I just realized that fixItDiag->flush() will work as well, since InFlightDiagnostic tracks whether the diagnostic has been flushed yet. I'll do that instead.

diag::witness_initializer_failability,
ctor->getFullName(),
witnessCtor->getFailability()
== OTK_ImplicitlyUnwrappedOptional)
.highlight(witnessCtor->getFailabilityLoc());
if (diagLoc != witness->getLoc()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems every second diagnostic touched here gets this if block almost verbatim. We could consider factoring it out somehow?

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 guess that makes sense for all the decl_declared_here ones, at least. I tried to think of how to fold it into getLocForDiagnosingWitness with a callback and couldn't, then tried to think of how to generalize it over any possible follow-up note and couldn't. But more of them ended up just being "declared here" than I originally thought.

witness->getFullName(),
proto->getFullName());
if (diagLoc == witness->getLoc()) {
addOptionalityFixIts(adjustments, ctx, witness, diag);
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the thinking behind these only applying in this case, rather than the reset/emplace thing you've done for other similar versions to get fixits on the note?

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 mostly only did that where there was a dedicated note, rather than just a "declared here". There are two reasons for that: "declared here" doesn't tell you what the fix-it is doing, and if the declaration is written somewhere else, it's less likely that its sole purpose is to satisfy this protocol. The one exception was the required initializer fix-it, because people complain that they never remember what they have to write for initializers but I couldn't think of a good custom note for that case.

Happy to accept feedback here.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sounds like reasonable logic.

ctor->getFullName()));
}
if (!inExtension) {
fixItDiag->fixItInsert(ctor->getAttributeInsertionLoc(true),
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 has changed behaviour: it will get attached to the witness_initializer_not_require diagnostic if ctor->isImplicit()?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's subtle, but for an implicit constructor getAttributeInsertionLoc will (now) return an invalid location, which drops the fix-it. I found this more natural than trying to untangle which conditions are needed and which aren't.

@jrose-apple jrose-apple force-pushed the to-put-it-in-context branch from ba5bca6 to 2be4616 Compare May 10, 2018 23:18
@jrose-apple
Copy link
Contributor Author

@swift-ci Please smoke test

@jrose-apple
Copy link
Contributor Author

@swift-ci Please smoke test Linux

type, genericParam, !genericParam.isNull(),
assocType->getFullName(), Proto->getFullName());
if (typeDecl && diagLoc != typeDecl->getLoc())
ctx.Diags.diagnose(typeDecl, diag::type_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.

emitDeclaredHereIfNeeded(ctx.Diags, diagLoc, typeDecl)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Huh. This one was "type declared here" instead of "'Foo' declared here", but I guess we have other type witness diagnostics where I went with the "'Foo' declared here" one.

witness->getFullName(),
proto->getFullName());
if (diagLoc == witness->getLoc()) {
addOptionalityFixIts(adjustments, ctx, witness, diag);
Copy link
Contributor

Choose a reason for hiding this comment

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

Sounds like reasonable logic.

That is, if there's a problem with a witness, and the witness comes
from a different extension from the conformance (or the original type,
when the conformance is on an extension), put the main diagnostic on
the conformance, with a note on the witness. This involves some
shuffling and rephrasing of existing diagnostics too.

There's a few reasons for this change:

- More context. It may not be obvious why a declaration in file
  A.swift needs to be marked 'public' if you can't see the conformance
  in B.swift.

- Better locations for imported declarations. If you're checking a
  conformance in a source file but the witness came from an imported
  module, it's better to put the diagnostic on the part you have
  control over. (This is especially true in Xcode, which can't display
  diagnostics on imported declarations in the source editor.)

- Plays better with batch mode. Without this change, you can have
  diagnostics being reported in file A.swift that are tied to a
  conformance declared in file B.swift. Of course the contents of
  A.swift also affect the diagnostic, but compiling A.swift on its
  own wouldn't produce the diagnostic, and so putting it there is
  problematic.

The change does in some cases make for a worse user experience,
though; if you just want to apply the changes and move on, the main
diagnostic isn't in the "right place". It's the note that has the info
and possible fix-it. It's also a slightly more complicated
implementation.
@jrose-apple jrose-apple force-pushed the to-put-it-in-context branch from 2be4616 to 6bd7e5e Compare May 11, 2018 02:31
@jrose-apple
Copy link
Contributor Author

@swift-ci Please smoke test

@jrose-apple jrose-apple merged commit 88169d6 into swiftlang:master May 11, 2018
@jrose-apple jrose-apple deleted the to-put-it-in-context branch May 11, 2018 04:44
jrose-apple added a commit to jrose-apple/swift that referenced this pull request May 11, 2018
Make sure protocol witness errors don't leave the conformance context

(cherry picked from commit 88169d6)
jrose-apple added a commit to jrose-apple/swift that referenced this pull request May 11, 2018
Make sure protocol witness errors don't leave the conformance context

(cherry picked from commit 88169d6)
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