-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[WIP] Improve ClangImporter Diagnostics #39923
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
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 like this approach a lot better. I have two comments, though:
- It would be nice to factor out the logic for diagnosing errors into helper methods. You could do something as fine grained as
diagnoseInvokedCFunctionNotImported(...)
or something likediagnoseImportFailures(...)
. Anyway, that can come later. - I think the biggest problem you're going to see is that these errors are emitted whenever we import an entire decl. So, if we ask for all the members of a record, you're going to get an error that the user tried to call a function which we failed to import (which they didn't). How do we solve this? There are two solutions that come to mind: either we could ensure that we only every lazily load types. This shouldn't actually be too hard, and I have patches open to implement this everywhere that it matters (which you're welcome to take over, or I could try to prioritize landing them). Alternatively, you could hook into the requests that are issued for direct lookups and enable certain diagnostics in the clang importer only when a direct lookup request is evaluated. This is the strategy I was showing you on our call yesterday. Essentially, whenever a user asks "please lookup this function for me" the compiler goes to look it up. If we find a clang decl for that function, then enable these diagnostics (maybe with a filter for certain types/decls if it gets too loud), then disable them again after we import (or fail to import) the decl/type. I'm happy to discuss this in more depth if you'd like.
Sure 👍
Yeah, we were discussing this problem earlier. I think I plan to take it situation by situation. For the diagnostics I've been adding thus far, it has been quite lazy. The only eager importing I've faced, as you mention is Obj-C classes are on occasion fetched in bulk. I don't really want to commit to getting involved in making the ClangImporter lazier, at least not at this point. If I get this project finished and there is extra time I would be interested though. At least initially, my strategy would be to identify eager loading and make sure we don't suppress the relevant diagnostics. Reporting diagnostics for nodes the user didn't use is by no means new to the ClangImporter, so if there is a little extra noise, I can live with that. If it really ends up being too much noise, I will try the filtering strategy you propose. I need to look at the details, I'm just not sure how easy it will be to pass the diagnostic filter information down the the ClangImporter. We could just manipulate the DiagnosticEngine itself, but that seems like a bad idea. |
Just leaving a note here for the future: I am using this PR as a way to share and discuss my approach, I don't expect an in-depth review, nor do I intend to merge this. |
583d12b
to
f00949d
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.
When things are more crystalized, I would recommend adding a little bit of commenting here and there to explain why in some cases the notes are iterated, and why in some other cases they are consumed. Without the context, someone that has to work on this 6 months later is going to find those parts confusing.
f00949d
to
07b3d56
Compare
Overall StructureThere are a few mechanisms I've introduced for controlling diagnostic emission that I'd like to explain. The overall diagnostic structure:
Next, there are three mechanisms in place to ensure diagnostics are only displayed once per directly accessed ClangDecl. That is the user should not see diagnostics relating to entities they have not directly imported and they should only see these diagnostics once. diagnosticTarget This is a field on the ClangImporter that is used to indicate the Clang entity we are interested in showing diagnostics about. This is most useful when the ClangImporter is reentrant. Consider importing a C struct. A name lookup is performed, and the diagnostic target is set to the Clang node corresponding to the C struct. While importing this C struct, the ClangImporter will import each field. We do not want to produce errors for each unimportable field until the user explicitly accesses them (until the diagnostic target points at them). Notes include the diagnostic target that was set when they were produced. This allows error to filter out notes that were produced for a different target but not yet consumed. eagerImportActive The task of only reporting diagnostics about used entities is made infinitely easier by lazy loading. However, sometimes the same direct import method used for a lazy load is used repeatedly for an eager load. For eager loads we usually want to suppress diagnostics, as eager loads contain imports the user did not explicitly as for and are usually only interested in successful imports anyways. We use the eagerImportActive boolean flag to indicate in methods like Visited diagnosticTargets A set keeping track of the diagnostic targets for which diagnostics were already produced. This allows us to eliminate repetition and avoid accessing cached import results when we know diagnostics for the entity have not yet been displayed. Outstanding IssuesUntil recently, I did all my initial testing and debugging on small scale examples invoking the swift-frontend manually. This is convenient as it reduces debugging noise. As I've learned, this can be problematic as the configuration of the swift-frontend can be different. I have since discovered two related issues that I am still working through. They are as follows:
I have tried a few approaches, but I am proposing the following. We modify the start of DirectLookupRequest::evaluate to roughly perform the following
|
I may have solved the aforementioned issues, patch update soon. |
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 looks really great. I only had time for these comments, but I will go over this again, and expand on my comments surrounding eagerImportActive
and the non-linear flow.
Anyway, the bottom line is this looks good. The errors look super helpful, they are very clear and are in the correct place. That's great! Nice job :)
test/ClangImporter/experimental_diagnostics_incomplete_types.swift
Outdated
Show resolved
Hide resolved
test/ClangImporter/experimental_diagnostics_incomplete_types.swift
Outdated
Show resolved
Hide resolved
07b3d56
to
3175c37
Compare
...
I thought about this a bit, and realized something from earlier. We went into this whole thing with the mindset that To step back a bit, we really have three different ways we can represent a thing here. One is as a reference (in the abstract, we can think of this as a name or a string, but concretely, I mean a Okay, so, where in the compiler do we have all of these representations? Unfortunately, not in What's the problem with "putting this logic" in What do you think of this idea? Do you think it might help get rid of |
983a76f
to
9af39fe
Compare
9af39fe
to
52f4ec5
Compare
Hi everyone, In the lastest commit, I have rewritten the improvements to the ClangImporter diagnostics. The behaviour is largely unchanged (existing tests were not modified), but the implementation is entirely different. Here I include a brief summary of the changes --let me know what you think -- any questions or feedback are appreciated. GoalsAt a high level the my goal with these changes is:
Design ChangesThe implementation changes themselves are as follows. Diagnostics are now stored instead of being emitted immediatley. The ClangImporter::Implementation now has this new API allowing clients to diagnose a declaration that couldn't be imported, either by name or by ClangNode directly, whichever is more convenient. More on this later. /// Emit any import diagnostics associated with the given name.
void diagnoseValue(SwiftLookupTable &table, DeclName name);
/// Emit any import diagnostics associated with the given Clang node.
void diagnoseTargetDirectly(ImportDiagnosticTarget target); The diagnostics themselves are all associated with a particular ClangNode. There is typically a natural ClangNode to attach to (ex: Failed to import a function, attach a diagnostic to the function). Diagnostics are deduplicated by insisting that each emitted diagnostic has a unique (SourceLocation, DiagID, ClangNode) combination. This means it does not matter how many times or when a ClangNode is visited by the ClangImporter. When either of the above methods are called, diagnostics attached to the target node, or any of its descendants* are emitted. To accomplish this, I traverse the Clang AST starting at the target node using what I am calling a DiagnosticWalker. * It is not really a complete traversal, the ClangImporter sometimes bails early which is reflected in the traversal. See Observed BenefitsOverall, the design is in my opinion simpler and more flexible. For example, the previous solution required the occasional bypass of a cache to ensure diagnostics are produced. This solution does not care if the ClangImporter caches or not. Similarly, while these changes are implemented on top of @zoecarver's changes to make the ClangImporter lazier, they do not depend on this lazy behaviour any longer. Even if the ClangImporter was entirely eager, we could still call Lastly, we have much more control over what diagnostics are emitted since we control the traversal of the ClangAST. If tomorrow we decided that a failed import of a struct should only display diagnostics for fields who's names starts with 'A', we could do that without modifying the rest of the ClangImporter. Remaining ChangesThe initial motivation behind these changes was @zoecarver's great suggestion to tie the emission of diagnostics to the existence of a referencing Swift AST node. I have mostly done this, in that calls to Misc Additions
|
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.
Really, really nicely done. This looks awesome.
I didn't do an in-depth review, but I'll try to do that later this week. I really like this new direction, it looks great.
@swift-ci please smoke test. |
@NuriAmari I'm really looking forward to landing this :) Thanks for all your work on it. I appreciate you spending the time to implement it correctly. |
UpdateCR Revision "4" addresses oustanding CR comments. CR Revision "5" introduces another flag for the eager displaying of diagnostics:
Side CommentIn this PR, Becca makes effort to reduce reentrancy and reduce the number of entry points into the ClangImporter. I haven't looked at the details, but if working on this has taught me anything, it's that this would be beneficial. It's quite difficult to implement solutions that work for a number of different types of imports when every one handled slightly differently. Any unification of API would help. |
test/ClangImporter/experimental_diagnostics_incomplete_types_negative.swift
Show resolved
Hide resolved
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.
Please address the remaining comments, but other than that, I think it's ready.
@swift-ci please smoke test. |
@swift-ci please test Windows. |
This patch introduces new diagnostics to the ClangImporter to help explain why certain C, Objective-C or C++ declarations fail to import into Swift. This patch includes new diagnostics for the following entities: - C functions - C struct fields - Macros - Objective-C properties - Objective-C methods In particular, notes are attached to indicate when any of the above entities fail to import as a result of refering an incomplete (only forward declared) type. The new diangostics are hidden behind two new flags, -enable-experimental-clang-importer-diagnostics and -enable-experimental-eager-clang-module-diagnostics. The first flag emits diagnostics lazily, while the second eagerly imports all declarations visible from loaded Clang modules. The first flag is intended for day to day swiftc use, the second for module linting or debugging the importer.
2b63fee
to
130f2de
Compare
@swift-ci please smoke 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.
This looks great. I'll try to land it tomorrow.
Thanks again @NuriAmari!
@swift-ci please smoke test. |
@swift-ci please smoke macOS. |
@swift-ci please smoke macOS platform. |
@swift-ci Please smoke test macOS platform |
2 similar comments
@swift-ci Please smoke test macOS platform |
@swift-ci Please smoke test macOS platform |
@NuriAmari: thanks a lot for all the work! This looks amazing and I hope a lot of people find it useful. I know we will. PD: I took my PTO seriously this year, so sorry for not chiming it earlier. |
In this diff, I took some of our discussion and made a small example using C functions. There are many ways to expand upon this strategy, this is just a baseline. High level summary:
Avoid wrapping return values in structs to bubble up diagnostics
Use the fatal errors to suppress further diagnostics
Overall, I think this strategy or one derived from it gives us a little less control, but I think its easier to work with and avoids some of the big problems we've been having with stubs