This is an archive of the discontinued LLVM Phabricator instance.

[clangd] Improve hover on arguments to function call
ClosedPublic

Authored by adamcz on Jun 4 2020, 8:24 AM.

Details

Summary

In cases like:

foo(a, ^b);

We now additionally show the name and type of the parameter to foo that
corresponds that "b" is passed as.

The name should help with understanding what it's used for and type can
be useful to find out if call to foo() can mutate variable "b" or not
(i.e. if it is pass by value, reference, const reference, etc).

Diff Detail

Event Timeline

adamcz created this revision.Jun 4 2020, 8:24 AM
Herald added a project: Restricted Project. · View Herald TranscriptJun 4 2020, 8:24 AM
kadircet marked an inline comment as done.Jun 4 2020, 1:06 PM

thanks! that looks really useful, especially knowing when a parameter is being passed as a ref/val.

clang-tools-extra/clangd/Hover.cpp
292

why not return a HoverInfo::Param instead ?

520

umm, is this a fixme or a leftover? I suppose it is from a previous version of the patch.

775

nit: move into anonymous namespace

777

nit: extract N->outerImplicit() to a variable, it seems to be repeated along.

790

i don't think this is necessary, we are baling out if FD->getNumParams() <= I anyways.

793

nit: put this before arg index finding logic and do an early exit:

if(!CE) return;
auto *FD = CE->getDirectCallee();
if(!FD || FD->isOverloaded || variadic..) return;

after doing this you can also change the arg index finding logic to:

auto *SelectedArg = N->outerImplicit().ASTNode.get<Expr>();
if(!SelectedParam) return; // you can also move this above other exit conditions.
for (unsigned I = 0; I < min(CE->getNumArgs, FD->getNumParams())...) { // I suppose these two can be different in presence of default params ?
  if (CE->getArg(I) == SelectedArg) {
      // do magic;
      break;
  }
}
``
794

i can see the reason for variadics, but why bail out on overloaded operators? also can we have some tests for these two?

833

this place is getting crowded. maybe we should start passing N into getHoverContents and encapsulate the logic in there or something. no action needed, just thinking out loud.

957

let's put this after Size and before Documentation we've been keeping the documentation and definition as kind of a footer for a while now.
that would also drop the need for a ruler.

961

i bet there will always be some people getting confused no matter what we say in here (especially since we are not just printing the type, and I totally find printing param name useful especially in case of literals). But to align with other types of information we provide maybe say:

Substitutes: ?

clang-tools-extra/clangd/unittests/HoverTests.cpp
730

can you also add a test for a literal ?

2200

i find showing the default value a little bit confusing, as user is providing a different value. i can also see some value in it though, so up to you.

adamcz updated this revision to Diff 268780.Jun 5 2020, 6:48 AM
adamcz marked 11 inline comments as done.

addressed review comments, some UI questions remain open for now.

adamcz added inline comments.Jun 5 2020, 6:57 AM
clang-tools-extra/clangd/Hover.cpp
520

Yep, previous version, sorry about that.

790

It's possible for FD->getNumParams() to be higher than CE->getNumArgs(), for example due to default arguments. If we couldn't find selected node inside CallExpr, we shouldn't be trying to match it to some "random" argument in FunctionDecl.

It's a bit irrelevant now, after addressing other review comments ;-)

793

Yea, that's nicer, thanks.

(I opted for "continue" rather than "break" to reduce indentation).

794

I could totally see us supporting variadic, I'm just not sure how useful that will be, so I didn't do it right away.

As for the operators, I don't think we should trigger on those. There are two cases:

  • things that do not look like a function call: operator+, operator<<, etc. When you hover over a variable it's not immediately obvious what the "passed to" would be referring to. Something like:

foo(a+b);
if I see "a passed as const int&" I might think this is about passing it to foo() (at least of first glance).
At the same time, the value of this is very low for operators. You know what their signature is already.

-operator() - this is much more like a function call. Maybe it would make sense to support it. It gets tricky with matching CallExpr args to FD params though. I'll leave a todo for now and worry about this in a separate change, if don't mind.jj

