]> granicus.if.org Git - clang/commitdiff
Implement code completion for Objective-C method declarations and
authorDouglas Gregor <dgregor@apple.com>
Wed, 7 Apr 2010 00:21:17 +0000 (00:21 +0000)
committerDouglas Gregor <dgregor@apple.com>
Wed, 7 Apr 2010 00:21:17 +0000 (00:21 +0000)
definitions, e.g., after

  -

or

  - (id)

we'll find all of the "likely" instance methods that one would want to
declare or define at this point. In the latter case, we only produce
results whose return types match "id".

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@100587 91177308-0d34-0410-b5e6-96231b3b80d8

include/clang/Parse/Action.h
lib/Parse/ParseObjc.cpp
lib/Sema/CodeCompleteConsumer.cpp
lib/Sema/Sema.h
lib/Sema/SemaCodeComplete.cpp
test/Index/complete-method-decls.m [new file with mode: 0644]

index 59cc0d218cec4bf481f0b2fd3bace57882e50573..4122aff91ffcbf2b6e5eeb8de8e86190ebbca591 100644 (file)
@@ -2795,6 +2795,32 @@ public:
                                                    IdentifierInfo *PropertyName,
                                                   DeclPtrTy ObjCImpDecl) {
   }
+
+  /// \brief Code completion for an Objective-C method declaration or
+  /// definition, which may occur within an interface, category,
+  /// extension, protocol, or implementation thereof (where applicable).
+  ///
+  /// This code completion action is invoked after the "-" or "+" that
+  /// starts a method declaration or definition, and after the return
+  /// type such a declaration (e.g., "- (id)").
+  ///
+  /// \param S The scope in which the completion occurs.
+  ///
+  /// \param IsInstanceMethod Whether this is an instance method
+  /// (introduced with '-'); otherwise, it's a class method
+  /// (introduced with '+').
+  ///
+  /// \param ReturnType If non-NULL, the specified return type of the method
+  /// being declared or defined.
+  ///
+  /// \param IDecl The interface, category, protocol, or
+  /// implementation, or category implementation in which this method
+  /// declaration or definition occurs.
+  virtual void CodeCompleteObjCMethodDecl(Scope *S, 
+                                          bool IsInstanceMethod,
+                                          TypeTy *ReturnType,
+                                          DeclPtrTy IDecl) {
+  }
   //@}
 };
 
