[PATCH] D116385: [clangd] Code action for creating an ObjC initializer

Sam McCall via Phabricator via cfe-commits cfe-commits at lists.llvm.org
Sun Jan 2 08:58:29 PST 2022


sammccall added a comment.

This looks like a useful feature, I had been thinking about adding the equivalent for C++...

High level feedback:

- this should generate both the declaration and definition
- i doubt the interaction around subsetting fields is going to be satisfying
- don't spend complexity on lots of options/micromanaging formatting until basic functionality is solid



================
Comment at: clang-tools-extra/clangd/refactor/tweaks/ObjCMemberwiseInitializer.cpp:66
+
+// Returns the effective begin loc for D, including attached leading comments.
+static SourceLocation effectiveBeginLoc(const Decl *D,
----------------
If we're going to care about this (which makes sense), this should be a rangeIncludingComment() in AST.h or so


================
Comment at: clang-tools-extra/clangd/refactor/tweaks/ObjCMemberwiseInitializer.cpp:85
+    if (SM.isBeforeInTranslationUnit(DeclEnd, CommentEnd)) {
+      return CommentEnd;
+    }
----------------
the two returns are inconsistent, this one is the first position outside the range, the other return is the last token (not character) in the range


================
Comment at: clang-tools-extra/clangd/refactor/tweaks/ObjCMemberwiseInitializer.cpp:91
+
+struct LocationWithPadding {
+  SourceLocation Loc;
----------------
We try not to spend complexity on formatting beyond what clang-format cares about. 

If you're inserting before a member (or @end), always inserting 2 newlines seems close enough? (clang-format will strip consecutive blank lines)


================
Comment at: clang-tools-extra/clangd/refactor/tweaks/ObjCMemberwiseInitializer.cpp:113
+    if (MD->getMethodFamily() == OMF_init) {
+      Loc = effectiveEndLoc(MD, SM).getLocWithOffset(1);
+    } else if (Loc.isValid()) {
----------------
This algorithm doesn't make much sense to me. 
You want to insert before the first non-init instance method, falling back to the end.
So why do you need to do more than search for the first non-init instance method, and find the location before it? In particular why are we looking at init methods and locations after them?


================
Comment at: clang-tools-extra/clangd/refactor/tweaks/ObjCMemberwiseInitializer.cpp:141
+    Name = ID.getName();
+    if (Name.startswith("_"))
+      Name = Name.substr(1);
----------------
Name.consume_front("_"); (FWIW I don't think this needs a comment)


================
Comment at: clang-tools-extra/clangd/refactor/tweaks/ObjCMemberwiseInitializer.cpp:199
+
+  // We support the following selected decls:
+  // - ObjCInterfaceDecl/ObjCImplementationDecl only - generate for all
----------------
I understand the desire to select the particular fields, but given the limitation of the interaction I wonder whether these cases are useful & discoverable enough to be worth the complexity.

- how often is an initializer that sets one variable of many useful?
- out of the times when a subset of variables should be initialized, how often are they contiguous in the code?

I think it would likely be better to start with initializing all members...


================
Comment at: clang-tools-extra/clangd/refactor/tweaks/ObjCMemberwiseInitializer.cpp:218
+    } else if (const auto *ID = dyn_cast<ObjCImplementationDecl>(DC)) {
+      Container = ID;
+    }
----------------
while here, also grab the InterfaceDecl into another field so you don't have to go casehunting again in initParams?


================
Comment at: clang-tools-extra/clangd/refactor/tweaks/ObjCMemberwiseInitializer.cpp:224
+
+void ObjCMemberwiseInitializer::initParams(
+    const SelectionTree::Node *N, SmallVectorImpl<MethodParameter> &Params) {
----------------
can you rename this method to something clearer?
unclear whether "init" stands for "initialize" or "initializer" here, which would refer to pretty different things!


================
Comment at: clang-tools-extra/clangd/refactor/tweaks/ObjCMemberwiseInitializer.cpp:251
+  }
+  // If just the Container itself was selected, fetch all properties and ivars
+  // for the given class.
----------------
Inferring the container was selected by looking at HadBadNode/Params seems very indirect.
Why not handle this case at the top? `if (Container == N->ASTNode.get<ObjcContainerDecl>()) return getAllParams(Iface)`



================
Comment at: clang-tools-extra/clangd/refactor/tweaks/ObjCMemberwiseInitializer.cpp:282
+  const SelectionTree::Node *N = Inputs.ASTSelection.commonAncestor();
+  if (!N || !Container)
+    return error("Invalid selection");
----------------
!Container isn't possible here


================
Comment at: clang-tools-extra/clangd/refactor/tweaks/ObjCMemberwiseInitializer.cpp:285
+
+  SmallVector<MethodParameter, 8> Params;
+  initParams(N, Params);
----------------
nit: why isn't this a return value?


================
Comment at: clang-tools-extra/clangd/refactor/tweaks/ObjCMemberwiseInitializer.cpp:290
+
+  bool GenerateImpl = isa<ObjCImplementationDecl>(Container);
+  llvm::SmallString<256> Text(std::string(LocPadding.PadStart, '\n'));
----------------
Wait, so if the user triggers the action on @interface then they'll get a decl with no impl, and if they trigger on @implementation they'll get the opposite?

Seems like we should at least try to generate both, no? (May not be possible if we're triggering on @interface and index lookup fails, but still...)


================
Comment at: clang-tools-extra/clangd/refactor/tweaks/ObjCMemberwiseInitializer.cpp:291
+  bool GenerateImpl = isa<ObjCImplementationDecl>(Container);
+  llvm::SmallString<256> Text(std::string(LocPadding.PadStart, '\n'));
+
----------------
For building strings like this with formatting, we usually use raw_string_ostream and `formatv`.
This would make the stuff starting at 303 easier to read


================
Comment at: clang-tools-extra/clangd/refactor/tweaks/ObjCMemberwiseInitializer.cpp:295
+    if (GenerateImpl) {
+      Text.append({"- (instancetype)init {\n  self = [super init];\n  if "
+                   "(self) {\n\n  }\n  return self;\n}"});
----------------
This is hard to read, use a raw string


================
Comment at: clang-tools-extra/clangd/refactor/tweaks/ObjCMemberwiseInitializer.cpp:298
+    } else {
+      Text.append({"- (instancetype)init;"});
+    }
----------------
nit: why braces? (and elsewhere)


================
Comment at: clang-tools-extra/clangd/refactor/tweaks/ObjCMemberwiseInitializer.cpp:320
+
+  const SourceManager &SM = Inputs.AST->getSourceManager();
+
----------------
you need to verify that Loc is actually in the main file :-(


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D116385/new/

https://reviews.llvm.org/D116385



More information about the cfe-commits mailing list