plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
d959708
— Peter John
2026-11-01T22:10:50+05:30
refactor: byteToStr in Plum; Int interpolation via Int.toStr; int_to_string wasm helper removed
- plum-checker/src/lib.rs +2 -5
- plum-std/Str.plum +4 -3
- plum-wasm-codegen/src/lib.rs +5 -156
- plum-wasm-codegen/tests/codegen_tests.rs +53 -20
plum-checker/src/lib.rs
CHANGED
|
@@ -3376,10 +3376,6 @@ fn inferMethodCall(
|
|
|
3376
3376
|
_ => Err(format!("a variadic parameter has no method '{}'", name)),
|
|
3377
3377
|
};
|
|
3378
3378
|
}
|
|
3379
|
-
// `Bool.toStr` is a compiler intrinsic ("true"/"false"), not a source method.
|
|
3380
|
-
if matches!(obj_ty, PlumType::TBool) && name == "toStr" && args.is_empty() {
|
|
3381
|
-
return Ok(PlumType::TStr);
|
|
3382
|
-
}
|
|
3383
3379
|
// A method on a generic parameter: duck-typed — checked when the enclosing generic body is
|
|
3384
3380
|
// checked per concrete instantiation.
|
|
3385
3381
|
if let PlumType::TParam(_) = obj_ty {
|
|
@@ -3440,7 +3436,8 @@ fn inferMethodCall(
|
|
|
3440
3436
|
// A fully concrete generic receiver (`Map[Str, Int]`) is checked in the
|
|
3441
3437
|
// erased pass too, so a typo'd method is a positioned checker error
|
|
3442
3438
|
// rather than a late codegen failure.
|
|
3443
|
-
if !hasFreeVars(obj_ty) && (INST_MODE.with(|m| m.get())
|
|
3439
|
+
if !hasFreeVars(obj_ty) && (INST_MODE.with(|m| m.get())
|
|
3440
|
+
|| matches!(obj_ty, PlumType::TApp(..) | PlumType::TInt | PlumType::TBool)) {
|
|
3444
3441
|
return Err(format!("{} has no method '{}'", obj_ty, name));
|
|
3445
3442
|
}
|
|
3446
3443
|
// Otherwise (erased pass): allow, the per-instance pass decides.
|
plum-std/Str.plum
CHANGED
|
@@ -393,10 +393,11 @@ record Str(bytes: []Byte) =
|
|
|
393
393
|
fun words(self) -> Str =
|
|
394
394
|
caseConvert(self, " ", 0)
|
|
395
395
|
|
|
396
|
-
# A new 1-byte Str holding `b`'s low 8 bits
|
|
396
|
+
# A new 1-byte Str holding `b`'s low 8 bits, the counterpart to `byteAt`.
|
|
397
|
-
# way to build a new array from Plum source), the counterpart to `byteAt`.
|
|
398
397
|
fun byteToStr(b: Int) -> Str =
|
|
398
|
+
out := makeBytes(1)
|
|
399
|
+
out.set(0, Byte(b))
|
|
399
|
-
|
|
400
|
+
return Str(bytes: out)
|
|
400
401
|
|
|
401
402
|
fun isAsciiUpper(b: Int) -> Bool =
|
|
402
403
|
b >= 65 && b <= 90 # 'A'..'Z'
|
plum-wasm-codegen/src/lib.rs
CHANGED
|
@@ -413,7 +413,6 @@ pub struct CompileCtx<'a> {
|
|
|
413
413
|
pub string_eq_func: u32,
|
|
414
414
|
/// Shared runtime helper `(n: i64) -> ref Str`: allocates a new `array<i8>`
|
|
415
415
|
/// holding `n`'s decimal representation, for interpolating an `Int`.
|
|
416
|
-
pub int_to_string_func: u32,
|
|
417
416
|
/// wasm-gc type-section indices for this program's enums/Str/closures —
|
|
418
417
|
/// every value's real representation (see `docs/superpowers/plans/2026-07-25-wasm-gc-migration.md`).
|
|
419
418
|
pub gc_types: GcTypeRegistry,
|
|
@@ -462,7 +461,6 @@ struct LocalCtx<'a> {
|
|
|
462
461
|
named_fn_values: &'a HashMap<(String, PlumType), u32>,
|
|
463
462
|
string_concat_func: u32,
|
|
464
463
|
string_eq_func: u32,
|
|
465
|
-
int_to_string_func: u32,
|
|
466
464
|
methods: &'a MethodEnv,
|
|
467
465
|
enum_variants: &'a EnumVariants,
|
|
468
466
|
enum_params: &'a EnumParams,
|
|
@@ -2493,7 +2491,6 @@ fn compileSourceInner(
|
|
|
2493
2491
|
|
|
2494
2492
|
let string_concat_func = registerStringConcatHelper(&mut module, gc_types.byte_array_type_idx);
|
|
2495
2493
|
let string_eq_func = registerStringEqHelper(&mut module, gc_types.byte_array_type_idx);
|
|
2496
|
-
let int_to_string_func = registerIntToStringHelper(&mut module, gc_types.byte_array_type_idx);
|
|
2497
2494
|
|
|
2498
2495
|
let ctx = CompileCtx {
|
|
2499
2496
|
func_ids,
|
|
@@ -2515,7 +2512,6 @@ fn compileSourceInner(
|
|
|
2515
2512
|
named_fn_values,
|
|
2516
2513
|
string_concat_func,
|
|
2517
2514
|
string_eq_func,
|
|
2518
|
-
int_to_string_func,
|
|
2519
2515
|
gc_types,
|
|
2520
2516
|
singleton_globals,
|
|
2521
2517
|
};
|
|
@@ -2822,138 +2818,6 @@ fn registerStringEqHelper(module: &mut WasmModule, byte_array_type_idx: u32) ->
|
|
|
2822
2818
|
module.addFunction(type_idx, &body)
|
|
2823
2819
|
}
|
|
2824
2820
|
|
|
2825
|
-
/// Registers `__int_to_string(n: i64) -> ref Str`, a hand-written runtime helper
|
|
2826
|
-
/// backing string interpolation: allocates a new `array<i8>` holding `n`'s decimal
|
|
2827
|
-
/// representation (handling a leading `-` for negatives, and `0` correctly via a
|
|
2828
|
-
/// do-while digit count that always runs at least once).
|
|
2829
|
-
fn registerIntToStringHelper(module: &mut WasmModule, byte_array_type_idx: u32) -> u32 {
|
|
2830
|
-
let byte_array_ref = gcRef(byte_array_type_idx);
|
|
2831
|
-
let str_ref = withGcTypes(strGcValtypeOrFallback);
|
|
2832
|
-
let type_idx = module.addType(&[ValType::I64], &[str_ref]);
|
|
2833
|
-
|
|
2834
|
-
// locals: 0=n (param, i64), 1=is_neg (i32), 2=count (i32), 3=total_len (i32),
|
|
2835
|
-
// 4=pos (i32), 5=result (ref), 6=abs_n (i64), 7=temp (i64)
|
|
2836
|
-
// Locals are declared as one group of 4 `i32`s, one group of 1 `ref`, then one
|
|
2837
|
-
// group of 2 `i64`s (see below), so indices must stay grouped by type in that
|
|
2838
|
-
// same order — NOT in whatever order reads best logically.
|
|
2839
|
-
const N: u32 = 0;
|
|
2840
|
-
const IS_NEG: u32 = 1;
|
|
2841
|
-
const COUNT: u32 = 2;
|
|
2842
|
-
const TOTAL_LEN: u32 = 3;
|
|
2843
|
-
const POS: u32 = 4;
|
|
2844
|
-
const RESULT: u32 = 5;
|
|
2845
|
-
const ABS_N: u32 = 6;
|
|
2846
|
-
const TEMP: u32 = 7;
|
|
2847
|
-
|
|
2848
|
-
let mut body = Vec::new();
|
|
2849
|
-
body.extend(encodeLeb128U32(3)); // three locals groups
|
|
2850
|
-
body.extend(encodeLeb128U32(4)); // is_neg, count, total_len, pos: i32
|
|
2851
|
-
ValType::I32.encode(&mut body);
|
|
2852
|
-
body.extend(encodeLeb128U32(1)); // result: ref
|
|
2853
|
-
byte_array_ref.encode(&mut body);
|
|
2854
|
-
body.extend(encodeLeb128U32(2)); // abs_n, temp: i64
|
|
2855
|
-
ValType::I64.encode(&mut body);
|
|
2856
|
-
|
|
2857
|
-
// is_neg = n < 0
|
|
2858
|
-
Instruction::LocalGet(N).encode(&mut body);
|
|
2859
|
-
Instruction::I64Const(0).encode(&mut body);
|
|
2860
|
-
Instruction::I64LtS.encode(&mut body);
|
|
2861
|
-
Instruction::LocalSet(IS_NEG).encode(&mut body);
|
|
2862
|
-
|
|
2863
|
-
// abs_n = if is_neg then (0 - n) else n
|
|
2864
|
-
Instruction::LocalGet(IS_NEG).encode(&mut body);
|
|
2865
|
-
Instruction::If(BlockType::Result(ValType::I64)).encode(&mut body);
|
|
2866
|
-
Instruction::I64Const(0).encode(&mut body);
|
|
2867
|
-
Instruction::LocalGet(N).encode(&mut body);
|
|
2868
|
-
Instruction::I64Sub.encode(&mut body);
|
|
2869
|
-
Instruction::Else.encode(&mut body);
|
|
2870
|
-
Instruction::LocalGet(N).encode(&mut body);
|
|
2871
|
-
Instruction::End.encode(&mut body);
|
|
2872
|
-
Instruction::LocalSet(ABS_N).encode(&mut body);
|
|
2873
|
-
|
|
2874
|
-
// count digits: do { temp /= 10; count++ } while (temp != 0); temp starts as abs_n
|
|
2875
|
-
Instruction::I32Const(0).encode(&mut body);
|
|
2876
|
-
Instruction::LocalSet(COUNT).encode(&mut body);
|
|
2877
|
-
Instruction::LocalGet(ABS_N).encode(&mut body);
|
|
2878
|
-
Instruction::LocalSet(TEMP).encode(&mut body);
|
|
2879
|
-
Instruction::Loop(BlockType::Empty).encode(&mut body);
|
|
2880
|
-
Instruction::LocalGet(TEMP).encode(&mut body);
|
|
2881
|
-
Instruction::I64Const(10).encode(&mut body);
|
|
2882
|
-
Instruction::I64DivS.encode(&mut body);
|
|
2883
|
-
Instruction::LocalSet(TEMP).encode(&mut body);
|
|
2884
|
-
Instruction::LocalGet(COUNT).encode(&mut body);
|
|
2885
|
-
Instruction::I32Const(1).encode(&mut body);
|
|
2886
|
-
Instruction::I32Add.encode(&mut body);
|
|
2887
|
-
Instruction::LocalSet(COUNT).encode(&mut body);
|
|
2888
|
-
Instruction::LocalGet(TEMP).encode(&mut body);
|
|
2889
|
-
Instruction::I64Const(0).encode(&mut body);
|
|
2890
|
-
Instruction::I64Ne.encode(&mut body);
|
|
2891
|
-
Instruction::BrIf(0).encode(&mut body);
|
|
2892
|
-
Instruction::End.encode(&mut body);
|
|
2893
|
-
|
|
2894
|
-
// total_len = count + is_neg
|
|
2895
|
-
Instruction::LocalGet(COUNT).encode(&mut body);
|
|
2896
|
-
Instruction::LocalGet(IS_NEG).encode(&mut body);
|
|
2897
|
-
Instruction::I32Add.encode(&mut body);
|
|
2898
|
-
Instruction::LocalSet(TOTAL_LEN).encode(&mut body);
|
|
2899
|
-
|
|
2900
|
-
// result = array.new_default(byte_array_type_idx, total_len) — no length prefix needed,
|
|
2901
|
-
// `array.len` reads it back natively.
|
|
2902
|
-
Instruction::LocalGet(TOTAL_LEN).encode(&mut body);
|
|
2903
|
-
Instruction::ArrayNewDefault(byte_array_type_idx).encode(&mut body);
|
|
2904
|
-
Instruction::LocalSet(RESULT).encode(&mut body);
|
|
2905
|
-
|
|
2906
|
-
// pos = total_len; temp = abs_n
|
|
2907
|
-
Instruction::LocalGet(TOTAL_LEN).encode(&mut body);
|
|
2908
|
-
Instruction::LocalSet(POS).encode(&mut body);
|
|
2909
|
-
Instruction::LocalGet(ABS_N).encode(&mut body);
|
|
2910
|
-
Instruction::LocalSet(TEMP).encode(&mut body);
|
|
2911
|
-
|
|
2912
|
-
// do { pos--; result[pos] = '0' + temp % 10; temp /= 10 } while (pos > is_neg)
|
|
2913
|
-
Instruction::Loop(BlockType::Empty).encode(&mut body);
|
|
2914
|
-
Instruction::LocalGet(POS).encode(&mut body);
|
|
2915
|
-
Instruction::I32Const(1).encode(&mut body);
|
|
2916
|
-
Instruction::I32Sub.encode(&mut body);
|
|
2917
|
-
Instruction::LocalSet(POS).encode(&mut body);
|
|
2918
|
-
// array.set(result, pos, value) — stack order [array_ref, index, value]
|
|
2919
|
-
Instruction::LocalGet(RESULT).encode(&mut body);
|
|
2920
|
-
Instruction::LocalGet(POS).encode(&mut body);
|
|
2921
|
-
// value = '0' + (temp % 10)
|
|
2922
|
-
Instruction::LocalGet(TEMP).encode(&mut body);
|
|
2923
|
-
Instruction::I64Const(10).encode(&mut body);
|
|
2924
|
-
Instruction::I64RemS.encode(&mut body);
|
|
2925
|
-
Instruction::I64Const(48).encode(&mut body);
|
|
2926
|
-
Instruction::I64Add.encode(&mut body);
|
|
2927
|
-
Instruction::I32WrapI64.encode(&mut body);
|
|
2928
|
-
Instruction::ArraySet(byte_array_type_idx).encode(&mut body);
|
|
2929
|
-
// temp /= 10
|
|
2930
|
-
Instruction::LocalGet(TEMP).encode(&mut body);
|
|
2931
|
-
Instruction::I64Const(10).encode(&mut body);
|
|
2932
|
-
Instruction::I64DivS.encode(&mut body);
|
|
2933
|
-
Instruction::LocalSet(TEMP).encode(&mut body);
|
|
2934
|
-
// while (pos > is_neg)
|
|
2935
|
-
Instruction::LocalGet(POS).encode(&mut body);
|
|
2936
|
-
Instruction::LocalGet(IS_NEG).encode(&mut body);
|
|
2937
|
-
Instruction::I32GtU.encode(&mut body);
|
|
2938
|
-
Instruction::BrIf(0).encode(&mut body);
|
|
2939
|
-
Instruction::End.encode(&mut body);
|
|
2940
|
-
|
|
2941
|
-
// if (is_neg) result[0] = '-'
|
|
2942
|
-
Instruction::LocalGet(IS_NEG).encode(&mut body);
|
|
2943
|
-
Instruction::If(BlockType::Empty).encode(&mut body);
|
|
2944
|
-
Instruction::LocalGet(RESULT).encode(&mut body);
|
|
2945
|
-
Instruction::I32Const(0).encode(&mut body);
|
|
2946
|
-
Instruction::I32Const(45).encode(&mut body); // '-'
|
|
2947
|
-
Instruction::ArraySet(byte_array_type_idx).encode(&mut body);
|
|
2948
|
-
Instruction::End.encode(&mut body);
|
|
2949
|
-
|
|
2950
|
-
Instruction::LocalGet(RESULT).encode(&mut body);
|
|
2951
|
-
wrapBytesAsStr(&mut body);
|
|
2952
|
-
Instruction::End.encode(&mut body);
|
|
2953
|
-
|
|
2954
|
-
module.addFunction(type_idx, &body)
|
|
2955
|
-
}
|
|
2956
|
-
|
|
2957
2821
|
/// Hand-written bodies for a handful of `libs/std/str.plum` primitives that can't
|
|
2958
2822
|
/// be expressed in Plum source at all (byte-level array access, building a new
|
|
2959
2823
|
/// one-element array) — these three are the whole reason every other `Str` method
|
|
@@ -2970,22 +2834,6 @@ fn compileIntrinsicFnBody(f: &ast::Fn, subst: &Subst) -> Option<Vec<u8>> {
|
|
|
2970
2834
|
}
|
|
2971
2835
|
let byte_array_type_idx = withGcTypes(|r| r.byte_array_type_idx);
|
|
2972
2836
|
match (f.type_param.as_deref(), f.name.as_str()) {
|
|
2973
|
-
// byteToStr(b: Int) -> Str — a new length-1 Str holding `b`'s low 8 bits,
|
|
2974
|
-
// wrapped into `Str`'s real `{bytes: []Byte}`
|
|
2975
|
-
// shape (see `wrapBytesAsStr`).
|
|
2976
|
-
(None, "byteToStr") => {
|
|
2977
|
-
let mut body = vec![0u8];
|
|
2978
|
-
Instruction::LocalGet(0).encode(&mut body); // b
|
|
2979
|
-
Instruction::I32WrapI64.encode(&mut body);
|
|
2980
|
-
Instruction::ArrayNewFixed {
|
|
2981
|
-
array_type_index: byte_array_type_idx,
|
|
2982
|
-
array_size: 1,
|
|
2983
|
-
}
|
|
2984
|
-
.encode(&mut body);
|
|
2985
|
-
wrapBytesAsStr(&mut body);
|
|
2986
|
-
Instruction::End.encode(&mut body);
|
|
2987
|
-
Some(body)
|
|
2988
|
-
}
|
|
2989
2837
|
// ByteSlice.length(self) -> Int — `[]Byte` reuses `Str`'s array<i8> wasm
|
|
2990
2838
|
// type (see the comment on `PlumType::TByteSlice` in `plumTypeToValtype`),
|
|
2991
2839
|
// so this is byte-for-byte identical to `Str.length` above.
|
|
@@ -3057,7 +2905,7 @@ fn compileIntrinsicFnBody(f: &ast::Fn, subst: &Subst) -> Option<Vec<u8>> {
|
|
|
3057
2905
|
/// best-effort `std/str` prelude), falling back to the raw `array<i8>` type
|
|
3058
2906
|
/// instead of panicking. Safe specifically because this is only used by the
|
|
3059
2907
|
/// three string runtime helpers (`registerStringConcatHelper`/
|
|
3060
|
-
/// `registerStringEqHelper`
|
|
2908
|
+
/// `registerStringEqHelper`), which are
|
|
3061
2909
|
/// registered UNCONDITIONALLY in every compiled program regardless of
|
|
3062
2910
|
/// whether it actually uses `Str` — a `Str`-less program (which, by
|
|
3063
2911
|
/// definition, never calls them) doesn't care what type they claim.
|
|
@@ -4265,7 +4113,6 @@ fn compileFnBodyImpl(
|
|
|
4265
4113
|
named_fn_values: &ctx.named_fn_values,
|
|
4266
4114
|
string_concat_func: ctx.string_concat_func,
|
|
4267
4115
|
string_eq_func: ctx.string_eq_func,
|
|
4268
|
-
int_to_string_func: ctx.int_to_string_func,
|
|
4269
4116
|
methods: &ctx.methods,
|
|
4270
4117
|
enum_variants: &ctx.enum_variants,
|
|
4271
4118
|
enum_params: &ctx.enum_params,
|
|
@@ -7479,8 +7326,11 @@ fn compileInterpolatedString(
|
|
|
7479
7326
|
compileExpr(expr, body, ctx, state)?;
|
|
7480
7327
|
}
|
|
7481
7328
|
PlumType::TInt => {
|
|
7329
|
+
// Ordinary `Int.toStr` (`plum-std/Int.plum`), like Float below.
|
|
7482
7330
|
compileExpr(expr, body, ctx, state)?;
|
|
7331
|
+
let func_idx = *ctx.func_ids.get("Int::toStr")
|
|
7332
|
+
.ok_or_else(|| "codegen: interpolating an Int value requires `Int.toStr` (import std/Int)".to_string())?;
|
|
7483
|
-
Instruction::Call(
|
|
7333
|
+
Instruction::Call(func_idx).encode(body);
|
|
7484
7334
|
}
|
|
7485
7335
|
PlumType::TBool => {
|
|
7486
7336
|
let str_ref = withGcTypes(|r| plumTypeToGcValtype(&PlumType::TStr, r));
|
|
@@ -7771,7 +7621,6 @@ fn compileClosureBody(
|
|
|
7771
7621
|
named_fn_values: &ctx.named_fn_values,
|
|
7772
7622
|
string_concat_func: ctx.string_concat_func,
|
|
7773
7623
|
string_eq_func: ctx.string_eq_func,
|
|
7774
|
-
int_to_string_func: ctx.int_to_string_func,
|
|
7775
7624
|
methods: &ctx.methods,
|
|
7776
7625
|
enum_variants: &ctx.enum_variants,
|
|
7777
7626
|
enum_params: &ctx.enum_params,
|
plum-wasm-codegen/tests/codegen_tests.rs
CHANGED
|
@@ -79,9 +79,14 @@ fn readStrResult(store: &mut wasmtime::Store<()>, val: &wasmtime::Val) -> String
|
|
|
79
79
|
let wasmtime::Val::AnyRef(Some(s)) = val else {
|
|
80
80
|
panic!("expected a Str result");
|
|
81
81
|
};
|
|
82
|
+
// `Str` is a record wrapping one `[]Byte` in field 0.
|
|
83
|
+
let st = s.unwrap_struct(&*store).expect("Str result should be a struct");
|
|
84
|
+
let wasmtime::Val::AnyRef(Some(bytes)) = st.field(&mut *store, 0).expect("bytes field") else {
|
|
85
|
+
panic!("Str.bytes should be a reference");
|
|
86
|
+
};
|
|
82
|
-
let arr =
|
|
87
|
+
let arr = bytes
|
|
83
|
-
.unwrap_array(&
|
|
88
|
+
.unwrap_array(&*store)
|
|
84
|
-
.expect("Str
|
|
89
|
+
.expect("Str.bytes should be an array");
|
|
85
90
|
let len = arr.len(&mut *store).expect("should read array length");
|
|
86
91
|
let mut buf = vec![0u8; len as usize];
|
|
87
92
|
arr.copy_to_i8_slice(&mut *store, &mut buf)
|
|
@@ -97,9 +102,17 @@ fn readStrResult(store: &mut wasmtime::Store<()>, val: &wasmtime::Val) -> String
|
|
|
97
102
|
#[test]
|
|
98
103
|
fn testBlocksCompileAndRun() {
|
|
99
104
|
let src = "\
|
|
105
|
+
import std/Str
|
|
106
|
+
import std/Int
|
|
107
|
+
import std/Float
|
|
108
|
+
|
|
100
109
|
fun add(a: Int, b: Int) -> Int =
|
|
101
110
|
a + b
|
|
102
111
|
|
|
112
|
+
fun hardAssertTrap() -> Int =
|
|
113
|
+
assert add(1, 1) == 99
|
|
114
|
+
0
|
|
115
|
+
|
|
103
116
|
test \"all pass\"
|
|
104
117
|
assert add(1, 2) == 3
|
|
105
118
|
assert add(2, 2) == 4
|
|
@@ -109,31 +122,29 @@ test \"one fails, rest still runs\"
|
|
|
109
122
|
assert add(1, 1) == 99
|
|
110
123
|
assert add(2, 2) == 4
|
|
111
124
|
|
|
112
|
-
fun hardAssertTrap() -> Int =
|
|
113
|
-
assert add(1, 1) == 99
|
|
114
|
-
0
|
|
115
|
-
|
|
116
125
|
test \"hard assert traps\"
|
|
117
126
|
assert hardAssertTrap() == 0
|
|
118
127
|
";
|
|
128
|
+
let dir = std::env::temp_dir().join(format!("plum_t3_testblocks_{}", std::process::id()));
|
|
129
|
+
std::fs::create_dir_all(&dir).unwrap();
|
|
130
|
+
let path = dir.join("main.plum");
|
|
119
|
-
|
|
131
|
+
std::fs::write(&path, src).unwrap();
|
|
132
|
+
let source = plum_core::loadAndMerge(&path, &repoRoot()).expect("load");
|
|
120
133
|
let (bytes, names) = compileTestSource(&source).expect("test source should compile");
|
|
121
134
|
let result = wasmparser::validate(&bytes);
|
|
122
135
|
assert!(result.is_ok(), "wasm validation failed: {:?}", result.err());
|
|
136
|
+
// The imported std modules contribute their own tests after this file's.
|
|
123
137
|
assert_eq!(
|
|
124
|
-
names,
|
|
138
|
+
names[..3],
|
|
125
|
-
vec![
|
|
126
|
-
"all pass",
|
|
127
|
-
|
|
139
|
+
["all pass", "one fails, rest still runs", "hard assert traps"]
|
|
128
|
-
"hard assert traps"
|
|
129
|
-
]
|
|
130
140
|
);
|
|
131
141
|
|
|
132
142
|
let engine = gcEngine();
|
|
133
143
|
let module = wasmtime::Module::new(&engine, &bytes).expect("module should be loadable");
|
|
134
144
|
let mut store = wasmtime::Store::new(&engine, ());
|
|
135
|
-
let
|
|
145
|
+
let mut linker = wasmtime::Linker::new(&engine);
|
|
146
|
+
linker.define_unknown_imports_as_default_values(&mut store, &module).unwrap();
|
|
136
|
-
|
|
147
|
+
let instance = linker.instantiate(&mut store, &module).expect("module should instantiate");
|
|
137
148
|
|
|
138
149
|
let allPass = instance
|
|
139
150
|
.get_func(&mut store, "test#all pass")
|
|
@@ -1057,6 +1068,10 @@ fn crash_import_not_triggered_by_string_literal() {
|
|
|
1057
1068
|
#[test]
|
|
1058
1069
|
fn catch_inside_string_interpolation_compiles() {
|
|
1059
1070
|
let src = "\
|
|
1071
|
+
import std/Str
|
|
1072
|
+
import std/Int
|
|
1073
|
+
import std/Float
|
|
1074
|
+
|
|
1060
1075
|
fun make() -> error{Nope}!Int =
|
|
1061
1076
|
return error.Nope
|
|
1062
1077
|
|
|
@@ -1064,7 +1079,7 @@ fun main() -> Int =
|
|
|
1064
1079
|
s := \"v={make() catch 5}\"
|
|
1065
1080
|
return 1
|
|
1066
1081
|
";
|
|
1067
|
-
assert_eq!(
|
|
1082
|
+
assert_eq!(runMerged::<i64>("catch_interp", src), 1);
|
|
1068
1083
|
}
|
|
1069
1084
|
|
|
1070
1085
|
// ---- mixed types via enums (T4) ----
|
|
@@ -1626,9 +1641,27 @@ fun main() -> Int =
|
|
|
1626
1641
|
let path = dir.join("main.plum");
|
|
1627
1642
|
std::fs::write(&path, src).unwrap();
|
|
1628
1643
|
let source = plum_core::loadAndMerge(&path, &repoRoot()).expect("load");
|
|
1629
|
-
// Same as any other method of a std type that was not imported: rejected by the checker
|
|
1644
|
+
// Same as any other method of a std type that was not imported: rejected by the checker.
|
|
1645
|
+
assert!(
|
|
1630
|
-
|
|
1646
|
+
plum_checker::checkSource(&source).is_err(),
|
|
1631
|
-
|
|
1647
|
+
"Bool.toStr without import std/Bool must be rejected by the checker"
|
|
1648
|
+
);
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
#[test]
|
|
1652
|
+
fn int_tostr_without_import_is_rejected() {
|
|
1653
|
+
// Unmerged source: nothing from std is loaded, so `Int` has no `toStr`.
|
|
1654
|
+
// (The loader itself also refuses a file that uses Int without importing it.)
|
|
1655
|
+
let src = "\
|
|
1656
|
+
fun main() -> Str =
|
|
1657
|
+
n := 5
|
|
1658
|
+
return n.toStr()
|
|
1659
|
+
";
|
|
1660
|
+
let source = parse(src);
|
|
1661
|
+
assert!(
|
|
1662
|
+
plum_checker::checkSource(&source).is_err(),
|
|
1663
|
+
"Int.toStr without import std/Int must be rejected by the checker"
|
|
1664
|
+
);
|
|
1632
1665
|
}
|
|
1633
1666
|
|
|
1634
1667
|
#[test]
|