Skip to content

[clang] Implement CWG2627 Bit-fields and narrowing conversions #78112

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,11 @@ Resolutions to C++ Defect Reports
- P0522 implementation is enabled by default in all language versions, and
provisional wording for CWG2398 is implemented.

- 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
------------------

Expand Down
118 changes: 77 additions & 41 deletions clang/lib/Sema/SemaOverload.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -464,61 +464,97 @@ 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:
IntegralConversion: {
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 (const FieldDecl *BitField = Initializer->getSourceBitField()) {
if (BitField->getBitWidth()->isValueDependent())
DependentBitField = true;
else {
unsigned BitFieldWidth = BitField->getBitWidthValue(Ctx);
if (CanRepresentAll(FromSigned, BitFieldWidth, ToSigned, ToWidth)) {
assert(BitFieldWidth < FromWidth &&
"Oversized bit-field can fit in target type but smaller field "
"type couldn't?");
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 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;
}
case ICK_Complex_Real:
Expand Down
24 changes: 24 additions & 0 deletions clang/test/CXX/dcl.decl/dcl.init/dcl.init.list/p7-0x.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
117 changes: 117 additions & 0 deletions clang/test/CXX/drs/cwg26xx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,26 @@
// 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 __INT16_TYPE__ int16_t;
typedef __UINT16_TYPE__ uint16_t;
typedef __INT32_TYPE__ int32_t;
typedef __UINT32_TYPE__ uint32_t;
typedef __INT64_TYPE__ int64_t;
typedef __UINT64_TYPE__ uint64_t;

template<typename T> T declval();
}

namespace cwg2621 { // cwg2621: 16
#if __cplusplus >= 202002L
Expand All @@ -24,6 +44,103 @@ using enum E;
#endif
}

namespace cwg2627 { // cwg2627: 19
#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<int W>
struct D {
__int128 i : W;
};

template<int W>
std::int64_t f(D<W> d) {
return std::int64_t{ d.i }; // #cwg2627-f
}

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@#cwg2627-f {{non-constant-expression cannot be narrowed from type '__int128' to 'std::int64_t' (aka '{{.+}}') in initializer list}}
// since-cxx11-note@-2 {{in instantiation of function template specialization 'cwg2627::f<65>' requested here}}
// since-cxx11-note@#cwg2627-f {{insert an explicit cast to silence this issue}}

template<typename Target, typename Source>
Target g(Source x) {
return Target{ x.i }; // #cwg2627-g
}

template<typename T, int N>
struct E {
T i : N;
};

template std::int16_t g(E<int, 16>);
template std::int16_t g(E<unsigned, 15>);
template std::int16_t g(E<unsigned, 16>);
// since-cxx11-error-re@#cwg2627-g {{non-constant-expression cannot be narrowed from type 'unsigned int' to '{{.+}}' in initializer list}}
// since-cxx11-note-re@-2 {{in instantiation of function template specialization 'cwg2627::g<{{.+}}, cwg2627::E<unsigned int, 16>>' requested here}}
// since-cxx11-note@#cwg2627-g {{insert an explicit cast to silence this issue}}
template std::uint16_t g(E<unsigned, 16>);
template std::uint16_t g(E<int, 1>);
// since-cxx11-error-re@#cwg2627-g {{non-constant-expression cannot be narrowed from type 'int' to '{{.+}}' in initializer list}}
// since-cxx11-note-re@-2 {{in instantiation of function template specialization 'cwg2627::g<{{.+}}, cwg2627::E<int, 1>>' requested here}}
// since-cxx11-note@#cwg2627-g {{insert an explicit cast to silence this issue}}

template bool g(E<unsigned, 1>);
template bool g(E<int, 1>);
// since-cxx11-error@#cwg2627-g {{non-constant-expression cannot be narrowed from type 'int' to 'bool' in initializer list}}
// since-cxx11-note@-2 {{in instantiation of function template specialization 'cwg2627::g<bool, cwg2627::E<int, 1>>' requested here}}
// since-cxx11-note@#cwg2627-g {{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, E<int, 16>>(0), "");
static_assert(!is_narrowing<std::int16_t, E<unsigned, 15>>(0), "");
static_assert(is_narrowing<std::int16_t, E<unsigned, 16>>(0), "");
static_assert(!is_narrowing<std::uint16_t, E<unsigned, 16>>(0), "");
static_assert(is_narrowing<std::uint16_t, E<int, 1>>(0), "");
static_assert(!is_narrowing<bool, E<unsigned, 1>>(0), "");
static_assert(is_narrowing<bool, E<int, 1>>(0), "");

template<int N>
struct F {
signed int x : N;
decltype(std::int16_t{ x }) dependent_narrowing;
decltype(unsigned{ x }) always_narrowing;
// since-cxx11-error@-1 {{non-constant-expression cannot be narrowed from type 'int' to 'unsigned int' in initializer list}}
// since-cxx11-note@-2 {{insert an explicit cast to silence this issue}}
};
#endif
}

namespace cwg2628 { // cwg2628: no
// this was reverted for the 16.x release
// due to regressions, see the issue for more details:
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 @@ -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 19</td>
</tr>
<tr id="2628">
<td><a href="https://cplusplus.github.io/CWG/issues/2628.html">2628</a></td>
Expand Down
Loading