xref: /llvm-project/clang-tools-extra/clangd/refactor/tweaks/ObjCMemberwiseInitializer.cpp (revision 29ffafb5754100502da70171b47ee8a0f722c994)
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 
27 namespace clang {
28 namespace clangd {
29 namespace {
30 
capitalize(std::string Message)31 static std::string capitalize(std::string Message) {
32   if (!Message.empty())
33     Message[0] = llvm::toUpper(Message[0]);
34   return Message;
35 }
36 
getTypeStr(const QualType & OrigT,const Decl & D,unsigned PropertyAttributes)37 static 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 
66 struct 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 
MethodParameterclang::clangd::__anonf22d6b430111::MethodParameter76   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("_");
81     Type = getTypeStr(ID.getType(), ID, ObjCPropertyAttribute::kind_noattr);
82     Assignee = ID.getName().str();
83   }
MethodParameterclang::clangd::__anonf22d6b430111::MethodParameter84   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   }
parameterForclang::clangd::__anonf22d6b430111::MethodParameter92   static std::optional<MethodParameter> parameterFor(const Decl &D) {
93     if (const auto *ID = dyn_cast<ObjCIvarDecl>(&D))
94       return MethodParameter(*ID);
95     if (const auto *PD = dyn_cast<ObjCPropertyDecl>(&D))
96       if (PD->isInstanceProperty())
97         return MethodParameter(*PD);
98     return std::nullopt;
99   }
100 };
101 
102 static SmallVector<MethodParameter, 8>
getAllParams(const ObjCInterfaceDecl * ID)103 getAllParams(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(P.Name).second)
113       Params.push_back(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 
123 static std::string
initializerForParams(const SmallVector<MethodParameter,8> & Params,bool GenerateImpl)124 initializerForParams(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("- (instancetype)initWith{0}:({1}){2}",
145                             capitalize(First.Name.trim().str()), First.Type,
146                             First.Name);
147     for (const auto &It : llvm::drop_begin(Params))
148       Stream << llvm::formatv(" {0}:({1}){0}", It.Name, 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("\n    {0} = {1};", Param.Assignee, 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.
172 class ObjCMemberwiseInitializer : public Tweak {
173 public:
174   const char *id() const final;
kind() const175   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 
183 private:
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 
REGISTER_TWEAK(ObjCMemberwiseInitializer)193 REGISTER_TWEAK(ObjCMemberwiseInitializer)
194 
195 bool 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>(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 
234 SmallVector<MethodParameter, 8>
paramsForSelection(const SelectionTree::Node * N)235 ObjCMemberwiseInitializer::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(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 
263 Expected<Tweak::Effect>
apply(const Selection & Inputs)264 ObjCMemberwiseInitializer::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("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       {[](const Decl *D) {
275          if (const auto *MD = llvm::dyn_cast<ObjCMethodDecl>(D)) {
276            return MD->getMethodFamily() != OMF_init && MD->isInstanceMethod();
277          }
278          return false;
279        },
280        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, SM.getFileID(Interface->getLocation()),
289                              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(Interface->getLocation(), 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, SM.getFileID(Impl->getLocation()),
311                                  tooling::Replacements(*ImplReplacement));
312       if (!FE)
313         return FE.takeError();
314       E.ApplyEdits.insert(std::move(*FE));
315     }
316   }
317   return E;
318 }
319 
title() const320 std::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