Skip to content

Commit

Permalink
[CodeCompletion] Provide placeholders for known attribute arguments
Browse files Browse the repository at this point in the history
Completion now looks more like function/member completion:

  used
  alias(Aliasee)
  abi_tag(Tags...)

Differential Revision: https://reviews.llvm.org/D108109
  • Loading branch information
sam-mccall committed Aug 19, 2021
1 parent 734708e commit cab7c52
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 72 deletions.
2 changes: 2 additions & 0 deletions clang/include/clang/Sema/ParsedAttr.h
Expand Up @@ -67,6 +67,8 @@ struct ParsedAttrInfo {
const char *NormalizedFullName;
};
ArrayRef<Spelling> Spellings;
// The names of the known arguments of this attribute.
ArrayRef<const char *> ArgNames;

ParsedAttrInfo(AttributeCommonInfo::Kind AttrKind =
AttributeCommonInfo::NoSemaHandlerAttribute)
Expand Down
46 changes: 36 additions & 10 deletions clang/lib/Sema/SemaCodeComplete.cpp
Expand Up @@ -4423,33 +4423,59 @@ void Sema::CodeCompleteAttribute(AttributeCommonInfo::Syntax Syntax,
Scope = "";
}

auto Add = [&](llvm::StringRef Scope, llvm::StringRef Name,
bool Underscores) {
CodeCompletionBuilder Builder(Results.getAllocator(),
Results.getCodeCompletionTUInfo());
llvm::SmallString<32> Text;
if (!Scope.empty()) {
Text.append(Scope);
Text.append("::");
}
if (Underscores)
Text.append("__");
Text.append(Name);
if (Underscores)
Text.append("__");
Builder.AddTypedTextChunk(Results.getAllocator().CopyString(Text));

if (!A.ArgNames.empty()) {
Builder.AddChunk(CodeCompletionString::CK_LeftParen, "(");
bool First = true;
for (const char *Arg : A.ArgNames) {
if (!First)
Builder.AddChunk(CodeCompletionString::CK_Comma, ", ");
First = false;
Builder.AddPlaceholderChunk(Arg);
}
Builder.AddChunk(CodeCompletionString::CK_RightParen, ")");
}

Results.AddResult(Builder.TakeString());
};

// 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);
Add(Scope, Name, /*Underscores=*/false);

// 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()) {
if (Scope.empty()) {
Add(Scope, Name, /*Underscores=*/true);
} else {
const char *GuardedScope = underscoreAttrScope(Scope);
if (!GuardedScope)
continue;
Guarded.append(GuardedScope);
Guarded.append("::");
Add(GuardedScope, Name, /*Underscores=*/true);
}
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.
}
};
Expand Down
126 changes: 64 additions & 62 deletions clang/test/CodeCompletion/attr.cpp
@@ -1,91 +1,93 @@
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-NOT: COMPLETION: clang::called_once
// STD: COMPLETION: clang::convergent
// STD-NOT: COMPLETION: convergent
// STD-NOT: COMPLETION: gnu::__used__
// STD: COMPLETION: gnu::used
// STD-NOT: COMPLETION: used
// STD: COMPLETION: Pattern : __carries_dependency__
// STD-NOT: COMPLETION: Pattern : __convergent__
// STD: COMPLETION: Pattern : __gnu__::__used__
// STD-NOT: COMPLETION: Pattern : __gnu__::used
// STD-NOT: COMPLETION: Pattern : __used__
// STD: COMPLETION: Pattern : _Clang::__convergent__
// STD: COMPLETION: Pattern : carries_dependency
// STD-NOT: COMPLETION: Pattern : clang::called_once
// STD: COMPLETION: Pattern : clang::convergent
// STD-NOT: COMPLETION: Pattern : convergent
// STD-NOT: COMPLETION: Pattern : gnu::__used__
// STD: COMPLETION: Pattern : gnu::abi_tag(<#Tags...#>)
// STD: COMPLETION: Pattern : gnu::alias(<#Aliasee#>)
// STD: COMPLETION: Pattern : gnu::used
// STD-NOT: COMPLETION: Pattern : used
// RUN: %clang_cc1 -code-completion-at=%s:1:9 -xobjective-c++ %s | FileCheck --check-prefix=STD-OBJC %s
// STD-OBJC: COMPLETION: clang::called_once
// STD-OBJC: COMPLETION: Pattern : clang::called_once
// 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
// STD-NS-NOT: COMPLETION: Pattern : __used__
// STD-NS-NOT: COMPLETION: Pattern : carries_dependency
// STD-NS-NOT: COMPLETION: Pattern : clang::convergent
// STD-NS-NOT: COMPLETION: Pattern : convergent
// STD-NS-NOT: COMPLETION: Pattern : gnu::used
// STD-NS: COMPLETION: Pattern : used
int b [[__gnu__::used]];
// RUN: %clang_cc1 -code-completion-at=%s:25:18 %s | FileCheck --check-prefix=STD-NSU %s
// STD-NSU: COMPLETION: __used__
// STD-NSU-NOT: COMPLETION: used
// RUN: %clang_cc1 -code-completion-at=%s:27:18 %s | FileCheck --check-prefix=STD-NSU %s
// STD-NSU: COMPLETION: Pattern : __used__
// STD-NSU-NOT: COMPLETION: Pattern : used