961

I am very much open to suggestions for phrasing, I'm not too happy about "Passed as".

However, I am not a fan of Substitutes. If I saw that, I would have no idea what it's referring to. Since there is no visible connection between this hover card and the function call, without knowing what I'm looking at I would not make this connection.

Might be just me. I am really as far from being a UI expert as possible. I'll ask around to see if people would get the Substitute: and get back to you on this one.

clang-tools-extra/clangd/unittests/HoverTests.cpp
730

Currently there is no hover at all for literals. The reason was that it wouldn't be useful. This information, however, might be, especially in cases like:
printThis(that, true, false, true);
knowing what these refer to could be useful.

Do you think this is good enough reason to enable hover on literals, with just this information? I'm thinking yes, but I'm not sure.

2200

I did that for consistency with how we format function arguments. IMHO that's a good choice, but, as I already mentioned, I have no UI skills at all, so if you or someone else feels strongly about this, I won't argue.

kadircet marked an inline comment as done.Jun 11 2020, 3:16 AM
kadircet added inline comments.
clang-tools-extra/clangd/Hover.cpp
689

nit: this is already checked before entering the function.

794

ok makes sense, you are definitely right about nested case, I wasn't thinking about it.

915

I know you are planning to make more changes on how we render this, but let's not forget to drop the ruler in here.

clang-tools-extra/clangd/unittests/HoverTests.cpp
730

you are right, i remember carving the codepath for handling literals but forgot that we've disabled them at the time because there wasn't much info we could surface. I believe this one is a pretty good candidate (assuming LSP doesn't get "inline parameter annotations").

But definitely doesn't need to be handled in this patch, feel free to update the fixme on getHoverContents(Expr ...) though, saying we might want to surface callee information.

nridge added a subscriber: nridge.Jun 21 2020, 3:01 PM
adamcz updated this revision to Diff 273742.Jun 26 2020, 8:18 AM
adamcz marked 2 inline comments as done.

Formatting changed to spell out [const] reference and hide type unless conversion happens.
Defining what the "correct" behavior is on various cases is very difficult.
This is my best shot at incorporating previous requests and comments.

Also addressed more review comments.

thanks a lot! haven't checked the tests yet.

clang-tools-extra/clangd/Hover.cpp
707

nit: Arg seems to be used only once, consider inlining.

712

nit: braces are usually omitted when "then" part has a single expression. (applies to multiple other places in the patch)

717

nit: you can drop hasValue, we mostly make use of the implicit bool conversion for checking optionals.

726

isn't this same as PVD->getType().isConstQualified()? maybe set it in there?

I think it is more explicit that way, because in theory the Expr above is always the DeclRefExpr referencing the current parameter, which is quite subtle to figure out while looking at this. Or am I missing something?

751

*sigh* wish we didn't have more than 60 types of implicit casts in c++.

I can see how it is hard to list everything in here and even if you did it would still be really hard to reason about them.
I can't help myself but wonder though, was there a reasoning when choosing what to leave out? Because that would help me a lot, and possibly future readers. (I guess I am also fine with "the list looked comprehensive enough", as we can always iterate over)

I am a little bit hesitant about the fail policy though(both in here and in other branches), as I can't prove to myself that being passed as a reference would be false in all the other cases for example. And showing incorrect information in here could really end up confusing the user. Unless there's a clear reasoning behind this failure mode, I would suggest dropping CalleeArgInfo completely to be on the safe side. That way we can improve the handling for important cases later on, without surfacing possibly wrong results. WDYT?

clang-tools-extra/clangd/Hover.h
86

why is this true by default ?

87

s/false/const/ ?

what would you say to having an enum instead ?

enum PassBy {
  Value,
   Ref,
   ConstRef,
};
adamcz updated this revision to Diff 274147.Jun 29 2020, 8:59 AM
adamcz marked 8 inline comments as done.

Addressed review comments, also added some "const" here and there.

adamcz added inline comments.Jun 29 2020, 9:08 AM
clang-tools-extra/clangd/Hover.cpp
712

Ah, yes, that. I keep doing that because I am personally strongly opposed to this convention. I respect the rules, of course, and avoid {} in those cases, but I think my personal feelings about it make it hard for me to avoid mistakes like this ;-). Sorry.

