[clang] [clang] [SemaCXX] Implement CWG2627 Bit-fields and narrowing conversions (PR #78112)
via cfe-commits
cfe-commits at lists.llvm.org
Sun Jan 14 12:12:31 PST 2024
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Mital Ashok (MitalAshok)
<details>
<summary>Changes</summary>
[CWG2627](https://wg21.link/CWG2627)
I've implemented this to apply to C++11 to 20 as well without a warning. Should this be a SFINAE-able error before C++23? (It isn't currently)
---
Full diff: https://github.com/llvm/llvm-project/pull/78112.diff
5 Files Affected:
- (modified) clang/docs/ReleaseNotes.rst (+4)
- (modified) clang/lib/Sema/SemaOverload.cpp (+66-41)
- (modified) clang/test/CXX/dcl.decl/dcl.init/dcl.init.list/p7-0x.cpp (+24)
- (modified) clang/test/CXX/drs/dr26xx.cpp (+46)
- (modified) clang/www/cxx_dr_status.html (+1-1)
``````````diff
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index dc8a6fe506bce6..6fd84d9c3364d3 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -226,6 +226,10 @@ C++2c Feature Support
Resolutions to C++ Defect Reports
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+- Implemented `CWG2627 <https://wg21.link/CWG2627>`_ which means 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.
C Language Changes
------------------
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 23b9bc0fe2d6e2..b053b9ddfa5f26 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -433,7 +433,11 @@ 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
+ // -- 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:
@@ -441,53 +445,74 @@ 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 different, 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 (auto *BF = Initializer->getSourceBitField()) {
+ auto *Width = BF->getBitWidth();
+ DependentBitField = Width->isValueDependent();
+ if (!DependentBitField) {
+ FromWidth = BF->getBitWidthValue(Ctx);
+ if (CanRepresentAll(FromSigned, FromWidth, ToSigned, ToWidth))
+ return NK_Not_Narrowing;
}
}
+
+ // 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;
+ if (!(OptInitializerValue = Initializer->getIntegerConstantExpr(Ctx))) {
+ // Such would be narrowing only if the source width ends up being too
+ // large
+ if (DependentBitField)
+ return NK_Dependent_Narrowing;
+ // Otherwise it is 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 different, 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;
}
diff --git a/clang/test/CXX/dcl.decl/dcl.init/dcl.init.list/p7-0x.cpp b/clang/test/CXX/dcl.decl/dcl.init/dcl.init.list/p7-0x.cpp
index 2bceb3e267790d..a3049dc57d9fac 100644
--- a/clang/test/CXX/dcl.decl/dcl.init/dcl.init.list/p7-0x.cpp
+++ b/clang/test/CXX/dcl.decl/dcl.init/dcl.init.list/p7-0x.cpp
@@ -203,6 +203,30 @@ void shrink_int() {
unsigned short usc1 = { c }; // expected-error {{non-constant-expression cannot be narrowed from type 'signed char'}} expected-note {{silence}}
unsigned short usc2 = { (signed char)'x' }; // OK
unsigned short usc3 = { (signed char)-1 }; // expected-error {{ -1 which cannot be narrowed}} expected-note {{silence}}
+
+#if __BITINT_MAXWIDTH__ >= 3
+ _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 {{cannot be narrowed from type 'unsigned _BitInt(2)'}}
+ _BitInt(2) bi2 = { S3 }; // expected-error {{cannot be narrowed from type '_BitInt(3)'}}
+ _BitInt(2) bi3 = { U3 }; // expected-error {{cannot be narrowed from type 'unsigned _BitInt(3)'}}
+ unsigned _BitInt(2) bi4 = { S2 }; // expected-error {{cannot be narrowed from type '_BitInt(2)'}}
+ unsigned _BitInt(2) bi5 = { U2 };
+ unsigned _BitInt(2) bi6 = { S3 }; // expected-error {{cannot be narrowed from type '_BitInt(3)'}}
+ unsigned _BitInt(2) bi7 = { U3 }; // expected-error {{cannot be narrowed from type 'unsigned _BitInt(3)'}}
+ _BitInt(3) bi8 = { S2 };
+ _BitInt(3) bi9 = { U2 };
+ _BitInt(3) bia = { S3 };
+ _BitInt(3) bib = { U3 }; // expected-error {{cannot be narrowed from type 'unsigned _BitInt(3)'}}
+ unsigned _BitInt(3) bic = { S2 }; // expected-error {{cannot be narrowed from type '_BitInt(2)'}}
+ unsigned _BitInt(3) bid = { U2 };
+ unsigned _BitInt(3) bie = { S3 }; // expected-error {{cannot be narrowed from type '_BitInt(3)'}}
+ unsigned _BitInt(3) bif = { U3 };
+#endif
}
// Be sure that type- and value-dependent expressions in templates get the error
diff --git a/clang/test/CXX/drs/dr26xx.cpp b/clang/test/CXX/drs/dr26xx.cpp
index f151c9eea051a3..3b2b26c101d61b 100644
--- a/clang/test/CXX/drs/dr26xx.cpp
+++ b/clang/test/CXX/drs/dr26xx.cpp
@@ -6,6 +6,19 @@
// RUN: %clang_cc1 -std=c++23 -triple x86_64-unknown-unknown %s -verify=expected,since-cxx11,since-cxx20,since-cxx23
// RUN: %clang_cc1 -std=c++2c -triple x86_64-unknown-unknown %s -verify=expected,since-cxx11,since-cxx20,since-cxx23
+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 __INT64_TYPE__ int64_t;
+}
namespace dr2621 { // dr2621: 16
#if __cplusplus >= 202002L
@@ -24,6 +37,39 @@ using enum E;
#endif
}
+namespace dr2627 { // dr2627: 18
+#if __cplusplus >= 201103L
+struct C {
+ long long i : 8;
+};
+
+#if __cplusplus >= 202002L
+void f() {
+ C x{1}, y{2};
+ static_cast<void>(x.i <=> y.i);
+}
+#endif
+
+template<int N>
+struct D {
+ __int128 i : N;
+};
+
+template<int N>
+std::int64_t f(D<N> d) {
+ return std::int64_t{ d.i }; // #dr2627-f-narrowing
+}
+
+template std::int64_t f(D<63>);
+template std::int64_t f(D<64>);
+template std::int64_t f(D<65>);
+// since-cxx11-error-re@#dr2627-f-narrowing {{non-constant-expression cannot be narrowed from type '__int128' to 'std::int64_t' (aka '{{.+}}') in initializer list}}
+// since-cxx11-note at -2 {{in instantiation of function template specialization 'dr2627::f<65>' requested here}}
+// since-cxx11-note@#dr2627-f-narrowing {{insert an explicit cast to silence this issue}}
+
+#endif
+}
+
namespace dr2628 { // dr2628: no
// this was reverted for the 16.x release
// due to regressions, see the issue for more details:
diff --git a/clang/www/cxx_dr_status.html b/clang/www/cxx_dr_status.html
index 397bf1357d3cb3..caecdfe27a9498 100755
--- a/clang/www/cxx_dr_status.html
+++ b/clang/www/cxx_dr_status.html
@@ -15570,7 +15570,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 18</td>
</tr>
<tr id="2628">
<td><a href="https://cplusplus.github.io/CWG/issues/2628.html">2628</a></td>
``````````
</details>
https://github.com/llvm/llvm-project/pull/78112
More information about the cfe-commits
mailing list