This is an archive of the discontinued LLVM Phabricator instance.

[CodeCompletion] (mostly) fix completion in incomplete C++ ctor initializers.
ClosedPublic

Authored by sammccall on Dec 26 2021, 7:10 PM.

Details

Summary

C++ member function bodies (including ctor initializers) are first captured
into a buffer and then parsed after the class is complete. (This allows
members to be referenced even if declared later).

When the boundary of the function body cannot be established, its buffer is
discarded and late-parsing never happens (it would surely fail).
For code completion this is the wrong tradeoff: the point of the parse is to
generate completions as a side-effect.
Today, when the ctor body wasn't typed yet there are no init list completions.
With this patch we parse such an init-list if it contains the completion point.

There's one caveat: the parser has to decide where to resume parsing members
after a broken init list. Often the first clear recovery point is *after* the
next member, so that member is missing from completion/signature help etc. e.g.

struct S {
  S() m  //<- completion here
  int maaa;
  int mbbb;
}

Here "int maaa;" is treated as part of the init list, so "maaa" is not available
as a completion. Maybe in future indentation can be used to recognize that
this is a separate member, not part of the init list.

Diff Detail

Event Timeline

sammccall created this revision.Dec 26 2021, 7:10 PM
sammccall requested review of this revision.Dec 26 2021, 7:10 PM
Herald added projects: Restricted Project, Restricted Project. · View Herald TranscriptDec 26 2021, 7:10 PM
Herald added a subscriber: cfe-commits. · View Herald Transcript
kadircet added inline comments.Jan 10 2022, 2:07 AM
clang/lib/Parse/ParseCXXInlineMethods.cpp
153

i don't follow the logic here. maybe i am reading the comment wrong, but we are actually going to eat more tokens by calling SkipMalformedDecl, possibly the following one, right? for example in a scenario like:

struct Foo {
  Foo : ^b
  int bar;
}

ConsumeAndStoreFunctionPrologue will actually put b following the code completion token (^) into Toks as well, hence when we skip, we actually skip until the next semicolon and throw away bar. But when the code completion token is after b, ConsumeAndStoreFunctionPrologue we'll have code completion token at the end of the Toks and won't skip anything

Do we have cases that break miserably when we don't perform an extra skip here for the (possible) reminder of current initalizer?

sammccall added inline comments.Jan 10 2022, 4:19 PM
clang/lib/Parse/ParseCXXInlineMethods.cpp
153

i don't follow the logic here. maybe i am reading the comment wrong,

Neither the code nor the comment are very good, but I think they are consistent.

Baseline behavior: we're going to recover by letting SkipMalformedDecl() eat tokens.
Exception: if we already ate the code completion token *and* stopped right afterwards.
Reason: CC token followed by heuristic stop are consistent with the function being truncated at the code completion point.

This exception allows some motivating testcases to pass. I thought maybe further improvements were possible but didn't want to get into them in this patch. However....

Do we have cases that break miserably when we don't perform an extra skip here for the (possible) reminder of current initalizer?

Um, apparently not. I thought I did!
Never skipping is simple and intuitive and makes more testcases pass.
Let's try it, the risk seems low.

sammccall updated this revision to Diff 398775.Jan 10 2022, 4:19 PM

Never eat malformed decl while code completing

kadircet accepted this revision.Jan 12 2022, 1:46 AM

thanks, lgtm!

This revision is now accepted and ready to land.Jan 12 2022, 1:46 AM