[clang-tools-extra] [clangd] Resolve the dependent type from its single instantiation. Take 1 (PR #71279)

Nathan Ridge via cfe-commits cfe-commits at lists.llvm.org
Sat Nov 25 23:26:32 PST 2023


HighCommander4 wrote:

I thought some more about this.

I appreciate better now the question you asked here:

> I realized that we had to tackle synthesized types e.g.
> > ```c++
> > template <template <class, int> typename C, typename D, int E>
> > void work() {
> >   C<D, E> complicated_type;
> >   // ^ Shall we perform the type substitution once again? ;)
> > }
> > ```

My previous suggestion side-steps this by saying that we're only going to do lookups in the primary template scope of the argument for `C`, so we don't need to worry about substituting the arguments for `D` and `E`.

However, if in the future we want to be more accurate and e.g. handle cases where a template specialization resolves to something different than the primary template (e.g. a partial specialization), then your question becomes relevant: do we perform type substitution again with the arguments for `D` and `E`, or do we just fish out the already-substituted type from the "only instantiation"?

I realized that this consideration actually applies to any other "resolution" step performed in `HeuristicResolver`, including steps it already does today such as name lookup.

To illustrate, let's go back to the `vector` example:

```c++
template <typename T>
void work() {
  std::vector<T> a;
  a.pop_back();
}
```

To resolve the dependent member expression `a.pop_back`, `HeuristicResolver` currently performs name lookup in the primary template scope of `std::vector` for the name `pop_back`.

The name lookup step could also be avoided in principle if we had an "only instantiation": if we find the non-dependent `MemberExpr` that `a.pop_back` instantiates to in the "only instantiation", that will point directly to a `CXXMethoDecl`, and we have our result without having done the work of a name lookup in the primary template scope.

And this method can lead to more accurate results, e.g. if instead of `pop_back` we have an overloaded method name, the name lookup in the primary template scope will just return all overloads (since we're not using information about the argument types), whereas the instantiated expression in the "only instantiation" will point to the correct overload resolution result.

---

So, I think we could consider two alternative approaches to this:

**Approach 1**: What I suggested in the previous review: a limited use of the "only instantiation" in `HeuristicResolver` to map template parameter types to the type of their argument in the "only instantiation".

**Approach 2**: If there is an "only instantiation", then resolve any expression, type, etc. in the template body to the corresponding expression, type, etc. in the "only instantiation".

The implementation of "Approach 2" could look similar to the current `InstantiatedDeclVisitor` in the patch, except the node type we are searching for is not limited to a Decl, it can also be other node types like Stmt etc.

"Approach 2" can be largely independent of `HeuristicResolver`: since we are finding a concrete result for the thing we're trying to resolve in the "only instantiation", no further resolution steps (e.g. name lookup, or anything else that `HeuristicResolver` does) should be necessary. However, the entry point to the check can be the same (e.g. for resolving a dependent member expr, we'd want to do the "only instantiation" check in the same cases where we call `HeuristicResolver::resolveMemberExpr()` from external code), so the functionality could still live in `HeuristicResolver` for organizational purposes.

https://github.com/llvm/llvm-project/pull/71279


More information about the cfe-commits mailing list