This is an archive of the discontinued LLVM Phabricator instance.

Adds tweak to add declarations for pure virtuals
Needs ReviewPublic

Authored by robot on Jun 16 2023, 9:21 AM.

Details

Reviewers
sammccall
Summary

This tweak searches the current or a selected base class for functions
which are still pure virtual in the current class, and adds function
declarations (overriders) for them.

Diff Detail

Event Timeline

robot created this revision.Jun 16 2023, 9:21 AM
Herald added a project: Restricted Project. · View Herald TranscriptJun 16 2023, 9:21 AM
robot requested review of this revision.Jun 16 2023, 9:21 AM
Herald added a project: Restricted Project. · View Herald TranscriptJun 16 2023, 9:21 AM
Herald added a subscriber: cfe-commits. · View Herald Transcript
robot added a comment.EditedJun 16 2023, 9:25 AM

This change is not done, I need some advice:

  • I don't understand why there are two levels of data structures in an entry of the final overriders map, hence I can't properly name the variables nor do I fully understand what I'm doing there.
  • It's not clear if the tweak should be available if there are no pure virtual functions.
  • It's not clear if we need to handle the case that there are multiple final overriders, or what we should do in that case.
  • There are some test cases at the end of the file which I don't know how to implement.
  • It's not clear if this tweak can apply inside a header.
  • It's not clear how to prepare the tweak when the cursor is inside the class body (see tests).
robot updated this revision to Diff 532199.Jun 16 2023, 9:28 AM

Remove left-over file of other patch from CMakeLists.txt

robot updated this revision to Diff 532575.Jun 19 2023, 2:23 AM

Remove left-over test file of other ptch from CMakeLists.txt

Thanks for working on this!

FWIW I had an old draft of this feature that may have interesting ideas: https://reviews.llvm.org/D122827

clang-tools-extra/clangd/refactor/tweaks/DeclarePureVirtuals.cpp
45

see the long comment on CXXFinalOverriderMap
TL;DR: a derived class can inherit multiple times from the same base, each such subobject could potentially have a separate override

59

