[clang] [Clang] Add diagnostic for why std::is_abstract is false (PR #156199)
Sebastian Proell via cfe-commits
cfe-commits at lists.llvm.org
Sat Aug 30 12:19:31 PDT 2025
https://github.com/sebproell created https://github.com/llvm/llvm-project/pull/156199
Adds onto #141911
>From d86aa58c30c784e99cc28cd2b90bd0d667b78ab6 Mon Sep 17 00:00:00 2001
From: Sebastian Proell <sebastian.proell at tum.de>
Date: Sat, 30 Aug 2025 14:46:25 +0200
Subject: [PATCH] [Clang] Add diagnostic for why std::is_abstract is false
---
.../clang/Basic/DiagnosticSemaKinds.td | 8 +-
clang/lib/Sema/SemaTypeTraits.cpp | 64 ++++++++++++++++
.../type-traits-unsatisfied-diags-std.cpp | 51 +++++++++++++
.../SemaCXX/type-traits-unsatisfied-diags.cpp | 76 +++++++++++++++++++
4 files changed, 197 insertions(+), 2 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index c934fed2c7462..36e7b66ea1fb0 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -1773,7 +1773,8 @@ def note_unsatisfied_trait
"%TriviallyCopyable{trivially copyable}|"
"%Empty{empty}|"
"%StandardLayout{standard-layout}|"
- "%Final{final}"
+ "%Final{final}|"
+ "%Abstract{abstract}"
"}1">;
def note_unsatisfied_trait_reason
@@ -1818,7 +1819,10 @@ def note_unsatisfied_trait_reason
"%CVVoidType{is a cv void type}|"
"%IncompleteArrayType{is an incomplete array type}|"
"%NotClassOrUnion{is not a class or union type}|"
- "%NotMarkedFinal{is not marked 'final'}"
+ "%NotMarkedFinal{is not marked 'final'}|"
+ "%UnionType{is a union type}|"
+ "%NotStructOrClass{is not a struct or class type}|"
+ "%OverridesAllPureVirtual{overrides all pure virtual functions from base class %1}"
"}0">;
def warn_consteval_if_always_true : Warning<
diff --git a/clang/lib/Sema/SemaTypeTraits.cpp b/clang/lib/Sema/SemaTypeTraits.cpp
index 37552331478f1..7a2b7a67e57c7 100644
--- a/clang/lib/Sema/SemaTypeTraits.cpp
+++ b/clang/lib/Sema/SemaTypeTraits.cpp
@@ -1966,6 +1966,7 @@ static std::optional<TypeTrait> StdNameToTypeTrait(StringRef Name) {
.Case("is_standard_layout", TypeTrait::UTT_IsStandardLayout)
.Case("is_constructible", TypeTrait::TT_IsConstructible)
.Case("is_final", TypeTrait::UTT_IsFinal)
+ .Case("is_abstract", TypeTrait::UTT_IsAbstract)
.Default(std::nullopt);
}
@@ -2640,6 +2641,66 @@ static void DiagnoseNonStandardLayoutReason(Sema &SemaRef, SourceLocation Loc,
SemaRef.Diag(D->getLocation(), diag::note_defined_here) << D;
}
+static void DiagnoseNonAbstractReason(Sema &SemaRef, SourceLocation Loc,
+ const CXXRecordDecl *D) {
+ // If this type has any abstract base classes, their respective virtual
+ // functions must have been overridden.
+ for (const CXXBaseSpecifier &B : D->bases()) {
+ assert(B.getType()->getAsCXXRecordDecl() && "invalid base?");
+ if (B.getType()->getAsCXXRecordDecl()->isAbstract()) {
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::OverridesAllPureVirtual
+ << B.getType() << B.getSourceRange();
+ }
+ }
+}
+
+static void DiagnoseNonAbstractReason(Sema &SemaRef, SourceLocation Loc,
+ QualType T) {
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait)
+ << T << diag::TraitName::Abstract;
+
+ if (T->isReferenceType()) {
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::Ref;
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::NotStructOrClass;
+ return;
+ }
+
+ if (T->isUnionType()) {
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::UnionType;
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::NotStructOrClass;
+ return;
+ }
+
+ if (SemaRef.Context.getAsArrayType(T)) {
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::NotStructOrClass;
+ return;
+ }
+
+ if (T->isFunctionType()) {
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::FunctionType;
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::NotStructOrClass;
+ return;
+ }
+
+ if (!T->isStructureOrClassType()) {
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::NotStructOrClass;
+ return;
+ }
+
+ const CXXRecordDecl *D = T->getAsCXXRecordDecl();
+ if (D->hasDefinition())
+ DiagnoseNonAbstractReason(SemaRef, Loc, D);
+}
+
void Sema::DiagnoseTypeTraitDetails(const Expr *E) {
E = E->IgnoreParenImpCasts();
if (E->containsErrors())
@@ -2681,6 +2742,9 @@ void Sema::DiagnoseTypeTraitDetails(const Expr *E) {
DiagnoseIsFinalReason(*this, E->getBeginLoc(), QT); // unsatisfied
break;
}
+ case UTT_IsAbstract:
+ DiagnoseNonAbstractReason(*this, E->getBeginLoc(), Args[0]);
+ break;
default:
break;
}
diff --git a/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp b/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp
index 7c6c9ea4dde80..4fb55f85d565d 100644
--- a/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp
+++ b/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp
@@ -58,6 +58,13 @@ struct is_final {
template <typename T>
constexpr bool is_final_v = __is_final(T);
+template <typename T>
+struct is_abstract {
+ static constexpr bool value = __is_abstract(T);
+};
+template <typename T>
+constexpr bool is_abstract_v = __is_abstract(T);
+
#endif
#ifdef STD2
@@ -134,6 +141,15 @@ using is_final = __details_is_final<T>;
template <typename T>
constexpr bool is_final_v = __is_final(T);
+template <typename T>
+struct __details_is_abstract {
+ static constexpr bool value = __is_abstract(T);
+};
+template <typename T>
+using is_abstract = __details_is_abstract<T>;
+template <typename T>
+constexpr bool is_abstract_v = __is_abstract(T);
+
#endif
@@ -203,6 +219,13 @@ using is_final = __details_is_final<T>;
template <typename T>
constexpr bool is_final_v = is_final<T>::value;
+template <typename T>
+struct __details_is_abstract : bool_constant<__is_abstract(T)> {};
+template <typename T>
+using is_abstract = __details_is_abstract<T>;
+template <typename T>
+constexpr bool is_abstract_v = is_abstract<T>::value;
+
#endif
}
@@ -299,6 +322,22 @@ static_assert(std::is_final_v<Arr>);
// expected-note at -1 {{'int[3]' is not final}} \
// expected-note at -1 {{because it is not a class or union type}}
+
+static_assert(!std::is_abstract<int>::value);
+
+static_assert(std::is_abstract<int&>::value);
+// expected-error-re at -1 {{static assertion failed due to requirement 'std::{{.*}}is_abstract<int &>::value'}} \
+// expected-note at -1 {{'int &' is not abstract}} \
+// expected-note at -1 {{because it is a reference type}} \
+// expected-note at -1 {{because it is not a struct or class type}}
+
+static_assert(std::is_abstract_v<int&>);
+// expected-error at -1 {{static assertion failed due to requirement 'std::is_abstract_v<int &>'}} \
+// expected-note at -1 {{'int &' is not abstract}} \
+// expected-note at -1 {{because it is a reference type}} \
+// expected-note at -1 {{because it is not a struct or class type}}
+
+
namespace test_namespace {
using namespace std;
static_assert(is_trivially_relocatable<int&>::value);
@@ -376,6 +415,18 @@ namespace test_namespace {
// expected-note at -1 {{'Fn' (aka 'void ()') is not final}} \
// expected-note at -1 {{because it is a function type}} \
// expected-note at -1 {{because it is not a class or union type}}
+
+ static_assert(is_abstract<int&>::value);
+ // expected-error-re at -1 {{static assertion failed due to requirement '{{.*}}is_abstract<int &>::value'}} \
+ // expected-note at -1 {{'int &' is not abstract}} \
+ // expected-note at -1 {{because it is a reference type}} \
+ // expected-note at -1 {{because it is not a struct or class type}}
+
+ static_assert(is_abstract_v<int&>);
+ // expected-error at -1 {{static assertion failed due to requirement 'is_abstract_v<int &>'}} \
+ // expected-note at -1 {{'int &' is not abstract}} \
+ // expected-note at -1 {{because it is a reference type}} \
+ // expected-note at -1 {{because it is not a struct or class type}}
}
diff --git a/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp b/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp
index 1619b0b22b85f..dff6ff59df275 100644
--- a/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp
+++ b/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp
@@ -872,3 +872,79 @@ namespace is_final_tests {
// expected-note at -1 {{because it is not a class or union type}}
}
+
+namespace is_abstract_tests {
+struct Abstract1 {
+ virtual void fn1() = 0;
+};
+
+struct Abstract2 {
+ virtual void fn2() = 0;
+};
+
+struct NonAbstract
+{
+ virtual void f() {}
+};
+
+// Multiple inheritance reports all abstract base classes that had their pure virtual functions overridden.
+struct Overrides : Abstract1, Abstract2, NonAbstract {
+ void fn1() override {}
+ void fn2() override {}
+};
+
+static_assert(__is_abstract(Overrides));
+// expected-error at -1 {{static assertion failed due to requirement '__is_abstract(is_abstract_tests::Overrides)'}} \
+// expected-note at -1 {{'Overrides' is not abstract}} \
+// expected-note at -1 {{because it overrides all pure virtual functions from base class 'Abstract1'}} \
+// expected-note at -1 {{because it overrides all pure virtual functions from base class 'Abstract2'}} \
+
+// Inheriting over two levels reports the last class only although the source of the pure virtual function
+// is the top-most base.
+struct Derived : Abstract1 {
+};
+
+struct Derived2 : Derived {
+ void fn1() override {}
+};
+
+static_assert(__is_abstract(Derived2));
+// expected-error at -1 {{static assertion failed due to requirement '__is_abstract(is_abstract_tests::Derived2)'}} \
+// expected-note at -1 {{'Derived2' is not abstract}} \
+// expected-note at -1 {{because it overrides all pure virtual functions from base class 'Derived'}} \
+
+
+using I = int;
+static_assert(__is_abstract(I));
+// expected-error at -1 {{static assertion failed due to requirement '__is_abstract(int)'}} \
+// expected-note at -1 {{'I' (aka 'int') is not abstract}} \
+// expected-note at -1 {{because it is not a struct or class type}}
+
+using Fty = void(); // function type
+static_assert(__is_abstract(Fty));
+// expected-error at -1 {{static assertion failed due to requirement '__is_abstract(void ())'}} \
+// expected-note at -1 {{'Fty' (aka 'void ()') is not abstract}} \
+// expected-note at -1 {{because it is a function type}} \
+// expected-note at -1 {{because it is not a struct or class type}}
+
+using Arr = int[3];
+static_assert(__is_abstract(Arr));
+// expected-error at -1 {{static assertion failed due to requirement '__is_abstract(int[3])'}} \
+// expected-note at -1 {{'Arr' (aka 'int[3]') is not abstract}} \
+// expected-note at -1 {{because it is not a struct or class type}}
+
+using Ref = int&;
+static_assert(__is_abstract(Ref));
+// expected-error at -1 {{static assertion failed due to requirement '__is_abstract(int &)'}} \
+// expected-note at -1 {{'Ref' (aka 'int &') is not abstract}} \
+// expected-note at -1 {{because it is a reference type}} \
+// expected-note at -1 {{because it is not a struct or class type}}
+
+using Ptr = int*;
+static_assert(__is_abstract(Ptr));
+// expected-error at -1 {{static assertion failed due to requirement '__is_abstract(int *)'}} \
+// expected-note at -1 {{'Ptr' (aka 'int *') is not abstract}} \
+// expected-note at -1 {{because it is not a struct or class type}}
+
+
+}
More information about the cfe-commits
mailing list