From 577cdfdb20840350e841a483df630237326126d5 Mon Sep 17 00:00:00 2001 From: Douglas Gregor Date: Thu, 17 Feb 2011 00:22:45 +0000 Subject: [PATCH] Implement code completion results for the Objective-C Key-Value Coding (KVC) and Key-Value Observing (KVO) protocols. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@125696 91177308-0d34-0410-b5e6-96231b3b80d8 --- include/clang/Sema/CodeCompleteConsumer.h | 30 +- lib/Sema/CodeCompleteConsumer.cpp | 9 + lib/Sema/SemaCodeComplete.cpp | 667 +++++++++++++++++++++- test/Index/complete-kvc.m | 87 +++ 4 files changed, 772 insertions(+), 21 deletions(-) create mode 100644 test/Index/complete-kvc.m diff --git a/include/clang/Sema/CodeCompleteConsumer.h b/include/clang/Sema/CodeCompleteConsumer.h index 4f28fb2cca..2838c19b72 100644 --- a/include/clang/Sema/CodeCompleteConsumer.h +++ b/include/clang/Sema/CodeCompleteConsumer.h @@ -23,6 +23,7 @@ namespace llvm { class raw_ostream; + class Twine; } namespace clang { @@ -80,7 +81,11 @@ enum { /// \brief Adjustment to the "bool" type in Objective-C, where the typedef /// "BOOL" is preferred. - CCD_bool_in_ObjC = 1 + CCD_bool_in_ObjC = 1, + + /// \brief Adjustment for KVC code pattern priorities when it doesn't look + /// like the + CCD_ProbablyNotObjCCollection = 15 }; /// \brief Priority value factors by which we will divide or multiply the @@ -426,6 +431,19 @@ class CodeCompletionAllocator : public llvm::BumpPtrAllocator { public: /// \brief Copy the given string into this allocator. const char *CopyString(llvm::StringRef String); + + /// \brief Copy the given string into this allocator. + const char *CopyString(llvm::Twine String); + + // \brief Copy the given string into this allocator. + const char *CopyString(const char *String) { + return CopyString(llvm::StringRef(String)); + } + + /// \brief Copy the given string into this allocator. + const char *CopyString(const std::string &String) { + return CopyString(llvm::StringRef(String)); + } }; /// \brief A builder class used to construct new code-completion strings. @@ -451,7 +469,7 @@ public: : Allocator(Allocator), Priority(Priority), Availability(Availability) { } /// \brief Retrieve the allocator into which the code completion - /// strings will be + /// strings should be allocated. CodeCompletionAllocator &getAllocator() const { return Allocator; } /// \brief Take the resulting completion string. @@ -460,42 +478,36 @@ public: CodeCompletionString *TakeString(); /// \brief Add a new typed-text chunk. - /// The text string will be copied. void AddTypedTextChunk(const char *Text) { Chunks.push_back(Chunk(CodeCompletionString::CK_TypedText, Text)); } /// \brief Add a new text chunk. - /// The text string will be copied. void AddTextChunk(const char *Text) { Chunks.push_back(Chunk::CreateText(Text)); } - + /// \brief Add a new optional chunk. void AddOptionalChunk(CodeCompletionString *Optional) { Chunks.push_back(Chunk::CreateOptional(Optional)); } /// \brief Add a new placeholder chunk. - /// The placeholder text will be copied. void AddPlaceholderChunk(const char *Placeholder) { Chunks.push_back(Chunk::CreatePlaceholder(Placeholder)); } /// \brief Add a new informative chunk. - /// The text will be copied. void AddInformativeChunk(const char *Text) { Chunks.push_back(Chunk::CreateInformative(Text)); } /// \brief Add a new result-type chunk. - /// The text will be copied. void AddResultTypeChunk(const char *ResultType) { Chunks.push_back(Chunk::CreateResultType(ResultType)); } /// \brief Add a new current-parameter chunk. - /// The text will be copied. void AddCurrentParameterChunk(const char *CurrentParameter) { Chunks.push_back(Chunk::CreateCurrentParameter(CurrentParameter)); } diff --git a/lib/Sema/CodeCompleteConsumer.cpp b/lib/Sema/CodeCompleteConsumer.cpp index cb2dd234b4..253c10e7d5 100644 --- a/lib/Sema/CodeCompleteConsumer.cpp +++ b/lib/Sema/CodeCompleteConsumer.cpp @@ -19,6 +19,7 @@ #include "clang/Lex/Preprocessor.h" #include "clang-c/Index.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/Twine.h" #include "llvm/Support/raw_ostream.h" #include #include @@ -227,6 +228,14 @@ const char *CodeCompletionAllocator::CopyString(llvm::StringRef String) { return Mem; } +const char *CodeCompletionAllocator::CopyString(llvm::Twine String) { + // FIXME: It would be more efficient to teach Twine to tell us its size and + // then add a routine there to fill in an allocated char* with the contents + // of the string. + llvm::SmallString<128> Data; + return CopyString(String.toStringRef(Data)); +} + CodeCompletionString *CodeCompletionBuilder::TakeString() { void *Mem = Allocator.Allocate( sizeof(CodeCompletionString) + sizeof(Chunk) * Chunks.size(), diff --git a/lib/Sema/SemaCodeComplete.cpp b/lib/Sema/SemaCodeComplete.cpp index d92b9fe71c..36fd04d8df 100644 --- a/lib/Sema/SemaCodeComplete.cpp +++ b/lib/Sema/SemaCodeComplete.cpp @@ -5427,6 +5427,631 @@ static void FindImplementableMethods(ASTContext &Context, } } +/// \brief Add the parenthesized return or parameter type chunk to a code +/// completion string. +static void AddObjCPassingTypeChunk(QualType Type, + ASTContext &Context, + CodeCompletionBuilder &Builder) { + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddTextChunk(GetCompletionTypeString(Type, Context, + Builder.getAllocator())); + Builder.AddChunk(CodeCompletionString::CK_RightParen); +} + +/// \brief Determine whether the given class is or inherits from a class by +/// the given name. +static bool InheritsFromClassNamed(ObjCInterfaceDecl *Class, + llvm::StringRef Name) { + if (!Class) + return false; + + if (Class->getIdentifier() && Class->getIdentifier()->getName() == Name) + return true; + + return InheritsFromClassNamed(Class->getSuperClass(), Name); +} + +/// \brief Add code completions for Objective-C Key-Value Coding (KVC) and +/// Key-Value Observing (KVO). +static void AddObjCKeyValueCompletions(ObjCPropertyDecl *Property, + bool IsInstanceMethod, + QualType ReturnType, + ASTContext &Context, + const KnownMethodsMap &KnownMethods, + ResultBuilder &Results) { + IdentifierInfo *PropName = Property->getIdentifier(); + if (!PropName || PropName->getLength() == 0) + return; + + + // Builder that will create each code completion. + typedef CodeCompletionResult Result; + CodeCompletionAllocator &Allocator = Results.getAllocator(); + CodeCompletionBuilder Builder(Allocator); + + // The selector table. + SelectorTable &Selectors = Context.Selectors; + + // The property name, copied into the code completion allocation region + // on demand. + struct KeyHolder { + CodeCompletionAllocator &Allocator; + llvm::StringRef Key; + const char *CopiedKey; + + KeyHolder(CodeCompletionAllocator &Allocator, llvm::StringRef Key) + : Allocator(Allocator), Key(Key), CopiedKey(0) { } + + operator const char *() { + if (CopiedKey) + return CopiedKey; + + return CopiedKey = Allocator.CopyString(Key); + } + } Key(Allocator, PropName->getName()); + + // The uppercased name of the property name. + std::string UpperKey = PropName->getName(); + if (!UpperKey.empty()) + UpperKey[0] = toupper(UpperKey[0]); + + bool ReturnTypeMatchesProperty = ReturnType.isNull() || + Context.hasSameUnqualifiedType(ReturnType.getNonReferenceType(), + Property->getType()); + bool ReturnTypeMatchesVoid + = ReturnType.isNull() || ReturnType->isVoidType(); + + // Add the normal accessor -(type)key. + if (IsInstanceMethod && + !KnownMethods.count(Selectors.getNullarySelector(PropName)) && + ReturnTypeMatchesProperty && !Property->getGetterMethodDecl()) { + if (ReturnType.isNull()) + AddObjCPassingTypeChunk(Property->getType(), Context, Builder); + + Builder.AddTypedTextChunk(Key); + Results.AddResult(Result(Builder.TakeString(), CCP_CodePattern, + CXCursor_ObjCInstanceMethodDecl)); + } + + // If we have an integral or boolean property (or the user has provided + // an integral or boolean return type), add the accessor -(type)isKey. + if (IsInstanceMethod && + ((!ReturnType.isNull() && + (ReturnType->isIntegerType() || ReturnType->isBooleanType())) || + (ReturnType.isNull() && + (Property->getType()->isIntegerType() || + Property->getType()->isBooleanType())))) { + llvm::Twine SelectorName = llvm::Twine("is") + UpperKey; + IdentifierInfo *SelectorId = &Context.Idents.get(SelectorName.str()); + if (!KnownMethods.count(Selectors.getNullarySelector(SelectorId))) { + if (ReturnType.isNull()) { + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddTextChunk("BOOL"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + } + + Builder.AddTypedTextChunk( + Allocator.CopyString(SelectorId->getName())); + Results.AddResult(Result(Builder.TakeString(), CCP_CodePattern, + CXCursor_ObjCInstanceMethodDecl)); + } + } + + // Add the normal mutator. + if (IsInstanceMethod && ReturnTypeMatchesVoid && + !Property->getSetterMethodDecl()) { + llvm::Twine SelectorName = llvm::Twine("set") + UpperKey; + IdentifierInfo *SelectorId = &Context.Idents.get(SelectorName.str()); + if (!KnownMethods.count(Selectors.getUnarySelector(SelectorId))) { + if (ReturnType.isNull()) { + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddTextChunk("void"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + } + + Builder.AddTypedTextChunk( + Allocator.CopyString(SelectorId->getName())); + Builder.AddTypedTextChunk(":"); + AddObjCPassingTypeChunk(Property->getType(), Context, Builder); + Builder.AddTextChunk(Key); + Results.AddResult(Result(Builder.TakeString(), CCP_CodePattern, + CXCursor_ObjCInstanceMethodDecl)); + } + } + + // Indexed and unordered accessors + unsigned IndexedGetterPriority = CCP_CodePattern; + unsigned IndexedSetterPriority = CCP_CodePattern; + unsigned UnorderedGetterPriority = CCP_CodePattern; + unsigned UnorderedSetterPriority = CCP_CodePattern; + if (const ObjCObjectPointerType *ObjCPointer + = Property->getType()->getAs()) { + if (ObjCInterfaceDecl *IFace = ObjCPointer->getInterfaceDecl()) { + // If this interface type is not provably derived from a known + // collection, penalize the corresponding completions. + if (!InheritsFromClassNamed(IFace, "NSMutableArray")) { + IndexedSetterPriority += CCD_ProbablyNotObjCCollection; + if (!InheritsFromClassNamed(IFace, "NSArray")) + IndexedGetterPriority += CCD_ProbablyNotObjCCollection; + } + + if (!InheritsFromClassNamed(IFace, "NSMutableSet")) { + UnorderedSetterPriority += CCD_ProbablyNotObjCCollection; + if (!InheritsFromClassNamed(IFace, "NSSet")) + UnorderedGetterPriority += CCD_ProbablyNotObjCCollection; + } + } + } else { + IndexedGetterPriority += CCD_ProbablyNotObjCCollection; + IndexedSetterPriority += CCD_ProbablyNotObjCCollection; + UnorderedGetterPriority += CCD_ProbablyNotObjCCollection; + UnorderedSetterPriority += CCD_ProbablyNotObjCCollection; + } + + // Add -(NSUInteger)countOf + if (IsInstanceMethod && + (ReturnType.isNull() || ReturnType->isIntegerType())) { + llvm::Twine SelectorName = llvm::Twine("countOf") + UpperKey; + IdentifierInfo *SelectorId = &Context.Idents.get(SelectorName.str()); + if (!KnownMethods.count(Selectors.getNullarySelector(SelectorId))) { + if (ReturnType.isNull()) { + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddTextChunk("NSUInteger"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + } + + Builder.AddTypedTextChunk( + Allocator.CopyString(SelectorId->getName())); + Results.AddResult(Result(Builder.TakeString(), + std::min(IndexedGetterPriority, + UnorderedGetterPriority), + CXCursor_ObjCInstanceMethodDecl)); + } + } + + // Indexed getters + // Add -(id)objectInKeyAtIndex:(NSUInteger)index + if (IsInstanceMethod && + (ReturnType.isNull() || ReturnType->isObjCObjectPointerType())) { + llvm::Twine SelectorName + = llvm::Twine("objectIn") + UpperKey + "AtIndex"; + IdentifierInfo *SelectorId = &Context.Idents.get(SelectorName.str()); + if (!KnownMethods.count(Selectors.getUnarySelector(SelectorId))) { + if (ReturnType.isNull()) { + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddTextChunk("id"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + } + + Builder.AddTypedTextChunk(Allocator.CopyString(SelectorName + ":")); + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddTextChunk("NSUInteger"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + Builder.AddTextChunk("index"); + Results.AddResult(Result(Builder.TakeString(), IndexedGetterPriority, + CXCursor_ObjCInstanceMethodDecl)); + } + } + + // Add -(NSArray *)keyAtIndexes:(NSIndexSet *)indexes + if (IsInstanceMethod && + (ReturnType.isNull() || + (ReturnType->isObjCObjectPointerType() && + ReturnType->getAs()->getInterfaceDecl() && + ReturnType->getAs()->getInterfaceDecl() + ->getName() == "NSArray"))) { + llvm::Twine SelectorName + = llvm::Twine(Property->getName()) + "AtIndexes"; + IdentifierInfo *SelectorId = &Context.Idents.get(SelectorName.str()); + if (!KnownMethods.count(Selectors.getUnarySelector(SelectorId))) { + if (ReturnType.isNull()) { + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddTextChunk("NSArray *"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + } + + Builder.AddTypedTextChunk(Allocator.CopyString(SelectorName + ":")); + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddTextChunk("NSIndexSet *"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + Builder.AddTextChunk("indexes"); + Results.AddResult(Result(Builder.TakeString(), IndexedGetterPriority, + CXCursor_ObjCInstanceMethodDecl)); + } + } + + // Add -(void)getKey:(type **)buffer range:(NSRange)inRange + if (IsInstanceMethod && ReturnTypeMatchesVoid) { + llvm::Twine SelectorName = llvm::Twine("get") + UpperKey; + IdentifierInfo *SelectorIds[2] = { + &Context.Idents.get(SelectorName.str()), + &Context.Idents.get("range") + }; + + if (!KnownMethods.count(Selectors.getSelector(2, SelectorIds))) { + if (ReturnType.isNull()) { + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddTextChunk("void"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + } + + Builder.AddTypedTextChunk(Allocator.CopyString(SelectorName + ":")); + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddPlaceholderChunk("object-type"); + Builder.AddTextChunk(" **"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + Builder.AddTextChunk("buffer"); + Builder.AddChunk(CodeCompletionString::CK_HorizontalSpace); + Builder.AddTypedTextChunk("range:"); + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddTextChunk("NSRange"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + Builder.AddTextChunk("inRange"); + Results.AddResult(Result(Builder.TakeString(), IndexedGetterPriority, + CXCursor_ObjCInstanceMethodDecl)); + } + } + + // Mutable indexed accessors + + // - (void)insertObject:(type *)object inKeyAtIndex:(NSUInteger)index + if (IsInstanceMethod && ReturnTypeMatchesVoid) { + llvm::Twine SelectorName = llvm::Twine("in") + UpperKey + "AtIndex"; + IdentifierInfo *SelectorIds[2] = { + &Context.Idents.get("insertObject"), + &Context.Idents.get(SelectorName.str()) + }; + + if (!KnownMethods.count(Selectors.getSelector(2, SelectorIds))) { + if (ReturnType.isNull()) { + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddTextChunk("void"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + } + + Builder.AddTypedTextChunk("insertObject:"); + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddPlaceholderChunk("object-type"); + Builder.AddTextChunk(" *"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + Builder.AddTextChunk("object"); + Builder.AddChunk(CodeCompletionString::CK_HorizontalSpace); + Builder.AddTypedTextChunk(Allocator.CopyString(SelectorName + ":")); + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddPlaceholderChunk("NSUInteger"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + Builder.AddTextChunk("index"); + Results.AddResult(Result(Builder.TakeString(), IndexedSetterPriority, + CXCursor_ObjCInstanceMethodDecl)); + } + } + + // - (void)insertKey:(NSArray *)array atIndexes:(NSIndexSet *)indexes + if (IsInstanceMethod && ReturnTypeMatchesVoid) { + llvm::Twine SelectorName = llvm::Twine("insert") + UpperKey; + IdentifierInfo *SelectorIds[2] = { + &Context.Idents.get(SelectorName.str()), + &Context.Idents.get("atIndexes") + }; + + if (!KnownMethods.count(Selectors.getSelector(2, SelectorIds))) { + if (ReturnType.isNull()) { + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddTextChunk("void"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + } + + Builder.AddTypedTextChunk(Allocator.CopyString(SelectorName + ":")); + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddTextChunk("NSArray *"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + Builder.AddTextChunk("array"); + Builder.AddChunk(CodeCompletionString::CK_HorizontalSpace); + Builder.AddTypedTextChunk("atIndexes:"); + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddPlaceholderChunk("NSIndexSet *"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + Builder.AddTextChunk("indexes"); + Results.AddResult(Result(Builder.TakeString(), IndexedSetterPriority, + CXCursor_ObjCInstanceMethodDecl)); + } + } + + // -(void)removeObjectFromKeyAtIndex:(NSUInteger)index + if (IsInstanceMethod && ReturnTypeMatchesVoid) { + llvm::Twine SelectorName + = llvm::Twine("removeObjectFrom") + UpperKey + "AtIndex"; + IdentifierInfo *SelectorId = &Context.Idents.get(SelectorName.str()); + if (!KnownMethods.count(Selectors.getUnarySelector(SelectorId))) { + if (ReturnType.isNull()) { + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddTextChunk("void"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + } + + Builder.AddTypedTextChunk(Allocator.CopyString(SelectorName + ":")); + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddTextChunk("NSUInteger"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + Builder.AddTextChunk("index"); + Results.AddResult(Result(Builder.TakeString(), IndexedSetterPriority, + CXCursor_ObjCInstanceMethodDecl)); + } + } + + // -(void)removeKeyAtIndexes:(NSIndexSet *)indexes + if (IsInstanceMethod && ReturnTypeMatchesVoid) { + llvm::Twine SelectorName + = llvm::Twine("remove") + UpperKey + "AtIndexes"; + IdentifierInfo *SelectorId = &Context.Idents.get(SelectorName.str()); + if (!KnownMethods.count(Selectors.getUnarySelector(SelectorId))) { + if (ReturnType.isNull()) { + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddTextChunk("void"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + } + + Builder.AddTypedTextChunk(Allocator.CopyString(SelectorName + ":")); + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddTextChunk("NSIndexSet *"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + Builder.AddTextChunk("indexes"); + Results.AddResult(Result(Builder.TakeString(), IndexedSetterPriority, + CXCursor_ObjCInstanceMethodDecl)); + } + } + + // - (void)replaceObjectInKeyAtIndex:(NSUInteger)index withObject:(id)object + if (IsInstanceMethod && ReturnTypeMatchesVoid) { + llvm::Twine SelectorName + = llvm::Twine("replaceObjectIn") + UpperKey + "AtIndex"; + IdentifierInfo *SelectorIds[2] = { + &Context.Idents.get(SelectorName.str()), + &Context.Idents.get("withObject") + }; + + if (!KnownMethods.count(Selectors.getSelector(2, SelectorIds))) { + if (ReturnType.isNull()) { + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddTextChunk("void"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + } + + Builder.AddTypedTextChunk(Allocator.CopyString(SelectorName + ":")); + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddPlaceholderChunk("NSUInteger"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + Builder.AddTextChunk("index"); + Builder.AddChunk(CodeCompletionString::CK_HorizontalSpace); + Builder.AddTypedTextChunk("withObject:"); + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddTextChunk("id"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + Builder.AddTextChunk("object"); + Results.AddResult(Result(Builder.TakeString(), IndexedSetterPriority, + CXCursor_ObjCInstanceMethodDecl)); + } + } + + // - (void)replaceKeyAtIndexes:(NSIndexSet *)indexes withKey:(NSArray *)array + if (IsInstanceMethod && ReturnTypeMatchesVoid) { + llvm::Twine SelectorName1 = llvm::Twine("replace") + UpperKey + "AtIndexes"; + llvm::Twine SelectorName2 = llvm::Twine("with") + UpperKey; + IdentifierInfo *SelectorIds[2] = { + &Context.Idents.get(SelectorName1.str()), + &Context.Idents.get(SelectorName2.str()) + }; + + if (!KnownMethods.count(Selectors.getSelector(2, SelectorIds))) { + if (ReturnType.isNull()) { + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddTextChunk("void"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + } + + Builder.AddTypedTextChunk(Allocator.CopyString(SelectorName1 + ":")); + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddPlaceholderChunk("NSIndexSet *"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + Builder.AddTextChunk("indexes"); + Builder.AddChunk(CodeCompletionString::CK_HorizontalSpace); + Builder.AddTypedTextChunk(Allocator.CopyString(SelectorName2 + ":")); + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddTextChunk("NSArray *"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + Builder.AddTextChunk("array"); + Results.AddResult(Result(Builder.TakeString(), IndexedSetterPriority, + CXCursor_ObjCInstanceMethodDecl)); + } + } + + // Unordered getters + // - (NSEnumerator *)enumeratorOfKey + if (IsInstanceMethod && + (ReturnType.isNull() || + (ReturnType->isObjCObjectPointerType() && + ReturnType->getAs()->getInterfaceDecl() && + ReturnType->getAs()->getInterfaceDecl() + ->getName() == "NSEnumerator"))) { + llvm::Twine SelectorName = llvm::Twine("enumeratorOf") + UpperKey; + IdentifierInfo *SelectorId = &Context.Idents.get(SelectorName.str()); + if (!KnownMethods.count(Selectors.getNullarySelector(SelectorId))) { + if (ReturnType.isNull()) { + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddTextChunk("NSEnumerator *"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + } + + Builder.AddTypedTextChunk(Allocator.CopyString(SelectorName)); + Results.AddResult(Result(Builder.TakeString(), UnorderedGetterPriority, + CXCursor_ObjCInstanceMethodDecl)); + } + } + + // - (type *)memberOfKey:(type *)object + if (IsInstanceMethod && + (ReturnType.isNull() || ReturnType->isObjCObjectPointerType())) { + llvm::Twine SelectorName = llvm::Twine("memberOf") + UpperKey; + IdentifierInfo *SelectorId = &Context.Idents.get(SelectorName.str()); + if (!KnownMethods.count(Selectors.getUnarySelector(SelectorId))) { + if (ReturnType.isNull()) { + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddPlaceholderChunk("object-type"); + Builder.AddTextChunk(" *"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + } + + Builder.AddTypedTextChunk(Allocator.CopyString(SelectorName + ":")); + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + if (ReturnType.isNull()) { + Builder.AddPlaceholderChunk("object-type"); + Builder.AddTextChunk(" *"); + } else { + Builder.AddTextChunk(GetCompletionTypeString(ReturnType, Context, + Builder.getAllocator())); + } + Builder.AddChunk(CodeCompletionString::CK_RightParen); + Builder.AddTextChunk("object"); + Results.AddResult(Result(Builder.TakeString(), UnorderedGetterPriority, + CXCursor_ObjCInstanceMethodDecl)); + } + } + + // Mutable unordered accessors + // - (void)addKeyObject:(type *)object + if (IsInstanceMethod && ReturnTypeMatchesVoid) { + llvm::Twine SelectorName + = llvm::Twine("add") + UpperKey + llvm::Twine("Object"); + IdentifierInfo *SelectorId = &Context.Idents.get(SelectorName.str()); + if (!KnownMethods.count(Selectors.getUnarySelector(SelectorId))) { + if (ReturnType.isNull()) { + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddTextChunk("void"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + } + + Builder.AddTypedTextChunk(Allocator.CopyString(SelectorName + ":")); + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddPlaceholderChunk("object-type"); + Builder.AddTextChunk(" *"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + Builder.AddTextChunk("object"); + Results.AddResult(Result(Builder.TakeString(), UnorderedSetterPriority, + CXCursor_ObjCInstanceMethodDecl)); + } + } + + // - (void)addKey:(NSSet *)objects + if (IsInstanceMethod && ReturnTypeMatchesVoid) { + llvm::Twine SelectorName = llvm::Twine("add") + UpperKey; + IdentifierInfo *SelectorId = &Context.Idents.get(SelectorName.str()); + if (!KnownMethods.count(Selectors.getUnarySelector(SelectorId))) { + if (ReturnType.isNull()) { + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddTextChunk("void"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + } + + Builder.AddTypedTextChunk(Allocator.CopyString(SelectorName + ":")); + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddTextChunk("NSSet *"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + Builder.AddTextChunk("objects"); + Results.AddResult(Result(Builder.TakeString(), UnorderedSetterPriority, + CXCursor_ObjCInstanceMethodDecl)); + } + } + + // - (void)removeKeyObject:(type *)object + if (IsInstanceMethod && ReturnTypeMatchesVoid) { + llvm::Twine SelectorName + = llvm::Twine("remove") + UpperKey + llvm::Twine("Object"); + IdentifierInfo *SelectorId = &Context.Idents.get(SelectorName.str()); + if (!KnownMethods.count(Selectors.getUnarySelector(SelectorId))) { + if (ReturnType.isNull()) { + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddTextChunk("void"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + } + + Builder.AddTypedTextChunk(Allocator.CopyString(SelectorName + ":")); + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddPlaceholderChunk("object-type"); + Builder.AddTextChunk(" *"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + Builder.AddTextChunk("object"); + Results.AddResult(Result(Builder.TakeString(), UnorderedSetterPriority, + CXCursor_ObjCInstanceMethodDecl)); + } + } + + // - (void)removeKey:(NSSet *)objects + if (IsInstanceMethod && ReturnTypeMatchesVoid) { + llvm::Twine SelectorName = llvm::Twine("remove") + UpperKey; + IdentifierInfo *SelectorId = &Context.Idents.get(SelectorName.str()); + if (!KnownMethods.count(Selectors.getUnarySelector(SelectorId))) { + if (ReturnType.isNull()) { + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddTextChunk("void"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + } + + Builder.AddTypedTextChunk(Allocator.CopyString(SelectorName + ":")); + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddTextChunk("NSSet *"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + Builder.AddTextChunk("objects"); + Results.AddResult(Result(Builder.TakeString(), UnorderedSetterPriority, + CXCursor_ObjCInstanceMethodDecl)); + } + } + + // - (void)intersectKey:(NSSet *)objects + if (IsInstanceMethod && ReturnTypeMatchesVoid) { + llvm::Twine SelectorName = llvm::Twine("intersect") + UpperKey; + IdentifierInfo *SelectorId = &Context.Idents.get(SelectorName.str()); + if (!KnownMethods.count(Selectors.getUnarySelector(SelectorId))) { + if (ReturnType.isNull()) { + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddTextChunk("void"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + } + + Builder.AddTypedTextChunk(Allocator.CopyString(SelectorName + ":")); + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddTextChunk("NSSet *"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + Builder.AddTextChunk("objects"); + Results.AddResult(Result(Builder.TakeString(), UnorderedSetterPriority, + CXCursor_ObjCInstanceMethodDecl)); + } + } + + // Key-Value Observing + // + (NSSet *)keyPathsForValuesAffectingKey + if (!IsInstanceMethod && + (ReturnType.isNull() || + (ReturnType->isObjCObjectPointerType() && + ReturnType->getAs()->getInterfaceDecl() && + ReturnType->getAs()->getInterfaceDecl() + ->getName() == "NSSet"))) { + llvm::Twine SelectorName + = llvm::Twine("keyPathsForValuesAffecting") + UpperKey; + IdentifierInfo *SelectorId = &Context.Idents.get(SelectorName.str()); + if (!KnownMethods.count(Selectors.getNullarySelector(SelectorId))) { + if (ReturnType.isNull()) { + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddTextChunk("NSSet *"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + } + + Builder.AddTypedTextChunk(Allocator.CopyString(SelectorName)); + Results.AddResult(Result(Builder.TakeString(), CCP_CodePattern, + CXCursor_ObjCInstanceMethodDecl)); + } + } +} + void Sema::CodeCompleteObjCMethodDecl(Scope *S, bool IsInstanceMethod, ParsedType ReturnTy, @@ -5482,13 +6107,8 @@ void Sema::CodeCompleteObjCMethodDecl(Scope *S, // If the result type was not already provided, add it to the // pattern as (type). - if (ReturnType.isNull()) { - Builder.AddChunk(CodeCompletionString::CK_LeftParen); - Builder.AddTextChunk(GetCompletionTypeString(Method->getResultType(), - Context, - Builder.getAllocator())); - Builder.AddChunk(CodeCompletionString::CK_RightParen); - } + if (ReturnType.isNull()) + AddObjCPassingTypeChunk(Method->getResultType(), Context, Builder); Selector Sel = Method->getSelector(); @@ -5514,11 +6134,7 @@ void Sema::CodeCompleteObjCMethodDecl(Scope *S, break; // Add the parameter type. - Builder.AddChunk(CodeCompletionString::CK_LeftParen); - Builder.AddTextChunk(GetCompletionTypeString((*P)->getOriginalType(), - Context, - Builder.getAllocator())); - Builder.AddChunk(CodeCompletionString::CK_RightParen); + AddObjCPassingTypeChunk((*P)->getOriginalType(), Context, Builder); if (IdentifierInfo *Id = (*P)->getIdentifier()) Builder.AddTextChunk(Builder.getAllocator().CopyString( Id->getName())); @@ -5558,6 +6174,33 @@ void Sema::CodeCompleteObjCMethodDecl(Scope *S, : CXCursor_ObjCClassMethodDecl)); } + // Add Key-Value-Coding and Key-Value-Observing accessor methods for all of + // the properties in this class and its categories. + if (Context.getLangOptions().ObjC2) { + llvm::SmallVector Containers; + Containers.push_back(SearchDecl); + + ObjCInterfaceDecl *IFace = dyn_cast(SearchDecl); + if (!IFace) + if (ObjCCategoryDecl *Category = dyn_cast(SearchDecl)) + IFace = Category->getClassInterface(); + + if (IFace) { + for (ObjCCategoryDecl *Category = IFace->getCategoryList(); Category; + Category = Category->getNextClassCategory()) + Containers.push_back(Category); + } + + for (unsigned I = 0, N = Containers.size(); I != N; ++I) { + for (ObjCContainerDecl::prop_iterator P = Containers[I]->prop_begin(), + PEnd = Containers[I]->prop_end(); + P != PEnd; ++P) { + AddObjCKeyValueCompletions(*P, IsInstanceMethod, ReturnType, Context, + KnownMethods, Results); + } + } + } + Results.ExitScope(); HandleCodeCompleteResults(this, CodeCompleter, diff --git a/test/Index/complete-kvc.m b/test/Index/complete-kvc.m new file mode 100644 index 0000000000..43a874b553 --- /dev/null +++ b/test/Index/complete-kvc.m @@ -0,0 +1,87 @@ +// Test for code completions related to Key-Value Coding and Key-Value Observing. +// Since this test is line- and column-sensitive, the run lines are at the end. + +typedef signed char BOOL; + +@interface NSSet +@end + +@interface NSMutableSet : NSSet +@end + +@interface MySet : NSMutableSet +@end + +@interface NSArray +@end + +@interface NSMutableArray : NSArray +@end + +@interface MyArray : NSMutableArray +@end + +@interface MyClass +@property int intProperty; +@property BOOL boolProperty; +@property int* intPointerProperty; + +@property NSSet *setProperty; +@property NSMutableSet *mutableSetProperty; +@property MySet *mySetProperty; + +@property NSArray *arrayProperty; +@property NSMutableArray *mutableArrayProperty; +@property MyArray *myArrayProperty; +@end + +@implementation MyClass +- (int)intProperty { return 0; } +- (void)setIntProperty:(int)newValue { } ++ (NSSet *)keyPathsForValuesAffectingIntProperty { return 0; } +@end + +// RUN: c-index-test -code-completion-at=%s:39:3 %s | FileCheck -check-prefix=CHECK-CC1 %s +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text void}{RightParen )}{TypedText addMutableArrayPropertyObject:}{LeftParen (}{Placeholder object-type}{Text *}{RightParen )}{Text object} (55) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text void}{RightParen )}{TypedText addMutableSetProperty:}{LeftParen (}{Text NSSet *}{RightParen )}{Text objects} (40) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text NSArray *}{RightParen )}{TypedText arrayProperty} (40) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text NSArray *}{RightParen )}{TypedText arrayPropertyAtIndexes:}{LeftParen (}{Text NSIndexSet *}{RightParen )}{Text indexes} (40) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text BOOL}{RightParen )}{TypedText boolProperty} (40) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text NSArray *}{RightParen )}{TypedText boolPropertyAtIndexes:}{LeftParen (}{Text NSIndexSet *}{RightParen )}{Text indexes} (55) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text NSUInteger}{RightParen )}{TypedText countOfArrayProperty} (40) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text NSUInteger}{RightParen )}{TypedText countOfBoolProperty} (55) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text NSUInteger}{RightParen )}{TypedText countOfSetProperty} (40) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text NSEnumerator *}{RightParen )}{TypedText enumeratorOfMutableArrayProperty} (55) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text NSEnumerator *}{RightParen )}{TypedText enumeratorOfMutableSetProperty} (40) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text void}{RightParen )}{TypedText getMyArrayProperty:}{LeftParen (}{Placeholder object-type}{Text **}{RightParen )}{Text buffer}{HorizontalSpace }{TypedText range:}{LeftParen (}{Text NSRange}{RightParen )}{Text inRange} (40) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text void}{RightParen )}{TypedText getMySetProperty:}{LeftParen (}{Placeholder object-type}{Text **}{RightParen )}{Text buffer}{HorizontalSpace }{TypedText range:}{LeftParen (}{Text NSRange}{RightParen )}{Text inRange} (55) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text void}{RightParen )}{TypedText insertIntProperty:}{LeftParen (}{Text NSArray *}{RightParen )}{Text array}{HorizontalSpace }{TypedText atIndexes:}{LeftParen (}{Placeholder NSIndexSet *}{RightParen )}{Text indexes} (55) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text void}{RightParen )}{TypedText insertMutableArrayProperty:}{LeftParen (}{Text NSArray *}{RightParen )}{Text array}{HorizontalSpace }{TypedText atIndexes:}{LeftParen (}{Placeholder NSIndexSet *}{RightParen )}{Text indexes} (40) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text void}{RightParen )}{TypedText insertObject:}{LeftParen (}{Placeholder object-type}{Text *}{RightParen )}{Text object}{HorizontalSpace }{TypedText inMyArrayPropertyAtIndex:}{LeftParen (}{Placeholder NSUInteger}{RightParen )}{Text index} (40) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text void}{RightParen )}{TypedText insertObject:}{LeftParen (}{Placeholder object-type}{Text *}{RightParen )}{Text object}{HorizontalSpace }{TypedText inMySetPropertyAtIndex:}{LeftParen (}{Placeholder NSUInteger}{RightParen )}{Text index} (55) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text void}{RightParen )}{TypedText intersectMySetProperty:}{LeftParen (}{Text NSSet *}{RightParen )}{Text objects} (40) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text void}{RightParen )}{TypedText intersectSetProperty:}{LeftParen (}{Text NSSet *}{RightParen )}{Text objects} (55) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text int *}{RightParen )}{TypedText intPointerProperty} (40) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text int}{RightParen )}{TypedText intProperty} (40) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text BOOL}{RightParen )}{TypedText isBoolProperty} (40) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text BOOL}{RightParen )}{TypedText isIntProperty} (40) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Placeholder object-type}{Text *}{RightParen )}{TypedText memberOfMyArrayProperty:}{LeftParen (}{Placeholder object-type}{Text *}{RightParen )}{Text object} (55) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Placeholder object-type}{Text *}{RightParen )}{TypedText memberOfMySetProperty:}{LeftParen (}{Placeholder object-type}{Text *}{RightParen )}{Text object} (40) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text NSMutableArray *}{RightParen )}{TypedText mutableArrayProperty} (40) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text NSMutableSet *}{RightParen )}{TypedText mutableSetProperty} (40) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text NSArray *}{RightParen )}{TypedText mutableSetPropertyAtIndexes:}{LeftParen (}{Text NSIndexSet *}{RightParen )}{Text indexes} (55) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text MyArray *}{RightParen )}{TypedText myArrayProperty} (40) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text NSArray *}{RightParen )}{TypedText myArrayPropertyAtIndexes:}{LeftParen (}{Text NSIndexSet *}{RightParen )}{Text indexes} (40) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text id}{RightParen )}{TypedText objectInMutableSetPropertyAtIndex:}{LeftParen (}{Text NSUInteger}{RightParen )}{Text index} (55) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text id}{RightParen )}{TypedText objectInMyArrayPropertyAtIndex:}{LeftParen (}{Text NSUInteger}{RightParen )}{Text index} (40) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text void}{RightParen )}{TypedText removeMyArrayPropertyAtIndexes:}{LeftParen (}{Text NSIndexSet *}{RightParen )}{Text indexes} (40) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text void}{RightParen )}{TypedText removeMySetPropertyAtIndexes:}{LeftParen (}{Text NSIndexSet *}{RightParen )}{Text indexes} (55) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text void}{RightParen )}{TypedText removeObjectFromIntPropertyAtIndex:}{LeftParen (}{Text NSUInteger}{RightParen )}{Text index} (55) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text void}{RightParen )}{TypedText removeObjectFromMutableArrayPropertyAtIndex:}{LeftParen (}{Text NSUInteger}{RightParen )}{Text index} (40) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text void}{RightParen )}{TypedText replaceMyArrayPropertyAtIndexes:}{LeftParen (}{Placeholder NSIndexSet *}{RightParen )}{Text indexes}{HorizontalSpace }{TypedText withMyArrayProperty:}{LeftParen (}{Text NSArray *}{RightParen )}{Text array} (40) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text void}{RightParen )}{TypedText replaceMySetPropertyAtIndexes:}{LeftParen (}{Placeholder NSIndexSet *}{RightParen )}{Text indexes}{HorizontalSpace }{TypedText withMySetProperty:}{LeftParen (}{Text NSArray *}{RightParen )}{Text array} (55) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text void}{RightParen )}{TypedText replaceObjectInMyArrayPropertyAtIndex:}{LeftParen (}{Placeholder NSUInteger}{RightParen )}{Text index}{HorizontalSpace }{TypedText withObject:}{LeftParen (}{Text id}{RightParen )}{Text object} (40) +// CHECK-CC1: ObjCInstanceMethodDecl:{LeftParen (}{Text void}{RightParen )}{TypedText replaceObjectInMySetPropertyAtIndex:}{LeftParen (}{Placeholder NSUInteger}{RightParen )}{Text index}{HorizontalSpace }{TypedText withObject:}{LeftParen (}{Text id}{RightParen )}{Text object} (55) + +// RUN: c-index-test -code-completion-at=%s:41:3 %s | FileCheck -check-prefix=CHECK-CC2 %s +// CHECK-CC2: ObjCInstanceMethodDecl:{LeftParen (}{Text NSSet *}{RightParen )}{TypedText keyPathsForValuesAffectingMutableArrayProperty} (40) -- 2.40.0