[clang] [clang] Implement CWG2851: floating-point conversions in converted constant expressions (PR #90387)
Mital Ashok via cfe-commits
cfe-commits at lists.llvm.org
Sun Apr 28 06:26:10 PDT 2024
https://github.com/MitalAshok updated https://github.com/llvm/llvm-project/pull/90387
>From 13e2943dea677daf8976ab55a673d43982ac8a4c Mon Sep 17 00:00:00 2001
From: Mital Ashok <mital at mitalashok.co.uk>
Date: Sun, 28 Apr 2024 09:48:47 +0100
Subject: [PATCH] [clang] Implement CWG2851: floating-point conversions in
converted constant expressions
---
clang/docs/ReleaseNotes.rst | 3 +
.../clang/Basic/DiagnosticSemaKinds.td | 4 ++
clang/lib/Sema/SemaOverload.cpp | 38 ++++++++++-
clang/test/CXX/drs/dr28xx.cpp | 64 +++++++++++++++++++
4 files changed, 106 insertions(+), 3 deletions(-)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index a1390d6536b28c..7c8d83bd73613b 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -164,6 +164,9 @@ Resolutions to C++ Defect Reports
- Clang now diagnoses declarative nested-name-specifiers with pack-index-specifiers.
(`CWG2858: Declarative nested-name-specifiers and pack-index-specifiers <https://cplusplus.github.io/CWG/issues/2858.html>`_).
+- Allow floating-point promotions and conversions in converted constant expressions.
+ (`CWG2851 Allow floating-point conversions in converted constant expressions <https://cplusplus.github.io/CWG/issues/2851.html>`_).
+
C Language Changes
------------------
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index fdca82934cb4dc..cb248f2ea6374b 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -85,6 +85,10 @@ def err_expr_not_cce : Error<
"%select{case value|enumerator value|non-type template argument|"
"array size|explicit specifier argument|noexcept specifier argument|"
"call to 'size()'|call to 'data()'}0 is not a constant expression">;
+def err_float_conv_cant_represent : Error<
+ "non-type template argument evaluates to %0 which cannot be "
+ "exactly represented in type %1"
+>;
def ext_cce_narrowing : ExtWarn<
"%select{case value|enumerator value|non-type template argument|"
"array size|explicit specifier argument|noexcept specifier argument|"
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 04cd9e78739d20..40d65638e3afc1 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -6072,6 +6072,10 @@ static bool CheckConvertedConstantConversions(Sema &S,
case ICK_Integral_Promotion:
case ICK_Integral_Conversion: // Narrowing conversions are checked elsewhere.
case ICK_Zero_Queue_Conversion:
+ // Per CWG2851, floating-point promotions and conversions are allowed.
+ // The value of a conversion is checked afterwards.
+ case ICK_Floating_Promotion:
+ case ICK_Floating_Conversion:
return true;
case ICK_Boolean_Conversion:
@@ -6091,9 +6095,7 @@ static bool CheckConvertedConstantConversions(Sema &S,
// only permitted if the source type is std::nullptr_t.
return SCS.getFromType()->isNullPtrType();
- case ICK_Floating_Promotion:
case ICK_Complex_Promotion:
- case ICK_Floating_Conversion:
case ICK_Complex_Conversion:
case ICK_Floating_Integral:
case ICK_Compatible_Conversion:
@@ -6229,7 +6231,37 @@ static ExprResult BuildConvertedConstantExpression(Sema &S, Expr *From,
if (Result.isInvalid())
return Result;
- // Check for a narrowing implicit conversion.
+ if (SCS->Second == ICK_Floating_Conversion) {
+ // Unlike with narrowing conversions, the value must fit
+ // exactly even if it is in range
+ assert(CCE == Sema::CCEKind::CCEK_TemplateArg &&
+ "Only non-type template args should use floating-point conversions");
+
+ // Initializer is From, except it is a full-expression
+ const Expr *Initializer =
+ IgnoreNarrowingConversion(S.Context, Result.get());
+
+ // If it's value-dependent, we can't tell whether it will fit
+ if (Initializer->isValueDependent())
+ return Result;
+
+ // Not-constant diagnosed afterwards
+ if (!Initializer->isCXX11ConstantExpr(S.Context, &PreNarrowingValue))
+ return Result;
+
+ llvm::APFloat PostNarrowingValue = PreNarrowingValue.getFloat();
+ bool LosesInfo = true;
+ PostNarrowingValue.convert(S.Context.getFloatTypeSemantics(T),
+ llvm::APFloat::rmNearestTiesToEven, &LosesInfo);
+
+ if (LosesInfo)
+ S.Diag(From->getBeginLoc(), diag::err_float_conv_cant_represent)
+ << PreNarrowingValue.getAsString(S.Context, From->getType()) << T;
+
+ return Result;
+ }
+
+ // Check for a narrowing integer conversion.
bool ReturnPreNarrowingValue = false;
QualType PreNarrowingType;
switch (SCS->getNarrowingKind(S.Context, Result.get(), PreNarrowingValue,
diff --git a/clang/test/CXX/drs/dr28xx.cpp b/clang/test/CXX/drs/dr28xx.cpp
index 4d9b0c76758d53..1626b1a9bf7b5b 100644
--- a/clang/test/CXX/drs/dr28xx.cpp
+++ b/clang/test/CXX/drs/dr28xx.cpp
@@ -3,6 +3,7 @@
// RUN: %clang_cc1 -std=c++14 -verify=expected %s
// RUN: %clang_cc1 -std=c++17 -verify=expected %s
// RUN: %clang_cc1 -std=c++20 -verify=expected,since-cxx20 %s
+// RUN: %clang_cc1 -std=c++20 -mlong-double-64 -verify=expected,since-cxx20 %s
// RUN: %clang_cc1 -std=c++23 -verify=expected,since-cxx20,since-cxx23 %s
// RUN: %clang_cc1 -std=c++2c -verify=expected,since-cxx20,since-cxx23,since-cxx26 %s
@@ -59,6 +60,69 @@ void B<int>::g() requires true;
} // namespace cwg2847
+namespace cwg2851 { // cwg2851: 19
+
+#if __cplusplus >= 202002L
+template<typename T, T v> struct Val { static constexpr T value = v; };
+
+
+// Floating-point promotions
+
+static_assert(Val<long double, 0.0>::value == 0.0L);
+static_assert(Val<long double, 0.0f>::value == 0.0L);
+static_assert(Val<double, 0.0f>::value == 0.0);
+static_assert(Val<long double, -0.0>::value == -0.0L);
+
+static_assert(!__is_same(Val<long double, -0.0>, Val<long double, 0.0L>));
+static_assert(__is_same(Val<long double, 0.5>, Val<long double, 0.5L>));
+
+static_assert(__is_same(Val<long double, __builtin_inff()>, Val<long double, __builtin_infl()>));
+
+static_assert(__is_same(Val<long double, __builtin_nanf("")>, Val<long double, static_cast<long double>(__builtin_nanf(""))>));
+static_assert(__is_same(Val<long double, __builtin_nansf("")>, Val<long double, static_cast<long double>(__builtin_nansf(""))>));
+static_assert(__is_same(Val<long double, __builtin_nanf("0x1")>, Val<long double, static_cast<long double>(__builtin_nanf("0x1"))>));
+static_assert(__is_same(Val<long double, __builtin_nansf("0x1")>, Val<long double, static_cast<long double>(__builtin_nansf("0x1"))>));
+
+
+// Floating-point conversions where the source value can be represented exactly in the destination type
+
+static_assert(Val<float, 0.0L>::value == 0.0L);
+static_assert(__is_same(Val<float, 0.0>, Val<float, 0.0L>));
+static_assert(__is_same(Val<float, 0.0>, Val<float, 0.0f>));
+static_assert(!__is_same(Val<float, -0.0L>, Val<float, 0.0f>));
+static_assert(__is_same(Val<float, 0.5L>, Val<float, 0.5f>));
+static_assert(__is_same(Val<float, 0.5L>, Val<float, 0.5f>));
+
+static_assert(__is_same(Val<float, double{__FLT_DENORM_MIN__}>, Val<float, __FLT_DENORM_MIN__>));
+Val<float, double{__FLT_DENORM_MIN__} / 2.0> _1;
+// since-cxx20-error-re at -1 {{non-type template argument evaluates to {{.+}} which cannot be exactly represented in type 'float'}}
+Val<float, static_cast<long double>(__FLT_DENORM_MIN__) / 2.0L> _2;
+// since-cxx20-error-re at -1 {{non-type template argument evaluates to {{.+}} which cannot be exactly represented in type 'float'}}
+Val<float, __DBL_MAX__> _3;
+// since-cxx20-error-re at -1 {{non-type template argument evaluates to {{.+}} which cannot be exactly represented in type 'float'}}
+
+static_assert(__is_same(Val<float, __builtin_infl()>, Val<float, __builtin_inff()>));
+
+static_assert(__is_same(Val<float, __builtin_nanl("")>, Val<float, static_cast<float>(__builtin_nanl(""))>));
+static_assert(__is_same(Val<float, __builtin_nansl("")>, Val<float, static_cast<float>(__builtin_nansl(""))>));
+#if __SIZEOF_LONG_DOUBLE__ > 8
+// since-cxx20-error at -2 {{non-type template argument evaluates to nan which cannot be exactly represented in type 'float'}}
+#endif
+// Payload is shifted right so these payloads will be preserved
+static_assert(__is_same(Val<float, __builtin_nan("0xFF00000000")>, Val<float, static_cast<float>(__builtin_nan("0xFF00000000"))>));
+static_assert(__is_same(Val<float, __builtin_nans("0xFF00000000")>, Val<float, static_cast<float>(__builtin_nans("0xFF00000000"))>));
+static_assert(__is_same(Val<float, __builtin_nanl("0x1")>, Val<float, static_cast<float>(__builtin_nanl("0x1"))>));
+// since-cxx20-error at -1 {{non-type template argument evaluates to nan which cannot be exactly represented in type 'float'}}
+static_assert(__is_same(Val<float, __builtin_nansl("0x1")>, Val<float, static_cast<float>(__builtin_nansl("0x1"))>));
+// since-cxx20-error at -1 {{non-type template argument evaluates to nan which cannot be exactly represented in type 'float'}}
+static_assert(__is_same(Val<float, __builtin_nanl("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")>, Val<float, static_cast<float>(__builtin_nanl("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"))>));
+// since-cxx20-error at -1 {{non-type template argument evaluates to nan which cannot be exactly represented in type 'float'}}
+static_assert(__is_same(Val<float, __builtin_nansl("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")>, Val<float, static_cast<float>(__builtin_nansl("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"))>));
+// since-cxx20-error at -1 {{non-type template argument evaluates to nan which cannot be exactly represented in type 'float'}}
+#endif
+
+}
+
namespace cwg2858 { // cwg2858: 19
#if __cplusplus > 202302L
More information about the cfe-commits
mailing list