[clang] [clang][HeuristicResolver] Resolve more dependent member accesses (PR #223667)
Christian Kandeler via cfe-commits
cfe-commits at lists.llvm.org
Thu Sep 24 06:22:30 PDT 2026
https://github.com/ckandeler updated https://github.com/llvm/llvm-project/pull/223667
>From c43ade51529cc0a8e8bd231f1b9364b7c481cf49 Mon Sep 17 00:00:00 2001
From: Christian Kandeler <christian.kandeler at qt.io>
Date: Tue, 15 Sep 2026 11:55:20 +0200
Subject: [PATCH 1/4] [clang] Heuristically resolve a member typedef naming a
template parameter
A class template commonly exposes one of its own template parameters
under a member typedef, e.g. libstdc++'s `vector<T, A>` has
typedef A allocator_type;
so `vector<T>::allocator_type` resolves to `A`, a bare template
parameter with no members to look up. HeuristicResolver already
substitutes a template parameter's default argument in that situation,
but only when it encounters the parameter directly: the substitution
was skipped both when the parameter is reached through a dependent
name and when it is reached through the sugar of the typedef itself.
Handle both. Note that neither can work on a canonicalized type, as a
canonical TemplateTypeParmType does not retain its
TemplateTypeParmDecl, and hence has no default argument to offer; look
at the typedef's underlying type as written instead.
This makes code completion work for
typename std::vector<T>::allocator_type al = ...;
al.
inside a template.
Assisted-by: Claude Opus 5
---
clang/lib/Sema/HeuristicResolver.cpp | 58 ++++++++++++++-----
.../unittests/Sema/HeuristicResolverTest.cpp | 47 +++++++++++++++
2 files changed, 90 insertions(+), 15 deletions(-)
diff --git a/clang/lib/Sema/HeuristicResolver.cpp b/clang/lib/Sema/HeuristicResolver.cpp
index 8a799a1b464365..b5bc270027cbdf 100644
--- a/clang/lib/Sema/HeuristicResolver.cpp
+++ b/clang/lib/Sema/HeuristicResolver.cpp
@@ -124,6 +124,30 @@ TemplateName getReferencedTemplateName(const Type *T) {
return TemplateName();
}
+// If `T` is a template parameter with a default argument, return that default
+// argument, otherwise return a null QualType.
+// We can't do anything useful with a template parameter itself (e.g. we cannot
+// look up member names inside it), so where one turns up, using its default
+// argument is a reasonable heuristic: it's what the parameter will be bound to
+// unless the instantiation site says otherwise.
+// Note that `T` must not be canonicalized: a canonical TemplateTypeParmType
+// does not retain its TemplateTypeParmDecl, and so has no default argument to
+// offer.
+QualType getDefaultTemplateArgument(QualType T) {
+ // Use getAs() rather than a dyn_cast, so that we see through sugar such as a
+ // member typedef naming the parameter (e.g. `typedef A allocator_type;`).
+ const auto *TTPT = T.isNull() ? nullptr : T->getAs<TemplateTypeParmType>();
+ if (!TTPT)
+ return QualType();
+ const auto *TTPD = TTPT->getDecl();
+ if (!TTPD || !TTPD->hasDefaultArgument())
+ return QualType();
+ const auto &DefaultArg = TTPD->getDefaultArgument().getArgument();
+ if (DefaultArg.getKind() != TemplateArgument::Type)
+ return QualType();
+ return DefaultArg.getAsType();
+}
+
// Helper function for HeuristicResolver::resolveDependentMember()
// which takes a possibly-dependent type `T` and heuristically
// resolves it to a CXXRecordDecl in which we can try name lookup.
@@ -136,8 +160,22 @@ TagDecl *HeuristicResolverImpl::resolveTypeToTagDecl(QualType QT) {
T = T->getCanonicalTypeInternal().getTypePtr();
if (const auto *DNT = T->getAs<DependentNameType>()) {
- T = resolveDeclsToType(resolveDependentNameType(DNT), Ctx)
- .getTypePtrOrNull();
+ auto Decls = resolveDependentNameType(DNT);
+ QualType Resolved = resolveDeclsToType(Decls, Ctx);
+ // `Resolved` is canonical, so if the name refers to a member typedef of a
+ // template parameter (as `vector<T>::allocator_type` does), it has lost the
+ // TemplateTypeParmDecl we would need to fall back to the parameter's
+ // default argument. Consult the typedef's underlying type as written
+ // instead.
+ if (Decls.size() == 1) {
+ if (const auto *TND = dyn_cast<TypedefNameDecl>(Decls[0])) {
+ if (QualType Default =
+ getDefaultTemplateArgument(TND->getUnderlyingType());
+ !Default.isNull())
+ Resolved = Default;
+ }
+ }
+ T = Resolved.getTypePtrOrNull();
if (!T)
return nullptr;
T = T->getCanonicalTypeInternal().getTypePtr();
@@ -246,19 +284,9 @@ QualType HeuristicResolverImpl::simplifyType(QualType Type, const Expr *E,
}
}
}
- if (const auto *TTPT = dyn_cast_if_present<TemplateTypeParmType>(T.Type)) {
- // We can't do much useful with a template parameter (e.g. we cannot look
- // up member names inside it). However, if the template parameter has a
- // default argument, as a heuristic we can replace T with the default
- // argument type.
- if (const auto *TTPD = TTPT->getDecl()) {
- if (TTPD->hasDefaultArgument()) {
- const auto &DefaultArg = TTPD->getDefaultArgument().getArgument();
- if (DefaultArg.getKind() == TemplateArgument::Type) {
- return {DefaultArg.getAsType()};
- }
- }
- }
+ if (QualType Default = getDefaultTemplateArgument(T.Type);
+ !Default.isNull()) {
+ return {Default};
}
// Similarly, heuristically replace a template template parameter with its
diff --git a/clang/unittests/Sema/HeuristicResolverTest.cpp b/clang/unittests/Sema/HeuristicResolverTest.cpp
index c592e74c41a95f..16a0dc53104342 100644
--- a/clang/unittests/Sema/HeuristicResolverTest.cpp
+++ b/clang/unittests/Sema/HeuristicResolverTest.cpp
@@ -580,6 +580,53 @@ TEST(HeuristicResolver, MemberExpr_DefaultTemplateArgument_Recursive) {
cxxMethodDecl(hasName("foo")).bind("output"));
}
+TEST(HeuristicResolver, MemberExpr_DefaultTemplateArgument_MemberTypedef) {
+ std::string Code = R"cpp(
+ struct Default {
+ void foo();
+ };
+ template <typename T, typename A = Default>
+ struct S {
+ typedef A type;
+ };
+ template <typename T>
+ void bar() {
+ typename S<T>::type t;
+ t.foo();
+ }
+ )cpp";
+ // Test resolution of "foo" in "t.foo()", where the type of "t" resolves
+ // to the template parameter "A" via the member typedef "type".
+ expectResolution(
+ Code, &HeuristicResolver::resolveMemberExpr,
+ cxxDependentScopeMemberExpr(hasMemberName("foo")).bind("input"),
+ cxxMethodDecl(hasName("foo")).bind("output"));
+}
+
+TEST(HeuristicResolver, MemberExpr_DefaultTemplateArgument_ReturnType) {
+ std::string Code = R"cpp(
+ struct Default {
+ void foo();
+ };
+ template <typename T, typename A = Default>
+ struct S {
+ typedef A type;
+ type get();
+ };
+ template <typename T>
+ void bar(S<T> s) {
+ s.get().foo();
+ }
+ )cpp";
+ // Test resolution of "foo" in "s.get().foo()", where the return type of
+ // "get()" resolves to the template parameter "A" via the member typedef
+ // "type".
+ expectResolution(
+ Code, &HeuristicResolver::resolveMemberExpr,
+ cxxDependentScopeMemberExpr(hasMemberName("foo")).bind("input"),
+ cxxMethodDecl(hasName("foo")).bind("output"));
+}
+
TEST(HeuristicResolver, MemberExpr_DefaultTemplateTemplateArgument) {
std::string Code = R"cpp(
template <typename T>
>From 41f0dd0bc0543ae24bbfcd423745c3a815592dd4 Mon Sep 17 00:00:00 2001
From: Christian Kandeler <christian.kandeler at qt.io>
Date: Tue, 15 Sep 2026 11:57:51 +0200
Subject: [PATCH 2/4] [clang] Heuristically resolve a call to a using-declared
member
A class template may re-export a member of a dependent base class with
a using declaration, e.g. libstdc++'s `vector` has
using _Base::get_allocator;
Looking the name up in the primary template then yields an
UnresolvedUsingValueDecl, which has no function type of its own, so
resolveTypeOfCallExpr() could not determine the type of a call to it
and resolution of anything applied to the result failed.
Use the existing resolveUsingValueDecl() to replace such a declaration
with what it names. Only one level is looked through; a using
declaration naming another one is still not resolved.
Assisted-by: Claude Opus 5
---
clang/lib/Sema/HeuristicResolver.cpp | 20 ++++++++++++--
.../unittests/Sema/HeuristicResolverTest.cpp | 27 +++++++++++++++++++
2 files changed, 45 insertions(+), 2 deletions(-)
diff --git a/clang/lib/Sema/HeuristicResolver.cpp b/clang/lib/Sema/HeuristicResolver.cpp
index b5bc270027cbdf..29ece2de5fbe8e 100644
--- a/clang/lib/Sema/HeuristicResolver.cpp
+++ b/clang/lib/Sema/HeuristicResolver.cpp
@@ -386,8 +386,24 @@ QualType HeuristicResolverImpl::resolveTypeOfCallExpr(const CallExpr *CE) {
// resolveExprToType(CE->getCallee()) would bail in the case of multiple
// overloads, as it can't produce a single type for them. We can be more
// permissive here, and allow multiple overloads with a common return type.
- std::vector<const NamedDecl *> CalleeDecls =
- resolveExprToDecls(CE->getCallee());
+ std::vector<const NamedDecl *> CalleeDecls;
+ for (const NamedDecl *D : resolveExprToDecls(CE->getCallee())) {
+ // The callee may be re-exported from a dependent base class by a using
+ // declaration, e.g. libstdc++'s `vector` has `using _Base::get_allocator;`.
+ // Such a declaration has no function type of its own, so replace it with
+ // what it names. That may be an overload set, but a conflicting return type
+ // within it is handled below just as it is between two distinct callee
+ // declarations, so simply flatten it in. Only one level is looked through;
+ // a using declaration naming another one is not resolved.
+ if (const auto *UUVD = dyn_cast<UnresolvedUsingValueDecl>(D)) {
+ auto Underlying = resolveUsingValueDecl(UUVD);
+ CalleeDecls.insert(CalleeDecls.end(), Underlying.begin(),
+ Underlying.end());
+ continue;
+ }
+ CalleeDecls.push_back(D);
+ }
+
QualType CommonReturnType;
for (const NamedDecl *CalleeDecl : CalleeDecls) {
QualType CalleeType = resolveDeclToType(CalleeDecl, Ctx);
diff --git a/clang/unittests/Sema/HeuristicResolverTest.cpp b/clang/unittests/Sema/HeuristicResolverTest.cpp
index 16a0dc53104342..35438266de19e1 100644
--- a/clang/unittests/Sema/HeuristicResolverTest.cpp
+++ b/clang/unittests/Sema/HeuristicResolverTest.cpp
@@ -627,6 +627,33 @@ TEST(HeuristicResolver, MemberExpr_DefaultTemplateArgument_ReturnType) {
cxxMethodDecl(hasName("foo")).bind("output"));
}
+TEST(HeuristicResolver, MemberExpr_CallOfUsingDeclFromDependentBase) {
+ std::string Code = R"cpp(
+ struct Result {
+ void foo();
+ };
+ template <typename T>
+ struct Base {
+ Result get();
+ };
+ template <typename T>
+ struct Derived : Base<T> {
+ typedef Base<T> _Base;
+ using _Base::get;
+ };
+ template <typename T>
+ void bar(Derived<T> d) {
+ d.get().foo();
+ }
+ )cpp";
+ // Test resolution of "foo" in "d.get().foo()", where "get" is found as a
+ // using declaration naming a member of a dependent base class.
+ expectResolution(
+ Code, &HeuristicResolver::resolveMemberExpr,
+ cxxDependentScopeMemberExpr(hasMemberName("foo")).bind("input"),
+ cxxMethodDecl(hasName("foo")).bind("output"));
+}
+
TEST(HeuristicResolver, MemberExpr_DefaultTemplateTemplateArgument) {
std::string Code = R"cpp(
template <typename T>
>From 49a0c6bb642b19bd017a6fb7248a63634d886ea2 Mon Sep 17 00:00:00 2001
From: Christian Kandeler <christian.kandeler at qt.io>
Date: Tue, 15 Sep 2026 12:49:10 +0200
Subject: [PATCH 3/4] [clang] Add tests documenting two limits of the
template-parameter heuristic
Falling back to a template parameter's default argument only gets us so
far. Pin down two cases where it does not, both reachable from
`std::vector`:
- the parameter has no default argument at all, as
`vector<T>::value_type` does not;
- the parameter that has the default is not the one we end up looking
at, because looking a name up in a base class's primary template
discards the arguments the derived class passes to it. This is why
`v.get_allocator().` still does not resolve: vector's allocator
parameter is defaulted, but _Vector_base's is not.
Both are written as ordinary tests asserting no resolution rather than
disabled ones, so that they fail, and get updated, if either limitation
is ever lifted.
Assisted-by: Claude Opus 5
---
.../unittests/Sema/HeuristicResolverTest.cpp | 56 +++++++++++++++++++
1 file changed, 56 insertions(+)
diff --git a/clang/unittests/Sema/HeuristicResolverTest.cpp b/clang/unittests/Sema/HeuristicResolverTest.cpp
index 35438266de19e1..b09877429df6d3 100644
--- a/clang/unittests/Sema/HeuristicResolverTest.cpp
+++ b/clang/unittests/Sema/HeuristicResolverTest.cpp
@@ -627,6 +627,27 @@ TEST(HeuristicResolver, MemberExpr_DefaultTemplateArgument_ReturnType) {
cxxMethodDecl(hasName("foo")).bind("output"));
}
+TEST(HeuristicResolver, MemberExpr_MemberTypedefWithoutDefaultArgument) {
+ std::string Code = R"cpp(
+ template <typename T>
+ struct S {
+ typedef T type;
+ };
+ template <typename T>
+ void bar() {
+ typename S<T>::type t;
+ t.foo();
+ }
+ )cpp";
+ // Test that "foo" in "t.foo()" does not resolve: "S<T>::type" names S's own
+ // parameter T, which has no default argument to fall back on, and nothing
+ // else says what T will be. This is the member-typedef analogue of
+ // `std::vector<T>::value_type`.
+ expectResolution(
+ Code, &HeuristicResolver::resolveMemberExpr,
+ cxxDependentScopeMemberExpr(hasMemberName("foo")).bind("input"));
+}
+
TEST(HeuristicResolver, MemberExpr_CallOfUsingDeclFromDependentBase) {
std::string Code = R"cpp(
struct Result {
@@ -654,6 +675,41 @@ TEST(HeuristicResolver, MemberExpr_CallOfUsingDeclFromDependentBase) {
cxxMethodDecl(hasName("foo")).bind("output"));
}
+TEST(HeuristicResolver, MemberExpr_DefaultTemplateArgumentNotSeenByBase) {
+ std::string Code = R"cpp(
+ struct Result {
+ void foo();
+ };
+ template <typename T, typename A>
+ struct Base {
+ typedef A type;
+ type get();
+ };
+ template <typename T, typename A = Result>
+ struct Derived : Base<T, A> {
+ typedef Base<T, A> _Base;
+ using _Base::get;
+ };
+ template <typename T>
+ void bar(Derived<T> d) {
+ d.get().foo();
+ }
+ )cpp";
+ // Test that "foo" in "d.get().foo()" does not resolve. Looking "get" up in
+ // Base's primary template discards the arguments Derived passes to Base, so
+ // the return type is Base's own "A", which -- unlike Derived's -- has no
+ // default argument to fall back on. Resolving this would require propagating
+ // template arguments to base classes, which this file's "look the name up in
+ // the primary template" approach does not do.
+ //
+ // This mirrors libstdc++, where vector's allocator parameter is defaulted but
+ // _Vector_base's is not, and is why completion after `v.get_allocator().`
+ // does not work even though `vector<T>::allocator_type` itself resolves.
+ expectResolution(
+ Code, &HeuristicResolver::resolveMemberExpr,
+ cxxDependentScopeMemberExpr(hasMemberName("foo")).bind("input"));
+}
+
TEST(HeuristicResolver, MemberExpr_DefaultTemplateTemplateArgument) {
std::string Code = R"cpp(
template <typename T>
>From 6d630e2f77d263158dbc2960976d542bbfcb5a5a Mon Sep 17 00:00:00 2001
From: Christian Kandeler <christian.kandeler at qt.io>
Date: Thu, 24 Sep 2026 15:14:50 +0200
Subject: [PATCH 4/4] [clang] Resolve a member typedef to a default argument in
resolveDeclToType()
Substituting a template parameter's default argument for a member typedef
that names it belongs next to the other decl-to-type logic, rather than
being special-cased at the one call site that first needed it. Doing it
in resolveDeclToType() also covers resolveNestedNameSpecifierToType(),
and restores resolveTypeToTagDecl() to its previous shape.
Also add tests for two cases where the default-argument heuristic gives a
wrong answer rather than no answer: an explicitly provided template
argument is ignored in favour of the default, both when the parameter is
reached through a method's return type and when it is reached through a
member typedef. Both are marked FIXME.
Finally, make the existing "does not resolve" tests say whether that is
the desired behaviour or a limitation worth lifting, and give the
member-typedef one a code example that actually declares the member being
looked up, so that it distinguishes the heuristic declining to guess from
the name simply not existing.
Assisted-by: Claude Opus 5
---
clang/lib/Sema/HeuristicResolver.cpp | 74 ++++++++--------
.../unittests/Sema/HeuristicResolverTest.cpp | 84 ++++++++++++++++---
2 files changed, 108 insertions(+), 50 deletions(-)
diff --git a/clang/lib/Sema/HeuristicResolver.cpp b/clang/lib/Sema/HeuristicResolver.cpp
index 29ece2de5fbe8e..16a6c8a04d8eb7 100644
--- a/clang/lib/Sema/HeuristicResolver.cpp
+++ b/clang/lib/Sema/HeuristicResolver.cpp
@@ -95,10 +95,42 @@ const auto TemplateFilter = [](const NamedDecl *D) {
return isa<TemplateDecl>(D);
};
+// If `T` is a template parameter with a default argument, return that default
+// argument, otherwise return a null QualType.
+// We can't do anything useful with a template parameter itself (e.g. we cannot
+// look up member names inside it), so where one turns up, using its default
+// argument is a reasonable heuristic: it's what the parameter will be bound to
+// unless the instantiation site says otherwise.
+// Note that `T` must not be canonicalized: a canonical TemplateTypeParmType
+// does not retain its TemplateTypeParmDecl, and so has no default argument to
+// offer.
+QualType getDefaultTemplateArgument(QualType T) {
+ // Use getAs() rather than a dyn_cast, so that we see through sugar such as a
+ // member typedef naming the parameter (e.g. `typedef A allocator_type;`).
+ const auto *TTPT = T.isNull() ? nullptr : T->getAs<TemplateTypeParmType>();
+ if (!TTPT)
+ return QualType();
+ const auto *TTPD = TTPT->getDecl();
+ if (!TTPD || !TTPD->hasDefaultArgument())
+ return QualType();
+ const auto &DefaultArg = TTPD->getDefaultArgument().getArgument();
+ if (DefaultArg.getKind() != TemplateArgument::Type)
+ return QualType();
+ return DefaultArg.getAsType();
+}
+
QualType resolveDeclToType(const NamedDecl *D, ASTContext &Ctx) {
if (const auto *TempD = dyn_cast<TemplateDecl>(D)) {
D = TempD->getTemplatedDecl();
}
+ // Check this before the TypeDecl case below, which a TypedefNameDecl also
+ // satisfies: canonicalizing it would discard the TemplateTypeParmDecl that
+ // the default argument hangs off.
+ if (const auto *TND = dyn_cast<TypedefNameDecl>(D)) {
+ if (QualType Default = getDefaultTemplateArgument(TND->getUnderlyingType());
+ !Default.isNull())
+ return Default;
+ }
if (const auto *TD = dyn_cast<TypeDecl>(D))
return Ctx.getCanonicalTypeDeclType(TD);
if (const auto *VD = dyn_cast<ValueDecl>(D)) {
@@ -124,30 +156,6 @@ TemplateName getReferencedTemplateName(const Type *T) {
return TemplateName();
}
-// If `T` is a template parameter with a default argument, return that default
-// argument, otherwise return a null QualType.
-// We can't do anything useful with a template parameter itself (e.g. we cannot
-// look up member names inside it), so where one turns up, using its default
-// argument is a reasonable heuristic: it's what the parameter will be bound to
-// unless the instantiation site says otherwise.
-// Note that `T` must not be canonicalized: a canonical TemplateTypeParmType
-// does not retain its TemplateTypeParmDecl, and so has no default argument to
-// offer.
-QualType getDefaultTemplateArgument(QualType T) {
- // Use getAs() rather than a dyn_cast, so that we see through sugar such as a
- // member typedef naming the parameter (e.g. `typedef A allocator_type;`).
- const auto *TTPT = T.isNull() ? nullptr : T->getAs<TemplateTypeParmType>();
- if (!TTPT)
- return QualType();
- const auto *TTPD = TTPT->getDecl();
- if (!TTPD || !TTPD->hasDefaultArgument())
- return QualType();
- const auto &DefaultArg = TTPD->getDefaultArgument().getArgument();
- if (DefaultArg.getKind() != TemplateArgument::Type)
- return QualType();
- return DefaultArg.getAsType();
-}
-
// Helper function for HeuristicResolver::resolveDependentMember()
// which takes a possibly-dependent type `T` and heuristically
// resolves it to a CXXRecordDecl in which we can try name lookup.
@@ -160,22 +168,8 @@ TagDecl *HeuristicResolverImpl::resolveTypeToTagDecl(QualType QT) {
T = T->getCanonicalTypeInternal().getTypePtr();
if (const auto *DNT = T->getAs<DependentNameType>()) {
- auto Decls = resolveDependentNameType(DNT);
- QualType Resolved = resolveDeclsToType(Decls, Ctx);
- // `Resolved` is canonical, so if the name refers to a member typedef of a
- // template parameter (as `vector<T>::allocator_type` does), it has lost the
- // TemplateTypeParmDecl we would need to fall back to the parameter's
- // default argument. Consult the typedef's underlying type as written
- // instead.
- if (Decls.size() == 1) {
- if (const auto *TND = dyn_cast<TypedefNameDecl>(Decls[0])) {
- if (QualType Default =
- getDefaultTemplateArgument(TND->getUnderlyingType());
- !Default.isNull())
- Resolved = Default;
- }
- }
- T = Resolved.getTypePtrOrNull();
+ T = resolveDeclsToType(resolveDependentNameType(DNT), Ctx)
+ .getTypePtrOrNull();
if (!T)
return nullptr;
T = T->getCanonicalTypeInternal().getTypePtr();
diff --git a/clang/unittests/Sema/HeuristicResolverTest.cpp b/clang/unittests/Sema/HeuristicResolverTest.cpp
index b09877429df6d3..15f1b19c020da5 100644
--- a/clang/unittests/Sema/HeuristicResolverTest.cpp
+++ b/clang/unittests/Sema/HeuristicResolverTest.cpp
@@ -633,16 +633,22 @@ TEST(HeuristicResolver, MemberExpr_MemberTypedefWithoutDefaultArgument) {
struct S {
typedef T type;
};
+ struct Candidate {
+ void foo();
+ };
+ S<Candidate> s;
template <typename T>
void bar() {
typename S<T>::type t;
t.foo();
}
)cpp";
- // Test that "foo" in "t.foo()" does not resolve: "S<T>::type" names S's own
- // parameter T, which has no default argument to fall back on, and nothing
- // else says what T will be. This is the member-typedef analogue of
- // `std::vector<T>::value_type`.
+ // Test that "foo" in "t.foo()" does not resolve, which is the desired
+ // behaviour here rather than a limitation: "S<T>::type" names S's own
+ // parameter T, which has no default argument to fall back on. A candidate
+ // does exist -- Candidate::foo, and S is even instantiated with it -- but
+ // nothing in the template body suggests that this is the instantiation the
+ // reference will end up in, so resolving to it would be a guess.
expectResolution(
Code, &HeuristicResolver::resolveMemberExpr,
cxxDependentScopeMemberExpr(hasMemberName("foo")).bind("input"));
@@ -675,6 +681,63 @@ TEST(HeuristicResolver, MemberExpr_CallOfUsingDeclFromDependentBase) {
cxxMethodDecl(hasName("foo")).bind("output"));
}
+TEST(HeuristicResolver, MemberExpr_ExplicitArgumentIgnored) {
+ std::string Code = R"cpp(
+ struct Default {
+ void foo();
+ };
+ struct Custom {
+ void foo();
+ };
+ template <class T, class A = Default>
+ struct S {
+ A bar();
+ };
+ template <class T>
+ void baz() {
+ S<T, Custom> s;
+ s.bar().foo();
+ }
+ )cpp";
+ // FIXME: "foo" in "s.bar().foo()" should resolve to Custom::foo. Looking a
+ // name up in the primary template discards the arguments the type was
+ // written with, so the default argument is substituted for "A" even though
+ // "Custom" was given explicitly.
+ expectResolution(
+ Code, &HeuristicResolver::resolveMemberExpr,
+ cxxDependentScopeMemberExpr(hasMemberName("foo")).bind("input"),
+ cxxMethodDecl(hasName("foo"), ofClass(hasName("Default")))
+ .bind("output"));
+}
+
+TEST(HeuristicResolver, MemberExpr_ExplicitArgumentIgnored_Typedef) {
+ std::string Code = R"cpp(
+ struct Default {
+ void foo();
+ };
+ struct Custom {
+ void foo();
+ };
+ template <class T, class A = Default>
+ struct S {
+ typedef A type;
+ };
+ template <class T>
+ void baz() {
+ typename S<T, Custom>::type t;
+ t.foo();
+ }
+ )cpp";
+ // FIXME: as above, "foo" should resolve to Custom::foo. Reaching the
+ // parameter through a member typedef rather than a return type makes no
+ // difference: the explicit argument is still discarded.
+ expectResolution(
+ Code, &HeuristicResolver::resolveMemberExpr,
+ cxxDependentScopeMemberExpr(hasMemberName("foo")).bind("input"),
+ cxxMethodDecl(hasName("foo"), ofClass(hasName("Default")))
+ .bind("output"));
+}
+
TEST(HeuristicResolver, MemberExpr_DefaultTemplateArgumentNotSeenByBase) {
std::string Code = R"cpp(
struct Result {
@@ -695,12 +758,13 @@ TEST(HeuristicResolver, MemberExpr_DefaultTemplateArgumentNotSeenByBase) {
d.get().foo();
}
)cpp";
- // Test that "foo" in "d.get().foo()" does not resolve. Looking "get" up in
- // Base's primary template discards the arguments Derived passes to Base, so
- // the return type is Base's own "A", which -- unlike Derived's -- has no
- // default argument to fall back on. Resolving this would require propagating
- // template arguments to base classes, which this file's "look the name up in
- // the primary template" approach does not do.
+ // FIXME: "foo" in "d.get().foo()" would ideally resolve to Result::foo, but
+ // currently does not resolve at all. Looking "get" up in Base's primary
+ // template discards the arguments Derived passes to Base, so the return type
+ // is Base's own "A", which -- unlike Derived's -- has no default argument to
+ // fall back on. Resolving this would require propagating template arguments
+ // to base classes, which this file's "look the name up in the primary
+ // template" approach does not do.
//
// This mirrors libstdc++, where vector's allocator parameter is defaulted but
// _Vector_base's is not, and is why completion after `v.get_allocator().`
More information about the cfe-commits
mailing list