plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
a8f2482
— Peter John
2026-10-26T23:46:13+05:30
feat(codegen): Zig-style error unions: !T multi-value lowering, fail/?/!/catch/?., boxed heap slots, nullable interop
- plum-checker/src/lib.rs +43 -0
- plum-wasm-codegen/src/lib.rs +582 -11
- plum-wasm-codegen/tests/codegen_tests.rs +328 -0
plum-checker/src/lib.rs
CHANGED
|
@@ -529,8 +529,51 @@ fn recordBind(name: &str, ty: &PlumType) {
|
|
|
529
529
|
pub const TRY_WRAP_OK: usize = 1;
|
|
530
530
|
pub const TRY_WRAP_ERR: usize = 2;
|
|
531
531
|
pub const TRY_RELAYOUT: usize = 3;
|
|
532
|
+
/// Error-union coercions (expected `!T`): `+4` a plain `T` becomes the success value,
|
|
533
|
+
/// `+5` a nullable `Try` becomes `!T`; `+6` (expected a nullable `Try`) a `!T` becomes null/value.
|
|
534
|
+
pub const ERR_WRAP: usize = 4;
|
|
535
|
+
pub const ERR_FROM_NULLABLE: usize = 5;
|
|
536
|
+
pub const ERR_TO_NULLABLE: usize = 6;
|
|
537
|
+
|
|
538
|
+
fn recordErrCoercion(expr: &ast::Expr, actual: &PlumType, expected: &PlumType, ctx: &CheckCtx) -> bool {
|
|
539
|
+
let kind = match (expected, actual) {
|
|
540
|
+
(_, PlumType::TVar(_) | PlumType::TParam(_)) => return false,
|
|
541
|
+
(PlumType::TErr(inner, _), a) => {
|
|
542
|
+
if matches!(a, PlumType::TErr(..)) {
|
|
543
|
+
None
|
|
544
|
+
} else if nullableTryPayload(a).is_some() {
|
|
545
|
+
Some(ERR_FROM_NULLABLE)
|
|
546
|
+
} else if isTryType(a) {
|
|
547
|
+
None
|
|
548
|
+
} else if unify(inner, a, ctx).is_ok() {
|
|
549
|
+
Some(ERR_WRAP)
|
|
550
|
+
} else {
|
|
551
|
+
None
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
(e, PlumType::TErr(..)) if nullableTryPayload(e).is_some() => Some(ERR_TO_NULLABLE),
|
|
555
|
+
_ => return false,
|
|
556
|
+
};
|
|
557
|
+
let ptr = expr as *const ast::Expr as usize;
|
|
558
|
+
RECORD.with(|r| {
|
|
559
|
+
if let Some(m) = r.borrow_mut().as_mut() {
|
|
560
|
+
for k in [TRY_WRAP_OK, TRY_WRAP_ERR, TRY_RELAYOUT, ERR_WRAP, ERR_FROM_NULLABLE, ERR_TO_NULLABLE] {
|
|
561
|
+
m.remove(&(ptr + k));
|
|
562
|
+
}
|
|
563
|
+
if let Some(k) = kind {
|
|
564
|
+
m.insert(ptr + k, expected.clone());
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
true
|
|
569
|
+
}
|
|
532
570
|
|
|
533
571
|
fn recordTryCoercion(expr: &ast::Expr, actual: &PlumType, expected: Option<&PlumType>, ctx: &CheckCtx) {
|
|
572
|
+
if let Some(e) = expected {
|
|
573
|
+
if recordErrCoercion(expr, actual, e, ctx) {
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
534
577
|
let Some(exp @ PlumType::TApp(n, args)) = expected else {
|
|
535
578
|
return;
|
|
536
579
|
};
|
plum-wasm-codegen/src/lib.rs
CHANGED
|
@@ -584,8 +584,9 @@ fn sourceUsesCrash(dbg: &str) -> bool {
|
|
|
584
584
|
}
|
|
585
585
|
i += 1;
|
|
586
586
|
}
|
|
587
|
+
b'C' | b'B'
|
|
587
|
-
|
|
588
|
+
if (dbg[i..].starts_with("Crash(") || dbg[i..].starts_with("Bang("))
|
|
588
|
-
|
|
589
|
+
&& (i == 0 || matches!(b[i - 1], b'[' | b'(' | b' ')) =>
|
|
589
590
|
{
|
|
590
591
|
return true
|
|
591
592
|
}
|
|
@@ -595,6 +596,18 @@ fn sourceUsesCrash(dbg: &str) -> bool {
|
|
|
595
596
|
false
|
|
596
597
|
}
|
|
597
598
|
|
|
599
|
+
/// The closure literal of an error-union `a.andThen(|x| ...)` (what `a?.b` desugars to).
|
|
600
|
+
fn andThenClosure<'e>(a: &'e ast::AttributeExpr, call: &'e ast::FnCall) -> Option<&'e ast::Closure> {
|
|
601
|
+
let _ = a;
|
|
602
|
+
if call.name != "andThen" || call.args.len() != 1 {
|
|
603
|
+
return None;
|
|
604
|
+
}
|
|
605
|
+
match argExprOf(&call.args[0]) {
|
|
606
|
+
ast::Expr::Closure(cl) if cl.params.len() == 1 => Some(cl),
|
|
607
|
+
_ => None,
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
598
611
|
fn tagId(name: &str) -> i32 {
|
|
599
612
|
CURRENT_TAG_IDS.with(|c| {
|
|
600
613
|
let mut m = c.borrow_mut();
|
|
@@ -796,11 +809,59 @@ thread_local! {
|
|
|
796
809
|
static VALUE_BLOCK_TYPES: RefCell<HashMap<u32, u32>> = RefCell::new(HashMap::new());
|
|
797
810
|
/// Helper function indices: `unbox:Vec2`, `eq:Vec2`, `proj:Vec2.x`.
|
|
798
811
|
static VALUE_HELPERS: RefCell<HashMap<String, u32>> = RefCell::new(HashMap::new());
|
|
812
|
+
/// Error-union facts from the checker for the program being compiled.
|
|
813
|
+
static ERR_INFO: RefCell<ErrInfo> = RefCell::new(ErrInfo::default());
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
#[derive(Default)]
|
|
817
|
+
struct ErrInfo {
|
|
818
|
+
file: String,
|
|
819
|
+
sites: HashMap<usize, plum_checker::ErrSite>,
|
|
820
|
+
codes: std::collections::BTreeMap<String, i32>,
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
fn errSite(expr: &ast::Expr) -> Option<plum_checker::ErrSite> {
|
|
824
|
+
ERR_INFO.with(|e| e.borrow().sites.get(&(expr as *const ast::Expr as usize)).cloned())
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
fn errCode(name: &str) -> Result<i32, String> {
|
|
828
|
+
ERR_INFO
|
|
829
|
+
.with(|e| e.borrow().codes.get(name).copied())
|
|
830
|
+
.ok_or_else(|| format!("internal codegen error: no error code for '{}'", name))
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
/// Scratch local (named, reserved by `Collector`) holding an error-union operand.
|
|
834
|
+
fn errScratch(expr: &ast::Expr) -> String {
|
|
835
|
+
format!("errtmp#{}", expr as *const ast::Expr as usize)
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
fn errScratchKey(expr: &ast::Expr, kind: usize) -> String {
|
|
839
|
+
format!("errtmp#{}", expr as *const ast::Expr as usize + kind)
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
/// The wasm leaf zero for an "undefined" value slot.
|
|
843
|
+
fn emitZeroLeaf(vt: ValType, body: &mut Vec<u8>) {
|
|
844
|
+
pushSelfPlaceholder(vt, body);
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
fn emitZeros(ty: &PlumType, body: &mut Vec<u8>) {
|
|
848
|
+
for vt in withGcTypes(|g| flatValtypes(ty, g)) {
|
|
849
|
+
emitZeroLeaf(vt, body);
|
|
850
|
+
}
|
|
799
851
|
}
|
|
800
852
|
|
|
801
853
|
fn valueFieldsOf(ty: &PlumType) -> Option<Vec<(String, PlumType)>> {
|
|
802
854
|
match ty {
|
|
803
855
|
PlumType::TNamed(n) => VALUE_ENUMS.with(|v| v.borrow().get(n).cloned()),
|
|
856
|
+
// `!T` is `(T leaves..., code: i32)`; a Unit success has only the code.
|
|
857
|
+
PlumType::TErr(t, _) => {
|
|
858
|
+
let mut f = Vec::new();
|
|
859
|
+
if !matches!(**t, PlumType::TUnit) {
|
|
860
|
+
f.push(("$value".to_string(), (**t).clone()));
|
|
861
|
+
}
|
|
862
|
+
f.push(("$code".to_string(), PlumType::TBool));
|
|
863
|
+
Some(f)
|
|
864
|
+
}
|
|
804
865
|
_ => None,
|
|
805
866
|
}
|
|
806
867
|
}
|
|
@@ -872,10 +933,17 @@ fn valueHelper(key: &str) -> u32 {
|
|
|
872
933
|
VALUE_HELPERS.with(|h| *h.borrow().get(key).unwrap_or_else(|| panic!("internal codegen error: missing helper {key}")))
|
|
873
934
|
}
|
|
874
935
|
|
|
936
|
+
fn unboxKey(ty: &PlumType) -> String {
|
|
937
|
+
match ty {
|
|
938
|
+
PlumType::TNamed(n) => format!("unbox:{n}"),
|
|
939
|
+
PlumType::TErr(t, _) => format!("unbox:!{t}"),
|
|
940
|
+
_ => unreachable!("emitUnbox on non-value type"),
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
|
|
875
944
|
/// A heap box reference on the stack -> flat leaves.
|
|
876
945
|
fn emitUnbox(ty: &PlumType, body: &mut Vec<u8>) {
|
|
877
|
-
let PlumType::TNamed(n) = ty else { unreachable!("emitUnbox on non-value type") };
|
|
878
|
-
Instruction::Call(valueHelper(&
|
|
946
|
+
Instruction::Call(valueHelper(&unboxKey(ty))).encode(body);
|
|
879
947
|
}
|
|
880
948
|
|
|
881
949
|
/// If the value just produced for a heap slot of type `ty` is a value enum, flatten it.
|
|
@@ -949,6 +1017,8 @@ pub struct GcTypeRegistry {
|
|
|
949
1017
|
pub tag_ref_idx: u32,
|
|
950
1018
|
/// Helper `(arr: array<i8>, len: i64) -> Str` building `Str{Buffer{arr,len}}`; `None` when the program has no `Str`/`Buffer`.
|
|
951
1019
|
pub mk_str_fn: Option<u32>,
|
|
1020
|
+
/// `!T` (keyed by `T`) -> its heap box struct `{leaves of T..., code: i32}`.
|
|
1021
|
+
pub err_box: HashMap<PlumType, u32>,
|
|
952
1022
|
}
|
|
953
1023
|
|
|
954
1024
|
impl GcTypeRegistry {
|
|
@@ -969,6 +1039,7 @@ impl GcTypeRegistry {
|
|
|
969
1039
|
match ty {
|
|
970
1040
|
PlumType::TNamed(n) => self.class_type_idx.get(n).copied(),
|
|
971
1041
|
PlumType::TApp(..) => self.app_class.get(ty).copied(),
|
|
1042
|
+
PlumType::TErr(t, _) => self.err_box.get(&**t).copied(),
|
|
972
1043
|
_ => None,
|
|
973
1044
|
}
|
|
974
1045
|
}
|
|
@@ -1024,7 +1095,10 @@ fn plumTypeToGcValtype(t: &PlumType, registry: &GcTypeRegistry) -> ValType {
|
|
|
1024
1095
|
},
|
|
1025
1096
|
PlumType::TTags(_) => gcRef(registry.tag_type_idx),
|
|
1026
1097
|
// Lowered by the error-union codegen (E3); until then, as its success type.
|
|
1027
|
-
PlumType::TErr(inner, _) =>
|
|
1098
|
+
PlumType::TErr(inner, _) => match registry.err_box.get(&**inner) {
|
|
1099
|
+
Some(idx) => gcRef(*idx),
|
|
1100
|
+
None => ValType::Ref(RefType::ANYREF),
|
|
1101
|
+
},
|
|
1028
1102
|
PlumType::TFun(_, _) | PlumType::TVariadic(_) => ValType::Ref(RefType::ANYREF),
|
|
1029
1103
|
PlumType::TParam(_) => ValType::Ref(RefType::ANYREF),
|
|
1030
1104
|
PlumType::TVar(_) | PlumType::TUnit => ValType::I64,
|
|
@@ -1063,6 +1137,7 @@ fn buildGcTypeRegistry(
|
|
|
1063
1137
|
enum_variants: &EnumVariants,
|
|
1064
1138
|
enum_params: &EnumParams,
|
|
1065
1139
|
apps: &[PlumType],
|
|
1140
|
+
errs: &[PlumType],
|
|
1066
1141
|
) -> GcTypeRegistry {
|
|
1067
1142
|
enum Slot {
|
|
1068
1143
|
Str,
|
|
@@ -1074,6 +1149,7 @@ fn buildGcTypeRegistry(
|
|
|
1074
1149
|
TagF64,
|
|
1075
1150
|
TagI32,
|
|
1076
1151
|
TagRef,
|
|
1152
|
+
ErrBox(PlumType),
|
|
1077
1153
|
}
|
|
1078
1154
|
let mut slots: Vec<Slot> = vec![Slot::Str];
|
|
1079
1155
|
let mut reg = GcTypeRegistry {
|
|
@@ -1095,6 +1171,7 @@ fn buildGcTypeRegistry(
|
|
|
1095
1171
|
tag_i32_idx: 0,
|
|
1096
1172
|
tag_ref_idx: 0,
|
|
1097
1173
|
mk_str_fn: None,
|
|
1174
|
+
err_box: HashMap::new(),
|
|
1098
1175
|
};
|
|
1099
1176
|
let mut enum_decls: HashMap<String, &ast::Enum> = HashMap::new();
|
|
1100
1177
|
let mut instances: Vec<PlumType> = Vec::new();
|
|
@@ -1164,6 +1241,13 @@ fn buildGcTypeRegistry(
|
|
|
1164
1241
|
reg.tag_ref_idx = slots.len() as u32;
|
|
1165
1242
|
slots.push(Slot::TagRef);
|
|
1166
1243
|
|
|
1244
|
+
for t in errs {
|
|
1245
|
+
if !reg.err_box.contains_key(t) {
|
|
1246
|
+
reg.err_box.insert(t.clone(), slots.len() as u32);
|
|
1247
|
+
slots.push(Slot::ErrBox(t.clone()));
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1167
1251
|
// Native arrays: one wasm array type per distinct element representation,
|
|
1168
1252
|
// innermost (least nested) element types first.
|
|
1169
1253
|
arrays.sort_by_key(arrayDepth);
|
|
@@ -1259,6 +1343,23 @@ fn buildGcTypeRegistry(
|
|
|
1259
1343
|
},
|
|
1260
1344
|
}
|
|
1261
1345
|
}
|
|
1346
|
+
Slot::ErrBox(t) => {
|
|
1347
|
+
let ty = PlumType::TErr(Box::new(t.clone()), plum_checker::types::ErrSet::Inferred(String::new()));
|
|
1348
|
+
SubType {
|
|
1349
|
+
is_final: true,
|
|
1350
|
+
supertype_idx: None,
|
|
1351
|
+
composite_type: CompositeType {
|
|
1352
|
+
inner: CompositeInnerType::Struct(StructType {
|
|
1353
|
+
fields: flatValtypes(&ty, ®)
|
|
1354
|
+
.into_iter()
|
|
1355
|
+
.map(|vt| FieldType { element_type: StorageType::Val(vt), mutable: true })
|
|
1356
|
+
.collect::<Vec<_>>()
|
|
1357
|
+
.into(),
|
|
1358
|
+
}),
|
|
1359
|
+
shared: false,
|
|
1360
|
+
},
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1262
1363
|
Slot::Tag => SubType {
|
|
1263
1364
|
is_final: false,
|
|
1264
1365
|
supertype_idx: None,
|
|
@@ -1763,6 +1864,7 @@ struct TypeCollector<'a> {
|
|
|
1763
1864
|
funs_seen: HashSet<PlumType>,
|
|
1764
1865
|
variadics: Vec<PlumType>,
|
|
1765
1866
|
variadics_seen: HashSet<PlumType>,
|
|
1867
|
+
errs: Vec<PlumType>,
|
|
1766
1868
|
enum_variants: &'a EnumVariants,
|
|
1767
1869
|
enum_params: &'a EnumParams,
|
|
1768
1870
|
eg: &'a EnumGenerics,
|
|
@@ -1822,6 +1924,12 @@ impl<'a> TypeCollector<'a> {
|
|
|
1822
1924
|
self.visit(v);
|
|
1823
1925
|
}
|
|
1824
1926
|
}
|
|
1927
|
+
PlumType::TErr(inner, _) => {
|
|
1928
|
+
self.visit(inner);
|
|
1929
|
+
if !self.errs.contains(inner) {
|
|
1930
|
+
self.errs.push((**inner).clone());
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1825
1933
|
_ => {}
|
|
1826
1934
|
}
|
|
1827
1935
|
}
|
|
@@ -1847,8 +1955,11 @@ fn compileSourceInner(
|
|
|
1847
1955
|
call_inst,
|
|
1848
1956
|
op_calls,
|
|
1849
1957
|
lowered,
|
|
1958
|
+
err_sites,
|
|
1959
|
+
error_codes,
|
|
1850
1960
|
..
|
|
1851
1961
|
} = typed;
|
|
1962
|
+
ERR_INFO.with(|e| *e.borrow_mut() = ErrInfo { file: source.module.as_ref().map(|m| m.name.clone()).unwrap_or_else(|| "main.plum".to_string()), sites: err_sites, codes: error_codes });
|
|
1852
1963
|
// The types are keyed by the addresses of the checker's lowered program.
|
|
1853
1964
|
let source: &ast::Source = lowered.as_deref().unwrap_or(source);
|
|
1854
1965
|
// Per-instantiation expression types, split out by instantiation id.
|
|
@@ -1939,6 +2050,7 @@ fn compileSourceInner(
|
|
|
1939
2050
|
funs_seen: HashSet::new(),
|
|
1940
2051
|
variadics: Vec::new(),
|
|
1941
2052
|
variadics_seen: HashSet::new(),
|
|
2053
|
+
errs: Vec::new(),
|
|
1942
2054
|
enum_variants: &enum_variants,
|
|
1943
2055
|
enum_params: &enum_params,
|
|
1944
2056
|
eg: &eg,
|
|
@@ -2041,7 +2153,7 @@ fn compileSourceInner(
|
|
|
2041
2153
|
VALUE_BLOCK_TYPES.with(|m| m.borrow_mut().clear());
|
|
2042
2154
|
VALUE_HELPERS.with(|m| m.borrow_mut().clear());
|
|
2043
2155
|
|
|
2044
|
-
let mut gc_types = buildGcTypeRegistry(&mut module, source, &enum_variants, &enum_params, &tc.apps);
|
|
2156
|
+
let mut gc_types = buildGcTypeRegistry(&mut module, source, &enum_variants, &enum_params, &tc.apps, &tc.errs);
|
|
2045
2157
|
CURRENT_GC_TYPES.with(|c| *c.borrow_mut() = Some(gc_types.clone()));
|
|
2046
2158
|
let value_names: Vec<String> = {
|
|
2047
2159
|
let mut v: Vec<String> = VALUE_ENUMS.with(|v| v.borrow().keys().cloned().collect());
|
|
@@ -2055,6 +2167,14 @@ fn compileSourceInner(
|
|
|
2055
2167
|
let bt = module.addType(&[], &leaves);
|
|
2056
2168
|
VALUE_BLOCK_TYPES.with(|m| m.borrow_mut().insert(box_idx, bt));
|
|
2057
2169
|
}
|
|
2170
|
+
let mut err_boxes: Vec<(PlumType, u32)> = gc_types.err_box.iter().map(|(t, i)| (t.clone(), *i)).collect();
|
|
2171
|
+
err_boxes.sort_by_key(|(_, i)| *i);
|
|
2172
|
+
for (t, box_idx) in &err_boxes {
|
|
2173
|
+
let ty = PlumType::TErr(Box::new(t.clone()), plum_checker::types::ErrSet::Inferred(String::new()));
|
|
2174
|
+
let leaves = flatValtypes(&ty, &gc_types);
|
|
2175
|
+
let bt = module.addType(&[], &leaves);
|
|
2176
|
+
VALUE_BLOCK_TYPES.with(|m| m.borrow_mut().insert(*box_idx, bt));
|
|
2177
|
+
}
|
|
2058
2178
|
|
|
2059
2179
|
CURRENT_CONSTS.with(|c| {
|
|
2060
2180
|
let mut consts = c.borrow_mut();
|
|
@@ -2105,6 +2225,9 @@ fn compileSourceInner(
|
|
|
2105
2225
|
continue;
|
|
2106
2226
|
}
|
|
2107
2227
|
known_fns.insert(f.name.clone());
|
|
2228
|
+
if matches!(f.returns.as_ref().map(plum_checker::plumTypeFromAstG), Some(PlumType::TErr(..))) {
|
|
2229
|
+
return Err(format!("extern fun '{}' cannot return an error union", f.name));
|
|
2230
|
+
}
|
|
2108
2231
|
let sig = unitFuncSig(f, &Subst::new(), &eg, &gc_types);
|
|
2109
2232
|
let param_types = sig.params.clone();
|
|
2110
2233
|
let ret = sig.ret.clone();
|
|
@@ -2250,6 +2373,21 @@ fn compileSourceInner(
|
|
|
2250
2373
|
}
|
|
2251
2374
|
}
|
|
2252
2375
|
|
|
2376
|
+
for (t, box_idx) in &err_boxes {
|
|
2377
|
+
let ty = PlumType::TErr(Box::new(t.clone()), plum_checker::types::ErrSet::Inferred(String::new()));
|
|
2378
|
+
let leaves = flatValtypes(&ty, &gc_types);
|
|
2379
|
+
let mut b = vec![0u8];
|
|
2380
|
+
for i in 0..leaves.len() as u32 {
|
|
2381
|
+
Instruction::LocalGet(0).encode(&mut b);
|
|
2382
|
+
Instruction::StructGet { struct_type_index: *box_idx, field_index: i }.encode(&mut b);
|
|
2383
|
+
}
|
|
2384
|
+
Instruction::End.encode(&mut b);
|
|
2385
|
+
let ft = module.addType(&[gcRef(*box_idx)], &leaves);
|
|
2386
|
+
let f = module.addFunction(ft, &b);
|
|
2387
|
+
module.nameFunction(f, &format!("unbox.!{t}"));
|
|
2388
|
+
VALUE_HELPERS.with(|h| h.borrow_mut().insert(unboxKey(&ty), f));
|
|
2389
|
+
}
|
|
2390
|
+
|
|
2253
2391
|
// Pre-allocate one instance of every payload-free variant of every enum instance.
|
|
2254
2392
|
let mut singleton_globals: HashMap<(PlumType, String), u32> = HashMap::new();
|
|
2255
2393
|
let mut start_body = Vec::new();
|
|
@@ -3450,7 +3588,15 @@ impl<'a, 't> ClosureWalker<'a, 't> {
|
|
|
3450
3588
|
|
|
3451
3589
|
fn walkExpr(&mut self, expr: &'a ast::Expr) {
|
|
3452
3590
|
match expr {
|
|
3453
|
-
ast::Expr::Yield(_) | ast::Expr::
|
|
3591
|
+
ast::Expr::Yield(_) | ast::Expr::Fail(_) => {}
|
|
3592
|
+
ast::Expr::Bang(b) => self.walkExpr(&b.value),
|
|
3593
|
+
ast::Expr::Catch(c) => {
|
|
3594
|
+
self.walkExpr(&c.value);
|
|
3595
|
+
match &c.handler {
|
|
3596
|
+
ast::CatchHandler::Default(d) => self.walkExpr(d),
|
|
3597
|
+
ast::CatchHandler::Block { body, .. } => self.walkBlock(body),
|
|
3598
|
+
}
|
|
3599
|
+
}
|
|
3454
3600
|
ast::Expr::Closure(cl) => self.recordClosure(expr, cl),
|
|
3455
3601
|
ast::Expr::Binary(b) => {
|
|
3456
3602
|
self.walkExpr(&b.left);
|
|
@@ -3733,6 +3879,19 @@ fn fvCollectRefsExpr(
|
|
|
3733
3879
|
}
|
|
3734
3880
|
ast::Expr::Not(inner) => fvCollectRefsExpr(inner, bound, seen, free, env, fn_decls, fn_locals),
|
|
3735
3881
|
ast::Expr::Try(inner) => fvCollectRefsExpr(inner, bound, seen, free, env, fn_decls, fn_locals),
|
|
3882
|
+
ast::Expr::Bang(b) => fvCollectRefsExpr(&b.value, bound, seen, free, env, fn_decls, fn_locals),
|
|
3883
|
+
ast::Expr::Catch(c) => {
|
|
3884
|
+
fvCollectRefsExpr(&c.value, bound, seen, free, env, fn_decls, fn_locals);
|
|
3885
|
+
match &c.handler {
|
|
3886
|
+
ast::CatchHandler::Default(d) => fvCollectRefsExpr(d, bound, seen, free, env, fn_decls, fn_locals),
|
|
3887
|
+
ast::CatchHandler::Block { binder, body } => {
|
|
3888
|
+
let mut inner_bound = bound.clone();
|
|
3889
|
+
inner_bound.insert(binder.clone());
|
|
3890
|
+
fvCollectBoundBlock(body, &mut inner_bound);
|
|
3891
|
+
fvCollectRefsBlock(body, &inner_bound, seen, free, env, fn_decls, fn_locals);
|
|
3892
|
+
}
|
|
3893
|
+
}
|
|
3894
|
+
}
|
|
3736
3895
|
ast::Expr::Unary(u) => fvCollectRefsExpr(&u.operand, bound, seen, free, env, fn_decls, fn_locals),
|
|
3737
3896
|
ast::Expr::Paren(inner) => fvCollectRefsExpr(inner, bound, seen, free, env, fn_decls, fn_locals),
|
|
3738
3897
|
ast::Expr::Ternary(t) => {
|
|
@@ -3991,8 +4150,42 @@ impl<'a> Collector<'a> {
|
|
|
3991
4150
|
self.nested_class_scratch_types.push((from, String::new()));
|
|
3992
4151
|
}
|
|
3993
4152
|
}
|
|
4153
|
+
for k in [plum_checker::ERR_FROM_NULLABLE, plum_checker::ERR_TO_NULLABLE] {
|
|
4154
|
+
if let Some(exp) = self.types.get(&(ptr + k)) {
|
|
4155
|
+
let ty = if k == plum_checker::ERR_FROM_NULLABLE { self.ty(expr) } else { self.ty(expr) };
|
|
4156
|
+
let _ = exp;
|
|
4157
|
+
let slot_ty = if k == plum_checker::ERR_FROM_NULLABLE {
|
|
4158
|
+
// the nullable ref is parked in a ref local
|
|
4159
|
+
match plum_checker::nullableTryPayload(&ty) {
|
|
4160
|
+
Some(p) => p.clone(),
|
|
4161
|
+
None => ty,
|
|
4162
|
+
}
|
|
4163
|
+
} else {
|
|
4164
|
+
ty
|
|
4165
|
+
};
|
|
4166
|
+
self.bind(&errScratchKey(expr, k), slot_ty);
|
|
4167
|
+
}
|
|
4168
|
+
}
|
|
3994
4169
|
match expr {
|
|
3995
|
-
ast::Expr::Yield(_) | ast::Expr::
|
|
4170
|
+
ast::Expr::Yield(_) | ast::Expr::Fail(_) => {}
|
|
4171
|
+
ast::Expr::Bang(b) => {
|
|
4172
|
+
let ty = self.ty(&b.value);
|
|
4173
|
+
self.bind(&errScratch(expr), ty);
|
|
4174
|
+
self.walkExpr(&b.value);
|
|
4175
|
+
}
|
|
4176
|
+
ast::Expr::Catch(c) => {
|
|
4177
|
+
let ty = self.ty(&c.value);
|
|
4178
|
+
self.bind(&errScratch(expr), ty);
|
|
4179
|
+
self.walkExpr(&c.value);
|
|
4180
|
+
match &c.handler {
|
|
4181
|
+
ast::CatchHandler::Default(d) => self.walkExpr(d),
|
|
4182
|
+
ast::CatchHandler::Block { binder, body } => {
|
|
4183
|
+
let names = errSite(expr).map(|s| s.names).unwrap_or_default();
|
|
4184
|
+
self.bind(binder, PlumType::TTags(names.into_iter().map(|n| (n, None)).collect()));
|
|
4185
|
+
self.walkBlock(body);
|
|
4186
|
+
}
|
|
4187
|
+
}
|
|
4188
|
+
}
|
|
3996
4189
|
ast::Expr::ClassCall(call) => {
|
|
3997
4190
|
for fa in &call.fields {
|
|
3998
4191
|
self.walkExpr(&fa.value);
|
|
@@ -4024,6 +4217,9 @@ impl<'a> Collector<'a> {
|
|
|
4024
4217
|
ast::Expr::Try(inner) => {
|
|
4025
4218
|
let key = expr as *const ast::Expr as usize;
|
|
4026
4219
|
let ty = self.ty(inner);
|
|
4220
|
+
if errSite(expr).is_some() {
|
|
4221
|
+
self.bind(&errScratch(expr), ty.clone());
|
|
4222
|
+
}
|
|
4027
4223
|
let slot = self.next_nested_class_slot;
|
|
4028
4224
|
self.next_nested_class_slot += 1;
|
|
4029
4225
|
self.nested_class_scratch.insert(key, slot);
|
|
@@ -4059,6 +4255,15 @@ impl<'a> Collector<'a> {
|
|
|
4059
4255
|
ast::Expr::Attribute(a) => {
|
|
4060
4256
|
self.walkExpr(&a.object);
|
|
4061
4257
|
if let ast::AttrKind::Method(call) = &a.attr {
|
|
4258
|
+
if matches!(self.ty(&a.object), PlumType::TTags(_)) && call.name == "name" {
|
|
4259
|
+
self.bind(&errScratch(expr), PlumType::TBool);
|
|
4260
|
+
}
|
|
4261
|
+
// `a?.b` on an error union is inlined (no closure is built).
|
|
4262
|
+
if let (PlumType::TErr(inner, _), Some(cl)) = (self.ty(&a.object), andThenClosure(a, call)) {
|
|
4263
|
+
self.bind(&errScratch(expr), self.ty(&a.object));
|
|
4264
|
+
self.bind(&cl.params[0], (*inner).clone());
|
|
4265
|
+
self.walkBlock(&cl.body);
|
|
4266
|
+
}
|
|
4062
4267
|
// A call through an fn-typed field needs a local to hold the
|
|
4063
4268
|
// closure (read twice: env, then table index). Reserved even if
|
|
4064
4269
|
// a same-named method wins at compile time (then left unused).
|
|
@@ -4283,6 +4488,14 @@ fn compileFnBodyImpl(
|
|
|
4283
4488
|
ast::FnBody::Expr(e) => {
|
|
4284
4489
|
compileExpr(e, &mut body, &local_ctx, state)?;
|
|
4285
4490
|
}
|
|
4491
|
+
ast::FnBody::Block(block)
|
|
4492
|
+
if matches!(&ret_decl, PlumType::TErr(t, _) if matches!(**t, PlumType::TUnit))
|
|
4493
|
+
&& !matches!(block.stmts.last(),
|
|
4494
|
+
Some(ast::Stmt::Expr(e, ..)) if matches!(inferLocalType(e, &local_ctx), PlumType::TErr(..))) =>
|
|
4495
|
+
{
|
|
4496
|
+
compileBlock(block, &mut body, &local_ctx, state)?;
|
|
4497
|
+
Instruction::I32Const(0).encode(&mut body);
|
|
4498
|
+
}
|
|
4286
4499
|
ast::FnBody::Block(block) => {
|
|
4287
4500
|
compileBlockAsFnBody(block, &mut body, &local_ctx, state, result_vt)?;
|
|
4288
4501
|
}
|
|
@@ -4678,6 +4891,9 @@ fn compileStmt(
|
|
|
4678
4891
|
Instruction::Return.encode(body);
|
|
4679
4892
|
}
|
|
4680
4893
|
ast::Stmt::Return(None) => {
|
|
4894
|
+
if matches!(&ctx.ret_type, Some(PlumType::TErr(t, _)) if matches!(**t, PlumType::TUnit)) {
|
|
4895
|
+
Instruction::I32Const(0).encode(body);
|
|
4896
|
+
}
|
|
4681
4897
|
Instruction::Return.encode(body);
|
|
4682
4898
|
}
|
|
4683
4899
|
ast::Stmt::If(if_) => {
|
|
@@ -4751,6 +4967,11 @@ fn compileStmt(
|
|
|
4751
4967
|
/// Returns true if the expression leaves a value on the wasm stack.
|
|
4752
4968
|
fn exprHasResult(expr: &ast::Expr, ctx: &LocalCtx) -> bool {
|
|
4753
4969
|
match expr {
|
|
4970
|
+
ast::Expr::Try(_) | ast::Expr::Bang(_) | ast::Expr::Catch(_)
|
|
4971
|
+
if matches!(inferLocalType(expr, ctx), PlumType::TUnit) =>
|
|
4972
|
+
{
|
|
4973
|
+
false
|
|
4974
|
+
}
|
|
4754
4975
|
ast::Expr::FnCall(call) => {
|
|
4755
4976
|
if ctx.locals.contains_key(&call.name)
|
|
4756
4977
|
&& matches!(
|
|
@@ -6126,6 +6347,44 @@ fn compileExpr(
|
|
|
6126
6347
|
if SKIP_COERCION.with(|c| c.replace(0)) == ptr {
|
|
6127
6348
|
return compileExprCore(expr, body, ctx, state);
|
|
6128
6349
|
}
|
|
6350
|
+
if ctx.types.contains_key(&(ptr + plum_checker::ERR_WRAP)) {
|
|
6351
|
+
compileExprCore(expr, body, ctx, state)?;
|
|
6352
|
+
Instruction::I32Const(0).encode(body);
|
|
6353
|
+
return Ok(());
|
|
6354
|
+
}
|
|
6355
|
+
if ctx.types.contains_key(&(ptr + plum_checker::ERR_FROM_NULLABLE)) {
|
|
6356
|
+
let scratch = *ctx
|
|
6357
|
+
.locals
|
|
6358
|
+
.get(&errScratchKey(expr, plum_checker::ERR_FROM_NULLABLE))
|
|
6359
|
+
.ok_or_else(|| "internal codegen error: missing nullable->!T scratch".to_string())?;
|
|
6360
|
+
compileExprCore(expr, body, ctx, state)?;
|
|
6361
|
+
Instruction::LocalSet(scratch).encode(body);
|
|
6362
|
+
Instruction::LocalGet(scratch).encode(body);
|
|
6363
|
+
Instruction::LocalGet(scratch).encode(body);
|
|
6364
|
+
Instruction::RefIsNull.encode(body);
|
|
6365
|
+
Instruction::If(BlockType::Result(ValType::I32)).encode(body);
|
|
6366
|
+
Instruction::I32Const(errCode("Missing")?).encode(body);
|
|
6367
|
+
Instruction::Else.encode(body);
|
|
6368
|
+
Instruction::I32Const(0).encode(body);
|
|
6369
|
+
Instruction::End.encode(body);
|
|
6370
|
+
return Ok(());
|
|
6371
|
+
}
|
|
6372
|
+
if let Some(to) = ctx.types.get(&(ptr + plum_checker::ERR_TO_NULLABLE)) {
|
|
6373
|
+
let scratch = *ctx
|
|
6374
|
+
.locals
|
|
6375
|
+
.get(&errScratchKey(expr, plum_checker::ERR_TO_NULLABLE))
|
|
6376
|
+
.ok_or_else(|| "internal codegen error: missing !T->nullable scratch".to_string())?;
|
|
6377
|
+
let ty = inferLocalType(expr, ctx);
|
|
6378
|
+
compileExprCore(expr, body, ctx, state)?;
|
|
6379
|
+
emitSetLocals(scratch, &ty, body);
|
|
6380
|
+
Instruction::LocalGet(scratch + leafCount(&ty) - 1).encode(body);
|
|
6381
|
+
Instruction::If(BlockType::Result(plumTypeToValtype(to))).encode(body);
|
|
6382
|
+
Instruction::RefNull(nullableHeapType(to)).encode(body);
|
|
6383
|
+
Instruction::Else.encode(body);
|
|
6384
|
+
Instruction::LocalGet(scratch).encode(body);
|
|
6385
|
+
Instruction::End.encode(body);
|
|
6386
|
+
return Ok(());
|
|
6387
|
+
}
|
|
6129
6388
|
if let Some(ety) = ctx.types.get(&(ptr + plum_checker::TRY_WRAP_OK)) {
|
|
6130
6389
|
return compileTryWrap(expr, ety, true, body, ctx, state);
|
|
6131
6390
|
}
|
|
@@ -6199,6 +6458,296 @@ fn emitMissingReturn(body: &mut Vec<u8>, ctx: &LocalCtx) -> Result<(), String> {
|
|
|
6199
6458
|
Ok(())
|
|
6200
6459
|
}
|
|
6201
6460
|
|
|
6461
|
+
/// A compiled error-union operand parked in its scratch local: `!T` as
|
|
6462
|
+
/// `(T leaves..., code)`, or a nullable `Try` as its (nullable) reference.
|
|
6463
|
+
struct ErrOperand {
|
|
6464
|
+
base: u32,
|
|
6465
|
+
ty: PlumType,
|
|
6466
|
+
success: PlumType,
|
|
6467
|
+
nullable: bool,
|
|
6468
|
+
}
|
|
6469
|
+
|
|
6470
|
+
impl ErrOperand {
|
|
6471
|
+
fn pushIsErr(&self, body: &mut Vec<u8>) {
|
|
6472
|
+
if self.nullable {
|
|
6473
|
+
Instruction::LocalGet(self.base).encode(body);
|
|
6474
|
+
Instruction::RefIsNull.encode(body);
|
|
6475
|
+
} else {
|
|
6476
|
+
self.pushCode(body).unwrap();
|
|
6477
|
+
Instruction::I32Const(0).encode(body);
|
|
6478
|
+
Instruction::I32Ne.encode(body);
|
|
6479
|
+
}
|
|
6480
|
+
}
|
|
6481
|
+
fn pushCode(&self, body: &mut Vec<u8>) -> Result<(), String> {
|
|
6482
|
+
if self.nullable {
|
|
6483
|
+
Instruction::I32Const(errCode("Missing")?).encode(body);
|
|
6484
|
+
} else {
|
|
6485
|
+
Instruction::LocalGet(self.base + leafCount(&self.ty) - 1).encode(body);
|
|
6486
|
+
}
|
|
6487
|
+
Ok(())
|
|
6488
|
+
}
|
|
6489
|
+
fn pushValue(&self, body: &mut Vec<u8>) {
|
|
6490
|
+
if self.nullable {
|
|
6491
|
+
Instruction::LocalGet(self.base).encode(body);
|
|
6492
|
+
} else {
|
|
6493
|
+
for i in 0..leafCount(&self.ty) - 1 {
|
|
6494
|
+
Instruction::LocalGet(self.base + i).encode(body);
|
|
6495
|
+
}
|
|
6496
|
+
}
|
|
6497
|
+
}
|
|
6498
|
+
}
|
|
6499
|
+
|
|
6500
|
+
/// Compiles `operand` (an error union) into `site_expr`'s scratch local.
|
|
6501
|
+
fn errOperand(
|
|
6502
|
+
site_expr: &ast::Expr,
|
|
6503
|
+
operand: &ast::Expr,
|
|
6504
|
+
body: &mut Vec<u8>,
|
|
6505
|
+
ctx: &LocalCtx,
|
|
6506
|
+
state: &mut ModuleState,
|
|
6507
|
+
) -> Result<ErrOperand, String> {
|
|
6508
|
+
let ty = inferLocalType(operand, ctx);
|
|
6509
|
+
let base = *ctx
|
|
6510
|
+
.locals
|
|
6511
|
+
.get(&errScratch(site_expr))
|
|
6512
|
+
.ok_or_else(|| "internal codegen error: missing error-union scratch local".to_string())?;
|
|
6513
|
+
compileExpr(operand, body, ctx, state)?;
|
|
6514
|
+
if let PlumType::TErr(t, _) = &ty {
|
|
6515
|
+
emitSetLocals(base, &ty, body);
|
|
6516
|
+
let success = (**t).clone();
|
|
6517
|
+
return Ok(ErrOperand { base, ty, success, nullable: false });
|
|
6518
|
+
}
|
|
6519
|
+
if let Some(p) = plum_checker::nullableTryPayload(&ty) {
|
|
6520
|
+
Instruction::LocalSet(base).encode(body);
|
|
6521
|
+
let success = p.clone();
|
|
6522
|
+
return Ok(ErrOperand { base, ty, success, nullable: true });
|
|
6523
|
+
}
|
|
6524
|
+
Err(format!("codegen: '?', '!' and 'catch' on {} are not supported", ty))
|
|
6525
|
+
}
|
|
6526
|
+
|
|
6527
|
+
fn resultBlock(success: &PlumType) -> BlockType {
|
|
6528
|
+
if matches!(success, PlumType::TUnit) {
|
|
6529
|
+
BlockType::Empty
|
|
6530
|
+
} else {
|
|
6531
|
+
blockTypeFor(Some(plumTypeToValtype(success)))
|
|
6532
|
+
}
|
|
6533
|
+
}
|
|
6534
|
+
|
|
6535
|
+
/// `return (zeros, code)` for an enclosing `!T` fn, or null for a nullable-`Try` fn.
|
|
6536
|
+
fn emitErrReturn(
|
|
6537
|
+
push_code: &dyn Fn(&mut Vec<u8>) -> Result<(), String>,
|
|
6538
|
+
body: &mut Vec<u8>,
|
|
6539
|
+
ctx: &LocalCtx,
|
|
6540
|
+
) -> Result<(), String> {
|
|
6541
|
+
let rt = ctx
|
|
6542
|
+
.ret_type
|
|
6543
|
+
.as_ref()
|
|
6544
|
+
.ok_or_else(|| "codegen: `fail`/`?` outside a function returning an error union".to_string())?;
|
|
6545
|
+
match rt {
|
|
6546
|
+
PlumType::TErr(t, _) => {
|
|
6547
|
+
if !matches!(**t, PlumType::TUnit) {
|
|
6548
|
+
emitZeros(t, body);
|
|
6549
|
+
}
|
|
6550
|
+
push_code(body)?;
|
|
6551
|
+
}
|
|
6552
|
+
_ if plum_checker::nullableTryPayload(rt).is_some() => {
|
|
6553
|
+
Instruction::RefNull(nullableHeapType(rt)).encode(body);
|
|
6554
|
+
}
|
|
6555
|
+
_ => return Err(format!("codegen: `fail`/`?` in a function returning {}", rt)),
|
|
6556
|
+
}
|
|
6557
|
+
Instruction::Return.encode(body);
|
|
6558
|
+
Ok(())
|
|
6559
|
+
}
|
|
6560
|
+
|
|
6561
|
+
fn calleeText(e: &ast::Expr) -> String {
|
|
6562
|
+
match e {
|
|
6563
|
+
ast::Expr::FnCall(c) => c.name.clone(),
|
|
6564
|
+
ast::Expr::Attribute(a) => match &a.attr {
|
|
6565
|
+
ast::AttrKind::Method(c) => c.name.clone(),
|
|
6566
|
+
ast::AttrKind::Field(f) => f.clone(),
|
|
6567
|
+
},
|
|
6568
|
+
ast::Expr::Var(n) => n.clone(),
|
|
6569
|
+
ast::Expr::Paren(i) => calleeText(i),
|
|
6570
|
+
_ => "value".to_string(),
|
|
6571
|
+
}
|
|
6572
|
+
}
|
|
6573
|
+
|
|
6574
|
+
fn compileErrTry(
|
|
6575
|
+
expr: &ast::Expr,
|
|
6576
|
+
inner: &ast::Expr,
|
|
6577
|
+
body: &mut Vec<u8>,
|
|
6578
|
+
ctx: &LocalCtx,
|
|
6579
|
+
state: &mut ModuleState,
|
|
6580
|
+
) -> Result<(), String> {
|
|
6581
|
+
let op = errOperand(expr, inner, body, ctx, state)?;
|
|
6582
|
+
op.pushIsErr(body);
|
|
6583
|
+
Instruction::If(BlockType::Empty).encode(body);
|
|
6584
|
+
emitErrReturn(&|b| op.pushCode(b), body, ctx)?;
|
|
6585
|
+
Instruction::End.encode(body);
|
|
6586
|
+
op.pushValue(body);
|
|
6587
|
+
Ok(())
|
|
6588
|
+
}
|
|
6589
|
+
|
|
6590
|
+
fn compileErrBang(
|
|
6591
|
+
expr: &ast::Expr,
|
|
6592
|
+
b: &ast::BangExpr,
|
|
6593
|
+
body: &mut Vec<u8>,
|
|
6594
|
+
ctx: &LocalCtx,
|
|
6595
|
+
state: &mut ModuleState,
|
|
6596
|
+
) -> Result<(), String> {
|
|
6597
|
+
let op = errOperand(expr, &b.value, body, ctx, state)?;
|
|
6598
|
+
let crash_idx = *ctx
|
|
6599
|
+
.func_ids
|
|
6600
|
+
.get(CRASH_FN_KEY)
|
|
6601
|
+
.ok_or_else(|| "internal codegen error: crash import missing".to_string())?;
|
|
6602
|
+
let callee = calleeText(&b.value);
|
|
6603
|
+
let file = ERR_INFO.with(|e| e.borrow().file.clone());
|
|
6604
|
+
let names = errSite(expr).map(|s| s.names).unwrap_or_default();
|
|
6605
|
+
let msg = |n: &str| format!("{} failed with {} at {}:{}:{}", callee, n, file, b.line, b.column);
|
|
6606
|
+
op.pushIsErr(body);
|
|
6607
|
+
Instruction::If(BlockType::Empty).encode(body);
|
|
6608
|
+
for n in &names {
|
|
6609
|
+
op.pushCode(body)?;
|
|
6610
|
+
Instruction::I32Const(errCode(n)?).encode(body);
|
|
6611
|
+
Instruction::I32Eq.encode(body);
|
|
6612
|
+
Instruction::If(BlockType::Empty).encode(body);
|
|
6613
|
+
compileStaticString(&msg(n), body, state);
|
|
6614
|
+
Instruction::Call(crash_idx).encode(body);
|
|
6615
|
+
Instruction::Unreachable.encode(body);
|
|
6616
|
+
Instruction::End.encode(body);
|
|
6617
|
+
}
|
|
6618
|
+
compileStaticString(&msg("error"), body, state);
|
|
6619
|
+
Instruction::Call(crash_idx).encode(body);
|
|
6620
|
+
Instruction::Unreachable.encode(body);
|
|
6621
|
+
Instruction::End.encode(body);
|
|
6622
|
+
op.pushValue(body);
|
|
6623
|
+
Ok(())
|
|
6624
|
+
}
|
|
6625
|
+
|
|
6626
|
+
fn compileErrCatch(
|
|
6627
|
+
expr: &ast::Expr,
|
|
6628
|
+
c: &ast::CatchExpr,
|
|
6629
|
+
body: &mut Vec<u8>,
|
|
6630
|
+
ctx: &LocalCtx,
|
|
6631
|
+
state: &mut ModuleState,
|
|
6632
|
+
) -> Result<(), String> {
|
|
6633
|
+
let op = errOperand(expr, &c.value, body, ctx, state)?;
|
|
6634
|
+
op.pushIsErr(body);
|
|
6635
|
+
Instruction::If(resultBlock(&op.success)).encode(body);
|
|
6636
|
+
match &c.handler {
|
|
6637
|
+
ast::CatchHandler::Default(d) => {
|
|
6638
|
+
ctx.loop_extra_depth.set(ctx.loop_extra_depth.get() + 1);
|
|
6639
|
+
let r = compileExprInto(d, &op.success, body, ctx, state);
|
|
6640
|
+
ctx.loop_extra_depth.set(ctx.loop_extra_depth.get() - 1);
|
|
6641
|
+
r?;
|
|
6642
|
+
}
|
|
6643
|
+
ast::CatchHandler::Block { binder, body: blk } => {
|
|
6644
|
+
let names = errSite(expr).map(|s| s.names).unwrap_or_default();
|
|
6645
|
+
// `e` is a tag value of the set's names: build it from the code.
|
|
6646
|
+
for n in &names {
|
|
6647
|
+
op.pushCode(body)?;
|
|
6648
|
+
Instruction::I32Const(errCode(n)?).encode(body);
|
|
6649
|
+
Instruction::I32Eq.encode(body);
|
|
6650
|
+
Instruction::If(BlockType::Result(ValType::I32)).encode(body);
|
|
6651
|
+
Instruction::I32Const(tagId(n)).encode(body);
|
|
6652
|
+
Instruction::Else.encode(body);
|
|
6653
|
+
}
|
|
6654
|
+
Instruction::I32Const(0).encode(body);
|
|
6655
|
+
for _ in &names {
|
|
6656
|
+
Instruction::End.encode(body);
|
|
6657
|
+
}
|
|
6658
|
+
Instruction::StructNew(ctx.gc_types.tag_type_idx).encode(body);
|
|
6659
|
+
let idx = *ctx
|
|
6660
|
+
.locals
|
|
6661
|
+
.get(binder)
|
|
6662
|
+
.ok_or_else(|| format!("undeclared catch binder '{}'", binder))?;
|
|
6663
|
+
Instruction::LocalSet(idx).encode(body);
|
|
6664
|
+
let tags = PlumType::TTags(names.iter().map(|n| (n.clone(), None)).collect());
|
|
6665
|
+
ctx.type_env.borrow_mut().insert(binder.clone(), TypeScheme::mono(tags));
|
|
6666
|
+
let rvt = if matches!(op.success, PlumType::TUnit) { None } else { Some(plumTypeToValtype(&op.success)) };
|
|
6667
|
+
compileCaseBody(blk, rvt, body, ctx, state)?;
|
|
6668
|
+
}
|
|
6669
|
+
}
|
|
6670
|
+
Instruction::Else.encode(body);
|
|
6671
|
+
op.pushValue(body);
|
|
6672
|
+
Instruction::End.encode(body);
|
|
6673
|
+
Ok(())
|
|
6674
|
+
}
|
|
6675
|
+
|
|
6676
|
+
/// `a?.b` on an error union: `a.andThen(|x| x.b)` inlined.
|
|
6677
|
+
fn compileErrAndThen(
|
|
6678
|
+
expr: &ast::Expr,
|
|
6679
|
+
obj: &ast::Expr,
|
|
6680
|
+
cl: &ast::Closure,
|
|
6681
|
+
body: &mut Vec<u8>,
|
|
6682
|
+
ctx: &LocalCtx,
|
|
6683
|
+
state: &mut ModuleState,
|
|
6684
|
+
) -> Result<(), String> {
|
|
6685
|
+
let op = errOperand(expr, obj, body, ctx, state)?;
|
|
6686
|
+
let out_ty = inferLocalType(expr, ctx);
|
|
6687
|
+
let PlumType::TErr(out_inner, _) = &out_ty else {
|
|
6688
|
+
return Err(format!("codegen: '?.' result is {}, not an error union", out_ty));
|
|
6689
|
+
};
|
|
6690
|
+
op.pushIsErr(body);
|
|
6691
|
+
Instruction::If(blockTypeFor(Some(plumTypeToValtype(&out_ty)))).encode(body);
|
|
6692
|
+
emitZeros(out_inner, body);
|
|
6693
|
+
op.pushCode(body)?;
|
|
6694
|
+
Instruction::Else.encode(body);
|
|
6695
|
+
let pidx = *ctx.locals.get(&cl.params[0]).ok_or_else(|| "missing '?.' binding".to_string())?;
|
|
6696
|
+
op.pushValue(body);
|
|
6697
|
+
emitSetLocals(pidx, &op.success, body);
|
|
6698
|
+
let (last, rest) = cl
|
|
6699
|
+
.body
|
|
6700
|
+
.stmts
|
|
6701
|
+
.split_last()
|
|
6702
|
+
.ok_or_else(|| "codegen: empty '?.' body".to_string())?;
|
|
6703
|
+
ctx.loop_extra_depth.set(ctx.loop_extra_depth.get() + 1);
|
|
6704
|
+
for st in rest {
|
|
6705
|
+
compileStmt(st, body, ctx, state)?;
|
|
6706
|
+
}
|
|
6707
|
+
let ast::Stmt::Expr(e, ..) = last else {
|
|
6708
|
+
return Err("codegen: '?.' body must be an expression".to_string());
|
|
6709
|
+
};
|
|
6710
|
+
compileExpr(e, body, ctx, state)?;
|
|
6711
|
+
ctx.loop_extra_depth.set(ctx.loop_extra_depth.get() - 1);
|
|
6712
|
+
if !matches!(inferLocalType(e, ctx), PlumType::TErr(..)) {
|
|
6713
|
+
Instruction::I32Const(0).encode(body);
|
|
6714
|
+
}
|
|
6715
|
+
Instruction::End.encode(body);
|
|
6716
|
+
Ok(())
|
|
6717
|
+
}
|
|
6718
|
+
|
|
6719
|
+
/// `e.name()` on a tag/error value: its text.
|
|
6720
|
+
fn compileTagName(
|
|
6721
|
+
expr: &ast::Expr,
|
|
6722
|
+
obj: &ast::Expr,
|
|
6723
|
+
tags: &std::collections::BTreeMap<String, Option<Box<PlumType>>>,
|
|
6724
|
+
body: &mut Vec<u8>,
|
|
6725
|
+
ctx: &LocalCtx,
|
|
6726
|
+
state: &mut ModuleState,
|
|
6727
|
+
) -> Result<(), String> {
|
|
6728
|
+
let tmp = *ctx
|
|
6729
|
+
.locals
|
|
6730
|
+
.get(&errScratch(expr))
|
|
6731
|
+
.ok_or_else(|| "internal codegen error: missing name() scratch".to_string())?;
|
|
6732
|
+
compileExpr(obj, body, ctx, state)?;
|
|
6733
|
+
Instruction::StructGet { struct_type_index: ctx.gc_types.tag_type_idx, field_index: 0 }.encode(body);
|
|
6734
|
+
Instruction::LocalSet(tmp).encode(body);
|
|
6735
|
+
let svt = plumTypeToValtype(&PlumType::TStr);
|
|
6736
|
+
for n in tags.keys() {
|
|
6737
|
+
Instruction::LocalGet(tmp).encode(body);
|
|
6738
|
+
Instruction::I32Const(tagId(n)).encode(body);
|
|
6739
|
+
Instruction::I32Eq.encode(body);
|
|
6740
|
+
Instruction::If(BlockType::Result(svt)).encode(body);
|
|
6741
|
+
compileStaticString(n, body, state);
|
|
6742
|
+
Instruction::Else.encode(body);
|
|
6743
|
+
}
|
|
6744
|
+
compileStaticString("", body, state);
|
|
6745
|
+
for _ in tags {
|
|
6746
|
+
Instruction::End.encode(body);
|
|
6747
|
+
}
|
|
6748
|
+
Ok(())
|
|
6749
|
+
}
|
|
6750
|
+
|
|
6202
6751
|
/// Converts a nullable `Try` value (null = `Missing`) into the ordinary struct
|
|
6203
6752
|
/// layout of the wider `to` type (`Try[T, [Missing, ..]]`).
|
|
6204
6753
|
fn compileNullableRelayout(
|
|
@@ -6241,8 +6790,23 @@ fn compileExprCore(
|
|
|
6241
6790
|
) -> Result<(), String> {
|
|
6242
6791
|
match expr {
|
|
6243
6792
|
ast::Expr::Yield(_) => return Err("internal codegen error: yield survived expansion".to_string()),
|
|
6244
|
-
ast::Expr::Bang(
|
|
6793
|
+
ast::Expr::Bang(b) => compileErrBang(expr, b, body, ctx, state)?,
|
|
6794
|
+
ast::Expr::Catch(c) => compileErrCatch(expr, c, body, ctx, state)?,
|
|
6795
|
+
ast::Expr::Fail(f) => {
|
|
6796
|
+
let tag = f.tag.clone();
|
|
6797
|
+
let rt = ctx.ret_type.clone();
|
|
6798
|
+
match rt {
|
|
6799
|
+
Some(ref r @ PlumType::TApp(..)) if plum_checker::nullableTryPayload(r).is_none() => {
|
|
6800
|
+
let idx = ctx.gc_types.variantIdx(r, "Err").ok_or_else(|| {
|
|
6245
|
-
|
|
6801
|
+
format!("codegen: `fail` in a function returning {}", r)
|
|
6802
|
+
})?;
|
|
6803
|
+
Instruction::I32Const(tagId(&tag)).encode(body);
|
|
6804
|
+
Instruction::StructNew(ctx.gc_types.tag_type_idx).encode(body);
|
|
6805
|
+
Instruction::StructNew(idx).encode(body);
|
|
6806
|
+
Instruction::Return.encode(body);
|
|
6807
|
+
}
|
|
6808
|
+
_ => emitErrReturn(&|b| { Instruction::I32Const(errCode(&tag)?).encode(b); Ok(()) }, body, ctx)?,
|
|
6809
|
+
}
|
|
6246
6810
|
}
|
|
6247
6811
|
ast::Expr::Int(n) => {
|
|
6248
6812
|
Instruction::I64Const(*n).encode(body);
|
|
@@ -6283,6 +6847,7 @@ fn compileExprCore(
|
|
|
6283
6847
|
// `else` branch — the same stack-polymorphism-after-`return` rule
|
|
6284
6848
|
// `compileStmt`'s own `Return` arm relies on, just reached from
|
|
6285
6849
|
// inside an expression here for the first time.
|
|
6850
|
+
ast::Expr::Try(inner) if errSite(expr).is_some() => compileErrTry(expr, inner, body, ctx, state)?,
|
|
6286
6851
|
ast::Expr::Try(inner) => {
|
|
6287
6852
|
let inner_ty = inferLocalType(inner, ctx);
|
|
6288
6853
|
let enum_name = match plum_checker::nominalName(&inner_ty) {
|
|
@@ -7024,7 +7589,13 @@ fn compileExprCore(
|
|
|
7024
7589
|
compileFieldRead(&attr.object, field_name, &obj_ty, expr, body, ctx, state)?
|
|
7025
7590
|
}
|
|
7026
7591
|
ast::AttrKind::Method(call) => {
|
|
7592
|
+
if let (PlumType::TErr(..), Some(cl)) = (&obj_ty, andThenClosure(attr, call)) {
|
|
7593
|
+
compileErrAndThen(expr, &attr.object, cl, body, ctx, state)?
|
|
7594
|
+
} else if let (PlumType::TTags(tags), "name", true) = (&obj_ty, call.name.as_str(), call.args.is_empty()) {
|
|
7595
|
+
compileTagName(expr, &attr.object, tags, body, ctx, state)?
|
|
7596
|
+
} else {
|
|
7027
|
-
|
|
7597
|
+
compileMethodCall(&attr.object, call, &obj_ty, expr, false, body, ctx, state)?
|
|
7598
|
+
}
|
|
7028
7599
|
}
|
|
7029
7600
|
}
|
|
7030
7601
|
}
|
plum-wasm-codegen/tests/codegen_tests.rs
CHANGED
|
@@ -2812,3 +2812,331 @@ fn extend_replaces_dummy_enums() {
|
|
|
2812
2812
|
assert!(!text.contains(&format!("enum {f} =")), "{f} still has a dummy enum");
|
|
2813
2813
|
}
|
|
2814
2814
|
}
|
|
2815
|
+
|
|
2816
|
+
// ---- Zig-style error unions (E3) ----
|
|
2817
|
+
|
|
2818
|
+
fn compileUnmerged(src: &str) -> Vec<u8> {
|
|
2819
|
+
assertValid(src)
|
|
2820
|
+
}
|
|
2821
|
+
|
|
2822
|
+
fn runInt(src: &str) -> i64 {
|
|
2823
|
+
let bytes = compileUnmerged(src);
|
|
2824
|
+
let engine = gcEngine();
|
|
2825
|
+
let module = wasmtime::Module::new(&engine, &bytes).unwrap();
|
|
2826
|
+
let mut store = wasmtime::Store::new(&engine, ());
|
|
2827
|
+
let instance = wasmtime::Instance::new(&mut store, &module, &[]).unwrap();
|
|
2828
|
+
instance.get_typed_func::<(), i64>(&mut store, "main").unwrap().call(&mut store, ()).unwrap()
|
|
2829
|
+
}
|
|
2830
|
+
|
|
2831
|
+
/// Runs a std-importing program; returns (result or trap, crash messages seen).
|
|
2832
|
+
fn runWithCrash(name: &str, src: &str) -> (Result<i64, String>, Vec<String>) {
|
|
2833
|
+
let dir = std::env::temp_dir().join(format!("plum_e3_{}_{}", name, std::process::id()));
|
|
2834
|
+
std::fs::create_dir_all(&dir).unwrap();
|
|
2835
|
+
let path = dir.join("main.plum");
|
|
2836
|
+
std::fs::write(&path, src).unwrap();
|
|
2837
|
+
let source = plum_core::loadAndMerge(&path, &repoRoot()).expect("load");
|
|
2838
|
+
plum_checker::checkSource(&source).unwrap_or_else(|e| {
|
|
2839
|
+
panic!("check errors: {:?}", e.iter().map(|x| x.message.clone()).collect::<Vec<_>>())
|
|
2840
|
+
});
|
|
2841
|
+
let bytes = compileSource(&source).expect("compile failed");
|
|
2842
|
+
wasmparser::validate(&bytes).expect("valid wasm");
|
|
2843
|
+
let _ = std::fs::remove_dir_all(&dir);
|
|
2844
|
+
let engine = gcEngine();
|
|
2845
|
+
let module = wasmtime::Module::new(&engine, &bytes).unwrap();
|
|
2846
|
+
let mut store = wasmtime::Store::new(&engine, Vec::<String>::new());
|
|
2847
|
+
let mut linker: wasmtime::Linker<Vec<String>> = wasmtime::Linker::new(&engine);
|
|
2848
|
+
for imp in module.imports() {
|
|
2849
|
+
if imp.module() == "plum" && imp.name() == "crash" {
|
|
2850
|
+
let ty = imp.ty().func().cloned().unwrap();
|
|
2851
|
+
linker
|
|
2852
|
+
.func_new("plum", "crash", ty, |mut caller, params, _| {
|
|
2853
|
+
let wasmtime::Val::AnyRef(Some(s)) = ¶ms[0] else { panic!("Str expected") };
|
|
2854
|
+
let st = s.unwrap_struct(&caller).unwrap();
|
|
2855
|
+
let wasmtime::Val::AnyRef(Some(buf)) = st.field(&mut caller, 0).unwrap() else { panic!() };
|
|
2856
|
+
let bs = buf.unwrap_struct(&caller).unwrap();
|
|
2857
|
+
let wasmtime::Val::AnyRef(Some(arr)) = bs.field(&mut caller, 0).unwrap() else { panic!() };
|
|
2858
|
+
let wasmtime::Val::I64(len) = bs.field(&mut caller, 1).unwrap() else { panic!() };
|
|
2859
|
+
let arr = arr.unwrap_array(&caller).unwrap();
|
|
2860
|
+
let mut b = vec![0u8; arr.len(&caller).unwrap() as usize];
|
|
2861
|
+
arr.copy_to_i8_slice(&mut caller, &mut b).unwrap();
|
|
2862
|
+
b.truncate(len as usize);
|
|
2863
|
+
caller.data_mut().push(String::from_utf8_lossy(&b).into_owned());
|
|
2864
|
+
Ok(())
|
|
2865
|
+
})
|
|
2866
|
+
.unwrap();
|
|
2867
|
+
}
|
|
2868
|
+
}
|
|
2869
|
+
linker.define_unknown_imports_as_default_values(&mut store, &module).unwrap();
|
|
2870
|
+
let instance = linker.instantiate(&mut store, &module).unwrap();
|
|
2871
|
+
let main = instance.get_typed_func::<(), i64>(&mut store, "main").unwrap();
|
|
2872
|
+
let r = main.call(&mut store, ()).map_err(|e| e.to_string());
|
|
2873
|
+
(r, store.data().clone())
|
|
2874
|
+
}
|
|
2875
|
+
|
|
2876
|
+
#[test]
|
|
2877
|
+
fn error_union_multi_value() {
|
|
2878
|
+
let bytes = assertValid("fun f(n: Int) -> !Int =\n if n < 0\n fail Neg\n n\n\nfun main() -> Int =\n f(3) catch 0\n");
|
|
2879
|
+
let mut found = false;
|
|
2880
|
+
for payload in wasmparser::Parser::new(0).parse_all(&bytes) {
|
|
2881
|
+
if let wasmparser::Payload::TypeSection(s) = payload.unwrap() {
|
|
2882
|
+
for g in s {
|
|
2883
|
+
for t in g.unwrap().types() {
|
|
2884
|
+
if let wasmparser::CompositeInnerType::Func(f) = &t.composite_type.inner {
|
|
2885
|
+
if f.params() == [wasmparser::ValType::I64]
|
|
2886
|
+
&& f.results() == [wasmparser::ValType::I64, wasmparser::ValType::I32]
|
|
2887
|
+
{
|
|
2888
|
+
found = true;
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
assert!(found, "no (i64)->(i64,i32) function type");
|
|
2896
|
+
}
|
|
2897
|
+
|
|
2898
|
+
#[test]
|
|
2899
|
+
fn fail_and_question_propagate() {
|
|
2900
|
+
let src = "\
|
|
2901
|
+
fun a(n: Int) -> !Int =
|
|
2902
|
+
if n < 0
|
|
2903
|
+
fail Negative
|
|
2904
|
+
n * 2
|
|
2905
|
+
|
|
2906
|
+
fun b(n: Int) -> !Int =
|
|
2907
|
+
x := a(n)?
|
|
2908
|
+
x + 1
|
|
2909
|
+
|
|
2910
|
+
fun main() -> Int =
|
|
2911
|
+
p := b(5) catch 100
|
|
2912
|
+
q := b(-5) catch 200
|
|
2913
|
+
p * 1000 + q
|
|
2914
|
+
";
|
|
2915
|
+
assert_eq!(runInt(src), 11 * 1000 + 200);
|
|
2916
|
+
}
|
|
2917
|
+
|
|
2918
|
+
#[test]
|
|
2919
|
+
fn catch_default_runtime() {
|
|
2920
|
+
let src = "\
|
|
2921
|
+
fun f(n: Int) -> !Int =
|
|
2922
|
+
if n == 0
|
|
2923
|
+
fail Zero
|
|
2924
|
+
n
|
|
2925
|
+
|
|
2926
|
+
fun main() -> Int =
|
|
2927
|
+
{f(7) catch 1} * 10 + {f(0) catch 3}
|
|
2928
|
+
";
|
|
2929
|
+
assert_eq!(runInt(src), 73);
|
|
2930
|
+
}
|
|
2931
|
+
|
|
2932
|
+
#[test]
|
|
2933
|
+
fn catch_fail_maps_tag() {
|
|
2934
|
+
let src = "\
|
|
2935
|
+
fun inner(n: Int) -> !Int =
|
|
2936
|
+
if n == 0
|
|
2937
|
+
fail Zero
|
|
2938
|
+
n
|
|
2939
|
+
|
|
2940
|
+
fun outer(n: Int) -> !Int =
|
|
2941
|
+
x := inner(n) catch fail Mapped
|
|
2942
|
+
x + 1
|
|
2943
|
+
|
|
2944
|
+
fun main() -> Int =
|
|
2945
|
+
a := outer(0) catch e
|
|
2946
|
+
match e
|
|
2947
|
+
Mapped => 40
|
|
2948
|
+
b := outer(1) catch 99
|
|
2949
|
+
a + b
|
|
2950
|
+
";
|
|
2951
|
+
assert_eq!(runInt(src), 42);
|
|
2952
|
+
}
|
|
2953
|
+
|
|
2954
|
+
#[test]
|
|
2955
|
+
fn catch_block_binds_error() {
|
|
2956
|
+
let src = "\
|
|
2957
|
+
fun f(n: Int) -> !Int =
|
|
2958
|
+
if n == 1
|
|
2959
|
+
fail One
|
|
2960
|
+
if n == 2
|
|
2961
|
+
fail Two
|
|
2962
|
+
n
|
|
2963
|
+
|
|
2964
|
+
fun main() -> Int =
|
|
2965
|
+
a := f(1) catch e
|
|
2966
|
+
match e
|
|
2967
|
+
One => 10
|
|
2968
|
+
Two => 20
|
|
2969
|
+
b := f(2) catch e
|
|
2970
|
+
match e
|
|
2971
|
+
One => 10
|
|
2972
|
+
Two => 20
|
|
2973
|
+
c := f(3) catch e
|
|
2974
|
+
return 0
|
|
2975
|
+
a * 100 + b * 10 + c
|
|
2976
|
+
";
|
|
2977
|
+
assert_eq!(runInt(src), 10 * 100 + 20 * 10 + 3);
|
|
2978
|
+
}
|
|
2979
|
+
|
|
2980
|
+
#[test]
|
|
2981
|
+
fn e_name_runtime() {
|
|
2982
|
+
let src = "\
|
|
2983
|
+
import std/Str
|
|
2984
|
+
import std/Number
|
|
2985
|
+
|
|
2986
|
+
fun f() -> !Int =
|
|
2987
|
+
fail NotFound
|
|
2988
|
+
|
|
2989
|
+
fun main() -> Int =
|
|
2990
|
+
s := f() catch e
|
|
2991
|
+
e.name()
|
|
2992
|
+
s.length()
|
|
2993
|
+
";
|
|
2994
|
+
// the catch block must yield Int, so name() is used for its length instead
|
|
2995
|
+
let src = src.replace(" s := f() catch e\n e.name()\n s.length()", " n := f() catch e\n e.name().length()\n n");
|
|
2996
|
+
let (r, _) = runWithCrash("ename", &src);
|
|
2997
|
+
assert_eq!(r.unwrap(), 8);
|
|
2998
|
+
}
|
|
2999
|
+
|
|
3000
|
+
#[test]
|
|
3001
|
+
fn bang_crashes_with_message() {
|
|
3002
|
+
let src = "\
|
|
3003
|
+
import std/Str
|
|
3004
|
+
import std/Number
|
|
3005
|
+
|
|
3006
|
+
fun risky() -> !Int =
|
|
3007
|
+
fail Boom
|
|
3008
|
+
|
|
3009
|
+
fun main() -> Int =
|
|
3010
|
+
risky()!
|
|
3011
|
+
";
|
|
3012
|
+
let (r, msgs) = runWithCrash("bang", src);
|
|
3013
|
+
assert!(r.is_err());
|
|
3014
|
+
assert_eq!(msgs.len(), 1, "{msgs:?}");
|
|
3015
|
+
assert!(msgs[0].starts_with("risky failed with Boom at "), "{}", msgs[0]);
|
|
3016
|
+
assert!(msgs[0].ends_with(":8:3"), "{}", msgs[0]);
|
|
3017
|
+
let ok = "import std/Str\nimport std/Number\n\nfun f() -> !Int =\n 7\n\nfun main() -> Int =\n f()!\n";
|
|
3018
|
+
assert_eq!(runWithCrash("bangok", ok).0.unwrap(), 7);
|
|
3019
|
+
}
|
|
3020
|
+
|
|
3021
|
+
#[test]
|
|
3022
|
+
fn unit_error_union() {
|
|
3023
|
+
let src = "\
|
|
3024
|
+
fun check(n: Int) -> !Unit =
|
|
3025
|
+
if n < 0
|
|
3026
|
+
fail Neg
|
|
3027
|
+
|
|
3028
|
+
fun run(n: Int) -> !Int =
|
|
3029
|
+
check(n)?
|
|
3030
|
+
n
|
|
3031
|
+
|
|
3032
|
+
fun main() -> Int =
|
|
3033
|
+
{run(4) catch 0} + {run(-1) catch 5}
|
|
3034
|
+
";
|
|
3035
|
+
assert_eq!(runInt(src), 9);
|
|
3036
|
+
}
|
|
3037
|
+
|
|
3038
|
+
#[test]
|
|
3039
|
+
fn error_union_boxed_in_list() {
|
|
3040
|
+
let src = "\
|
|
3041
|
+
import std/Str
|
|
3042
|
+
import std/Number
|
|
3043
|
+
import std/List
|
|
3044
|
+
|
|
3045
|
+
fun f(n: Int) -> !Int =
|
|
3046
|
+
if n < 0
|
|
3047
|
+
fail Neg
|
|
3048
|
+
n
|
|
3049
|
+
|
|
3050
|
+
fun main() -> Int =
|
|
3051
|
+
xs := List[!Int]()
|
|
3052
|
+
xs.push(f(4))
|
|
3053
|
+
xs.push(f(-1))
|
|
3054
|
+
a := xs.at(0) catch 0
|
|
3055
|
+
b := xs.at(1) catch 100
|
|
3056
|
+
a + b
|
|
3057
|
+
";
|
|
3058
|
+
assert_eq!(runWithCrash("boxlist", src).0.unwrap(), 104);
|
|
3059
|
+
}
|
|
3060
|
+
|
|
3061
|
+
#[test]
|
|
3062
|
+
fn error_union_in_class_field() {
|
|
3063
|
+
let src = "\
|
|
3064
|
+
enum Box =
|
|
3065
|
+
| Box(v: !Int)
|
|
3066
|
+
|
|
3067
|
+
fun f(n: Int) -> !Int =
|
|
3068
|
+
if n < 0
|
|
3069
|
+
fail Neg
|
|
3070
|
+
n
|
|
3071
|
+
|
|
3072
|
+
fun main() -> Int =
|
|
3073
|
+
a := Box(v: f(6))
|
|
3074
|
+
b := Box(v: f(-6))
|
|
3075
|
+
{a.v catch 0} * 10 + {b.v catch 1}
|
|
3076
|
+
";
|
|
3077
|
+
assert_eq!(runInt(src), 61);
|
|
3078
|
+
}
|
|
3079
|
+
|
|
3080
|
+
#[test]
|
|
3081
|
+
fn safe_nav_on_error_union() {
|
|
3082
|
+
let src = "\
|
|
3083
|
+
enum User =
|
|
3084
|
+
| User(age: Int)
|
|
3085
|
+
|
|
3086
|
+
fun load(n: Int) -> !User =
|
|
3087
|
+
if n < 0
|
|
3088
|
+
fail NoUser
|
|
3089
|
+
User(age: n)
|
|
3090
|
+
|
|
3091
|
+
fun age(n: Int) -> !Int =
|
|
3092
|
+
load(n)?.age
|
|
3093
|
+
|
|
3094
|
+
fun main() -> Int =
|
|
3095
|
+
{age(30) catch 0} + {age(-1) catch 1}
|
|
3096
|
+
";
|
|
3097
|
+
assert_eq!(runInt(src), 31);
|
|
3098
|
+
}
|
|
3099
|
+
|
|
3100
|
+
#[test]
|
|
3101
|
+
fn nullable_ref_still_null() {
|
|
3102
|
+
let src = "\
|
|
3103
|
+
import std/Str
|
|
3104
|
+
import std/Number
|
|
3105
|
+
import std/Try
|
|
3106
|
+
|
|
3107
|
+
enum Item =
|
|
3108
|
+
| Item(v: Int)
|
|
3109
|
+
|
|
3110
|
+
fun find(n: Int) -> Item? =
|
|
3111
|
+
if n < 0
|
|
3112
|
+
fail Missing
|
|
3113
|
+
Item(v: n)
|
|
3114
|
+
|
|
3115
|
+
fun main() -> Int =
|
|
3116
|
+
a := find(3) catch Item(v: 0)
|
|
3117
|
+
b := find(-3) catch Item(v: 5)
|
|
3118
|
+
a.v * 10 + b.v
|
|
3119
|
+
";
|
|
3120
|
+
assert_eq!(runWithCrash("nullable", src).0.unwrap(), 35);
|
|
3121
|
+
}
|
|
3122
|
+
|
|
3123
|
+
#[test]
|
|
3124
|
+
fn no_alloc_on_error_path() {
|
|
3125
|
+
let src = "\
|
|
3126
|
+
fun a(n: Int) -> !Int =
|
|
3127
|
+
if n < 0
|
|
3128
|
+
fail Neg
|
|
3129
|
+
n
|
|
3130
|
+
|
|
3131
|
+
fun b(n: Int) -> !Int =
|
|
3132
|
+
x := a(n)?
|
|
3133
|
+
y := a(x + 1)?
|
|
3134
|
+
x + y
|
|
3135
|
+
|
|
3136
|
+
fun main() -> Int =
|
|
3137
|
+
b(2) catch 0
|
|
3138
|
+
";
|
|
3139
|
+
let bytes = assertValid(src);
|
|
3140
|
+
let ops = opsOf(&bytes, "b");
|
|
3141
|
+
assert_eq!(countOps(&ops, "StructNew"), 0, "{ops:?}");
|
|
3142
|
+
}
|