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