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 32 static std::string capitalize(std::string Message) { 33 if (!Message.empty()) 34 Message[0] = llvm::toUpper(Message[0]); 35 return Message; 36 } 37 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 77 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 } 85 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 } 93 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> 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 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 override final; 176 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 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> 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> 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 321 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