Feat: add UnboundInt type for integer literal inference
Integer literals now produce `Ty::UnboundInt` instead of a hardcoded `i32`. This flexible type promotes to/from any concrete integer, so literals resolve naturally in context (e.g. `n < 2` when `n: u8` works without an explicit cast). `common(UnboundInt, T)` returns `T` when `T` is a concrete integer, so binary ops adopt the concrete operand's type. Includes 8 new tests covering literal coercion, fibonacci-style patterns, and negative cases (literals still don't coerce to float types). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -16,8 +16,8 @@ impl Checker {
|
||||
assigned: &HashSet<String>,
|
||||
) -> Ty {
|
||||
match &expr.kind {
|
||||
// T-IntLit → i32
|
||||
ExprKind::IntLit(_) => Ty::I32,
|
||||
// T-IntLit → UnboundInt (resolved from context; defaults to i32)
|
||||
ExprKind::IntLit(_) => Ty::UnboundInt,
|
||||
|
||||
// T-FloatLit → f64
|
||||
ExprKind::FloatLit(_) => Ty::F64,
|
||||
@@ -338,6 +338,10 @@ impl Checker {
|
||||
) -> Ty {
|
||||
match op {
|
||||
UnaryOp::Neg => {
|
||||
// UnboundInt is a literal-origin integer; negating keeps it unbound.
|
||||
if matches!(inner_ty, Ty::UnboundInt) {
|
||||
return Ty::UnboundInt;
|
||||
}
|
||||
if !inner_ty.is_signed() && !inner_ty.is_float() {
|
||||
self.emit(
|
||||
Diagnostic::error(format!(
|
||||
@@ -501,7 +505,7 @@ impl Checker {
|
||||
Ty::Bool
|
||||
}
|
||||
|
||||
// Ordering: numeric or char
|
||||
// Ordering: numeric or char (UnboundInt resolves to the other operand's type)
|
||||
BinaryOp::Lt | BinaryOp::Gt | BinaryOp::Le | BinaryOp::Ge => {
|
||||
let lhs_ty = self.check_expr(lhs, env, assigned);
|
||||
let rhs_ty = self.check_expr(rhs, env, assigned);
|
||||
@@ -524,7 +528,7 @@ impl Checker {
|
||||
Ty::Bool
|
||||
}
|
||||
|
||||
// Bitwise: require matching integer types
|
||||
// Bitwise: require integer types (UnboundInt is fine)
|
||||
BinaryOp::BitOr | BinaryOp::BitXor | BinaryOp::BitAnd => {
|
||||
let lhs_ty = self.check_expr(lhs, env, assigned);
|
||||
let rhs_ty = self.check_expr(rhs, env, assigned);
|
||||
@@ -547,7 +551,9 @@ impl Checker {
|
||||
}
|
||||
}
|
||||
|
||||
// Shift: LHS integer, RHS any integer; result type = LHS type
|
||||
// Shift: LHS integer, RHS any integer; result type = LHS type.
|
||||
// If LHS is UnboundInt and RHS is concrete, result is still UnboundInt
|
||||
// (it will be resolved when the result is used in context).
|
||||
BinaryOp::Shl | BinaryOp::Shr => {
|
||||
let lhs_ty = self.check_expr(lhs, env, assigned);
|
||||
let rhs_ty = self.check_expr(rhs, env, assigned);
|
||||
@@ -625,6 +631,8 @@ impl Checker {
|
||||
fn check_arith_or_shift(&mut self, lhs_ty: Ty, rhs_ty: Ty, op: BinaryOp, op_span: Span) -> Ty {
|
||||
match Ty::common(&lhs_ty, &rhs_ty) {
|
||||
Some(Ty::Error) => Ty::Error,
|
||||
// UnboundInt + UnboundInt → still UnboundInt (resolved downstream)
|
||||
Some(Ty::UnboundInt) => Ty::UnboundInt,
|
||||
Some(ref c) if c.is_numeric() => c.clone(),
|
||||
_ => {
|
||||
let sym = match op {
|
||||
|
||||
Reference in New Issue
Block a user