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
Treat explicit instantiations as full uses #578
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.
Looks great, thanks!
I wonder if the test case would be better if it used individual header files for the different cases, so we can see that cross-file analysis does the right thing? The current test is kind-of a no-brainer -- "here's a bunch of declarations, you're going to be happy with that." (but this test did fail before your change, though, right?)
Well, in this case the header file doesn't play any role, that's why I didn't add any. The result of the test previous to the change suggests the instantiation definition for the ExplicitlyDeclaredAndDefined template to get removed:
Honestly, I haven't analyzed completely why the third declaration and other two end up being treated as full uses (I am guessing something in the semantics changes when one follows the other), but in any case we don't want them to be confused with forward declarations, so I made sure to keep them. |
I think a more typical layout/use of explicit instantiations is to have the template definition and explicit instantiation decl in a header file, and the explicit instantiation defn in a .cpp file. So rather than a minimized test case to provoke this bug, we could put up some representative code. Something like: // explicit_template_instantiation-template.h
// Base template
template<class T>
class Template {};
// Explicit instantiation declaration.
extern template class Template<int>;
// explicit_template_instantiation-short.h
// Another explicit instantiation declaration.
extern template class Template<short>;
// explicit_template_instantiation.cc
#include "explicit_template_instantiation-template.h"
#include "explicit_template_instantiation-short.h"
// Define explicit instantiation
template class Template<int>;
// Some uses
void f() {
// Use the base template
Template<bool> t0;
// Use the locally-defined explicit instantiation
Template<int> t1;
// Use the declared but not defined explicit instantiation
// (I'm guessing this should type-check, but not link?)
// Should make sure the -short.h header is not removed
Template<short> t2;
} I left out the |
I played with it a bit more and I found some other cases where IWYU is not doing the right thing. This needs more work than previously thought.. |
I wonder if it's worth treating that as a separate problem? I've noticed before that The current patch does a nice job of fixing class templates, I guess? |
I think we should leave function templates as a known issue to make some headway with what we have here. |
Yes, I don't think they can be tackled the same way since they are not visited directly, unless Class and Var templates are almost working, but I got stuck for a while. I think I could finalize this tomorrow. |
16bf119
to
fb37b60
Compare
I finally managed to finalize this, for class templates so far. Not sure if we can add something else to the test case. What you mentioned in your example above about verifying that a header file with a explicit instantiation declaration is included is not really possible, since such declaration is not necessary for the usage of template itself. |
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.
What you mentioned in your example above about verifying that a header file
with a explicit instantiation declaration is included is not really possible, since
such declaration is not necessary for the usage of template itself.
But it should be, right? We included a header saying there's a singular instantiation for short
and if that include is removed, we'll implicitly instantiate it, generating more code. Or if the base template has no definition, we'll fail to compile.
I think the tests would benefit from separation like I suggested earlier. Maybe you need to be explicit with As for the |
I guess it makes sense. But, it'll need some work :) For example, looking at the 7 cases below. Should we keep the include for all of them, or only the uses of the same specialization? And only full uses, or also fwd decl uses? What about implicit uses? // file template.h
template<class>
class Template {};
// file template-decl-short.h
extern template class Template<short>;
//file main.cc
#include "template.h"
#include "template-decl-short.h"
template <class T> class FullUseArg { T t; }
template <class T> class FwdDeclUseArg { T* t; }
template <class T = Template<short>> class TemplateAsDefaultFull { T t; };
template <class T = Template<short>> class TemplateAsDefaultFwd { T* t; };
Template<short> s; // 1
template Template<short>; // 2
FullUseArg<Template<short>> fu; // 3
FwdDeclUseArg<Template<short>> fd; // 4
TemplateAsDefaultFull<> td; // 5
TemplateAsDefaultFwd<> td; // 6
Template<int> s; // 7
I would go for 1-3 and 5, but maybe that misses other cases. For example, if this is itself an include and there are only forward decl uses of the template, but it is then fully used somewhere else. Also note, if this is implemented the include 'template-decl-short.h' will be required in all compilation units that use that specialization. Do we want that? |
I think so... Or you mean that it could be transitively satisfied, so the header doesn't need to be included everywhere? From a strict IWYU perspective it would be nice to explicitly include that specialization everywhere it's used because it makes the code resilient to refactors -- if the specialization include is removed from an incidental dependency, our code will silently change meaning (using the base template instead). I find it hard to reason about the seven cases when they're all in the same file, but I'll give it a shot...
I'm not sure this is 100% consistent, but it seems to me IWYU's general rule is that the file that spells out the type should include the type. So template argument defaults should be attributed to the file declaring the template. I can't play this out in my head for the templates above, so not sure how forward-decl vs full uses should behave.
This seems like it should obviously require
Explicit template instantiation definition of
These are tricky. The general rule says we shouldn't need
No use of |
I also think it's hard to reason about this, because I keep confusing "explicit instantiation" with "explicit specialization". Specializations, whether partial or total, alter the behavior of the program if visible to the compilation unit. Explicit instantiations, on the other hand, seem to be more of a build-time optimization: we can use them to help the compiler do separate compilation of templates. The challenge in IWYU is that we want to preserve the behavior of the program and not worsen build times. So it would seem we can never remove a specialization or explicit instantiation unless we can prove that they're unused. I'm pretty sure this is not helping :), I just needed to type it out to understand it better. I'm really out of my depth when it comes to template handling in IWYU. |
fb37b60
to
4829090
Compare
I just added some work-in-progress that already satisifies 1 and 2. For the rest, there is more work to do. Also something I noticed is that IWYU tries very hard to normalize decls and their locations. :) For usual template instantiations that's not a problem (as we usually want the definition), but for explicit instantiation decls, I really had to fight it so that it would leave the decl I pass in to ReportDeclUse alone. I am not happy with the result so far, so I would like to gather more input before I continue with the other cases. |
FYI, I am still working on this. It turned out to be much harder than I thought, but I think I start to see the light at the end of the tunnel :) |
22966d3
to
b2b3686
Compare
I finally managed to finish this. There are a few more commits that in the beginning, so let me explain briefly (more details in the commits themselves): These are the three main changes to make explicit template instantiations work:
These contain some refactoring work that simplifies the implementation:
Supporting change to be able to report specific redecls:
Unrelated bugfix discovered during the implementation:
Also unrelated to my changes. Seems to be failing after a new warning from clang:
|
Very cool! I haven't had a chance to look at the body of the template instantiation logic, but it strikes me that many of the refactors would be nice on their own. Can you separate them out and post them as individual PRs? The problem with huge PRs like this is that they're both hard to review and blocked until all commits are ready to go in, and much of this looks useful even before trying to fix the explicit instantiation problems. I'll actually start off by cherry-picking the |
Is If it doesn't depend on anything else, I think this would be nice to get on master first. |
It seems suspicious that Overall, I feel like if we could break this commit into its constituent parts, maybe there are fragments that could go in earlier and solve smaller problems than the elusive explicit template instantiation :) There's a lot of spurious reformatting in this commit (and maybe others) -- please tread lightly; it's so much harder to review (and blame in the future) when lines are reformatted as part of a functional change. |
b2b3686
to
f66ce3f
Compare
Hey, I extracted the two refactor commits here: #607. I also rebased this branch on top of it to be sure there are no conflicts. Once it is merged I'll rebase again on top of master.
You were right about the whitespace, I messed it up somehow. Should be fixed now! Also made some small changes to ReportFullSymbolUse to hopefully make it clearer.
The main idea here was to pass extra information to the reporting logic that says "please, treat this decl as-is". I went back and forth trying out different ideas and this seemed like the cleanest solution. Although, not perfect by any means :) Right now, the flag is not used inside OneUse for anything else, but I can imagine this might turn out to be useful for other tings. In fact, during the implementation I had to use the UF_TargetRedecl to reimplement The existing UF_FunctionDfn is used to implement In general, it seems to me the reporting side of IWYU |
Yes, it is. I dropped it from the branch and will try to think of a test for it. |
Ah, I see. No, I think a new flag is probably warranted.
I've actually been considering going in the opposite direction -- capture and expose parts of the AST and expose to Some things are hard to classify correctly when traversing the AST because they require cross-examination of multiple structures (can't think of an example now, but So I'm not sure I share the concern that |
f66ce3f
to
8b63152
Compare
Hi, refactors are in and branch rebased.
Yes, that might work too. The main point I was trying to make is that the visiting side is lower level, and as such, it doesn't have the complete picture. However, it does have access to all of the details about the AST. The analysis (reporting/iwyu_output) side, on the other hand, is much higher level, with much more context, but has lost much of the detail from the lower phase. The main problem, as I see it, is that this separation of concerns is not well defined or maintained in the code. For example, the visiting side tries to make high-level decisions about what things to report, whereas the analysis will very often overrule those decisions at a later point after doing some low-level visiting of certain nodes. I think this interplay between the two should be represented with better abstractions. Maybe the analysis phase needs to use the AST directly after all, but I can imagine that a higher-level model of the AST where we expose what we need and hide the rest, could be more benefitial in the long run. |
Exactly, that's what I was trying to say too.
Agreed!
Yes, I think there might be a phase missing to sort out stuff like author intent. That is;
There are abstractions missing, for sure, and some amount of purity where phases don't make too many "forward" assumptions. I.e. visiting the AST should be mostly oblivious to whether a use will count for something (except possibly for And the Clang AST data structures are mostly wonderful to work with, so it would be nice to use them as a lingua franca throughout the pipeline (with some additional decoration like parent maps, etc) instead of inventing our own representations. I'm regularly missing an include graph abstraction, where it's possible to ask about the "path" between two files (e.g. between the main file and |
Yep, all good ideas! :) Sadly, it is not obvious where to start with it all while not breaking anything.. Btw, would you like me to rework the UF_TargetRedecl commit? Or is it clear enough? |
Yes, I think it would be nice if it followed existing form and described the context of the use, rather than telling the next phase what to do. |
Right. The thing is that it is not really a flag for the use, but for the decl associated to it. I could name it UF_ExplicitInstantiation and explain that the use targets one. Another option is to create another set of |
b0469cd
to
66c7c1c
Compare
|
Done. All other comments were addressed too. |
I'd rather review the changes commit-by-commit, so please rewrite the commit history to look the way you want to ship it. I started looking at the total diff, and overall it looks really nice! |
3c30f28
to
5b2e01a
Compare
It is ready. |
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 this incredibly well-factored work! I feel like this really pushes IWYU's understanding of templates forward.
Some nits and questions inline, but this mostly looks good to me.
5b2e01a
to
c5ce8b6
Compare
Thanks for the kind words! |
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 good. Please merge with the small inline suggestions fixed (or not, if you disagree vehemently). Thank you!
@@ -1649,12 +1652,14 @@ class IwyuBaseAstVisitor : public BaseAstVisitor<Derived> { | |||
if (CanIgnoreDecl(target_decl)) | |||
return; | |||
|
|||
const UseFlags use_flags = |
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.
We usually don't use const
for locals, and removing this makes the line fit in colwidth 80
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.
Well, I think is good practice to prevent accidental modifications, especially in long functions. It also helps document the intent.
Teach IsFowardDecl() to not confuse an explicit template instantiation with a forward declaration. Also make sure to report explicit instantiations (declaration or definition) as full uses during visitation. Fixes include-what-you-use#558
To signal that the use refers to an explicit instantiation, for which the "canonical" decl is not suitable. Also, since it is meant to be used in a specific context, the ReportDeclUse prototype has been adapted to take optional extra flags as an additional input.
If the usage of a template has visible explicit instantiations decls, require them to be included along with the main template definition. ScanInstantiatedType has also been changed to make sure that the template being analyzed always corresponds to the definition to make it work as expected when there is an explicit instantiation definition visible. Fixes include-what-you-use#558
TypeToDeclAsWritten would not return the written definition in all cases. For templates, for example, the selected declaration could be the one where it was explicitly instantiatiated. That, in turn, affects the source location that is returned and ultimately the outcome of the function. Make sure to always get to the definition before using it to determine whether the template being instantiated provides the decl. Fixes include-what-you-use#558
c5ce8b6
to
3a6c608
Compare
\o/ |
Modify IsFowardDecl() to check that the decl is not an explicit template instantiation (declaration or definition) This check is also in its own function, IsExplicitInstantiation(), for convenience.