1//===--- ObjCMemberwiseInitializer.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 "ParsedAST.h"
10#include "SourceCode.h"
11#include "refactor/InsertionPoint.h"
12#include "refactor/Tweak.h"
13#include "support/Logger.h"
14#include "clang/AST/DeclObjC.h"
15#include "clang/AST/PrettyPrinter.h"
16#include "clang/Basic/LLVM.h"
17#include "clang/Basic/LangOptions.h"
18#include "clang/Basic/SourceLocation.h"
19#include "clang/Basic/SourceManager.h"
20#include "clang/Tooling/Core/Replacement.h"
21#include "llvm/ADT/StringRef.h"
22#include "llvm/ADT/iterator_range.h"
23#include "llvm/Support/Casting.h"
24#include "llvm/Support/Error.h"
25#include <optional>
26
27namespace clang {
28namespace clangd {
29namespace {
30
31static std::string capitalize(std::string Message) {
32 if (!Message.empty())
33 Message[0] = llvm::toUpper(x: Message[0]);
34 return Message;
35}
36
37static std::string getTypeStr(const QualType &OrigT, const Decl &D,
38 unsigned PropertyAttributes) {
39 QualType T = OrigT;
40 PrintingPolicy Policy(D.getASTContext().getLangOpts());
41 Policy.SuppressStrongLifetime = true;
42 std::string Prefix;
43 // If the nullability is specified via a property attribute, use the shorter
44 // `nullable` form for the method parameter.
45 if (PropertyAttributes & ObjCPropertyAttribute::kind_nullability) {
46 if (auto Kind = AttributedType::stripOuterNullability(T)) {
47 switch (*Kind) {
48 case NullabilityKind::Nullable:
49 Prefix = "nullable ";
50 break;
51 case NullabilityKind::NonNull:
52 Prefix = "nonnull ";
53 break;
54 case NullabilityKind::Unspecified:
55 Prefix = "null_unspecified ";
56 break;
57 case NullabilityKind::NullableResult:
58 T = OrigT;
59 break;
60 }
61 }
62 }
63 return Prefix + T.getAsString(Policy);
64}
65
66struct MethodParameter {
67 // Parameter name.
68 llvm::StringRef Name;
69
70 // Type of the parameter.
71 std::string Type;
72
73 // Assignment target (LHS).
74 std::string Assignee;
75
76 MethodParameter(const ObjCIvarDecl &ID) {
77 // Convention maps `@property int foo` to ivar `int _foo`, so drop the
78 // leading `_` if there is one.
79 Name = ID.getName();
80 Name.consume_front(Prefix: "_");
81 Type = getTypeStr(ID.getType(), ID, ObjCPropertyAttribute::kind_noattr);
82 Assignee = ID.getName().str();
83 }
84 MethodParameter(const ObjCPropertyDecl &PD) {
85 Name = PD.getName();
86 Type = getTypeStr(PD.getType(), PD, PD.getPropertyAttributes());
87 if (const auto *ID = PD.getPropertyIvarDecl())
88 Assignee = ID->getName().str();
89 else // Could be a dynamic property or a property in a header.
90 Assignee = ("self." + Name).str();
91 }
92 static std::optional<MethodParameter> parameterFor(const Decl &D) {
93 if (const auto *ID = dyn_cast<ObjCIvarDecl>(Val: &D))
94 return MethodParameter(*ID);
95 if (const auto *PD = dyn_cast<ObjCPropertyDecl>(Val: &D))
96 if (PD->isInstanceProperty())
97 return MethodParameter(*PD);
98 return std::nullopt;
99 }
100};
101
102static SmallVector<MethodParameter, 8>
103getAllParams(const ObjCInterfaceDecl *ID) {
104 SmallVector<MethodParameter, 8> Params;
105 // Currently we only generate based on the ivars and properties declared
106 // in the interface. We could consider expanding this to include visible
107 // categories + class extensions in the future (see
108 // all_declared_ivar_begin).
109 llvm::DenseSet<llvm::StringRef> Names;
110 for (const auto *Ivar : ID->ivars()) {
111 MethodParameter P(*Ivar);
112 if (Names.insert(V: P.Name).second)
113 Params.push_back(Elt: P);
114 }
115 for (const auto *Prop : ID->properties()) {
116 MethodParameter P(*Prop);
117 if (Names.insert(P.Name).second)
118 Params.push_back(P);
119 }
120 return Params;
121}
122
123static std::string
124initializerForParams(const SmallVector<MethodParameter, 8> &Params,
125 bool GenerateImpl) {
126 std::string Code;
127 llvm::raw_string_ostream Stream(Code);
128
129 if (Params.empty()) {
130 if (GenerateImpl) {
131 Stream <<
132 R"cpp(- (instancetype)init {
133 self = [super init];
134 if (self) {
135
136 }
137 return self;
138})cpp";
139 } else {
140 Stream << "- (instancetype)init;";
141 }
142 } else {
143 const auto &First = Params.front();
144 Stream << llvm::formatv(Fmt: "- (instancetype)initWith{0}:({1}){2}",
145 Vals: capitalize(Message: First.Name.trim().str()), Vals: First.Type,
146 Vals: First.Name);
147 for (const auto &It : llvm::drop_begin(RangeOrContainer: Params))
148 Stream << llvm::formatv(Fmt: " {0}:({1}){0}", Vals: It.Name, Vals: It.Type);
149
150 if (GenerateImpl) {
151 Stream <<
152 R"cpp( {
153 self = [super init];
154 if (self) {)cpp";
155 for (const auto &Param : Params)
156 Stream << llvm::formatv(Fmt: "\n {0} = {1};", Vals: Param.Assignee, Vals: Param.Name);
157 Stream <<
158 R"cpp(
159 }
160 return self;
161})cpp";
162 } else {
163 Stream << ";";
164 }
165 }
166 Stream << "\n\n";
167 return Code;
168}
169
170/// Generate an initializer for an Objective-C class based on selected
171/// properties and instance variables.
172class ObjCMemberwiseInitializer : public Tweak {
173public:
174 const char *id() const final;
175 llvm::StringLiteral kind() const override {
176 return CodeAction::REFACTOR_KIND;
177 }
178
179 bool prepare(const Selection &Inputs) override;
180 Expected<Tweak::Effect> apply(const Selection &Inputs) override;
181 std::string title() const override;
182
183private:
184 SmallVector<MethodParameter, 8>
185 paramsForSelection(const SelectionTree::Node *N);
186
187 const ObjCInterfaceDecl *Interface = nullptr;
188
189 // Will be nullptr if running on an interface.
190 const ObjCImplementationDecl *Impl = nullptr;
191};
192
193REGISTER_TWEAK(ObjCMemberwiseInitializer)
194
195bool ObjCMemberwiseInitializer::prepare(const Selection &Inputs) {
196 const SelectionTree::Node *N = Inputs.ASTSelection.commonAncestor();
197 if (!N)
198 return false;
199 const Decl *D = N->ASTNode.get<Decl>();
200 if (!D)
201 return false;
202 const auto &LangOpts = Inputs.AST->getLangOpts();
203 // Require ObjC w/ arc enabled since we don't emit retains.
204 if (!LangOpts.ObjC || !LangOpts.ObjCAutoRefCount)
205 return false;
206
207 // We support the following selected decls:
208 // - ObjCInterfaceDecl/ObjCImplementationDecl only - generate for all
209 // properties and ivars
210 //
211 // - Specific ObjCPropertyDecl(s)/ObjCIvarDecl(s) - generate only for those
212 // selected. Note that if only one is selected, the common ancestor will be
213 // the ObjCPropertyDecl/ObjCIvarDecl itself instead of the container.
214 if (const auto *ID = dyn_cast<ObjCInterfaceDecl>(D)) {
215 // Ignore forward declarations (@class Name;).
216 if (!ID->isThisDeclarationADefinition())
217 return false;
218 Interface = ID;
219 } else if (const auto *ID = dyn_cast<ObjCImplementationDecl>(D)) {
220 Interface = ID->getClassInterface();
221 Impl = ID;
222 } else if (isa<ObjCPropertyDecl, ObjCIvarDecl>(Val: D)) {
223 const auto *DC = D->getDeclContext();
224 if (const auto *ID = dyn_cast<ObjCInterfaceDecl>(DC)) {
225 Interface = ID;
226 } else if (const auto *ID = dyn_cast<ObjCImplementationDecl>(DC)) {
227 Interface = ID->getClassInterface();
228 Impl = ID;
229 }
230 }
231 return Interface != nullptr;
232}
233
234SmallVector<MethodParameter, 8>
235ObjCMemberwiseInitializer::paramsForSelection(const SelectionTree::Node *N) {
236 SmallVector<MethodParameter, 8> Params;
237 // Base case: selected a single ivar or property.
238 if (const auto *D = N->ASTNode.get<Decl>()) {
239 if (auto Param = MethodParameter::parameterFor(*D)) {
240 Params.push_back(*Param);
241 return Params;
242 }
243 }
244 const ObjCContainerDecl *Container =
245 Impl ? static_cast<const ObjCContainerDecl *>(Impl)
246 : static_cast<const ObjCContainerDecl *>(Interface);
247 if (Container == N->ASTNode.get<ObjCContainerDecl>() && N->Children.empty())
248 return getAllParams(ID: Interface);
249
250 llvm::DenseSet<llvm::StringRef> Names;
251 // Check for selecting multiple ivars/properties.
252 for (const auto *CNode : N->Children) {
253 const Decl *D = CNode->ASTNode.get<Decl>();
254 if (!D)
255 continue;
256 if (auto P = MethodParameter::parameterFor(*D))
257 if (Names.insert(P->Name).second)
258 Params.push_back(*P);
259 }
260 return Params;
261}
262
263Expected<Tweak::Effect>
264ObjCMemberwiseInitializer::apply(const Selection &Inputs) {
265 const auto &SM = Inputs.AST->getASTContext().getSourceManager();
266 const SelectionTree::Node *N = Inputs.ASTSelection.commonAncestor();
267 if (!N)
268 return error(Fmt: "Invalid selection");
269
270 SmallVector<MethodParameter, 8> Params = paramsForSelection(N);
271
272 // Insert before the first non-init instance method.
273 std::vector<Anchor> Anchors = {
274 {.Match: [](const Decl *D) {
275 if (const auto *MD = llvm::dyn_cast<ObjCMethodDecl>(Val: D)) {
276 return MD->getMethodFamily() != OMF_init && MD->isInstanceMethod();
277 }
278 return false;
279 },
280 .Direction: Anchor::Above}};
281 Effect E;
282
283 auto InterfaceReplacement =
284 insertDecl(initializerForParams(Params, /*GenerateImpl=*/false),
285 *Interface, Anchors);
286 if (!InterfaceReplacement)
287 return InterfaceReplacement.takeError();
288 auto FE = Effect::fileEdit(SM, FID: SM.getFileID(Interface->getLocation()),
289 Replacements: tooling::Replacements(*InterfaceReplacement));
290 if (!FE)
291 return FE.takeError();
292 E.ApplyEdits.insert(std::move(*FE));
293
294 if (Impl) {
295 // If we see the class implementation, add the initializer there too.
296 // FIXME: merging the edits is awkward, do this elsewhere.
297 auto ImplReplacement = insertDecl(
298 initializerForParams(Params, /*GenerateImpl=*/true), *Impl, Anchors);
299 if (!ImplReplacement)
300 return ImplReplacement.takeError();
301
302 if (SM.isWrittenInSameFile(Loc1: Interface->getLocation(), Loc2: Impl->getLocation())) {
303 // Merge with previous edit if they are in the same file.
304 if (auto Err =
305 E.ApplyEdits.begin()->second.Replacements.add(*ImplReplacement))
306 return std::move(Err);
307 } else {
308 // Generate a new edit if the interface and implementation are in
309 // different files.
310 auto FE = Effect::fileEdit(SM, FID: SM.getFileID(Impl->getLocation()),
311 Replacements: tooling::Replacements(*ImplReplacement));
312 if (!FE)
313 return FE.takeError();
314 E.ApplyEdits.insert(std::move(*FE));
315 }
316 }
317 return E;
318}
319
320std::string ObjCMemberwiseInitializer::title() const {
321 if (Impl)
322 return "Generate memberwise initializer";
323 return "Declare memberwise initializer";
324}
325
326} // namespace
327} // namespace clangd
328} // namespace clang
329

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