This is an archive of the discontinued LLVM Phabricator instance.

[clangd] Add designator inlay hints for initializer lists.
ClosedPublic

Authored by sammccall on Jan 6 2022, 7:15 PM.

Details

Summary

These make the init lists appear as if designated initialization was used.

Example:

ExpectedHint{"param: ", "arg"}

becomes

ExpectedHint{.Label="param: ", .RangeName="arg"}

Diff Detail

Event Timeline

sammccall created this revision.Jan 6 2022, 7:15 PM
sammccall requested review of this revision.Jan 6 2022, 7:15 PM

(I know you're on vacation, this isn't urgent, just wanted to send it so I didn't lose it to git obscurity!)

clang-tools-extra/clangd/InlayHints.cpp
418–422

This makes the existing comment suppression (for param names) a bit sloppier, but I have a hard time imagining a case where punctuation makes a difference.

Thanks, this is a pretty nice addition!

My only piece of high-level feedback is probing if the array element designators ([0]=) are useful enough to be worth the space/noise. The rest is minor implementation comments.

clang-tools-extra/clangd/InlayHints.cpp
71

We could consider using the type name of the base (but I also get the impression that aggregate initialization with bases is uncommon enough to not worry about right now)

81

Should we try to more specifically restrict this to the case where the one field is an array? Otherwise skipping the field name feels a bit arbitrary.

99

suggest Iter and End, I and E are a bit cryptic

117

Can we add that the written sublist will get its own VisitInitListExpr call by RAV, while the implicit sublist will not? (If I've understood that correctly.)

128

"of the aggregate type"? ("the type Type" is a bit confusing, for a moment I thought you were talking about clang::Type)

145

aside: Prefix on this line is a great use case for the UsedAsMutableReference modifier. Now, if only we could somehow integrate clangd's semantic highlighting into Phabricator...

151

It took me a while to think through why it's not necessary to add more things into NestedBraces for the recursive call: since we only recurse in the case of elided braces, if a brace-elided subobject has an initializer with an explicit brace then syntactically that's a direct child of the containing explicit-brace initializer (even if semantically it's several levels deep), and thus its brace is already represented in NestedBraces. Might be worth pointing that out in a comment.

165

collectDesignators

176

"EmptyPrefix"? ("Scratch" makes it sound like "scratch space for it to write temporary things into")

292

I take it the parameter name Syn is supposed to suggest that the InitListExpr is the syntactic form (of the two forms described here), and that we know this because InlayHintVisitor does not override shouldVisitImplicitCode(), which makes RAV only traverse the syntactic form.

I think it would aid understandability if we mentioned these things in a comment.

422

Why do we need to trim ParamName?

clang-tools-extra/clangd/unittests/InlayHintTests.cpp
687

I wonder how useful these array-index hints are.

Unlike the struct case, they're not taking information present "elsewhere" (field names from the structure declaration) and making it available in the initializer, they're just taking information already present in the initializer and making it more explicit.

On the other hand, I guess if it's important that you initialize the element at index 15 in particular to some value, these hints help ensure you don't make an off-by-one error...

740

Maybe add a clarifying comment saying "this argument gets a hint, but not of type Designator", as this confused me for a while.

a drive by concern around possible flakiness of the interaction. have you checked how does it look like when the user is still forming the initializer? it might be annoying if their cursor kept jumping around while they're editing the (possibly half-formed) initializer.

sammccall updated this revision to Diff 401410.Jan 19 2022, 2:38 PM
sammccall marked 8 inline comments as done.

Address comments, rebase.
This category is *off* by default.

a drive by concern around possible flakiness of the interaction. have you checked how does it look like when the user is still forming the initializer? it might be annoying if their cursor kept jumping around while they're editing the (possibly half-formed) initializer.

Hmm, just tested and this is definitely a thing.
The biggest problem seems to be typing something that causes all prior designators to disappear.
This happens when you add an invalid expression to the init list, e.g a partial identifier you're typing.
(RecoveryExpr doesn't help here unless we can determine the type - dependent initlistexprs are not semantically analyzed).

Any ideas? :-(

clang-tools-extra/clangd/InlayHints.cpp
71

Yeah, I think this is rare.
Given that, I'd rather not create the confusion by having these hints be almost-but-not-quite-always valid syntax.

81

I modeled this after isIdiomaticBraceElisionEntity in SemaInit.cpp, which doesn't have this restriction.
(Though I didn't bother to implement the "base only" case, and restricted it to fields with reserved names).

I can change it if you want, though note that this is only eliding reserved names, and we *fail* (refuse to produce a designator) for reserved names otherwise.

145

Agree. I've also wondered about using 🌀 as an inlay-hint instead of a token modifier.

422

We're just passing in the designator string here, it may be .foo= and I want to match /*foo*/ and /*foo=*/ as well as /*.foo=*/.

I'd consider adding [] here too, to allow /*1=*/ to match [1]=.

clang-tools-extra/clangd/unittests/InlayHintTests.cpp
687

they're just taking information already present in the initializer and making it more explicit

This is usually true but not always, e.g.

struct Point { float x, y, z; };
Point shape[] = {
  /*[0].x=*/1.0,
  /*[0].y=*/0.3,
  /*[0].z=*/0.3,
  /*[1].x=*/0.3,
  /*[1].y=*/0.1,
  /*[1].z=*/0.6,
  ...
};

We could try some other behavior:

  • don't emit a hint if there's any array component
  • don't emit a hint if there's exactly one array component and nothing else
  • have array vs field designators separately configurable

I'm not sure how this will feel in practice. I quite like the idea of having them initially, but turning this category off by default to give a chance to dogfood and reevaluate

Any ideas? :-(

I've got a feeling that, this should also be annoying in the case of function calls (especially when there's only one overload)? Maybe we should just debounce the inlayhint requests (by saying contents modified to the clients)?

Any ideas? :-(

I've got a feeling that, this should also be annoying in the case of function calls (especially when there's only one overload)? Maybe we should just debounce the inlayhint requests (by saying contents modified to the clients)?

This seems like the right track, but I'm not sure rejecting requests specifically is the right answer. Two reasons:

  1. This requires careful impl on the client side. LSP has been amended to greatly discourage contentmodified. We still send it heuristically, but there's a capability to opt out of this and vscode sets it.

(As long as this is a custom protocol we can say what we want, but it won't be forever).

  1. Choosing which requests to respond to is hard, stateful work.

This isn't the drop-for-efficiency situation where the queue is full, this behavior is most annoying when the server is able to keep up with keystrokes. And the errors that cause this may not be trivially brief "i'm in the middle of typing a word" anyway.
Really you want to work out if this is a "good" request based on the AST. And in fact the analysis you'll do looks a lot like actually generating the hints, and probably comparing with the last ones. Then we throw them all away?!


I'd suggest something slightly different: we compute inlay hints, heuristically merge with some previous state, and return the merged results.
Heuristics are something like:

  • generally prefer freshly computed hints
  • be willing to add old ones where newer ones are missing
  • be willing to use older ones where code has changed near the hints (but not when they overlap!)

(There's not much here that's specific to inlay hints vs other decorations tied to ranges - certainly we could/should do this for semantictokens too, and documentSymbols, ...)


A variation on this idea is to compute these decorations frequently but "whenever we like" and always serve them from this cache, just patching them for changes in the source code.
This gives us more control of when the analyses run, maybe there's a lot of extra efficiency here. But I think the cache still needs stateful updates to solve this bug, unless we have *great* heuristics for "whenever we like".

I was also thinking about a cache-based solution in which we can update results as we please, and as you noted the idea definitely generalizes to other requests as well, with possibly little nuances around the definition of "as we please", which makes things a lot more annoying as we can't have this as a single layer between all requests/responses but will require some feature specific customization.
I feel like this is the way to go, but we'll need to put more thought into the design especially considering the future improvements like pseudoparser and the way it's possibly going to change how these features are going to look like in presence of it. Maybe all of these features would work on top of a fresh pseudo-parse + last "good" ast :shrug:

I feel like this is the way to go, but we'll need to put more thought into the design

Agree. I guess then we ignore the issue for this patch? As you say that's affects other hints too, and a mitigation (hopefully) won't be specific to the kind of hint.

nridge accepted this revision.Jan 23 2022, 11:10 PM

Thanks, I'm happy to try the array designators and see how they work out in practice.

The issue of hints appearing and disappearing as you type a function call is one I've noticed, but I've gotten used to it after a while. Improvements in this area would be nice but probably don't need to block the addition of new hints.

This revision is now accepted and ready to land.Jan 23 2022, 11:10 PM

Agree. I guess then we ignore the issue for this patch? As you say that's affects other hints too, and a mitigation (hopefully) won't be specific to the kind of hint.

Yup.