This is an archive of the discontinued LLVM Phabricator instance.

Refactor getDeclAtPosition() to use SelectionTree + targetDecl()
ClosedPublic

Authored by nridge on Oct 20 2019, 10:28 PM.

Details

Summary

This fixes issue #163, among other improvements to go-to-definition.

Diff Detail

Event Timeline

nridge created this revision.Oct 20 2019, 10:28 PM
nridge marked 3 inline comments as done.Oct 20 2019, 10:37 PM
nridge added inline comments.
clang-tools-extra/clangd/XRefs.cpp
137

This part is a super hacky way to keep case #9 in LocateSymbol.Ambiguous passing while not regressing other tests.

In that case, the selection tree looks like this:

DeclStmt Foo foox = Foo("asdf");

  VarDecl Foo foox = Foo("asdf")
    ExprWithCleanups Foo("asdf")
      CXXConstructExpr Foo("asdf")
        MaterializeTemporaryExpr Foo("asdf")
          CXXFunctionalCastExpr Foo("asdf")
           .RecordTypeLoc Foo

The common ancestor is the RecordTypeLoc, but we'd like to find the constructor referred to by the CXXConstructExpr as a target as well. To achieve that, my current code walks up the parent tree until we encounter a CXXConstructExpr, but aborts the walk if certain node types are encountered to avoid certain other scenarios where we have a CXXConstructExpr ancestor.

This is almost certainly wrong, as there are likely many other cases where we have a CXXConstructExpr ancestor but don't want to find the constructor as a target which are not being handled correctly.

Is there a better way to identify the syntactic form at issue in this testcase?

138

I would appreciate some tips on how we could handle this in targetDecl(). Please see more specific comments below.

140

This part is intended to handle case #8 in LocateSymbol.Ambiguous, where you have a declaration of the form Foo abcd("asdf"); which invokes a constructor.

A naive attempt to add this logic to TargetFinder::add(const Decl*) resulted in the constructor being a target even for references to the variable, not just its declaration.

Thanks! I'd been meaning to do this before my vacation (and had a draft I'll dig up in case there were other issues).

Generally I think we should be willing to change behavior in some marginal cases, and I do think it's important to care about the layering of seltree vs targetDecl because we'll want to plug them into other things in the future. (E.g. using targetDecl + recursiveASTVisitor for more flexible indexing)

clang-tools-extra/clangd/XRefs.cpp
137

This case is gross, but as above I think it would be OK to have the targeting work like:

Foo("asdf")
FFFCSSSSSSC

F -> RecordTypeLoc    -> CXXRecordDecl Foo
C -> CXXConstructExpr -> CXXConstructorDecl Foo(string)
S -> StringLiteral    -> nullptr

WDYT?

138

My thinking is basically:

  • C++ syntax is vague and doesn't give us great ways of referring directly to constructors.
  • there's value to simplicity, and I've found go-to-definition additionally finding implicit nodes to be confusing as often as useful. I think on balance and given the constraints of LSP, we should consider dropping this and having implicit references be returned by find-refs but not targetable by go-to-def/hover/etc. It's OK for simplicity of implementation to be a concern here.
  • the node that targets the constructor is the CXXConstructExpr. Treating the VarDecl conditionally as a reference to the constructor, while clever, seems like a hack. Ideally the fix is to make SelectionTree yield the CXXConstructExpr, not to change TargetDecl.
  • CXXConstructExpr is only sometimes implicit. SelectionTree should return it for the parens in Foo() or Foo x(). And ideally for the equals in Foo x = {}, though I think there's no CXXConstructExpr in the AST in that case :-(
  • When the AST modelling is bad (as it seems to be for some aspects CXXConstructExpr) we should consider accepting some glitches and trying to improve things in clang if they're important. Hacking around them will lead to inconsistencies between different clangd features.

The TL;DR is I think I'd be happy to drop this special case and try to make SelectionTree surface CXXConstructExpr more often, even if it changes some behavior.

138

I'd prefer not to privilege any defaults here, I think this is unfortunately something that each caller needs to think about.

846

might be clearer to handle that case first then, and guard each with if (!HI)

clang-tools-extra/clangd/unittests/XRefsTests.cpp
2202

nit: can we add the trailing comma in the init-list to preserve the older, clearer formatting instead?

ilya-biryukov added inline comments.
clang-tools-extra/clangd/XRefs.cpp
138

+1 to the idea of landing this and changing behavior.
I don't think we're loosing much in terms of functionality and I personally like the new code much more.

nridge marked an inline comment as done.Oct 22 2019, 10:37 PM
nridge added inline comments.
clang-tools-extra/clangd/XRefs.cpp
138

FWIW, I do find being able to get from a variable declaration to the invoked constructor to be very useful.

If the class has several constructors, there's no other easy way to determine which one is being invoked (only other thing I can think of is to perform "find references" on each constructor and see which one includes the variable declaration as a reference, which is a lot more tedious), and I think that's an important thing to be able to do (just like for named functions).

So I'd definitely like to keep this case working. However, I'd be fine with only having the parens or braces target the constructor. (That's still slightly annoying, as you have to place the cursor more precisely, and it also may not be obvious to users, but at least there's a way to do it.) I'm also fine with changing the behaviour for now, and deferring constructor targeting to a follow-up, as there are other benefits this patch brings.