int c [[using gnu: used]];
// RUN: %clang_cc1 -code-completion-at=%s:30:15 %s | FileCheck --check-prefix=STD-USING %s
// RUN: %clang_cc1 -code-completion-at=%s:32:15 %s | FileCheck --check-prefix=STD-USING %s
// STD-USING: COMPLETION: __gnu__
// STD-USING: COMPLETION: _Clang
// STD-USING-NOT: COMPLETION: carries_dependency
// STD-USING-NOT: COMPLETION: Pattern : carries_dependency
// STD-USING: COMPLETION: clang
// STD-USING-NOT: COMPLETION: clang::
// STD-USING-NOT: COMPLETION: gnu::
// STD-USING-NOT: COMPLETION: Pattern : clang::
// STD-USING-NOT: COMPLETION: Pattern : gnu::
// STD-USING: COMPLETION: gnu
// RUN: %clang_cc1 -code-completion-at=%s:30:20 %s | FileCheck --check-prefix=STD-NS %s
// RUN: %clang_cc1 -code-completion-at=%s:32:20 %s | FileCheck --check-prefix=STD-NS %s

int d __attribute__((used));
// RUN: %clang_cc1 -code-completion-at=%s:41: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
// RUN: %clang_cc1 -code-completion-at=%s:43:22 %s | FileCheck --check-prefix=GNU %s
// GNU: COMPLETION: Pattern : __carries_dependency__
// GNU: COMPLETION: Pattern : __convergent__
// GNU-NOT: COMPLETION: Pattern : __gnu__::__used__
// GNU: COMPLETION: Pattern : __used__
// GNU-NOT: COMPLETION: Pattern : _Clang::__convergent__
// GNU: COMPLETION: Pattern : carries_dependency
// GNU-NOT: COMPLETION: Pattern : clang::convergent
// GNU: COMPLETION: Pattern : convergent
// GNU-NOT: COMPLETION: Pattern : gnu::used
// GNU: COMPLETION: Pattern : used

#pragma clang attribute push (__attribute__((internal_linkage)), apply_to=variable)
int e;
#pragma clang attribute pop
// RUN: %clang_cc1 -code-completion-at=%s:54:46 %s | FileCheck --check-prefix=PRAGMA %s
// PRAGMA: internal_linkage
// RUN: %clang_cc1 -code-completion-at=%s:56:46 %s | FileCheck --check-prefix=PRAGMA %s
// PRAGMA: COMPLETION: Pattern : internal_linkage

#ifdef MS_EXT
int __declspec(thread) f;
// RUN: %clang_cc1 -fms-extensions -DMS_EXT -code-completion-at=%s:61: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
// RUN: %clang_cc1 -fms-extensions -DMS_EXT -code-completion-at=%s:63:16 %s | FileCheck --check-prefix=DS %s
// DS-NOT: COMPLETION: Pattern : __convergent__
// DS-NOT: COMPLETION: Pattern : __used__
// DS-NOT: COMPLETION: Pattern : clang::convergent
// DS-NOT: COMPLETION: Pattern : convergent
// DS: COMPLETION: Pattern : thread
// DS-NOT: COMPLETION: Pattern : used
// DS: COMPLETION: Pattern : uuid

