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