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
21namespace clang {
22namespace clangd {
23namespace {
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/// ^^^
39class ExpandDeducedType : public Tweak {
40public:
41 const char *id() const final;
42 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
49private:
50 SourceRange Range;
51};
52
53REGISTER_TWEAK(ExpandDeducedType)
54
55std::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.
61bool 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
69bool 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.
78bool 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.
96bool isTemplateParam(const SelectionTree::Node *Node) {
97 if (Node->Parent)
98 if (Node->Parent->ASTNode.get<NonTypeTemplateParmDecl>())
99 return true;
100 return false;
101}
102
103bool 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(N: 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
132Expected<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(Fmt: "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(Fmt: "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(Suffix: "DECLARATOR_ID"))
162 return error(Fmt: "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(SM: SrcMgr, Replacements: tooling::Replacements(Expansion));
169}
170
171} // namespace
172} // namespace clangd
173} // namespace clang
174

source code of clang-tools-extra/clangd/refactor/tweaks/ExpandDeducedType.cpp