diff options
-rw-r--r-- | include/clang/Parse/Action.h | 26 | ||||
-rw-r--r-- | lib/Parse/ParseObjc.cpp | 12 | ||||
-rw-r--r-- | lib/Sema/CodeCompleteConsumer.cpp | 2 | ||||
-rw-r--r-- | lib/Sema/Sema.h | 4 | ||||
-rw-r--r-- | lib/Sema/SemaCodeComplete.cpp | 221 | ||||
-rw-r--r-- | test/Index/complete-method-decls.m | 82 |
6 files changed, 346 insertions, 1 deletions
diff --git a/include/clang/Parse/Action.h b/include/clang/Parse/Action.h index 59cc0d218c..4122aff91f 100644 --- a/include/clang/Parse/Action.h +++ b/include/clang/Parse/Action.h @@ -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) { + } //@} }; diff --git a/lib/Parse/ParseObjc.cpp b/lib/Parse/ParseObjc.cpp index 123a96261a..155e352073 100644 --- a/lib/Parse/ParseObjc.cpp +++ b/lib/Parse/ParseObjc.cpp @@ -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); diff --git a/lib/Sema/CodeCompleteConsumer.cpp b/lib/Sema/CodeCompleteConsumer.cpp index 5483a292e9..9f0ee5cde7 100644 --- a/lib/Sema/CodeCompleteConsumer.cpp +++ b/lib/Sema/CodeCompleteConsumer.cpp @@ -86,7 +86,7 @@ CodeCompletionString::Chunk::Chunk(ChunkKind Kind, llvm::StringRef Text) break; case CK_Colon: - this->Text = ": "; + this->Text = ":"; break; case CK_SemiColon: diff --git a/lib/Sema/Sema.h b/lib/Sema/Sema.h index 13aa27ea96..5394f06985 100644 --- a/lib/Sema/Sema.h +++ b/lib/Sema/Sema.h @@ -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); //@} //===--------------------------------------------------------------------===// diff --git a/lib/Sema/SemaCodeComplete.cpp b/lib/Sema/SemaCodeComplete.cpp index beed0ed2b6..66b04a25bc 100644 --- a/lib/Sema/SemaCodeComplete.cpp +++ b/lib/Sema/SemaCodeComplete.cpp @@ -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 index 0000000000..c18994ec20 --- /dev/null +++ b/test/Index/complete-method-decls.m @@ -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 + |