-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[Batch mode] Cope with bugs that cause error suppression. #23735
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
Conversation
…agnostic. Unformmated.
@swift-ci please smoke test os x platform |
@swift-ci please smoke test os x platform |
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 can't work as is. We emit diagnostics when we first see a problem, but we might first see a problem for declaration X1 in file X because we're trying to use X1 in file Y. Putting that diagnostic in Y's .dia file isn't correct because it won't always be Y that first tries to use X1.
Please test with following pull request: @swift-ci please smoke test os x platform |
Thanks! But I don't understand. Isn't this PR doing the same thing as in the pre-batch-mode, single-file case? That's what it seems to do in non-batch mode. Also, what is the significance of "first"? Why is that important? |
David and I talked in person (well, over live video) and he convinced me that 1. We won't lose diagnostics this way even if they're not always ascribed to the "right" file due to other bugs; they may move from file to file but won't disappear. 2. In a particularly contrived set of dependencies we might end up with warnings that persist in incremental builds even if you fix the issue, but (a) those would be fixed by clean builds, (b) it's not clear that that contrived case can even be built today, and (c) that's still better than not seeing diagnostics that are present. 3. We already do this when we compile a single file, because that looks the same as "no batch mode" and so we don't try to filter diagnostics in any special way. So I'm taking another look.
ArrayRef<DiagnosticArgument> FormatArgs, | ||
const DiagnosticInfo &Info) = 0; | ||
const DiagnosticInfo &Info, | ||
StringRef currentPrimaryInput) = 0; |
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.
Hm. I feel really weird having a DiagnosticConsumer know about "primary inputs". DiagnosticConsumer is a general interface, and not everything we do has a particular primary file associated with it. Moreover, "primary files" are a Frontend concept, and DiagnosticConsumer is down in AST, so this smells like a conceptual layering violation to me. (Also, I can't remember if whole-module mode uses "primary files" at all.)
An alternate idea I had was to use a SourceLoc as the "current" token, which for now could be the start of the "current" file. I still don't really know what to call it, though.
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.
Thank you for your clear summary of our talk.
I like your thinking about the layering. If we pass in a SourceLoc
, we could call it the diagnosticDestinationOfLastResort
. Or the finalRestingPlace
.
OTOH, it might seem unintuitive to use a SourceLoc
as a stand-in for the .dia
file. Here's another idea: we could pass in the .dia
file of the current primary, but then FileSpecificDiagnosticConsumer would need another (unordered) map. What do you think? Any other ideas?
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.
Another thought, we could call the thing that's passed in to the DiagnosticEngine
, by it filename or location, the diagnosticOrphanage
, since it's where homeless ones go. diagnosticMotel
? (Diagnostics check in but they don't check out.) "Refuge"? I hope all these suggestions will inspire you to think of something really 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.
Let me try it with a location and see how it looks
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 really want to keep knowledge of FileSpecificDiagnosticConsumer out of DiagnosticEngine as much as possible, mostly to preserve separation of concerns. It already feels weird to me to have it track which file is currently being processed.
fallbackBufferLocation
? (I put "buffer" in there to make it clear that the exact SourceLoc isn't relevant.) activeBuffer
? bufferIndirectlyResponsibleForDiagnostic
?
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.
These are good! I'll ponder. Thanks!
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.
Another design alternative would be to not store the fallbackBufferLocation in the DiagnosticEngine, but for it to merely forward the set method to all consumers. The FileSpecificDiagnosticConsumer would be the only one to store it. How does that alternative feel to you?
lib/AST/DiagnosticConsumer.cpp
Outdated
Optional<FileSpecificDiagnosticConsumer::Subconsumer *> subconsumer; | ||
auto subconsumer = findSubconsumerAndRememberItForNotes(SM, Loc, Kind); | ||
if (subconsumer.hasValue() && !(*subconsumer)->getConsumer()) | ||
subconsumer = findSubconsumerForPrimaryCausingErrorInNonprimary( |
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 you need to remember-for-notes in this case too, so I suggest just pushing this into your new findSubconsumerAndRememberItForNotes
, or even subconsumerForLocation
.
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 agree, and I was under the impression that the code in this PR does that. Specifically,findSubconsumerForPrimaryCausingErrorInNonprimary
returns through findSubconsumerAndRememberItForNotes
. I see why it was misleading, though I don't have a better alternative.
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.
If you think it's clearer, findSubconsumerForPrimaryCausingErrorInNonprimary
could return a possibly-invalid SourceLoc
. And the call to findSubconsumerAndRememberItForNotes
could then be moved up into handleDiagnostic
.
Hmm... I have an idea for a better refactoring. Will try it out.
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.
Oh, whoops. I read the code and didn't take that in at all because of the name.
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.
But your reaction was good because it led to clearer code!
3e276a0
to
b112f73
Compare
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.
Thanks, this factoring of the consumer-finding makes more sense to me too.
lib/AST/DiagnosticConsumer.cpp
Outdated
} | ||
|
||
Optional<FileSpecificDiagnosticConsumer::Subconsumer *> | ||
FileSpecificDiagnosticConsumer::findSubconsumerForAnyKind( |
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'd just drop the "ForAnyKind" here. This is the general entry point for finding a consumer; the other one specifically calls out the "non-note"-ness. I guess it is edging a little close to the lower-level subconsumerForLocation
, but the longer name isn't addressing that part anyway.
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! I tried thinking of a name including "AndMakingSureThatNotesWouldBeInTheSamePlaceAsDiagnostics" but I couldn't find anything I liked.
Glad the new factoring works. I have a bug to track down, then I'll rerun the tests. |
No more vacuous subconsumers. Output into active primary or everywhere.
The new refactoring led to a more complete change for how we handle non-primaries. I've pushed that so you can see it. Still plan to rename |
Please test with following pull request: @swift-ci please smoke test os x platform |
Please test with following pull request: @swift-ci please smoke test os x platform |
@jrose-apple I've finished the changes we spoke of, and also they led to a redoing on the subconsumer invariants. (Consumer cannot not be null anymore.) Check it out at your convenience and let me know. (Thanks for the great name!) Also, I'm starting to look at the testing aspect. I want to include a test before landing this. Thanks. |
Please test with following pull request: @swift-ci please smoke test os x platform |
1 similar comment
Please test with following pull request: @swift-ci please smoke test os x platform |
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 reasonable. It needs a new test, though, even if it's a new case in unittests/ that's contrived.
StringRef FormatString, | ||
ArrayRef<DiagnosticArgument> FormatArgs, | ||
const DiagnosticInfo &Info, | ||
SourceLoc bufferIndirectlyCausingDiagnostic) = 0; |
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.
Can you document the parameter too? It's pretty subtle for someone writing their own DiagnosticConsumer. (It may even be worth mentioning that it's safe to ignore if your consumer doesn't do anything special with different files.)
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.
You could mention setBufferIndirectlyCausingDiagnosticToInput
too.
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.
Good idea! Done, but a little verbosely.
include/swift/AST/DiagnosticEngine.h
Outdated
void setBufferIndirectlyCausingDiagnosticToInput( | ||
StringRef defaultDiagnosticInputFile); | ||
void resetBufferIndirectlyCausingDiagnostic(); | ||
SourceLoc getDefaultDiagnostLoc() const { |
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.
Typo: "Diagnost".
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.
Thanks!! Fixing it.
include/swift/AST/DiagnosticEngine.h
Outdated
/// a non-primary file, use this affordance to place it in the .dia | ||
/// file for the primary that is currently being worked on. | ||
void setBufferIndirectlyCausingDiagnosticToInput( | ||
StringRef defaultDiagnosticInputFile); |
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.
Hm, why make the DiagnosticEngine do the string lookup? It feels to me like DiagnosticEngine API ought to traffic in SourceLocs.
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.
The alternatives that I see are:
- Expose the
DiagnosticEngine::SourcMgr
instance variable (via a get method?) so that a method in the RAII struct can use it do to the string lookup. - Do the lookup in every caller of the RAII class (two, so far).
- Pass in a SourceManager to the RAII constructor.
Which, if any, are better than what's there now?
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 was thinking # 2 is the best because we probably don't have to do a string lookup in all cases. In particular, SourceFile::getBufferID is available in all cases, right?
(I guess we could make this interface take a buffer ID too, but I don't like that either because it doesn't have a strong type today, and it's also not a type we traffic in in many parts of the compiler. The other advantage of a SourceLoc is that we could some day use it for something more specific.)
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 tried a couple of alternatives. As a result, I quite dislike #2 because we end up with too many lines (for my taste) of duplicated code. Rather, prefer moving the functionality in question to the RAII object, which is sort of a go-between anyway. Also, following your suggestion, the id is obtained without using the filename. So the RAII object gets a SourceFile
, translates that to a SourceLoc
, and passes that in to the DiagnosticEngine
. This layering feels right to me. If you think it best, the RAII definitions and declarations can be moved out of DiagnosticEngine.*. Just tell me where to put them & I'm happy to do it.
lib/AST/DiagnosticConsumer.cpp
Outdated
@@ -118,6 +118,11 @@ FileSpecificDiagnosticConsumer::subconsumerForLocation(SourceManager &SM, | |||
if (loc.isInvalid()) | |||
return None; | |||
|
|||
// What if a there's a consumer for fixits but there is no fixits output path? |
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.
Nitpick: "fix-its". Also, I don't understand the point here—what makes fix-it consumers different from serializing consumers?
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.
Tx for the nitpick. I've clarified the comment, hope it works better now.
lib/FrontendTool/FrontendTool.cpp
Outdated
@@ -1218,6 +1219,9 @@ static bool performCompileStepsPostSILGen( | |||
SILOptions &SILOpts = Invocation.getSILOptions(); | |||
IRGenOptions &IRGenOpts = Invocation.getIRGenOptions(); | |||
|
|||
BufferIndirectlyCausingDiagnosticRAII cpi(Context.Diags, | |||
PSPs.MainInputFilenameForDebugInfo); |
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.
Eek. Now it's no longer "for debug info". I don't think this is safe to use because the name for debug info may not be the same as the name of the buffer.
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.
Thanks--great catch! I switched it to use the MSF when it's a source file. Does that look right?
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.
Tx.
lib/FrontendTool/FrontendTool.cpp
Outdated
// multiple times. So, create a diagnostic "eater" for those non-primary | ||
// files. | ||
// | ||
// This routine gets called for \c createJSONFixItDiagnosticConsumerIfNeeded |
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 seems overly specific. "This routine gets called in cases where…" seems fine to me.
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!
lib/FrontendTool/FrontendTool.cpp
Outdated
@@ -1218,6 +1219,11 @@ static bool performCompileStepsPostSILGen( | |||
SILOptions &SILOpts = Invocation.getSILOptions(); | |||
IRGenOptions &IRGenOpts = Invocation.getIRGenOptions(); | |||
|
|||
BufferIndirectlyCausingDiagnosticRAII ricd( |
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.
In case it's useful, here's the idiom for an RAII you may or may not want:
Optional<BufferIndirectlyCausingDiagnosticRAII> bicd;
if (condition)
bicd.emplace(Context.Diags, "foo");
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.
Thanks, that's better. I 'll use it.
Regarding testing, see https://github.com/apple/swift/pull/23735/files#diff-43eb268d973af29cc397746d4ed96b7a , which shows a modified test that is part of this PR. The test fails with the status quo and passes with this fix. Is that enough? |
Please test with following pull request: @swift-ci please smoke test os x platform |
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.
Implementation looks good. :-) All remaining comments are polish.
lib/AST/DiagnosticConsumer.cpp
Outdated
@@ -118,6 +118,12 @@ FileSpecificDiagnosticConsumer::subconsumerForLocation(SourceManager &SM, | |||
if (loc.isInvalid()) | |||
return None; | |||
|
|||
// What if a there's a FileSpecificDiagnosticConsumer but there are no | |||
// subconsumers in it? (This situation obtains for the fix-its |
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.
Typo: "obtains" instead of "occurs".
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 meant "obtains", but "occurs" is better. Fixed, tx.
: Diags(SF.getASTContext().Diags) { | ||
auto id = SF.getBufferID(); | ||
if (!id) | ||
return; |
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.
Nitpick: This won't come up in practice because you only modify the setting using RAII, but otherwise: should it return? Or should it reset the buffer instead?
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.
Yes, it was reset in an earlier draft. Hmm... This question feels like it illuminates a flaw: We don't account for nested RAII's. Rather than implement a push-down stack for the buffer, let me try just outlawing such nesting for today with an assertion. That will at least detect the occurrence of such a situation.
@@ -1,18 +0,0 @@ | |||
// Ensure that an error in a primary causes an error in the errorless primary. |
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 test is still relevant, isn't it?
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 see, it is partly still relevant because it tests an erroneous primary. I'll modify it and put it back. Thanks for the catch! (Although we may have other tests that cover this.)
@@ -1,16 +0,0 @@ | |||
// Ensure that an error in a non-primary causes an error in the errorless primary. |
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 can't tell if this one is still relevant or not.
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 don't think it is still needed because it checks for:
- The absence of "compilation stopped by errors in other files", which is a string which is not in the compiler at all anymore, AFAICT.
- The empty.dia file existing and being empty, but this same check is in the remaining test,
serialized-diagnostics-batch-mode-nonprimary-errors-only.swift
.
Summary: one predicate is meaningless today because the code is no longer in the compiler, and the other is covered by the remaining 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.
I guess I feel like it's still a slightly different configuration even if it doesn't test any new code paths. But I'll defer to you on this.
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.
Thanks. You have a point, but I think I'm confident that this test doesn't carry it's weight.
Please test with following pull request: @swift-ci please smoke test linux platform |
Please test with following pull request: @swift-ci please smoke test os x platform |
4c8722c
to
6d79bed
Compare
Please test with following pull request: @swift-ci please smoke test linux platform |
PS: Thanks for that "Implementation looks good"! |
Please test with following pull request: @swift-ci please smoke test os x platform |
[Batch mode] Cope with bugs that cause error suppression.
…sion-fix-swift-5.1-branch [Batch mode] Merge pull request #23735 -rdar-40167351
[Batch mode] Cope with bugs that cause error suppression. # Conflicts: # include/swift/AST/DiagnosticConsumer.h # lib/FrontendTool/FrontendTool.cpp # tools/libSwiftSyntaxParser/libSwiftSyntaxParser.cpp
In batch mode, any diagnostics located in non-primary files are suppressed, with the expectation that they will surface when the containing file is compiled as primary. Although this assumption should hold, it does not always. For example, certain inputs can interact with associated type inferencing to violate it.
This PR tracks the current primary input in the
DiagnosticEngine,
which passed it down to theDiagnosticConsumer.
TheFileSpecificDiagnosticConsumer
uses this information to output the diagnostic into the appropriate primary's.dia
file. It addresses rdar://49303574.Needs apple/swift-lldb#1431