750 lines
31 KiB
Rust
750 lines
31 KiB
Rust
use std::{fs, path::Path, process::Command};
|
|
|
|
#[test]
|
|
fn option_result_fixture_emits_llvm_aggregate_shape() {
|
|
let output = run_glagol(["../examples/option-result.slo"]);
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
assert!(
|
|
output.status.success(),
|
|
"compiler rejected option/result fixture\nstdout:\n{}\nstderr:\n{}",
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stdout.contains("define { i1, i32 } @maybe_value()")
|
|
&& stdout.contains("define { i1, i32 } @maybe_empty()")
|
|
&& stdout.contains("define { i1, i64 } @maybe_wide_value()")
|
|
&& stdout.contains("define { i1, i64 } @maybe_wide_empty()")
|
|
&& stdout.contains("define { i1, double } @maybe_float_value()")
|
|
&& stdout.contains("define { i1, double } @maybe_float_empty()")
|
|
&& stdout.contains("define { i1, ptr } @maybe_string_value()")
|
|
&& stdout.contains("define { i1, ptr } @maybe_string_empty()")
|
|
&& stdout.contains("define { i1, i32 } @result_ok()")
|
|
&& stdout.contains("define { i1, i32 } @result_err()")
|
|
&& stdout.contains("insertvalue { i1, i32 } undef, i1 1, 0")
|
|
&& stdout.contains("insertvalue { i1, i32 } undef, i1 0, 0")
|
|
&& stdout.contains("insertvalue { i1, i64 } undef, i1 1, 0")
|
|
&& stdout.contains("insertvalue { i1, i64 } undef, i1 0, 0")
|
|
&& stdout.contains("insertvalue { i1, double } undef, i1 1, 0")
|
|
&& stdout.contains("insertvalue { i1, double } %")
|
|
&& stdout.contains("double 0.0")
|
|
&& stdout.contains("insertvalue { i1, ptr } undef, i1 1, 0")
|
|
&& stdout.contains("insertvalue { i1, ptr } undef, i1 0, 0")
|
|
&& stdout.contains("insertvalue { i1, i32 } %")
|
|
&& stdout.contains("insertvalue { i1, i64 } %")
|
|
&& stdout.contains("insertvalue { i1, ptr } %")
|
|
&& stdout.contains("ret { i1, i32 } %"),
|
|
"LLVM output did not contain expected option/result aggregate shape\nstdout:\n{}",
|
|
stdout
|
|
);
|
|
assert!(
|
|
!stdout.contains("define ptr @maybe_value")
|
|
&& !stdout.contains("define ptr @maybe_empty")
|
|
&& !stdout.contains("define ptr @maybe_string_value")
|
|
&& !stdout.contains("define ptr @maybe_string_empty")
|
|
&& !stdout.contains("define ptr @result_ok")
|
|
&& !stdout.contains("define ptr @result_err"),
|
|
"LLVM output used ptr fallback for option/result returns\nstdout:\n{}",
|
|
stdout
|
|
);
|
|
assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr);
|
|
}
|
|
|
|
#[test]
|
|
fn option_result_fixture_is_formatter_stable() {
|
|
let expected = fs::read_to_string("../tests/option-result.slo").expect("read fixture");
|
|
let output = run_glagol(["--format", "../tests/option-result.slo"]);
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
assert!(
|
|
output.status.success(),
|
|
"formatter rejected option/result fixture\nstdout:\n{}\nstderr:\n{}",
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert_eq!(stdout, expected, "formatter output drifted");
|
|
assert!(stderr.is_empty(), "formatter wrote stderr:\n{}", stderr);
|
|
}
|
|
|
|
#[test]
|
|
fn option_result_fixture_prints_lowered_shape() {
|
|
let surface = run_glagol([
|
|
"--inspect-lowering=surface",
|
|
"../examples/option-result.slo",
|
|
]);
|
|
let surface_stdout = String::from_utf8_lossy(&surface.stdout);
|
|
let surface_stderr = String::from_utf8_lossy(&surface.stderr);
|
|
assert!(
|
|
surface.status.success(),
|
|
"surface lowering rejected option/result fixture\nstdout:\n{}\nstderr:\n{}",
|
|
surface_stdout,
|
|
surface_stderr
|
|
);
|
|
assert!(
|
|
surface_stdout.contains("fn maybe_value() -> (option i32)")
|
|
&& surface_stdout.contains("fn maybe_wide_value() -> (option i64)")
|
|
&& surface_stdout.contains("fn maybe_string_value() -> (option string)")
|
|
&& surface_stdout.contains("some i32")
|
|
&& surface_stdout.contains("some i64")
|
|
&& surface_stdout.contains("some string")
|
|
&& surface_stdout.contains("none i32")
|
|
&& surface_stdout.contains("none i64")
|
|
&& surface_stdout.contains("none string")
|
|
&& surface_stdout.contains("ok i32 i32")
|
|
&& surface_stdout.contains("err i32 i32"),
|
|
"surface lowering output lost option/result shape\nstdout:\n{}",
|
|
surface_stdout
|
|
);
|
|
assert!(
|
|
surface_stderr.is_empty(),
|
|
"surface lowering wrote stderr:\n{}",
|
|
surface_stderr
|
|
);
|
|
|
|
let checked = run_glagol([
|
|
"--inspect-lowering=checked",
|
|
"../examples/option-result.slo",
|
|
]);
|
|
let checked_stdout = String::from_utf8_lossy(&checked.stdout);
|
|
let checked_stderr = String::from_utf8_lossy(&checked.stderr);
|
|
assert!(
|
|
checked.status.success(),
|
|
"checked lowering rejected option/result fixture\nstdout:\n{}\nstderr:\n{}",
|
|
checked_stdout,
|
|
checked_stderr
|
|
);
|
|
assert!(
|
|
checked_stdout.contains("some : (option i32)")
|
|
&& checked_stdout.contains("none : (option i32)")
|
|
&& checked_stdout.contains("some : (option i64)")
|
|
&& checked_stdout.contains("none : (option i64)")
|
|
&& checked_stdout.contains("some : (option string)")
|
|
&& checked_stdout.contains("none : (option string)")
|
|
&& checked_stdout.contains("ok : (result i32 i32)")
|
|
&& checked_stdout.contains("err : (result i32 i32)"),
|
|
"checked lowering output lost typed option/result shape\nstdout:\n{}",
|
|
checked_stdout
|
|
);
|
|
assert!(
|
|
checked_stderr.is_empty(),
|
|
"checked lowering wrote stderr:\n{}",
|
|
checked_stderr
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn option_result_flow_fixture_emits_llvm_value_flow_shape() {
|
|
let output = run_glagol(["../examples/option-result-flow.slo"]);
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
assert!(
|
|
output.status.success(),
|
|
"compiler rejected option/result flow fixture\nstdout:\n{}\nstderr:\n{}",
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stdout.contains("define i32 @option_score({ i1, i32 } %value)")
|
|
&& stdout.contains("define i32 @option_empty_score({ i1, i32 } %value)")
|
|
&& stdout.contains("define i32 @option_float_score({ i1, double } %value)")
|
|
&& stdout.contains("define i32 @option_float_empty_score({ i1, double } %value)")
|
|
&& stdout.contains("define i32 @option_string_score({ i1, ptr } %value)")
|
|
&& stdout.contains("define i32 @option_string_empty_score({ i1, ptr } %value)")
|
|
&& stdout.contains("define i32 @result_success_score({ i1, i32 } %value)")
|
|
&& stdout.contains("define i32 @result_failure_score({ i1, i32 } %value)")
|
|
&& stdout.contains("%value.addr = alloca { i1, i32 }")
|
|
&& stdout.contains("store { i1, i32 } %")
|
|
&& stdout.contains("load { i1, i32 }, ptr %value.addr")
|
|
&& stdout.contains("extractvalue { i1, i32 } %")
|
|
&& stdout.contains("%value.addr = alloca { i1, double }")
|
|
&& stdout.contains("store { i1, double } %")
|
|
&& stdout.contains("load { i1, double }, ptr %value.addr")
|
|
&& stdout.contains("extractvalue { i1, double } %")
|
|
&& stdout.contains("%value.addr = alloca { i1, ptr }")
|
|
&& stdout.contains("store { i1, ptr } %")
|
|
&& stdout.contains("load { i1, ptr }, ptr %value.addr")
|
|
&& stdout.contains("extractvalue { i1, ptr } %")
|
|
&& stdout.contains("xor i1 %"),
|
|
"LLVM output did not contain expected option/result value-flow shape\nstdout:\n{}",
|
|
stdout
|
|
);
|
|
assert!(
|
|
!stdout.contains("define ptr @option_score")
|
|
&& !stdout.contains("define ptr @result_success_score"),
|
|
"LLVM output used ptr fallback for option/result params\nstdout:\n{}",
|
|
stdout
|
|
);
|
|
assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr);
|
|
}
|
|
|
|
#[test]
|
|
fn option_result_flow_fixture_runs_top_level_tests() {
|
|
let output = run_glagol(["--run-tests", "../examples/option-result-flow.slo"]);
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
assert!(
|
|
output.status.success(),
|
|
"test runner rejected option/result flow fixture\nstdout:\n{}\nstderr:\n{}",
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert_eq!(
|
|
stdout,
|
|
concat!(
|
|
"test \"option local value flow\" ... ok\n",
|
|
"test \"option call observation\" ... ok\n",
|
|
"test \"option i64 local value flow\" ... ok\n",
|
|
"test \"option i64 call observation\" ... ok\n",
|
|
"test \"option f64 local value flow\" ... ok\n",
|
|
"test \"option f64 call observation\" ... ok\n",
|
|
"test \"option bool local value flow\" ... ok\n",
|
|
"test \"option bool call observation\" ... ok\n",
|
|
"test \"option string local value flow\" ... ok\n",
|
|
"test \"option string call observation\" ... ok\n",
|
|
"test \"result call observation\" ... ok\n",
|
|
"test \"result local value flow\" ... ok\n",
|
|
"12 test(s) passed\n",
|
|
)
|
|
);
|
|
assert!(stderr.is_empty(), "test runner wrote stderr:\n{}", stderr);
|
|
}
|
|
|
|
#[test]
|
|
fn option_result_flow_fixture_is_formatter_stable() {
|
|
let expected = fs::read_to_string("../tests/option-result-flow.slo").expect("read fixture");
|
|
let output = run_glagol(["--format", "../tests/option-result-flow.slo"]);
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
assert!(
|
|
output.status.success(),
|
|
"formatter rejected option/result flow fixture\nstdout:\n{}\nstderr:\n{}",
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert_eq!(stdout, expected, "formatter output drifted");
|
|
assert!(stderr.is_empty(), "formatter wrote stderr:\n{}", stderr);
|
|
}
|
|
|
|
#[test]
|
|
fn option_result_flow_fixture_prints_lowered_shape() {
|
|
let surface = run_glagol([
|
|
"--inspect-lowering=surface",
|
|
"../examples/option-result-flow.slo",
|
|
]);
|
|
let surface_stdout = String::from_utf8_lossy(&surface.stdout);
|
|
let surface_stderr = String::from_utf8_lossy(&surface.stderr);
|
|
assert!(
|
|
surface.status.success(),
|
|
"surface lowering rejected option/result flow fixture\nstdout:\n{}\nstderr:\n{}",
|
|
surface_stdout,
|
|
surface_stderr
|
|
);
|
|
assert!(
|
|
surface_stdout.contains("fn option_score(value: (option i32)) -> i32")
|
|
&& surface_stdout.contains("fn option_wide_score(value: (option i64)) -> i32")
|
|
&& surface_stdout.contains("fn option_string_score(value: (option string)) -> i32")
|
|
&& surface_stdout.contains("is_some")
|
|
&& surface_stdout.contains("is_none")
|
|
&& surface_stdout.contains("is_ok")
|
|
&& surface_stdout.contains("is_err"),
|
|
"surface lowering output lost option/result flow shape\nstdout:\n{}",
|
|
surface_stdout
|
|
);
|
|
assert!(
|
|
surface_stderr.is_empty(),
|
|
"surface lowering wrote stderr:\n{}",
|
|
surface_stderr
|
|
);
|
|
|
|
let checked = run_glagol([
|
|
"--inspect-lowering=checked",
|
|
"../examples/option-result-flow.slo",
|
|
]);
|
|
let checked_stdout = String::from_utf8_lossy(&checked.stdout);
|
|
let checked_stderr = String::from_utf8_lossy(&checked.stderr);
|
|
assert!(
|
|
checked.status.success(),
|
|
"checked lowering rejected option/result flow fixture\nstdout:\n{}\nstderr:\n{}",
|
|
checked_stdout,
|
|
checked_stderr
|
|
);
|
|
assert!(
|
|
checked_stdout.contains("is_some : bool")
|
|
&& checked_stdout.contains("is_none : bool")
|
|
&& checked_stdout.contains("is_ok : bool")
|
|
&& checked_stdout.contains("is_err : bool")
|
|
&& checked_stdout.contains("call maybe_wide_value : (option i64)")
|
|
&& checked_stdout.contains("call maybe_string_value : (option string)")
|
|
&& checked_stdout.contains("var value : (option i64)")
|
|
&& checked_stdout.contains("var value : (option string)")
|
|
&& checked_stdout.contains("local let value : unit"),
|
|
"checked lowering output lost typed option/result flow shape\nstdout:\n{}",
|
|
checked_stdout
|
|
);
|
|
assert!(
|
|
checked_stderr.is_empty(),
|
|
"checked lowering wrote stderr:\n{}",
|
|
checked_stderr
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn option_result_payload_fixture_emits_llvm_unwrap_shape() {
|
|
let output = run_glagol(["../examples/option-result-payload.slo"]);
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
assert!(
|
|
output.status.success(),
|
|
"compiler rejected option/result payload fixture\nstdout:\n{}\nstderr:\n{}",
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stdout.contains("declare void @__glagol_unwrap_some_trap()")
|
|
&& stdout.contains("declare void @__glagol_unwrap_ok_trap()")
|
|
&& stdout.contains("declare void @__glagol_unwrap_err_trap()")
|
|
&& stdout.contains("define i32 @option_direct_payload()")
|
|
&& stdout.contains("define i32 @option_param_payload({ i1, i32 } %value)")
|
|
&& stdout.contains("define i32 @option_local_payload()")
|
|
&& stdout.contains("define i32 @option_call_payload()")
|
|
&& stdout.contains("define i32 @option_guarded_payload({ i1, i32 } %value)")
|
|
&& stdout.contains("define ptr @option_string_direct_payload()")
|
|
&& stdout.contains("define ptr @option_string_param_payload({ i1, ptr } %value)")
|
|
&& stdout.contains("define ptr @option_string_local_payload()")
|
|
&& stdout.contains("define ptr @option_string_call_payload()")
|
|
&& stdout.contains("define ptr @option_string_guarded_payload({ i1, ptr } %value)")
|
|
&& stdout.contains("define i32 @result_ok_direct_payload()")
|
|
&& stdout.contains("define i32 @result_err_direct_payload()")
|
|
&& stdout.contains("define i32 @result_ok_param_payload({ i1, i32 } %value)")
|
|
&& stdout.contains("define i32 @result_err_param_payload({ i1, i32 } %value)")
|
|
&& stdout.contains("define i32 @result_ok_local_payload()")
|
|
&& stdout.contains("define i32 @result_err_call_payload()")
|
|
&& stdout.contains("call void @__glagol_unwrap_some_trap()")
|
|
&& stdout.contains("call void @__glagol_unwrap_ok_trap()")
|
|
&& stdout.contains("call void @__glagol_unwrap_err_trap()")
|
|
&& stdout.contains("unwrap.trap")
|
|
&& stdout.contains("if.then")
|
|
&& stdout.contains(" phi i32 ")
|
|
&& stdout.contains(" phi ptr ")
|
|
&& stdout.contains("extractvalue { i1, i32 } %"),
|
|
"LLVM output did not contain expected option/result payload-access shape\nstdout:\n{}",
|
|
stdout
|
|
);
|
|
assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr);
|
|
}
|
|
|
|
#[test]
|
|
fn option_result_payload_fixture_runs_top_level_tests() {
|
|
let output = run_glagol(["--run-tests", "../examples/option-result-payload.slo"]);
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
assert!(
|
|
output.status.success(),
|
|
"test runner rejected option/result payload fixture\nstdout:\n{}\nstderr:\n{}",
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert_eq!(
|
|
stdout,
|
|
concat!(
|
|
"test \"unwrap some direct constructor\" ... ok\n",
|
|
"test \"unwrap some local\" ... ok\n",
|
|
"test \"unwrap some call\" ... ok\n",
|
|
"test \"unwrap some parameter\" ... ok\n",
|
|
"test \"guarded unwrap some present\" ... ok\n",
|
|
"test \"guarded unwrap some absent\" ... ok\n",
|
|
"test \"unwrap some i64 direct constructor\" ... ok\n",
|
|
"test \"unwrap some i64 local\" ... ok\n",
|
|
"test \"unwrap some i64 call\" ... ok\n",
|
|
"test \"unwrap some i64 parameter\" ... ok\n",
|
|
"test \"guarded unwrap some i64 present\" ... ok\n",
|
|
"test \"guarded unwrap some i64 absent\" ... ok\n",
|
|
"test \"unwrap some f64 direct constructor\" ... ok\n",
|
|
"test \"unwrap some f64 local\" ... ok\n",
|
|
"test \"unwrap some f64 call\" ... ok\n",
|
|
"test \"unwrap some f64 parameter\" ... ok\n",
|
|
"test \"guarded unwrap some f64 present\" ... ok\n",
|
|
"test \"guarded unwrap some f64 absent\" ... ok\n",
|
|
"test \"unwrap some bool direct constructor\" ... ok\n",
|
|
"test \"unwrap some bool local\" ... ok\n",
|
|
"test \"unwrap some bool call\" ... ok\n",
|
|
"test \"unwrap some bool parameter\" ... ok\n",
|
|
"test \"guarded unwrap some bool present\" ... ok\n",
|
|
"test \"guarded unwrap some bool absent\" ... ok\n",
|
|
"test \"unwrap some string direct constructor\" ... ok\n",
|
|
"test \"unwrap some string local\" ... ok\n",
|
|
"test \"unwrap some string call\" ... ok\n",
|
|
"test \"unwrap some string parameter\" ... ok\n",
|
|
"test \"guarded unwrap some string present\" ... ok\n",
|
|
"test \"guarded unwrap some string absent\" ... ok\n",
|
|
"test \"unwrap ok direct constructor\" ... ok\n",
|
|
"test \"unwrap err direct constructor\" ... ok\n",
|
|
"test \"unwrap ok parameter\" ... ok\n",
|
|
"test \"unwrap err parameter\" ... ok\n",
|
|
"test \"unwrap ok local\" ... ok\n",
|
|
"test \"unwrap err call\" ... ok\n",
|
|
"36 test(s) passed\n",
|
|
)
|
|
);
|
|
assert!(stderr.is_empty(), "test runner wrote stderr:\n{}", stderr);
|
|
}
|
|
|
|
#[test]
|
|
fn option_result_payload_fixture_is_formatter_stable() {
|
|
let expected = fs::read_to_string("../tests/option-result-payload.slo").expect("read fixture");
|
|
let output = run_glagol(["--format", "../tests/option-result-payload.slo"]);
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
assert!(
|
|
output.status.success(),
|
|
"formatter rejected option/result payload fixture\nstdout:\n{}\nstderr:\n{}",
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert_eq!(stdout, expected, "formatter output drifted");
|
|
assert!(stderr.is_empty(), "formatter wrote stderr:\n{}", stderr);
|
|
}
|
|
|
|
#[test]
|
|
fn option_result_payload_fixture_prints_lowered_shape() {
|
|
let surface = run_glagol([
|
|
"--inspect-lowering=surface",
|
|
"../examples/option-result-payload.slo",
|
|
]);
|
|
let surface_stdout = String::from_utf8_lossy(&surface.stdout);
|
|
let surface_stderr = String::from_utf8_lossy(&surface.stderr);
|
|
assert!(
|
|
surface.status.success(),
|
|
"surface lowering rejected option/result payload fixture\nstdout:\n{}\nstderr:\n{}",
|
|
surface_stdout,
|
|
surface_stderr
|
|
);
|
|
assert!(
|
|
surface_stdout.contains("fn option_direct_payload() -> i32")
|
|
&& surface_stdout.contains("fn option_param_payload(value: (option i32)) -> i32")
|
|
&& surface_stdout.contains("fn option_guarded_payload(value: (option i32)) -> i32")
|
|
&& surface_stdout.contains("fn option_wide_direct_payload() -> i64")
|
|
&& surface_stdout.contains("fn option_wide_param_payload(value: (option i64)) -> i64")
|
|
&& surface_stdout
|
|
.contains("fn option_wide_guarded_payload(value: (option i64)) -> i64")
|
|
&& surface_stdout.contains("fn option_string_direct_payload() -> string")
|
|
&& surface_stdout
|
|
.contains("fn option_string_param_payload(value: (option string)) -> string")
|
|
&& surface_stdout
|
|
.contains("fn option_string_guarded_payload(value: (option string)) -> string")
|
|
&& surface_stdout.contains("fn result_ok_direct_payload() -> i32")
|
|
&& surface_stdout
|
|
.contains("fn result_err_param_payload(value: (result i32 i32)) -> i32")
|
|
&& surface_stdout.contains("is_some")
|
|
&& surface_stdout.contains("unwrap_some")
|
|
&& surface_stdout.contains("unwrap_ok")
|
|
&& surface_stdout.contains("unwrap_err"),
|
|
"surface lowering output lost option/result payload shape\nstdout:\n{}",
|
|
surface_stdout
|
|
);
|
|
assert!(
|
|
surface_stderr.is_empty(),
|
|
"surface lowering wrote stderr:\n{}",
|
|
surface_stderr
|
|
);
|
|
|
|
let checked = run_glagol([
|
|
"--inspect-lowering=checked",
|
|
"../examples/option-result-payload.slo",
|
|
]);
|
|
let checked_stdout = String::from_utf8_lossy(&checked.stdout);
|
|
let checked_stderr = String::from_utf8_lossy(&checked.stderr);
|
|
assert!(
|
|
checked.status.success(),
|
|
"checked lowering rejected option/result payload fixture\nstdout:\n{}\nstderr:\n{}",
|
|
checked_stdout,
|
|
checked_stderr
|
|
);
|
|
assert!(
|
|
checked_stdout.contains("unwrap_some : i32")
|
|
&& checked_stdout.contains("unwrap_some : i64")
|
|
&& checked_stdout.contains("unwrap_some : string")
|
|
&& checked_stdout.contains("unwrap_ok : i32")
|
|
&& checked_stdout.contains("unwrap_err : i32")
|
|
&& checked_stdout.contains("some : (option i32)")
|
|
&& checked_stdout.contains("some : (option i64)")
|
|
&& checked_stdout.contains("some : (option string)")
|
|
&& checked_stdout.contains("err : (result i32 i32)")
|
|
&& checked_stdout.contains("is_some : bool"),
|
|
"checked lowering output lost typed option/result payload shape\nstdout:\n{}",
|
|
checked_stdout
|
|
);
|
|
assert!(
|
|
checked_stderr.is_empty(),
|
|
"checked lowering wrote stderr:\n{}",
|
|
checked_stderr
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn option_result_payload_llvm_traps_before_extracting_payload() {
|
|
let output = run_glagol(["../examples/option-result-payload.slo"]);
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
assert!(
|
|
output.status.success(),
|
|
"compiler rejected option/result payload fixture\nstdout:\n{}\nstderr:\n{}",
|
|
stdout,
|
|
stderr
|
|
);
|
|
|
|
assert_unwrap_traps_before_payload_extract(
|
|
&function_body(
|
|
&stdout,
|
|
"define i32 @option_param_payload({ i1, i32 } %value)",
|
|
),
|
|
false,
|
|
);
|
|
assert_unwrap_traps_before_payload_extract(
|
|
&function_body(
|
|
&stdout,
|
|
"define ptr @option_string_param_payload({ i1, ptr } %value)",
|
|
),
|
|
false,
|
|
);
|
|
assert_unwrap_traps_before_payload_extract(
|
|
&function_body(
|
|
&stdout,
|
|
"define i32 @result_ok_param_payload({ i1, i32 } %value)",
|
|
),
|
|
false,
|
|
);
|
|
assert_unwrap_traps_before_payload_extract(
|
|
&function_body(
|
|
&stdout,
|
|
"define i32 @result_err_param_payload({ i1, i32 } %value)",
|
|
),
|
|
true,
|
|
);
|
|
assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr);
|
|
}
|
|
|
|
#[test]
|
|
fn option_result_match_fixture_emits_llvm_branch_shape() {
|
|
let output = run_glagol(["../examples/option-result-match.slo"]);
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
assert!(
|
|
output.status.success(),
|
|
"compiler rejected option/result match fixture\nstdout:\n{}\nstderr:\n{}",
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stdout.contains("define i32 @option_value_or({ i1, i32 } %value, i32 %fallback)")
|
|
&& stdout.contains("define i32 @option_bump_or_zero({ i1, i32 } %value)")
|
|
&& stdout
|
|
.contains("define ptr @option_string_value_or({ i1, ptr } %value, ptr %fallback)")
|
|
&& stdout.contains(
|
|
"define ptr @option_string_selected_or({ i1, ptr } %value, ptr %fallback)"
|
|
)
|
|
&& stdout.contains("define i32 @result_value_or_code({ i1, i32 } %value)")
|
|
&& stdout.contains("define i32 @result_score({ i1, i32 } %value)")
|
|
&& stdout.contains("match.some")
|
|
&& stdout.contains("match.none")
|
|
&& stdout.contains("match.ok")
|
|
&& stdout.contains("match.err")
|
|
&& stdout.contains("match.end")
|
|
&& stdout.contains("br i1")
|
|
&& stdout.contains(" phi i32 ")
|
|
&& stdout.contains(" phi ptr ")
|
|
&& stdout.contains("extractvalue { i1, i32 } %"),
|
|
"LLVM output did not contain expected match branch shape\nstdout:\n{}",
|
|
stdout
|
|
);
|
|
assert!(
|
|
!function_body(
|
|
&stdout,
|
|
"define i32 @option_value_or({ i1, i32 } %value, i32 %fallback)",
|
|
)
|
|
.contains("__glagol_unwrap"),
|
|
"match lowering must not call unwrap traps\nstdout:\n{}",
|
|
stdout
|
|
);
|
|
assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr);
|
|
}
|
|
|
|
#[test]
|
|
fn option_result_match_fixture_runs_top_level_tests() {
|
|
let output = run_glagol(["--run-tests", "../examples/option-result-match.slo"]);
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
assert!(
|
|
output.status.success(),
|
|
"test runner rejected option/result match fixture\nstdout:\n{}\nstderr:\n{}",
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert_eq!(
|
|
stdout,
|
|
concat!(
|
|
"test \"option match some payload\" ... ok\n",
|
|
"test \"option match none fallback\" ... ok\n",
|
|
"test \"option match multi expression arm\" ... ok\n",
|
|
"test \"option i64 match some payload\" ... ok\n",
|
|
"test \"option i64 match none fallback\" ... ok\n",
|
|
"test \"option i64 match multi expression arm\" ... ok\n",
|
|
"test \"option f64 match some payload\" ... ok\n",
|
|
"test \"option f64 match none fallback\" ... ok\n",
|
|
"test \"option f64 match multi expression arm\" ... ok\n",
|
|
"test \"option bool match some payload\" ... ok\n",
|
|
"test \"option bool match none fallback\" ... ok\n",
|
|
"test \"option bool match multi expression arm\" ... ok\n",
|
|
"test \"option string match some payload\" ... ok\n",
|
|
"test \"option string match none fallback\" ... ok\n",
|
|
"test \"option string match multi expression arm\" ... ok\n",
|
|
"test \"result match ok payload\" ... ok\n",
|
|
"test \"result match err payload\" ... ok\n",
|
|
"test \"result match computed arm\" ... ok\n",
|
|
"18 test(s) passed\n",
|
|
)
|
|
);
|
|
assert!(stderr.is_empty(), "test runner wrote stderr:\n{}", stderr);
|
|
}
|
|
|
|
#[test]
|
|
fn option_result_match_fixture_is_formatter_stable() {
|
|
let expected = fs::read_to_string("../tests/option-result-match.slo").expect("read fixture");
|
|
let output = run_glagol(["--format", "../tests/option-result-match.slo"]);
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
assert!(
|
|
output.status.success(),
|
|
"formatter rejected option/result match fixture\nstdout:\n{}\nstderr:\n{}",
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert_eq!(stdout, expected, "formatter output drifted");
|
|
assert!(stderr.is_empty(), "formatter wrote stderr:\n{}", stderr);
|
|
}
|
|
|
|
#[test]
|
|
fn option_result_match_fixture_prints_lowered_shape() {
|
|
let surface = run_glagol([
|
|
"--inspect-lowering=surface",
|
|
"../examples/option-result-match.slo",
|
|
]);
|
|
let surface_stdout = String::from_utf8_lossy(&surface.stdout);
|
|
let surface_stderr = String::from_utf8_lossy(&surface.stderr);
|
|
assert!(
|
|
surface.status.success(),
|
|
"surface lowering rejected option/result match fixture\nstdout:\n{}\nstderr:\n{}",
|
|
surface_stdout,
|
|
surface_stderr
|
|
);
|
|
assert!(
|
|
surface_stdout.contains("match")
|
|
&& surface_stdout.contains("subject")
|
|
&& surface_stdout.contains("arm some payload")
|
|
&& surface_stdout.contains("arm none")
|
|
&& surface_stdout.contains("arm ok payload")
|
|
&& surface_stdout.contains("arm err code"),
|
|
"surface lowering output lost match shape\nstdout:\n{}",
|
|
surface_stdout
|
|
);
|
|
assert!(
|
|
surface_stderr.is_empty(),
|
|
"surface lowering wrote stderr:\n{}",
|
|
surface_stderr
|
|
);
|
|
|
|
let checked = run_glagol([
|
|
"--inspect-lowering=checked",
|
|
"../examples/option-result-match.slo",
|
|
]);
|
|
let checked_stdout = String::from_utf8_lossy(&checked.stdout);
|
|
let checked_stderr = String::from_utf8_lossy(&checked.stderr);
|
|
assert!(
|
|
checked.status.success(),
|
|
"checked lowering rejected option/result match fixture\nstdout:\n{}\nstderr:\n{}",
|
|
checked_stdout,
|
|
checked_stderr
|
|
);
|
|
assert!(
|
|
checked_stdout.contains("match : i32")
|
|
&& checked_stdout.contains("var payload : i32")
|
|
&& checked_stdout.contains("var payload : i64")
|
|
&& checked_stdout.contains("var fallback : i32")
|
|
&& checked_stdout.contains("var fallback : i64")
|
|
&& checked_stdout.contains("var code : i32"),
|
|
"checked lowering output lost typed match shape\nstdout:\n{}",
|
|
checked_stdout
|
|
);
|
|
assert!(
|
|
checked_stderr.is_empty(),
|
|
"checked lowering wrote stderr:\n{}",
|
|
checked_stderr
|
|
);
|
|
}
|
|
|
|
fn function_body<'a>(ir: &'a str, signature: &str) -> &'a str {
|
|
let start = ir
|
|
.find(signature)
|
|
.unwrap_or_else(|| panic!("missing function signature `{}`\n{}", signature, ir));
|
|
let rest = &ir[start..];
|
|
let end = rest
|
|
.find("\n}\n")
|
|
.unwrap_or_else(|| panic!("missing function end for `{}`", signature));
|
|
&rest[..end]
|
|
}
|
|
|
|
fn assert_unwrap_traps_before_payload_extract(body: &str, expects_inverted_tag: bool) {
|
|
let tag_extract = body
|
|
.find(", 0")
|
|
.unwrap_or_else(|| panic!("missing tag extract in function body:\n{}", body));
|
|
let branch = body
|
|
.find("br i1")
|
|
.unwrap_or_else(|| panic!("missing unwrap branch in function body:\n{}", body));
|
|
let trap_call = body
|
|
.find("call void @__glagol_unwrap")
|
|
.unwrap_or_else(|| panic!("missing unwrap trap call in function body:\n{}", body));
|
|
let payload_extract = body
|
|
.rfind(", 1")
|
|
.unwrap_or_else(|| panic!("missing payload extract in function body:\n{}", body));
|
|
|
|
assert!(
|
|
tag_extract < branch && branch < trap_call && trap_call < payload_extract,
|
|
"unwrap payload extraction was not guarded by a prior tag trap path\n{}",
|
|
body
|
|
);
|
|
assert!(
|
|
body.contains("unreachable"),
|
|
"unwrap trap block must terminate with unreachable\n{}",
|
|
body
|
|
);
|
|
|
|
if expects_inverted_tag {
|
|
assert!(
|
|
body.contains("icmp eq i1"),
|
|
"unwrap_err must branch on the inverted result tag\n{}",
|
|
body
|
|
);
|
|
}
|
|
}
|
|
|
|
fn run_glagol<const N: usize>(args: [&str; N]) -> std::process::Output {
|
|
Command::new(env!("CARGO_BIN_EXE_glagol"))
|
|
.args(args)
|
|
.current_dir(Path::new(env!("CARGO_MANIFEST_DIR")))
|
|
.output()
|
|
.expect("run glagol")
|
|
}
|