This is an archive of the discontinued LLVM Phabricator instance.

[clang][clangd] Improve signature help for variadic functions.
ClosedPublic

Authored by adamcz on Oct 7 2021, 8:36 AM.

Details

Summary

This covers both C-style variadic functions and template variadic w/
parameter packs.

Previously we would return no signatures when working with template
variadic functions once activeParameter reached the position of the
parameter pack (except when it was the only param, then we'd still
show it when no arguments were given). With this commit, we now show
signathure help correctly.

Additionally, this commit fixes the activeParameter value in LSP output
of clangd in the presence of variadic functions (both kinds). LSP does
not allow the activeParamter to be higher than the number of parameters
in the active signature. With "..." or parameter pack being just one
argument, for all but first argument passed to "..." we'd report
incorrect activeParameter value. Clients such as VSCode would then treat
it as 0, as suggested in the spec) and highlight the wrong parameter.

In the future, we should add support for per-signature activeParamter
value, which exists in LSP since 3.16.0. This is not part of this
commit.

Diff Detail

Event Timeline

adamcz created this revision.Oct 7 2021, 8:36 AM
adamcz requested review of this revision.Oct 7 2021, 8:36 AM
Herald added projects: Restricted Project, Restricted Project. · View Herald TranscriptOct 7 2021, 8:36 AM

Thanks, this looks great, just want to check if there are more callsites we can hit while here.

clang/lib/Sema/SemaOverload.cpp
6458

Hmm, there are something like 6 callers of TooManyArguments.
They all check isVariadic, and probably should all be adjusted in this way (except in once case where PartialOverloading is constant false).
This includes one almost-identical caller in this file, for AddMethodCandidate...

It's tempting to say that we should just pass some more args into TooManyArguments and have it do this logic internally. Maybe pass the FunctionType and the FunctionDecl* in, with the latter nullable?
At least this would keep this nice comment in one place.

adamcz updated this revision to Diff 385071.Nov 5 2021, 7:45 AM

addressed review comment

adamcz added a comment.Nov 5 2021, 7:45 AM

Sorry for delay, I got distracted with other stuff. I addressed your comment, partially, and also added more tests and fixed one more issue (see the FunctionType test, it would've failed before).

clang/lib/Sema/SemaOverload.cpp
6458

TooManyArguments is called in 5 places:

  • Here
  • Below, in the method version, which is identical to this case
  • In ProduceCallSignatureHelp, where function can be variadic, but it cannot be a template, so no change needed and we don't even have FunctionDecl (just FunctionType)
  • in checkDirectCallValidity, where PartialOverloading is unconditionally false, none of this matters and we shouldn't be checking isTemplateVariadic()
  • in DeduceTemplateArguments, where we do check isTemplateVariadic() already, but we shouldn't be looking at template instantiation pattern.

additionally there is mergeCandidatesWithResult, where we don't call TooManyArguments, but need this logic too. If we did call TooManyArguments, we'd have to pass PartialOverloading = false, but still check for template variadic stuff.

We only need the pattern logic in 2 out of 5 places and, considering how specific to code completion/sig help it is, and how weird it is too, I'm leaning towards having this be a separate function.

I renamed the helper to something more appropriate and added a call to it in the method case. Let me know if this is better, or if you think I definitely should put this in TooManyArguments() (I'm leaning towards this solution, but don't have a strong opinion on this myself).

Thanks, this looks nice!

Since the code is in Sema, we'd should (also) test this in clang/test/CodeCompletion/ if possible.

clang-tools-extra/clangd/CodeComplete.cpp
928

I'd consider pulling out the logic into a helper named something like paramIndexForArg(int, OverloadCandidate)

just because this function is ridiculously long and this piece of logic encapsulates nicely

929

hmm, now that I think about it, should this conversion from arg-index to param-index be happening here, or done already by sema and the param index passed to ProcessOverloadCandidates?

(The docs say "current arg" but I'm struggling to see how that can really be useful)

945

hmm, if foo() is a non-variadic 0-arg function, we set activeParameter to -1.
Before this patch it's zero which is still weird, but less weird.

adamcz updated this revision to Diff 388167.Nov 18 2021, 5:34 AM
adamcz marked an inline comment as done.

addressed review comments, added new test

Added a slightly awkward clang (not clangd) test. The output is suboptimial, because it's not clangd, but it shows things work as they should.

clang-tools-extra/clangd/CodeComplete.cpp
929

Since ProcessOverloadCandidates is called with multiple candidates CurrentParam wouldn't make sense - which candidate would that be referring to? What if the candidates are:

void fun1(int x, ...);
void fun2(int x, int y, ...);

and CurrentArg is 3, that means for the first signature activeParameter = 1, while for the second one activeParameter = 2.

Right now we do not return this information, but we should (I left a FIXME and intend to follow up, LSP already has the per-signature field).

We could consider moving the CurrentArg out of this function signature and into the OverloadCandidate, so it's per candidate in Sema too. Is that what you're suggesting? I think the current state of things is fine, but if you think I should remove CurrentArg and replace it with a field in OverloadCandidate, let me know.

945

Right, that's unintended. Fixed.

sammccall accepted this revision.Nov 18 2021, 5:58 AM
sammccall added inline comments.
clang-tools-extra/clangd/CodeComplete.cpp
929

Whoops, of course you're right, it's more complicated.

In principle, this seems like it does belong on OverloadCandidate. Doing this as a field seems like a yak-shave, maybe it makes sense to make paramIndexForArg a method on OverloadCandidate though. (And still require CurrentArg as input). Up to you, this is fine as it is.

This revision is now accepted and ready to land.Nov 18 2021, 5:58 AM