Skip to content

Commit e47d83d

Browse files
authored
Merge pull request #1980 from wado-lang/claude/wide-int-refactoring-18yc7s
refactor(compiler): give `i128` / `u128` one spelling in the type table
2 parents 6c883ab + 82c8420 commit e47d83d

33 files changed

Lines changed: 3268 additions & 868 deletions

docs/spec.md

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1121,26 +1121,26 @@ Wado types are stored internally as WebAssembly core and GC types, and lift/lowe
11211121

11221122
The table below is the Wado↔CM correspondence, read in both directions: Wado→CM when generating a component's exported interface, and CM→Wado when importing an external component (`use { Iface } from "./c.wasm" with { type: "wasm" }`, see [Wasm Module and Component Imports](#wasm-module-and-component-imports)). CM types are written in their WIT spelling.
11231123

1124-
| Wado Type | Internal Representation | CM Type at Boundary | Notes |
1125-
| ------------------------- | ---------------------------- | ------------------------------------ | ------------------------------------------------------------------------ |
1126-
| `bool` | `i32` | `bool` | Boolean value |
1127-
| `char` | `i32` | `char` | Unicode scalar value |
1128-
| `i8`, `i16`, `i32`, `i64` | `i32`, `i32`, `i32`, `i64` | `s8`, `s16`, `s32`, `s64` | Signed integers |
1129-
| `u8`, `u16`, `u32`, `u64` | `i32`, `i32`, `i32`, `i64` | `u8`, `u16`, `u32`, `u64` | Unsigned integers |
1130-
| `i128`, `u128` | `i64` pair (Wide Arithmetic) | `tuple<s64, s64>`, `tuple<u64, u64>` | 128-bit integers |
1131-
| `f32`, `f64` | `f32`, `f64` | `f32`, `f64` | Floating point |
1132-
| `String` | GC `array i8` (UTF-8) | `string` | UTF-8 string, GC-managed internally |
1133-
| `List<T>` | GC `array T` | `list<T>` | Dynamic array, GC-managed internally |
1134-
| `[T1, T2, ...]` | GC `struct {T1, T2, ...}` | `tuple<T1, T2, ...>` | Tuple types |
1135-
| `Option<T>` | GC variant | `option<T>` | Optional value |
1136-
| `Result<T, E>` | GC variant | `result<T, E>` | Result type; `result<ok>` and bare `result` are the payload-elided forms |
1137-
| `struct { ... }` | GC `struct` | `record { ... }` | Wasm GC struct internally, record at CM boundary |
1138-
| `enum { ... }` | `i32` | `enum { ... }` | Enumeration without payloads |
1139-
| `variant { ... }` | GC variant | `variant { ... }` | Variant/sum type with payloads |
1140-
| `flags { ... }` | `i32`/`i64` | `flags { ... }` | Bit flags |
1141-
| `resource` | `i32` (handle) | `resource` | Resource handle; owned and borrowed handles both map here |
1142-
| `Stream<T>` | CM stream (P3) | `stream<T>` | Component Model async stream |
1143-
| `Future<T>` | CM future (P3) | `future<T>` | Component Model async future |
1124+
| Wado Type | Internal Representation | CM Type at Boundary | Notes |
1125+
| ------------------------- | -------------------------- | ------------------------- | ------------------------------------------------------------------------ |
1126+
| `bool` | `i32` | `bool` | Boolean value |
1127+
| `char` | `i32` | `char` | Unicode scalar value |
1128+
| `i8`, `i16`, `i32`, `i64` | `i32`, `i32`, `i32`, `i64` | `s8`, `s16`, `s32`, `s64` | Signed integers |
1129+
| `u8`, `u16`, `u32`, `u64` | `i32`, `i32`, `i32`, `i64` | `u8`, `u16`, `u32`, `u64` | Unsigned integers |
1130+
| `i128`, `u128` | GC `struct {u64, i64/u64}` | `record { low, high }` | Prelude structs, so each crosses as its own record |
1131+
| `f32`, `f64` | `f32`, `f64` | `f32`, `f64` | Floating point |
1132+
| `String` | GC `array i8` (UTF-8) | `string` | UTF-8 string, GC-managed internally |
1133+
| `List<T>` | GC `array T` | `list<T>` | Dynamic array, GC-managed internally |
1134+
| `[T1, T2, ...]` | GC `struct {T1, T2, ...}` | `tuple<T1, T2, ...>` | Tuple types |
1135+
| `Option<T>` | GC variant | `option<T>` | Optional value |
1136+
| `Result<T, E>` | GC variant | `result<T, E>` | Result type; `result<ok>` and bare `result` are the payload-elided forms |
1137+
| `struct { ... }` | GC `struct` | `record { ... }` | Wasm GC struct internally, record at CM boundary |
1138+
| `enum { ... }` | `i32` | `enum { ... }` | Enumeration without payloads |
1139+
| `variant { ... }` | GC variant | `variant { ... }` | Variant/sum type with payloads |
1140+
| `flags { ... }` | `i32`/`i64` | `flags { ... }` | Bit flags |
1141+
| `resource` | `i32` (handle) | `resource` | Resource handle; owned and borrowed handles both map here |
1142+
| `Stream<T>` | CM stream (P3) | `stream<T>` | Component Model async stream |
1143+
| `Future<T>` | CM future (P3) | `future<T>` | Component Model async future |
11441144

