[clang] [Clang][Sema] Diagnose class member access expressions naming non-existent members of the current instantiation prior to instantiation in the absence of dependent base classes (PR #84050)
Krystian Stasiowski via cfe-commits
cfe-commits at lists.llvm.org
Tue Mar 5 10:03:13 PST 2024
https://github.com/sdkrystian updated https://github.com/llvm/llvm-project/pull/84050
>From 1495e87d7cbbab6a824d9d1d2bf53d0ef9ec9125 Mon Sep 17 00:00:00 2001
From: Krystian Stasiowski <sdkrystian at gmail.com>
Date: Fri, 1 Mar 2024 08:08:52 -0500
Subject: [PATCH 1/7] [Clang][Sema] Earlier resolution of class member access
expressions naming member of the current instantiation
---
clang/lib/AST/Expr.cpp | 2 +-
clang/lib/Sema/SemaExpr.cpp | 4 +-
clang/lib/Sema/SemaExprMember.cpp | 121 ++++++++++++++------
clang/lib/Sema/SemaOverload.cpp | 3 +
clang/test/SemaTemplate/dependent-names.cpp | 4 +-
5 files changed, 93 insertions(+), 41 deletions(-)
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index b4de2155adcebd..643c938d63c654 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -103,7 +103,7 @@ const Expr *Expr::skipRValueSubobjectAdjustments(
}
} else if (const auto *ME = dyn_cast<MemberExpr>(E)) {
if (!ME->isArrow()) {
- assert(ME->getBase()->getType()->isRecordType());
+ assert(ME->getBase()->getType()->getAsRecordDecl());
if (const auto *Field = dyn_cast<FieldDecl>(ME->getMemberDecl())) {
if (!Field->isBitField() && !Field->getType()->isReferenceType()) {
E = ME->getBase();
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 47bb263f56aade..e071c3e580b9d8 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -666,7 +666,9 @@ ExprResult Sema::DefaultLvalueConversion(Expr *E) {
// expressions of certain types in C++.
if (getLangOpts().CPlusPlus &&
(E->getType() == Context.OverloadTy ||
- T->isDependentType() ||
+ // FIXME: This is a hack! We want the lvalue-to-rvalue conversion applied
+ // to pointer types even if the pointee type is dependent.
+ (T->isDependentType() && !T->isPointerType()) ||
T->isRecordType()))
return E;
diff --git a/clang/lib/Sema/SemaExprMember.cpp b/clang/lib/Sema/SemaExprMember.cpp
index 32998ae60eafe2..fd55e6f46a1066 100644
--- a/clang/lib/Sema/SemaExprMember.cpp
+++ b/clang/lib/Sema/SemaExprMember.cpp
@@ -624,8 +624,8 @@ namespace {
// classes, one of its base classes.
class RecordMemberExprValidatorCCC final : public CorrectionCandidateCallback {
public:
- explicit RecordMemberExprValidatorCCC(const RecordType *RTy)
- : Record(RTy->getDecl()) {
+ explicit RecordMemberExprValidatorCCC(QualType RTy)
+ : Record(RTy->getAsRecordDecl()) {
// Don't add bare keywords to the consumer since they will always fail
// validation by virtue of not being associated with any decls.
WantTypeSpecifiers = false;
@@ -671,33 +671,55 @@ class RecordMemberExprValidatorCCC final : public CorrectionCandidateCallback {
static bool LookupMemberExprInRecord(Sema &SemaRef, LookupResult &R,
Expr *BaseExpr,
- const RecordType *RTy,
+ QualType RTy,
SourceLocation OpLoc, bool IsArrow,
CXXScopeSpec &SS, bool HasTemplateArgs,
SourceLocation TemplateKWLoc,
TypoExpr *&TE) {
+ RecordDecl *RDecl = RTy->getAsRecordDecl();
+ DeclContext *DC = SemaRef.computeDeclContext(RTy);
+ // If the object expression is dependent and isn't the current instantiation,
+ // lookup will not find anything and we must defer until instantiation.
+ if (!DC) {
+ R.setNotFoundInCurrentInstantiation();
+ return false;
+ }
+
+ // FIXME: Should this use Name.isDependentName()?
+ if (DeclarationName Name = R.getLookupName();
+ Name.getNameKind() == DeclarationName::CXXConversionFunctionName &&
+ Name.getCXXNameType()->isDependentType()) {
+ R.setNotFoundInCurrentInstantiation();
+ return false;
+ }
+
SourceRange BaseRange = BaseExpr ? BaseExpr->getSourceRange() : SourceRange();
- RecordDecl *RDecl = RTy->getDecl();
- if (!SemaRef.isThisOutsideMemberFunctionBody(QualType(RTy, 0)) &&
- SemaRef.RequireCompleteType(OpLoc, QualType(RTy, 0),
+ if (!RTy->isDependentType() &&
+ !SemaRef.isThisOutsideMemberFunctionBody(RTy) &&
+ SemaRef.RequireCompleteType(OpLoc, RTy,
diag::err_typecheck_incomplete_tag,
BaseRange))
return true;
if (HasTemplateArgs || TemplateKWLoc.isValid()) {
// LookupTemplateName doesn't expect these both to exist simultaneously.
- QualType ObjectType = SS.isSet() ? QualType() : QualType(RTy, 0);
+ QualType ObjectType = SS.isSet() ? QualType() : RTy;
bool MOUS;
return SemaRef.LookupTemplateName(R, nullptr, SS, ObjectType, false, MOUS,
TemplateKWLoc);
}
- DeclContext *DC = RDecl;
if (SS.isSet()) {
// If the member name was a qualified-id, look into the
// nested-name-specifier.
DC = SemaRef.computeDeclContext(SS, false);
+ // We tried to look into a dependent context that is not the current
+ // instantiation. Defer lookup until instantiation.
+ if (!DC) {
+ R.setNotFoundInCurrentInstantiation();
+ return false;
+ }
if (SemaRef.RequireCompleteDeclContext(SS, DC)) {
SemaRef.Diag(SS.getRange().getEnd(), diag::err_typecheck_incomplete_tag)
@@ -717,7 +739,7 @@ static bool LookupMemberExprInRecord(Sema &SemaRef, LookupResult &R,
// The record definition is complete, now look up the member.
SemaRef.LookupQualifiedName(R, DC, SS);
- if (!R.empty())
+ if (!R.empty() || R.wasNotFoundInCurrentInstantiation())
return false;
DeclarationName Typo = R.getLookupName();
@@ -781,6 +803,7 @@ Sema::BuildMemberReferenceExpr(Expr *Base, QualType BaseType,
const TemplateArgumentListInfo *TemplateArgs,
const Scope *S,
ActOnMemberAccessExtraArgs *ExtraArgs) {
+ #if 0
if (BaseType->isDependentType() ||
(SS.isSet() && isDependentScopeSpecifier(SS)) ||
NameInfo.getName().isDependentName())
@@ -788,6 +811,7 @@ Sema::BuildMemberReferenceExpr(Expr *Base, QualType BaseType,
IsArrow, OpLoc,
SS, TemplateKWLoc, FirstQualifierInScope,
NameInfo, TemplateArgs);
+ #endif
LookupResult R(*this, NameInfo, LookupMemberName);
@@ -797,7 +821,7 @@ Sema::BuildMemberReferenceExpr(Expr *Base, QualType BaseType,
QualType RecordTy = BaseType;
if (IsArrow) RecordTy = RecordTy->castAs<PointerType>()->getPointeeType();
if (LookupMemberExprInRecord(
- *this, R, nullptr, RecordTy->castAs<RecordType>(), OpLoc, IsArrow,
+ *this, R, nullptr, RecordTy, OpLoc, IsArrow,
SS, TemplateArgs != nullptr, TemplateKWLoc, TE))
return ExprError();
if (TE)
@@ -990,6 +1014,14 @@ Sema::BuildMemberReferenceExpr(Expr *BaseExpr, QualType BaseExprType,
const Scope *S,
bool SuppressQualifierCheck,
ActOnMemberAccessExtraArgs *ExtraArgs) {
+
+ if (R.wasNotFoundInCurrentInstantiation() || (SS.isValid() && !computeDeclContext(SS, false))) {
+ return ActOnDependentMemberExpr(BaseExpr, BaseExprType,
+ IsArrow, OpLoc,
+ SS, TemplateKWLoc, FirstQualifierInScope,
+ R.getLookupNameInfo(), TemplateArgs);
+ }
+
QualType BaseType = BaseExprType;
if (IsArrow) {
assert(BaseType->isPointerType());
@@ -1028,11 +1060,11 @@ Sema::BuildMemberReferenceExpr(Expr *BaseExpr, QualType BaseExprType,
// Rederive where we looked up.
DeclContext *DC = (SS.isSet()
? computeDeclContext(SS, false)
- : BaseType->castAs<RecordType>()->getDecl());
+ : BaseType->getAsRecordDecl());
if (ExtraArgs) {
ExprResult RetryExpr;
- if (!IsArrow && BaseExpr) {
+ if (!IsArrow && BaseExpr && !BaseExpr->isTypeDependent()) {
SFINAETrap Trap(*this, true);
ParsedType ObjectType;
bool MayBePseudoDestructor = false;
@@ -1055,9 +1087,16 @@ Sema::BuildMemberReferenceExpr(Expr *BaseExpr, QualType BaseExprType,
}
}
- Diag(R.getNameLoc(), diag::err_no_member)
- << MemberName << DC
- << (BaseExpr ? BaseExpr->getSourceRange() : SourceRange());
+ if(DC) {
+ Diag(R.getNameLoc(), diag::err_no_member)
+ << MemberName << DC
+ << (BaseExpr ? BaseExpr->getSourceRange() : SourceRange());
+ } else {
+ // FIXME: Is this needed?
+ Diag(R.getNameLoc(), diag::err_no_member)
+ << MemberName << BaseExprType
+ << (BaseExpr ? BaseExpr->getSourceRange() : SourceRange());
+ }
return ExprError();
}
@@ -1287,7 +1326,10 @@ static ExprResult LookupMemberExpr(Sema &S, LookupResult &R,
return ExprError();
QualType BaseType = BaseExpr.get()->getType();
+
+ #if 0
assert(!BaseType->isDependentType());
+ #endif
DeclarationName MemberName = R.getLookupName();
SourceLocation MemberLoc = R.getNameLoc();
@@ -1299,29 +1341,32 @@ static ExprResult LookupMemberExpr(Sema &S, LookupResult &R,
if (IsArrow) {
if (const PointerType *Ptr = BaseType->getAs<PointerType>())
BaseType = Ptr->getPointeeType();
- else if (const ObjCObjectPointerType *Ptr
+ else if (!BaseType->isDependentType()) {
+ if (const ObjCObjectPointerType *Ptr
= BaseType->getAs<ObjCObjectPointerType>())
BaseType = Ptr->getPointeeType();
- else if (BaseType->isRecordType()) {
- // Recover from arrow accesses to records, e.g.:
- // struct MyRecord foo;
- // foo->bar
- // This is actually well-formed in C++ if MyRecord has an
- // overloaded operator->, but that should have been dealt with
- // by now--or a diagnostic message already issued if a problem
- // was encountered while looking for the overloaded operator->.
- if (!S.getLangOpts().CPlusPlus) {
- S.Diag(OpLoc, diag::err_typecheck_member_reference_suggestion)
- << BaseType << int(IsArrow) << BaseExpr.get()->getSourceRange()
- << FixItHint::CreateReplacement(OpLoc, ".");
+ else if (BaseType->isRecordType()) {
+ // Recover from arrow accesses to records, e.g.:
+ // struct MyRecord foo;
+ // foo->bar
+ // This is actually well-formed in C++ if MyRecord has an
+ // overloaded operator->, but that should have been dealt with
+ // by now--or a diagnostic message already issued if a problem
+ // was encountered while looking for the overloaded operator->.
+ if (!S.getLangOpts().CPlusPlus) {
+ S.Diag(OpLoc, diag::err_typecheck_member_reference_suggestion)
+ << BaseType << int(IsArrow) << BaseExpr.get()->getSourceRange()
+ << FixItHint::CreateReplacement(OpLoc, ".");
+ }
+ IsArrow = false;
+ } else if (BaseType->isFunctionType()) {
+ goto fail;
+ } else {
+ S.Diag(MemberLoc, diag::err_typecheck_member_reference_arrow)
+ << BaseType << BaseExpr.get()->getSourceRange();
+ return ExprError();
}
- IsArrow = false;
- } else if (BaseType->isFunctionType()) {
- goto fail;
- } else {
- S.Diag(MemberLoc, diag::err_typecheck_member_reference_arrow)
- << BaseType << BaseExpr.get()->getSourceRange();
- return ExprError();
+
}
}
@@ -1341,9 +1386,9 @@ static ExprResult LookupMemberExpr(Sema &S, LookupResult &R,
}
// Handle field access to simple records.
- if (const RecordType *RTy = BaseType->getAs<RecordType>()) {
+ if (BaseType->getAsRecordDecl() || BaseType->isDependentType()) {
TypoExpr *TE = nullptr;
- if (LookupMemberExprInRecord(S, R, BaseExpr.get(), RTy, OpLoc, IsArrow, SS,
+ if (LookupMemberExprInRecord(S, R, BaseExpr.get(), BaseType, OpLoc, IsArrow, SS,
HasTemplateArgs, TemplateKWLoc, TE))
return ExprError();
@@ -1781,12 +1826,14 @@ ExprResult Sema::ActOnMemberAccessExpr(Scope *S, Expr *Base,
if (Result.isInvalid()) return ExprError();
Base = Result.get();
+ #if 0
if (Base->getType()->isDependentType() || Name.isDependentName() ||
isDependentScopeSpecifier(SS)) {
return ActOnDependentMemberExpr(Base, Base->getType(), IsArrow, OpLoc, SS,
TemplateKWLoc, FirstQualifierInScope,
NameInfo, TemplateArgs);
}
+ #endif
ActOnMemberAccessExtraArgs ExtraArgs = {S, Id, ObjCImpDecl};
ExprResult Res = BuildMemberReferenceExpr(
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 7d38043890ca20..7218553bcd77f8 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -5744,7 +5744,10 @@ static ImplicitConversionSequence TryObjectArgumentInitialization(
return ICS;
}
+ // FIXME: Should this check getAsRecordDecl instead?
+ #if 0
assert(FromType->isRecordType());
+ #endif
QualType ClassType = S.Context.getTypeDeclType(ActingContext);
// C++98 [class.dtor]p2:
diff --git a/clang/test/SemaTemplate/dependent-names.cpp b/clang/test/SemaTemplate/dependent-names.cpp
index 641ec950054f57..a661f14af80d34 100644
--- a/clang/test/SemaTemplate/dependent-names.cpp
+++ b/clang/test/SemaTemplate/dependent-names.cpp
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 %s
+// RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 %s
typedef double A;
template<typename T> class B {
@@ -334,7 +334,7 @@ int arr[sizeof(Sub)];
namespace PR11421 {
template < unsigned > struct X {
static const unsigned dimension = 3;
- template<unsigned dim=dimension>
+ template<unsigned dim=dimension>
struct Y: Y<dim> { }; // expected-error{{circular inheritance between 'Y<dim>' and 'Y<dim>'}}
};
typedef X<3> X3;
>From 4f149a17695867fae6487cf09c4a17652e5029d9 Mon Sep 17 00:00:00 2001
From: Krystian Stasiowski <sdkrystian at gmail.com>
Date: Fri, 1 Mar 2024 11:54:23 -0500
Subject: [PATCH 2/7] [FOLD] update tests
---
.../temp.res/temp.dep/temp.dep.type/p4.cpp | 110 ++++++++++++++++++
.../SemaTemplate/instantiate-function-1.cpp | 14 +--
2 files changed, 117 insertions(+), 7 deletions(-)
create mode 100644 clang/test/CXX/temp/temp.res/temp.dep/temp.dep.type/p4.cpp
diff --git a/clang/test/CXX/temp/temp.res/temp.dep/temp.dep.type/p4.cpp b/clang/test/CXX/temp/temp.res/temp.dep/temp.dep.type/p4.cpp
new file mode 100644
index 00000000000000..7b36e3ad3b0131
--- /dev/null
+++ b/clang/test/CXX/temp/temp.res/temp.dep/temp.dep.type/p4.cpp
@@ -0,0 +1,110 @@
+// RUN: %clang_cc1 -Wno-unused-value -verify %s
+
+namespace N0 {
+ template<typename T>
+ struct A {
+ int x;
+ void f();
+ using X = int;
+
+ void not_instantiated(A *a, A &b) {
+ x;
+ f();
+ new X;
+
+ this->x;
+ this->f();
+ this->A::x;
+ this->A::f();
+
+ a->x;
+ a->f();
+ a->A::x;
+ a->A::f();
+
+ (*this).x;
+ (*this).f();
+ (*this).A::x;
+ (*this).A::f();
+
+ b.x;
+ b.f();
+ b.A::x;
+ b.A::f();
+
+ A::x;
+ A::f();
+ new A::X;
+
+ y; // expected-error{{use of undeclared identifier 'y'}}
+ g(); // expected-error{{use of undeclared identifier 'g'}}
+ new Y; // expected-error{{unknown type name 'Y'}}
+
+ this->y; // expected-error{{no member named 'y' in 'A<T>'}}
+ this->g(); // expected-error{{no member named 'g' in 'A<T>'}}
+ this->A::y; // expected-error{{no member named 'y' in 'A<T>'}}
+ this->A::g(); // expected-error{{no member named 'g' in 'A<T>'}}
+
+ a->y; // expected-error{{no member named 'y' in 'A<T>'}}
+ a->g(); // expected-error{{no member named 'g' in 'A<T>'}}
+ a->A::y; // expected-error{{no member named 'y' in 'A<T>'}}
+ a->A::g(); // expected-error{{no member named 'g' in 'A<T>'}}
+
+ // FIXME: An overloaded unary 'operator*' is built for these
+ // even though the operand is a pointer (to a dependent type).
+ // Type::isOverloadableType should return false for such cases.
+ (*this).y;
+ (*this).g();
+ (*this).A::y;
+ (*this).A::g();
+
+ b.y; // expected-error{{no member named 'y' in 'A<T>'}}
+ b.g(); // expected-error{{no member named 'g' in 'A<T>'}}
+ b.A::y; // expected-error{{no member named 'y' in 'A<T>'}}
+ b.A::g(); // expected-error{{no member named 'g' in 'A<T>'}}
+
+ A::y; // expected-error{{no member named 'y' in 'A<T>'}}
+ A::g(); // expected-error{{no member named 'g' in 'A<T>'}}
+ new A::Y; // expected-error{{no type named 'Y' in 'A<T>'}}
+ }
+ };
+} // namespace N0
+
+namespace N1 {
+ struct A {
+ template<int I>
+ void f();
+ };
+
+ template<typename T>
+ struct B {
+ template<int I>
+ void f();
+
+ A x;
+ A g();
+
+ void not_instantiated(B *a, B &b) {
+ f<0>();
+ this->f<0>();
+ a->f<0>();
+ // FIXME: This should not require 'template'!
+ (*this).f<0>(); // expected-error{{missing 'template' keyword prior to dependent template name 'f'}}
+ b.f<0>();
+
+ x.f<0>();
+ this->x.f<0>();
+ a->x.f<0>();
+ // FIXME: This should not require 'template'!
+ (*this).x.f<0>(); // expected-error{{missing 'template' keyword prior to dependent template name 'f'}}
+ b.x.f<0>();
+
+ // FIXME: None of these should require 'template'!
+ g().f<0>(); // expected-error{{missing 'template' keyword prior to dependent template name 'f'}}
+ this->g().f<0>(); // expected-error{{missing 'template' keyword prior to dependent template name 'f'}}
+ a->g().f<0>(); // expected-error{{missing 'template' keyword prior to dependent template name 'f'}}
+ (*this).g().f<0>(); // expected-error{{missing 'template' keyword prior to dependent template name 'f'}}
+ b.g().f<0>(); // expected-error{{missing 'template' keyword prior to dependent template name 'f'}}
+ }
+ };
+} // namespace N1
diff --git a/clang/test/SemaTemplate/instantiate-function-1.cpp b/clang/test/SemaTemplate/instantiate-function-1.cpp
index ceef2743774805..a4967264c654b7 100644
--- a/clang/test/SemaTemplate/instantiate-function-1.cpp
+++ b/clang/test/SemaTemplate/instantiate-function-1.cpp
@@ -1,7 +1,7 @@
// RUN: %clang_cc1 -fcxx-exceptions -fexceptions -fsyntax-only -verify %s
template<typename T, typename U>
struct X0 {
- void f(T x, U y) {
+ void f(T x, U y) {
(void)(x + y); // expected-error{{invalid operands}}
}
};
@@ -41,7 +41,7 @@ template <typename T> struct X4 {
T f() const {
return; // expected-error{{non-void function 'f' should return a value}}
}
-
+
T g() const {
return 1; // expected-error{{void function 'g' should not return a value}}
}
@@ -64,7 +64,7 @@ template<typename T, typename U, typename V> struct X6 {
// IfStmt
if (t > 0)
return u;
- else {
+ else {
if (t < 0)
return v; // expected-error{{cannot initialize return object of type}}
}
@@ -131,12 +131,12 @@ template<typename T> struct Member0 {
t;
t.f;
t->f;
-
+
T* tp;
tp.f; // expected-error{{member reference base type 'T *' is not a structure or union}}
tp->f;
- this->f;
+ this->f; // expected-error{{reference to non-static member function must be called}}
this.f; // expected-error{{member reference base type 'Member0<T> *' is not a structure or union}}
}
};
@@ -239,11 +239,11 @@ namespace PR9880 {
static yes_tag check(char[sizeof(&U::luaIndex)]);
enum { value = sizeof(check<T>(0)) == sizeof(yes_tag) };
};
-
+
class SomeClass {
public:
int luaIndex(lua_State* L);
};
-
+
int i = HasIndexMetamethod<SomeClass>::value;
}
>From 2b67b4bb590c6ec9dbf89118a52d92c04daa1b86 Mon Sep 17 00:00:00 2001
From: Krystian Stasiowski <sdkrystian at gmail.com>
Date: Mon, 4 Mar 2024 06:59:58 -0500
Subject: [PATCH 3/7] [FOLD] build DependentMemberExpr for
LookupResult::FoundUnresolved
---
clang/lib/Sema/SemaExprMember.cpp | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/clang/lib/Sema/SemaExprMember.cpp b/clang/lib/Sema/SemaExprMember.cpp
index fd55e6f46a1066..95ad16571de8d6 100644
--- a/clang/lib/Sema/SemaExprMember.cpp
+++ b/clang/lib/Sema/SemaExprMember.cpp
@@ -676,7 +676,6 @@ static bool LookupMemberExprInRecord(Sema &SemaRef, LookupResult &R,
CXXScopeSpec &SS, bool HasTemplateArgs,
SourceLocation TemplateKWLoc,
TypoExpr *&TE) {
- RecordDecl *RDecl = RTy->getAsRecordDecl();
DeclContext *DC = SemaRef.computeDeclContext(RTy);
// If the object expression is dependent and isn't the current instantiation,
// lookup will not find anything and we must defer until instantiation.
@@ -1015,7 +1014,9 @@ Sema::BuildMemberReferenceExpr(Expr *BaseExpr, QualType BaseExprType,
bool SuppressQualifierCheck,
ActOnMemberAccessExtraArgs *ExtraArgs) {
- if (R.wasNotFoundInCurrentInstantiation() || (SS.isValid() && !computeDeclContext(SS, false))) {
+ if (R.wasNotFoundInCurrentInstantiation() ||
+ (SS.isValid() && !computeDeclContext(SS, false)) ||
+ (R.isUnresolvableResult() && R.isClassLookup() && R.getNamingClass()->isDependentContext())) {
return ActOnDependentMemberExpr(BaseExpr, BaseExprType,
IsArrow, OpLoc,
SS, TemplateKWLoc, FirstQualifierInScope,
>From 6cde66598f8ae689f252f61997928c81f046cf9a Mon Sep 17 00:00:00 2001
From: Krystian Stasiowski <sdkrystian at gmail.com>
Date: Tue, 5 Mar 2024 10:03:05 -0500
Subject: [PATCH 4/7] [FOLD] TransformOverloadExprDecls filters template names
---
clang/lib/Sema/SemaExprMember.cpp | 3 +--
clang/lib/Sema/TreeTransform.h | 20 ++++++++++++++++++++
2 files changed, 21 insertions(+), 2 deletions(-)
diff --git a/clang/lib/Sema/SemaExprMember.cpp b/clang/lib/Sema/SemaExprMember.cpp
index 95ad16571de8d6..fd3f8860d46f98 100644
--- a/clang/lib/Sema/SemaExprMember.cpp
+++ b/clang/lib/Sema/SemaExprMember.cpp
@@ -1015,8 +1015,7 @@ Sema::BuildMemberReferenceExpr(Expr *BaseExpr, QualType BaseExprType,
ActOnMemberAccessExtraArgs *ExtraArgs) {
if (R.wasNotFoundInCurrentInstantiation() ||
- (SS.isValid() && !computeDeclContext(SS, false)) ||
- (R.isUnresolvableResult() && R.isClassLookup() && R.getNamingClass()->isDependentContext())) {
+ (SS.isValid() && !computeDeclContext(SS, false))) {
return ActOnDependentMemberExpr(BaseExpr, BaseExprType,
IsArrow, OpLoc,
SS, TemplateKWLoc, FirstQualifierInScope,
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index 7389a48fe56fcc..27d3588aaa9082 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -12930,6 +12930,26 @@ bool TreeTransform<Derived>::TransformOverloadExprDecls(OverloadExpr *Old,
// Resolve a kind, but don't do any further analysis. If it's
// ambiguous, the callee needs to deal with it.
R.resolveKind();
+
+ if (Old->hasTemplateKeyword() && !R.empty()) {
+ NamedDecl *FoundDecl = R.getRepresentativeDecl()->getUnderlyingDecl();
+ getSema().FilterAcceptableTemplateNames(R,
+ /*AllowFunctionTemplates=*/true,
+ /*AllowDependent=*/true);
+ if (R.empty()) {
+ // If a 'template' keyword was used, a lookup that finds only non-template
+ // names is an error.
+ getSema().Diag(R.getNameLoc(), diag::err_template_kw_refers_to_non_template)
+ << R.getLookupName()
+ << Old->getQualifierLoc().getSourceRange()
+ << Old->hasTemplateKeyword()
+ << Old->getTemplateKeywordLoc();
+ getSema().Diag(FoundDecl->getLocation(), diag::note_template_kw_refers_to_non_template)
+ << R.getLookupName();
+ return true;
+ }
+ }
+
return false;
}
>From 4a57a13ac96a9e5562b026e15077f9fa9aad544a Mon Sep 17 00:00:00 2001
From: Krystian Stasiowski <sdkrystian at gmail.com>
Date: Tue, 5 Mar 2024 10:50:53 -0500
Subject: [PATCH 5/7] [FOLD] update tests
---
clang/test/AST/HLSL/this-reference-template.hlsl | 2 +-
clang/test/CodeGenCXX/mangle.cpp | 8 --------
clang/test/Index/annotate-nested-name-specifier.cpp | 4 ++--
clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp | 4 ++--
4 files changed, 5 insertions(+), 13 deletions(-)
diff --git a/clang/test/AST/HLSL/this-reference-template.hlsl b/clang/test/AST/HLSL/this-reference-template.hlsl
index 60e057986ebf80..d427e73044b788 100644
--- a/clang/test/AST/HLSL/this-reference-template.hlsl
+++ b/clang/test/AST/HLSL/this-reference-template.hlsl
@@ -24,7 +24,7 @@ void main() {
// CHECK: -CXXMethodDecl 0x{{[0-9A-Fa-f]+}} <line:8:3, line:10:3> line:8:5 getFirst 'K ()' implicit-inline
// CHECK-NEXT:-CompoundStmt 0x{{[0-9A-Fa-f]+}} <col:16, line:10:3>
// CHECK-NEXT:-ReturnStmt 0x{{[0-9A-Fa-f]+}} <line:9:4, col:16>
-// CHECK-NEXT:-CXXDependentScopeMemberExpr 0x{{[0-9A-Fa-f]+}} <col:11, col:16> '<dependent type>' lvalue .First
+// CHECK-NEXT:-MemberExpr 0x{{[0-9A-Fa-f]+}} <col:11, col:16> 'K' lvalue .First 0x{{[0-9A-Fa-f]+}}
// CHECK-NEXT:-CXXThisExpr 0x{{[0-9A-Fa-f]+}} <col:11> 'Pair<K, V>' lvalue this
// CHECK-NEXT:-CXXMethodDecl 0x{{[0-9A-Fa-f]+}} <line:12:3, line:14:3> line:12:5 getSecond 'V ()' implicit-inline
// CHECK-NEXT:-CompoundStmt 0x{{[0-9A-Fa-f]+}} <col:17, line:14:3>
diff --git a/clang/test/CodeGenCXX/mangle.cpp b/clang/test/CodeGenCXX/mangle.cpp
index 31467d943840e0..d0800af55c87e8 100644
--- a/clang/test/CodeGenCXX/mangle.cpp
+++ b/clang/test/CodeGenCXX/mangle.cpp
@@ -1032,10 +1032,6 @@ namespace test51 {
template <typename T>
decltype(S1<T>().~S1<T>(), S1<T>().~S1<T>()) fun4() {};
template <typename T>
- decltype(S1<int>().~S1<T>()) fun5(){};
- template <template <typename T> class U>
- decltype(S1<int>().~U<int>()) fun6(){};
- template <typename T>
decltype(E().E::~T()) fun7() {}
template <template <typename> class U>
decltype(X<int>::Y().U<int>::Y::~Y()) fun8() {}
@@ -1047,10 +1043,6 @@ namespace test51 {
// CHECK-LABEL: @_ZN6test514fun3I2S1IiEiEEDTcldtcvS1_IT0_E_EdnT_EEv
template void fun4<int>();
// CHECK-LABEL: @_ZN6test514fun4IiEEDTcmcldtcv2S1IT_E_Edn2S1IS2_EEcldtcvS3__Edn2S1IS2_EEEv
- template void fun5<int>();
- // CHECK-LABEL: @_ZN6test514fun5IiEEDTcldtcv2S1IiE_Edn2S1IT_EEEv
- template void fun6<S1>();
- // CHECK-LABEL: @_ZN6test514fun6I2S1EEDTcldtcvS1_IiE_EdnT_IiEEEv
template void fun7<E>();
// CHECK-LABEL: @_ZN6test514fun7INS_1EEEEDTcldtcvS1__Esr1EEdnT_EEv
template void fun8<X>();
diff --git a/clang/test/Index/annotate-nested-name-specifier.cpp b/clang/test/Index/annotate-nested-name-specifier.cpp
index a7338db6b05b77..3181497258407f 100644
--- a/clang/test/Index/annotate-nested-name-specifier.cpp
+++ b/clang/test/Index/annotate-nested-name-specifier.cpp
@@ -132,7 +132,7 @@ struct X8 {
struct X9 : X8 {
typedef X8 inherited;
- void f() {
+ void f() {
inherited::f();
}
};
@@ -299,7 +299,7 @@ struct X9 : X8 {
// CHECK: Identifier: "type" [77:16 - 77:20] TypeRef=X4::type:70:13
// CHECK: Punctuation: ">" [77:20 - 77:21] MemberRefExpr=
// CHECK: Punctuation: "::" [77:21 - 77:23] MemberRefExpr=
-// CHECK: Identifier: "g" [77:23 - 77:24] MemberRefExpr=
+// CHECK: Identifier: "g" [77:23 - 77:24] OverloadedDeclRef=
// CHECK: Punctuation: "(" [77:24 - 77:25] CallExpr=
// CHECK: Identifier: "t" [77:25 - 77:26] DeclRefExpr=t:74:12
// CHECK: Punctuation: ")" [77:26 - 77:27] CallExpr=
diff --git a/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp b/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp
index b75da7bc1ed069..3af67671aa5b5c 100644
--- a/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp
+++ b/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp
@@ -1569,8 +1569,8 @@ TEST_P(ASTMatchersTest, IsArrow_MatchesMemberVariablesViaArrow) {
matches("class Y { void x() { y; } int y; };", memberExpr(isArrow())));
EXPECT_TRUE(notMatches("class Y { void x() { (*this).y; } int y; };",
memberExpr(isArrow())));
- EXPECT_TRUE(matches("template <class T> class Y { void x() { this->m; } };",
- cxxDependentScopeMemberExpr(isArrow())));
+ EXPECT_TRUE(matches("template <class T> class Y { void x() { this->m; } int m; };",
+ memberExpr(isArrow())));
EXPECT_TRUE(
notMatches("template <class T> class Y { void x() { (*this).m; } };",
cxxDependentScopeMemberExpr(isArrow())));
>From 5fc710a43e7799d2721e56dd53d5828e55090a0d Mon Sep 17 00:00:00 2001
From: Krystian Stasiowski <sdkrystian at gmail.com>
Date: Tue, 5 Mar 2024 12:33:40 -0500
Subject: [PATCH 6/7] [FOLD] cleanup
---
clang/lib/Sema/SemaExprMember.cpp | 19 -------------------
clang/test/SemaTemplate/dependent-names.cpp | 4 ++--
2 files changed, 2 insertions(+), 21 deletions(-)
diff --git a/clang/lib/Sema/SemaExprMember.cpp b/clang/lib/Sema/SemaExprMember.cpp
index fd3f8860d46f98..eb87e0edda0128 100644
--- a/clang/lib/Sema/SemaExprMember.cpp
+++ b/clang/lib/Sema/SemaExprMember.cpp
@@ -802,16 +802,6 @@ Sema::BuildMemberReferenceExpr(Expr *Base, QualType BaseType,
const TemplateArgumentListInfo *TemplateArgs,
const Scope *S,
ActOnMemberAccessExtraArgs *ExtraArgs) {
- #if 0
- if (BaseType->isDependentType() ||
- (SS.isSet() && isDependentScopeSpecifier(SS)) ||
- NameInfo.getName().isDependentName())
- return ActOnDependentMemberExpr(Base, BaseType,
- IsArrow, OpLoc,
- SS, TemplateKWLoc, FirstQualifierInScope,
- NameInfo, TemplateArgs);
- #endif
-
LookupResult R(*this, NameInfo, LookupMemberName);
// Implicit member accesses.
@@ -1826,15 +1816,6 @@ ExprResult Sema::ActOnMemberAccessExpr(Scope *S, Expr *Base,
if (Result.isInvalid()) return ExprError();
Base = Result.get();
- #if 0
- if (Base->getType()->isDependentType() || Name.isDependentName() ||
- isDependentScopeSpecifier(SS)) {
- return ActOnDependentMemberExpr(Base, Base->getType(), IsArrow, OpLoc, SS,
- TemplateKWLoc, FirstQualifierInScope,
- NameInfo, TemplateArgs);
- }
- #endif
-
ActOnMemberAccessExtraArgs ExtraArgs = {S, Id, ObjCImpDecl};
ExprResult Res = BuildMemberReferenceExpr(
Base, Base->getType(), OpLoc, IsArrow, SS, TemplateKWLoc,
diff --git a/clang/test/SemaTemplate/dependent-names.cpp b/clang/test/SemaTemplate/dependent-names.cpp
index a661f14af80d34..641ec950054f57 100644
--- a/clang/test/SemaTemplate/dependent-names.cpp
+++ b/clang/test/SemaTemplate/dependent-names.cpp
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 %s
+// RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 %s
typedef double A;
template<typename T> class B {
@@ -334,7 +334,7 @@ int arr[sizeof(Sub)];
namespace PR11421 {
template < unsigned > struct X {
static const unsigned dimension = 3;
- template<unsigned dim=dimension>
+ template<unsigned dim=dimension>
struct Y: Y<dim> { }; // expected-error{{circular inheritance between 'Y<dim>' and 'Y<dim>'}}
};
typedef X<3> X3;
>From 15c28809ba87909bffbf930e5c8f3c549a9e3828 Mon Sep 17 00:00:00 2001
From: Krystian Stasiowski <sdkrystian at gmail.com>
Date: Tue, 5 Mar 2024 13:03:00 -0500
Subject: [PATCH 7/7] [FOLD] format
---
clang/lib/Sema/SemaExpr.cpp | 7 +--
clang/lib/Sema/SemaExprMember.cpp | 59 ++++++++-----------
clang/lib/Sema/SemaOverload.cpp | 6 +-
clang/lib/Sema/TreeTransform.h | 14 ++---
.../ASTMatchers/ASTMatchersNarrowingTest.cpp | 5 +-
5 files changed, 41 insertions(+), 50 deletions(-)
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index e071c3e580b9d8..22216dc9e0fb5f 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -666,10 +666,9 @@ ExprResult Sema::DefaultLvalueConversion(Expr *E) {
// expressions of certain types in C++.
if (getLangOpts().CPlusPlus &&
(E->getType() == Context.OverloadTy ||
- // FIXME: This is a hack! We want the lvalue-to-rvalue conversion applied
- // to pointer types even if the pointee type is dependent.
- (T->isDependentType() && !T->isPointerType()) ||
- T->isRecordType()))
+ // FIXME: This is a hack! We want the lvalue-to-rvalue conversion applied
+ // to pointer types even if the pointee type is dependent.
+ (T->isDependentType() && !T->isPointerType()) || T->isRecordType()))
return E;
// The C standard is actually really unclear on this point, and
diff --git a/clang/lib/Sema/SemaExprMember.cpp b/clang/lib/Sema/SemaExprMember.cpp
index eb87e0edda0128..da32ea251aeee1 100644
--- a/clang/lib/Sema/SemaExprMember.cpp
+++ b/clang/lib/Sema/SemaExprMember.cpp
@@ -670,8 +670,7 @@ class RecordMemberExprValidatorCCC final : public CorrectionCandidateCallback {
}
static bool LookupMemberExprInRecord(Sema &SemaRef, LookupResult &R,
- Expr *BaseExpr,
- QualType RTy,
+ Expr *BaseExpr, QualType RTy,
SourceLocation OpLoc, bool IsArrow,
CXXScopeSpec &SS, bool HasTemplateArgs,
SourceLocation TemplateKWLoc,
@@ -695,9 +694,8 @@ static bool LookupMemberExprInRecord(Sema &SemaRef, LookupResult &R,
SourceRange BaseRange = BaseExpr ? BaseExpr->getSourceRange() : SourceRange();
if (!RTy->isDependentType() &&
!SemaRef.isThisOutsideMemberFunctionBody(RTy) &&
- SemaRef.RequireCompleteType(OpLoc, RTy,
- diag::err_typecheck_incomplete_tag,
- BaseRange))
+ SemaRef.RequireCompleteType(
+ OpLoc, RTy, diag::err_typecheck_incomplete_tag, BaseRange))
return true;
if (HasTemplateArgs || TemplateKWLoc.isValid()) {
@@ -792,16 +790,12 @@ static ExprResult LookupMemberExpr(Sema &S, LookupResult &R,
Decl *ObjCImpDecl, bool HasTemplateArgs,
SourceLocation TemplateKWLoc);
-ExprResult
-Sema::BuildMemberReferenceExpr(Expr *Base, QualType BaseType,
- SourceLocation OpLoc, bool IsArrow,
- CXXScopeSpec &SS,
- SourceLocation TemplateKWLoc,
- NamedDecl *FirstQualifierInScope,
- const DeclarationNameInfo &NameInfo,
- const TemplateArgumentListInfo *TemplateArgs,
- const Scope *S,
- ActOnMemberAccessExtraArgs *ExtraArgs) {
+ExprResult Sema::BuildMemberReferenceExpr(
+ Expr *Base, QualType BaseType, SourceLocation OpLoc, bool IsArrow,
+ CXXScopeSpec &SS, SourceLocation TemplateKWLoc,
+ NamedDecl *FirstQualifierInScope, const DeclarationNameInfo &NameInfo,
+ const TemplateArgumentListInfo *TemplateArgs, const Scope *S,
+ ActOnMemberAccessExtraArgs *ExtraArgs) {
LookupResult R(*this, NameInfo, LookupMemberName);
// Implicit member accesses.
@@ -809,9 +803,9 @@ Sema::BuildMemberReferenceExpr(Expr *Base, QualType BaseType,
TypoExpr *TE = nullptr;
QualType RecordTy = BaseType;
if (IsArrow) RecordTy = RecordTy->castAs<PointerType>()->getPointeeType();
- if (LookupMemberExprInRecord(
- *this, R, nullptr, RecordTy, OpLoc, IsArrow,
- SS, TemplateArgs != nullptr, TemplateKWLoc, TE))
+ if (LookupMemberExprInRecord(*this, R, nullptr, RecordTy, OpLoc, IsArrow,
+ SS, TemplateArgs != nullptr, TemplateKWLoc,
+ TE))
return ExprError();
if (TE)
return TE;
@@ -1006,9 +1000,8 @@ Sema::BuildMemberReferenceExpr(Expr *BaseExpr, QualType BaseExprType,
if (R.wasNotFoundInCurrentInstantiation() ||
(SS.isValid() && !computeDeclContext(SS, false))) {
- return ActOnDependentMemberExpr(BaseExpr, BaseExprType,
- IsArrow, OpLoc,
- SS, TemplateKWLoc, FirstQualifierInScope,
+ return ActOnDependentMemberExpr(BaseExpr, BaseExprType, IsArrow, OpLoc, SS,
+ TemplateKWLoc, FirstQualifierInScope,
R.getLookupNameInfo(), TemplateArgs);
}
@@ -1048,9 +1041,8 @@ Sema::BuildMemberReferenceExpr(Expr *BaseExpr, QualType BaseExprType,
if (R.empty()) {
// Rederive where we looked up.
- DeclContext *DC = (SS.isSet()
- ? computeDeclContext(SS, false)
- : BaseType->getAsRecordDecl());
+ DeclContext *DC = (SS.isSet() ? computeDeclContext(SS, false)
+ : BaseType->getAsRecordDecl());
if (ExtraArgs) {
ExprResult RetryExpr;
@@ -1077,7 +1069,7 @@ Sema::BuildMemberReferenceExpr(Expr *BaseExpr, QualType BaseExprType,
}
}
- if(DC) {
+ if (DC) {
Diag(R.getNameLoc(), diag::err_no_member)
<< MemberName << DC
<< (BaseExpr ? BaseExpr->getSourceRange() : SourceRange());
@@ -1317,9 +1309,9 @@ static ExprResult LookupMemberExpr(Sema &S, LookupResult &R,
QualType BaseType = BaseExpr.get()->getType();
- #if 0
+#if 0
assert(!BaseType->isDependentType());
- #endif
+#endif
DeclarationName MemberName = R.getLookupName();
SourceLocation MemberLoc = R.getNameLoc();
@@ -1332,12 +1324,12 @@ static ExprResult LookupMemberExpr(Sema &S, LookupResult &R,
if (const PointerType *Ptr = BaseType->getAs<PointerType>())
BaseType = Ptr->getPointeeType();
else if (!BaseType->isDependentType()) {
- if (const ObjCObjectPointerType *Ptr
- = BaseType->getAs<ObjCObjectPointerType>())
- BaseType = Ptr->getPointeeType();
+ if (const ObjCObjectPointerType *Ptr =
+ BaseType->getAs<ObjCObjectPointerType>())
+ BaseType = Ptr->getPointeeType();
else if (BaseType->isRecordType()) {
// Recover from arrow accesses to records, e.g.:
- // struct MyRecord foo;
+ // struct MyRecord foo;
// foo->bar
// This is actually well-formed in C++ if MyRecord has an
// overloaded operator->, but that should have been dealt with
@@ -1356,7 +1348,6 @@ static ExprResult LookupMemberExpr(Sema &S, LookupResult &R,
<< BaseType << BaseExpr.get()->getSourceRange();
return ExprError();
}
-
}
}
@@ -1378,8 +1369,8 @@ static ExprResult LookupMemberExpr(Sema &S, LookupResult &R,
// Handle field access to simple records.
if (BaseType->getAsRecordDecl() || BaseType->isDependentType()) {
TypoExpr *TE = nullptr;
- if (LookupMemberExprInRecord(S, R, BaseExpr.get(), BaseType, OpLoc, IsArrow, SS,
- HasTemplateArgs, TemplateKWLoc, TE))
+ if (LookupMemberExprInRecord(S, R, BaseExpr.get(), BaseType, OpLoc, IsArrow,
+ SS, HasTemplateArgs, TemplateKWLoc, TE))
return ExprError();
// Returning valid-but-null is how we indicate to the caller that
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 7218553bcd77f8..27de4045311547 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -5744,10 +5744,10 @@ static ImplicitConversionSequence TryObjectArgumentInitialization(
return ICS;
}
- // FIXME: Should this check getAsRecordDecl instead?
- #if 0
+// FIXME: Should this check getAsRecordDecl instead?
+#if 0
assert(FromType->isRecordType());
- #endif
+#endif
QualType ClassType = S.Context.getTypeDeclType(ActingContext);
// C++98 [class.dtor]p2:
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index 27d3588aaa9082..585f4f11af66ee 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -12939,14 +12939,14 @@ bool TreeTransform<Derived>::TransformOverloadExprDecls(OverloadExpr *Old,
if (R.empty()) {
// If a 'template' keyword was used, a lookup that finds only non-template
// names is an error.
- getSema().Diag(R.getNameLoc(), diag::err_template_kw_refers_to_non_template)
- << R.getLookupName()
- << Old->getQualifierLoc().getSourceRange()
- << Old->hasTemplateKeyword()
- << Old->getTemplateKeywordLoc();
- getSema().Diag(FoundDecl->getLocation(), diag::note_template_kw_refers_to_non_template)
+ getSema().Diag(R.getNameLoc(),
+ diag::err_template_kw_refers_to_non_template)
+ << R.getLookupName() << Old->getQualifierLoc().getSourceRange()
+ << Old->hasTemplateKeyword() << Old->getTemplateKeywordLoc();
+ getSema().Diag(FoundDecl->getLocation(),
+ diag::note_template_kw_refers_to_non_template)
<< R.getLookupName();
- return true;
+ return true;
}
}
diff --git a/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp b/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp
index 3af67671aa5b5c..16b18b80f29019 100644
--- a/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp
+++ b/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp
@@ -1569,8 +1569,9 @@ TEST_P(ASTMatchersTest, IsArrow_MatchesMemberVariablesViaArrow) {
matches("class Y { void x() { y; } int y; };", memberExpr(isArrow())));
EXPECT_TRUE(notMatches("class Y { void x() { (*this).y; } int y; };",
memberExpr(isArrow())));
- EXPECT_TRUE(matches("template <class T> class Y { void x() { this->m; } int m; };",
- memberExpr(isArrow())));
+ EXPECT_TRUE(
+ matches("template <class T> class Y { void x() { this->m; } int m; };",
+ memberExpr(isArrow())));
EXPECT_TRUE(
notMatches("template <class T> class Y { void x() { (*this).m; } };",
cxxDependentScopeMemberExpr(isArrow())));
More information about the cfe-commits
mailing list