Skip to content

Commit

Permalink
[CodeCompletion] Avoid spurious signature help for init-list args
Browse files Browse the repository at this point in the history
Somewhat surprisingly, signature help is emitted as a side-effect of
computing the expected type of a function argument.
The reason is that both actions require enumerating the possible
function signatures and running partial overload resolution, and doing
this twice would be wasteful and complicated.

Change #1: document this, it's subtle :-)

However, sometimes we need to compute the expected type without having
reached the code completion cursor yet - in particular to allow
completion of designators.
eb4ab33 did this but introduced a
regression - it emits signature help in the wrong location as a side-effect.

Change rust-lang#2: only emit signature help if the code completion cursor was reached.

Currently there is PP.isCodeCompletionReached(), but we can't use it
because it's set *after* running code completion.
It'd be nice to set this implicitly when the completion token is lexed,
but ConsumeCodeCompletionToken() makes this complicated.

Change rust-lang#3: call cutOffParsing() *first* when seeing a completion token.

After this, the fact that the Sema::Produce*SignatureHelp() functions
are even more confusing, as they only sometimes do that.
I don't want to rename them in this patch as it's another large
mechanical change, but we should soon.

Change rust-lang#4: prepare to rename ProduceSignatureHelp() to GuessArgumentType() etc.

Differential Revision: https://reviews.llvm.org/D98488
  • Loading branch information
sam-mccall committed Mar 16, 2021
1 parent 5ac3b37 commit 128ce70
Show file tree
Hide file tree
Showing 15 changed files with 144 additions and 84 deletions.
13 changes: 13 additions & 0 deletions clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1253,6 +1253,19 @@ TEST(SignatureHelpTest, Overloads) {
EXPECT_EQ(0, Results.activeParameter);
}

TEST(SignatureHelpTest, OverloadInitListRegression) {
auto Results = signatures(R"cpp(
struct A {int x;};
struct B {B(A);};
void f();
int main() {
B b({1});
f(^);
}
)cpp");
EXPECT_THAT(Results.signatures, UnorderedElementsAre(Sig("f() -> void")));
}

