This is an archive of the discontinued LLVM Phabricator instance.

[PATCH] [clang-tidy] Add inconsistent declaration parameter name check
ClosedPublic

Authored by piotrdz on Aug 28 2015, 6:09 PM.

Details

Reviewers
alexfh
Summary

This is first of series of patches, porting code from my project colobot-lint, as I mentioned recently in cfe-dev mailing list.

This patch adds a new check in readability module: readability-inconsistent-declaration-parameter-name. I also added appropriate testcases and documentation.

I chose readability module, as it seems it is the best place for it.

I think I followed the rules of LLVM coding guideline, but I may have missed something, as I usually use other code formatting style.

Diff Detail

Repository
rL LLVM

Event Timeline

piotrdz updated this revision to Diff 33499.Aug 28 2015, 6:09 PM
piotrdz retitled this revision from to [PATCH] [clang-tidy] Add inconsistent declaration parameter name check.
piotrdz updated this object.
piotrdz set the repository for this revision to rL LLVM.
piotrdz added a subscriber: cfe-commits.
piotrdz updated this revision to Diff 33527.Aug 30 2015, 3:41 AM
piotrdz removed rL LLVM as the repository for this revision.

I noticed a few things I should have done better:

  • use std::set instead of std::unordered_set (it doesn't seem to be used elsewhere)
  • move checking set of reported functions to just before emitting diagnostic
  • improved documentation
  • changed diff directory to inside clang-tools-extra repository
alexfh edited edge metadata.Aug 30 2015, 3:52 PM

Thanks for contributing the new check!

This check seems pretty similar to the check implemented in clang-tidy/readability/NamedParameterCheck.h/.cpp. Having both doesn't make much sense, so one of them will have to die (probably, the other one). Nothing should be done to this effect now, we'll figure out after this check is polished enough.

See a few other comments inline.

clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp
23 ↗(On Diff #33527)

Maybe use llvm::Optional<SourceLocation>? Or, if you don't need to store invalid locations, just use SourceLocation and use SourceLocation() as a magic value?

33 ↗(On Diff #33527)

Why do you need a struct? It's not Java, free functions are totally fine.

43 ↗(On Diff #33527)

Clang-tidy takes care of filtering out non-user code, so there's no need to do this in the check. There's even a special mode for running clang-tidy on system libraries (for library developers), which this condition will interfere with.

53 ↗(On Diff #33527)

We use auto when the type is obvious from the RHS (e.g. line 49 above) or when the specific type is complex and not particularly helpful (e.g. when iterating over std::map<>, or for iterators in general). In almost all other cases the specific type is preferred.

54 ↗(On Diff #33527)

nit: I'd prefer to use early return here:

if (!CheckResult.HasInconsistentParams)
  return;
56 ↗(On Diff #33527)

This could be stored more effectively as a set of pointers to canonical declarations (llvm::DenseSet<FunctionDecl*>).

59 ↗(On Diff #33527)

The message could be more helpful, if it contained the differing names of the parameters.

A significant improvement would be to propose a fix when it's more or less clear what the correct name of each parameter should be (e.g. 2 of 3 declarations have the same parameter names, and the third one has different names, or we could consider parameter names used in the definition correct). Without a fix, the value of the check will be much lower. In my experience, developers are reluctant to fix readability warnings manually.

81 ↗(On Diff #33527)

Code formatting is sub-optimal here. Could you clang-format the code?

clang-tidy/readability/InconsistentDeclarationParameterNameCheck.h
22 ↗(On Diff #33527)

It seems that the documentation here is different from the one in the .rst file. I'd remove the documentation from here (after syncing the recent changes to .rst, if needed) and just leave a one-sentence summary and the URL where the .rst document is going to be published (http://clang.llvm.org/extra/clang-tidy/checks/<check-name>.html).

64 ↗(On Diff #33527)

If you don't need these to be sorted, you can use llvm::StringSet<> which should be more effective.

test/clang-tidy/readability-inconsistent-declaration-parameter-name.cpp
1 ↗(On Diff #33527)

Please add tests for special functions (constructor, copy operator), implicit functions, template functions with several instantiations, function declarations in macros.

@alexfh: Thanks for reviewing my code. The amount of comments makes it pretty clear that it's my first contribution here :-). But no worries, I'll address each issue.

This check seems pretty similar to the check implemented in clang-tidy/readability/NamedParameterCheck.h/.cpp. Having both doesn't make much sense, so one of them will have to die (probably, the other one). Nothing should be done to this effect now, we'll figure out after this check is polished enough.

Ah, I only now noticed it. It's kind of "opposite" to my check - it checks for unnamed parameters that should have their names in comments, while my check completely ignores unnamed parameters and checks the named ones. Anyways, I agree, that it would be good to merge them and have one check do both things.

piotrdz marked 7 inline comments as done.Aug 31 2015, 1:11 PM
piotrdz added inline comments.
clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp
23 ↗(On Diff #33527)

In the end, I used SourceLocation.

33 ↗(On Diff #33527)

That was my sort of "ingenious" workaround for not being able to do this:

namespace {
void checkForInconsitentDeclarationParameters(MatchFinder *Finder); // declaration
}
void checkForInconsitentDeclarationParameters(MatchFinder *Finder) { /*..*/ } // definition

But I suppose we're better off without such inventions ;). So I moved it all into one anonymous namespace.

53 ↗(On Diff #33527)

All right, I didn't know about such rule here. In my colobot-lint code I prefer the use of auto whenever possible, to isolate my code from possible API changes. But here, as part of Clang project itself, I guess it's not necessary.

59 ↗(On Diff #33527)

All right, I added note messages about parameter names.

As to adding FixIt hints, I also think they're more useful than simple diagnostic, but in my opinion, it can be difficult to tell what is the intended name in given case. I think there are two cases we can consider:

  • If we see the definition of function in current translation unit, we may assume that the most up-to-date parameter names are those of the definition, since they are the closest to actual code that uses them in some context. So the proposed fix would be to update all other declarations we see to match the definition.
  • If we see only declarations in current translation unit, things start getting difficult, as we (usually) have no way to tell which declaration is considered more important than any other. In such case, I would not propose any hints and leave it up to the user to decide what to do.
clang-tidy/readability/InconsistentDeclarationParameterNameCheck.h
64 ↗(On Diff #33527)

I went with llvm::DenseSet<>, as suggested below

test/clang-tidy/readability-inconsistent-declaration-parameter-name.cpp
1 ↗(On Diff #33527)

Thanks for pointing it out. I found there are some problems with formatting note messages with templates, but I don't know how to solve them yet :-(

piotrdz updated this revision to Diff 33619.Aug 31 2015, 1:12 PM
piotrdz edited edge metadata.
piotrdz marked 3 inline comments as done.

Applied fixes for most issues found in review.

piotrdz marked an inline comment as done.Aug 31 2015, 1:16 PM

Now this issue with templates is a bit difficult for me. I tried everything that seems to have made sense, but I still get output like this:

/work/clang-trunk/clang-tools-extra/test/clang-tidy/readability-inconsistent-declaration-parameter-name.cpp:47:6: warning: function 'templateFunction' has other declaration with different parameter name [readability-inconsistent-declaration-parameter-name]
void templateFunction(float b)
     ^
/work/clang-trunk/clang-tools-extra/test/clang-tidy/readability-inconsistent-declaration-parameter-name.cpp:47:6: note: other declaration seen here
void templateFunction(float b)
     ^
/work/clang-trunk/clang-tools-extra/test/clang-tidy/readability-inconsistent-declaration-parameter-name.cpp:47:29: note: parameter 1 is named 'b' here, but 'a' in other declaration
void templateFunction(float b)
                            ^

But I'd like to get output like this:

/work/clang-trunk/clang-tools-extra/test/clang-tidy/readability-inconsistent-declaration-parameter-name.cpp:47:6: warning: function 'templateFunction<float>' has other declaration with different parameter name [readability-inconsistent-declaration-parameter-name]
void templateFunction(float b)
     ^
/work/clang-trunk/clang-tools-extra/test/clang-tidy/readability-inconsistent-declaration-parameter-name.cpp:47:6: note: other declaration seen here
void templateFunction(T a);
     ^
/work/clang-trunk/clang-tools-extra/test/clang-tidy/readability-inconsistent-declaration-parameter-name.cpp:47:29: note: parameter 1 is named 'b' here, but 'a' in other declaration
void templateFunction(float b)
                            ^
piotrdz updated this revision to Diff 33879.Sep 2 2015, 4:37 PM

In this third version I did the following:

  • fixed problems which I noticed with template specializations,
  • changed output diagnostics to be more detailed, covering multiple declarations
  • added FixIt hints to refactor inconsistent declarations to parameter names we see in definition
  • added new template-related testcases and extended existing ones with checking of note diagnostics and FixIt hints
alexfh added a comment.Sep 4 2015, 2:42 PM

Sorry for the long delay.

This version looks significantly better. Thank you for the updates!

clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp
1 ↗(On Diff #33879)

Seems like clang-format (or something else) broke this comment.

17 ↗(On Diff #33879)

nit: If you move this below namespace clang {, you can omit the clang:: part.

38 ↗(On Diff #33879)

I think, llvm:: is not needed here, as StringRef should also be declared in namespace clang.

86 ↗(On Diff #33879)

Please use proper Capitalization and punctuation. http://llvm.org/docs/CodingStandards.html#commenting

Same in other comments.

180 ↗(On Diff #33879)

That can quickly become extremely chatty. Maybe cramp two lists of names in a single message? Something along the lines of: "parameter names here: (a, b, c, d), in the other declaration: (q, u, x)"

clang-tidy/readability/InconsistentDeclarationParameterNameCheck.h
24 ↗(On Diff #33879)

nit: I'd say "For detailed documentation see:"

piotrdz updated this revision to Diff 34113.Sep 5 2015, 3:29 PM
piotrdz marked 6 inline comments as done.

I addressed all latest review issues.

Now that I fixed all review issues, I think this version would be acceptable for commit. @alexfh: do you agree?
There are of course two outstanding issues marked with TODO and FIXME comments, but these are areas of improvement. I would like to first discuss the best solution to them, before attempting to fix them in code.

alexfh added a comment.Sep 7 2015, 6:11 AM

Now that I fixed all review issues, I think this version would be acceptable for commit. @alexfh: do you agree?

Almost. I've found a few more minor issues and also found out that the warnings could structured in a more useful way. See the inline comments.

Thanks for working on this!

clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp
60 ↗(On Diff #34113)

That's what I would do as well: the parameter names from the function definition can be the source of truth (maybe only when they are actually _used_ inside the function body, because unused arguments don't tell us much), everything else is more likely to be outdated.

106 ↗(On Diff #34113)

I don't see why we couldn't just ignore compiler-generated template specializations (hand-written ones should probably be handled). It should be easy to filter them out by adding unless(isInTemplateInstantiation()) inside the functionDecl matcher.

141 ↗(On Diff #34113)

nit: I'd put the comment right before the return.

147 ↗(On Diff #34113)

nit: I think, callable variables should be named more like functions, so ChooseParamName would be better.

148 ↗(On Diff #34113)

It's preferred to use SmallVector<> and raw_svector_ostream in LLVM code to avoid extra heap allocations.

172 ↗(On Diff #34113)

nit: It's better to use const auto * to avoid repeating the type name.

174 ↗(On Diff #34113)

This should never happen, since the type of the getNodeAs template argument is the same as the type of the matched node with this identifier, and there's no optional branches in the matcher. This check is redundant.

188 ↗(On Diff #34113)

nit: If you put the comment above the statement, you'll avoid this weird line break.

196 ↗(On Diff #34113)

Why is the cast needed here?

213 ↗(On Diff #34113)

Is there a specific reason to specify the return type here (-> StringRef)?

227 ↗(On Diff #34113)

I don't think we can choose any of the declarations as the source of truth. Choosing more frequently used parameter names could work, if there are more than two declarations and just one of them has different names, but this seems to be a corner case rather than a regular practice to have more than two declarations.

This TODO seems to be the best solution for now.

clang-tidy/readability/InconsistentDeclarationParameterNameCheck.h
1 ↗(On Diff #34113)

nit: Please fit the line to 80 characters by removing extra minuses.

21 ↗(On Diff #34113)

nit: Please add trailing period.

test/clang-tidy/readability-inconsistent-declaration-parameter-name.cpp
21 ↗(On Diff #34113)

I've just noticed that the warning appears on the function definition in this case. It would be more logical to issue a separate warning per each declaration where we think the parameter names are wrong. It's easier for the user to understand when the warning shown is where the (likely) incorrect code is, and when the fix is local to the warning (and not applied somewhere in a different file).

It also works better with features like -line-filter and the clang-tidy-diff.py script.

Here's how it would look like:

a.h:
...
10| void f(int x);
...

b.h:
...
20| void f(int y);
...

file.cpp:
...
30| void f(int z) {}
...

// a.h:10:6: warning: function 'f' has a definition with different parameter name(s) ('z')
// the fix for a.h is attached to this warning
// file.cpp:30:6: note: function 'f' defined here

// b.h:20:6: warning: function 'f' has a definition with different parameter name(s) ('z')
// the fix for b.h is attached to this warning
// file.cpp:30:6: note: function 'f' defined here
43 ↗(On Diff #34113)

nit: To make the test more readable, it's better to truncate warning messages in CHECK-MESSAGES lines leaving all dynamic information and a bit of text to make the message recognizable. The whole message should be present once in the test file.

piotrdz updated this revision to Diff 34267.Sep 8 2015, 2:52 PM
piotrdz marked 10 inline comments as done.

I hope this is the final re-write of my code. In this version, I addressed most recent review comments, while also refactoring code to better handle template specializations and correctly display diagnostics in case when function definition is visible. In the end I had to change over half of the code, but perhaps it was worth it.

@alexfh: What do you think now? Are we getting nearer to making a commit?

clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp
60 ↗(On Diff #34113)

All right. I added a check for whether the parameter is referenced.

106 ↗(On Diff #34113)

Unfortunately, it doesn't work like that. We get this declaration not from matcher, but by iterating redecls().

In any case, I reconsidered this problem, and I came to conclusion that since we need special code for handling template specializations, we may as well do it properly. The only reason that this version of code works at all, is because of incidental generation of this special declaration appearing in the same place where we have function specialization. This, I think, should be considered a side effect of how AST generation works now, and not how it must necessarily work. It may change or even disappear in the future, making this code brittle.

To do this correctly, I believe we should retrieve main template declaration by using getPrimaryTemplate()->getTemplatedDecl() and process that. This fixes the issue of wrong location reporting, and also makes it clear what is happening to someone reading the code.

So in the end, partly because of this issue, I ended up rewriting over half of my code, but what we're left with is I think much better.

196 ↗(On Diff #34113)

Because without it, there is comple error about ambiguous operator<< overload. SmallVector::size() returns unsigned long, while DiagnosticBuilder provides operator<< overloads only for int and unsigned int.

213 ↗(On Diff #34113)

No, I just prefer that style. Anyway, I removed it.

227 ↗(On Diff #34113)

All right. In new version of code, I moved this comment to extracted function checkIfFixItHintIsApplicable() and extended it to document the current reasoning.

test/clang-tidy/readability-inconsistent-declaration-parameter-name.cpp
21 ↗(On Diff #34113)

I didn't think of it this way, but you're right, the user would expect the diagnostics to be at the declaration location. I changed the code to reflect that.

43 ↗(On Diff #34113)

OK. I didn't know I could do that, thanks.

alexfh added a comment.Sep 9 2015, 9:35 AM

Thank you for the fixes. I really like this way of structuring the warnings more. We might need to polish the text in the messages a bit, and maybe also change the case without definitions, but overall this seems fine. I have a few more comments.

clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp
271 ↗(On Diff #34267)

The name is too long for my taste. It doesn't look well with 80-columns limit. Maybe formatDiagnosticsForDeclarations or something like this?

275 ↗(On Diff #34267)

I'm not a native speaker, but "with differently named parameters" doesn't sound well to me. Maybe "with different parameter names"?

289 ↗(On Diff #34267)

I really don't like this idea of introducing a structure for the sole purpose of passing three parameters to a function. I don't see what we gain by doing so, and I would prefer the usual way to pass parameters. The same argument is valid for formatDiagnosticsInCaseOfOnlyFunctionDeclarationsPresent and formatDiagnosticsInOtherCases.

290 ↗(On Diff #34267)

Maybe "the other declaration"?

297 ↗(On Diff #34267)

The name formatDiagnosticsInOtherCases only makes sense when you have the other function name in mind (formatDiagnosticsInCaseOfOnlyFunctionDeclarationsPresent). I don't have a good name for it, but even just removing InOtherCases would make things better.

307 ↗(On Diff #34267)

Maybe "the %0 seen here"?

326 ↗(On Diff #34267)

I'd remove "while".

106 ↗(On Diff #34113)

I see now. Thanks for explaining!

piotrdz updated this revision to Diff 34358.Sep 9 2015, 12:27 PM
piotrdz marked 6 inline comments as done.

Again, addressed all review issues. I hope this is the final version

@alexfh: here we go again. Any comments?

clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp
289 ↗(On Diff #34267)

There is something to gain by it. Although in the end this proved to be a false problem, and I was able to get rid of these structures, I'll explain my reasoning.

The problem I was trying to solve here by introducing these structures, is actually the problem ofpassing reference to the results in InconsistentDeclarationsContainer and DifferingParamsContainer.

C++ does not allow for forward declarations of typedefs, so I cannot just say class InconsistentDeclarationsContainer; in header file. I have to provide full typedef involving SmallVector and its magic number in second template argument. This, I believe, is unnecessary introduction of implementation detail in header file.

So you see, I created such problem for myself, and this is the solution that I came up with.

However, now that I looked again at this, I saw a simpler solution.

All these functions formatting diagnostics, use only one member function of the check class: diag(). I was working under the assumption that it was a protected member of ClangTidyCheck. But I was wrong. It's public. So these don't have to be member functions.*Poof* and all our problems are gone :-)

alexfh accepted this revision.Sep 9 2015, 5:14 PM
alexfh edited edge metadata.

Looks good now.
Thank you for the hard work! I'll commit the patch for you.

This revision is now accepted and ready to land.Sep 9 2015, 5:14 PM

\o/ Yay! Thanks!

alexfh closed this revision.Sep 10 2015, 3:09 AM

Committed revision 247261.

Thank you again for the new awesome check!