Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
33 changes: 33 additions & 0 deletions crates/sema/src/ty/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,39 @@ impl<'gcx> Ty<'gcx> {
}
}

// Integer literals can coerce to typed integers if they fit.
// Non-negative literals can coerce to both uint and int types.
//
// TypeSize stores ceil(bit_len/8), so `int_literal[1]` means the literal needs
// at most 8 bits. For unsigned targets, we check size.bits() <= target.bits().
// For signed targets, we need strict inequality since we lose precision in the
// rounding - e.g., both 127 (7 bits) and 128 (8 bits) become int_literal[1],
// but only 127 fits in int8.
(IntLiteral(neg, size), Elementary(UInt(target_size))) => {
// Unsigned: reject negative, check size fits
if neg {
Result::Err(())
} else if size.bits() <= target_size.bits() {
Ok(())
} else {
Result::Err(())
}
}
(IntLiteral(neg, size), Elementary(Int(target_size))) => {
// Signed: need strict inequality for non-negative values because TypeSize
// rounds up to bytes, losing precision. E.g., int_literal[1] can only
// safely coerce to int16+, not int8, since we can't distinguish 127 from 128.
// For negative values, we use non-strict inequality since negative int_literal[N]
// can fit in int(N*8) (e.g., -128 fits in int8).
if neg {
if size.bits() <= target_size.bits() { Ok(()) } else { Result::Err(()) }
} else if size.bits() < target_size.bits() {
Ok(())
} else {
Result::Err(())
}
}

// TODO: more implicit conversions
_ => Result::Err(()),
}
Expand Down
40 changes: 40 additions & 0 deletions tests/ui/typeck/implicit_int_literal.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//@compile-flags: -Ztypeck
function f() {
// === Non-negative literals to uint ===
// Value must fit in the unsigned range [0, 2^N - 1]
uint8 u8_max = 255;
uint8 u8_overflow = 256; //~ ERROR: mismatched types
uint16 u16_max = 65535;
uint16 u16_overflow = 65536; //~ ERROR: mismatched types
uint32 u32_max = 4294967295;
uint256 u256_max = 115792089237316195423570985008687907853269984665640564039457584007913129639935;

// === Non-negative literals to int ===
// Due to TypeSize storing ceil(bit_len/8), literals are grouped by byte size.
// int_literal[N] can safely coerce to int types with more than N bytes.
// This is conservative: e.g., 127 is int_literal[1] and can't coerce to int8,
// even though 127 fits in int8's range [-128, 127].

// int_literal[1] (0-255) -> int16+ works
int16 i16_from_small = 127;
int16 i16_from_255 = 255;

// int_literal[2] (256-65535) -> int32+ works
int32 i32_from_256 = 256;
int32 i32_from_65535 = 65535;

// Overflow cases
int16 i16_overflow = 65536; //~ ERROR: mismatched types

// === Zero and small values ===
// Zero and 1 are int_literal[1], so they work with uint8+ and int16+
uint8 zero_u8 = 0;
uint256 zero_u256 = 0;
uint8 one_u8 = 1;
uint256 one_u256 = 1;

int16 zero_i16 = 0;
int256 zero_i256 = 0;
int16 one_i16 = 1;
int256 one_i256 = 1;
}
20 changes: 20 additions & 0 deletions tests/ui/typeck/implicit_int_literal.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
error: mismatched types
╭▸ ROOT/tests/ui/typeck/implicit_int_literal.sol:LL:CC
LL │ uint8 u8_overflow = 256;
╰╴ ━━━ expected `uint8`, found `int_literal[2]`

error: mismatched types
╭▸ ROOT/tests/ui/typeck/implicit_int_literal.sol:LL:CC
LL │ uint16 u16_overflow = 65536;
╰╴ ━━━━━ expected `uint16`, found `int_literal[3]`

error: mismatched types
╭▸ ROOT/tests/ui/typeck/implicit_int_literal.sol:LL:CC
LL │ int16 i16_overflow = 65536;
╰╴ ━━━━━ expected `int16`, found `int_literal[3]`

error: aborting due to 3 previous errors

Loading