sammccall created this revision. sammccall added reviewers: aaron.ballman, kadircet. Herald added subscribers: usaxena95, arphaman. sammccall requested review of this revision. Herald added projects: clang, clang-tools-extra. Herald added a subscriber: cfe-commits.
Only the bare name is completed, with no args. For args to be useful we need arg names. These *are* in the tablegen but not currently emitted in usable form, so left this as future work. C++11, C2x, GNU, declspec, MS syntax is supported, with the appropriate spellings of attributes suggested. `#pragma clang attribute` is supported but not terribly useful as we only reach completion if parens are balanced (i.e. the line is not truncated) There's no filtering of which attributes might make sense in this grammatical context (e.g. attached to a function). In code-completion context this is hard to do, and will only work in few cases :-( There's also no filtering by langopts: this is because currently the only way of checking is to try to produce diagnostics, which requires a valid ParsedAttr which is hard to get. This should be fairly simple to fix but requires some tablegen changes to expose the logic without the side-effect. Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D107696 Files: clang-tools-extra/clangd/CodeComplete.cpp clang/include/clang/Parse/Parser.h clang/include/clang/Sema/CodeCompleteConsumer.h clang/include/clang/Sema/ParsedAttr.h clang/include/clang/Sema/Sema.h clang/lib/Frontend/ASTUnit.cpp clang/lib/Parse/ParseDecl.cpp clang/lib/Parse/ParseDeclCXX.cpp clang/lib/Parse/ParsePragma.cpp clang/lib/Sema/CodeCompleteConsumer.cpp clang/lib/Sema/ParsedAttr.cpp clang/lib/Sema/SemaCodeComplete.cpp clang/test/CodeCompletion/attr.cpp clang/tools/libclang/CIndexCodeCompletion.cpp
Index: clang/tools/libclang/CIndexCodeCompletion.cpp =================================================================== --- clang/tools/libclang/CIndexCodeCompletion.cpp +++ clang/tools/libclang/CIndexCodeCompletion.cpp @@ -541,6 +541,7 @@ case CodeCompletionContext::CCC_MacroName: case CodeCompletionContext::CCC_PreprocessorExpression: case CodeCompletionContext::CCC_PreprocessorDirective: + case CodeCompletionContext::CCC_Attribute: case CodeCompletionContext::CCC_TypeQualifiers: { //Only Clang results should be accepted, so we'll set all of the other //context bits to 0 (i.e. the empty set) Index: clang/test/CodeCompletion/attr.cpp =================================================================== --- /dev/null +++ clang/test/CodeCompletion/attr.cpp @@ -0,0 +1,53 @@ +int a [[gnu::used]]; +// RUN: %clang_cc1 -code-completion-at=%s:1:9 %s | FileCheck --check-prefix=STD %s +// STD: COMPLETION: carries_dependency +// STD: COMPLETION: clang::convergent +// STD-NOT: COMPLETION: convergent +// STD: COMPLETION: gnu::used +// STD-NOT: COMPLETION: used +// RUN: %clang_cc1 -code-completion-at=%s:1:14 %s | FileCheck --check-prefix=STD-NS %s +// STD-NS-NOT: COMPLETION: carries_dependency +// STD-NS-NOT: COMPLETION: clang::convergent +// STD-NS-NOT: COMPLETION: convergent +// STD-NS-NOT: COMPLETION: gnu::used +// STD-NS: COMPLETION: used + +int b [[using gnu: used]]; +// RUN: %clang_cc1 -code-completion-at=%s:15:15 %s | FileCheck --check-prefix=STD-USING %s +// STD-USING-NOT: COMPLETION: carries_dependency +// STD-USING: COMPLETION: clang +// STD-USING-NOT: COMPLETION: clang:: +// STD-USING-NOT: COMPLETION: gnu:: +// STD-USING: COMPLETION: gnu +// RUN: %clang_cc1 -code-completion-at=%s:15:20 %s | FileCheck --check-prefix=STD-NS %s + +int c __attribute__((used)); +// RUN: %clang_cc1 -code-completion-at=%s:24:22 %s | FileCheck --check-prefix=GNU %s +// GNU: COMPLETION: carries_dependency +// GNU-NOT: COMPLETION: clang::convergent +// GNU: COMPLETION: convergent +// GNU-NOT: COMPLETION: gnu::used +// GNU: COMPLETION: used + +#pragma clang attribute push (__attribute__((internal_linkage)), apply_to=variable) +int d; +#pragma clang attribute pop +// RUN: %clang_cc1 -code-completion-at=%s:32:46 %s | FileCheck --check-prefix=PRAGMA %s +// PRAGMA: internal_linkage + +int __declspec(thread) e; +// RUN: %clang_cc1 -fms-extensions -code-completion-at=%s:38:16 %s | FileCheck --check-prefix=DS %s +// DS-NOT: COMPLETION: clang::convergent +// DS-NOT: COMPLETION: convergent +// DS: COMPLETION: thread +// DS-NOT: COMPLETION: used +// DS: COMPLETION: uuid + +[uuid("123e4567-e89b-12d3-a456-426614174000")] struct f; +// RUN: %clang_cc1 -fms-extensions -code-completion-at=%s:46:2 %s | FileCheck --check-prefix=MS %s +// MS-NOT: COMPLETION: clang::convergent +// MS-NOT: COMPLETION: convergent +// MS-NOT: COMPLETION: thread +// MS-NOT: COMPLETION: used +// MS: COMPLETION: uuid + Index: clang/lib/Sema/SemaCodeComplete.cpp =================================================================== --- clang/lib/Sema/SemaCodeComplete.cpp +++ clang/lib/Sema/SemaCodeComplete.cpp @@ -23,6 +23,7 @@ #include "clang/AST/QualTypeNames.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/AST/Type.h" +#include "clang/Basic/AttributeCommonInfo.h" #include "clang/Basic/CharInfo.h" #include "clang/Basic/OperatorKinds.h" #include "clang/Basic/Specifiers.h" @@ -34,6 +35,7 @@ #include "clang/Sema/Designator.h" #include "clang/Sema/Lookup.h" #include "clang/Sema/Overload.h" +#include "clang/Sema/ParsedAttr.h" #include "clang/Sema/Scope.h" #include "clang/Sema/ScopeInfo.h" #include "clang/Sema/Sema.h" @@ -4335,6 +4337,51 @@ Results.data(), Results.size()); } +void Sema::CodeCompleteAttribute(AttributeCommonInfo::Syntax Syntax, + AttributeCompletion Completion, + const IdentifierInfo *Scope) { + if (Completion == AttributeCompletion::None) + return; + ResultBuilder Results(*this, CodeCompleter->getAllocator(), + CodeCompleter->getCodeCompletionTUInfo(), + CodeCompletionContext::CCC_Attribute); + + llvm::DenseSet<llvm::StringRef> FoundScopes; + auto AddCompletions = [&](const ParsedAttrInfo &A) { + if (A.IsTargetSpecific && !A.existsInTarget(Context.getTargetInfo())) + return; + // FIXME: filter by langopts (diagLangOpts method requires a ParsedAttr) + for (const auto &S : A.Spellings) { + if (S.Syntax != Syntax) + continue; + llvm::StringRef Name = S.NormalizedFullName; + if (Completion == AttributeCompletion::Scope) { + auto Parts = Name.split("::"); + if (!Parts.second.empty() && FoundScopes.insert(Parts.first).second) { + Results.AddResult(CodeCompletionResult( + Results.getAllocator().CopyString(Parts.first))); + } + return; + } + if (Scope && + !(Name.consume_front(Scope->getName()) && Name.consume_front("::"))) + continue; + // FIXME: include the list of arg names (not currently exposed). + // It may be nice to include the Kind so we can look up the docs later. + + Results.AddResult(CodeCompletionResult(Name.data())); + } + }; + + for (const auto *A : ParsedAttrInfo::getAllBuiltin()) + AddCompletions(*A); + for (const auto &Entry : ParsedAttrInfoRegistry::entries()) + AddCompletions(*Entry.instantiate()); + + HandleCodeCompleteResults(this, CodeCompleter, Results.getCompletionContext(), + Results.data(), Results.size()); +} + struct Sema::CodeCompleteExpressionData { CodeCompleteExpressionData(QualType PreferredType = QualType(), bool IsParenthesized = false) Index: clang/lib/Sema/ParsedAttr.cpp =================================================================== --- clang/lib/Sema/ParsedAttr.cpp +++ clang/lib/Sema/ParsedAttr.cpp @@ -145,6 +145,10 @@ return DefaultParsedAttrInfo; } +ArrayRef<const ParsedAttrInfo *> ParsedAttrInfo::getAllBuiltin() { + return AttrInfoMap; +} + unsigned ParsedAttr::getMinArgs() const { return getInfo().NumArgs; } unsigned ParsedAttr::getMaxArgs() const { Index: clang/lib/Sema/CodeCompleteConsumer.cpp =================================================================== --- clang/lib/Sema/CodeCompleteConsumer.cpp +++ clang/lib/Sema/CodeCompleteConsumer.cpp @@ -82,6 +82,7 @@ case CCC_ObjCInterfaceName: case CCC_ObjCCategoryName: case CCC_IncludedFile: + case CCC_Attribute: return false; } @@ -161,6 +162,8 @@ return "ObjCCategoryName"; case CCKind::CCC_IncludedFile: return "IncludedFile"; + case CCKind::CCC_Attribute: + return "Attribute"; case CCKind::CCC_Recovery: return "Recovery"; } Index: clang/lib/Parse/ParsePragma.cpp =================================================================== --- clang/lib/Parse/ParsePragma.cpp +++ clang/lib/Parse/ParsePragma.cpp @@ -1592,6 +1592,15 @@ if (ExpectAndConsume(tok::l_paren, diag::err_expected_lparen_after, "(")) return SkipToEnd(); + // FIXME: The practical usefulness of completion here is limited because + // we only get here if the line has balanced parens. + if (Tok.is(tok::code_completion)) { + cutOffParsing(); + // FIXME: suppress completion of unsupported attributes? + Actions.CodeCompleteAttribute(AttributeCommonInfo::Syntax::AS_GNU); + return SkipToEnd(); + } + if (Tok.isNot(tok::identifier)) { Diag(Tok, diag::err_pragma_attribute_expected_attribute_name); SkipToEnd(); Index: clang/lib/Parse/ParseDeclCXX.cpp =================================================================== --- clang/lib/Parse/ParseDeclCXX.cpp +++ clang/lib/Parse/ParseDeclCXX.cpp @@ -4086,7 +4086,10 @@ /// If a keyword or an alternative token that satisfies the syntactic /// requirements of an identifier is contained in an attribute-token, /// it is considered an identifier. -IdentifierInfo *Parser::TryParseCXX11AttributeIdentifier(SourceLocation &Loc) { +IdentifierInfo * +Parser::TryParseCXX11AttributeIdentifier(SourceLocation &Loc, + Sema::AttributeCompletion Completion, + const IdentifierInfo *Scope) { switch (Tok.getKind()) { default: // Identifiers and keywords have identifier info attached. @@ -4098,6 +4101,13 @@ } return nullptr; + case tok::code_completion: + cutOffParsing(); + Actions.CodeCompleteAttribute(getLangOpts().CPlusPlus ? ParsedAttr::AS_CXX11 + : ParsedAttr::AS_C2x, + Completion, Scope); + return nullptr; + case tok::numeric_constant: { // If we got a numeric constant, check to see if it comes from a macro that // corresponds to the predefined __clang__ macro. If it does, warn the user @@ -4373,7 +4383,8 @@ : diag::ext_using_attribute_ns); ConsumeToken(); - CommonScopeName = TryParseCXX11AttributeIdentifier(CommonScopeLoc); + CommonScopeName = TryParseCXX11AttributeIdentifier( + CommonScopeLoc, Sema::AttributeCompletion::Scope); if (!CommonScopeName) { Diag(Tok.getLocation(), diag::err_expected) << tok::identifier; SkipUntil(tok::r_square, tok::colon, StopBeforeMatch); @@ -4403,7 +4414,8 @@ SourceLocation ScopeLoc, AttrLoc; IdentifierInfo *ScopeName = nullptr, *AttrName = nullptr; - AttrName = TryParseCXX11AttributeIdentifier(AttrLoc); + AttrName = TryParseCXX11AttributeIdentifier( + AttrLoc, Sema::AttributeCompletion::Attribute, CommonScopeName); if (!AttrName) // Break out to the "expected ']'" diagnostic. break; @@ -4413,7 +4425,8 @@ ScopeName = AttrName; ScopeLoc = AttrLoc; - AttrName = TryParseCXX11AttributeIdentifier(AttrLoc); + AttrName = TryParseCXX11AttributeIdentifier( + AttrLoc, Sema::AttributeCompletion::Attribute, ScopeName); if (!AttrName) { Diag(Tok.getLocation(), diag::err_expected) << tok::identifier; SkipUntil(tok::r_square, tok::comma, StopAtSemi | StopBeforeMatch); @@ -4628,7 +4641,15 @@ // Skip most ms attributes except for a specific list. while (true) { - SkipUntil(tok::r_square, tok::identifier, StopAtSemi | StopBeforeMatch); + SkipUntil(tok::r_square, tok::identifier, + StopAtSemi | StopBeforeMatch | StopAtCodeCompletion); + if (Tok.is(tok::code_completion)) { + cutOffParsing(); + Actions.CodeCompleteAttribute(AttributeCommonInfo::AS_Microsoft, + Sema::AttributeCompletion::Attribute, + /*Scope=*/nullptr); + break; + } if (Tok.isNot(tok::identifier)) // ']', but also eof break; if (Tok.getIdentifierInfo()->getName() == "uuid") Index: clang/lib/Parse/ParseDecl.cpp =================================================================== --- clang/lib/Parse/ParseDecl.cpp +++ clang/lib/Parse/ParseDecl.cpp @@ -195,6 +195,11 @@ // Expect an identifier or declaration specifier (const, int, etc.) if (Tok.isAnnotation()) break; + if (Tok.is(tok::code_completion)) { + cutOffParsing(); + Actions.CodeCompleteAttribute(AttributeCommonInfo::Syntax::AS_GNU); + break; + } IdentifierInfo *AttrName = Tok.getIdentifierInfo(); if (!AttrName) break; @@ -714,6 +719,12 @@ if (TryConsumeToken(tok::comma)) continue; + if (Tok.is(tok::code_completion)) { + cutOffParsing(); + Actions.CodeCompleteAttribute(AttributeCommonInfo::AS_Declspec); + return; + } + // We expect either a well-known identifier or a generic string. Anything // else is a malformed declspec. bool IsString = Tok.getKind() == tok::string_literal; Index: clang/lib/Frontend/ASTUnit.cpp =================================================================== --- clang/lib/Frontend/ASTUnit.cpp +++ clang/lib/Frontend/ASTUnit.cpp @@ -1989,6 +1989,7 @@ case CodeCompletionContext::CCC_ObjCClassMessage: case CodeCompletionContext::CCC_ObjCCategoryName: case CodeCompletionContext::CCC_IncludedFile: + case CodeCompletionContext::CCC_Attribute: case CodeCompletionContext::CCC_NewName: // We're looking for nothing, or we're looking for names that cannot // be hidden. Index: clang/include/clang/Sema/Sema.h =================================================================== --- clang/include/clang/Sema/Sema.h +++ clang/include/clang/Sema/Sema.h @@ -12377,6 +12377,15 @@ const VirtSpecifiers *VS = nullptr); void CodeCompleteBracketDeclarator(Scope *S); void CodeCompleteCase(Scope *S); + enum class AttributeCompletion { + Attribute, + Scope, + None, + }; + void CodeCompleteAttribute( + AttributeCommonInfo::Syntax Syntax, + AttributeCompletion Completion = AttributeCompletion::Attribute, + const IdentifierInfo *Scope = nullptr); /// Determines the preferred type of the current function argument, by /// examining the signatures of all possible overloads. /// Returns null if unknown or ambiguous, or if code completion is off. Index: clang/include/clang/Sema/ParsedAttr.h =================================================================== --- clang/include/clang/Sema/ParsedAttr.h +++ clang/include/clang/Sema/ParsedAttr.h @@ -125,6 +125,7 @@ } static const ParsedAttrInfo &get(const AttributeCommonInfo &A); + static ArrayRef<const ParsedAttrInfo *> getAllBuiltin(); }; typedef llvm::Registry<ParsedAttrInfo> ParsedAttrInfoRegistry; Index: clang/include/clang/Sema/CodeCompleteConsumer.h =================================================================== --- clang/include/clang/Sema/CodeCompleteConsumer.h +++ clang/include/clang/Sema/CodeCompleteConsumer.h @@ -329,6 +329,9 @@ /// Code completion inside the filename part of a #include directive. CCC_IncludedFile, + /// Code completion of an attribute name. + CCC_Attribute, + /// An unknown context, in which we are recovering from a parsing /// error and don't know which completions we should give. CCC_Recovery Index: clang/include/clang/Parse/Parser.h =================================================================== --- clang/include/clang/Parse/Parser.h +++ clang/include/clang/Parse/Parser.h @@ -2834,7 +2834,10 @@ SourceLocation ScopeLoc, CachedTokens &OpenMPTokens); - IdentifierInfo *TryParseCXX11AttributeIdentifier(SourceLocation &Loc); + IdentifierInfo *TryParseCXX11AttributeIdentifier( + SourceLocation &Loc, + Sema::AttributeCompletion Completion = Sema::AttributeCompletion::None, + const IdentifierInfo *EnclosingScope = nullptr); void MaybeParseMicrosoftAttributes(ParsedAttributes &attrs, SourceLocation *endLoc = nullptr) { Index: clang-tools-extra/clangd/CodeComplete.cpp =================================================================== --- clang-tools-extra/clangd/CodeComplete.cpp +++ clang-tools-extra/clangd/CodeComplete.cpp @@ -709,6 +709,7 @@ case CodeCompletionContext::CCC_ObjCInstanceMessage: case CodeCompletionContext::CCC_ObjCClassMessage: case CodeCompletionContext::CCC_IncludedFile: + case CodeCompletionContext::CCC_Attribute: // FIXME: Provide identifier based completions for the following contexts: case CodeCompletionContext::CCC_Other: // Be conservative. case CodeCompletionContext::CCC_NaturalLanguage:
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits