[clang] [Clang] Implement diagnostics for why is_final is false (PR #154863)
Samarth Narang via cfe-commits
cfe-commits at lists.llvm.org
Thu Aug 21 17:47:56 PDT 2025
https://github.com/snarang181 created https://github.com/llvm/llvm-project/pull/154863
Adds onto https://github.com/llvm/llvm-project/issues/141911
>From a30420c14387dd41e7ab7ea1924b4471b3f0b71d Mon Sep 17 00:00:00 2001
From: Samarth Narang <snarang at umass.edu>
Date: Thu, 21 Aug 2025 20:45:53 -0400
Subject: [PATCH] Implement why is_final evaluates to false
---
.../clang/Basic/DiagnosticSemaKinds.td | 7 +-
clang/lib/Sema/SemaTypeTraits.cpp | 56 ++++++++++++++
.../type-traits-unsatisfied-diags-std.cpp | 76 +++++++++++++++++++
.../SemaCXX/type-traits-unsatisfied-diags.cpp | 43 +++++++++++
4 files changed, 180 insertions(+), 2 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index f40cac865ade0..f50d245965648 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -1772,7 +1772,8 @@ def note_unsatisfied_trait
"%Replaceable{replaceable}|"
"%TriviallyCopyable{trivially copyable}|"
"%Empty{empty}|"
- "%StandardLayout{standard-layout}"
+ "%StandardLayout{standard-layout}|"
+ "%Final{final}"
"}1">;
def note_unsatisfied_trait_reason
@@ -1815,7 +1816,9 @@ def note_unsatisfied_trait_reason
"%sub{select_special_member_kind}1}|"
"%FunctionType{is a function type}|"
"%CVVoidType{is a cv void type}|"
- "%IncompleteArrayType{is an incomplete array type}"
+ "%IncompleteArrayType{is an incomplete array type}|"
+ "%NotClassOrUnion{is not a class or union type}|"
+ "%NotMarkedFinal{is not marked 'final'}"
"}0">;
def warn_consteval_if_always_true : Warning<
diff --git a/clang/lib/Sema/SemaTypeTraits.cpp b/clang/lib/Sema/SemaTypeTraits.cpp
index 9b9dd172003a0..0b4d5916f8dc3 100644
--- a/clang/lib/Sema/SemaTypeTraits.cpp
+++ b/clang/lib/Sema/SemaTypeTraits.cpp
@@ -1964,6 +1964,7 @@ static std::optional<TypeTrait> StdNameToTypeTrait(StringRef Name) {
.Case("is_empty", TypeTrait::UTT_IsEmpty)
.Case("is_standard_layout", TypeTrait::UTT_IsStandardLayout)
.Case("is_constructible", TypeTrait::TT_IsConstructible)
+ .Case("is_final", TypeTrait::UTT_IsFinal)
.Default(std::nullopt);
}
@@ -2448,6 +2449,52 @@ static void DiagnoseIsEmptyReason(Sema &S, SourceLocation Loc, QualType T) {
}
}
+static void DiagnoseIsFinalReason(Sema &S, SourceLocation Loc,
+ const CXXRecordDecl *D) {
+ if (!D || D->isInvalidDecl())
+ return;
+
+ // Complete record but not 'final'.
+ if (!D->isEffectivelyFinal()) {
+ S.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::NotMarkedFinal;
+ S.Diag(D->getLocation(), diag::note_defined_here) << D;
+ return;
+ }
+}
+
+static void DiagnoseIsFinalReason(Sema &S, SourceLocation Loc, QualType T) {
+ // Primary: “%0 is not final”
+ S.Diag(Loc, diag::note_unsatisfied_trait) << T << diag::TraitName::Final;
+ if (T->isReferenceType()) {
+ S.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::Ref;
+ S.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::NotClassOrUnion;
+ return;
+ }
+ // Arrays / functions / non-records → not a class/union.
+ if (S.Context.getAsArrayType(T)) {
+ S.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::NotClassOrUnion;
+ return;
+ }
+ if (T->isFunctionType()) {
+ S.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::FunctionType;
+ S.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::NotClassOrUnion;
+ return;
+ }
+ if (!T->isRecordType()) {
+ S.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::NotClassOrUnion;
+ return;
+ }
+ if (const auto *D = T->getAsCXXRecordDecl())
+ DiagnoseIsFinalReason(S, Loc, D);
+}
+
static bool hasMultipleDataBaseClassesWithFields(const CXXRecordDecl *D) {
int NumBasesWithFields = 0;
for (const CXXBaseSpecifier &Base : D->bases()) {
@@ -2624,6 +2671,15 @@ void Sema::DiagnoseTypeTraitDetails(const Expr *E) {
case TT_IsConstructible:
DiagnoseNonConstructibleReason(*this, E->getBeginLoc(), Args);
break;
+ case UTT_IsFinal: {
+ QualType QT = Args[0];
+ if (QT->isDependentType())
+ break;
+ const auto *RD = QT->getAsCXXRecordDecl();
+ if (!RD || !RD->isEffectivelyFinal())
+ DiagnoseIsFinalReason(*this, E->getBeginLoc(), QT); // unsatisfied
+ 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 f3ddbbfe15bdc..7c6c9ea4dde80 100644
--- a/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp
+++ b/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp
@@ -50,6 +50,14 @@ struct is_constructible {
template <typename... Args>
constexpr bool is_constructible_v = __is_constructible(Args...);
+
+template <typename T>
+struct is_final {
+ static constexpr bool value = __is_final(T);
+};
+template <typename T>
+constexpr bool is_final_v = __is_final(T);
+
#endif
#ifdef STD2
@@ -116,6 +124,16 @@ using is_constructible = __details_is_constructible<Args...>;
template <typename... Args>
constexpr bool is_constructible_v = __is_constructible(Args...);
+
+template <typename T>
+struct __details_is_final {
+ static constexpr bool value = __is_final(T);
+};
+template <typename T>
+using is_final = __details_is_final<T>;
+template <typename T>
+constexpr bool is_final_v = __is_final(T);
+
#endif
@@ -177,6 +195,14 @@ using is_constructible = __details_is_constructible<Args...>;
template <typename... Args>
constexpr bool is_constructible_v = is_constructible<Args...>::value;
+
+template <typename T>
+struct __details_is_final : bool_constant<__is_final(T)> {};
+template <typename T>
+using is_final = __details_is_final<T>;
+template <typename T>
+constexpr bool is_final_v = is_final<T>::value;
+
#endif
}
@@ -248,6 +274,31 @@ static_assert(std::is_constructible_v<void>);
// expected-error at -1 {{static assertion failed due to requirement 'std::is_constructible_v<void>'}} \
// expected-note at -1 {{because it is a cv void type}}
+static_assert(!std::is_final<int>::value);
+
+static_assert(std::is_final<int&>::value);
+// expected-error-re at -1 {{static assertion failed due to requirement 'std::{{.*}}is_final<int &>::value'}} \
+// expected-note at -1 {{'int &' is not final}} \
+// expected-note at -1 {{because it is a reference type}} \
+// expected-note at -1 {{because it is not a class or union type}}
+
+static_assert(std::is_final_v<int&>);
+// expected-error at -1 {{static assertion failed due to requirement 'std::is_final_v<int &>'}} \
+// expected-note at -1 {{'int &' is not final}} \
+// expected-note at -1 {{because it is a reference type}} \
+// expected-note at -1 {{because it is not a class or union type}}
+
+using Arr = int[3];
+static_assert(std::is_final<Arr>::value);
+// expected-error-re at -1 {{static assertion failed due to requirement 'std::{{.*}}is_final<int[3]>::value'}} \
+// expected-note at -1 {{'Arr' (aka 'int[3]') is not final}} \
+// expected-note at -1 {{because it is not a class or union type}}
+
+static_assert(std::is_final_v<Arr>);
+// expected-error at -1 {{static assertion failed due to requirement 'std::is_final_v<int[3]>'}} \
+// expected-note at -1 {{'int[3]' is not final}} \
+// expected-note at -1 {{because it is not a class or union type}}
+
namespace test_namespace {
using namespace std;
static_assert(is_trivially_relocatable<int&>::value);
@@ -300,6 +351,31 @@ namespace test_namespace {
static_assert(is_constructible_v<void>);
// expected-error at -1 {{static assertion failed due to requirement 'is_constructible_v<void>'}} \
// expected-note at -1 {{because it is a cv void type}}
+
+ static_assert(is_final<int&>::value);
+ // expected-error-re at -1 {{static assertion failed due to requirement '{{.*}}is_final<int &>::value'}} \
+ // expected-note at -1 {{'int &' is not final}} \
+ // expected-note at -1 {{because it is a reference type}} \
+ // expected-note at -1 {{because it is not a class or union type}}
+
+ static_assert(is_final_v<int&>);
+ // expected-error at -1 {{static assertion failed due to requirement 'is_final_v<int &>'}} \
+ // expected-note at -1 {{'int &' is not final}} \
+ // expected-note at -1 {{because it is a reference type}} \
+ // expected-note at -1 {{because it is not a class or union type}}
+
+ using A = int[2];
+ static_assert(is_final<A>::value);
+ // expected-error-re at -1 {{static assertion failed due to requirement '{{.*}}is_final<int[2]>::value'}} \
+ // expected-note at -1 {{'A' (aka 'int[2]') is not final}} \
+ // expected-note at -1 {{because it is not a class or union type}}
+
+ using Fn = void();
+ static_assert(is_final<Fn>::value);
+ // expected-error-re at -1 {{static assertion failed due to requirement '{{.*}}is_final<void ()>::value'}} \
+ // 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}}
}
diff --git a/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp b/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp
index 54806a93ddf80..1619b0b22b85f 100644
--- a/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp
+++ b/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp
@@ -829,3 +829,46 @@ static_assert(__is_standard_layout(H)); // no diagnostics
static_assert(__is_standard_layout(I)); // no diagnostics
}
+namespace is_final_tests {
+ struct C {}; // #e-C
+ static_assert(__is_final(C));
+ // expected-error at -1 {{static assertion failed due to requirement '__is_final(is_final_tests::C)'}} \
+ // expected-note at -1 {{'C' is not final}} \
+ // expected-note at -1 {{because it is not marked 'final'}} \
+ // expected-note@#e-C {{'C' defined here}}
+
+ union U {}; // #e-U
+ static_assert(__is_final(U));
+ // expected-error at -1 {{static assertion failed due to requirement '__is_final(is_final_tests::U)'}} \
+ // expected-note at -1 {{'U' is not final}} \
+ // expected-note at -1 {{because it is not marked 'final'}} \
+ // expected-note@#e-U {{'U' defined here}}
+
+ // ----- non-class/union types -----
+ using I = int;
+ static_assert(__is_final(I));
+ // expected-error at -1 {{static assertion failed due to requirement '__is_final(int)'}} \
+ // expected-note at -1 {{'I' (aka 'int') is not final}} \
+ // expected-note at -1 {{because it is not a class or union type}}
+
+ using Fty = void(); // function type
+ static_assert(__is_final(Fty));
+ // expected-error at -1 {{static assertion failed due to requirement '__is_final(void ())'}} \
+ // expected-note at -1 {{'Fty' (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}}
+
+ using Arr = int[3];
+ static_assert(__is_final(Arr));
+ // expected-error at -1 {{static assertion failed due to requirement '__is_final(int[3])'}} \
+ // expected-note at -1 {{'Arr' (aka 'int[3]') is not final}} \
+ // expected-note at -1 {{because it is not a class or union type}}
+
+ using Ref = int&;
+ static_assert(__is_final(Ref));
+ // expected-error at -1 {{static assertion failed due to requirement '__is_final(int &)'}} \
+ // expected-note at -1 {{'Ref' (aka 'int &') is not final}} \
+ // expected-note at -1 {{because it is a reference type}} \
+ // expected-note at -1 {{because it is not a class or union type}}
+
+}
More information about the cfe-commits
mailing list