From f64d1261f0bb43e2fc70772aa6ec334e8c38d432 Mon Sep 17 00:00:00 2001 From: Lexy Plt Date: Wed, 18 Mar 2026 17:51:02 +0100 Subject: [PATCH 1/2] feat: when using let/mut/set as expressions, load the variable as a copy and not a ref --- CHANGELOG.md | 1 + .../Compiler/Lowerer/ASTLowerer.cpp | 9 +--- .../CompilerSuite/ir/closures.expected | 2 +- .../optimized_ir/closures.expected | 2 +- .../CompilerSuite/optimized_ir/mul.expected | 2 +- .../runtime/call_let_mut_not_a_function.ark | 2 + .../call_let_mut_not_a_function.expected | 7 +++ .../resources/LangSuite/weird-tests.ark | 45 ++++++++++++++++--- 8 files changed, 53 insertions(+), 17 deletions(-) create mode 100644 tests/unittests/resources/DiagnosticsSuite/runtime/call_let_mut_not_a_function.ark create mode 100644 tests/unittests/resources/DiagnosticsSuite/runtime/call_let_mut_not_a_function.expected diff --git a/CHANGELOG.md b/CHANGELOG.md index dd45973f8..4054cf1ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ ### Changed - instruction counter in the bytecode reader are displayed in hex, and count each instruction instead of each byte +- `let` / `mut` / `set` push a copy of their value when used as expression (instead of an internal reference) ### Removed - removed a nearly never emitted `GET_CURRENT_PAGE_ADDR` instruction, since it's now always optimised with `CALL` into a `CALL_CURRENT_PAGE` instruction diff --git a/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp b/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp index c824800f6..124e1fb23 100644 --- a/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp +++ b/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp @@ -597,17 +597,12 @@ namespace Ark::internal { page(p).emplace_back(STORE, i); m_locals_locator.addLocal(name); - - if (!is_result_unused) - page(p).emplace_back(LOAD_FAST_BY_INDEX, 0); } else - { page(p).emplace_back(SET_VAL, i); - if (!is_result_unused) - page(p).emplace_back(LOAD_FAST, i); - } + if (!is_result_unused) + page(p).emplace_back(LOAD_SYMBOL, i); if (is_function) m_opened_vars.pop(); diff --git a/tests/unittests/resources/CompilerSuite/ir/closures.expected b/tests/unittests/resources/CompilerSuite/ir/closures.expected index 1446ff729..33a7cec5f 100644 --- a/tests/unittests/resources/CompilerSuite/ir/closures.expected +++ b/tests/unittests/resources/CompilerSuite/ir/closures.expected @@ -99,7 +99,7 @@ page_2 STORE 5 LOAD_FAST_BY_INDEX 0 SET_VAL 2 - LOAD_FAST 2 + LOAD_SYMBOL 2 RET 0 HALT 0 diff --git a/tests/unittests/resources/CompilerSuite/optimized_ir/closures.expected b/tests/unittests/resources/CompilerSuite/optimized_ir/closures.expected index 41d9dbe6c..10b488274 100644 --- a/tests/unittests/resources/CompilerSuite/optimized_ir/closures.expected +++ b/tests/unittests/resources/CompilerSuite/optimized_ir/closures.expected @@ -67,7 +67,7 @@ page_1 page_2 STORE 5 SET_VAL_FROM_INDEX 0, 2 - LOAD_FAST 2 + LOAD_SYMBOL 2 RET 0 HALT 0 diff --git a/tests/unittests/resources/CompilerSuite/optimized_ir/mul.expected b/tests/unittests/resources/CompilerSuite/optimized_ir/mul.expected index a8ba10dd3..4719494a3 100644 --- a/tests/unittests/resources/CompilerSuite/optimized_ir/mul.expected +++ b/tests/unittests/resources/CompilerSuite/optimized_ir/mul.expected @@ -18,6 +18,6 @@ page_1 STORE 2 MUL_SET_VAL 0, 2056 MUL_SET_VAL 0, 2056 - LOAD_FAST 0 + LOAD_SYMBOL 0 RET 0 HALT 0 diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/call_let_mut_not_a_function.ark b/tests/unittests/resources/DiagnosticsSuite/runtime/call_let_mut_not_a_function.ark new file mode 100644 index 000000000..39e1307c7 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/call_let_mut_not_a_function.ark @@ -0,0 +1,2 @@ +(mut output ((let T "hello") (mut T print))) +(print output) diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/call_let_mut_not_a_function.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/call_let_mut_not_a_function.expected new file mode 100644 index 000000000..93cacaba6 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/call_let_mut_not_a_function.expected @@ -0,0 +1,7 @@ +TypeError: hello is not a Function but a String + +In file tests/unittests/resources/DiagnosticsSuite/runtime/call_let_mut_not_a_function.ark:1 + 1 | (mut output ((let T "hello") (mut T print))) + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 2 | (print output) + 3 | diff --git a/tests/unittests/resources/LangSuite/weird-tests.ark b/tests/unittests/resources/LangSuite/weird-tests.ark index 233cecc82..db7dd5453 100644 --- a/tests/unittests/resources/LangSuite/weird-tests.ark +++ b/tests/unittests/resources/LangSuite/weird-tests.ark @@ -4,23 +4,54 @@ # just putting any and all weird code samples golfers come up with here, # to ensure what's fixed stay fixed +(let f1 (fun (e) (append! output e))) +(let f2 (fun (x y) (append! output x y))) +(let f4 (fun (a b c d) (append! output a b c d))) + (test:suite weird { (test:case "list:fill with let" { - (test:eq (list:fill (let x 2) x) [2 2]) }) + (test:eq (list:fill (let x 2) x) [2 2]) + (test:eq (builtin__list:fill (let y 3) y) [3 3 3]) }) (test:case "list:map with let" { (mut output []) - (let f (fun (e) (append! output e))) - (list:map (let a [0 1]) f) + (list:map (let a [0 1]) f1) (test:eq output [0 1]) }) - (test:case "declare var inside call" { + (test:case "(Q ((let Q f1) 0))" { # those append 0 to output first, due to the inner call, # then they append [0] since 'append!' returns the modified list (mut output []) - (Q ((let Q (fun (x) (append! output x))) 0)) + (let f1 (fun (x) (append! output x))) + (Q ((let Q f1) 0)) (test:eq output [0 [0]]) (set output []) - ((let P (fun (x) (append! output x))) (P 0)) - (test:eq output [0 [0]]) }) }) + ((let P f1) (P 0)) + (test:eq output [0 [0]]) }) + + (test:case "((let m list:map) (list:iota 0 5) f)" { + (mut output []) + ((let m list:map) (list:iota 0 5) f1) + (test:eq output [0 1 2 3 4]) + + (set output []) + (m (list:iota 0 5) f1) + (test:eq output [0 1 2 3 4]) }) + + (test:case "(R 6 [(let R f2) 2])" { + (mut output []) + (R 6 [(let R f2) 2]) + (test:eq output [6 [f2 2]]) }) + + (test:case "((@ [(let S f1) 2] 0) S)" { + (mut output []) + ((@ [(let S f1) 2] 0) S) + (test:eq output [f1]) }) + + # this is another insanity due to how pushing variables works: we use LOAD_FAST for `n`, which pushes a ref, + # and just after, (set n 2) modifies it + (test:case "(f4 (mut n 1) (+ 0 n) n (set n 2))" { + (mut output []) + (f4 (mut n 1) (+ 0 n) n (set n 2)) + (test:eq output [1 1 2 2]) }) }) From bab33aa9a5332a3a47e5f7f06d0b2ed5185c3d19 Mon Sep 17 00:00:00 2001 From: Lexy Plt Date: Wed, 18 Mar 2026 17:51:32 +0100 Subject: [PATCH 2/2] feat(vm): in type errors, display the bad values as code, to add the quotes around strings --- include/Ark/VM/VM.inl | 2 +- .../runtime/call_let_mut_not_a_function.expected | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/Ark/VM/VM.inl b/include/Ark/VM/VM.inl index ce95da894..7bd2b7b0b 100644 --- a/include/Ark/VM/VM.inl +++ b/include/Ark/VM/VM.inl @@ -325,7 +325,7 @@ inline void VM::call(internal::ExecutionContext& context, const uint16_t argc, V ErrorKind::Type, fmt::format( "{} is not a Function but a {}", - maybe_value_ptr->toString(*this), std::to_string(call_type))); + maybe_value_ptr->toString(*this, /* show_as_code= */ true), std::to_string(call_type))); } } diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/call_let_mut_not_a_function.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/call_let_mut_not_a_function.expected index 93cacaba6..26b1bbdd2 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/call_let_mut_not_a_function.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/call_let_mut_not_a_function.expected @@ -1,4 +1,4 @@ -TypeError: hello is not a Function but a String +TypeError: "hello" is not a Function but a String In file tests/unittests/resources/DiagnosticsSuite/runtime/call_let_mut_not_a_function.ark:1 1 | (mut output ((let T "hello") (mut T print)))