clang-tools 22.0.0git
InconsistentDeclarationParameterNameCheck.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
10#include "clang/ASTMatchers/ASTMatchFinder.h"
11#include "llvm/ADT/STLExtras.h"
12
13using namespace clang::ast_matchers;
14
16
17namespace {
18
19AST_MATCHER(FunctionDecl, hasOtherDeclarations) {
20 auto It = Node.redecls_begin();
21 auto EndIt = Node.redecls_end();
22
23 if (It == EndIt)
24 return false;
25
26 ++It;
27 return It != EndIt;
28}
29
30struct DifferingParamInfo {
31 DifferingParamInfo(StringRef SourceName, StringRef OtherName,
32 SourceRange OtherNameRange, bool GenerateFixItHint)
33 : SourceName(SourceName), OtherName(OtherName),
34 OtherNameRange(OtherNameRange), GenerateFixItHint(GenerateFixItHint) {}
35
36 StringRef SourceName;
37 StringRef OtherName;
38 SourceRange OtherNameRange;
39 bool GenerateFixItHint;
40};
41
42using DifferingParamsContainer = llvm::SmallVector<DifferingParamInfo, 10>;
43
44struct InconsistentDeclarationInfo {
45 InconsistentDeclarationInfo(SourceLocation DeclarationLocation,
46 DifferingParamsContainer &&DifferingParams)
47 : DeclarationLocation(DeclarationLocation),
48 DifferingParams(std::move(DifferingParams)) {}
49
50 SourceLocation DeclarationLocation;
51 DifferingParamsContainer DifferingParams;
52};
53
54using InconsistentDeclarationsContainer =
55 llvm::SmallVector<InconsistentDeclarationInfo, 2>;
56
57} // namespace
58
59static bool
60checkIfFixItHintIsApplicable(const FunctionDecl *ParameterSourceDeclaration,
61 const ParmVarDecl *SourceParam,
62 const FunctionDecl *OriginalDeclaration) {
63 // Assumptions with regard to function declarations/definition:
64 // * If both function declaration and definition are seen, assume that
65 // definition is most up-to-date, and use it to generate replacements.
66 // * If only function declarations are seen, there is no easy way to tell
67 // which is up-to-date and which is not, so don't do anything.
68 // TODO: This may be changed later, but for now it seems the reasonable
69 // solution.
70 if (!ParameterSourceDeclaration->isThisDeclarationADefinition())
71 return false;
72
73 // Assumption: if parameter is not referenced in function definition body, it
74 // may indicate that it's outdated, so don't touch it.
75 if (!SourceParam->isReferenced())
76 return false;
77
78 // In case there is the primary template definition and (possibly several)
79 // template specializations (and each with possibly several redeclarations),
80 // it is not at all clear what to change.
81 if (OriginalDeclaration->getTemplatedKind() ==
82 FunctionDecl::TK_FunctionTemplateSpecialization)
83 return false;
84
85 // Other cases seem OK to allow replacements.
86 return true;
87}
88
89static bool nameMatch(StringRef L, StringRef R, bool Strict) {
90 if (Strict)
91 return L.empty() || R.empty() || L == R;
92 // We allow two names if one is a prefix/suffix of the other, ignoring case.
93 // Important special case: this is true if either parameter has no name!
94 return L.starts_with_insensitive(R) || R.starts_with_insensitive(L) ||
95 L.ends_with_insensitive(R) || R.ends_with_insensitive(L);
96}
97
98static DifferingParamsContainer
99findDifferingParamsInDeclaration(const FunctionDecl *ParameterSourceDeclaration,
100 const FunctionDecl *OtherDeclaration,
101 const FunctionDecl *OriginalDeclaration,
102 bool Strict) {
103 DifferingParamsContainer DifferingParams;
104
105 const auto *SourceParamIt = ParameterSourceDeclaration->param_begin();
106 const auto *OtherParamIt = OtherDeclaration->param_begin();
107
108 while (SourceParamIt != ParameterSourceDeclaration->param_end() &&
109 OtherParamIt != OtherDeclaration->param_end()) {
110 if ((*SourceParamIt)->isParameterPack() !=
111 (*OtherParamIt)->isParameterPack())
112 break;
113
114 auto SourceParamName = (*SourceParamIt)->getName();
115 auto OtherParamName = (*OtherParamIt)->getName();
116
117 // FIXME: Provide a way to extract commented out parameter name from comment
118 // next to it.
119 if (!nameMatch(SourceParamName, OtherParamName, Strict)) {
120 const SourceRange OtherParamNameRange =
121 DeclarationNameInfo((*OtherParamIt)->getDeclName(),
122 (*OtherParamIt)->getLocation())
123 .getSourceRange();
124
125 const bool GenerateFixItHint = checkIfFixItHintIsApplicable(
126 ParameterSourceDeclaration, *SourceParamIt, OriginalDeclaration);
127
128 DifferingParams.emplace_back(SourceParamName, OtherParamName,
129 OtherParamNameRange, GenerateFixItHint);
130 }
131
132 ++SourceParamIt;
133 ++OtherParamIt;
134 }
135
136 return DifferingParams;
137}
138
139static InconsistentDeclarationsContainer
140findInconsistentDeclarations(const FunctionDecl *OriginalDeclaration,
141 const FunctionDecl *ParameterSourceDeclaration,
142 SourceManager &SM, bool Strict) {
143 InconsistentDeclarationsContainer InconsistentDeclarations;
144 const SourceLocation ParameterSourceLocation =
145 ParameterSourceDeclaration->getLocation();
146
147 for (const FunctionDecl *OtherDeclaration : OriginalDeclaration->redecls()) {
148 const SourceLocation OtherLocation = OtherDeclaration->getLocation();
149 if (OtherLocation != ParameterSourceLocation) { // Skip self.
150 DifferingParamsContainer DifferingParams =
151 findDifferingParamsInDeclaration(ParameterSourceDeclaration,
152 OtherDeclaration,
153 OriginalDeclaration, Strict);
154 if (!DifferingParams.empty()) {
155 InconsistentDeclarations.emplace_back(OtherDeclaration->getLocation(),
156 std::move(DifferingParams));
157 }
158 }
159 }
160
161 // Sort in order of appearance in translation unit to generate clear
162 // diagnostics.
163 llvm::sort(InconsistentDeclarations,
164 [&SM](const InconsistentDeclarationInfo &Info1,
165 const InconsistentDeclarationInfo &Info2) {
166 return SM.isBeforeInTranslationUnit(Info1.DeclarationLocation,
167 Info2.DeclarationLocation);
168 });
169 return InconsistentDeclarations;
170}
171
172static const FunctionDecl *
173getParameterSourceDeclaration(const FunctionDecl *OriginalDeclaration) {
174 const FunctionTemplateDecl *PrimaryTemplate =
175 OriginalDeclaration->getPrimaryTemplate();
176 if (PrimaryTemplate != nullptr) {
177 // In case of template specializations, use primary template declaration as
178 // the source of parameter names.
179 return PrimaryTemplate->getTemplatedDecl();
180 }
181
182 // In other cases, try to change to function definition, if available.
183
184 if (OriginalDeclaration->isThisDeclarationADefinition())
185 return OriginalDeclaration;
186
187 for (const FunctionDecl *OtherDeclaration : OriginalDeclaration->redecls()) {
188 if (OtherDeclaration->isThisDeclarationADefinition()) {
189 return OtherDeclaration;
190 }
191 }
192
193 // No definition found, so return original declaration.
194 return OriginalDeclaration;
195}
196
197static std::string joinParameterNames(
198 const DifferingParamsContainer &DifferingParams,
199 llvm::function_ref<StringRef(const DifferingParamInfo &)> ChooseParamName) {
200 llvm::SmallString<40> Str;
201 bool First = true;
202 for (const DifferingParamInfo &ParamInfo : DifferingParams) {
203 if (First)
204 First = false;
205 else
206 Str += ", ";
207 Str.append({"'", ChooseParamName(ParamInfo), "'"});
208 }
209 return std::string(Str);
210}
211
214 StringRef OtherDeclarationDescription,
215 const DifferingParamsContainer &DifferingParams) {
216 auto ChooseOtherName = [](const DifferingParamInfo &ParamInfo) {
217 return ParamInfo.OtherName;
218 };
219 auto ChooseSourceName = [](const DifferingParamInfo &ParamInfo) {
220 return ParamInfo.SourceName;
221 };
222
223 auto ParamDiag =
224 Check->diag(Location,
225 "differing parameters are named here: (%0), in %1: (%2)",
226 DiagnosticIDs::Level::Note)
227 << joinParameterNames(DifferingParams, ChooseOtherName)
228 << OtherDeclarationDescription
229 << joinParameterNames(DifferingParams, ChooseSourceName);
230
231 for (const DifferingParamInfo &ParamInfo : DifferingParams) {
232 if (ParamInfo.GenerateFixItHint) {
233 ParamDiag << FixItHint::CreateReplacement(
234 CharSourceRange::getTokenRange(ParamInfo.OtherNameRange),
235 ParamInfo.SourceName);
236 }
237 }
238}
239
242 const FunctionDecl *ParameterSourceDeclaration,
243 const FunctionDecl *OriginalDeclaration,
244 const InconsistentDeclarationsContainer &InconsistentDeclarations) {
245 Check->diag(
246 OriginalDeclaration->getLocation(),
247 "function %q0 has %1 other declaration%s1 with different parameter names")
248 << OriginalDeclaration
249 << static_cast<int>(InconsistentDeclarations.size());
250 int Count = 1;
251 for (const InconsistentDeclarationInfo &InconsistentDeclaration :
252 InconsistentDeclarations) {
253 Check->diag(InconsistentDeclaration.DeclarationLocation,
254 "the %ordinal0 inconsistent declaration seen here",
255 DiagnosticIDs::Level::Note)
256 << Count;
257
259 Check, InconsistentDeclaration.DeclarationLocation,
260 "the other declaration", InconsistentDeclaration.DifferingParams);
261
262 ++Count;
263 }
264}
265
268 const FunctionDecl *ParameterSourceDeclaration,
269 const FunctionDecl *OriginalDeclaration,
270 const InconsistentDeclarationsContainer &InconsistentDeclarations,
271 StringRef FunctionDescription, StringRef ParameterSourceDescription) {
272 for (const InconsistentDeclarationInfo &InconsistentDeclaration :
273 InconsistentDeclarations) {
274 Check->diag(InconsistentDeclaration.DeclarationLocation,
275 "%0 %q1 has a %2 with different parameter names")
276 << FunctionDescription << OriginalDeclaration
277 << ParameterSourceDescription;
278
279 Check->diag(ParameterSourceDeclaration->getLocation(), "the %0 seen here",
280 DiagnosticIDs::Level::Note)
281 << ParameterSourceDescription;
282
284 Check, InconsistentDeclaration.DeclarationLocation,
285 ParameterSourceDescription, InconsistentDeclaration.DifferingParams);
286 }
287}
288
291 Options.store(Opts, "IgnoreMacros", IgnoreMacros);
292 Options.store(Opts, "Strict", Strict);
293}
294
296 MatchFinder *Finder) {
297 Finder->addMatcher(functionDecl(hasOtherDeclarations()).bind("functionDecl"),
298 this);
299}
300
302 const MatchFinder::MatchResult &Result) {
303 const auto *OriginalDeclaration =
304 Result.Nodes.getNodeAs<FunctionDecl>("functionDecl");
305
306 if (VisitedDeclarations.contains(OriginalDeclaration))
307 return; // Avoid multiple warnings.
308
309 const FunctionDecl *ParameterSourceDeclaration =
310 getParameterSourceDeclaration(OriginalDeclaration);
311
312 const InconsistentDeclarationsContainer InconsistentDeclarations =
313 findInconsistentDeclarations(OriginalDeclaration,
314 ParameterSourceDeclaration,
315 *Result.SourceManager, Strict);
316 if (InconsistentDeclarations.empty()) {
317 // Avoid unnecessary further visits.
318 markRedeclarationsAsVisited(OriginalDeclaration);
319 return;
320 }
321
322 const SourceLocation StartLoc = OriginalDeclaration->getBeginLoc();
323 if (StartLoc.isMacroID() && IgnoreMacros) {
324 markRedeclarationsAsVisited(OriginalDeclaration);
325 return;
326 }
327
328 if (OriginalDeclaration->getTemplatedKind() ==
329 FunctionDecl::TK_FunctionTemplateSpecialization) {
330 formatDiagnostics(this, ParameterSourceDeclaration, OriginalDeclaration,
331 InconsistentDeclarations,
332 "function template specialization",
333 "primary template declaration");
334 } else if (ParameterSourceDeclaration->isThisDeclarationADefinition()) {
335 formatDiagnostics(this, ParameterSourceDeclaration, OriginalDeclaration,
336 InconsistentDeclarations, "function", "definition");
337 } else {
338 formatDiagnosticsForDeclarations(this, ParameterSourceDeclaration,
339 OriginalDeclaration,
340 InconsistentDeclarations);
341 }
342
343 markRedeclarationsAsVisited(OriginalDeclaration);
344}
345
346void InconsistentDeclarationParameterNameCheck::markRedeclarationsAsVisited(
347 const FunctionDecl *OriginalDeclaration) {
348 VisitedDeclarations.insert_range(OriginalDeclaration->redecls());
349}
350
351} // namespace clang::tidy::readability
Checks for declarations of functions which differ in parameter names.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
AST_MATCHER(BinaryOperator, isRelationalOperator)
static InconsistentDeclarationsContainer findInconsistentDeclarations(const FunctionDecl *OriginalDeclaration, const FunctionDecl *ParameterSourceDeclaration, SourceManager &SM, bool Strict)
static const FunctionDecl * getParameterSourceDeclaration(const FunctionDecl *OriginalDeclaration)
static void formatDiagnosticsForDeclarations(InconsistentDeclarationParameterNameCheck *Check, const FunctionDecl *ParameterSourceDeclaration, const FunctionDecl *OriginalDeclaration, const InconsistentDeclarationsContainer &InconsistentDeclarations)
static void formatDifferingParamsDiagnostic(InconsistentDeclarationParameterNameCheck *Check, SourceLocation Location, StringRef OtherDeclarationDescription, const DifferingParamsContainer &DifferingParams)
static bool nameMatch(StringRef L, StringRef R, bool Strict)
static void formatDiagnostics(InconsistentDeclarationParameterNameCheck *Check, const FunctionDecl *ParameterSourceDeclaration, const FunctionDecl *OriginalDeclaration, const InconsistentDeclarationsContainer &InconsistentDeclarations, StringRef FunctionDescription, StringRef ParameterSourceDescription)
static std::string joinParameterNames(const DifferingParamsContainer &DifferingParams, llvm::function_ref< StringRef(const DifferingParamInfo &)> ChooseParamName)
static bool checkIfFixItHintIsApplicable(const FunctionDecl *ParameterSourceDeclaration, const ParmVarDecl *SourceParam, const FunctionDecl *OriginalDeclaration)
static DifferingParamsContainer findDifferingParamsInDeclaration(const FunctionDecl *ParameterSourceDeclaration, const FunctionDecl *OtherDeclaration, const FunctionDecl *OriginalDeclaration, bool Strict)
llvm::StringMap< ClangTidyValue > OptionMap