[clang][Sema] Add CodeCompletionContext::CCC_ObjCClassForwardDecl

- Use this new context in Sema to limit completions to seen ObjC class
  names

- Use this new context in clangd to disable include insertions when
  completing ObjC forward decls

Reviewed By: kadircet

Differential Revision: https://reviews.llvm.org/D150978
This commit is contained in:
David Goldman 2023-06-27 14:16:13 -04:00
parent 9fdde69f72
commit a42ce094d9
9 changed files with 72 additions and 8 deletions

View File

@ -214,7 +214,8 @@ struct CompletionCandidate {
// Returns a token identifying the overload set this is part of.
// 0 indicates it's not part of any overload set.
size_t overloadSet(const CodeCompleteOptions &Opts, llvm::StringRef FileName,
IncludeInserter *Inserter) const {
IncludeInserter *Inserter,
CodeCompletionContext::Kind CCContextKind) const {
if (!Opts.BundleOverloads.value_or(false))
return 0;
@ -223,7 +224,7 @@ struct CompletionCandidate {
// bundle those, so we must resolve the header to be included here.
std::string HeaderForHash;
if (Inserter) {
if (auto Header = headerToInsertIfAllowed(Opts)) {
if (auto Header = headerToInsertIfAllowed(Opts, CCContextKind)) {
if (auto HeaderFile = toHeaderFile(*Header, FileName)) {
if (auto Spelled =
Inserter->calculateIncludePath(*HeaderFile, FileName))
@ -271,11 +272,21 @@ struct CompletionCandidate {
return 0;
}
bool contextAllowsHeaderInsertion(CodeCompletionContext::Kind Kind) const {
// Explicitly disable insertions for forward declarations since they don't
// reference the declaration.
if (Kind == CodeCompletionContext::CCC_ObjCClassForwardDecl)
return false;
return true;
}
// The best header to include if include insertion is allowed.
std::optional<llvm::StringRef>
headerToInsertIfAllowed(const CodeCompleteOptions &Opts) const {
headerToInsertIfAllowed(const CodeCompleteOptions &Opts,
CodeCompletionContext::Kind ContextKind) const {
if (Opts.InsertIncludes == CodeCompleteOptions::NeverInsert ||
RankedIncludeHeaders.empty())
RankedIncludeHeaders.empty() ||
!contextAllowsHeaderInsertion(ContextKind))
return std::nullopt;
if (SemaResult && SemaResult->Declaration) {
// Avoid inserting new #include if the declaration is found in the current
@ -401,7 +412,8 @@ struct CodeCompletionBuilder {
std::move(*Spelled),
Includes.shouldInsertInclude(*ResolvedDeclaring, *ResolvedInserted));
};
bool ShouldInsert = C.headerToInsertIfAllowed(Opts).has_value();
bool ShouldInsert =
C.headerToInsertIfAllowed(Opts, ContextKind).has_value();
Symbol::IncludeDirective Directive = insertionDirective(Opts);
// Calculate include paths and edits for all possible headers.
for (const auto &Inc : C.RankedIncludeHeaders) {
@ -780,6 +792,7 @@ bool contextAllowsIndex(enum CodeCompletionContext::Kind K) {
case CodeCompletionContext::CCC_ObjCInterfaceName:
case CodeCompletionContext::CCC_Symbol:
case CodeCompletionContext::CCC_SymbolOrNewName:
case CodeCompletionContext::CCC_ObjCClassForwardDecl:
return true;
case CodeCompletionContext::CCC_OtherWithMacros:
case CodeCompletionContext::CCC_DotMemberAccess:
@ -1422,6 +1435,10 @@ bool includeSymbolFromIndex(CodeCompletionContext::Kind Kind,
else if (Kind == CodeCompletionContext::CCC_ObjCProtocolName)
// Don't show anything else in ObjC protocol completions.
return false;
if (Kind == CodeCompletionContext::CCC_ObjCClassForwardDecl)
return Sym.SymInfo.Kind == index::SymbolKind::Class &&
Sym.SymInfo.Lang == index::SymbolLanguage::ObjC;
return true;
}
@ -1832,8 +1849,8 @@ private:
assert(IdentifierResult);
C.Name = IdentifierResult->Name;
}
if (auto OverloadSet =
C.overloadSet(Opts, FileName, Inserter ? &*Inserter : nullptr)) {
if (auto OverloadSet = C.overloadSet(
Opts, FileName, Inserter ? &*Inserter : nullptr, CCContextKind)) {
auto Ret = BundleLookup.try_emplace(OverloadSet, Bundles.size());
if (Ret.second)
Bundles.emplace_back();

View File

@ -3434,6 +3434,20 @@ TEST(CompletionTest, ObjectiveCCategoryFromIndexIgnored) {
EXPECT_THAT(Results.Completions, IsEmpty());
}
TEST(CompletionTest, ObjectiveCForwardDeclFromIndex) {
Symbol FoodClass = objcClass("FoodClass");
FoodClass.IncludeHeaders.emplace_back("\"Foo.h\"", 2, Symbol::Import);
Symbol SymFood = objcProtocol("Food");
auto Results = completions("@class Foo^", {SymFood, FoodClass},
/*Opts=*/{}, "Foo.m");
// Should only give class names without any include insertion.
EXPECT_THAT(Results.Completions,
UnorderedElementsAre(AllOf(named("FoodClass"),
kind(CompletionItemKind::Class),
Not(insertInclude()))));
}
TEST(CompletionTest, CursorInSnippets) {
clangd::CodeCompleteOptions Options;
Options.EnableSnippets = true;

View File

@ -333,7 +333,10 @@ public:
/// An unknown context, in which we are recovering from a parsing
/// error and don't know which completions we should give.
CCC_Recovery
CCC_Recovery,
/// Code completion in a @class forward declaration.
CCC_ObjCClassForwardDecl
};
using VisitedContextSet = llvm::SmallPtrSet<DeclContext *, 8>;

View File

@ -13429,6 +13429,7 @@ public:
ArrayRef<IdentifierLocPair> Protocols);
void CodeCompleteObjCProtocolDecl(Scope *S);
void CodeCompleteObjCInterfaceDecl(Scope *S);
void CodeCompleteObjCClassForwardDecl(Scope *S);
void CodeCompleteObjCSuperclass(Scope *S,
IdentifierInfo *ClassName,
SourceLocation ClassNameLoc);

View File

@ -322,6 +322,7 @@ static uint64_t getDeclShowContexts(const NamedDecl *ND,
if (ID->getDefinition())
Contexts |= (1LL << CodeCompletionContext::CCC_Expression);
Contexts |= (1LL << CodeCompletionContext::CCC_ObjCInterfaceName);
Contexts |= (1LL << CodeCompletionContext::CCC_ObjCClassForwardDecl);
}
// Deal with tag names.
@ -2028,6 +2029,7 @@ static void CalculateHiddenNames(const CodeCompletionContext &Context,
case CodeCompletionContext::CCC_IncludedFile:
case CodeCompletionContext::CCC_Attribute:
case CodeCompletionContext::CCC_NewName:
case CodeCompletionContext::CCC_ObjCClassForwardDecl:
// We're looking for nothing, or we're looking for names that cannot
// be hidden.
return;

View File

@ -153,6 +153,11 @@ Parser::ParseObjCAtClassDeclaration(SourceLocation atLoc) {
while (true) {
MaybeSkipAttributes(tok::objc_class);
if (Tok.is(tok::code_completion)) {
cutOffParsing();
Actions.CodeCompleteObjCClassForwardDecl(getCurScope());
return Actions.ConvertDeclToDeclGroup(nullptr);
}
if (expectIdentifier()) {
SkipUntil(tok::semi);
return Actions.ConvertDeclToDeclGroup(nullptr);

View File

@ -83,6 +83,7 @@ bool CodeCompletionContext::wantConstructorResults() const {
case CCC_ObjCCategoryName:
case CCC_IncludedFile:
case CCC_Attribute:
case CCC_ObjCClassForwardDecl:
return false;
}
@ -166,6 +167,8 @@ StringRef clang::getCompletionKindString(CodeCompletionContext::Kind Kind) {
return "Attribute";
case CCKind::CCC_Recovery:
return "Recovery";
case CCKind::CCC_ObjCClassForwardDecl:
return "ObjCClassForwardDecl";
}
llvm_unreachable("Invalid CodeCompletionContext::Kind!");
}

View File

@ -8460,6 +8460,24 @@ void Sema::CodeCompleteObjCInterfaceDecl(Scope *S) {
Results.data(), Results.size());
}
void Sema::CodeCompleteObjCClassForwardDecl(Scope *S) {
ResultBuilder Results(*this, CodeCompleter->getAllocator(),
CodeCompleter->getCodeCompletionTUInfo(),
CodeCompletionContext::CCC_ObjCClassForwardDecl);
Results.EnterNewScope();
if (CodeCompleter->includeGlobals()) {
// Add all classes.
AddInterfaceResults(Context.getTranslationUnitDecl(), CurContext, false,
false, Results);
}
Results.ExitScope();
HandleCodeCompleteResults(this, CodeCompleter, Results.getCompletionContext(),
Results.data(), Results.size());
}
void Sema::CodeCompleteObjCSuperclass(Scope *S, IdentifierInfo *ClassName,
SourceLocation ClassNameLoc) {
ResultBuilder Results(*this, CodeCompleter->getAllocator(),

View File

@ -537,6 +537,7 @@ static unsigned long long getContextsForContextKind(
case CodeCompletionContext::CCC_Other:
case CodeCompletionContext::CCC_ObjCInterface:
case CodeCompletionContext::CCC_ObjCImplementation:
case CodeCompletionContext::CCC_ObjCClassForwardDecl:
case CodeCompletionContext::CCC_NewName:
case CodeCompletionContext::CCC_MacroName:
case CodeCompletionContext::CCC_PreprocessorExpression: