[clang] [clang-tools-extra] [Clang][Sema]Report error for non-base types in constructor initializers before causing issue in initializer order (PR #201379)
Gábor Spaits via cfe-commits
cfe-commits at lists.llvm.org
Wed Jun 24 02:26:41 PDT 2026
https://github.com/spaits updated https://github.com/llvm/llvm-project/pull/201379
>From cd148bf201459dbe9ac18b8cf4a473147a67ab1e Mon Sep 17 00:00:00 2001
From: Gabor Spaits <gaborspaits1 at gmail.com>
Date: Thu, 4 Jun 2026 21:50:26 +0200
Subject: [PATCH 1/5] [Sema]Report error for non-base types in constructor
initializers before causing issue in initializer order
---
clang/lib/Sema/SemaDeclCXX.cpp | 49 +++++++++++++++++-----------------
1 file changed, 25 insertions(+), 24 deletions(-)
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 418ff01f3d98a..215bd3a7d3327 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -17,6 +17,7 @@
#include "clang/AST/CXXInheritance.h"
#include "clang/AST/CharUnits.h"
#include "clang/AST/ComparisonCategories.h"
+#include "clang/AST/DeclBase.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/DynamicRecursiveASTVisitor.h"
@@ -4819,33 +4820,33 @@ Sema::BuildBaseInitializer(QualType BaseType, TypeSourceInfo *BaseTInfo,
return true;
}
+ if (!Dependent && declaresSameEntity(ClassDecl, BaseType->getAsCXXRecordDecl()))
+ return BuildDelegatingInitializer(BaseTInfo, Init, ClassDecl);
+
// Check for direct and virtual base classes.
const CXXBaseSpecifier *DirectBaseSpec = nullptr;
const CXXBaseSpecifier *VirtualBaseSpec = nullptr;
- if (!Dependent) {
- if (declaresSameEntity(ClassDecl, BaseType->getAsCXXRecordDecl()))
- return BuildDelegatingInitializer(BaseTInfo, Init, ClassDecl);
-
- FindBaseInitializer(*this, ClassDecl, BaseType, DirectBaseSpec,
- VirtualBaseSpec);
-
- // C++ [base.class.init]p2:
- // Unless the mem-initializer-id names a nonstatic data member of the
- // constructor's class or a direct or virtual base of that class, the
- // mem-initializer is ill-formed.
- if (!DirectBaseSpec && !VirtualBaseSpec) {
- // If the class has any dependent bases, then it's possible that
- // one of those types will resolve to the same type as
- // BaseType. Therefore, just treat this as a dependent base
- // class initialization. FIXME: Should we try to check the
- // initialization anyway? It seems odd.
- if (ClassDecl->hasAnyDependentBases())
- Dependent = true;
- else
- return Diag(BaseLoc, diag::err_not_direct_base_or_virtual)
- << BaseType << Context.getCanonicalTagType(ClassDecl)
- << BaseTInfo->getTypeLoc().getSourceRange();
- }
+
+ FindBaseInitializer(*this, ClassDecl, BaseType, DirectBaseSpec,
+ VirtualBaseSpec);
+
+ // C++ [base.class.init]p2:
+ // Unless the mem-initializer-id names a nonstatic data member of the
+ // constructor's class or a direct or virtual base of that class, the
+ // mem-initializer is ill-formed.
+ if (!DirectBaseSpec && !VirtualBaseSpec) {
+ // If the class has any dependent bases, then it's possible that
+ // one of those types will resolve to the same type as
+ // BaseType. Therefore, just treat this as a dependent base
+ // class initialization. FIXME: Should we try to check the
+ // initialization anyway? It seems odd.
+ if (ClassDecl->hasAnyDependentBases())
+ Dependent = true;
+ // We may have a delegating initializer
+ else if (!declaresSameEntity(ClassDecl, BaseType->getAsCXXRecordDecl()))
+ return Diag(BaseLoc, diag::err_not_direct_base_or_virtual)
+ << BaseType << Context.getCanonicalTagType(ClassDecl)
+ << BaseTInfo->getTypeLoc().getSourceRange();
}
if (Dependent) {
>From 60e1c1fdeadc699efc7d08edca5862f63f584bf3 Mon Sep 17 00:00:00 2001
From: Gabor Spaits <gaborspaits1 at gmail.com>
Date: Thu, 4 Jun 2026 22:26:40 +0200
Subject: [PATCH 2/5] Fix and clean up testing
---
.../cppcoreguidelines/pro-type-member-init.cpp | 2 +-
clang/test/SemaCXX/constructor-initializer.cpp | 18 ++++++++++++++++++
.../warn-reorder-ctor-initialization.cpp | 10 ----------
3 files changed, 19 insertions(+), 11 deletions(-)
diff --git a/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines/pro-type-member-init.cpp b/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines/pro-type-member-init.cpp
index 55963eae2c1b5..e1724c3c1e338 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines/pro-type-member-init.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines/pro-type-member-init.cpp
@@ -647,7 +647,7 @@ namespace gh192510 {
};
template<typename T>
- class X: public Base {
+ class X: public Base, private C<T> {
using INT = C<T>;
X(INT i) : INT(i) {} // no crash
diff --git a/clang/test/SemaCXX/constructor-initializer.cpp b/clang/test/SemaCXX/constructor-initializer.cpp
index 96be8dda97735..ff6374d6eb6ef 100644
--- a/clang/test/SemaCXX/constructor-initializer.cpp
+++ b/clang/test/SemaCXX/constructor-initializer.cpp
@@ -323,3 +323,21 @@ A f2(const B &b) {
return b; // expected-error {{no matching constructor for initialization of 'B'}}
}
}
+
+namespace PR7179 {
+struct X
+{
+ struct Y
+ {
+ template <class T> Y(T x) : X(x) { } // expected-error {{type 'X' is not a direct or virtual base of 'PR7179::X::Y'}}
+ };
+};
+}
+
+namespace PR201379 {
+struct S1 {
+ template<typename T>
+ S1(T) : d(), T() {} // expected-error {{type 'T' is not a direct or virtual base of 'PR201379::S1'}}
+ int d;
+};
+}
diff --git a/clang/test/SemaCXX/warn-reorder-ctor-initialization.cpp b/clang/test/SemaCXX/warn-reorder-ctor-initialization.cpp
index 0613338945978..b0153c6984b5b 100644
--- a/clang/test/SemaCXX/warn-reorder-ctor-initialization.cpp
+++ b/clang/test/SemaCXX/warn-reorder-ctor-initialization.cpp
@@ -120,16 +120,6 @@ namespace test3 {
};
}
-namespace PR7179 {
- struct X
- {
- struct Y
- {
- template <class T> Y(T x) : X(x) { }
- };
- };
-}
-
namespace test3 {
struct foo {
struct {
>From 10ce0dacdf643412bf7a9a8871f74d6cf433a3d2 Mon Sep 17 00:00:00 2001
From: Gabor Spaits <gaborspaits1 at gmail.com>
Date: Thu, 4 Jun 2026 22:30:46 +0200
Subject: [PATCH 3/5] Fix and clean up testing and some formatting too
---
clang/lib/Sema/SemaDeclCXX.cpp | 16 +++++++++-------
1 file changed, 9 insertions(+), 7 deletions(-)
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 215bd3a7d3327..aeb4258e9603d 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -4820,7 +4820,8 @@ Sema::BuildBaseInitializer(QualType BaseType, TypeSourceInfo *BaseTInfo,
return true;
}
- if (!Dependent && declaresSameEntity(ClassDecl, BaseType->getAsCXXRecordDecl()))
+ if (!Dependent &&
+ declaresSameEntity(ClassDecl, BaseType->getAsCXXRecordDecl()))
return BuildDelegatingInitializer(BaseTInfo, Init, ClassDecl);
// Check for direct and virtual base classes.
@@ -4835,14 +4836,15 @@ Sema::BuildBaseInitializer(QualType BaseType, TypeSourceInfo *BaseTInfo,
// constructor's class or a direct or virtual base of that class, the
// mem-initializer is ill-formed.
if (!DirectBaseSpec && !VirtualBaseSpec) {
- // If the class has any dependent bases, then it's possible that
- // one of those types will resolve to the same type as
- // BaseType. Therefore, just treat this as a dependent base
- // class initialization. FIXME: Should we try to check the
- // initialization anyway? It seems odd.
+ // If the class has any dependent bases, then it's possible that one of
+ // those types will resolve to the same type as BaseType. Therefore, just
+ // treat this as a dependent base class initialization.
+ // FIXME: Should we try to check the initialization anyway? It seems odd.
if (ClassDecl->hasAnyDependentBases())
Dependent = true;
- // We may have a delegating initializer
+ // We may have a delegating initializer here, since that is also a type,
+ // that isn't a direct or virtual base of the instantiated type. That will
+ // be handled later.
else if (!declaresSameEntity(ClassDecl, BaseType->getAsCXXRecordDecl()))
return Diag(BaseLoc, diag::err_not_direct_base_or_virtual)
<< BaseType << Context.getCanonicalTagType(ClassDecl)
>From 32b76252ff31ee9a376e84a018c233717e537038 Mon Sep 17 00:00:00 2001
From: Gabor Spaits <gaborspaits1 at gmail.com>
Date: Thu, 4 Jun 2026 22:35:19 +0200
Subject: [PATCH 4/5] Comment
---
clang/lib/Sema/SemaDeclCXX.cpp | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index aeb4258e9603d..125f2ba82553a 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -4842,9 +4842,9 @@ Sema::BuildBaseInitializer(QualType BaseType, TypeSourceInfo *BaseTInfo,
// FIXME: Should we try to check the initialization anyway? It seems odd.
if (ClassDecl->hasAnyDependentBases())
Dependent = true;
- // We may have a delegating initializer here, since that is also a type,
- // that isn't a direct or virtual base of the instantiated type. That will
- // be handled later.
+ // We may have a delegating initializer here but in a dependent context.
+ // Since that is also a type, that isn't a direct or virtual base of the
+ // instantiated type. That will be handled later.
else if (!declaresSameEntity(ClassDecl, BaseType->getAsCXXRecordDecl()))
return Diag(BaseLoc, diag::err_not_direct_base_or_virtual)
<< BaseType << Context.getCanonicalTagType(ClassDecl)
>From 393f244a70537d93051e82b4795e4e5c721a51e2 Mon Sep 17 00:00:00 2001
From: Gabor Spaits <gaborspaits1 at gmail.com>
Date: Wed, 24 Jun 2026 11:26:12 +0200
Subject: [PATCH 5/5] Think of the case where there is a base class
---
clang/lib/Sema/SemaDeclCXX.cpp | 68 +++++++++++++++++++++++-----------
1 file changed, 46 insertions(+), 22 deletions(-)
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 125f2ba82553a..3e62fe3d30631 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -4802,6 +4802,7 @@ Sema::BuildBaseInitializer(QualType BaseType, TypeSourceInfo *BaseTInfo,
bool Dependent = CurContext->isDependentContext() &&
(BaseType->isDependentType() || Init->isTypeDependent());
+ llvm::errs() << "Dependent? " << Dependent << '\n';
SourceRange InitRange = Init->getSourceRange();
if (EllipsisLoc.isValid()) {
// This is a pack expansion.
@@ -4828,28 +4829,51 @@ Sema::BuildBaseInitializer(QualType BaseType, TypeSourceInfo *BaseTInfo,
const CXXBaseSpecifier *DirectBaseSpec = nullptr;
const CXXBaseSpecifier *VirtualBaseSpec = nullptr;
- FindBaseInitializer(*this, ClassDecl, BaseType, DirectBaseSpec,
- VirtualBaseSpec);
-
- // C++ [base.class.init]p2:
- // Unless the mem-initializer-id names a nonstatic data member of the
- // constructor's class or a direct or virtual base of that class, the
- // mem-initializer is ill-formed.
- if (!DirectBaseSpec && !VirtualBaseSpec) {
- // If the class has any dependent bases, then it's possible that one of
- // those types will resolve to the same type as BaseType. Therefore, just
- // treat this as a dependent base class initialization.
- // FIXME: Should we try to check the initialization anyway? It seems odd.
- if (ClassDecl->hasAnyDependentBases())
- Dependent = true;
- // We may have a delegating initializer here but in a dependent context.
- // Since that is also a type, that isn't a direct or virtual base of the
- // instantiated type. That will be handled later.
- else if (!declaresSameEntity(ClassDecl, BaseType->getAsCXXRecordDecl()))
- return Diag(BaseLoc, diag::err_not_direct_base_or_virtual)
- << BaseType << Context.getCanonicalTagType(ClassDecl)
- << BaseTInfo->getTypeLoc().getSourceRange();
- }
+ FindBaseInitializer(*this, ClassDecl, BaseType, DirectBaseSpec,
+ VirtualBaseSpec);
+
+ // C++ [base.class.init]p2:
+ // Unless the mem-initializer-id names a nonstatic data member of the
+ // constructor's class or a direct or virtual base of that class, the
+ // mem-initializer is ill-formed.
+ if (!DirectBaseSpec && !VirtualBaseSpec) {
+ // If the class has any dependent bases, then it's possible that one of
+ // those types will resolve to the same type as BaseType. Therefore, just
+ // treat this as a dependent base class initialization.
+ // FIXME: Should we try to check the initialization anyway? It seems odd.
+ if (ClassDecl->hasAnyDependentBases())
+ Dependent = true;
+ // We may have a delegating initializer here but in a dependent context.
+ // Since that is also a type, that isn't a direct or virtual base of the
+ // instantiated type. That will be handled later.
+ //
+ // Another scenario we may get here is when the initialization list contains
+ // a type template parameter. If the class has any bases classes, the program can be valid, but if not, then the program must be invalid.
+ // For example, the following case must be invalid, no matter how we choose the type of the template at initialization:
+ //
+ // struct S0 {
+ // S0(int) {}
+ // template <class T>
+ // S0(T) : T(42), Member(42) {};
+ // int Member{21};
+ // };
+ //
+ // This code can be valid, if we choose `SomeBase` as our template argument:
+ //
+ // class SomeBase {};
+ //
+ // struct S0 : SomeBase {
+ // S0(int) {}
+ // template <class T>
+ // S0(T) : Member(42), T(42) {};
+ // int Member{21};
+ // };
+ else if (!declaresSameEntity(ClassDecl, BaseType->getAsCXXRecordDecl()) &&
+ ClassDecl->bases().empty())
+ return Diag(BaseLoc, diag::err_not_direct_base_or_virtual)
+ << BaseType << Context.getCanonicalTagType(ClassDecl)
+ << BaseTInfo->getTypeLoc().getSourceRange();
+ }
if (Dependent) {
DiscardCleanupsInEvaluationContext();
More information about the cfe-commits
mailing list