From feafdf6be48da5819d1a03f672d9afdbf526025d Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Thu, 8 Dec 2016 15:09:40 +0000 Subject: [PATCH] [CodeCompletion] Provide Objective-C class property completion results This commit provides class property code completion results. It supports explicit and implicit class properties, but the special block completion is done only for explicit properties right now. rdar://25636195 Differential Revision: https://reviews.llvm.org/D27053 llvm-svn: 289058 --- clang/include/clang/Sema/Sema.h | 3 + clang/lib/Parse/ParseExpr.cpp | 8 +- clang/lib/Sema/SemaCodeComplete.cpp | 113 +++++++++++++++++++-------- clang/test/Index/complete-block-properties.m | 16 ++++ clang/test/Index/complete-properties.m | 81 +++++++++++++++++++ 5 files changed, 187 insertions(+), 34 deletions(-) diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index d5a6912..fcc0d53 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -9662,6 +9662,9 @@ public: bool AtParameterName, ParsedType ReturnType, ArrayRef SelIdents); + void CodeCompleteObjCClassPropertyRefExpr(Scope *S, IdentifierInfo &ClassName, + SourceLocation ClassNameLoc, + bool IsBaseExprStatement); void CodeCompletePreprocessorDirective(bool InConditional); void CodeCompleteInPreprocessorConditionalExclusion(Scope *S); void CodeCompletePreprocessorMacroName(bool IsDefinition); diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp index b9988bb..4b0a834 100644 --- a/clang/lib/Parse/ParseExpr.cpp +++ b/clang/lib/Parse/ParseExpr.cpp @@ -885,7 +885,13 @@ ExprResult Parser::ParseCastExpression(bool isUnaryExpression, // Allow the base to be 'super' if in an objc-method. (&II == Ident_super && getCurScope()->isInObjcMethodScope()))) { ConsumeToken(); - + + if (Tok.is(tok::code_completion) && &II != Ident_super) { + Actions.CodeCompleteObjCClassPropertyRefExpr( + getCurScope(), II, ILoc, ExprStatementTokLoc == ILoc); + cutOffParsing(); + return ExprError(); + } // Allow either an identifier or the keyword 'class' (in C++). if (Tok.isNot(tok::identifier) && !(getLangOpts().CPlusPlus && Tok.is(tok::kw_class))) { diff --git a/clang/lib/Sema/SemaCodeComplete.cpp b/clang/lib/Sema/SemaCodeComplete.cpp index 2a78c981..4bcf405 100644 --- a/clang/lib/Sema/SemaCodeComplete.cpp +++ b/clang/lib/Sema/SemaCodeComplete.cpp @@ -3654,22 +3654,20 @@ static void AddObjCBlockCall(ASTContext &Context, const PrintingPolicy &Policy, Builder.AddChunk(CodeCompletionString::CK_RightParen); } -static void AddObjCProperties(const CodeCompletionContext &CCContext, - ObjCContainerDecl *Container, - bool AllowCategories, bool AllowNullaryMethods, - DeclContext *CurContext, - AddedPropertiesSet &AddedProperties, - ResultBuilder &Results, - bool IsBaseExprStatement = false) { +static void AddObjCProperties( + const CodeCompletionContext &CCContext, ObjCContainerDecl *Container, + bool AllowCategories, bool AllowNullaryMethods, DeclContext *CurContext, + AddedPropertiesSet &AddedProperties, ResultBuilder &Results, + bool IsBaseExprStatement = false, bool IsClassProperty = false) { typedef CodeCompletionResult Result; // Retrieve the definition. Container = getContainerDef(Container); // Add properties in this container. - for (const auto *P : Container->instance_properties()) { + const auto AddProperty = [&](const ObjCPropertyDecl *P) { if (!AddedProperties.insert(P->getIdentifier()).second) - continue; + return; // FIXME: Provide block invocation completion for non-statement // expressions. @@ -3677,7 +3675,7 @@ static void AddObjCProperties(const CodeCompletionContext &CCContext, !IsBaseExprStatement) { Results.MaybeAddResult(Result(P, Results.getBasePriority(P), nullptr), CurContext); - continue; + return; } // Block setter and invocation completion is provided only when we are able @@ -3689,7 +3687,7 @@ static void AddObjCProperties(const CodeCompletionContext &CCContext, if (!BlockLoc) { Results.MaybeAddResult(Result(P, Results.getBasePriority(P), nullptr), CurContext); - continue; + return; } // The default completion result for block properties should be the block @@ -3727,64 +3725,89 @@ static void AddObjCProperties(const CodeCompletionContext &CCContext, Results.getBasePriority(P) + CCD_BlockPropertySetter), CurContext); } + }; + + if (IsClassProperty) { + for (const auto *P : Container->class_properties()) + AddProperty(P); + } else { + for (const auto *P : Container->instance_properties()) + AddProperty(P); } - // Add nullary methods + // Add nullary methods or implicit class properties if (AllowNullaryMethods) { ASTContext &Context = Container->getASTContext(); PrintingPolicy Policy = getCompletionPrintingPolicy(Results.getSema()); - for (auto *M : Container->methods()) { - if (M->getSelector().isUnarySelector()) - if (IdentifierInfo *Name = M->getSelector().getIdentifierInfoForSlot(0)) - if (AddedProperties.insert(Name).second) { - CodeCompletionBuilder Builder(Results.getAllocator(), - Results.getCodeCompletionTUInfo()); - AddResultTypeChunk(Context, Policy, M, CCContext.getBaseType(), - Builder); - Builder.AddTypedTextChunk( - Results.getAllocator().CopyString(Name->getName())); - - Results.MaybeAddResult(Result(Builder.TakeString(), M, - CCP_MemberDeclaration + CCD_MethodAsProperty), - CurContext); - } + // Adds a method result + const auto AddMethod = [&](const ObjCMethodDecl *M) { + IdentifierInfo *Name = M->getSelector().getIdentifierInfoForSlot(0); + if (!Name) + return; + if (!AddedProperties.insert(Name).second) + return; + CodeCompletionBuilder Builder(Results.getAllocator(), + Results.getCodeCompletionTUInfo()); + AddResultTypeChunk(Context, Policy, M, CCContext.getBaseType(), Builder); + Builder.AddTypedTextChunk( + Results.getAllocator().CopyString(Name->getName())); + Results.MaybeAddResult( + Result(Builder.TakeString(), M, + CCP_MemberDeclaration + CCD_MethodAsProperty), + CurContext); + }; + + if (IsClassProperty) { + for (const auto *M : Container->methods()) { + // Gather the class method that can be used as implicit property + // getters. Methods with arguments or methods that return void aren't + // added to the results as they can't be used as a getter. + if (!M->getSelector().isUnarySelector() || + M->getReturnType()->isVoidType() || M->isInstanceMethod()) + continue; + AddMethod(M); + } + } else { + for (auto *M : Container->methods()) { + if (M->getSelector().isUnarySelector()) + AddMethod(M); + } } } - // Add properties in referenced protocols. if (ObjCProtocolDecl *Protocol = dyn_cast(Container)) { for (auto *P : Protocol->protocols()) AddObjCProperties(CCContext, P, AllowCategories, AllowNullaryMethods, CurContext, AddedProperties, Results, - IsBaseExprStatement); + IsBaseExprStatement, IsClassProperty); } else if (ObjCInterfaceDecl *IFace = dyn_cast(Container)){ if (AllowCategories) { // Look through categories. for (auto *Cat : IFace->known_categories()) AddObjCProperties(CCContext, Cat, AllowCategories, AllowNullaryMethods, CurContext, AddedProperties, Results, - IsBaseExprStatement); + IsBaseExprStatement, IsClassProperty); } // Look through protocols. for (auto *I : IFace->all_referenced_protocols()) AddObjCProperties(CCContext, I, AllowCategories, AllowNullaryMethods, CurContext, AddedProperties, Results, - IsBaseExprStatement); + IsBaseExprStatement, IsClassProperty); // Look in the superclass. if (IFace->getSuperClass()) AddObjCProperties(CCContext, IFace->getSuperClass(), AllowCategories, AllowNullaryMethods, CurContext, AddedProperties, - Results, IsBaseExprStatement); + Results, IsBaseExprStatement, IsClassProperty); } else if (const ObjCCategoryDecl *Category = dyn_cast(Container)) { // Look through protocols. for (auto *P : Category->protocols()) AddObjCProperties(CCContext, P, AllowCategories, AllowNullaryMethods, CurContext, AddedProperties, Results, - IsBaseExprStatement); + IsBaseExprStatement, IsClassProperty); } } @@ -3909,6 +3932,30 @@ void Sema::CodeCompleteMemberReferenceExpr(Scope *S, Expr *Base, Results.data(),Results.size()); } +void Sema::CodeCompleteObjCClassPropertyRefExpr(Scope *S, + IdentifierInfo &ClassName, + SourceLocation ClassNameLoc, + bool IsBaseExprStatement) { + IdentifierInfo *ClassNamePtr = &ClassName; + ObjCInterfaceDecl *IFace = getObjCInterfaceDecl(ClassNamePtr, ClassNameLoc); + if (!IFace) + return; + CodeCompletionContext CCContext( + CodeCompletionContext::CCC_ObjCPropertyAccess); + ResultBuilder Results(*this, CodeCompleter->getAllocator(), + CodeCompleter->getCodeCompletionTUInfo(), CCContext, + &ResultBuilder::IsMember); + Results.EnterNewScope(); + AddedPropertiesSet AddedProperties; + AddObjCProperties(CCContext, IFace, true, + /*AllowNullaryMethods=*/true, CurContext, AddedProperties, + Results, IsBaseExprStatement, + /*IsClassProperty=*/true); + Results.ExitScope(); + HandleCodeCompleteResults(this, CodeCompleter, Results.getCompletionContext(), + Results.data(), Results.size()); +} + void Sema::CodeCompleteTag(Scope *S, unsigned TagSpec) { if (!CodeCompleter) return; diff --git a/clang/test/Index/complete-block-properties.m b/clang/test/Index/complete-block-properties.m index 47eee02..d166147 100644 --- a/clang/test/Index/complete-block-properties.m +++ b/clang/test/Index/complete-block-properties.m @@ -70,3 +70,19 @@ void noQualifierParens(NoQualifierParens *f) { //CHECK-CC2-NEXT: ObjCInstanceMethodDecl:{ResultType BarBlock}{TypedText blockProperty2} (35) //CHECK-CC2-NEXT: ObjCInstanceMethodDecl:{ResultType void}{TypedText setBlockProperty2:}{Placeholder BarBlock blockProperty2} (35) //CHECK-CC2-NEXT: ObjCInstanceMethodDecl:{ResultType void}{TypedText setBlockProperty:}{Placeholder void (^)(void)blockProperty} (35) + +@interface ClassProperties + +@property(class) void (^explicit)(); +@property(class, readonly) void (^explicitReadonly)(); + +@end + +void classBlockProperties() { + ClassProperties.explicit; +} + +// RUN: c-index-test -code-completion-at=%s:82:19 %s | FileCheck -check-prefix=CHECK-CC3 %s +//CHECK-CC3: ObjCPropertyDecl:{ResultType void}{TypedText explicit}{LeftParen (}{RightParen )} (35) +//CHECK-CC3-NEXT: ObjCPropertyDecl:{ResultType void (^)()}{TypedText explicit}{Equal = }{Placeholder ^(void)} (38) +//CHECK-CC3-NEXT: ObjCPropertyDecl:{ResultType void}{TypedText explicitReadonly}{LeftParen (}{RightParen )} (35) diff --git a/clang/test/Index/complete-properties.m b/clang/test/Index/complete-properties.m index a445056..0c49819 100644 --- a/clang/test/Index/complete-properties.m +++ b/clang/test/Index/complete-properties.m @@ -94,3 +94,84 @@ id test(I3 *i3) { // RUN: c-index-test -code-completion-at=%s:57:13 -fobjc-nonfragile-abi %s | FileCheck -check-prefix=CHECK-CC8 %s // CHECK-CC8: ObjCPropertyDecl:{ResultType int}{TypedText Prop5} (35) + +@interface ClassProperties + +@property int instanceProperty; +@property(class) int explicit; +@property(class, readonly) int explicitReadonly; + ++ (int)implicit; ++ (int)setImplicit:(int)x; + ++ (int)implicitReadonly; + ++ (void)noProperty; + +- (int)implicitInstance; + ++ (int)shadowedImplicit; + +@end + +@interface ClassProperties (Category) + ++ (int)implicitInCategory; + +@end + +@protocol ProtocolClassProperties + +@property(class, readonly) int explicitInProtocol; + +@end + +@interface SubClassProperties: ClassProperties + +@property(class) ClassProperties *shadowedImplicit; + +@end + +@implementation SubClassProperties + +-(void) foo { + super.instanceProperty; +} + +@end + +void classProperties() { + (void)ClassProperties.implicit; + (void)SubClassProperties.explicit; +} + +// RUN: c-index-test -code-completion-at=%s:144:25 -fobjc-nonfragile-abi %s | FileCheck -check-prefix=CHECK-CC9 %s +// CHECK-CC9: ObjCPropertyDecl:{ResultType int}{TypedText explicit} (35) +// CHECK-CC9-NEXT: ObjCPropertyDecl:{ResultType int}{TypedText explicitReadonly} (35) +// CHECK-CC9-NEXT: ObjCClassMethodDecl:{ResultType int}{TypedText implicit} (37) +// CHECK-CC9-NEXT: ObjCClassMethodDecl:{ResultType int}{TypedText implicitInCategory} (37) +// CHECK-CC9-NEXT: ObjCClassMethodDecl:{ResultType int}{TypedText implicitReadonly} (37) +// CHECK-CC9-NEXT: ObjCClassMethodDecl:{ResultType int}{TypedText shadowedImplicit} (37) +// CHECK-CC9-NOT: implicitInstance +// CHECK-CC9-NOT: noProperty +// CHECK-CC9-NOT: instanceProperty + +// RUN: c-index-test -code-completion-at=%s:145:28 -fobjc-nonfragile-abi %s | FileCheck -check-prefix=CHECK-CC10 %s +// CHECK-CC10: ObjCPropertyDecl:{ResultType int}{TypedText explicit} (35) +// CHECK-CC10-NEXT: ObjCPropertyDecl:{ResultType int}{TypedText explicitInProtocol} (35) +// CHECK-CC10-NEXT: ObjCPropertyDecl:{ResultType int}{TypedText explicitReadonly} (35) +// CHECK-CC10-NEXT: ObjCClassMethodDecl:{ResultType int}{TypedText implicit} (37) +// CHECK-CC10-NEXT: ObjCClassMethodDecl:{ResultType int}{TypedText implicitInCategory} (37) +// CHECK-CC10-NEXT: ObjCClassMethodDecl:{ResultType int}{TypedText implicitReadonly} (37) +// CHECK-CC10-NEXT: ObjCPropertyDecl:{ResultType ClassProperties *}{TypedText shadowedImplicit} (35) +// CHECK-CC10-NOT: implicitInstance +// CHECK-CC10-NOT: noProperty +// CHECK-CC10-NOT: instanceProperty + +// RUN: c-index-test -code-completion-at=%s:138:9 -fobjc-nonfragile-abi %s | FileCheck -check-prefix=CHECK-CC11 %s +// CHECK-CC11-NOT: explicit +// CHECK-CC11-NOT: explicitReadonly +// CHECK-CC11-NOT: implicit +// CHECK-CC11-NOT: implicitReadonly +// CHECK-CC11-NOT: shadowedImplicit +// CHECK-CC11-NOT: implicitInCategory -- 2.7.4