Skip to content

[Diagnostics][NFC] Optionally add notes as explicit children of an error/warning #26830

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
Oct 2, 2019

Conversation

owenv
Copy link
Contributor

@owenv owenv commented Aug 25, 2019

Add a new builder method, addChildNotes, to InFlightDiagnostic, which accepts a lambda expression. Any notes diagnosed inside will be added as children of the parent error or warning. DiagnosticConsumers may opt in to consuming these child notes alongside the parent
diagnostic, or they can continue to consider them seperately.

Moved PrintingDiagnosticConsumer and a couple of diagnostics to the new
system as a proof of concept.

This is intended to unblock some future improvements to diagnostic presentation and formatting. The most controversial bits of this are probably the DiagnosticInfo refactor and the use of addChildNotes blocks to associate notes with a parent diagnostic.

This change is opt-in for DiagnosticConsumers. Child notes are both attached to their parent, and emitted standalone. Consumers which have been updated with knowledge of child notes can ignore the standalone emissions. Also, this change makes many of the handleDiagnostic arguments redundant now that everything is stored in DiagnosticInfo. Since there are many consumers that would need to be migrated, I thought it would be best not to clutter this PR.

@xedin xedin requested a review from jrose-apple August 26, 2019 17:18
Copy link
Contributor

@jrose-apple jrose-apple left a comment

Choose a reason for hiding this comment

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

I definitely like this idea, and it helps enforce the idea that notes should always be attached to a parent. I am worried about the implementation, though. Ownership needs to be clear at all stages. Would it be possible to reuse more of the existing transaction machinery for this?

@owenv owenv force-pushed the diag_child_notes_refactor branch from ae19b41 to 4d1eb0c Compare August 28, 2019 16:15
@owenv
Copy link
Contributor Author

owenv commented Aug 28, 2019

@jrose-apple Thanks for the feedback, apologies for some of the rookie c++ mistakes! I've fixed the smaller issues you pointed out.

I'm not sure there's a good way to leverage transactions here. The issue I ran into is that the order of events when attaching child notes is roughly:

  • begin building the parent diagnostic
  • begin building notes
  • as notes are built and go out of scope, attach them to the parent
  • finish building the parent, emit it when it goes out of scope
  • emit the parent's children after the parent is emitted

Emitting the parent in between finishing building the notes and emitting them breaks the LIFO transaction model.

Having both TransactionStrings and ChildNoteStrings around to hold arguments is definitely less than ideal, but they are at least disjoint because child notes don't get added to a transaction, they're just emitted whenever their parent is.

@owenv owenv force-pushed the diag_child_notes_refactor branch from 4d1eb0c to c076133 Compare August 28, 2019 19:13
@owenv owenv force-pushed the diag_child_notes_refactor branch 2 times, most recently from 8c81fd7 to d74a340 Compare September 16, 2019 15:49
@owenv
Copy link
Contributor Author

owenv commented Sep 16, 2019

@jrose-apple I've updated this PR to use a transaction-based API to group diagnostics, which simplifies argument ownership quite a bit. Do you mind taking another look?

The DiagnosticConsumer interface could still be simplified to take only a DiagnosticInfo after these updates, but I was going to leave that refactor to another PR because a lot of tools use PrintingDiagnosticConsumer in somewhat unconventional ways right now.

Copy link
Contributor

@jrose-apple jrose-apple left a comment

Choose a reason for hiding this comment

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

I like how this turned out, though it's too bad we end up copying things around a little more instead of getting to rely on strings just being alive by virtue of being on the stack when we call diagnose. I think there's still an ownership problem here, though—see the comments.

