[clang] 574e958 - [clang] Implement CWG2627 Bit-fields and narrowing conversions (#78112)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Aug 9 05:46:32 PDT 2024
Author: Mital Ashok
Date: 2024-08-09T14:46:28+02:00
New Revision: 574e9584494cd408fb3595df9b9d52bb3e03921a
URL: https://github.com/llvm/llvm-project/commit/574e9584494cd408fb3595df9b9d52bb3e03921a
DIFF: https://github.com/llvm/llvm-project/commit/574e9584494cd408fb3595df9b9d52bb3e03921a.diff
LOG: [clang] Implement CWG2627 Bit-fields and narrowing conversions (#78112)
https://cplusplus.github.io/CWG/issues/2627.html
It is no longer a narrowing conversion when converting a bit-field to a
type smaller than the field's declared type if the bit-field has a width
small enough to fit in the target type. This includes integral
promotions (`long long i : 8` promoted to `int` is no longer narrowing,
allowing `c.i <=> c.i`) and list-initialization (`int n{ c.i };`)
Also applies back to C++11 as this is a defect report.
Added:
clang/test/SemaCXX/bitint-narrowing.cpp
Modified:
clang/docs/ReleaseNotes.rst
clang/lib/Sema/SemaOverload.cpp
clang/test/CXX/drs/cwg26xx.cpp
clang/test/Sema/constexpr.c
clang/www/cxx_dr_status.html
Removed:
################################################################################
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 7beef7be0e6a53..7a05ccf3184111 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -111,6 +111,11 @@ Resolutions to C++ Defect Reports
Otherwise, if there is no initializer list constructor, the copy will be elided as if it was ``T(e)``.
(`CWG2311: Missed case for guaranteed copy elision <https://cplusplus.github.io/CWG/issues/2311.html>`)
+- Casts from a bit-field to an integral type is now not considered narrowing if the
+ width of the bit-field means that all potential values are in the range
+ of the target type, even if the type of the bit-field is larger.
+ (`CWG2627: Bit-fields and narrowing conversions <https://cplusplus.github.io/CWG/issues/2627.html>`_)
+
C Language Changes
------------------
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 0f196ddf812fdf..52f640eb96b73b 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -489,7 +489,12 @@ NarrowingKind StandardConversionSequence::getNarrowingKind(
// -- from an integer type or unscoped enumeration type to an integer type
// that cannot represent all the values of the original type, except where
- // the source is a constant expression and the actual value after
+ // (CWG2627) -- the source is a bit-field whose width w is less than that
+ // of its type (or, for an enumeration type, its underlying type) and the
+ // target type can represent all the values of a hypothetical extended
+ // integer type with width w and with the same signedness as the original
+ // type or
+ // -- the source is a constant expression and the actual value after
// conversion will fit into the target type and will produce the original
// value when converted back to the original type.
case ICK_Integral_Conversion:
@@ -497,53 +502,80 @@ NarrowingKind StandardConversionSequence::getNarrowingKind(
assert(FromType->isIntegralOrUnscopedEnumerationType());
assert(ToType->isIntegralOrUnscopedEnumerationType());
const bool FromSigned = FromType->isSignedIntegerOrEnumerationType();
- const unsigned FromWidth = Ctx.getIntWidth(FromType);
+ unsigned FromWidth = Ctx.getIntWidth(FromType);
const bool ToSigned = ToType->isSignedIntegerOrEnumerationType();
const unsigned ToWidth = Ctx.getIntWidth(ToType);
- if (FromWidth > ToWidth ||
- (FromWidth == ToWidth && FromSigned != ToSigned) ||
- (FromSigned && !ToSigned)) {
- // Not all values of FromType can be represented in ToType.
- const Expr *Initializer = IgnoreNarrowingConversion(Ctx, Converted);
+ constexpr auto CanRepresentAll = [](bool FromSigned, unsigned FromWidth,
+ bool ToSigned, unsigned ToWidth) {
+ return (FromWidth < ToWidth + (FromSigned == ToSigned)) &&
+ (FromSigned <= ToSigned);
+ };
- // If it's value-dependent, we can't tell whether it's narrowing.
- if (Initializer->isValueDependent())
- return NK_Dependent_Narrowing;
+ if (CanRepresentAll(FromSigned, FromWidth, ToSigned, ToWidth))
+ return NK_Not_Narrowing;
- std::optional<llvm::APSInt> OptInitializerValue;
- if (!(OptInitializerValue = Initializer->getIntegerConstantExpr(Ctx))) {
- // Such conversions on variables are always narrowing.
- return NK_Variable_Narrowing;
- }
- llvm::APSInt &InitializerValue = *OptInitializerValue;
- bool Narrowing = false;
- if (FromWidth < ToWidth) {
- // Negative -> unsigned is narrowing. Otherwise, more bits is never
- // narrowing.
- if (InitializerValue.isSigned() && InitializerValue.isNegative())
- Narrowing = true;
- } else {
- // Add a bit to the InitializerValue so we don't have to worry about
- // signed vs. unsigned comparisons.
- InitializerValue = InitializerValue.extend(
- InitializerValue.getBitWidth() + 1);
- // Convert the initializer to and from the target width and signed-ness.
- llvm::APSInt ConvertedValue = InitializerValue;
- ConvertedValue = ConvertedValue.trunc(ToWidth);
- ConvertedValue.setIsSigned(ToSigned);
- ConvertedValue = ConvertedValue.extend(InitializerValue.getBitWidth());
- ConvertedValue.setIsSigned(InitializerValue.isSigned());
- // If the result is
diff erent, this was a narrowing conversion.
- if (ConvertedValue != InitializerValue)
- Narrowing = true;
- }
- if (Narrowing) {
- ConstantType = Initializer->getType();
- ConstantValue = APValue(InitializerValue);
- return NK_Constant_Narrowing;
+ // Not all values of FromType can be represented in ToType.
+ const Expr *Initializer = IgnoreNarrowingConversion(Ctx, Converted);
+
+ bool DependentBitField = false;
+ if (const FieldDecl *BitField = Initializer->getSourceBitField()) {
+ if (BitField->getBitWidth()->isValueDependent())
+ DependentBitField = true;
+ else if (unsigned BitFieldWidth = BitField->getBitWidthValue(Ctx);
+ BitFieldWidth < FromWidth) {
+ if (CanRepresentAll(FromSigned, BitFieldWidth, ToSigned, ToWidth))
+ return NK_Not_Narrowing;
+
+ // The initializer will be truncated to the bit-field width
+ FromWidth = BitFieldWidth;
}
}
+
+ // If it's value-dependent, we can't tell whether it's narrowing.
+ if (Initializer->isValueDependent())
+ return NK_Dependent_Narrowing;
+
+ std::optional<llvm::APSInt> OptInitializerValue =
+ Initializer->getIntegerConstantExpr(Ctx);
+ if (!OptInitializerValue) {
+ // If the bit-field width was dependent, it might end up being small
+ // enough to fit in the target type (unless the target type is unsigned
+ // and the source type is signed, in which case it will never fit)
+ if (DependentBitField && (FromSigned <= ToSigned))
+ return NK_Dependent_Narrowing;
+
+ // Otherwise, such a conversion is always narrowing
+ return NK_Variable_Narrowing;
+ }
+ llvm::APSInt &InitializerValue = *OptInitializerValue;
+ bool Narrowing = false;
+ if (FromWidth < ToWidth) {
+ // Negative -> unsigned is narrowing. Otherwise, more bits is never
+ // narrowing.
+ if (InitializerValue.isSigned() && InitializerValue.isNegative())
+ Narrowing = true;
+ } else {
+ // Add a bit to the InitializerValue so we don't have to worry about
+ // signed vs. unsigned comparisons.
+ InitializerValue =
+ InitializerValue.extend(InitializerValue.getBitWidth() + 1);
+ // Convert the initializer to and from the target width and signed-ness.
+ llvm::APSInt ConvertedValue = InitializerValue;
+ ConvertedValue = ConvertedValue.trunc(ToWidth);
+ ConvertedValue.setIsSigned(ToSigned);
+ ConvertedValue = ConvertedValue.extend(InitializerValue.getBitWidth());
+ ConvertedValue.setIsSigned(InitializerValue.isSigned());
+ // If the result is
diff erent, this was a narrowing conversion.
+ if (ConvertedValue != InitializerValue)
+ Narrowing = true;
+ }
+ if (Narrowing) {
+ ConstantType = Initializer->getType();
+ ConstantValue = APValue(InitializerValue);
+ return NK_Constant_Narrowing;
+ }
+
return NK_Not_Narrowing;
}
case ICK_Complex_Real:
diff --git a/clang/test/CXX/drs/cwg26xx.cpp b/clang/test/CXX/drs/cwg26xx.cpp
index d843b09ee075ae..63a954c803b77a 100644
--- a/clang/test/CXX/drs/cwg26xx.cpp
+++ b/clang/test/CXX/drs/cwg26xx.cpp
@@ -1,11 +1,39 @@
-// RUN: %clang_cc1 -std=c++98 -triple x86_64-unknown-unknown -pedantic-errors %s -verify=expected
-// RUN: %clang_cc1 -std=c++11 -triple x86_64-unknown-unknown -pedantic-errors %s -verify=expected,since-cxx11,cxx11
-// RUN: %clang_cc1 -std=c++14 -triple x86_64-unknown-unknown -pedantic-errors %s -verify=expected,since-cxx11
-// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-unknown -pedantic-errors %s -verify=expected,since-cxx11
-// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-unknown -pedantic-errors %s -verify=expected,since-cxx11,since-cxx20
-// RUN: %clang_cc1 -std=c++23 -triple x86_64-unknown-unknown -pedantic-errors %s -verify=expected,since-cxx11,since-cxx20,since-cxx23
-// RUN: %clang_cc1 -std=c++2c -triple x86_64-unknown-unknown -pedantic-errors %s -verify=expected,since-cxx11,since-cxx20,since-cxx23
+// RUN: %clang_cc1 -std=c++98 -pedantic-errors %s -verify=expected,cxx98
+// RUN: %clang_cc1 -std=c++11 -pedantic-errors %s -verify=expected,since-cxx11,cxx11
+// RUN: %clang_cc1 -std=c++14 -pedantic-errors %s -verify=expected,since-cxx11
+// RUN: %clang_cc1 -std=c++17 -pedantic-errors %s -verify=expected,since-cxx11
+// RUN: %clang_cc1 -std=c++20 -pedantic-errors %s -verify=expected,since-cxx11,since-cxx20
+// RUN: %clang_cc1 -std=c++23 -pedantic-errors %s -verify=expected,since-cxx11,since-cxx20,since-cxx23
+// RUN: %clang_cc1 -std=c++2c -pedantic-errors %s -verify=expected,since-cxx11,since-cxx20,since-cxx23
+
+#if __cplusplus == 199711L
+#define static_assert(...) __extension__ _Static_assert(__VA_ARGS__)
+// cxx98-error at -1 {{variadic macros are a C99 feature}}
+#endif
+namespace std {
+#if __cplusplus >= 202002L
+ struct strong_ordering {
+ int n;
+ constexpr operator int() const { return n; }
+ static const strong_ordering less, equal, greater;
+ };
+ constexpr strong_ordering strong_ordering::less{-1},
+ strong_ordering::equal{0}, strong_ordering::greater{1};
+#endif
+
+ typedef short int16_t;
+ typedef unsigned short uint16_t;
+ typedef int int32_t;
+ typedef unsigned uint32_t;
+ typedef long long int64_t;
+ // cxx98-error at -1 {{'long long' is a C++11 extension}}
+ typedef unsigned long long uint64_t;
+ // cxx98-error at -1 {{'long long' is a C++11 extension}}
+ static_assert(sizeof(int16_t) == 2 && sizeof(int32_t) == 4 && sizeof(int64_t) == 8, "Some tests rely on these sizes");
+
+ template<typename T> T declval();
+}
namespace cwg2621 { // cwg2621: sup 2877
#if __cplusplus >= 202002L
@@ -23,6 +51,87 @@ using enum E;
#endif
}
+namespace cwg2627 { // cwg2627: 20
+#if __cplusplus >= 202002L
+struct C {
+ long long i : 8;
+ friend auto operator<=>(C, C) = default;
+};
+
+void f() {
+ C x{1}, y{2};
+ static_cast<void>(x <=> y);
+ static_cast<void>(x.i <=> y.i);
+}
+
+template<typename T>
+struct CDependent {
+ T i : 8;
+ friend auto operator<=>(CDependent, CDependent) = default;
+};
+
+template<typename T>
+concept three_way_comparable = requires(T t) { { t <=> t }; };
+template<typename T>
+concept bf_three_way_comparable = requires(T t) { { t.i <=> t.i }; };
+static_assert(three_way_comparable<CDependent<long long>>);
+static_assert(bf_three_way_comparable<CDependent<long long>>);
+#endif
+
+#if __cplusplus >= 201103L
+template<typename T, int N>
+struct D {
+ T i : N;
+};
+
+template<typename T, int N>
+D<T, N> d();
+
+std::int32_t d1{ d<std::int64_t, 31>().i };
+std::int32_t d2{ d<std::int64_t, 32>().i };
+std::int32_t d3{ d<std::int64_t, 33>().i };
+// since-cxx11-error at -1 {{non-constant-expression cannot be narrowed from type 'long long' to 'std::int32_t' (aka 'int') in initializer list}}
+// since-cxx11-note at -2 {{insert an explicit cast to silence this issue}}
+
+std::int16_t d6{ d<int, 16>().i };
+std::int16_t d7{ d<unsigned, 15>().i };
+std::int16_t d8{ d<unsigned, 16>().i };
+// since-cxx11-error at -1 {{non-constant-expression cannot be narrowed from type 'unsigned int' to 'std::int16_t' (aka 'short') in initializer list}}
+// since-cxx11-note at -2 {{insert an explicit cast to silence this issue}}
+std::uint16_t d9{ d<unsigned, 16>().i };
+std::uint16_t da{ d<int, 1>().i };
+// since-cxx11-error at -1 {{non-constant-expression cannot be narrowed from type 'int' to 'std::uint16_t' (aka 'unsigned short') in initializer list}}
+// since-cxx11-note at -2 {{insert an explicit cast to silence this issue}}
+
+bool db{ d<unsigned, 1>().i };
+bool dc{ d<int, 1>().i };
+// since-cxx11-error at -1 {{non-constant-expression cannot be narrowed from type 'int' to 'bool' in initializer list}}
+// since-cxx11-note at -2 {{insert an explicit cast to silence this issue}}
+
+template<typename Target, typename Source>
+constexpr decltype(Target{ std::declval<Source>().i }, false) is_narrowing(int) { return false; }
+template<typename Target, typename Source>
+constexpr bool is_narrowing(long) { return true; }
+
+static_assert(!is_narrowing<std::int16_t, D<int, 16>>(0), "");
+static_assert(!is_narrowing<std::int16_t, D<unsigned, 15>>(0), "");
+static_assert(is_narrowing<std::int16_t, D<unsigned, 16>>(0), "");
+static_assert(!is_narrowing<std::uint16_t, D<unsigned, 16>>(0), "");
+static_assert(is_narrowing<std::uint16_t, D<int, 1>>(0), "");
+static_assert(!is_narrowing<bool, D<unsigned, 1>>(0), "");
+static_assert(is_narrowing<bool, D<int, 1>>(0), "");
+
+template<int N>
+struct E {
+ signed int x : N;
+ decltype(std::int16_t{ x }) dependent_narrowing;
+ decltype(unsigned{ x }) always_narrowing;
+ // since-cxx11-error at -1 {{non-constant-expression cannot be narrowed from type 'int' to 'unsigned int' in initializer list}}
+ // since-cxx11-note at -2 {{insert an explicit cast to silence this issue}}
+};
+#endif
+} // namespace cwg2627
+
namespace cwg2628 { // cwg2628: no
// this was reverted for the 16.x release
// due to regressions, see the issue for more details:
diff --git a/clang/test/Sema/constexpr.c b/clang/test/Sema/constexpr.c
index 5ea2ac24a503a7..0cf9491c4a42bf 100644
--- a/clang/test/Sema/constexpr.c
+++ b/clang/test/Sema/constexpr.c
@@ -360,3 +360,10 @@ void infsNaNs() {
constexpr struct S9 s9 = { }; // expected-error {{variable has incomplete type 'const struct S9'}} \
// expected-note {{forward declaration of 'struct S9'}}
+
+struct S10 {
+ signed long long i : 8;
+};
+constexpr struct S10 c = { 255 };
+// FIXME-expected-error at -1 {{constexpr initializer evaluates to 255 which is not exactly representable in 'long long' bit-field with width 8}}
+// See: GH#101299
diff --git a/clang/test/SemaCXX/bitint-narrowing.cpp b/clang/test/SemaCXX/bitint-narrowing.cpp
new file mode 100644
index 00000000000000..81ca27fa856217
--- /dev/null
+++ b/clang/test/SemaCXX/bitint-narrowing.cpp
@@ -0,0 +1,36 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 %s
+// RUN: %clang_cc1 -triple x86_64 -fsyntax-only -verify -std=c++11 %s
+// RUN: %clang_cc1 -triple i386 -fsyntax-only -verify -std=c++11 %s
+
+struct {
+ _BitInt(35) i : 33;
+} x;
+struct {
+ _BitInt(35) i : 34;
+} y;
+_BitInt(33) xx{ x.i };
+_BitInt(33) yy{ y.i };
+// expected-error at -1 {{non-constant-expression cannot be narrowed from type '_BitInt(35)' to '_BitInt(33)' in initializer list}}
+// FIXME-expected-note at -2 {{insert an explicit cast to silence this issue}}
+
+ _BitInt(2) S2 = 0;
+unsigned _BitInt(2) U2 = 0;
+ _BitInt(3) S3 = 0;
+unsigned _BitInt(3) U3 = 0;
+
+ _BitInt(2) bi0{ S2 };
+ _BitInt(2) bi1{ U2 }; // expected-error {{non-constant-expression cannot be narrowed from type 'unsigned _BitInt(2)' to '_BitInt(2)' in initializer list}}
+ _BitInt(2) bi2{ S3 }; // expected-error {{non-constant-expression cannot be narrowed from type '_BitInt(3)' to '_BitInt(2)' in initializer list}}
+ _BitInt(2) bi3{ U3 }; // expected-error {{non-constant-expression cannot be narrowed from type 'unsigned _BitInt(3)' to '_BitInt(2)' in initializer list}}
+unsigned _BitInt(2) bi4{ S2 }; // expected-error {{non-constant-expression cannot be narrowed from type '_BitInt(2)' to 'unsigned _BitInt(2)' in initializer list}}
+unsigned _BitInt(2) bi5{ U2 };
+unsigned _BitInt(2) bi6{ S3 }; // expected-error {{non-constant-expression cannot be narrowed from type '_BitInt(3)' to 'unsigned _BitInt(2)' in initializer list}}
+unsigned _BitInt(2) bi7{ U3 }; // expected-error {{non-constant-expression cannot be narrowed from type 'unsigned _BitInt(3)' to 'unsigned _BitInt(2)' in initializer list}}
+ _BitInt(3) bi8{ S2 };
+ _BitInt(3) bi9{ U2 };
+ _BitInt(3) bia{ S3 };
+ _BitInt(3) bib{ U3 }; // expected-error {{non-constant-expression cannot be narrowed from type 'unsigned _BitInt(3)' to '_BitInt(3)' in initializer list}}
+unsigned _BitInt(3) bic{ S2 }; // expected-error {{non-constant-expression cannot be narrowed from type '_BitInt(2)' to 'unsigned _BitInt(3)' in initializer list}}
+unsigned _BitInt(3) bid{ U2 };
+unsigned _BitInt(3) bie{ S3 }; // expected-error {{non-constant-expression cannot be narrowed from type '_BitInt(3)' to 'unsigned _BitInt(3)' in initializer list}}
+unsigned _BitInt(3) bif{ U3 };
diff --git a/clang/www/cxx_dr_status.html b/clang/www/cxx_dr_status.html
index 5ad60002733a04..6c283b68aa9656 100755
--- a/clang/www/cxx_dr_status.html
+++ b/clang/www/cxx_dr_status.html
@@ -15577,7 +15577,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2>
<td><a href="https://cplusplus.github.io/CWG/issues/2627.html">2627</a></td>
<td>C++23</td>
<td>Bit-fields and narrowing conversions</td>
- <td class="unknown" align="center">Unknown</td>
+ <td class="unreleased" align="center">Clang 20</td>
</tr>
<tr id="2628">
<td><a href="https://cplusplus.github.io/CWG/issues/2628.html">2628</a></td>
More information about the cfe-commits
mailing list