[uuid("123e4567-e89b-12d3-a456-426614174000")] struct g;
// RUN: %clang_cc1 -fms-extensions -DMS_EXT -code-completion-at=%s:71: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
// RUN: %clang_cc1 -fms-extensions -DMS_EXT -code-completion-at=%s:73:2 %s | FileCheck --check-prefix=MS %s
// MS-NOT: COMPLETION: Pattern : __uuid__
// MS-NOT: COMPLETION: Pattern : clang::convergent
// MS-NOT: COMPLETION: Pattern : convergent
// MS-NOT: COMPLETION: Pattern : thread
// MS-NOT: COMPLETION: Pattern : used
// MS: COMPLETION: Pattern : 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:82:5 %s | FileCheck --check-prefix=OMP-NS --allow-empty %s
// OMP-NS-NOT: omp
// RUN: %clang_cc1 -fopenmp -code-completion-at=%s:82:10 %s | FileCheck --check-prefix=OMP-ATTR --allow-empty %s
// OMP-ATTR-NOT: sequence
// RUN: %clang_cc1 -fopenmp -code-completion-at=%s:82:19 %s | FileCheck --check-prefix=OMP-NESTED --allow-empty %s
// OMP-NESTED-NOT: directive
// RUN: %clang_cc1 -fopenmp -code-completion-at=%s:84:5 %s | FileCheck --check-prefix=OMP-NS --allow-empty %s
// OMP-NS-NOT: COMPLETION: omp
// RUN: %clang_cc1 -fopenmp -code-completion-at=%s:84:10 %s | FileCheck --check-prefix=OMP-ATTR --allow-empty %s
// OMP-ATTR-NOT: COMPLETION: Pattern : sequence
// RUN: %clang_cc1 -fopenmp -code-completion-at=%s:84:19 %s | FileCheck --check-prefix=OMP-NESTED --allow-empty %s
// OMP-NESTED-NOT: COMPLETION: Pattern : directive
23 changes: 23 additions & 0 deletions clang/utils/TableGen/ClangAttrEmitter.cpp
Expand Up @@ -3959,6 +3959,27 @@ void EmitClangAttrParsedAttrImpl(RecordKeeper &Records, raw_ostream &OS) {
}
OS << "};\n";
}

std::vector<std::string> ArgNames;
for (const auto &Arg : Attr.getValueAsListOfDefs("Args")) {
bool UnusedUnset;
if (Arg->getValueAsBitOrUnset("Fake", UnusedUnset))
continue;
ArgNames.push_back(Arg->getValueAsString("Name").str());
for (const auto &Class : Arg->getSuperClasses()) {
if (Class.first->getName().startswith("Variadic")) {
ArgNames.back().append("...");
break;
}
}
}
if (!ArgNames.empty()) {
OS << "static constexpr const char *" << I->first << "ArgNames[] = {\n";
for (const auto &N : ArgNames)
OS << '"' << N << "\",";
OS << "};\n";
}

OS << "struct ParsedAttrInfo" << I->first
<< " final : public ParsedAttrInfo {\n";
OS << " ParsedAttrInfo" << I->first << "() {\n";
Expand All @@ -3980,6 +4001,8 @@ void EmitClangAttrParsedAttrImpl(RecordKeeper &Records, raw_ostream &OS) {
OS << PragmaAttributeSupport.isAttributedSupported(*I->second) << ";\n";
if (!Spellings.empty())
OS << " Spellings = " << I->first << "Spellings;\n";
if (!ArgNames.empty())
OS << " ArgNames = " << I->first << "ArgNames;\n";
OS << " }\n";
GenerateAppertainsTo(Attr, OS);
GenerateMutualExclusionsChecks(Attr, Records, OS, MergeDeclOS, MergeStmtOS);
Expand Down

0 comments on commit cab7c52

Please sign in to comment.