Skip to content

Commit

Permalink
[clang] Implement CWG2851: floating-point conversions in converted co…
Browse files Browse the repository at this point in the history
…nstant expressions
  • Loading branch information
MitalAshok committed Apr 29, 2024
1 parent 5f9ae61 commit 77cb28e
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 4 deletions.
3 changes: 3 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <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
------------------

Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -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|"
Expand Down
38 changes: 35 additions & 3 deletions clang/lib/Sema/SemaOverload.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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,
Expand Down
64 changes: 64 additions & 0 deletions clang/test/CXX/drs/dr28xx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -67,6 +68,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@-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@-1 {{non-type template argument evaluates to {{.+}} which cannot be exactly represented in type 'float'}}
Val<float, __DBL_MAX__> _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<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@-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@-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@-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@-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@-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
Expand Down
2 changes: 1 addition & 1 deletion clang/www/cxx_dr_status.html
Original file line number Diff line number Diff line change
Expand Up @@ -16915,7 +16915,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2>
<td><a href="https://cplusplus.github.io/CWG/issues/2851.html">2851</a></td>
<td>DR</td>
<td>Allow floating-point conversions in converted constant expressions</td>
<td class="unknown" align="center">Unknown</td>
<td class="unreleased" align="center">Clang 19</td>
</tr>
<tr class="open" id="2852">
<td><a href="https://cplusplus.github.io/CWG/issues/2852.html">2852</a></td>
Expand Down

0 comments on commit 77cb28e

Please sign in to comment.