ilya-biryukov added inline comments.Oct 23 2019, 4:28 AM
clang-tools-extra/clangd/XRefs.cpp
138

We discussed this offline: finding constructor references is useful, but current approach does not generalize very well, e.g. for implicit constructor calls:

struct Foo {
  Foo(int);
};

int func(Foo a, Foo b);
int test() {
  func(1, 2); // no way to find Foo(1) and Foo(2) calls.
}

So we'd probably want some other mechanism to expose *all* possible references.
In any case, changing behavior now and tweaking it with a follow-up patch is probably the best way to proceed with this change.

nridge updated this revision to Diff 226372.Oct 24 2019, 9:48 PM
nridge marked 4 inline comments as done.

Removed constructor handling, deferring that to a follow-up

Addressed other comments

nridge marked an inline comment as done.Oct 24 2019, 9:52 PM
nridge added inline comments.
clang-tools-extra/clangd/XRefs.cpp
137

In your description, the target nodes are written underneath the characters. However, our input here is a cursor position (in between two characters).

Should we target the CXXConstructExpr if the cursor is just before the parenthesis, just after, or both?

(I am aware that SelectionTree can take a range as well as a position, so from the point of view of that API, "only when the selection is a range covering the parenthesis" is an option as well. However, this would not benefit go-to-definition, for which the request as specified in LSP only contains a position, not a range.)

nridge marked an inline comment as done.Oct 24 2019, 9:55 PM
nridge added inline comments.
clang-tools-extra/clangd/XRefs.cpp
138

So we'd probably want some other mechanism to expose *all* possible references.

Any ideas for what such a mechanism might look like?

ilya-biryukov marked an inline comment as done.Oct 25 2019, 12:44 AM
ilya-biryukov added inline comments.
clang-tools-extra/clangd/XRefs.cpp
138

We were discussing this offline, a few options that came up are:

Expose tree view of the AST (that links to the source code) with implicit nodes, allow to navigate from the conversion nodes to the references.
Code lens in VSCode, i.e. in-editor annotations right in the middle of the code

nridge marked an inline comment as done.Oct 26 2019, 6:13 PM

So, to summarize my understanding of the way forward here:

  1. Land the refactoring without any constructor targeting (i.e. the current patch, which is ready to review in this state).
  2. In a follow-up patch: target constructor in cases where this can be accomplished by having SelectionTree expose the CXXConstructorExpr on the parenthesis.
  3. Longer-term: put in place a mechanism for exposing all implicit names via code lens or somesuch.
clang-tools-extra/clangd/XRefs.cpp
137

Ah, never mind, I see now that SelectionTree has existing logic for mapping a cursor position to a one-character selection, which in this case would result in mapping the cursor onto the parenthesis if it's just before the parenthesis.

nridge updated this revision to Diff 226561.Oct 26 2019, 8:33 PM

Updated not to use the beginning-of-identifier location as input to SelectionTree

So, to summarize my understanding of the way forward here:

  1. Land the refactoring without any constructor targeting (i.e. the current patch, which is ready to review in this state).
  2. In a follow-up patch: target constructor in cases where this can be accomplished by having SelectionTree expose the CXXConstructorExpr on the parenthesis.

While trying to implement #2, I realized that SelectionTree already has this behaviour (exposes CXXConstructorExpr on the parenthesis), we just weren't benefitting from it because locateSymbolAt() was passing in the beginning-of-identifier location to getDeclAtPosition(), so Foo^(arg) and F^oo(arg) would yield the same result.

The updated patch fixes this and updates the tests to reflect the constructor targeting in cases where it now works.

sammccall accepted this revision.Oct 30 2019, 7:32 AM

LG, just some style nits, thanks!

clang-tools-extra/clangd/XRefs.cpp
259

rather than justifying *not* doing something here, can we make it clearer that macros are the special case?
maybe move the "SourceLocationBeg" initialization under the "macros are simple" comment, and rename it to "MaybeMacroLocation" or so.

266

log should have more context, and be an elog instead.
Return afterward?

269

I don't really understand this comment.

If the intention is just not to look at the AST or index when we have a macro (which seems reasonable to me), then just return Result instead of HaveMacro = true above?

895–899

want -> show references to

1132

Are you sure you don't want type hierarchy to work on aliases to record types? (i.e. specify underlying rather than alias)

clang-tools-extra/clangd/unittests/XRefsTests.cpp
527

I'd suggest dropping these two lines of comment. The fact that it's testing constructors is clear from the range name. The FIXME comment describes the future change.

529

This could just be // FIXME: target constructor instead of class..
We already have a test, so the comment belongs above point("9") instead of here

This revision is now accepted and ready to land.Oct 30 2019, 7:32 AM
nridge updated this revision to Diff 227352.Oct 31 2019, 2:32 PM
nridge marked 12 inline comments as done.

Address latest comments

clang-tools-extra/clangd/XRefs.cpp
1132

Good catch! I added a test case to TypeHierarchyTests for this as well.

This revision was automatically updated to reflect the committed changes.