1 | //===--- UseUsingCheck.cpp - clang-tidy------------------------------------===// |
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 | |
9 | #include "UseUsingCheck.h" |
10 | #include "clang/AST/ASTContext.h" |
11 | #include "clang/AST/DeclGroup.h" |
12 | #include "clang/Lex/Lexer.h" |
13 | |
14 | using namespace clang::ast_matchers; |
15 | namespace { |
16 | |
17 | AST_MATCHER(clang::LinkageSpecDecl, isExternCLinkage) { |
18 | return Node.getLanguage() == clang::LinkageSpecLanguageIDs::C; |
19 | } |
20 | } // namespace |
21 | |
22 | namespace clang::tidy::modernize { |
23 | |
24 | static constexpr llvm::StringLiteral ExternCDeclName = "extern-c-decl" ; |
25 | static constexpr llvm::StringLiteral ParentDeclName = "parent-decl" ; |
26 | static constexpr llvm::StringLiteral TagDeclName = "tag-decl" ; |
27 | static constexpr llvm::StringLiteral TypedefName = "typedef" ; |
28 | static constexpr llvm::StringLiteral DeclStmtName = "decl-stmt" ; |
29 | |
30 | UseUsingCheck::UseUsingCheck(StringRef Name, ClangTidyContext *Context) |
31 | : ClangTidyCheck(Name, Context), |
32 | IgnoreMacros(Options.getLocalOrGlobal(LocalName: "IgnoreMacros" , Default: true)), |
33 | IgnoreExternC(Options.get(LocalName: "IgnoreExternC" , Default: false)) {} |
34 | |
35 | void UseUsingCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { |
36 | Options.store(Options&: Opts, LocalName: "IgnoreMacros" , Value: IgnoreMacros); |
37 | Options.store(Options&: Opts, LocalName: "IgnoreExternC" , Value: IgnoreExternC); |
38 | } |
39 | |
40 | void UseUsingCheck::registerMatchers(MatchFinder *Finder) { |
41 | Finder->addMatcher( |
42 | NodeMatch: typedefDecl( |
43 | unless(isInstantiated()), |
44 | optionally(hasAncestor( |
45 | linkageSpecDecl(isExternCLinkage()).bind(ID: ExternCDeclName))), |
46 | anyOf(hasParent(decl().bind(ID: ParentDeclName)), |
47 | hasParent(declStmt().bind(ID: DeclStmtName)))) |
48 | .bind(ID: TypedefName), |
49 | Action: this); |
50 | |
51 | // This matcher is used to find tag declarations in source code within |
52 | // typedefs. They appear in the AST just *prior* to the typedefs. |
53 | Finder->addMatcher( |
54 | NodeMatch: tagDecl( |
55 | anyOf(allOf(unless(anyOf(isImplicit(), |
56 | classTemplateSpecializationDecl())), |
57 | anyOf(hasParent(decl().bind(ID: ParentDeclName)), |
58 | hasParent(declStmt().bind(ID: DeclStmtName)))), |
59 | // We want the parent of the ClassTemplateDecl, not the parent |
60 | // of the specialization. |
61 | classTemplateSpecializationDecl(hasAncestor(classTemplateDecl( |
62 | anyOf(hasParent(decl().bind(ID: ParentDeclName)), |
63 | hasParent(declStmt().bind(ID: DeclStmtName)))))))) |
64 | .bind(ID: TagDeclName), |
65 | Action: this); |
66 | } |
67 | |
68 | void UseUsingCheck::check(const MatchFinder::MatchResult &Result) { |
69 | const auto *ParentDecl = Result.Nodes.getNodeAs<Decl>(ID: ParentDeclName); |
70 | |
71 | if (!ParentDecl) { |
72 | const auto *ParentDeclStmt = Result.Nodes.getNodeAs<DeclStmt>(ID: DeclStmtName); |
73 | if (ParentDeclStmt) { |
74 | if (ParentDeclStmt->isSingleDecl()) |
75 | ParentDecl = ParentDeclStmt->getSingleDecl(); |
76 | else |
77 | ParentDecl = |
78 | ParentDeclStmt->getDeclGroup().getDeclGroup() |
79 | [ParentDeclStmt->getDeclGroup().getDeclGroup().size() - 1]; |
80 | } |
81 | } |
82 | |
83 | if (!ParentDecl) |
84 | return; |
85 | |
86 | // Match CXXRecordDecl only to store the range of the last non-implicit full |
87 | // declaration, to later check whether it's within the typdef itself. |
88 | const auto *MatchedTagDecl = Result.Nodes.getNodeAs<TagDecl>(ID: TagDeclName); |
89 | if (MatchedTagDecl) { |
90 | // It is not sufficient to just track the last TagDecl that we've seen, |
91 | // because if one struct or union is nested inside another, the last TagDecl |
92 | // before the typedef will be the nested one (PR#50990). Therefore, we also |
93 | // keep track of the parent declaration, so that we can look up the last |
94 | // TagDecl that is a sibling of the typedef in the AST. |
95 | if (MatchedTagDecl->isThisDeclarationADefinition()) |
96 | LastTagDeclRanges[ParentDecl] = MatchedTagDecl->getSourceRange(); |
97 | return; |
98 | } |
99 | |
100 | const auto *MatchedDecl = Result.Nodes.getNodeAs<TypedefDecl>(ID: TypedefName); |
101 | if (MatchedDecl->getLocation().isInvalid()) |
102 | return; |
103 | |
104 | const auto *ExternCDecl = |
105 | Result.Nodes.getNodeAs<LinkageSpecDecl>(ID: ExternCDeclName); |
106 | if (ExternCDecl && IgnoreExternC) |
107 | return; |
108 | |
109 | SourceLocation StartLoc = MatchedDecl->getBeginLoc(); |
110 | |
111 | if (StartLoc.isMacroID() && IgnoreMacros) |
112 | return; |
113 | |
114 | static const char *UseUsingWarning = "use 'using' instead of 'typedef'" ; |
115 | |
116 | // Warn at StartLoc but do not fix if there is macro or array. |
117 | if (MatchedDecl->getUnderlyingType()->isArrayType() || StartLoc.isMacroID()) { |
118 | diag(Loc: StartLoc, Description: UseUsingWarning); |
119 | return; |
120 | } |
121 | |
122 | PrintingPolicy PrintPolicy(getLangOpts()); |
123 | PrintPolicy.SuppressScope = true; |
124 | PrintPolicy.ConstantArraySizeAsWritten = true; |
125 | PrintPolicy.UseVoidForZeroParams = false; |
126 | PrintPolicy.PrintInjectedClassNameWithArguments = false; |
127 | |
128 | std::string Type = MatchedDecl->getUnderlyingType().getAsString(PrintPolicy); |
129 | std::string Name = MatchedDecl->getNameAsString(); |
130 | SourceRange ReplaceRange = MatchedDecl->getSourceRange(); |
131 | |
132 | // typedefs with multiple comma-separated definitions produce multiple |
133 | // consecutive TypedefDecl nodes whose SourceRanges overlap. Each range starts |
134 | // at the "typedef" and then continues *across* previous definitions through |
135 | // the end of the current TypedefDecl definition. |
136 | // But also we need to check that the ranges belong to the same file because |
137 | // different files may contain overlapping ranges. |
138 | std::string Using = "using " ; |
139 | if (ReplaceRange.getBegin().isMacroID() || |
140 | (Result.SourceManager->getFileID(SpellingLoc: ReplaceRange.getBegin()) != |
141 | Result.SourceManager->getFileID(SpellingLoc: LastReplacementEnd)) || |
142 | (ReplaceRange.getBegin() >= LastReplacementEnd)) { |
143 | // This is the first (and possibly the only) TypedefDecl in a typedef. Save |
144 | // Type and Name in case we find subsequent TypedefDecl's in this typedef. |
145 | FirstTypedefType = Type; |
146 | FirstTypedefName = Name; |
147 | } else { |
148 | // This is additional TypedefDecl in a comma-separated typedef declaration. |
149 | // Start replacement *after* prior replacement and separate with semicolon. |
150 | ReplaceRange.setBegin(LastReplacementEnd); |
151 | Using = ";\nusing " ; |
152 | |
153 | // If this additional TypedefDecl's Type starts with the first TypedefDecl's |
154 | // type, make this using statement refer back to the first type, e.g. make |
155 | // "typedef int Foo, *Foo_p;" -> "using Foo = int;\nusing Foo_p = Foo*;" |
156 | if (Type.size() > FirstTypedefType.size() && |
157 | Type.substr(pos: 0, n: FirstTypedefType.size()) == FirstTypedefType) |
158 | Type = FirstTypedefName + Type.substr(pos: FirstTypedefType.size() + 1); |
159 | } |
160 | if (!ReplaceRange.getEnd().isMacroID()) { |
161 | const SourceLocation::IntTy Offset = |
162 | MatchedDecl->getFunctionType() ? 0 : Name.size(); |
163 | LastReplacementEnd = ReplaceRange.getEnd().getLocWithOffset(Offset); |
164 | } |
165 | |
166 | auto Diag = diag(Loc: ReplaceRange.getBegin(), Description: UseUsingWarning); |
167 | |
168 | // If typedef contains a full tag declaration, extract its full text. |
169 | auto LastTagDeclRange = LastTagDeclRanges.find(Val: ParentDecl); |
170 | if (LastTagDeclRange != LastTagDeclRanges.end() && |
171 | LastTagDeclRange->second.isValid() && |
172 | ReplaceRange.fullyContains(other: LastTagDeclRange->second)) { |
173 | Type = std::string(Lexer::getSourceText( |
174 | Range: CharSourceRange::getTokenRange(R: LastTagDeclRange->second), |
175 | SM: *Result.SourceManager, LangOpts: getLangOpts())); |
176 | if (Type.empty()) |
177 | return; |
178 | } |
179 | |
180 | std::string Replacement = Using + Name + " = " + Type; |
181 | Diag << FixItHint::CreateReplacement(RemoveRange: ReplaceRange, Code: Replacement); |
182 | } |
183 | } // namespace clang::tidy::modernize |
184 | |