TEST(SignatureHelpTest, DefaultArgs) {
auto Results = signatures(R"cpp(
void bar(int x, int y = 0);
Expand Down
19 changes: 17 additions & 2 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,9 @@ class PreferredTypeBuilder {
/// Clients should be very careful when using this funciton, as it stores a
/// function_ref, clients should make sure all calls to get() with the same
/// location happen while function_ref is alive.
///
/// The callback should also emit signature help as a side-effect, but only
/// if the completion point has been reached.
void enterFunctionArgument(SourceLocation Tok,
llvm::function_ref<QualType()> ComputeType);

Expand All @@ -318,6 +321,12 @@ class PreferredTypeBuilder {
/// Handles all type casts, including C-style cast, C++ casts, etc.
void enterTypeCast(SourceLocation Tok, QualType CastType);

/// Get the expected type associated with this location, if any.
///
/// If the location is a function argument, determining the expected type
/// involves considering all function overloads and the arguments so far.
/// In this case, signature help for these function overloads will be reported
/// as a side-effect (only if the completion point has been reached).
QualType get(SourceLocation Tok) const {
if (!Enabled || Tok != ExpectedLoc)
return QualType();
Expand Down Expand Up @@ -12216,8 +12225,14 @@ class Sema final {
const VirtSpecifiers *VS = nullptr);
void CodeCompleteBracketDeclarator(Scope *S);
void CodeCompleteCase(Scope *S);
/// Reports signatures for a call to CodeCompleteConsumer and returns the
/// preferred type for the current argument. Returned type can be null.
/// 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.
///
/// If the code completion point has been reached, also reports the function
/// signatures that were considered.
///
/// FIXME: rename to GuessCallArgumentType to reduce confusion.
QualType ProduceCallSignatureHelp(Scope *S, Expr *Fn, ArrayRef<Expr *> Args,
SourceLocation OpenParLoc);
QualType ProduceConstructorSignatureHelp(Scope *S, QualType Type,
Expand Down
4 changes: 2 additions & 2 deletions clang/lib/Lex/PPDirectives.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -441,9 +441,9 @@ void Preprocessor::SkipExcludedConditionalBlock(SourceLocation HashTokenLoc,
CurLexer->Lex(Tok);

if (Tok.is(tok::code_completion)) {
setCodeCompletionReached();
if (CodeComplete)
CodeComplete->CodeCompleteInConditionalExclusion();
setCodeCompletionReached();
continue;
}

Expand Down Expand Up @@ -966,10 +966,10 @@ void Preprocessor::HandleDirective(Token &Result) {
case tok::eod:
return; // null directive.
case tok::code_completion:
setCodeCompletionReached();
if (CodeComplete)
CodeComplete->CodeCompleteDirective(
CurPPLexer->getConditionalStackDepth() > 0);
setCodeCompletionReached();
return;
case tok::numeric_constant: // # 7 GNU line marker directive.
if (getLangOpts().AsmPreprocessor)
Expand Down
4 changes: 2 additions & 2 deletions clang/lib/Lex/Preprocessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -442,15 +442,15 @@ bool Preprocessor::SetCodeCompletionPoint(const FileEntry *File,

void Preprocessor::CodeCompleteIncludedFile(llvm::StringRef Dir,
bool IsAngled) {
setCodeCompletionReached();
if (CodeComplete)
CodeComplete->CodeCompleteIncludedFile(Dir, IsAngled);
setCodeCompletionReached();
}

void Preprocessor::CodeCompleteNaturalLanguage() {
setCodeCompletionReached();
if (CodeComplete)
CodeComplete->CodeCompleteNaturalLanguage();
setCodeCompletionReached();
}

/// getSpelling - This method is used to get the spelling of a token into a
Expand Down
19 changes: 12 additions & 7 deletions clang/lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1970,8 +1970,8 @@ Parser::DeclGroupPtrTy Parser::ParseDeclGroup(ParsingDeclSpec &DS,
// Check to see if we have a function *definition* which must have a body.
if (D.isFunctionDeclarator()) {
if (Tok.is(tok::equal) && NextToken().is(tok::code_completion)) {
Actions.CodeCompleteAfterFunctionEquals(D);
cutOffParsing();
Actions.CodeCompleteAfterFunctionEquals(D);
return nullptr;
}
// Look at the next token to make sure that this isn't a function
Expand Down Expand Up @@ -2310,9 +2310,9 @@ Decl *Parser::ParseDeclarationAfterDeclaratorAndAttributes(
InitializerScopeRAII InitScope(*this, D, ThisDecl);

if (Tok.is(tok::code_completion)) {
cutOffParsing();
Actions.CodeCompleteInitializer(getCurScope(), ThisDecl);
Actions.FinalizeDeclaration(ThisDecl);
cutOffParsing();
return nullptr;
}

Expand Down Expand Up @@ -3090,10 +3090,11 @@ void Parser::ParseDeclarationSpecifiers(DeclSpec &DS,
= DSContext == DeclSpecContext::DSC_top_level ||
(DSContext == DeclSpecContext::DSC_class && DS.isFriendSpecified());

cutOffParsing();
Actions.CodeCompleteDeclSpec(getCurScope(), DS,
AllowNonIdentifiers,
AllowNestedNameSpecifiers);
return cutOffParsing();
return;
}

if (getCurScope()->getFnParent() || getCurScope()->getBlockParent())
Expand All @@ -3106,8 +3107,9 @@ void Parser::ParseDeclarationSpecifiers(DeclSpec &DS,
else if (CurParsedObjCImpl)
CCC = Sema::PCC_ObjCImplementation;

cutOffParsing();
Actions.CodeCompleteOrdinaryName(getCurScope(), CCC);
return cutOffParsing();
return;
}

case tok::coloncolon: // ::foo::bar
Expand Down Expand Up @@ -4362,8 +4364,9 @@ void Parser::ParseEnumSpecifier(SourceLocation StartLoc, DeclSpec &DS,
// Parse the tag portion of this.
if (Tok.is(tok::code_completion)) {
// Code completion for an enum name.
cutOffParsing();
Actions.CodeCompleteTag(getCurScope(), DeclSpec::TST_enum);
return cutOffParsing();
return;
}

// If attributes exist after tag, parse them.
Expand Down Expand Up @@ -5457,11 +5460,12 @@ void Parser::ParseTypeQualifierListOpt(

switch (Tok.getKind()) {
case tok::code_completion:
cutOffParsing();
if (CodeCompletionHandler)
(*CodeCompletionHandler)();
else
Actions.CodeCompleteTypeQualifiers(DS);
return cutOffParsing();
return;

case tok::kw_const:
isInvalid = DS.SetTypeQual(DeclSpec::TQ_const , Loc, PrevSpec, DiagID,
Expand Down Expand Up @@ -6998,8 +7002,9 @@ void Parser::ParseBracketDeclarator(Declarator &D) {
std::move(attrs), T.getCloseLocation());
return;
} else if (Tok.getKind() == tok::code_completion) {
cutOffParsing();
Actions.CodeCompleteBracketDeclarator(getCurScope());
return cutOffParsing();
return;
}

// If valid, this location is the position where we read the 'static' keyword.
Expand Down
16 changes: 9 additions & 7 deletions clang/lib/Parse/ParseDeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ Parser::DeclGroupPtrTy Parser::ParseNamespace(DeclaratorContext Context,
ObjCDeclContextSwitch ObjCDC(*this);

if (Tok.is(tok::code_completion)) {
Actions.CodeCompleteNamespaceDecl(getCurScope());
cutOffParsing();
Actions.CodeCompleteNamespaceDecl(getCurScope());
return nullptr;
}

Expand Down Expand Up @@ -283,8 +283,8 @@ Decl *Parser::ParseNamespaceAlias(SourceLocation NamespaceLoc,
ConsumeToken(); // eat the '='.

if (Tok.is(tok::code_completion)) {
Actions.CodeCompleteNamespaceAliasDecl(getCurScope());
cutOffParsing();
Actions.CodeCompleteNamespaceAliasDecl(getCurScope());
return nullptr;
}

Expand Down Expand Up @@ -471,8 +471,8 @@ Parser::ParseUsingDirectiveOrDeclaration(DeclaratorContext Context,
SourceLocation UsingLoc = ConsumeToken();

if (Tok.is(tok::code_completion)) {
Actions.CodeCompleteUsing(getCurScope());
cutOffParsing();
Actions.CodeCompleteUsing(getCurScope());
return nullptr;
}

Expand Down Expand Up @@ -525,8 +525,8 @@ Decl *Parser::ParseUsingDirective(DeclaratorContext Context,
SourceLocation NamespcLoc = ConsumeToken();

if (Tok.is(tok::code_completion)) {
Actions.CodeCompleteUsingDirective(getCurScope());
cutOffParsing();
Actions.CodeCompleteUsingDirective(getCurScope());
return nullptr;
}

Expand Down Expand Up @@ -1433,8 +1433,9 @@ void Parser::ParseClassSpecifier(tok::TokenKind TagTokKind,

if (Tok.is(tok::code_completion)) {
// Code completion for a struct, class, or union name.
cutOffParsing();
Actions.CodeCompleteTag(getCurScope(), TagType);
return cutOffParsing();
return;
}

// C++03 [temp.explicit] 14.7.2/8:
Expand Down Expand Up @@ -2749,8 +2750,8 @@ Parser::ParseCXXClassMemberDeclaration(AccessSpecifier AS,
else if (KW.is(tok::kw_delete))
DefinitionKind = FunctionDefinitionKind::Deleted;
else if (KW.is(tok::code_completion)) {
Actions.CodeCompleteAfterFunctionEquals(DeclaratorInfo);
cutOffParsing();
Actions.CodeCompleteAfterFunctionEquals(DeclaratorInfo);
return nullptr;
}
}
Expand Down Expand Up @@ -3498,9 +3499,10 @@ void Parser::ParseConstructorInitializer(Decl *ConstructorDecl) {

do {
if (Tok.is(tok::code_completion)) {
cutOffParsing();
Actions.CodeCompleteConstructorInitializer(ConstructorDecl,
MemInitializers);
return cutOffParsing();
return;
}

MemInitResult MemInit = ParseMemInitializer(ConstructorDecl);
Expand Down
17 changes: 9 additions & 8 deletions clang/lib/Parse/ParseExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,9 @@ Parser::ParseExpressionWithLeadingExtension(SourceLocation ExtLoc) {
/// Parse an expr that doesn't include (top-level) commas.
ExprResult Parser::ParseAssignmentExpression(TypeCastState isTypeCast) {
if (Tok.is(tok::code_completion)) {
cutOffParsing();
Actions.CodeCompleteExpression(getCurScope(),
PreferredType.get(Tok.getLocation()));
cutOffParsing();
return ExprError();
}

Expand Down Expand Up @@ -1156,9 +1156,9 @@ ExprResult Parser::ParseCastExpression(CastParseKind ParseKind,
ConsumeToken();

if (Tok.is(tok::code_completion) && &II != Ident_super) {
cutOffParsing();
Actions.CodeCompleteObjCClassPropertyRefExpr(
getCurScope(), II, ILoc, ExprStatementTokLoc == ILoc);
cutOffParsing();
return ExprError();
}
// Allow either an identifier or the keyword 'class' (in C++).
Expand Down Expand Up @@ -1724,9 +1724,9 @@ ExprResult Parser::ParseCastExpression(CastParseKind ParseKind,
Res = ParseBlockLiteralExpression();
break;
case tok::code_completion: {
cutOffParsing();
Actions.CodeCompleteExpression(getCurScope(),
PreferredType.get(Tok.getLocation()));
cutOffParsing();
return ExprError();
}
case tok::l_square:
Expand Down Expand Up @@ -1856,9 +1856,9 @@ Parser::ParsePostfixExpressionSuffix(ExprResult LHS) {
if (InMessageExpression)
return LHS;

cutOffParsing();
Actions.CodeCompletePostfixExpression(
getCurScope(), LHS, PreferredType.get(Tok.getLocation()));
cutOffParsing();
return ExprError();

case tok::identifier:
Expand Down Expand Up @@ -2140,12 +2140,12 @@ Parser::ParsePostfixExpressionSuffix(ExprResult LHS) {
CorrectedBase = Base;

// Code completion for a member access expression.
cutOffParsing();
Actions.CodeCompleteMemberReferenceExpr(
getCurScope(), Base, CorrectedBase, OpLoc, OpKind == tok::arrow,
Base && ExprStatementTokLoc == Base->getBeginLoc(),
PreferredType.get(Tok.getLocation()));

cutOffParsing();
return ExprError();
}

Expand Down Expand Up @@ -2778,10 +2778,10 @@ Parser::ParseParenExpression(ParenParseOption &ExprType, bool stopIfCastExpr,
CastTy = nullptr;

if (Tok.is(tok::code_completion)) {
cutOffParsing();
Actions.CodeCompleteExpression(
getCurScope(), PreferredType.get(Tok.getLocation()),
/*IsParenthesized=*/ExprType >= CompoundLiteral);
cutOffParsing();
return ExprError();
}

Expand Down Expand Up @@ -3412,8 +3412,9 @@ Parser::ParseSimpleExpressionList(SmallVectorImpl<Expr*> &Exprs,
/// \endverbatim
void Parser::ParseBlockId(SourceLocation CaretLoc) {
if (Tok.is(tok::code_completion)) {
cutOffParsing();
Actions.CodeCompleteOrdinaryName(getCurScope(), Sema::PCC_Type);
return cutOffParsing();
return;
}

// Parse the specifier-qualifier-list piece.
Expand Down Expand Up @@ -3598,8 +3599,8 @@ Optional<AvailabilitySpec> Parser::ParseAvailabilitySpec() {
} else {
// Parse the platform name.
if (Tok.is(tok::code_completion)) {
Actions.CodeCompleteAvailabilityPlatformName();
cutOffParsing();
Actions.CodeCompleteAvailabilityPlatformName();
return None;
}
if (Tok.isNot(tok::identifier)) {
Expand Down
Loading

0 comments on commit 128ce70

Please sign in to comment.