This is an archive of the discontinued LLVM Phabricator instance.

[clangd] Implementation of auto type expansion.
ClosedPublic

Authored by kuhnel on Jun 4 2019, 6:19 AM.

Details

Summary

Add a tweak for clangd to replace an auto keyword to the deduced type.

This way a user can declare something with auto and then have the IDE/clangd replace auto with whatever type clangd thinks it is. In case of long/complext types this makes is reduces writing effort for the user.

The functionality is similar to the hover over the auto keyword.

Example (from the header):

/// Before:
///    auto x = Something();
///    ^^^^
/// After:
///    MyClass x = Something();
///    ^^^^^^^

Diff Detail

Repository
rL LLVM

Event Timeline

kuhnel created this revision.Jun 4 2019, 6:19 AM
Herald added a project: Restricted Project. · View Herald TranscriptJun 4 2019, 6:19 AM

Sorry about the delay here. Happy to chat offline if I'm being confusing.

clang-tools-extra/clangd/AST.cpp
172 ↗(On Diff #202920)

It looks like this has been moved from somewhere (at least code looks familiar) but isn't deleted anywhere. (The code in XRefs is touched but doesn't seem to use this). Is there a reason we can't reuse one copy?

clang-tools-extra/clangd/AST.h
72 ↗(On Diff #202920)

nit: deduced
(also docs! In particular the fact that the SearchedLocation should point to exactly an auto, decltype etc token)

clang-tools-extra/clangd/refactor/tweaks/ExpandAutoType.cpp
76 ↗(On Diff #202920)

This is nice. We might want to move it to SourceCode.h, somewhere near SplitQualifiedName. Then we'd generally unit test all the cases in SourceCodeTests, and we only have to smoke test the shortening here.

One limitation is that it only handles shortening the qualifier on the name itself, but not other parts of a printed type. e.g. namespace ns { struct S(); auto* x = new std::vector<S>(); } will expand to std::vector<ns::S>, not std::vector<S>. To fully solve this we may want to modify PrintingPolicy at some point, though we could probably get a long way by searching for chunks of text inside printed types that look like qualified names.

I think it might more clearly communicate the limitations if this function operated on the scope part only, e.g. printScope("clang::clangd::", "clang::") --> "clangd::".

In the general case, CurrentNamespace should be a vector, because there can be others (introduced by using-declarations). Fine to leave this as a fixme if it's hard to do here, though.

76 ↗(On Diff #202920)

StringRefs are passed by value. They're just shorthand for a char* + length.

clang-tools-extra/clangd/refactor/tweaks/ExpandAutoType.h
1 ↗(On Diff #202920)

update filename

(these headers are so silly :-()

21 ↗(On Diff #202920)

Maybe add a FIXME to handle decltype here too? It's pretty similar (though rarer).

clang-tools-extra/clangd/unittests/TweakTests.cpp
221 ↗(On Diff #202920)

can you add testcases for:

  • unknown types in a template template <typename T> void x() { auto y = T::z(); }
  • broken code auto x = doesnt_exist();
  • lambda auto x = []{};
  • inline/anon namespace: inline namespace x { namespace { struct S; } } auto y = S(); should insert "S" not "x::S" or "(anonymous)::S".
  • local class: namespace x { void y() { struct S{}; auto z = S(); } } (should insert "S")

(it's ok if the behavior is bad in these cases, we can fix that later. Ideal in first 3 is no change)

kuhnel updated this revision to Diff 207037.Jun 28 2019, 4:50 AM
kuhnel marked 10 inline comments as done.
fixed code review comments from sam
kuhnel updated this revision to Diff 207039.Jun 28 2019, 4:58 AM
kuhnel marked an inline comment as done.
fixed merge error around hasDeducedType
lebedev.ri retitled this revision from Implementation of auto type expansion. to [clangd] Implementation of auto type expansion..Jun 28 2019, 5:25 AM

The patch is completely missing description.

kuhnel edited the summary of this revision. (Show Details)Jun 28 2019, 5:35 AM

Let's chat offline about how to land this.

Main points are:

  • we may just be able to use the AutoTypeLoc instead of the separate RecursiveASTVisitor. (If so, we can do more checks in prepare())
  • I have lots of questions about where to draw the line on pretty-printing, and whether the initial version here is a useful first step. Maybe we should just move this part to AST.h and stub it out, with a plan to incrementally improve later.
clang-tools-extra/clangd/AST.h
20 ↗(On Diff #207039)

(can now revert this change)

clang-tools-extra/clangd/XRefs.h
143 ↗(On Diff #207039)

Can you elaborate slightly whether this returns the AutoType/DecltypeType or its underlying type?

144 ↗(On Diff #207039)

nit: I'd change the second sentence to be "Retuns None unless SourceLocationBeg starts an auto/decltype token".

The current wording isn't totally specific about the token, or what happens in other cases (UB is common for preconditions)

150 ↗(On Diff #207039)

This appears to be unused, and isn't really worth having unless it's significantly more efficient than getDeducedType().hasValue() and we think that matters.

clang-tools-extra/clangd/refactor/tweaks/ExpandAutoType.cpp
32 ↗(On Diff #207039)

&& !CachedLocation->getTypePtr()->getDeducedType()->isNull()?

to avoid triggering in e.g. dependent code like

template <typename T> void f() {
auto X = T::foo();
}
39 ↗(On Diff #207039)

is this ever not just CachedLocation->getTypePtr()->getDeducedType()?

47 ↗(On Diff #207039)

again, this is actually a cheap test (if we don't need to use the deduced type visitor), we can lift it into prepare

53 ↗(On Diff #207039)

nit: this is just CachedLocation->getSourceRange()

55 ↗(On Diff #207039)

this logic around pretty-printing a type for insertion in code is going to be:

  • eventually more complicated than this: (e.g. this won't trim the "clangd" from std::vector<clangd::Tweak>)
  • needed in several places, like "extract expression" refactoring
  • fairly easily isolated from the rest of the code here

Can we move this to AST.h, with some signature like:
std::string printType(QualType, DeclContext &)

Note that the namespace and (most) governing using decls/directives can be obtained from the DC (printNamespaceScope in AST.h).
The limitations in the current version are fine for now, but we should document them.

68 ↗(On Diff #207039)

I'm confused about this - how can the user select the child of an AutoTypeLoc rather than the AutoTypeLoc itself? i.e. do we need the loop?

70 ↗(On Diff #207039)

(nit: for loop in disguise!)

108 ↗(On Diff #207039)

also a for loop in disguise

111 ↗(On Diff #207039)

nit: you can combine these with

if (const auto* NSD = dyn_cast_or_null<NamespaceDecl>(Node->ASTNode.get<Decl>()))

112 ↗(On Diff #207039)

nit: prefer auto*

113 ↗(On Diff #207039)

with namespaces, both inline and (probably) anonymous namespaces should be ignored, which I don't think this function will do.

note there can also be non-namespace qualifiers like MyClass::MyNestedClass.

clang-tools-extra/clangd/unittests/TweakTests.cpp
396 ↗(On Diff #207039)

can we have one for function pointers?

int foo();
auto x = &foo;

The correct replacement would be int (*x)() = &foo; but I think we should just aim to produce no edits in such cases.

clang-tools-extra/clangd/unittests/XRefsTests.cpp
2111 ↗(On Diff #207039)

(still "deducted" here)

kuhnel updated this revision to Diff 208705.Jul 9 2019, 8:46 AM
kuhnel marked 22 inline comments as done.

Allrighty then, next update:

  • moved 2 helper funtions to AST.cpp
  • added ASTTests.cpp
  • added more tests
  • removed a lot of redundant code
  • implemented corner case for not replacing function pointers
  • removed unwanted header file
clang-tools-extra/clangd/AST.cpp
172 ↗(On Diff #202920)

Ah interesting. It got messed up during rebase and someone implemented a similar function in XRefs.cpp in the mean time. So I'll just move over to their implementation. And remove the changes to AST...

clang-tools-extra/clangd/refactor/tweaks/ExpandAutoType.cpp
76 ↗(On Diff #202920)
  1. added documentation on that.
  1. In general it would be nicer to do these operations on a some model and not on bare strings. We could probably recursively search for more namespaces inside angle brackets <>...
32 ↗(On Diff #207039)

I added the new testcase and if fails with and without the added condition.

39 ↗(On Diff #207039)

There is some difference, because quite a few tests fail, wenn I use your call. --> Staying with current implementation

47 ↗(On Diff #207039)

Then I would have to move the call to getDeducedType(...) also to prepare....

53 ↗(On Diff #207039)

correct.

68 ↗(On Diff #207039)

At first I understood that we would have to walk the AST, but it seems we don't have to...

clang-tools-extra/clangd/unittests/TweakTests.cpp
396 ↗(On Diff #207039)

You're an infinite oracle of strange C++ corner cases ;)
I added a test and another check for this case...

221 ↗(On Diff #202920)

Did not add tests, as the code does not yet support this:

  • inline/anon namespace: inline namespace x { namespace { struct S; } } auto y = S(); should insert "S" not "x::S" or "(anonymous)::S".
  • local class: namespace x { void y() { struct S{}; auto z = S(); } } (should insert "S")

This requires some changes on how I identify the namespaces. I would do that in a second version/increment of the feature.

Working:

  • unknown types in a template template <typename T> void x() { auto y = T::z(); }
  • broken code auto x = doesnt_exist();
  • lambda auto x = []{};
kuhnel updated this revision to Diff 208706.Jul 9 2019, 8:50 AM

fixed typo on "Deducted"

sammccall accepted this revision.Jul 9 2019, 1:41 PM

Great, let's land this!
We can enhance further later, but this covers the most common cases.

(I think the remaining comments are trivial - then I/you can land without further review)

clang-tools-extra/clangd/AST.cpp
181 ↗(On Diff #208706)

unsigned rather than u_int (just common llvm style)

clang-tools-extra/clangd/AST.h
79 ↗(On Diff #208706)

can you leave a FIXME that this should take a DeclContext for lookup instead of the current namespace name, take in to account using directives etc?
(Otherwise it's not obvious why it belongs in AST instead of SourceCode - but it does)

clang-tools-extra/clangd/Selection.cpp
369 ↗(On Diff #208706)

I think this function is not quite correct. At least, it returns the enclosing DC outside the enclosing Decl, but that doesn't seem quite right.

There are three critical cases:

  • StartNode is a DeclContext
  • StartNode is (e.g.) an Expr whose containing Decl is a DeclContext
  • StartNode is an Expr whose containing Decl is not a DeclContext

Currently, you return the second, second, and first DeclContext encountered, respectively.

  • If the definition is "enclosing DC that is not the node itself", it should be second, first, first.
  • If the definition is "enclosing DC that may be the node itself", it should be first, first, first.

I think "enclosing DC that's not the node itself" is simplest in which case you should add something like if (CurrentNode != StartNode) if (auto *DC = dyn_cast<DeclContext>(DC)) return DC; in the inner if.

clang-tools-extra/clangd/Selection.h
104 ↗(On Diff #208706)

, which is not the node itself. (Is this the intended behavior? worth calling out either way)

105 ↗(On Diff #208706)

Thinking about this again - this can't ever happen, it violates the invariant that a tree must be rooted at TranslationUnitDecl. Just return a reference and add an assert to the implementation.

106 ↗(On Diff #208706)

nit: I think this would be slightly clearer as a (non-static) member function on Node. (It also makes it obvious node can't be null).

106 ↗(On Diff #208706)

please run git clang-format.
(LLVM uses type *var not type* var)

clang-tools-extra/clangd/refactor/tweaks/ExpandAutoType.cpp
88 ↗(On Diff #208706)

comment should say why
// naively replacing 'auto' with the type will break declarations.

89 ↗(On Diff #208706)

no need for this first check/cast: isFunctionPointerType is a member of Type

90 ↗(On Diff #208706)

please also check isFunctionReferenceType, isMemberPointerType, isArrayType for the same reason

clang-tools-extra/clangd/unittests/ASTTests.cpp
34 ↗(On Diff #208706)

rather than commenting out the test, assert the (bad) behavior and keep the comment.
That way someone gets a happy surprise when they fix it :-)

42 ↗(On Diff #208706)

delete the comment unless the tests will be added in this patch :-)

clang-tools-extra/clangd/unittests/TweakTests.cpp
23 ↗(On Diff #208706)

remove

This revision is now accepted and ready to land.Jul 9 2019, 1:41 PM
kuhnel updated this revision to Diff 208923.Jul 10 2019, 5:12 AM
kuhnel marked 13 inline comments as done.

fixed parts of the 3rd round of review comments

kuhnel updated this revision to Diff 209234.Jul 11 2019, 8:46 AM
kuhnel marked 3 inline comments as done.
pair programming with Sam, fixed final issues
This revision was automatically updated to reflect the committed changes.
Herald added a project: Restricted Project. · View Herald TranscriptJul 11 2019, 9:04 AM
thakis added a subscriber: thakis.Oct 13 2019, 6:01 PM
thakis added inline comments.
clang-tools-extra/trunk/clangd/test/code-action-request.test
54

FYI, referring to a file opened as test:///foo.cpp as file:///clangd-test/file.cpp is wrong on Windows. I fixed this in rL374746.