xref: /llvm-project/clang-tools-extra/clangd/refactor/tweaks/ExpandDeducedType.cpp (revision 1feb7af046889728233e67e3163ab30020207bb2)
1 //===--- ExpandDeducedType.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 #include "refactor/Tweak.h"
9 
10 #include "support/Logger.h"
11 #include "clang/AST/Type.h"
12 #include "clang/AST/TypeLoc.h"
13 #include "clang/Basic/LLVM.h"
14 #include "llvm/Support/Error.h"
15 #include <AST.h>
16 #include <climits>
17 #include <memory>
18 #include <optional>
19 #include <string>
20 
21 namespace clang {
22 namespace clangd {
23 namespace {
24 
25 /// Expand the "auto" type to the derived type
26 /// Before:
27 ///    auto x = Something();
28 ///    ^^^^
29 /// After:
30 ///    MyClass x = Something();
31 ///    ^^^^^^^
32 /// Expand `decltype(expr)` to the deduced type
33 /// Before:
34 ///   decltype(0) i;
35 ///   ^^^^^^^^^^^
36 /// After:
37 ///   int i;
38 ///   ^^^
39 class ExpandDeducedType : public Tweak {
40 public:
41   const char *id() const final;
kind() const42   llvm::StringLiteral kind() const override {
43     return CodeAction::REFACTOR_KIND;
44   }
45   bool prepare(const Selection &Inputs) override;
46   Expected<Effect> apply(const Selection &Inputs) override;
47   std::string title() const override;
48 
49 private:
50   SourceRange Range;
51 };
52 
REGISTER_TWEAK(ExpandDeducedType)53 REGISTER_TWEAK(ExpandDeducedType)
54 
55 std::string ExpandDeducedType::title() const {
56   return "Replace with deduced type";
57 }
58 
59 // Structured bindings must use auto, e.g. `const auto& [a,b,c] = ...;`.
60 // Return whether N (an AutoTypeLoc) is such an auto that must not be expanded.
isStructuredBindingType(const SelectionTree::Node * N)61 bool isStructuredBindingType(const SelectionTree::Node *N) {
62   // Walk up the TypeLoc chain, because auto may be qualified.
63   while (N && N->ASTNode.get<TypeLoc>())
64     N = N->Parent;
65   // The relevant type is the only direct type child of a Decomposition.
66   return N && N->ASTNode.get<DecompositionDecl>();
67 }
68 
isLambda(QualType QT)69 bool isLambda(QualType QT) {
70   if (!QT.isNull())
71     if (const auto *RD = QT->getAsRecordDecl())
72       return RD->isLambda();
73   return false;
74 }
75 
76 // Returns true iff Node is a lambda, and thus should not be expanded. Loc is
77 // the location of the auto type.
isDeducedAsLambda(const SelectionTree::Node * Node,SourceLocation Loc)78 bool isDeducedAsLambda(const SelectionTree::Node *Node, SourceLocation Loc) {
79   // getDeducedType() does a traversal, which we want to avoid in prepare().
80   // But at least check this isn't auto x = []{...};, which can't ever be
81   // expanded.
82   // (It would be nice if we had an efficient getDeducedType(), instead).
83   for (const auto *It = Node; It; It = It->Parent) {
84     if (const auto *DD = It->ASTNode.get<DeclaratorDecl>()) {
85       if (DD->getTypeSourceInfo() &&
86           DD->getTypeSourceInfo()->getTypeLoc().getBeginLoc() == Loc &&
87           isLambda(DD->getType()))
88         return true;
89     }
90   }
91   return false;
92 }
93 
94 // Returns true iff "auto" in Node is really part of the template parameter,
95 // which we cannot expand.
isTemplateParam(const SelectionTree::Node * Node)96 bool isTemplateParam(const SelectionTree::Node *Node) {
97   if (Node->Parent)
98     if (Node->Parent->ASTNode.get<NonTypeTemplateParmDecl>())
99       return true;
100   return false;
101 }
102 
prepare(const Selection & Inputs)103 bool ExpandDeducedType::prepare(const Selection &Inputs) {
104   if (auto *Node = Inputs.ASTSelection.commonAncestor()) {
105     if (auto *TypeNode = Node->ASTNode.get<TypeLoc>()) {
106       if (const AutoTypeLoc Result = TypeNode->getAs<AutoTypeLoc>()) {
107         if (!isStructuredBindingType(Node) &&
108             !isDeducedAsLambda(Node, Result.getBeginLoc()) &&
109             !isTemplateParam(Node))
110           Range = Result.getSourceRange();
111       }
112       if (auto TTPAuto = TypeNode->getAs<TemplateTypeParmTypeLoc>()) {
113         // We exclude concept constraints for now, as the SourceRange is wrong.
114         // void foo(C auto x) {};
115         //            ^^^^
116         // TTPAuto->getSourceRange only covers "auto", not "C auto".
117         if (TTPAuto.getDecl()->isImplicit() &&
118             !TTPAuto.getDecl()->hasTypeConstraint())
119           Range = TTPAuto.getSourceRange();
120       }
121 
122       if (auto DTTL = TypeNode->getAs<DecltypeTypeLoc>()) {
123         if (!isLambda(cast<DecltypeType>(DTTL.getType())->getUnderlyingType()))
124           Range = DTTL.getSourceRange();
125       }
126     }
127   }
128 
129   return Range.isValid();
130 }
131 
apply(const Selection & Inputs)132 Expected<Tweak::Effect> ExpandDeducedType::apply(const Selection &Inputs) {
133   auto &SrcMgr = Inputs.AST->getSourceManager();
134 
135   std::optional<clang::QualType> DeducedType =
136       getDeducedType(Inputs.AST->getASTContext(), Range.getBegin());
137 
138   // if we can't resolve the type, return an error message
139   if (DeducedType == std::nullopt || (*DeducedType)->isUndeducedAutoType())
140     return error("Could not deduce type for 'auto' type");
141 
142   // we shouldn't replace a dependent type which is likely not to print
143   // usefully, e.g.
144   //   template <class T>
145   //   struct Foobar {
146   //     decltype(T{}) foobar;
147   //     ^^^^^^^^^^^^^ would turn out to be `<dependent-type>`
148   //   };
149   if ((*DeducedType)->isDependentType())
150     return error("Could not expand a dependent type");
151 
152   // Some types aren't written as single chunks of text, e.g:
153   //   auto fptr = &func; // auto is void(*)()
154   // ==>
155   //   void (*fptr)() = &func;
156   // Replacing these requires examining the declarator, we don't support it yet.
157   std::string PrettyDeclarator = printType(
158       *DeducedType, Inputs.ASTSelection.commonAncestor()->getDeclContext(),
159       "DECLARATOR_ID");
160   llvm::StringRef PrettyTypeName = PrettyDeclarator;
161   if (!PrettyTypeName.consume_back("DECLARATOR_ID"))
162     return error("Could not expand type that isn't a simple string");
163   PrettyTypeName = PrettyTypeName.rtrim();
164 
165   tooling::Replacement Expansion(SrcMgr, CharSourceRange(Range, true),
166                                  PrettyTypeName);
167 
168   return Effect::mainFileEdit(SrcMgr, tooling::Replacements(Expansion));
169 }
170 
171 } // namespace
172 } // namespace clangd
173 } // namespace clang
174