Skip to content

Commit 92db371

Browse files
committed
feat(typeck): implement implicit integer literal coercion
Add implicit type coercion for integer literals (IntLiteral) to typed integers (uint/int). The coercion rules are: - IntLiteral -> uint: Allowed if the literal is non-negative and fits in the target size (size.bits() <= target.bits()) - IntLiteral -> int: Allowed with strict inequality (size.bits() < target.bits()) for non-negative values due to TypeSize rounding, and non-strict for negative values TypeSize stores ceil(bit_len/8), so int_literal[1] covers 0-255. This means we can't distinguish edge cases like 127 (fits in int8) from 128 (doesn't fit), so we conservatively require int16+ for int_literal[1]. Note: Negative literal support requires additional work in the type checker to propagate negativity through unary negation.
1 parent 4051954 commit 92db371

11 files changed

Lines changed: 106 additions & 64 deletions

File tree

crates/sema/src/ty/ty.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,40 @@ impl<'gcx> Ty<'gcx> {
548548
}
549549
}
550550

551+
// Integer literals can coerce to typed integers if they fit.
552+
// Non-negative literals can coerce to both uint and int types.
553+
//
554+
// TypeSize stores the actual bit length. For unsigned targets, we check
555+
// size.bits() <= target.bits(). For signed targets, non-negative values
556+
// need strict inequality since the sign bit takes one slot (e.g., 128
557+
// requires 8 bits but int8 only has 7 bits for magnitude).
558+
(IntLiteral(neg, size), Elementary(UInt(target_size))) => {
559+
// Unsigned: reject negative, check size fits
560+
if neg {
561+
Result::Err(TyConvertError::Incompatible)
562+
} else if size.bits() <= target_size.bits() {
563+
Ok(())
564+
} else {
565+
Result::Err(TyConvertError::Incompatible)
566+
}
567+
}
568+
(IntLiteral(neg, size), Elementary(Int(target_size))) => {
569+
// Signed: non-negative values need strict inequality since they use the
570+
// positive range [0, 2^(N-1)-1]. Negative values use <= since negative
571+
// int_literal[N] can fit in int(N) (e.g., -128 needs 8 bits, fits in int8).
572+
if neg {
573+
if size.bits() <= target_size.bits() {
574+
Ok(())
575+
} else {
576+
Result::Err(TyConvertError::Incompatible)
577+
}
578+
} else if size.bits() < target_size.bits() {
579+
Ok(())
580+
} else {
581+
Result::Err(TyConvertError::Incompatible)
582+
}
583+
}
584+
551585
// TODO: more implicit conversions
552586
_ => Result::Err(TyConvertError::Incompatible),
553587
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//@compile-flags: -Ztypeck
2+
function f() {
3+
// === Non-negative literals to uint ===
4+
// Value must fit in the unsigned range [0, 2^N - 1]
5+
uint8 u8_max = 255;
6+
uint8 u8_overflow = 256; //~ ERROR: mismatched types
7+
uint16 u16_max = 65535;
8+
uint16 u16_overflow = 65536; //~ ERROR: mismatched types
9+
uint32 u32_max = 4294967295;
10+
uint256 u256_max = 115792089237316195423570985008687907853269984665640564039457584007913129639935;
11+
12+
// === Non-negative literals to int ===
13+
// TypeSize stores the actual bit length. For signed types, non-negative values
14+
// need strict bit comparison since the sign bit takes one slot.
15+
// E.g., 127 needs 7 bits, and int8 has 7 bits for magnitude, so it fits.
16+
// But 128 needs 8 bits, which exceeds int8's 7-bit positive range.
17+
18+
// int_literal[1] (1-8 bits) -> int8+ works for values that fit (0-127)
19+
int8 i8_max = 127;
20+
// int_literal[1] (8 bits) -> int16+ works for 128-255 (needs 8 bits, exceeds int8's 7-bit positive range)
21+
int16 i16_from_128 = 128;
22+
int16 i16_from_255 = 255;
23+
24+
// int_literal[2] (9-16 bits) -> int32+ works for 256-32767
25+
int16 i16_max = 32767;
26+
// 32768 needs 16 bits, exceeds int16's 15-bit positive range
27+
int32 i32_from_32768 = 32768;
28+
int32 i32_from_65535 = 65535;
29+
30+
// Overflow cases
31+
int16 i16_overflow = 65536; //~ ERROR: mismatched types
32+
33+
// === Zero and small values ===
34+
// Zero needs 1 bit, works with uint8+ and int8+
35+
uint8 zero_u8 = 0;
36+
uint256 zero_u256 = 0;
37+
uint8 one_u8 = 1;
38+
uint256 one_u256 = 1;
39+
40+
int8 zero_i8 = 0;
41+
int256 zero_i256 = 0;
42+
int8 one_i8 = 1;
43+
int256 one_i256 = 1;
44+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: mismatched types
2+
╭▸ ROOT/tests/ui/typeck/implicit_int_literal.sol:LL:CC
3+
4+
LL │ uint8 u8_overflow = 256;
5+
╰╴ ━━━ expected `uint8`, found `int_literal[2]`
6+
7+
error: mismatched types
8+
╭▸ ROOT/tests/ui/typeck/implicit_int_literal.sol:LL:CC
9+
10+
LL │ uint16 u16_overflow = 65536;
11+
╰╴ ━━━━━ expected `uint16`, found `int_literal[3]`
12+
13+
error: mismatched types
14+
╭▸ ROOT/tests/ui/typeck/implicit_int_literal.sol:LL:CC
15+
16+
LL │ int16 i16_overflow = 65536;
17+
╰╴ ━━━━━ expected `int16`, found `int_literal[3]`
18+
19+
error: aborting due to 3 previous errors
20+

tests/ui/typeck/lvalue/array_length.sol

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
//@compile-flags: -Ztypeck
2-
// TODO: `mismatched types` errors on integer literals are a current limitation of solar
32

43
contract Test {
54
uint256[] dynamicArray;
6-
uint256[10] fixedArray; //~ ERROR: mismatched types
5+
uint256[10] fixedArray;
76
uint256 state;
87

98
function testDynamic() external {

tests/ui/typeck/lvalue/array_length.stderr

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
error: mismatched types
2-
╭▸ ROOT/tests/ui/typeck/lvalue/array_length.sol:LL:CC
3-
4-
LL │ uint256[10] fixedArray;
5-
╰╴ ━━ expected `uint256`, found `int_literal[1]`
6-
71
error: member `length` is read-only and cannot be used to resize arrays
82
╭▸ ROOT/tests/ui/typeck/lvalue/array_length.sol:LL:CC
93
@@ -22,5 +16,5 @@ error: member `length` is read-only and cannot be used to resize arrays
2216
LL │ arr.length = state;
2317
╰╴ ━━━━━━━━━━
2418

25-
error: aborting due to 4 previous errors
19+
error: aborting due to 3 previous errors
2620

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
11
//@compile-flags: -Ztypeck
2-
// TODO: `mismatched types` errors on integer literals are a current limitation of solar
32

43
contract Test {
5-
uint256 constant CONST = 1; //~ ERROR: mismatched types
4+
uint256 constant CONST = 1;
65

76
function test() external {
87
CONST = 2; //~ ERROR: cannot assign to a constant variable
9-
//~^ ERROR: mismatched types
108
}
119
}
1210

13-
uint256 constant FILE_CONST = 1; //~ ERROR: mismatched types
11+
uint256 constant FILE_CONST = 1;
1412

1513
function fileLevel() {
1614
FILE_CONST = 2; //~ ERROR: cannot assign to a constant variable
17-
//~^ ERROR: mismatched types
1815
}
Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,14 @@
1-
error: mismatched types
2-
╭▸ ROOT/tests/ui/typeck/lvalue/constant.sol:LL:CC
3-
4-
LL │ uint256 constant CONST = 1;
5-
╰╴ ━ expected `uint256`, found `int_literal[1]`
6-
71
error: cannot assign to a constant variable
82
╭▸ ROOT/tests/ui/typeck/lvalue/constant.sol:LL:CC
93
104
LL │ CONST = 2;
115
╰╴ ━━━━━
126

13-
error: mismatched types
14-
╭▸ ROOT/tests/ui/typeck/lvalue/constant.sol:LL:CC
15-
16-
LL │ CONST = 2;
17-
╰╴ ━ expected `uint256`, found `int_literal[1]`
18-
19-
error: mismatched types
20-
╭▸ ROOT/tests/ui/typeck/lvalue/constant.sol:LL:CC
21-
22-
LL │ uint256 constant FILE_CONST = 1;
23-
╰╴ ━ expected `uint256`, found `int_literal[1]`
24-
257
error: cannot assign to a constant variable
268
╭▸ ROOT/tests/ui/typeck/lvalue/constant.sol:LL:CC
279
2810
LL │ FILE_CONST = 2;
2911
╰╴ ━━━━━━━━━━
3012

31-
error: mismatched types
32-
╭▸ ROOT/tests/ui/typeck/lvalue/constant.sol:LL:CC
33-
34-
LL │ FILE_CONST = 2;
35-
╰╴ ━ expected `uint256`, found `int_literal[1]`
36-
37-
error: aborting due to 6 previous errors
13+
error: aborting due to 2 previous errors
3814

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
11
//@compile-flags: -Ztypeck
22
// TODO: assignments to immutables in the constructor should be allowed
3-
// TODO: `mismatched types` errors on integer literals are a current limitation of solar
43

54
contract Test {
65
uint256 immutable IMMUT;
76

87
constructor() {
98
// This should be OK in constructor but currently errors
109
IMMUT = 1; //~ ERROR: cannot assign to an immutable variable
11-
//~^ ERROR: mismatched types
1210
}
1311

1412
function test() external {
1513
IMMUT = 2; //~ ERROR: cannot assign to an immutable variable
16-
//~^ ERROR: mismatched types
1714
}
1815
}

tests/ui/typeck/lvalue/immutable.stderr

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,11 @@ error: cannot assign to an immutable variable
44
LL │ IMMUT = 1;
55
╰╴ ━━━━━
66

7-
error: mismatched types
8-
╭▸ ROOT/tests/ui/typeck/lvalue/immutable.sol:LL:CC
9-
10-
LL │ IMMUT = 1;
11-
╰╴ ━ expected `uint256`, found `int_literal[1]`
12-
137
error: cannot assign to an immutable variable
148
╭▸ ROOT/tests/ui/typeck/lvalue/immutable.sol:LL:CC
159
1610
LL │ IMMUT = 2;
1711
╰╴ ━━━━━
1812

19-
error: mismatched types
20-
╭▸ ROOT/tests/ui/typeck/lvalue/immutable.sol:LL:CC
21-
22-
LL │ IMMUT = 2;
23-
╰╴ ━ expected `uint256`, found `int_literal[1]`
24-
25-
error: aborting due to 4 previous errors
13+
error: aborting due to 2 previous errors
2614

tests/ui/typeck/lvalue/tuple.sol

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
//@compile-flags: -Ztypeck
2-
// TODO: `mismatched types` errors on integer literals are a current limitation of solar
32

43
contract Test {
5-
uint256 constant CONST = 1; //~ ERROR: mismatched types
4+
uint256 constant CONST = 1;
65
uint256 state;
76

87
function testTupleWithConstant() external {

0 commit comments

Comments
 (0)