diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp --- a/clang-tools-extra/clangd/CodeComplete.cpp +++ b/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: diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h --- a/clang/include/clang/Parse/Parser.h +++ b/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) { diff --git a/clang/include/clang/Sema/CodeCompleteConsumer.h b/clang/include/clang/Sema/CodeCompleteConsumer.h --- a/clang/include/clang/Sema/CodeCompleteConsumer.h +++ b/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 diff --git a/clang/include/clang/Sema/ParsedAttr.h b/clang/include/clang/Sema/ParsedAttr.h --- a/clang/include/clang/Sema/ParsedAttr.h +++ b/clang/include/clang/Sema/ParsedAttr.h @@ -125,6 +125,7 @@ } static const ParsedAttrInfo &get(const AttributeCommonInfo &A); + static ArrayRef getAllBuiltin(); }; typedef llvm::Registry ParsedAttrInfoRegistry; diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h --- a/clang/include/clang/Sema/Sema.h +++ b/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. diff --git a/clang/lib/Frontend/ASTUnit.cpp b/clang/lib/Frontend/ASTUnit.cpp --- a/clang/lib/Frontend/ASTUnit.cpp +++ b/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. diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp --- a/clang/lib/Parse/ParseDecl.cpp +++ b/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; diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp --- a/clang/lib/Parse/ParseDeclCXX.cpp +++ b/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") diff --git a/clang/lib/Parse/ParsePragma.cpp b/clang/lib/Parse/ParsePragma.cpp --- a/clang/lib/Parse/ParsePragma.cpp +++ b/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(); diff --git a/clang/lib/Sema/CodeCompleteConsumer.cpp b/clang/lib/Sema/CodeCompleteConsumer.cpp --- a/clang/lib/Sema/CodeCompleteConsumer.cpp +++ b/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"; } diff --git a/clang/lib/Sema/ParsedAttr.cpp b/clang/lib/Sema/ParsedAttr.cpp --- a/clang/lib/Sema/ParsedAttr.cpp +++ b/clang/lib/Sema/ParsedAttr.cpp @@ -145,6 +145,10 @@ return DefaultParsedAttrInfo; } +ArrayRef ParsedAttrInfo::getAllBuiltin() { + return AttrInfoMap; +} + unsigned ParsedAttr::getMinArgs() const { return getInfo().NumArgs; } unsigned ParsedAttr::getMaxArgs() const { diff --git a/clang/lib/Sema/SemaCodeComplete.cpp b/clang/lib/Sema/SemaCodeComplete.cpp --- a/clang/lib/Sema/SemaCodeComplete.cpp +++ b/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 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) diff --git a/clang/test/CodeCompletion/attr.cpp b/clang/test/CodeCompletion/attr.cpp new file mode 100644 --- /dev/null +++ b/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 + diff --git a/clang/tools/libclang/CIndexCodeCompletion.cpp b/clang/tools/libclang/CIndexCodeCompletion.cpp --- a/clang/tools/libclang/CIndexCodeCompletion.cpp +++ b/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)