clang-tools 22.0.0git
AddUsing.cpp
Go to the documentation of this file.
1//===--- AddUsing.cpp --------------------------------------------*- C++-*-===//
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 "AST.h"
10#include "Config.h"
11#include "SourceCode.h"
12#include "refactor/Tweak.h"
13#include "support/Logger.h"
14#include "clang/AST/Decl.h"
15#include "clang/AST/Expr.h"
16#include "clang/AST/NestedNameSpecifier.h"
17#include "clang/AST/RecursiveASTVisitor.h"
18#include "clang/AST/Type.h"
19#include "clang/AST/TypeLoc.h"
20#include "clang/Basic/LLVM.h"
21#include "clang/Basic/SourceLocation.h"
22#include "clang/Tooling/Core/Replacement.h"
23#include "clang/Tooling/Syntax/Tokens.h"
24#include "llvm/ADT/StringRef.h"
25#include "llvm/Support/FormatVariadic.h"
26#include "llvm/Support/raw_ostream.h"
27#include <string>
28#include <tuple>
29#include <utility>
30
31namespace clang {
32namespace clangd {
33namespace {
34
35// Tweak for removing full namespace qualifier under cursor on DeclRefExpr and
36// types and adding "using" statement instead.
37//
38// Only qualifiers that refer exclusively to namespaces (no record types) are
39// supported. There is some guessing of appropriate place to insert the using
40// declaration. If we find any existing usings, we insert it there. If not, we
41// insert right after the inner-most relevant namespace declaration. If there is
42// none, or there is, but it was declared via macro, we insert above the first
43// top level decl.
44//
45// Currently this only removes qualifier from under the cursor. In the future,
46// we should improve this to remove qualifier from all occurrences of this
47// symbol.
48class AddUsing : public Tweak {
49public:
50 const char *id() const override;
51
52 bool prepare(const Selection &Inputs) override;
53 Expected<Effect> apply(const Selection &Inputs) override;
54 std::string title() const override;
55 llvm::StringLiteral kind() const override {
57 }
58
59private:
60 // All of the following are set by prepare().
61 // The qualifier to remove.
62 NestedNameSpecifierLoc QualifierToRemove;
63 // Qualified name to use when spelling the using declaration. This might be
64 // different than SpelledQualifier in presence of error correction.
65 std::string QualifierToSpell;
66 // The name and qualifier as spelled in the code.
67 llvm::StringRef SpelledQualifier;
68 llvm::StringRef SpelledName;
69 // If valid, the insertion point for "using" statement must come after this.
70 // This is relevant when the type is defined in the main file, to make sure
71 // the type/function is already defined at the point where "using" is added.
72 SourceLocation MustInsertAfterLoc;
73};
74REGISTER_TWEAK(AddUsing)
75
76std::string AddUsing::title() const {
77 return std::string(llvm::formatv(
78 "Add using-declaration for {0} and remove qualifier", SpelledName));
79}
80
81// Locates all "using" statements relevant to SelectionDeclContext.
82class UsingFinder : public RecursiveASTVisitor<UsingFinder> {
83public:
84 UsingFinder(std::vector<const UsingDecl *> &Results,
85 const DeclContext *SelectionDeclContext, const SourceManager &SM)
86 : Results(Results), SelectionDeclContext(SelectionDeclContext), SM(SM) {}
87
88 bool VisitUsingDecl(UsingDecl *D) {
89 auto Loc = D->getUsingLoc();
90 if (SM.getFileID(Loc) != SM.getMainFileID()) {
91 return true;
92 }
93 if (D->getDeclContext()->Encloses(SelectionDeclContext)) {
94 Results.push_back(D);
95 }
96 return true;
97 }
98
99 bool TraverseDecl(Decl *Node) {
100 if (!Node)
101 return true;
102 // There is no need to go deeper into nodes that do not enclose selection,
103 // since "using" there will not affect selection, nor would it make a good
104 // insertion point.
105 if (!Node->getDeclContext() ||
106 Node->getDeclContext()->Encloses(SelectionDeclContext)) {
107 return RecursiveASTVisitor<UsingFinder>::TraverseDecl(Node);
108 }
109 return true;
110 }
111
112private:
113 std::vector<const UsingDecl *> &Results;
114 const DeclContext *SelectionDeclContext;
115 const SourceManager &SM;
116};
117
118struct InsertionPointData {
119 // Location to insert the "using" statement. If invalid then the statement
120 // should not be inserted at all (it already exists).
121 SourceLocation Loc;
122 // Extra suffix to place after the "using" statement. Depending on what the
123 // insertion point is anchored to, we may need one or more \n to ensure
124 // proper formatting.
125 std::string Suffix;
126 // Whether using should be fully qualified, even if what the user typed was
127 // not. This is based on our detection of the local style.
128 bool AlwaysFullyQualify = false;
129};
130
131// Finds the best place to insert the "using" statement. Returns invalid
132// SourceLocation if the "using" statement already exists.
133//
134// The insertion point might be a little awkward if the decl we're anchoring to
135// has a comment in an unfortunate place (e.g. directly above function or using
136// decl, or immediately following "namespace {". We should add some helpers for
137// dealing with that and use them in other code modifications as well.
138llvm::Expected<InsertionPointData>
139findInsertionPoint(const Tweak::Selection &Inputs,
140 const NestedNameSpecifierLoc &QualifierToRemove,
141 const llvm::StringRef Name,
142 const SourceLocation MustInsertAfterLoc) {
143 auto &SM = Inputs.AST->getSourceManager();
144
145 // Search for all using decls that affect this point in file. We need this for
146 // two reasons: to skip adding "using" if one already exists and to find best
147 // place to add it, if it doesn't exist.
148 SourceLocation LastUsingLoc;
149 std::vector<const UsingDecl *> Usings;
150 UsingFinder(Usings, &Inputs.ASTSelection.commonAncestor()->getDeclContext(),
151 SM)
152 .TraverseAST(Inputs.AST->getASTContext());
153
154 auto IsValidPoint = [&](const SourceLocation Loc) {
155 return MustInsertAfterLoc.isInvalid() ||
156 SM.isBeforeInTranslationUnit(MustInsertAfterLoc, Loc);
157 };
158
159 bool AlwaysFullyQualify = true;
160 for (auto &U : Usings) {
161 // Only "upgrade" to fully qualified is all relevant using decls are fully
162 // qualified. Otherwise trust what the user typed.
163 if (!U->getQualifier().isFullyQualified())
164 AlwaysFullyQualify = false;
165
166 if (SM.isBeforeInTranslationUnit(Inputs.Cursor, U->getUsingLoc()))
167 // "Usings" is sorted, so we're done.
168 break;
169 if (NestedNameSpecifier Qualifier = U->getQualifier();
170 Qualifier.getKind() == NestedNameSpecifier::Kind::Namespace) {
171 const auto *Namespace =
172 U->getQualifier().getAsNamespaceAndPrefix().Namespace;
173 if (Namespace->getCanonicalDecl() ==
174 QualifierToRemove.getNestedNameSpecifier()
175 .getAsNamespaceAndPrefix()
176 .Namespace->getCanonicalDecl() &&
177 U->getName() == Name) {
178 return InsertionPointData();
179 }
180 }
181
182 // Insertion point will be before last UsingDecl that affects cursor
183 // position. For most cases this should stick with the local convention of
184 // add using inside or outside namespace.
185 LastUsingLoc = U->getUsingLoc();
186 }
187 if (LastUsingLoc.isValid() && IsValidPoint(LastUsingLoc)) {
188 InsertionPointData Out;
189 Out.Loc = LastUsingLoc;
190 Out.AlwaysFullyQualify = AlwaysFullyQualify;
191 return Out;
192 }
193
194 // No relevant "using" statements. Try the nearest namespace level.
195 const DeclContext *ParentDeclCtx =
196 &Inputs.ASTSelection.commonAncestor()->getDeclContext();
197 while (ParentDeclCtx && !ParentDeclCtx->isFileContext()) {
198 ParentDeclCtx = ParentDeclCtx->getLexicalParent();
199 }
200 if (auto *ND = llvm::dyn_cast_or_null<NamespaceDecl>(ParentDeclCtx)) {
201 auto Toks = Inputs.AST->getTokens().expandedTokens(ND->getSourceRange());
202 const auto *Tok = llvm::find_if(Toks, [](const syntax::Token &Tok) {
203 return Tok.kind() == tok::l_brace;
204 });
205 if (Tok == Toks.end() || Tok->endLocation().isInvalid()) {
206 return error("Namespace with no {{");
207 }
208 if (!Tok->endLocation().isMacroID() && IsValidPoint(Tok->endLocation())) {
209 InsertionPointData Out;
210 Out.Loc = Tok->endLocation();
211 Out.Suffix = "\n";
212 return Out;
213 }
214 }
215 // No using, no namespace, no idea where to insert. Try above the first
216 // top level decl after MustInsertAfterLoc.
217 auto TLDs = Inputs.AST->getLocalTopLevelDecls();
218 for (const auto &TLD : TLDs) {
219 if (!IsValidPoint(TLD->getBeginLoc()))
220 continue;
221 InsertionPointData Out;
222 Out.Loc = SM.getExpansionLoc(TLD->getBeginLoc());
223 Out.Suffix = "\n\n";
224 return Out;
225 }
226 return error("Cannot find place to insert \"using\"");
227}
228
229bool isNamespaceForbidden(const Tweak::Selection &Inputs,
230 NestedNameSpecifier Namespace) {
231 const auto *NS =
232 dyn_cast<NamespaceDecl>(Namespace.getAsNamespaceAndPrefix().Namespace);
233 if (!NS)
234 return true;
235 std::string NamespaceStr = printNamespaceScope(*NS);
236
237 for (StringRef Banned : Config::current().Style.FullyQualifiedNamespaces) {
238 StringRef PrefixMatch = NamespaceStr;
239 if (PrefixMatch.consume_front(Banned) && PrefixMatch.consume_front("::"))
240 return true;
241 }
242
243 return false;
244}
245
246std::string getNNSLAsString(NestedNameSpecifierLoc NNSL,
247 const PrintingPolicy &Policy) {
248 std::string Out;
249 llvm::raw_string_ostream OutStream(Out);
250 NNSL.getNestedNameSpecifier().print(OutStream, Policy);
251 return OutStream.str();
252}
253
254bool AddUsing::prepare(const Selection &Inputs) {
255 auto &SM = Inputs.AST->getSourceManager();
256 const auto &TB = Inputs.AST->getTokens();
257
258 // Do not suggest "using" in header files. That way madness lies.
259 if (isHeaderFile(SM.getFileEntryRefForID(SM.getMainFileID())->getName(),
260 Inputs.AST->getLangOpts()))
261 return false;
262
263 auto *Node = Inputs.ASTSelection.commonAncestor();
264 if (Node == nullptr)
265 return false;
266
267 // If we're looking at a type or NestedNameSpecifier, walk up the tree until
268 // we find the "main" node we care about, which would be ElaboratedTypeLoc or
269 // DeclRefExpr.
270 for (; Node->Parent; Node = Node->Parent) {
271 if (Node->ASTNode.get<NestedNameSpecifierLoc>()) {
272 continue;
273 }
274 if (auto *T = Node->ASTNode.get<TypeLoc>()) {
275 // Find the outermost TypeLoc.
276 if (Node->Parent->ASTNode.get<NestedNameSpecifierLoc>())
277 continue;
278 if (isa<TagType, TemplateSpecializationType, TypedefType, UsingType,
279 UnresolvedUsingType>(T->getTypePtr()))
280 break;
281 // Find the outermost TypeLoc.
282 if (Node->Parent->ASTNode.get<TypeLoc>())
283 continue;
284 }
285 break;
286 }
287 if (Node == nullptr)
288 return false;
289
290 // Closed range for the fully qualified name as spelled in source code.
291 SourceRange SpelledNameRange;
292 if (auto *D = Node->ASTNode.get<DeclRefExpr>()) {
293 if (D->getDecl()->getIdentifier()) {
294 QualifierToRemove = D->getQualifierLoc();
295 // Use the name range rather than expr, as the latter can contain template
296 // arguments in the range.
297 SpelledNameRange = D->getSourceRange();
298 // Remove the template arguments from the name, as they shouldn't be
299 // spelled in the using declaration.
300 if (auto AngleLoc = D->getLAngleLoc(); AngleLoc.isValid())
301 SpelledNameRange.setEnd(AngleLoc.getLocWithOffset(-1));
302 MustInsertAfterLoc = D->getDecl()->getBeginLoc();
303 }
304 } else if (auto *T = Node->ASTNode.get<TypeLoc>()) {
305 switch (T->getTypeLocClass()) {
306 case TypeLoc::TemplateSpecialization: {
307 auto TL = T->castAs<TemplateSpecializationTypeLoc>();
308 QualifierToRemove = TL.getQualifierLoc();
309 if (!QualifierToRemove)
310 break;
311 SpelledNameRange = TL.getTemplateNameLoc();
312 if (auto *TD = TL.getTypePtr()->getTemplateName().getAsTemplateDecl(
313 /*IgnoreDeduced=*/true))
314 MustInsertAfterLoc = TD->getBeginLoc();
315 break;
316 }
317 case TypeLoc::Enum:
318 case TypeLoc::Record:
319 case TypeLoc::InjectedClassName: {
320 auto TL = T->castAs<TagTypeLoc>();
321 QualifierToRemove = TL.getQualifierLoc();
322 if (!QualifierToRemove)
323 break;
324 SpelledNameRange = TL.getNameLoc();
325 MustInsertAfterLoc = TL.getOriginalDecl()->getBeginLoc();
326 break;
327 }
328 case TypeLoc::Typedef: {
329 auto TL = T->castAs<TypedefTypeLoc>();
330 QualifierToRemove = TL.getQualifierLoc();
331 if (!QualifierToRemove)
332 break;
333 SpelledNameRange = TL.getNameLoc();
334 MustInsertAfterLoc = TL.getDecl()->getBeginLoc();
335 break;
336 }
337 case TypeLoc::UnresolvedUsing: {
338 auto TL = T->castAs<UnresolvedUsingTypeLoc>();
339 QualifierToRemove = TL.getQualifierLoc();
340 if (!QualifierToRemove)
341 break;
342 SpelledNameRange = TL.getNameLoc();
343 MustInsertAfterLoc = TL.getDecl()->getBeginLoc();
344 break;
345 }
346 case TypeLoc::Using: {
347 auto TL = T->castAs<UsingTypeLoc>();
348 QualifierToRemove = TL.getQualifierLoc();
349 if (!QualifierToRemove)
350 break;
351 SpelledNameRange = TL.getNameLoc();
352 MustInsertAfterLoc = TL.getDecl()->getBeginLoc();
353 break;
354 }
355 default:
356 break;
357 }
358 if (QualifierToRemove)
359 SpelledNameRange.setBegin(QualifierToRemove.getBeginLoc());
360 }
361 if (!QualifierToRemove ||
362 // FIXME: This only supports removing qualifiers that are made up of just
363 // namespace names. If qualifier contains a type, we could take the
364 // longest namespace prefix and remove that.
365 QualifierToRemove.getNestedNameSpecifier().getKind() !=
366 NestedNameSpecifier::Kind::Namespace ||
367 // Respect user config.
368 isNamespaceForbidden(Inputs, QualifierToRemove.getNestedNameSpecifier()))
369 return false;
370 // Macros are difficult. We only want to offer code action when what's spelled
371 // under the cursor is a namespace qualifier. If it's a macro that expands to
372 // a qualifier, user would not know what code action will actually change.
373 // On the other hand, if the qualifier is part of the macro argument, we
374 // should still support that.
375 if (SM.isMacroBodyExpansion(QualifierToRemove.getBeginLoc()) ||
376 !SM.isWrittenInSameFile(QualifierToRemove.getBeginLoc(),
377 QualifierToRemove.getEndLoc())) {
378 return false;
379 }
380
381 auto SpelledTokens =
382 TB.spelledForExpanded(TB.expandedTokens(SpelledNameRange));
383 if (!SpelledTokens)
384 return false;
385 auto SpelledRange =
386 syntax::Token::range(SM, SpelledTokens->front(), SpelledTokens->back());
387 // We only drop qualifiers that're namespaces, so this is safe.
388 std::tie(SpelledQualifier, SpelledName) =
389 splitQualifiedName(SpelledRange.text(SM));
390 QualifierToSpell = getNNSLAsString(
391 QualifierToRemove, Inputs.AST->getASTContext().getPrintingPolicy());
392 if (!llvm::StringRef(QualifierToSpell).ends_with(SpelledQualifier) ||
393 SpelledName.empty())
394 return false; // What's spelled doesn't match the qualifier.
395 return true;
396}
397
398Expected<Tweak::Effect> AddUsing::apply(const Selection &Inputs) {
399 auto &SM = Inputs.AST->getSourceManager();
400
401 tooling::Replacements R;
402 if (auto Err = R.add(tooling::Replacement(
403 SM, SM.getSpellingLoc(QualifierToRemove.getBeginLoc()),
404 SpelledQualifier.size(), ""))) {
405 return std::move(Err);
406 }
407
408 auto InsertionPoint = findInsertionPoint(Inputs, QualifierToRemove,
409 SpelledName, MustInsertAfterLoc);
410 if (!InsertionPoint) {
411 return InsertionPoint.takeError();
412 }
413
414 if (InsertionPoint->Loc.isValid()) {
415 // Add the using statement at appropriate location.
416 std::string UsingText;
417 llvm::raw_string_ostream UsingTextStream(UsingText);
418 UsingTextStream << "using ";
419 if (InsertionPoint->AlwaysFullyQualify &&
420 !QualifierToRemove.getNestedNameSpecifier().isFullyQualified())
421 UsingTextStream << "::";
422 UsingTextStream << QualifierToSpell << SpelledName << ";"
423 << InsertionPoint->Suffix;
424
425 assert(SM.getFileID(InsertionPoint->Loc) == SM.getMainFileID());
426 if (auto Err = R.add(tooling::Replacement(SM, InsertionPoint->Loc, 0,
427 UsingTextStream.str()))) {
428 return std::move(Err);
429 }
430 }
431
432 return Effect::mainFileEdit(Inputs.AST->getASTContext().getSourceManager(),
433 std::move(R));
434}
435
436} // namespace
437} // namespace clangd
438} // namespace clang
#define REGISTER_TWEAK(Subclass)
Definition Tweak.h:129
An interface base for small context-sensitive refactoring actions.
Definition Tweak.h:46
FIXME: Skip testing on windows temporarily due to the different escaping code mode.
Definition AST.cpp:45
std::pair< StringRef, StringRef > splitQualifiedName(StringRef QName)
llvm::Error error(std::error_code EC, const char *Fmt, Ts &&... Vals)
Definition Logger.h:79
bool isHeaderFile(llvm::StringRef FileName, std::optional< LangOptions > LangOpts)
Infers whether this is a header from the FileName and LangOpts (if presents).
std::string printNamespaceScope(const DeclContext &DC)
Returns the first enclosing namespace scope starting from DC.
Definition AST.cpp:303
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
static const llvm::StringLiteral REFACTOR_KIND
Definition Protocol.h:1085
static const Config & current()
Returns the Config of the current Context, or an empty configuration.
Definition Config.cpp:17