1 //===--- InconsistentDeclarationParameterNameCheck.cpp - clang-tidy-------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "InconsistentDeclarationParameterNameCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
12 #include "llvm/ADT/STLExtras.h"
16 using namespace clang::ast_matchers
;
18 namespace clang::tidy::readability
{
22 AST_MATCHER(FunctionDecl
, hasOtherDeclarations
) {
23 auto It
= Node
.redecls_begin();
24 auto EndIt
= Node
.redecls_end();
33 struct DifferingParamInfo
{
34 DifferingParamInfo(StringRef SourceName
, StringRef OtherName
,
35 SourceRange OtherNameRange
, bool GenerateFixItHint
)
36 : SourceName(SourceName
), OtherName(OtherName
),
37 OtherNameRange(OtherNameRange
), GenerateFixItHint(GenerateFixItHint
) {}
41 SourceRange OtherNameRange
;
42 bool GenerateFixItHint
;
45 using DifferingParamsContainer
= llvm::SmallVector
<DifferingParamInfo
, 10>;
47 struct InconsistentDeclarationInfo
{
48 InconsistentDeclarationInfo(SourceLocation DeclarationLocation
,
49 DifferingParamsContainer
&&DifferingParams
)
50 : DeclarationLocation(DeclarationLocation
),
51 DifferingParams(std::move(DifferingParams
)) {}
53 SourceLocation DeclarationLocation
;
54 DifferingParamsContainer DifferingParams
;
57 using InconsistentDeclarationsContainer
=
58 llvm::SmallVector
<InconsistentDeclarationInfo
, 2>;
60 bool checkIfFixItHintIsApplicable(
61 const FunctionDecl
*ParameterSourceDeclaration
,
62 const ParmVarDecl
*SourceParam
, 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
70 if (!ParameterSourceDeclaration
->isThisDeclarationADefinition())
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())
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
)
85 // Other cases seem OK to allow replacements.
89 bool nameMatch(StringRef L
, StringRef R
, bool 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
);
98 DifferingParamsContainer
99 findDifferingParamsInDeclaration(const FunctionDecl
*ParameterSourceDeclaration
,
100 const FunctionDecl
*OtherDeclaration
,
101 const FunctionDecl
*OriginalDeclaration
,
103 DifferingParamsContainer DifferingParams
;
105 const auto *SourceParamIt
= ParameterSourceDeclaration
->param_begin();
106 const auto *OtherParamIt
= OtherDeclaration
->param_begin();
108 while (SourceParamIt
!= ParameterSourceDeclaration
->param_end() &&
109 OtherParamIt
!= OtherDeclaration
->param_end()) {
110 auto SourceParamName
= (*SourceParamIt
)->getName();
111 auto OtherParamName
= (*OtherParamIt
)->getName();
113 // FIXME: Provide a way to extract commented out parameter name from comment
115 if (!nameMatch(SourceParamName
, OtherParamName
, Strict
)) {
116 SourceRange OtherParamNameRange
=
117 DeclarationNameInfo((*OtherParamIt
)->getDeclName(),
118 (*OtherParamIt
)->getLocation())
121 bool GenerateFixItHint
= checkIfFixItHintIsApplicable(
122 ParameterSourceDeclaration
, *SourceParamIt
, OriginalDeclaration
);
124 DifferingParams
.emplace_back(SourceParamName
, OtherParamName
,
125 OtherParamNameRange
, GenerateFixItHint
);
132 return DifferingParams
;
135 InconsistentDeclarationsContainer
136 findInconsistentDeclarations(const FunctionDecl
*OriginalDeclaration
,
137 const FunctionDecl
*ParameterSourceDeclaration
,
138 SourceManager
&SM
, bool Strict
) {
139 InconsistentDeclarationsContainer InconsistentDeclarations
;
140 SourceLocation ParameterSourceLocation
=
141 ParameterSourceDeclaration
->getLocation();
143 for (const FunctionDecl
*OtherDeclaration
: OriginalDeclaration
->redecls()) {
144 SourceLocation OtherLocation
= OtherDeclaration
->getLocation();
145 if (OtherLocation
!= ParameterSourceLocation
) { // Skip self.
146 DifferingParamsContainer DifferingParams
=
147 findDifferingParamsInDeclaration(ParameterSourceDeclaration
,
149 OriginalDeclaration
, Strict
);
150 if (!DifferingParams
.empty()) {
151 InconsistentDeclarations
.emplace_back(OtherDeclaration
->getLocation(),
152 std::move(DifferingParams
));
157 // Sort in order of appearance in translation unit to generate clear
159 llvm::sort(InconsistentDeclarations
,
160 [&SM
](const InconsistentDeclarationInfo
&Info1
,
161 const InconsistentDeclarationInfo
&Info2
) {
162 return SM
.isBeforeInTranslationUnit(Info1
.DeclarationLocation
,
163 Info2
.DeclarationLocation
);
165 return InconsistentDeclarations
;
169 getParameterSourceDeclaration(const FunctionDecl
*OriginalDeclaration
) {
170 const FunctionTemplateDecl
*PrimaryTemplate
=
171 OriginalDeclaration
->getPrimaryTemplate();
172 if (PrimaryTemplate
!= nullptr) {
173 // In case of template specializations, use primary template declaration as
174 // the source of parameter names.
175 return PrimaryTemplate
->getTemplatedDecl();
178 // In other cases, try to change to function definition, if available.
180 if (OriginalDeclaration
->isThisDeclarationADefinition())
181 return OriginalDeclaration
;
183 for (const FunctionDecl
*OtherDeclaration
: OriginalDeclaration
->redecls()) {
184 if (OtherDeclaration
->isThisDeclarationADefinition()) {
185 return OtherDeclaration
;
189 // No definition found, so return original declaration.
190 return OriginalDeclaration
;
193 std::string
joinParameterNames(
194 const DifferingParamsContainer
&DifferingParams
,
195 llvm::function_ref
<StringRef(const DifferingParamInfo
&)> ChooseParamName
) {
196 llvm::SmallString
<40> Str
;
198 for (const DifferingParamInfo
&ParamInfo
: DifferingParams
) {
203 Str
.append({"'", ChooseParamName(ParamInfo
), "'"});
205 return std::string(Str
);
208 void formatDifferingParamsDiagnostic(
209 InconsistentDeclarationParameterNameCheck
*Check
, SourceLocation Location
,
210 StringRef OtherDeclarationDescription
,
211 const DifferingParamsContainer
&DifferingParams
) {
212 auto ChooseOtherName
= [](const DifferingParamInfo
&ParamInfo
) {
213 return ParamInfo
.OtherName
;
215 auto ChooseSourceName
= [](const DifferingParamInfo
&ParamInfo
) {
216 return ParamInfo
.SourceName
;
220 Check
->diag(Location
,
221 "differing parameters are named here: (%0), in %1: (%2)",
222 DiagnosticIDs::Level::Note
)
223 << joinParameterNames(DifferingParams
, ChooseOtherName
)
224 << OtherDeclarationDescription
225 << joinParameterNames(DifferingParams
, ChooseSourceName
);
227 for (const DifferingParamInfo
&ParamInfo
: DifferingParams
) {
228 if (ParamInfo
.GenerateFixItHint
) {
229 ParamDiag
<< FixItHint::CreateReplacement(
230 CharSourceRange::getTokenRange(ParamInfo
.OtherNameRange
),
231 ParamInfo
.SourceName
);
236 void formatDiagnosticsForDeclarations(
237 InconsistentDeclarationParameterNameCheck
*Check
,
238 const FunctionDecl
*ParameterSourceDeclaration
,
239 const FunctionDecl
*OriginalDeclaration
,
240 const InconsistentDeclarationsContainer
&InconsistentDeclarations
) {
242 OriginalDeclaration
->getLocation(),
243 "function %q0 has %1 other declaration%s1 with different parameter names")
244 << OriginalDeclaration
245 << static_cast<int>(InconsistentDeclarations
.size());
247 for (const InconsistentDeclarationInfo
&InconsistentDeclaration
:
248 InconsistentDeclarations
) {
249 Check
->diag(InconsistentDeclaration
.DeclarationLocation
,
250 "the %ordinal0 inconsistent declaration seen here",
251 DiagnosticIDs::Level::Note
)
254 formatDifferingParamsDiagnostic(
255 Check
, InconsistentDeclaration
.DeclarationLocation
,
256 "the other declaration", InconsistentDeclaration
.DifferingParams
);
262 void formatDiagnostics(
263 InconsistentDeclarationParameterNameCheck
*Check
,
264 const FunctionDecl
*ParameterSourceDeclaration
,
265 const FunctionDecl
*OriginalDeclaration
,
266 const InconsistentDeclarationsContainer
&InconsistentDeclarations
,
267 StringRef FunctionDescription
, StringRef ParameterSourceDescription
) {
268 for (const InconsistentDeclarationInfo
&InconsistentDeclaration
:
269 InconsistentDeclarations
) {
270 Check
->diag(InconsistentDeclaration
.DeclarationLocation
,
271 "%0 %q1 has a %2 with different parameter names")
272 << FunctionDescription
<< OriginalDeclaration
273 << ParameterSourceDescription
;
275 Check
->diag(ParameterSourceDeclaration
->getLocation(), "the %0 seen here",
276 DiagnosticIDs::Level::Note
)
277 << ParameterSourceDescription
;
279 formatDifferingParamsDiagnostic(
280 Check
, InconsistentDeclaration
.DeclarationLocation
,
281 ParameterSourceDescription
, InconsistentDeclaration
.DifferingParams
);
285 } // anonymous namespace
287 void InconsistentDeclarationParameterNameCheck::storeOptions(
288 ClangTidyOptions::OptionMap
&Opts
) {
289 Options
.store(Opts
, "IgnoreMacros", IgnoreMacros
);
290 Options
.store(Opts
, "Strict", Strict
);
293 void InconsistentDeclarationParameterNameCheck::registerMatchers(
294 MatchFinder
*Finder
) {
295 Finder
->addMatcher(functionDecl(hasOtherDeclarations()).bind("functionDecl"),
299 void InconsistentDeclarationParameterNameCheck::check(
300 const MatchFinder::MatchResult
&Result
) {
301 const auto *OriginalDeclaration
=
302 Result
.Nodes
.getNodeAs
<FunctionDecl
>("functionDecl");
304 if (VisitedDeclarations
.contains(OriginalDeclaration
))
305 return; // Avoid multiple warnings.
307 const FunctionDecl
*ParameterSourceDeclaration
=
308 getParameterSourceDeclaration(OriginalDeclaration
);
310 InconsistentDeclarationsContainer InconsistentDeclarations
=
311 findInconsistentDeclarations(OriginalDeclaration
,
312 ParameterSourceDeclaration
,
313 *Result
.SourceManager
, Strict
);
314 if (InconsistentDeclarations
.empty()) {
315 // Avoid unnecessary further visits.
316 markRedeclarationsAsVisited(OriginalDeclaration
);
320 SourceLocation StartLoc
= OriginalDeclaration
->getBeginLoc();
321 if (StartLoc
.isMacroID() && IgnoreMacros
) {
322 markRedeclarationsAsVisited(OriginalDeclaration
);
326 if (OriginalDeclaration
->getTemplatedKind() ==
327 FunctionDecl::TK_FunctionTemplateSpecialization
) {
328 formatDiagnostics(this, ParameterSourceDeclaration
, OriginalDeclaration
,
329 InconsistentDeclarations
,
330 "function template specialization",
331 "primary template declaration");
332 } else if (ParameterSourceDeclaration
->isThisDeclarationADefinition()) {
333 formatDiagnostics(this, ParameterSourceDeclaration
, OriginalDeclaration
,
334 InconsistentDeclarations
, "function", "definition");
336 formatDiagnosticsForDeclarations(this, ParameterSourceDeclaration
,
338 InconsistentDeclarations
);
341 markRedeclarationsAsVisited(OriginalDeclaration
);
344 void InconsistentDeclarationParameterNameCheck::markRedeclarationsAsVisited(
345 const FunctionDecl
*OriginalDeclaration
) {
346 for (const FunctionDecl
*Redecl
: OriginalDeclaration
->redecls()) {
347 VisitedDeclarations
.insert(Redecl
);
351 } // namespace clang::tidy::readability