Fixed.

726

It's different. Consider:
class C {
public:

C(int &x) {}

};
void foo(const C &c) {}

int y;
foo(y);

PVD is const-qualified (and it's type is const C&), but y is int and passed by non-const reference to the C c-tor, which might mutate y, even though the resulting instance of C will be const.

751

I basically went through the list of all CK_ values and looked for ones that preserve the ref bit. There aren't many. Basically any type conversion other than casting to base must drop the ref bit (because it's a different type then) and then there's few that are explicitly about copying data. So in the end the default is to drop the reference bit and there's only a handful that preserve it (DerivedToBase, Decay and NoOp, basically).

I do agree with you that this is fragile, likely to be slightly wrong and too complicated. I can't think of a better solution though.

I prefer to not drop the whole CalleeArgInfo / hover thing in those cases because, even if you ignore the whole mutable/non-mutable bit of information, I still find the name of the argument it's passed as to be very valuable).

I like the idea of now showing the ref/const-ref/value bit in cases where we fear it might not be correct, but I do not know how to display this to the user without confusing them (i.e. if there's no mention of ref/const-ref, does that mean it's by-value or that we couldn't tell?). Note that I intentionally avoided showing the words "by value" to the user, since it will confuse everyone when used on pointers.

This is essentially why I was hesitant to extract the reference bit and why my initial change had only the type, to let user figure it out themselves. It's going to be quite hard to get this right.

clang-tools-extra/clangd/Hover.h
86

Passing by reference is the default. It takes extra nodes in the AST to turn it to pass by value. Basically the defaults are what we see on the very inner node and then we adjust this as we go up the tree. Does that make sense?

87

I initially had an enum for the whole thing, but the Ref/Const/Ref/Value is very independent from Converted, so it resulted in too many values and awkward code. A PassBy enum and a boolean for Converted bit is better.

Makes the code slightly and tests a lot more readable. Thanks!

kadircet accepted this revision.Jul 1 2020, 2:34 AM
kadircet marked an inline comment as done.

Thanks, LGTM!

clang-tools-extra/clangd/Hover.cpp
751

I prefer to not drop the whole CalleeArgInfo / hover thing in those cases because, even if you ignore the whole mutable/non-mutable bit of information, I still find the name of the argument it's passed as to be very valuable).

I would expect people to file bugs in such cases for missing calleearginfo on hover card, hence we catching up quickly to a big enough coverage (which I think is already provided by this baseline). But the same argument goes both ways, users will also likely file bugs when the passby info is wrong, only problem is they'll likely be confused for a while :D

I like the idea of now showing the ref/const-ref/value bit in cases where we fear it might not be correct, but I do not know how to display this to the user without confusing them (i.e. if there's no mention of ref/const-ref, does that mean it's by-value or that we couldn't tell?). Note that I intentionally avoided showing the words "by value" to the user, since it will confuse everyone when used on pointers.

I agree, hence just dropping the passby field isn't enough, if we chose to go down this way I think we should drop full CalleArgInfo.

I don't think it is a decision we should block this patch on, we can decide what to do by looking at user reactions.

920

nit:

OS << "by ";
if(CallPassType->PassBy == ConstRef)
  OS << "const ";
OS << "reference ";
925

nit: again some redundant braces :D

clang-tools-extra/clangd/Hover.h
86

yes totally! (actually I had figured this one out while reading the rest, but forgot to delete the comment, sorry)

This revision is now accepted and ready to land.Jul 1 2020, 2:34 AM
adamcz updated this revision to Diff 275073.Jul 2 2020, 5:35 AM

Final review comment + rebase on top of HEAD

adamcz updated this revision to Diff 275104.Jul 2 2020, 7:05 AM
adamcz marked an inline comment as done.

removed some more {}

adamcz marked an inline comment as done.Jul 2 2020, 7:05 AM
This revision was automatically updated to reflect the committed changes.