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 @@ -715,6 +715,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 @@ -123,6 +123,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 @@ -4105,7 +4105,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. @@ -4117,6 +4120,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 @@ -4392,7 +4402,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); @@ -4422,7 +4433,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; @@ -4432,7 +4444,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); @@ -4647,7 +4660,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,131 @@ Results.data(), Results.size()); } +static const char *underscoreAttrScope(llvm::StringRef Scope) { + if (Scope == "clang") + return "_Clang"; + if (Scope == "gnu") + return "__gnu__"; + return nullptr; +} + +static const char *noUnderscoreAttrScope(llvm::StringRef Scope) { + if (Scope == "_Clang") + return "clang"; + if (Scope == "__gnu__") + return "gnu"; + return nullptr; +} + +void Sema::CodeCompleteAttribute(AttributeCommonInfo::Syntax Syntax, + AttributeCompletion Completion, + const IdentifierInfo *InScope) { + if (Completion == AttributeCompletion::None) + return; + ResultBuilder Results(*this, CodeCompleter->getAllocator(), + CodeCompleter->getCodeCompletionTUInfo(), + CodeCompletionContext::CCC_Attribute); + + // We're going to iterate over the normalized spellings of the attribute. + // These don't include "underscore guarding": the normalized spelling is + // clang::foo but you can also write _Clang::__foo__. + // + // (Clang supports a mix like clang::__foo__ but we won't suggest it: either + // you care about clashing with macros or you don't). + // + // So if we're already in a scope, we determine its canonical spellings + // (for comparison with normalized attr spelling) and remember whether it was + // underscore-guarded (so we know how to spell contained attributes). + llvm::StringRef InScopeName; + bool InScopeUnderscore = false; + if (InScope) { + InScopeName = InScope->getName(); + if (const char *NoUnderscore = noUnderscoreAttrScope(InScopeName)) { + InScopeName = NoUnderscore; + InScopeUnderscore = true; + } + } + bool SyntaxSupportsGuards = Syntax == AttributeCommonInfo::AS_GNU || + Syntax == AttributeCommonInfo::AS_CXX11 || + Syntax == AttributeCommonInfo::AS_C2x; + + 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; + llvm::StringRef Scope; + if ((Syntax == AttributeCommonInfo::AS_CXX11 || + Syntax == AttributeCommonInfo::AS_C2x)) { + std::tie(Scope, Name) = Name.split("::"); + if (Name.empty()) // oops, unscoped + std::swap(Name, Scope); + } + + // Do we just want a list of scopes rather than attributes? + if (Completion == AttributeCompletion::Scope) { + // Make sure to emit each scope only once. + if (!Scope.empty() && FoundScopes.insert(Scope).second) { + Results.AddResult( + CodeCompletionResult(Results.getAllocator().CopyString(Scope))); + // Include alternate form (__gnu__ instead of gnu). + if (const char *Scope2 = underscoreAttrScope(Scope)) + Results.AddResult(CodeCompletionResult(Scope2)); + } + continue; + } + + // If a scope was specified, it must match but we don't need to print it. + if (!InScopeName.empty()) { + if (Scope != InScopeName) + continue; + Scope = ""; + } + + // Generate the non-underscore-guarded result. + // Note this is (a suffix of) the NormalizedFullName, no need to copy. + // If an underscore-guarded scope was specified, only the + // underscore-guarded attribute name is relevant. + if (!InScopeUnderscore) + Results.AddResult(Scope.empty() ? Name.data() : S.NormalizedFullName); + + // Generate the underscore-guarded version, for syntaxes that support it. + // We skip this if the scope was already spelled and not guarded, or + // we must spell it and can't guard it. + if (!(InScope && !InScopeUnderscore) && SyntaxSupportsGuards) { + llvm::SmallString<32> Guarded; + if (!Scope.empty()) { + const char *GuardedScope = underscoreAttrScope(Scope); + if (!GuardedScope) + continue; + Guarded.append(GuardedScope); + Guarded.append("::"); + } + Guarded.append("__"); + Guarded.append(Name); + Guarded.append("__"); + Results.AddResult( + CodeCompletionResult(Results.getAllocator().CopyString(Guarded))); + } + + // 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. + } + }; + + 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,88 @@ +int a [[gnu::used]]; +// RUN: %clang_cc1 -code-completion-at=%s:1:9 %s | FileCheck --check-prefix=STD %s +// STD: COMPLETION: __carries_dependency__ +// STD-NOT: COMPLETION: __convergent__ +// STD: COMPLETION: __gnu__::__used__ +// STD-NOT: COMPLETION: __gnu__::used +// STD-NOT: COMPLETION: __used__ +// STD: COMPLETION: _Clang::__convergent__ +// STD: COMPLETION: carries_dependency +// STD: COMPLETION: clang::convergent +// STD-NOT: COMPLETION: convergent +// STD-NOT: COMPLETION: gnu::__used__ +// 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: __used__ +// 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 [[__gnu__::used]]; +// RUN: %clang_cc1 -code-completion-at=%s:22:18 %s | FileCheck --check-prefix=STD-NSU %s +// STD-NSU: COMPLETION: __used__ +// STD-NSU-NOT: COMPLETION: used + +int c [[using gnu: used]]; +// RUN: %clang_cc1 -code-completion-at=%s:27:15 %s | FileCheck --check-prefix=STD-USING %s +// STD-USING: COMPLETION: __gnu__ +// STD-USING: COMPLETION: _Clang +// 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:27:20 %s | FileCheck --check-prefix=STD-NS %s + +int d __attribute__((used)); +// RUN: %clang_cc1 -code-completion-at=%s:38:22 %s | FileCheck --check-prefix=GNU %s +// GNU: COMPLETION: __carries_dependency__ +// GNU: COMPLETION: __convergent__ +// GNU-NOT: COMPLETION: __gnu__::__used__ +// GNU: COMPLETION: __used__ +// GNU-NOT: COMPLETION: _Clang::__convergent__ +// 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 e; +#pragma clang attribute pop +// RUN: %clang_cc1 -code-completion-at=%s:51:46 %s | FileCheck --check-prefix=PRAGMA %s +// PRAGMA: internal_linkage + +#ifdef MS_EXT +int __declspec(thread) f; +// RUN: %clang_cc1 -fms-extensions -DMS_EXT -code-completion-at=%s:58:16 %s | FileCheck --check-prefix=DS %s +// DS-NOT: COMPLETION: __convergent__ +// DS-NOT: COMPLETION: __used__ +// 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 g; +// RUN: %clang_cc1 -fms-extensions -DMS_EXT -code-completion-at=%s:68:2 %s | FileCheck --check-prefix=MS %s +// MS-NOT: COMPLETION: __uuid__ +// MS-NOT: COMPLETION: clang::convergent +// MS-NOT: COMPLETION: convergent +// MS-NOT: COMPLETION: thread +// MS-NOT: COMPLETION: used +// MS: COMPLETION: uuid +#endif // MS_EXT + +void foo() { + [[omp::sequence(directive(parallel), directive(critical))]] + {} +} +// FIXME: support for omp attributes would be nice. +// RUN: %clang_cc1 -fopenmp -code-completion-at=%s:79:5 %s | FileCheck --check-prefix=OMP-NS --allow-empty %s +// OMP-NS-NOT: omp +// RUN: %clang_cc1 -fopenmp -code-completion-at=%s:79:10 %s | FileCheck --check-prefix=OMP-ATTR --allow-empty %s +// OMP-ATTR-NOT: sequence +// RUN: %clang_cc1 -fopenmp -code-completion-at=%s:79:19 %s | FileCheck --check-prefix=OMP-NESTED --allow-empty %s +// OMP-NESTED-NOT: directive 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)