Skip to content
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