diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 604782ca43dd5..6fb0b2d1030be 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -166,6 +166,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 `_). +- Allow floating-point promotions and conversions in converted constant expressions. + (`CWG2851 Allow floating-point conversions in converted constant expressions `_). + C Language Changes ------------------ diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index fdca82934cb4d..cb248f2ea6374 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 04cd9e78739d2..40d65638e3afc 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 be35d366bdd61..9076598da1418 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 @@ -67,6 +68,69 @@ void B::g() requires true; } // namespace cwg2847 +namespace cwg2851 { // cwg2851: 19 + +#if __cplusplus >= 202002L +template struct Val { static constexpr T value = v; }; + + +// Floating-point promotions + +static_assert(Val::value == 0.0L); +static_assert(Val::value == 0.0L); +static_assert(Val::value == 0.0); +static_assert(Val::value == -0.0L); + +static_assert(!__is_same(Val, Val)); +static_assert(__is_same(Val, Val)); + +static_assert(__is_same(Val, Val)); + +static_assert(__is_same(Val, Val(__builtin_nanf(""))>)); +static_assert(__is_same(Val, Val(__builtin_nansf(""))>)); +static_assert(__is_same(Val, Val(__builtin_nanf("0x1"))>)); +static_assert(__is_same(Val, Val(__builtin_nansf("0x1"))>)); + + +// Floating-point conversions where the source value can be represented exactly in the destination type + +static_assert(Val::value == 0.0L); +static_assert(__is_same(Val, Val)); +static_assert(__is_same(Val, Val)); +static_assert(!__is_same(Val, Val)); +static_assert(__is_same(Val, Val)); +static_assert(__is_same(Val, Val)); + +static_assert(__is_same(Val, Val)); +Val _1; +// since-cxx20-error-re@-1 {{non-type template argument evaluates to {{.+}} which cannot be exactly represented in type 'float'}} +Val(__FLT_DENORM_MIN__) / 2.0L> _2; +// since-cxx20-error-re@-1 {{non-type template argument evaluates to {{.+}} which cannot be exactly represented in type 'float'}} +Val _3; +// since-cxx20-error-re@-1 {{non-type template argument evaluates to {{.+}} which cannot be exactly represented in type 'float'}} + +static_assert(__is_same(Val, Val)); + +static_assert(__is_same(Val, Val(__builtin_nanl(""))>)); +static_assert(__is_same(Val, Val(__builtin_nansl(""))>)); +#if __SIZEOF_LONG_DOUBLE__ > 8 +// since-cxx20-error@-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, Val(__builtin_nan("0xFF00000000"))>)); +static_assert(__is_same(Val, Val(__builtin_nans("0xFF00000000"))>)); +static_assert(__is_same(Val, Val(__builtin_nanl("0x1"))>)); +// since-cxx20-error@-1 {{non-type template argument evaluates to nan which cannot be exactly represented in type 'float'}} +static_assert(__is_same(Val, Val(__builtin_nansl("0x1"))>)); +// since-cxx20-error@-1 {{non-type template argument evaluates to nan which cannot be exactly represented in type 'float'}} +static_assert(__is_same(Val, Val(__builtin_nanl("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"))>)); +// since-cxx20-error@-1 {{non-type template argument evaluates to nan which cannot be exactly represented in type 'float'}} +static_assert(__is_same(Val, Val(__builtin_nansl("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"))>)); +// since-cxx20-error@-1 {{non-type template argument evaluates to nan which cannot be exactly represented in type 'float'}} +#endif + +} + namespace cwg2858 { // cwg2858: 19 tentatively ready 2024-04-05 #if __cplusplus > 202302L diff --git a/clang/www/cxx_dr_status.html b/clang/www/cxx_dr_status.html index 875521bd505d5..331962ba0b9c3 100755 --- a/clang/www/cxx_dr_status.html +++ b/clang/www/cxx_dr_status.html @@ -16915,7 +16915,7 @@

C++ defect report implementation status

2851 DR Allow floating-point conversions in converted constant expressions - Unknown + Clang 19 2852