Checks if constructors and assignment operators that are marked '= default' are actually deleted by the compiler.
Details
Diff Detail
- Repository
- rL LLVM
Event Timeline
clang-tidy/readability/DeletedDefaultCheck.cpp | ||
---|---|---|
26 ↗ | (On Diff #53229) | unless(isInTemplateInstantiation()) is the canonical way to do this. |
30 ↗ | (On Diff #53229) | I think, we need at most two matchers here: one for constructors and one for assignment operators. We could also cram these into a single matcher. I also suggest to combine the diag() calls to a single one using %N or %select{}N format to parametrize the message. Something along the lines of: Finder->addMatcher(cxxMethodDecl(isDefaulted(), isDeleted(), unless(isImplicit()), unless(isInTemplateInstantiation()), anyOf(isCopyAssignmentOperator(), isMoveAssignmentOperator(), cxxConstructorDecl().bind("ctor"))).bind("method")); ... const auto *Method = Result.Nodes.getNodeAs<CXXMethodDecl>("method"); assert(Method != nullptr); diag(Method->getLocStart(), "this %select{method|constructor}0 is marked '= default' but is actually " "implicitly deleted, probably because an instance variable or a base " "class is not copyable nor movable; this definition should either be removed " "or explicitly marked as '= delete'") << (Result.Nodes.getNodeAs<Decl>("ctor") ? 1 : 0); If you think that adding "copy", "move" or "default" makes the message any better, this could also be accommodated in this approach. |
31 ↗ | (On Diff #53229) | Alternatively, you can use isDefaulted(), unless(isImplicit()). |
test/clang-tidy/readability-deleted-default.cpp | ||
13 ↗ | (On Diff #53229) | Please specify the message completely once. |
clang-tidy/readability/DeletedDefaultCheck.cpp | ||
---|---|---|
82 ↗ | (On Diff #53229) | nit: CopyAssignment |
I agree that it might be a good idea. However, it doesn't hurt to have this in clang-tidy (at least as a prototype - to figure out details and see how it works on real code) until we have it in Clang. Also, making it a Clang diagnostic might be more difficult, especially without a prior experience.
I honestly don't feel competent right now to write compiler warnings: clang-tidy has a nice, well defined interface. A compiler warning would force me to dig in the internals of clang. But if you feel strongly about it and give me a couple of pointers (like a diff that adds a similar warning, and maybe a pointer to the general area in the code where I would add it), I can maybe give it a try in the next few days/weeks.
Thank you for addressing the comments!
clang-tidy/readability/DeletedDefaultCheck.cpp | ||
---|---|---|
27 ↗ | (On Diff #53257) | What about this comment? |
32 ↗ | (On Diff #53257) | Yes, using standard tools instead of introducing a new one. |
44 ↗ | (On Diff #53257) | A few suggestions to improve the wording:
|
49 ↗ | (On Diff #53257) | If the check() method issues a diagnostic in any case, I'd prefer a slightly different pattern to reduce code duplication a bit more: auto Diag = diag(Constructor->getLocStart(), "....."); if (x) { Diag << "qqq" << "eee"; } ... |
70 ↗ | (On Diff #53257) | nit: No need for this variable. |
72 ↗ | (On Diff #53257) | "marked 'const'" sounds better to me than "marked as const". |
I don't feel sufficiently strongly to insist - clang-tidy's mostly outside
my wheelhouse anyway.
As for how to go about it - my rough approach would be to write a small
test case that calls an implicitly-deleted-but-explicitly-defaulted move
op, run it, check the diagnostic text, find that in DiagnosticSemaKinds,
find where the diagnostic identifier is used in the code, set a breakpoint,
break there - then trace back to when/where it got marked as deleted (find
the member function of the AST node in question that is used to mark it as
deleted - break in there, with the condition that 'this ==' the specific
instance you're looking at, etc). Then see if, at the point at which the
thing is marked as deleted, you can check that it's not dependent (not a
template, etc) and emit the appropriate warning.
- Dave
I hope I answered all comments (sorry if I missed some, I'm not yet used to this UI).
I have an open question about isInTemplateInstantiation, and added a test (since I missed templated function)
clang-tidy/readability/DeletedDefaultCheck.cpp | ||
---|---|---|
27 ↗ | (On Diff #53257) | isInTemplateInstantiation() is a matcher on Stmt only (when hasAncestor is a polymorphic matcher). When using it, it does not compile. So I copied the body here. I don't know how bad that is. |
clang-tidy/readability/DeletedDefaultCheck.cpp | ||
---|---|---|
28 ↗ | (On Diff #53289) | For decls there is isInstantiated(), which is defined as: auto IsInstantiation = decl(anyOf(cxxRecordDecl(isTemplateInstantiation()), functionDecl(isTemplateInstantiation()))); return decl(anyOf(IsInstantiation, hasAncestor(IsInstantiation))); and should be just what you need. |
35 ↗ | (On Diff #53289) | It's not overly important, but you can further reduce the code by folding the first matcher into the second one (move cxxConstructorDecl() inside anyOf here, since CXXConstructorDecl is a CXXMethodDecl). You can also use just a single id to bind the node and distinguish the constructor using dyn_cast. Then you'll be able to use a single diag() call below and remove the unneeded variables Message and isBadlyDefaulted. |
clang-tidy/readability/DeletedDefaultCheck.cpp | ||
---|---|---|
35 ↗ | (On Diff #53289) | Mmh, I'm not convinced it really helps with readability here. I can do it if you insist though. |
Looks good. A couple of comments.
I'm happy to commit the patch for you once you answer the comments.
Thank you for the work!
clang-tidy/readability/DeletedDefaultCheck.cpp | ||
---|---|---|
36 ↗ | (On Diff #53371) | I would at least join matchers, since, I believe, it might be more effective that way. cxxMethodDecl(anyOf(cxxConstructorDecl().bind("constructor"), isCopyAssignmentOperator(), isMoveAssignmentOperator()), isBadlyDefaulted) .bind("assignment") Optionally, you might want to inline the isBadlyDefaulted matcher (without the external allOf matcher). As for restructuring the check() method, I don't insist. |
49 ↗ | (On Diff #53371) | If you don't feel strongly, I'd mildly suggest to turn if { ... return } to a chain of if/else (also the top-level ones). The code will become denser, but it will be easier to see the whole logic in a glance. |
clang-tidy/readability/DeletedDefaultCheck.cpp | ||
---|---|---|
36 ↗ | (On Diff #53371) | I'm a bit confused by this suggestion. It will bind to "assignment" even when it is a constructor. It works because we get "constructor" first in check(), but I find the resulting contract between the check() and registerMatchers awkward. |
49 ↗ | (On Diff #53371) | I thought that in the clang codebase, return were preferred to 'else'. Done. |
I share David's consideration for making this a compiler warning instead of a clang-tidy check. Perhaps it would make sense to gather some data using this check over some large code bases to see how often it triggers. If it's overly chatty (or we discover interesting usage scenarios), then leaving it as a clang-tidy check may make more sense.
clang-tidy/readability/DeletedDefaultCheck.cpp | ||
---|---|---|
38 ↗ | (On Diff #53381) | This is a bit verbose. How about: "%0 is explicitly defaulted but implicitly deleted, probably because %1; definition can either be removed or explicitly deleted" ? |
47 ↗ | (On Diff #53381) | "non-static data member" instead of "instance variable" |
55 ↗ | (On Diff #53381) | s/not/neither |
clang-tidy/readability/DeletedDefaultCheck.cpp | ||
---|---|---|
37 ↗ | (On Diff #53404) | Will it be less confusing to you, if you change "assignment" to something more generic, e.g. "method" or "decl"? Also, if you find it less readable that way, we can leave it as is for now. |
50 ↗ | (On Diff #53404) | I usually prefer early returns, however, in this case I find it better to make the code compact. This way it's also immediately clear that the code does nothing but mapping certain properties of matched objects to a message. |
clang-tidy/readability/DeletedDefaultCheck.cpp | ||
---|---|---|
37 ↗ | (On Diff #53404) | I implemented the requested changes |
I did svn up and reuploaded the diff. I did not see any changes on files I touched, so I'm not sure it worked.
Something is weird with your patch: it has names starting with a space (note two spaces between "Index:" and "clang-tidy/..."):
Index: clang-tidy/readability/CMakeLists.txt =================================================================== --- clang-tidy/readability/CMakeLists.txt +++ clang-tidy/readability/CMakeLists.txt @@ -4,6 +4,7 @@ AvoidConstParamsInDecls.cpp BracesAroundStatementsCheck.cpp ContainerSizeEmptyCheck.cpp + DeletedDefaultCheck.cpp ElseAfterReturnCheck.cpp FunctionSizeCheck.cpp IdentifierNamingCheck.cpp Index: clang-tidy/readability/DeletedDefaultCheck.h ===================================================================
For comparison, a normal diff:
http://reviews.llvm.org/file/data/ldmp5jqxuvect5cwpyer/PHID-FILE-3rnivuajuco4mxhb6nzh/D18962.diff
Index: llvm/trunk/lib/Target/SystemZ/README.txt =================================================================== --- llvm/trunk/lib/Target/SystemZ/README.txt +++ llvm/trunk/lib/Target/SystemZ/README.txt @@ -43,11 +43,6 @@
Sorry, both my 'diff' and my 'svn' command added some magical coloring... It should be good now.
Now that there is a stable warning in clang for this check that is enabled without specifying any warning flags, Is there reason to think about retiring this check?
I'd be fine with that. We may even have the time to get a deprecation note into Clang 12 so that we can remove this code in Clang 13 if we felt so inclined.