11451145
### The Prelude
11461146

@@ -1222,6 +1222,10 @@ Available operations:
12221222
| Bitwise | `&`, `\|`, `^`, `~`, `<<`, `>>` |
12231223
| Conversion | `from_u64()`, `from_i64()`, `low()`, `high()`, `as`, `TryFrom` |
12241224

1225+
Literal and range patterns work on them in every pattern position, nested ones
1226+
included: `match [x, y] { [1..=5, _] => … }`. Each lowers to the same `Eq` /
1227+
`Ord` call the equivalent comparison does.
1228+
12251229
`as` casts follow Rust semantics in both directions:
12261230

12271231
```wado

wado-compiler/src/builtin_registry.rs

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::hashmap::IndexMap;
77
use std::cell::RefCell;
88

99
use crate::ast::{CmBoundary, Function, Type};
10+
use crate::compiler_item::CompilerItem;
1011
use crate::tir::{ResolvedType, TypeId, TypeTable};
1112

1213
/// Information about a builtin function
@@ -169,24 +170,20 @@ impl BuiltinRegistry {
169170
name: named.name.clone(),
170171
});
171172
}
172-
// Otherwise, check for primitive types
173+
if let Some(id) = TypeTable::primitive_by_name(&named.name) {
174+
return id;
175+
}
176+
// `i128` / `u128` are prelude struct declarations, which
177+
// `core:builtin` imports like any other module's type — these
178+
// arms are what honour that import, since the name is matched
179+
// before the import list is consulted.
173180
match named.name.as_str() {
174-
"i8" => TypeTable::I8,
175-
"i16" => TypeTable::I16,
176-
"i32" => TypeTable::I32,
177-
"i64" => TypeTable::I64,
178-
"i128" => TypeTable::I128,
179-
"u8" => TypeTable::U8,
180-
"u16" => TypeTable::U16,
181-
"u32" => TypeTable::U32,
182-
"u64" => TypeTable::U64,
183-
"u128" => TypeTable::U128,
184-
"f32" => TypeTable::F32,
185-
"f64" => TypeTable::F64,
186-
"bool" => TypeTable::BOOL,
187-
"char" => TypeTable::CHAR,
188-
"v128" => TypeTable::V128,
189-
"!" => TypeTable::NEVER,
181+
"i128" => type_table
182+
.borrow_mut()
183+
.make_compiler_struct(CompilerItem::I128),
184+
"u128" => type_table
185+
.borrow_mut()
186+
.make_compiler_struct(CompilerItem::U128),
190187
_ => TypeTable::UNIT, // Unknown type defaults to UNIT
191188
}
192189
}

wado-compiler/src/component_model.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4418,11 +4418,6 @@ pub fn type_id_to_valtype(type_id: TypeId) -> ValType {
44184418
TypeTable::U8 | TypeTable::U16 | TypeTable::U32 => ValType::I32,
44194419
TypeTable::BOOL | TypeTable::CHAR => ValType::I32,
44204420
TypeTable::I64 | TypeTable::U64 => ValType::I64,
4421-
TypeTable::I128 | TypeTable::U128 => {
4422-
// 128-bit integers are handled specially in wide arithmetic
4423-
// Default to i64 for now (caller should handle specially)
4424-
ValType::I64
4425-
}
44264421
TypeTable::F32 => ValType::F32,
44274422
TypeTable::F64 => ValType::F64,
44284423
TypeTable::UNIT | TypeTable::NEVER => {

wado-compiler/src/const_eval.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ impl Value {
179179
PrimitiveType::F32 | PrimitiveType::F64 => Some(Self::Float { value: 0.0, prim }),
180180
PrimitiveType::Bool => Some(Self::Bool(false)),
181181
PrimitiveType::Char => Some(Self::Char('\0')),
182-
PrimitiveType::I128 | PrimitiveType::U128 | PrimitiveType::V128 => None,
182+
PrimitiveType::V128 => None,
183183
}
184184
}
185185

@@ -579,8 +579,6 @@ fn trunc_to_int(value: f64, target: PrimitiveType) -> Option<u64> {
579579
| PrimitiveType::F64
580580
| PrimitiveType::Bool
581581
| PrimitiveType::Char
582-
| PrimitiveType::I128
583-
| PrimitiveType::U128
584582
| PrimitiveType::V128 => panic!("trunc_to_int: non-integer target {target:?}"),
585583
}
586584
}

wado-compiler/src/elaborator.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ mod type_resolution;
3838
mod typecheck;
3939
pub(crate) mod types;
4040
mod tysys;
41-
mod util;
41+
pub(crate) mod util;
4242
mod written;
4343

4444
use std::cell::RefCell;

wado-compiler/src/elaborator/expr.rs

Lines changed: 8 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2935,20 +2935,11 @@ impl<H: CompilerHost> Elaborator<'_, H> {
29352935
}
29362936
}
29372937

2938-
/// True when the scrutinee is an unsigned integer type (governs literal
2939-
/// parsing). Mirrors the `is_unsigned` check in `resolve_if_pattern_inner`.
29402938
fn exh_is_unsigned(&self, scrutinee_type: TypeId) -> bool {
2941-
let resolved = self.tysys.type_table.borrow().get(scrutinee_type).clone();
2942-
matches!(
2943-
resolved,
2944-
ResolvedType::Primitive(
2945-
crate::tir::PrimitiveType::U8
2946-
| crate::tir::PrimitiveType::U16
2947-
| crate::tir::PrimitiveType::U32
2948-
| crate::tir::PrimitiveType::U64
2949-
| crate::tir::PrimitiveType::U128
2950-
)
2951-
) || matches!(resolved, ResolvedType::Struct { def, .. } if self.tysys.type_table.borrow().struct_head_name(def) == "u128")
2939+
self.tysys
2940+
.type_table
2941+
.borrow()
2942+
.is_unsigned_int(scrutinee_type)
29522943
}
29532944

29542945
fn exh_literal(&self, lit: &Literal, scrutinee_type: TypeId) -> ExhPattern {
@@ -3101,50 +3092,22 @@ impl<H: CompilerHost> Elaborator<'_, H> {
31013092
) -> ExhPattern {
31023093
let is_unsigned = self.exh_is_unsigned(scrutinee_type);
31033094
let (Some(start_val), Some(end_val)) = (
3104-
self.exh_pattern_to_i128(start, is_unsigned),
3105-
self.exh_pattern_to_i128(end, is_unsigned),
3095+
util::range_endpoint_to_i128(start, is_unsigned),
3096+
util::range_endpoint_to_i128(end, is_unsigned),
31063097
) else {
31073098
// Bad bounds → old path returned `Wildcard` (catch-all).
31083099
return ExhPattern::CatchAll;
31093100
};
31103101
let inclusive = matches!(kind, ast::RangeKind::Inclusive);
31113102
// Reversed / empty ranges → old path returned `Wildcard` (catch-all).
3112-
if start_val > end_val || (!inclusive && start_val >= end_val) {
3103+
let order = util::range_endpoints_ordered(start_val, end_val, is_unsigned);
3104+
if order.is_gt() || (!inclusive && order.is_ge()) {
31133105
return ExhPattern::CatchAll;
31143106
}
31153107
let hi = if inclusive { end_val } else { end_val - 1 };
31163108
ExhPattern::Range(start_val, hi)
31173109
}
31183110

3119-
/// Resolve a range-bound AST pattern to its `i128` value. Mirrors
3120-
/// `Elaborator::pattern_to_i128`.
3121-
fn exh_pattern_to_i128(&self, pattern: &ast::Pattern, is_unsigned: bool) -> Option<i128> {
3122-
match pattern {
3123-
ast::Pattern::Literal(Literal::Number(repr)) => {
3124-
if is_unsigned {
3125-
util::parse_u128_literal(repr).ok().map(|v| v as i128)
3126-
} else {
3127-
util::parse_i128_literal(repr).ok()
3128-
}
3129-
}
3130-
ast::Pattern::Literal(Literal::Char(raw)) => {
3131-
util::unescape_char(raw).ok().map(|c| c as i128)
3132-
}
3133-
ast::Pattern::Literal(Literal::Byte(raw)) => {
3134-
util::unescape_byte(raw).ok().map(i128::from)
3135-
}
3136-
ast::Pattern::Variant {
3137-
variant_name,
3138-
variant_qualifier,
3139-
bindings,
3140-
..
3141-
} if bindings.is_empty() => {
3142-
super::stmt::primitive_assoc_const_to_i128(variant_qualifier.as_ref(), variant_name)
3143-
}
3144-
_ => None,
3145-
}
3146-
}
3147-
31483111
fn check_variant_exhaustiveness(
31493112
&self,
31503113
classified: &[(bool, ExhPattern)],

0 commit comments

Comments
 (0)