{
CompoundDiagnosticTransaction transaction(tc.Diags);
tc.diagnose(loc, swift::diag::optional_ambiguous_case_ref,
baseTyName, baseTyUnwrappedName, memberName.str());
Copy link
Contributor

Choose a reason for hiding this comment

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

The separate transaction is a little clunky. What you think about having something like diagnoseWithNotes on DiagnosticEngine that returns an InFlightDiagnosticWithNotes, which inherits from InFlightDiagnostic but also carries a CompoundDiagnosticTransaction?

auto errorDiag = tc.diagnoseWithNotes(…);
tc.diagnose(…);
tc.diagnose(…);

instead of

CompoundDiagnosticTransaction transaction(tc.Diags);
tc.diagnose(…);
tc.diagnose(…);
tc.diagnose(…);

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 agree, this is a really great idea! It strikes a nice balance between the two approaches I've tried so far.

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 turns out this doesn't work out too well because the transaction needs to outlive the parent InFlightDiagnostic(WithNotes). Otherwise we end up keeping the parent alive while diagnosing the notes which breaks a lot of important assumptions.

The best alternative I can come up with at the moment is a buildCompoundDiagnostic helper which accepts a lambda with an implicit transaction (see my second commit). This doesn't reduce the code involved, but it's easier to read the code and understand what's going on.

tc.buildCompoundDiagnostic([&]() {
  tc.diagnose(...);
  tc.diagnose(...);
  tc.diagnose(...);
});

Copy link
Contributor

Choose a reason for hiding this comment

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

Hm. I like the explicit stack scoping, but it still feels like the parent diagnostic should be in the compound action. What about this (on DiagnosticEngine)?

template<typename ...ArgTypes>
void diagnoseWithNotes(
    SourceLoc Loc, Diag<ArgTypes...> ID,
    typename detail::PassArgument<ArgTypes>::type... Args,
    llvm::function_ref<void(void)> builder) {
  CompoundDiagnosticTransaction transaction(*this);
  diagnose(Loc, ID, Args...);
  builder();
}

The one thing I don't like is that you can't add highlights or fix-its this way, but it would be possible to have another overload that took an InFlightDiagnostic as well. Though you'd have to call flush() explicitly there.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, types are only deduced for variadic parameter packs when they come at the end of an argument list though, which makes this kind of unwieldy. On the other hand, moving builder to the front of the list would be pretty unintuitive.

I don't think the inability to add fix-its is that big an issue. If an error has notes, it very rarely has fix-its of its own.

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 ended up updating this to just accept an InFlightDiagnostic, manually specifying template parameters became too annoying.

That allows us to write:

tc.diagnoseWithNotes(
  tc.diagnose(...),
  [&]() {
    tc.diagnose(...);
    tc.diagnose(...);
});

It's not perfect, but I think it's still an improvement

…ror/warning

Add a new type of diagnostic transaction, CompoundDiagnosticTransaction.
The first diagnostic emitted inside the transaction will become the parent of the subsequent notes.
DiagnosticConsumers may opt in to consuming these child notes alongside the parent
diagnostic, or they can continue to consider them seperately.

Moved PrintingDiagnosticConsumer and a couple of diagnostics to the new
system as a proof of concept.
@owenv owenv force-pushed the diag_child_notes_refactor branch 2 times, most recently from 8368b74 to 8d6b60d Compare September 17, 2019 19:04
Copy link
Contributor

@jrose-apple jrose-apple left a comment

Choose a reason for hiding this comment

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

This works for me! @xedin, what do you think of these use sites?

@owenv owenv force-pushed the diag_child_notes_refactor branch from 8d6b60d to 6e1e768 Compare September 17, 2019 20:43
@jrose-apple jrose-apple requested a review from xedin September 17, 2019 20:45
@jrose-apple
Copy link
Contributor

@swift-ci Please smoke test

@owenv
Copy link
Contributor Author

owenv commented Sep 24, 2019

@xedin bump, mind taking a quick look at this when you have a chance?

@owenv
Copy link
Contributor Author

owenv commented Oct 1, 2019

@xedin bump

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.

Sorry I missed this one. Nice improvement! I think time will be a good judge whether we'll have to change something here when we start using this in more places.

@jrose-apple
Copy link
Contributor

Let's do a retest just to be sure.

@swift-ci Please smoke test

@jrose-apple jrose-apple self-assigned this Oct 2, 2019
@owenv
Copy link
Contributor Author

owenv commented Oct 2, 2019

I'm pretty sure the linux CI didn't actually hit an unexpected compile error, it looks like the real failure was in sourcekit-lsp integration testing. It's hard to say for sure whether it's related to this change, I may try to spin up an Ubuntu install later and investigate if it continues to fail.

@jrose-apple
Copy link
Contributor

Let's try again.

@swift-ci Please smoke test Linux

@jrose-apple
Copy link
Contributor

@swift-ci Please smoke test Linux

@jrose-apple
Copy link
Contributor

*grumble*

@swift-ci Please smoke test Linux

@jrose-apple jrose-apple merged commit 5f26958 into swiftlang:master Oct 2, 2019
@jrose-apple
Copy link
Contributor

Thanks, @owenv!

@owenv
Copy link
Contributor Author

owenv commented Oct 2, 2019

🎉🎉🎉 Thanks for keeping an eye on the CI!

@xedin
Copy link
Contributor

xedin commented Oct 2, 2019

@owenv Adoption of the new setup in https://github.com/apple/swift/blob/master/lib/Sema/ConstraintSystem.cpp#L2521 would be a real challenge, so I'd suggest we try to do that next.

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