Fixes clangd/clangd#1026
Details
Diff Detail
- Repository
- rG LLVM Github Monorepo
Event Timeline
clang-tools-extra/clangd/HeuristicResolver.h | ||
---|---|---|
75 | Not sure if it's the right call to make this public? The documentation needs to be adapted at least I think |
Thanks! I just have a question if this behavior should be even "stronger":
In most editors, if you return one result you go straight there, if you return two results you get a menu.
A menu is significantly worse than going straight to the right result - the UI is clunkier and the choice interrupts your workflow.
So my question is, do you think that we should *only* return the pointee in these cases?
(It's possible to change this later, but I'd like to take a reasonable guess as this is forcing an API change from Type -> vector<Type>)
cc @kadircet
clang-tools-extra/clangd/HeuristicResolver.h | ||
---|---|---|
75 | It's a bit unfortunate that it doesn't fit the usual pattern of "resolve the target of this AST node", but it's fairly closely related, it needs to use the Ctx in the same way, and it's already implemented here... I think it's OK (at least I can't see a better alternative). We should probably have a unit test for it though. |
Sorry, I didn't mean to accept yet - wanted some discussion on behavior & probably a unittest for the newly-public function.
I'm using this with neovim's builtin LSP, where when there are multiple results it takes me to the first one, and also opens the quickfix window that shows all results. But I can see how the current implementation could be annoying with other editors.
So here's what I thought: Say you have a variable that's a smart pointer and trigger textDocument/typeDefinition on it and there's only a single result that takes you straight to the type. In this case, you might not even know that the type is actually wrapped inside a smart pointer and might make wrong assumptions about the code. Of course you could've done a Hover beforehand, but in the case where there are 2 results, you don't need that extra step.
Maybe it would be best to make this configurable via the config file? Since most people are using "most editors", I think taking you straight to the type would be better, but I'm really not sure.
Btw, I think it would be useful to extend this feature to not only smart pointers, but also containers like std::vector. Then there'd also be the question what would happen for std::vector<std::unique_ptr<T>>: should there be 1, 2 or 3 results?
TL;DR: I'm convinced that this patch (return multiple locations) is a good choice. Also talked to Kadir offline.
(Interesting, I'm using the same and find it annoying: it focuses the quickfix window and that's disruptive enough that I never noticed the cursor also moved. But https://github.com/neovim/neovim/pull/14878 lists some workarounds)
So here's what I thought: Say you have a variable that's a smart pointer and trigger textDocument/typeDefinition on it and there's only a single result that takes you straight to the type. In this case, you might not even know that the type is actually wrapped inside a smart pointer and might make wrong assumptions about the code. Of course you could've done a Hover beforehand, but in the case where there are 2 results, you don't need that extra step.
Yeah. There's no guarantee the type in question is well-represented by a decl, but this is a bit confusing.
I think it might still be worth it for specific types like unique_ptr<Foo>, but for vector<Foo> it's too surprising even if you want Foo 99% of the time.
Maybe it would be best to make this configurable via the config file? Since most people are using "most editors", I think taking you straight to the type would be better, but I'm really not sure.
Making it configurable doesn't actually solve the problem: we still need to think hard about what the best default is, because relatively few users ever configure anything.
So let's not rule it out, but there's not much point doing it in this patch.
Btw, I think it would be useful to extend this feature to not only smart pointers, but also containers like std::vector. Then there'd also be the question what would happen for std::vector<std::unique_ptr<T>>: should there be 1, 2 or 3 results?
Yeah I find this pretty compelling. I think we should start with the approach in this patch, returning {pointer, pointee}, and iterate from there (add containers, or templates in general, or omit the pointer type in certain special cases...).
clang-tools-extra/clangd/XRefs.cpp | ||
---|---|---|
1913–1914 | Ergonomically I might prefer void unwrapFindType(QualType, ..., vector<QualType>& Out) or so. You can still return Out.push_back(T) etc on a single line. | |
1916 | I think we want to return 0 types in this case | |
1974 | (if you modify unwrap above to not return null, you can skip this check) | |
1975 | we now have the potential for duplicates (already in this patch in fact: unique_ptr<unique_ptr<int>>. Deduplicating types isn't that useful (unique_ptr<unique_ptr<int>> != unique_ptr<int>), I guess it's either deduplicate locations or allow the duplicates. |
(Interesting, I'm using the same and find it annoying: it focuses the quickfix window and that's disruptive enough that I never noticed the cursor also moved. But https://github.com/neovim/neovim/pull/14878 lists some workarounds)
(Ah, I also forgot that I'm not actually using quickfix, but rather https://github.com/folke/trouble.nvim, which also gets focused but has a preview feature)
So here's what I thought: Say you have a variable that's a smart pointer and trigger textDocument/typeDefinition on it and there's only a single result that takes you straight to the type. In this case, you might not even know that the type is actually wrapped inside a smart pointer and might make wrong assumptions about the code. Of course you could've done a Hover beforehand, but in the case where there are 2 results, you don't need that extra step.
Yeah. There's no guarantee the type in question is well-represented by a decl, but this is a bit confusing.
I think it might still be worth it for specific types like unique_ptr<Foo>, but for vector<Foo> it's too surprising even if you want Foo 99% of the time.
Right, maybe it would make sense to go straight to T for declarations, but include smart_ptr when it's used in an expression? But that's something for a follow-up patch.
Maybe it would be best to make this configurable via the config file? Since most people are using "most editors", I think taking you straight to the type would be better, but I'm really not sure.
Making it configurable doesn't actually solve the problem: we still need to think hard about what the best default is, because relatively few users ever configure anything.
So let's not rule it out, but there's not much point doing it in this patch.Btw, I think it would be useful to extend this feature to not only smart pointers, but also containers like std::vector. Then there'd also be the question what would happen for std::vector<std::unique_ptr<T>>: should there be 1, 2 or 3 results?
Yeah I find this pretty compelling. I think we should start with the approach in this patch, returning {pointer, pointee}, and iterate from there (add containers, or templates in general, or omit the pointer type in certain special cases...).
Agreed. The heuristic there would be "template class with at least 1 type parameter, where all other parameters except the first have a default value", right?
But maybe it even makes sense for stuff like std::map, so it's more like "template class, with n>0 type template parameters, followed by m>=0 defaulted template parameters"
Do we already have an issue for this? If not, I'd open a new one.
clang-tools-extra/clangd/HeuristicResolver.h | ||
---|---|---|
75 | Not really sure where to put the unit test, probably FindTargetTests.cpp? It looks like it tests HeuristicResolver implicitly. Couldn't find anything that tests HeuristicResolver directly. | |
clang-tools-extra/clangd/XRefs.cpp | ||
1913–1914 | Ah yes that makes sense. Didn't think about the return Out.push_back(T) trick to keep using early returns, thought I'd have to add a ton of else ifs instead. | |
1975 | Not sure about this either. I'll go with not deduplicating and adding a comment for now. |
clang-tools-extra/clangd/XRefs.cpp | ||
---|---|---|
1913–1914 | It makes the call side a bit uglier though - Is it okay to add a convenience wrapper vector<QualType> unwrapFindType(QualType, HeuristicResolver*) that forwards to the overload that uses the out-parameter? |
LG, just nits you know of outstanding and I think no extra tests needed.
Hmm, is the distinction that the type is written in the declaration (not auto)? I'm not sure it's worth specializing behavior for that scenario too much, when it's not too hard to go-to-definition on the specific token you want.
The chance of guessing right has to be weighed against keeping the behavior predictable and comprehensible...
Agreed. The heuristic there would be "template class with at least 1 type parameter, where all other parameters except the first have a default value", right?
But maybe it even makes sense for stuff like std::map, so it's more like "template class, with n>0 type template parameters, followed by m>=0 defaulted template parameters"
Right, if we have type template parameters we should offer them in addition to the template.
Defaulted is tricky, actually. (And important for stuff like container allocators and string traits).
- the type sometimes has "as-written" sugar which will tell you the spelled args, but the spelled form isn't that interesting when you can't see the spelling (and sometimes the sugar is missing entirely)
- TypePrinter has some logic to determine whether the template arg is equivalent to the default (isSubstitutedDefaultArgument in TypePrinter.cpp), but would need some refactoring to expose it
- the simplistic option is just to suppress a template arg if the parameter has a default (without checking whether it is default or not). In the common case this is correct, in the uncommon case it is conservative.
Do we already have an issue for this? If not, I'd open a new one.
I don't think so, the bug you found is the closest we have.
clang-tools-extra/clangd/HeuristicResolver.h | ||
---|---|---|
75 | Oh you're right - we don't have direct tests :-\ | |
clang-tools-extra/clangd/XRefs.cpp | ||
1913–1914 | Just the final call, not the recursive calls, right? They can be return unwrapFindType(...); too. It doesn't look ugly to me (2 extra lines at one callsite) but up to you. |
Hmm, is the distinction that the type is written in the declaration (not auto)? I'm not sure it's worth specializing behavior for that scenario too much, when it's not too hard to go-to-definition on the specific token you want.
The chance of guessing right has to be weighed against keeping the behavior predictable and comprehensible...
Yes that's what I meant, but I agree with you here.
clang-tools-extra/clangd/XRefs.cpp | ||
---|---|---|
1913–1914 | Yeah just the final call. I just don't like adding (non-const) helper variables on the call site. |
- Remove obsolete comment
- Return empty vector when type isNull()
- Add comment about why we don't deduplicate results
- Use out-parameter in unwrapFindType to make structure more consistent
@sammccall I think you can merge this for me now (and also https://reviews.llvm.org/D128202)
Done, sorry for the delay.
FWIW I think you should be able to get commit access at this point, if you want it.
Cheers, Sam
Not sure if it's the right call to make this public? The documentation needs to be adapted at least I think