From: Douglas Gregor Date: Fri, 18 Sep 2009 15:37:17 +0000 (+0000) Subject: Implement code completion for tags, e.g., code completion after "enum" X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=374929f7e88f6c7a96382b3eb4201b721c418372;p=clang Implement code completion for tags, e.g., code completion after "enum" will provide the names of various enumerations currently visible. Introduced filtering of code-completion results when we build the result set, so that we can identify just the kinds of declarations we want. This implementation is incomplete for C++, since we don't consider that the token after the tag keyword could start a nested-name-specifier. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@82222 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/Parse/Action.h b/include/clang/Parse/Action.h index 6a2ff62839..16bd8d15ce 100644 --- a/include/clang/Parse/Action.h +++ b/include/clang/Parse/Action.h @@ -2210,8 +2210,18 @@ public: /// \param IsArrow true when the operator is "->", false when it is ".". virtual void CodeCompleteMemberReferenceExpr(Scope *S, ExprTy *Base, SourceLocation OpLoc, - bool IsArrow) { - } + bool IsArrow) { } + + /// \brief Code completion for a reference to a tag. + /// + /// This code completion action is invoked when the code-completion + /// token is found after a tag keyword (struct, union, enum, or class). + /// + /// \para, S the scope in which the tag reference occurs. + /// + /// \param TagSpec an instance of DeclSpec::TST, indicating what kind of tag + /// this is (struct/union/enum/class). + virtual void CodeCompleteTag(Scope *S, unsigned TagSpec) { } /// \brief Code completion for a C++ nested-name-specifier that precedes a /// qualified-id of some form. diff --git a/include/clang/Sema/CodeCompleteConsumer.h b/include/clang/Sema/CodeCompleteConsumer.h index 29b6de1c88..0da5541c3d 100644 --- a/include/clang/Sema/CodeCompleteConsumer.h +++ b/include/clang/Sema/CodeCompleteConsumer.h @@ -75,9 +75,17 @@ public: Result(const char *Keyword, unsigned Rank) : Kind(RK_Keyword), Keyword(Keyword), Rank(Rank), Hidden(false) { } }; - + /// \brief A container of code-completion results. class ResultSet { + public: + /// \brief The type of a name-lookup filter, which can be provided to the + /// name-lookup routines to specify which declarations should be included in + /// the result set (when it returns true) and which declarations should be + /// filtered out (returns false). + typedef bool (CodeCompleteConsumer::*LookupFilter)(NamedDecl *) const; + + private: /// \brief The actual results we have found. std::vector Results; @@ -87,11 +95,27 @@ public: typedef std::multimap > ShadowMap; + /// \brief The code-completion consumer that is producing these results. + CodeCompleteConsumer &Completer; + + /// \brief If non-NULL, a filter function used to remove any code-completion + /// results that are not desirable. + LookupFilter Filter; + /// \brief A list of shadow maps, which is used to model name hiding at /// different levels of, e.g., the inheritance hierarchy. std::list ShadowMaps; - + public: + explicit ResultSet(CodeCompleteConsumer &Completer, + LookupFilter Filter = 0) + : Completer(Completer), Filter(Filter) { } + + /// \brief Set the filter used for code-completion results. + void setFilter(LookupFilter Filter) { + this->Filter = Filter; + } + typedef std::vector::iterator iterator; iterator begin() { return Results.begin(); } iterator end() { return Results.end(); } @@ -99,7 +123,7 @@ public: Result *data() { return Results.empty()? 0 : &Results.front(); } unsigned size() const { return Results.size(); } bool empty() const { return Results.empty(); } - + /// \brief Add a new result to this result set (if it isn't already in one /// of the shadow maps), or replace an existing result (for, e.g., a /// redeclaration). @@ -142,6 +166,10 @@ public: virtual void CodeCompleteMemberReferenceExpr(Scope *S, QualType BaseType, bool IsArrow); + /// \brief Code completion for a tag name following an enum, class, struct, + /// or union keyword. + virtual void CodeCompleteTag(Scope *S, ElaboratedType::TagKind TK); + /// \brief Code completion for a qualified-id, e.g., "std::" /// /// \param S the scope in which the nested-name-specifier occurs. @@ -154,10 +182,27 @@ public: bool EnteringContext); //@} - /// \name Utility functions + /// \name Name lookup functions + /// + /// The name lookup functions in this group collect code-completion results + /// by performing a form of name looking into a scope or declaration context. //@{ - unsigned CollectMemberResults(DeclContext *Ctx, unsigned InitialRank, + unsigned CollectLookupResults(Scope *S, unsigned InitialRank, ResultSet &Results); + unsigned CollectMemberLookupResults(DeclContext *Ctx, unsigned InitialRank, + ResultSet &Results); + //@} + + /// \name Name lookup predicates + /// + /// These predicates can be passed to the name lookup functions to filter the + /// results of name lookup. All of the predicates have the same type, so that + /// + //@{ + bool IsNestedNameSpecifier(NamedDecl *ND) const; + bool IsEnum(NamedDecl *ND) const; + bool IsClassOrStruct(NamedDecl *ND) const; + bool IsUnion(NamedDecl *ND) const; //@} }; diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index f08c481c52..2eccbd04da 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -1589,7 +1589,12 @@ void Parser::ParseStructUnionBody(SourceLocation RecordLoc, void Parser::ParseEnumSpecifier(SourceLocation StartLoc, DeclSpec &DS, AccessSpecifier AS) { // Parse the tag portion of this. - + if (Tok.is(tok::code_completion)) { + // Code completion for an enum name. + Actions.CodeCompleteTag(CurScope, DeclSpec::TST_enum); + ConsumeToken(); + } + AttributeList *Attr = 0; // If attributes exist after tag, parse them. if (Tok.is(tok::kw___attribute)) diff --git a/lib/Parse/ParseDeclCXX.cpp b/lib/Parse/ParseDeclCXX.cpp index 671a5421cc..1b82c06bf8 100644 --- a/lib/Parse/ParseDeclCXX.cpp +++ b/lib/Parse/ParseDeclCXX.cpp @@ -527,6 +527,12 @@ void Parser::ParseClassSpecifier(tok::TokenKind TagTokKind, TagType = DeclSpec::TST_union; } + if (Tok.is(tok::code_completion)) { + // Code completion for a struct, class, or union name. + Actions.CodeCompleteTag(CurScope, TagType); + ConsumeToken(); + } + AttributeList *Attr = 0; // If attributes exist after tag, parse them. if (Tok.is(tok::kw___attribute)) diff --git a/lib/Sema/CodeCompleteConsumer.cpp b/lib/Sema/CodeCompleteConsumer.cpp index 7464bc1f88..707ad9a143 100644 --- a/lib/Sema/CodeCompleteConsumer.cpp +++ b/lib/Sema/CodeCompleteConsumer.cpp @@ -11,6 +11,7 @@ // //===----------------------------------------------------------------------===// #include "clang/Sema/CodeCompleteConsumer.h" +#include "clang/Parse/Scope.h" #include "clang/Lex/Preprocessor.h" #include "Sema.h" #include "llvm/ADT/STLExtras.h" @@ -41,11 +42,11 @@ CodeCompleteConsumer::CodeCompleteMemberReferenceExpr(Scope *S, return; } - ResultSet Results; + ResultSet Results(*this); unsigned NextRank = 0; if (const RecordType *Record = BaseType->getAs()) { - NextRank = CollectMemberResults(Record->getDecl(), NextRank, Results); + NextRank = CollectMemberLookupResults(Record->getDecl(), NextRank, Results); if (getSema().getLangOptions().CPlusPlus) { if (!Results.empty()) @@ -64,6 +65,32 @@ CodeCompleteConsumer::CodeCompleteMemberReferenceExpr(Scope *S, } } +void CodeCompleteConsumer::CodeCompleteTag(Scope *S, ElaboratedType::TagKind TK) { + ResultSet::LookupFilter Filter = 0; + switch (TK) { + case ElaboratedType::TK_enum: + Filter = &CodeCompleteConsumer::IsEnum; + break; + + case ElaboratedType::TK_class: + case ElaboratedType::TK_struct: + Filter = &CodeCompleteConsumer::IsClassOrStruct; + break; + + case ElaboratedType::TK_union: + Filter = &CodeCompleteConsumer::IsUnion; + break; + } + + ResultSet Results(*this, Filter); + CollectLookupResults(S, 0, Results); + + // FIXME: In C++, we could have the start of a nested-name-specifier. + // Add those results (with a poorer rank, naturally). + + ProcessCodeCompleteResults(Results.data(), Results.size()); +} + void CodeCompleteConsumer::CodeCompleteQualifiedId(Scope *S, NestedNameSpecifier *NNS, @@ -74,8 +101,8 @@ CodeCompleteConsumer::CodeCompleteQualifiedId(Scope *S, if (!Ctx) return; - ResultSet Results; - unsigned NextRank = CollectMemberResults(Ctx, 0, Results); + ResultSet Results(*this); + unsigned NextRank = CollectMemberLookupResults(Ctx, 0, Results); // The "template" keyword can follow "::" in the grammar if (!Results.empty()) @@ -92,6 +119,11 @@ void CodeCompleteConsumer::ResultSet::MaybeAddResult(Result R) { } // FIXME: Using declarations + // FIXME: Separate overload sets + + // Filter out any unwanted results. + if (Filter && !(Completer.*Filter)(R.Declaration)) + return; Decl *CanonDecl = R.Declaration->getCanonicalDecl(); unsigned IDNS = CanonDecl->getIdentifierNamespace(); @@ -164,23 +196,89 @@ void CodeCompleteConsumer::ResultSet::ExitScope() { ShadowMaps.pop_back(); } +// Find the next outer declaration context corresponding to this scope. +static DeclContext *findOuterContext(Scope *S) { + for (S = S->getParent(); S; S = S->getParent()) + if (S->getEntity()) + return static_cast(S->getEntity())->getPrimaryContext(); + + return 0; +} + +/// \brief Collect the results of searching for declarations within the given +/// scope and its parent scopes. +/// +/// \param S the scope in which we will start looking for declarations. +/// +/// \param InitialRank the initial rank given to results in this scope. +/// Larger rank values will be used for results found in parent scopes. +unsigned CodeCompleteConsumer::CollectLookupResults(Scope *S, + unsigned InitialRank, + ResultSet &Results) { + if (!S) + return InitialRank; + + // FIXME: Using directives! + + unsigned NextRank = InitialRank; + Results.EnterNewScope(); + if (S->getEntity() && + !((DeclContext *)S->getEntity())->isFunctionOrMethod()) { + // Look into this scope's declaration context, along with any of its + // parent lookup contexts (e.g., enclosing classes), up to the point + // where we hit the context stored in the next outer scope. + DeclContext *Ctx = (DeclContext *)S->getEntity(); + DeclContext *OuterCtx = findOuterContext(S); + + for (; Ctx && Ctx->getPrimaryContext() != OuterCtx; + Ctx = Ctx->getLookupParent()) { + if (Ctx->isFunctionOrMethod()) + continue; + + NextRank = CollectMemberLookupResults(Ctx, NextRank + 1, Results); + } + } else if (!S->getParent()) { + // Look into the translation unit scope. We walk through the translation + // unit's declaration context, because the Scope itself won't have all of + // the declarations if + NextRank = CollectMemberLookupResults( + getSema().Context.getTranslationUnitDecl(), + NextRank + 1, Results); + } else { + // Walk through the declarations in this Scope. + for (Scope::decl_iterator D = S->decl_begin(), DEnd = S->decl_end(); + D != DEnd; ++D) { + if (NamedDecl *ND = dyn_cast((Decl *)((*D).get()))) + Results.MaybeAddResult(Result(ND, NextRank)); + } + + NextRank = NextRank + 1; + } + + // Lookup names in the parent scope. + NextRank = CollectLookupResults(S->getParent(), NextRank, Results); + Results.ExitScope(); + + return NextRank; +} + /// \brief Collect the results of searching for members within the given /// declaration context. /// /// \param Ctx the declaration context from which we will gather results. /// -/// \param InitialRank the initial rank given to results in this tag -/// declaration. Larger rank values will be used for, e.g., members found -/// in base classes. +/// \param InitialRank the initial rank given to results in this declaration +/// context. Larger rank values will be used for, e.g., members found in +/// base classes. /// /// \param Results the result set that will be extended with any results /// found within this declaration context (and, for a C++ class, its bases). /// /// \returns the next higher rank value, after considering all of the /// names within this declaration context. -unsigned CodeCompleteConsumer::CollectMemberResults(DeclContext *Ctx, - unsigned InitialRank, - ResultSet &Results) { +unsigned CodeCompleteConsumer::CollectMemberLookupResults(DeclContext *Ctx, + unsigned InitialRank, + ResultSet &Results) { // Enumerate all of the results in this context. Results.EnterNewScope(); for (DeclContext *CurCtx = Ctx->getPrimaryContext(); CurCtx; @@ -188,10 +286,8 @@ unsigned CodeCompleteConsumer::CollectMemberResults(DeclContext *Ctx, for (DeclContext::decl_iterator D = CurCtx->decls_begin(), DEnd = CurCtx->decls_end(); D != DEnd; ++D) { - if (NamedDecl *ND = dyn_cast(*D)) { - // FIXME: Apply a filter to the results + if (NamedDecl *ND = dyn_cast(*D)) Results.MaybeAddResult(Result(ND, InitialRank)); - } } } @@ -235,9 +331,9 @@ unsigned CodeCompleteConsumer::CollectMemberResults(DeclContext *Ctx, // Collect results from this base class (and its bases). NextRank = std::max(NextRank, - CollectMemberResults(Record->getDecl(), - InitialRank + 1, - Results)); + CollectMemberLookupResults(Record->getDecl(), + InitialRank + 1, + Results)); } } @@ -247,6 +343,46 @@ unsigned CodeCompleteConsumer::CollectMemberResults(DeclContext *Ctx, return NextRank; } +/// \brief Determines whether the given declaration is suitable as the +/// start of a C++ nested-name-specifier, e.g., a class or namespace. +bool CodeCompleteConsumer::IsNestedNameSpecifier(NamedDecl *ND) const { + // Allow us to find class templates, too. + if (ClassTemplateDecl *ClassTemplate = dyn_cast(ND)) + ND = ClassTemplate->getTemplatedDecl(); + + return getSema().isAcceptableNestedNameSpecifier(ND); +} + +/// \brief Determines whether the given declaration is an enumeration. +bool CodeCompleteConsumer::IsEnum(NamedDecl *ND) const { + return isa(ND); +} + +/// \brief Determines whether the given declaration is a class or struct. +bool CodeCompleteConsumer::IsClassOrStruct(NamedDecl *ND) const { + // Allow us to find class templates, too. + if (ClassTemplateDecl *ClassTemplate = dyn_cast(ND)) + ND = ClassTemplate->getTemplatedDecl(); + + if (RecordDecl *RD = dyn_cast(ND)) + return RD->getTagKind() == TagDecl::TK_class || + RD->getTagKind() == TagDecl::TK_struct; + + return false; +} + +/// \brief Determines whether the given declaration is a union. +bool CodeCompleteConsumer::IsUnion(NamedDecl *ND) const { + // Allow us to find class templates, too. + if (ClassTemplateDecl *ClassTemplate = dyn_cast(ND)) + ND = ClassTemplate->getTemplatedDecl(); + + if (RecordDecl *RD = dyn_cast(ND)) + return RD->getTagKind() == TagDecl::TK_union; + + return false; +} + namespace { struct VISIBILITY_HIDDEN SortCodeCompleteResult { typedef CodeCompleteConsumer::Result Result; diff --git a/lib/Sema/Sema.h b/lib/Sema/Sema.h index 915a85990c..2952c843f4 100644 --- a/lib/Sema/Sema.h +++ b/lib/Sema/Sema.h @@ -2083,6 +2083,7 @@ public: virtual CXXScopeTy *ActOnCXXGlobalScopeSpecifier(Scope *S, SourceLocation CCLoc); + bool isAcceptableNestedNameSpecifier(NamedDecl *SD); NamedDecl *FindFirstQualifierInScope(Scope *S, NestedNameSpecifier *NNS); @@ -3635,6 +3636,8 @@ public: SourceLocation OpLoc, bool IsArrow); + virtual void CodeCompleteTag(Scope *S, unsigned TagSpec); + virtual void CodeCompleteQualifiedId(Scope *S, const CXXScopeSpec &SS, bool EnteringContext); //@} diff --git a/lib/Sema/SemaCXXScopeSpec.cpp b/lib/Sema/SemaCXXScopeSpec.cpp index 759b56ced2..60661f147e 100644 --- a/lib/Sema/SemaCXXScopeSpec.cpp +++ b/lib/Sema/SemaCXXScopeSpec.cpp @@ -260,7 +260,7 @@ Sema::CXXScopeTy *Sema::ActOnCXXGlobalScopeSpecifier(Scope *S, /// \brief Determines whether the given declaration is an valid acceptable /// result for name lookup of a nested-name-specifier. -bool isAcceptableNestedNameSpecifier(ASTContext &Context, NamedDecl *SD) { +bool Sema::isAcceptableNestedNameSpecifier(NamedDecl *SD) { if (!SD) return false; @@ -307,7 +307,7 @@ NamedDecl *Sema::FindFirstQualifierInScope(Scope *S, NestedNameSpecifier *NNS) { assert(!Found.isAmbiguous() && "Cannot handle ambiguities here yet"); NamedDecl *Result = Found; - if (isAcceptableNestedNameSpecifier(Context, Result)) + if (isAcceptableNestedNameSpecifier(Result)) return Result; return 0; @@ -406,7 +406,7 @@ Sema::CXXScopeTy *Sema::BuildCXXNestedNameSpecifier(Scope *S, // FIXME: Deal with ambiguities cleanly. NamedDecl *SD = Found; - if (isAcceptableNestedNameSpecifier(Context, SD)) { + if (isAcceptableNestedNameSpecifier(SD)) { if (!ObjectType.isNull() && !ObjectTypeSearchedInScope) { // C++ [basic.lookup.classref]p4: // [...] If the name is found in both contexts, the @@ -425,7 +425,7 @@ Sema::CXXScopeTy *Sema::BuildCXXNestedNameSpecifier(Scope *S, // FIXME: Handle ambiguities in FoundOuter! NamedDecl *OuterDecl = FoundOuter; - if (isAcceptableNestedNameSpecifier(Context, OuterDecl) && + if (isAcceptableNestedNameSpecifier(OuterDecl) && OuterDecl->getCanonicalDecl() != SD->getCanonicalDecl() && (!isa(OuterDecl) || !isa(SD) || !Context.hasSameType( diff --git a/lib/Sema/SemaCodeComplete.cpp b/lib/Sema/SemaCodeComplete.cpp index 2183cfad21..bfee4d8b76 100644 --- a/lib/Sema/SemaCodeComplete.cpp +++ b/lib/Sema/SemaCodeComplete.cpp @@ -34,6 +34,35 @@ void Sema::CodeCompleteMemberReferenceExpr(Scope *S, ExprTy *BaseE, CodeCompleter->CodeCompleteMemberReferenceExpr(S, BaseType, IsArrow); } +void Sema::CodeCompleteTag(Scope *S, unsigned TagSpec) { + if (!CodeCompleter) + return; + + TagDecl::TagKind TK; + switch ((DeclSpec::TST)TagSpec) { + case DeclSpec::TST_enum: + TK = TagDecl::TK_enum; + break; + + case DeclSpec::TST_union: + TK = TagDecl::TK_union; + break; + + case DeclSpec::TST_struct: + TK = TagDecl::TK_struct; + break; + + case DeclSpec::TST_class: + TK = TagDecl::TK_class; + break; + + default: + assert(false && "Unknown type specifier kind in CodeCompleteTag"); + return; + } + CodeCompleter->CodeCompleteTag(S, TK); +} + void Sema::CodeCompleteQualifiedId(Scope *S, const CXXScopeSpec &SS, bool EnteringContext) { if (!SS.getScopeRep() || !CodeCompleter) diff --git a/test/CodeCompletion/tag.c b/test/CodeCompletion/tag.c new file mode 100644 index 0000000000..06a2e20474 --- /dev/null +++ b/test/CodeCompletion/tag.c @@ -0,0 +1,15 @@ +// RUN: clang-cc -fsyntax-only -code-completion-dump=1 %s -o - | FileCheck -check-prefix=CC1 %s && +// RUN: true + +enum X { x }; +enum Y { y }; +struct Z { }; + +void X(); + +void test() { + enum X { x }; + // CHECK-CC1: X : 0 + // CHECK-CC1: Y : 2 + // CHECK-CC1: X : 2 (Hidden) + enum