copying the tokens directly seems brittle, for example we shouldn't be emitting "virtual" but its's hard to avoid doing so.
(Also, surprising that getEndLoc() doesn't cover the = 0!)

D122827 has a reprint() function that seems to work

76

this probably needs to be restructured somewhat as need to enumerate these in prepare() to be sure there are any, but don't want to pay to produce the strings

87

FWIW, I'm not actually sure that the case of multiple bases classes each providing pure-virtual-methods of which you want to override only one set is common enough to be worth this extra complexity.

140

please use ^^ markers underneath the characters where you expect the tweak to trigger

144

no "virtual"

150

what do you think about listing the functions we'll define?

Especially in the case where there's only one.

154

I don't think this is a quick-fix, which should address a diagnostic or so.

Really there doesn't seem to be a standard kind that fits here. Maybe we should add a new constant "generate"?

157

it's important that we only offer the code action (i.e. prepare returns true) if there are actually methods to override.

This means actually enumerating the functions needs to happen in prepare rather than apply. (Which means it needs to be fast...)

164

this should only trigger on the class definition, not forward declarations

171

this seems confusing: if you're going to walk up looking for a CXXBaseSpecifier, why not just do that in the first place?

197

this doesn't seem like a condition we should be surfacing to the user.
How do we get here? if it's e.g. because the base is dependent, then we should be returning false from prepare() in the appropriate dependent-code cases.

213

inserting at the end of the class isn't the most appropriate place.

we have InsertionPoint.h for this, see usage in D122827

clang-tools-extra/clangd/unittests/tweaks/DeclarePureVirtualsTests.cpp
69

it should be unavailable
(this is important, availability triggers the "lightbulb" icon in vscode and is interpreted by users as "something to do here")

592

EXPECT_UNAVAILABLE, we shouldn't be offering the tweak

robot planned changes to this revision.Aug 18 2023, 7:39 AM
robot marked 3 inline comments as done.

I'm working on the comments, but it will take a while.

clang-tools-extra/clangd/refactor/tweaks/DeclarePureVirtuals.cpp
59

My original idea was to take the AST node, clone it, remove the pure-specifier, and print that. To me, that seemed like the obvious (object-oriented) way how a tweak would manipulate or generate text.
Alas, I was rather stumped that there didn't seem to be support for this already, and it was rather hard to do. I gave up when default arguments came into play.

I've got the idea of manipulating the tokens from the "extract function" tweak (DefineOutline.cpp / getFunctionSourceCode).

I've tried the reprint function and it does not copy noexcept. That is simple to fix but my guess is there are more issues. The idea of token manipulation was to rather copy too much than too little. I have no intuition how many issues could arise from that :)

When I try to naively remove the virtual I currently print (virtual void foo() override;) in the token manipulation approach by skipping the first tokens, I end up skipping any attributes that precede the virtual as well. So I wrote a test for that, and reprint also doesn't copy attributes at the moment. It is not obvious to me if attributes should be copied, e.g. [[noreturn]].

I have a slight preference for my original approach but it's probably too complicated if this is the only use case. Therefore I'll just implement whatever looks most promising to you.

154

For reference:
rust-analyser shows a "Quick Fix" in VSCode for implementing the functions in a trait.
The go extension shows a "Rewrite" (which is an entirely different action?) in VSCode for filling a structure object (inserting code which explicitly initializes each member with its null value).

sammccall added inline comments.Aug 18 2023, 10:01 AM
clang-tools-extra/clangd/refactor/tweaks/DeclarePureVirtuals.cpp
59

Yeah, I think extract function copies tokens because there could be anything in the selected code, and there are certain constructs clang doesn't print well, it's unclear what to do in the presence of macros etc. Those concerns don't apply here I think.

My original idea was to take the AST node, clone it, remove the pure-specifier, and print that. To me, that seemed like the obvious (object-oriented) way how a tweak would manipulate or generate text.

Yes, unfortunately this approach doesn't work because the AST isn't designed to be mutated, and carries not just syntactic information but also semantic invariants.
(There was an effort to produce a separate representation of the syntax, but it was never finished)

I've tried the reprint function and it does not copy noexcept

True. It looks like the noexcept is part of the functionprototype, so you could replace the logic that prints the return type + arg list with printType(functype, class, methodname) (from AST.h). That should pick up noexcept etc.

It is not obvious to me if attributes should be copied, e.g. [[noreturn]].

No, I don't think attributes should be copied. They're not required to override, and whether they semantically belong there depends on the particular attribute - we can leave this up to the user.

robot marked an inline comment as done.Aug 18 2023, 10:09 AM
robot added inline comments.
clang-tools-extra/clangd/refactor/tweaks/DeclarePureVirtuals.cpp
59

Yeah, I think extract function copies tokens because there could be anything in the selected code, and there are certain constructs clang doesn't print well, it's unclear what to do in the presence of macros etc. Those concerns don't apply here I think.

Sorry, wrong tweak name (but right file/function). I meant "define outline". It also needs to massage the declaration since it needs to remove those parts which are not allowed in an outlined function definition, like virtual and static.

Anyway, I'll try adjusting reprint() next week.

robot added a comment.Aug 25 2023, 9:22 AM

so you could replace the logic that prints the return type + arg list with printType(functype, class, methodname) (from AST.h). That should pick up noexcept etc.

I've tried it and it's not obvious to me how to do it properly: Since printType wants to print the _function type_, it will parenthesize the placeholder (methodname):

void (foo)()

I've tried adding a flag to PrintingPolicy to avoid parenthesizing the name, but I wonder if adjusting a "type printer" to print a function declaration is a clean design. Next problem I ran into is parameters: A type printer does not need to concern itself with parameter names nor default arguments:

void foo(int x = 0) = 0;
void foo(int) override;

Shall I continue to pursue that path? Shall I adjust the type printer to be able to print parameter names and default arguments?

so you could replace the logic that prints the return type + arg list with printType(functype, class, methodname) (from AST.h). That should pick up noexcept etc.

I've tried it and it's not obvious to me how to do it properly: Since printType wants to print the _function type_, it will parenthesize the placeholder (methodname):

void (foo)()

I've tried adding a flag to PrintingPolicy to avoid parenthesizing the name, but I wonder if adjusting a "type printer" to print a function declaration is a clean design. Next problem I ran into is parameters: A type printer does not need to concern itself with parameter names nor default arguments:

void foo(int x = 0) = 0;
void foo(int) override;

Shall I continue to pursue that path? Shall I adjust the type printer to be able to print parameter names and default arguments?

Sorry, it does sound like I sent you down a bad path.
You *could* (no need for an option for parens, here we can detect that the placeholder isValidAsciiIdentifier), but entirely possible more things are broken still.

If you prefer the approach with copying the tokens, adding heuristics to patch them up as needed is maybe no worse than the alternatives.

(BTW, I don't think we want to copy default arguments - it's certainly not necessary, and not helpful in common cases where the function is only called through the base)

robot updated this revision to Diff 556283.Sep 8 2023, 10:20 AM
robot marked 3 inline comments as done.

WIP - several improvements

I thought it might be a good idea to give an update even though this is
still work in progress.

  • Don't print virtual for added declarations
  • Some rework of the declaration generation
  • Add title with preview
  • Make tweak only available if it adds anything
  • Mostly fix multiple inheritance
  • Disable tweak on forward declarations
Herald added a project: Restricted Project. · View Herald TranscriptSep 8 2023, 10:20 AM
robot added a comment.Sep 8 2023, 10:21 AM

I've pushed some WIP change to inform that I'm still working on this topic :) Not sure if I also need to submit a comment to publish what I've marked as "done" via checkboxes.