brintos

brintos / llvm-project-archived public Read only

0
0
Text · 10.4 KiB · c462a69 Raw
329 lines · cpp
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-exception6//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(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 shorter44  // `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 the78    // 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  }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>(&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 102static SmallVector<MethodParameter, 8>103getAllParams(const ObjCInterfaceDecl *ID) {104  SmallVector<MethodParameter, 8> Params;105  // Currently we only generate based on the ivars and properties declared106  // in the interface. We could consider expanding this to include visible107  // categories + class extensions in the future (see108  // 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 123static std::string124initializerForParams(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 selected171/// 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 all209  //   properties and ivars210  //211  // - Specific ObjCPropertyDecl(s)/ObjCIvarDecl(s) - generate only for those212  //   selected. Note that if only one is selected, the common ancestor will be213  //   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 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(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("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 in309      // 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 320std::string ObjCMemberwiseInitializer::title() const {321  if (Impl)322    return "Generate memberwise initializer";323  return "Declare memberwise initializer";324}325 326} // namespace327} // namespace clangd328} // namespace clang329