[CodeComplete] Basic code completion for attribute names.

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.

Differential Revision: https://reviews.llvm.org/D107696
This commit is contained in:
Sam McCall 2021-08-07 17:36:26 +02:00
parent 3b99acbff2
commit ece4e92085
14 changed files with 288 additions and 6 deletions

View File

@ -715,6 +715,7 @@ bool contextAllowsIndex(enum CodeCompletionContext::Kind K) {
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:

View File

@ -2834,7 +2834,10 @@ private:
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) {

View File

@ -329,6 +329,9 @@ public:
/// 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

View File

@ -123,6 +123,7 @@ struct ParsedAttrInfo {
}
static const ParsedAttrInfo &get(const AttributeCommonInfo &A);
static ArrayRef<const ParsedAttrInfo *> getAllBuiltin();
};
typedef llvm::Registry<ParsedAttrInfo> ParsedAttrInfoRegistry;

View File

@ -12377,6 +12377,15 @@ public:
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.

View File

@ -1989,6 +1989,7 @@ static void CalculateHiddenNames(const CodeCompletionContext &Context,
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.

View File

@ -195,6 +195,11 @@ void Parser::ParseGNUAttributes(ParsedAttributesWithRange &Attrs,
// 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 @@ void Parser::ParseMicrosoftDeclSpecs(ParsedAttributes &Attrs,
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;

View File

@ -4105,7 +4105,10 @@ void Parser::PopParsingClass(Sema::ParsingClassState state) {
/// 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 @@ IdentifierInfo *Parser::TryParseCXX11AttributeIdentifier(SourceLocation &Loc) {
}
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 @@ void Parser::ParseCXX11AttributeSpecifierInternal(ParsedAttributes &Attrs,
: 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 @@ void Parser::ParseCXX11AttributeSpecifierInternal(ParsedAttributes &Attrs,
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 @@ void Parser::ParseCXX11AttributeSpecifierInternal(ParsedAttributes &Attrs,
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 @@ void Parser::ParseMicrosoftAttributes(ParsedAttributes &attrs,
// 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")

View File

@ -1592,6 +1592,15 @@ void Parser::HandlePragmaAttribute() {
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();

View File

@ -82,6 +82,7 @@ bool CodeCompletionContext::wantConstructorResults() const {
case CCC_ObjCInterfaceName:
case CCC_ObjCCategoryName:
case CCC_IncludedFile:
case CCC_Attribute:
return false;
}
@ -161,6 +162,8 @@ StringRef clang::getCompletionKindString(CodeCompletionContext::Kind Kind) {
return "ObjCCategoryName";
case CCKind::CCC_IncludedFile:
return "IncludedFile";
case CCKind::CCC_Attribute:
return "Attribute";
case CCKind::CCC_Recovery:
return "Recovery";
}

View File

@ -145,6 +145,10 @@ const ParsedAttrInfo &ParsedAttrInfo::get(const AttributeCommonInfo &A) {
return DefaultParsedAttrInfo;
}
ArrayRef<const ParsedAttrInfo *> ParsedAttrInfo::getAllBuiltin() {
return AttrInfoMap;
}
unsigned ParsedAttr::getMinArgs() const { return getInfo().NumArgs; }
unsigned ParsedAttr::getMaxArgs() const {

View File

@ -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 @@ void Sema::CodeCompleteDeclSpec(Scope *S, DeclSpec &DS,
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<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;
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)

View File

@ -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

View File

@ -541,6 +541,7 @@ static unsigned long long getContextsForContextKind(
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)