[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