Skip to content

Commit 644ccf2

Browse files
onbjergmablr
andauthored
feat(typeck): support negative integer literal coercion (#648)
Implement proper handling of negative integer literals in the type checker. Previously, negative literals like -42 were broken because: 1. The parser represents them as unary negation applied to a positive literal, so type_of_lit only sees the positive value 2. The negativity flag in IntLiteral was always false This commit fixes the issue by: - Allowing unary negation on IntLiteral types (they can always be negated since the result is just a negative literal) - Propagating the negativity flag when applying unary negation to an IntLiteral, flipping neg from false to true - Not propagating the expected type through negation when targeting signed types, to avoid premature type mismatch errors on the inner expression Also simplifies the coercion rule to use strict inequality for both positive and negative literals, since TypeSize rounding means we can't reliably distinguish edge cases in either direction. On top of #647 Supercedes #566 (will mark @mablr as a co-author to commend effort) and closes #560 (closes #566) Stack: - #647 - #648 (this) - #649 Co-Authored-By: Mablr <[email protected]>
1 parent 16b0732 commit 644ccf2

File tree

3 files changed

+64
-9
lines changed

3 files changed

+64
-9
lines changed

crates/sema/src/typeck/checker.rs

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -482,13 +482,25 @@ impl<'gcx> TypeChecker<'gcx> {
482482
self.gcx.mk_ty(TyKind::Type(self.gcx.type_of_hir_ty(ty)))
483483
}
484484
hir::ExprKind::Unary(op, expr) => {
485+
// For negation, don't propagate expected type to the inner expression
486+
// because we'll modify the type (flipping the sign for int literals).
487+
let propagate_expected = op.kind != hir::UnOpKind::Neg
488+
|| !matches!(expected, Some(ty) if ty.is_signed());
485489
let ty = if op.kind.has_side_effects() {
486490
self.require_lvalue(expr)
487-
} else {
491+
} else if propagate_expected {
488492
self.check_expr_with(expr, expected)
493+
} else {
494+
self.check_expr(expr)
489495
};
490496
// TODO: custom operators
491497
if valid_unop(ty, op.kind) {
498+
// Propagate negativity for integer literals under unary negation.
499+
if op.kind == hir::UnOpKind::Neg
500+
&& let TyKind::IntLiteral(neg, size) = ty.kind
501+
{
502+
return self.gcx.mk_ty(TyKind::IntLiteral(!neg, size));
503+
}
492504
ty
493505
} else {
494506
let msg = format!(
@@ -1001,15 +1013,25 @@ fn valid_unop(ty: Ty<'_>, op: hir::UnOpKind) -> bool {
10011013

10021014
let ty = ty.peel_refs();
10031015
match ty.kind {
1004-
TyKind::Elementary(hir::ElementaryType::Int(_) | hir::ElementaryType::UInt(_))
1005-
| TyKind::IntLiteral(..) => match op {
1006-
hir::UnOpKind::Neg => ty.is_signed(),
1007-
hir::UnOpKind::Not => false,
1008-
hir::UnOpKind::PreInc
1016+
TyKind::Elementary(hir::ElementaryType::Int(_) | hir::ElementaryType::UInt(_)) => {
1017+
match op {
1018+
hir::UnOpKind::Neg => ty.is_signed(),
1019+
hir::UnOpKind::Not => false,
1020+
hir::UnOpKind::PreInc
1021+
| hir::UnOpKind::PreDec
1022+
| hir::UnOpKind::BitNot
1023+
| hir::UnOpKind::PostInc
1024+
| hir::UnOpKind::PostDec => true,
1025+
}
1026+
}
1027+
// IntLiteral can always be negated (it becomes a negative literal).
1028+
TyKind::IntLiteral(..) => match op {
1029+
hir::UnOpKind::Neg | hir::UnOpKind::BitNot => true,
1030+
hir::UnOpKind::Not
1031+
| hir::UnOpKind::PreInc
10091032
| hir::UnOpKind::PreDec
1010-
| hir::UnOpKind::BitNot
10111033
| hir::UnOpKind::PostInc
1012-
| hir::UnOpKind::PostDec => true,
1034+
| hir::UnOpKind::PostDec => false,
10131035
},
10141036
TyKind::Elementary(hir::ElementaryType::FixedBytes(_)) => op == hir::UnOpKind::BitNot,
10151037
TyKind::Elementary(hir::ElementaryType::Bool) => op == hir::UnOpKind::Not,

tests/ui/typeck/implicit_int_literal.sol

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,25 @@ function f() {
4141
int256 zero_i256 = 0;
4242
int8 one_i8 = 1;
4343
int256 one_i256 = 1;
44+
45+
// === Negative literals to int ===
46+
// Negative literals can only coerce to signed int types.
47+
// TypeSize stores actual bits. Negative int_literal[N] can fit in int(N) since
48+
// the negative range uses all N bits (e.g., -128 needs 8 bits, fits in int8).
49+
50+
// int_literal[1] negative (1-8 bits) -> int8+ works
51+
int8 neg_1_i8 = -1;
52+
int8 neg_128_i8 = -128;
53+
int256 neg_1_i256 = -1;
54+
55+
// int_literal[2] negative (9-16 bits) -> int16+ works
56+
int16 neg_129_i16 = -129;
57+
int16 neg_32768_i16 = -32768;
58+
59+
// int_literal[3] negative (17-24 bits) -> int24+ works
60+
int32 neg_32769_i32 = -32769;
61+
62+
// Negative literals cannot coerce to unsigned types
63+
uint8 neg_to_uint8 = -1; //~ ERROR: mismatched types
64+
uint256 neg_to_uint256 = -42; //~ ERROR: mismatched types
4465
}

tests/ui/typeck/implicit_int_literal.stderr

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,17 @@ error: mismatched types
1616
LL │ int16 i16_overflow = 65536;
1717
╰╴ ━━━━━ expected `int16`, found `int_literal[3]`
1818

19-
error: aborting due to 3 previous errors
19+
error: mismatched types
20+
╭▸ ROOT/tests/ui/typeck/implicit_int_literal.sol:LL:CC
21+
22+
LL │ uint8 neg_to_uint8 = -1;
23+
╰╴ ━━ expected `uint8`, found `int_literal[1]`
24+
25+
error: mismatched types
26+
╭▸ ROOT/tests/ui/typeck/implicit_int_literal.sol:LL:CC
27+
28+
LL │ uint256 neg_to_uint256 = -42;
29+
╰╴ ━━━ expected `uint256`, found `int_literal[1]`
30+
31+
error: aborting due to 5 previous errors
2032

0 commit comments

Comments
 (0)