index 123a96261ad5d8891f359104dadd424f87a954a2..155e3520731abeb3185944efa17f90b8dec85470 100644 (file)
@@ -787,6 +787,12 @@ Parser::DeclPtrTy Parser::ParseObjCMethodDecl(SourceLocation mLoc,
                                           tok::ObjCKeywordKind MethodImplKind) {
   ParsingDeclRAIIObject PD(*this);
 
+  if (Tok.is(tok::code_completion)) {
+    Actions.CodeCompleteObjCMethodDecl(CurScope, mType == tok::minus, 
+                                       /*ReturnType=*/0, IDecl);
+    ConsumeToken();
+  }
+
   // Parse the return type if present.
   TypeTy *ReturnType = 0;
   ObjCDeclSpec DSRet;
@@ -798,6 +804,12 @@ Parser::DeclPtrTy Parser::ParseObjCMethodDecl(SourceLocation mLoc,
   if (getLang().ObjC2 && Tok.is(tok::kw___attribute))
     MethodAttrs.reset(ParseGNUAttributes());
 
+  if (Tok.is(tok::code_completion)) {
+    Actions.CodeCompleteObjCMethodDecl(CurScope, mType == tok::minus, 
+                                       ReturnType, IDecl);
+    ConsumeToken();
+  }
+
   // Now parse the selector.
   SourceLocation selLoc;
   IdentifierInfo *SelIdent = ParseObjCSelectorPiece(selLoc);
index 5483a292e9558f73d1394fdc2ab948178a27f66f..9f0ee5cde737129574a3ef0542785d3151af1fc6 100644 (file)
@@ -86,7 +86,7 @@ CodeCompletionString::Chunk::Chunk(ChunkKind Kind, llvm::StringRef Text)
     break;
 
   case CK_Colon:
-    this->Text = ": ";
+    this->Text = ":";
     break;
 
   case CK_SemiColon:
index 13aa27ea96f8f0ad50442f42bb4d4f4c5ec4c8d8..5394f06985d1de484711a5e6e04e31bc7f4f45a7 100644 (file)
@@ -4299,6 +4299,10 @@ public:
   virtual void CodeCompleteObjCPropertySynthesizeIvar(Scope *S, 
                                                   IdentifierInfo *PropertyName,
                                                       DeclPtrTy ObjCImpDecl);
+  virtual void CodeCompleteObjCMethodDecl(Scope *S, 
+                                          bool IsInstanceMethod,
+                                          TypeTy *ReturnType,
+                                          DeclPtrTy IDecl);
   //@}
   
   //===--------------------------------------------------------------------===//
index beed0ed2b6c1c6d47f5ec18003882cf6f4615557..66b04a25bcfdd0cc5d27c4ceda2551fe870fc3ce 100644 (file)
@@ -3411,3 +3411,224 @@ void Sema::CodeCompleteObjCPropertySynthesizeIvar(Scope *S,
   
   HandleCodeCompleteResults(this, CodeCompleter, Results.data(),Results.size());
 }
+
+typedef llvm::DenseMap<Selector, ObjCMethodDecl *> KnownMethodsMap;
+
+/// \brief Find all of the methods that reside in the given container
+/// (and its superclasses, protocols, etc.) that meet the given
+/// criteria. Insert those methods into the map of known methods,
+/// indexed by selector so they can be easily found.
+static void FindImplementableMethods(ASTContext &Context,
+                                     ObjCContainerDecl *Container,
+                                     bool WantInstanceMethods,
+                                     QualType ReturnType,
+                                     bool IsInImplementation,
+                                     KnownMethodsMap &KnownMethods) {
+  if (ObjCInterfaceDecl *IFace = dyn_cast<ObjCInterfaceDecl>(Container)) {
+    // Recurse into protocols.
+    const ObjCList<ObjCProtocolDecl> &Protocols
+      = IFace->getReferencedProtocols();
+    for (ObjCList<ObjCProtocolDecl>::iterator I = Protocols.begin(),
+           E = Protocols.end(); 
+         I != E; ++I)
+      FindImplementableMethods(Context, *I, WantInstanceMethods, ReturnType,
+                               IsInImplementation, KnownMethods);
+
+    // If we're not in the implementation of a class, also visit the
+    // superclass.
+    if (!IsInImplementation && IFace->getSuperClass())
+      FindImplementableMethods(Context, IFace->getSuperClass(), 
+                               WantInstanceMethods, ReturnType,
+                               IsInImplementation, KnownMethods);
+
+    // Add methods from any class extensions (but not from categories;
+    // those should go into category implementations).
+    for (ObjCCategoryDecl *Cat = IFace->getCategoryList(); Cat;
+         Cat = Cat->getNextClassCategory()) {
+      if (!Cat->IsClassExtension())
+        continue;
+
+      FindImplementableMethods(Context, Cat, WantInstanceMethods, ReturnType,
+                               IsInImplementation, KnownMethods);      
+    }
+  }
+
+  if (ObjCCategoryDecl *Category = dyn_cast<ObjCCategoryDecl>(Container)) {
+    // Recurse into protocols.
+    const ObjCList<ObjCProtocolDecl> &Protocols
+      = Category->getReferencedProtocols();
+    for (ObjCList<ObjCProtocolDecl>::iterator I = Protocols.begin(),
+           E = Protocols.end(); 
+         I != E; ++I)
+      FindImplementableMethods(Context, *I, WantInstanceMethods, ReturnType,
+                               IsInImplementation, KnownMethods);
+  }
+
+  if (ObjCProtocolDecl *Protocol = dyn_cast<ObjCProtocolDecl>(Container)) {
+    // Recurse into protocols.
+    const ObjCList<ObjCProtocolDecl> &Protocols
+      = Protocol->getReferencedProtocols();
+    for (ObjCList<ObjCProtocolDecl>::iterator I = Protocols.begin(),
+           E = Protocols.end(); 
+         I != E; ++I)
+      FindImplementableMethods(Context, *I, WantInstanceMethods, ReturnType,
+                               IsInImplementation, KnownMethods);
+  }
+
+  // Add methods in this container. This operation occurs last because
+  // we want the methods from this container to override any methods
+  // we've previously seen with the same selector.
+  for (ObjCContainerDecl::method_iterator M = Container->meth_begin(),
+                                       MEnd = Container->meth_end();
+       M != MEnd; ++M) {
+    if ((*M)->isInstanceMethod() == WantInstanceMethods) {
+      if (!ReturnType.isNull() &&
+          !Context.hasSameUnqualifiedType(ReturnType, (*M)->getResultType()))
+        continue;
+
+      KnownMethods[(*M)->getSelector()] = *M;
+    }
+  }
+}
+
+void Sema::CodeCompleteObjCMethodDecl(Scope *S, 
+                                      bool IsInstanceMethod,
+                                      TypeTy *ReturnTy,
+                                      DeclPtrTy IDecl) {
+  // Determine the return type of the method we're declaring, if
+  // provided.
+  QualType ReturnType = GetTypeFromParser(ReturnTy);
+
+  // Determine where we should start searching for methods, and where we 
+  ObjCContainerDecl *SearchDecl = 0, *CurrentDecl = 0;
+  bool IsInImplementation = false;
+  if (Decl *D = IDecl.getAs<Decl>()) {
+    if (ObjCImplementationDecl *Impl = dyn_cast<ObjCImplementationDecl>(D)) {
+      SearchDecl = Impl->getClassInterface();
+      CurrentDecl = Impl;
+      IsInImplementation = true;
+    } else if (ObjCCategoryImplDecl *CatImpl 
+                                       = dyn_cast<ObjCCategoryImplDecl>(D)) {
+      SearchDecl = CatImpl->getCategoryDecl();
+      CurrentDecl = CatImpl;
+      IsInImplementation = true;
+    } else {
+      SearchDecl = dyn_cast<ObjCContainerDecl>(D);
+      CurrentDecl = SearchDecl;
+    }
+  }
+
+  if (!SearchDecl && S) {
+    if (DeclContext *DC = static_cast<DeclContext *>(S->getEntity())) {
+      SearchDecl = dyn_cast<ObjCContainerDecl>(DC);
+      CurrentDecl = SearchDecl;
+    }
+  }
+
+  if (!SearchDecl || !CurrentDecl) {
+    HandleCodeCompleteResults(this, CodeCompleter, 0, 0);
+    return;
+  }
+    
+  // Find all of the methods that we could declare/implement here.
+  KnownMethodsMap KnownMethods;
+  FindImplementableMethods(Context, SearchDecl, IsInstanceMethod, 
+                           ReturnType, IsInImplementation, KnownMethods);
+  
+  // Erase any methods that have already been declared or
+  // implemented here.
+  for (ObjCContainerDecl::method_iterator M = CurrentDecl->meth_begin(),
+                                       MEnd = CurrentDecl->meth_end();
+       M != MEnd; ++M) {
+    if ((*M)->isInstanceMethod() != IsInstanceMethod)
+      continue;
+    
+    KnownMethodsMap::iterator Pos = KnownMethods.find((*M)->getSelector());
+    if (Pos != KnownMethods.end())
+      KnownMethods.erase(Pos);
+  }
+
+  // Add declarations or definitions for each of the known methods.
+  typedef CodeCompleteConsumer::Result Result;
+  ResultBuilder Results(*this);
+  Results.EnterNewScope();
+  PrintingPolicy Policy(Context.PrintingPolicy);
+  Policy.AnonymousTagLocations = false;
+  for (KnownMethodsMap::iterator M = KnownMethods.begin(), 
+                              MEnd = KnownMethods.end();
+       M != MEnd; ++M) {
+    ObjCMethodDecl *Method = M->second;
+    CodeCompletionString *Pattern = new CodeCompletionString;
+    
+    // If the result type was not already provided, add it to the
+    // pattern as (type).
+    if (ReturnType.isNull()) {
+      std::string TypeStr;
+      Method->getResultType().getAsStringInternal(TypeStr, Policy);
+      Pattern->AddChunk(CodeCompletionString::CK_LeftParen);
+      Pattern->AddTextChunk(TypeStr);
+      Pattern->AddChunk(CodeCompletionString::CK_RightParen);
+    }
+
+    Selector Sel = Method->getSelector();
+
+    // Add the first part of the selector to the pattern.
+    Pattern->AddTypedTextChunk(Sel.getIdentifierInfoForSlot(0)->getName());
+
+    // Add parameters to the pattern.
+    unsigned I = 0;
+    for (ObjCMethodDecl::param_iterator P = Method->param_begin(), 
+                                     PEnd = Method->param_end();
+         P != PEnd; (void)++P, ++I) {
+      // Add the part of the selector name.
+      if (I == 0)
+        Pattern->AddChunk(CodeCompletionString::CK_Colon);
+      else if (I < Sel.getNumArgs()) {
+        Pattern->AddChunk(CodeCompletionString::CK_HorizontalSpace);
+        Pattern->AddTextChunk(Sel.getIdentifierInfoForSlot(1)->getName());
+        Pattern->AddChunk(CodeCompletionString::CK_Colon);
+      } else
+        break;
+
+      // Add the parameter type.
+      std::string TypeStr;
+      (*P)->getOriginalType().getAsStringInternal(TypeStr, Policy);
+      Pattern->AddChunk(CodeCompletionString::CK_LeftParen);
+      Pattern->AddTextChunk(TypeStr);
+      Pattern->AddChunk(CodeCompletionString::CK_RightParen);
+      
+      if (IdentifierInfo *Id = (*P)->getIdentifier())
+        Pattern->AddTextChunk(Id->getName());
+    }
+
+    if (Method->isVariadic()) {
+      if (Method->param_size() > 0)
+        Pattern->AddChunk(CodeCompletionString::CK_Comma);
+      Pattern->AddTextChunk("...");
+    }
+
+    if (IsInImplementation) {
+      // We will be defining the method here, so add a compound statement.
+      Pattern->AddChunk(CodeCompletionString::CK_HorizontalSpace);
+      Pattern->AddChunk(CodeCompletionString::CK_LeftBrace);
+      Pattern->AddChunk(CodeCompletionString::CK_VerticalSpace);
+      if (!Method->getResultType()->isVoidType()) {
+        // If the result type is not void, add a return clause.
+        Pattern->AddTextChunk("return");
+        Pattern->AddChunk(CodeCompletionString::CK_HorizontalSpace);
+        Pattern->AddPlaceholderChunk("expression");
+        Pattern->AddChunk(CodeCompletionString::CK_SemiColon);
+      } else
+        Pattern->AddPlaceholderChunk("statements");
+        
+      Pattern->AddChunk(CodeCompletionString::CK_VerticalSpace);
+      Pattern->AddChunk(CodeCompletionString::CK_RightBrace);
+    }
+
+    Results.AddResult(Result(Pattern));
+  }
+
+  Results.ExitScope();
+  
+  HandleCodeCompleteResults(this, CodeCompleter, Results.data(),Results.size());
+}
diff --git a/test/Index/complete-method-decls.m b/test/Index/complete-method-decls.m
new file mode 100644 (file)
index 0000000..c18994e
--- /dev/null
@@ -0,0 +1,82 @@
+/* Note: the RUN lines are near the end of the file, since line/column
+   matter for this test. */
+
+@protocol P1
+- (id)abc;
+- (id)initWithInt:(int)x;
+- (id)initWithTwoInts:(int)x second:(int)y;
+- (int)getInt;
+- (id)getSelf;
+@end
+
+@protocol P2<P1>
++ (id)alloc;
+@end
+
+@interface A <P1>
+- (id)init;
+- (int)getValue;
+@end
+
+@interface B : A<P2>
+- (id)initWithInt:(int)x;
+- (int)getSecondValue;
+- (id)getSelf;
+- (int)setValue:(int)x;
+@end
+
+@interface B (FooBar)
+- (id)categoryFunction:(int)x;
+@end
+
+@implementation B
+- (int)getSecondValue { return 0; }
+- (id)init { return self; }
+- (id)getSelf { return self; }
+- (void)setValue:(int)x { }
+- (id)initWithTwoInts:(int)x second:(int)y { return self; }
++ (id)alloc { return 0; }
+@end
+
+@implementation B (FooBar)
+- (id)categoryFunction:(int)x { return self; }
+@end
+
+// RUN: c-index-test -code-completion-at=%s:17:3 %s | FileCheck -check-prefix=CHECK-CC1 %s
+// CHECK-CC1: NotImplemented:{LeftParen (}{Text id}{RightParen )}{TypedText abc}
+// CHECK-CC1: NotImplemented:{LeftParen (}{Text int}{RightParen )}{TypedText getInt}
+// CHECK-CC1: NotImplemented:{LeftParen (}{Text id}{RightParen )}{TypedText getSelf}
+// CHECK-CC1: NotImplemented:{LeftParen (}{Text id}{RightParen )}{TypedText initWithInt}{Colon :}{LeftParen (}{Text int}{RightParen )}{Text x}
+// CHECK-CC1: NotImplemented:{LeftParen (}{Text id}{RightParen )}{TypedText initWithTwoInts}{Colon :}{LeftParen (}{Text int}{RightParen )}{Text x}{HorizontalSpace  }{Text second}{Colon :}{LeftParen (}{Text int}{RightParen )}{Text y}
+// RUN: c-index-test -code-completion-at=%s:17:7 %s | FileCheck -check-prefix=CHECK-CC2 %s
+// CHECK-CC2: NotImplemented:{TypedText abc}
+// CHECK-CC2-NEXT: NotImplemented:{TypedText getSelf}
+// CHECK-CC2: NotImplemented:{TypedText initWithInt}{Colon :}{LeftParen (}{Text int}{RightParen )}{Text x}
+// CHECK-CC2: NotImplemented:{TypedText initWithTwoInts}{Colon :}{LeftParen (}{Text int}{RightParen )}{Text x}{HorizontalSpace  }{Text second}{Colon :}{LeftParen (}{Text int}{RightParen )}{Text y}
+// RUN: c-index-test -code-completion-at=%s:24:7 %s | FileCheck -check-prefix=CHECK-CC3 %s
+// CHECK-CC3: NotImplemented:{TypedText abc}
+// CHECK-CC3-NEXT: NotImplemented:{TypedText getSelf}
+// CHECK-CC3: NotImplemented:{TypedText init}
+// CHECK-CC3: NotImplemented:{TypedText initWithInt}{Colon :}{LeftParen (}{Text int}{RightParen )}{Text x}
+// CHECK-CC3: NotImplemented:{TypedText initWithTwoInts}{Colon :}{LeftParen (}{Text int}{RightParen )}{Text x}{HorizontalSpace  }{Text second}{Colon :}{LeftParen (}{Text int}{RightParen )}{Text y}
+// RUN: c-index-test -code-completion-at=%s:33:3 %s | FileCheck -check-prefix=CHECK-CC4 %s
+// CHECK-CC4: NotImplemented:{LeftParen (}{Text id}{RightParen )}{TypedText abc}{HorizontalSpace  }{LeftBrace {}{VerticalSpace
+// CHECK-CC4: NotImplemented:{LeftParen (}{Text int}{RightParen )}{TypedText getInt}{HorizontalSpace  }{LeftBrace {}{VerticalSpace
+// CHECK-CC4: NotImplemented:{LeftParen (}{Text int}{RightParen )}{TypedText getSecondValue}{HorizontalSpace  }{LeftBrace {}{VerticalSpace
+// CHECK-CC4: NotImplemented:{LeftParen (}{Text id}{RightParen )}{TypedText getSelf}{HorizontalSpace  }{LeftBrace {}{VerticalSpace
+// CHECK-CC4: NotImplemented:{LeftParen (}{Text id}{RightParen )}{TypedText initWithInt}{Colon :}{LeftParen (}{Text int}{RightParen )}{Text x}{HorizontalSpace  }{LeftBrace {}{VerticalSpace
+// CHECK-CC4: NotImplemented:{LeftParen (}{Text id}{RightParen )}{TypedText initWithTwoInts}{Colon :}{LeftParen (}{Text int}{RightParen )}{Text x}{HorizontalSpace  }{Text second}{Colon :}{LeftParen (}{Text int}{RightParen )}{Text y}{HorizontalSpace  }{LeftBrace {}{VerticalSpace
+// CHECK-CC4: NotImplemented:{LeftParen (}{Text int}{RightParen )}{TypedText setValue}{Colon :}{LeftParen (}{Text int}{RightParen )}{Text x}{HorizontalSpace  }{LeftBrace {}{VerticalSpace
+// RUN: c-index-test -code-completion-at=%s:33:8 %s | FileCheck -check-prefix=CHECK-CC5 %s
+// CHECK-CC5: NotImplemented:{TypedText getInt}{HorizontalSpace  }{LeftBrace {}{VerticalSpace
+// CHECK-CC5: NotImplemented:{TypedText getSecondValue}{HorizontalSpace  }{LeftBrace {}{VerticalSpace
+// CHECK-CC5-NOT: {TypedText getSelf}{HorizontalSpace  }{LeftBrace {}{VerticalSpace
+// CHECK-CC5: NotImplemented:{TypedText setValue}{Colon :}{LeftParen (}{Text int}{RightParen )}{Text x}{HorizontalSpace  }{LeftBrace {}{VerticalSpace
+// RUN: c-index-test -code-completion-at=%s:37:7 %s | FileCheck -check-prefix=CHECK-CC6 %s
+// CHECK-CC6: NotImplemented:{TypedText abc}{HorizontalSpace  }{LeftBrace {}{VerticalSpace 
+// CHECK-CC6-NOT: getSelf
+// CHECK-CC6: NotImplemented:{TypedText initWithInt}{Colon :}{LeftParen (}{Text int}{RightParen )}{Text x}{HorizontalSpace  }{LeftBrace {}{VerticalSpace 
+// CHECK-CC6: NotImplemented:{TypedText initWithTwoInts}{Colon :}{LeftParen (}{Text int}{RightParen )}{Text x}{HorizontalSpace  }{Text second}{Colon :}{LeftParen (}{Text int}{RightParen )}{Text y}{HorizontalSpace  }{LeftBrace {}{VerticalSpace 
+// RUN: c-index-test -code-completion-at=%s:42:3 %s | FileCheck -check-prefix=CHECK-CC7 %s
+// CHECK-CC7: NotImplemented:{LeftParen (}{Text id}{RightParen )}{TypedText categoryFunction}{Colon :}{LeftParen (}{Text int}{RightParen )}{Text x}{HorizontalSpace  }{LeftBrace {}{VerticalSpace 
+