From 869bcf8b2bce0bde992e24f11f0ee742d04a6e1f Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Mon, 21 Jul 2025 03:20:40 +0200 Subject: [PATCH] Removing use of `$assignable` and deprecate it. Fix regression for stacktraces on MacOS. Added readline_to_stream. Regression: Chaining an optional together with contracts could in some cases lose the optional. --- lib/std/io/io.c3 | 92 +++++++++++++------ lib/std/os/macos/darwin.c3 | 4 +- releasenotes.md | 2 + src/compiler/llvm_codegen_expr.c | 3 +- src/compiler/llvm_codegen_stmt.c | 8 +- test/src/test_suite_runner.c3 | 5 +- test/test_suite/errors/rethrow_macro.c3 | 2 +- .../expressions/casts/void_casting.c3t | 1 - .../generic/generic_lambda_complex.c3t | 2 +- .../switch/switch_in_defer_macro.c3t | 2 +- test/unit/regression/castable_assignable.c3 | 16 ++-- test/unit/stdlib/collections/copy_map.c3 | 2 +- test/unit/stdlib/collections/object.c3 | 8 +- test/unit/stdlib/core/mem_allocator.c3 | 8 +- test/unit/stdlib/io/path.c3 | 16 ++-- test/unit/stdlib/io/stream.c3 | 4 +- test/unit/stdlib/math/bigint.c3 | 6 +- test/unit/stdlib/mem/temp_mem.c3 | 4 +- 18 files changed, 116 insertions(+), 69 deletions(-) diff --git a/lib/std/io/io.c3 b/lib/std/io/io.c3 index 57c340388..b48f4fddd 100644 --- a/lib/std/io/io.c3 +++ b/lib/std/io/io.c3 @@ -57,35 +57,16 @@ faultdef *> macro String? readline(Allocator allocator, stream = io::stdin()) { - bool $is_stream = @typeis(stream, InStream); - $if $is_stream: - $typeof(&stream.read_byte) func = &stream.read_byte; - char val = func((void*)stream)!; - $else - char val = stream.read_byte()!; - $endif - - if (val == '\n') return ""; + if (allocator == tmem) + { + DString str = dstring::temp_with_capacity(256); + readline_to_stream(&str, stream)!; + return str.str_view(); + } @pool() { DString str = dstring::temp_with_capacity(256); - if (val != '\r') str.append(val); - while (1) - { - $if $is_stream: - char? c = func((void*)stream); - $else - char? c = stream.read_byte(); - $endif - if (catch err = c) - { - if (err == io::EOF) break; - return err?; - } - if (c == '\r') continue; - if (c == '\n') break; - str.append_char(c); - } + readline_to_stream(&str, stream)!; return str.copy_str(allocator); }; } @@ -104,6 +85,65 @@ macro String? treadline(stream = io::stdin()) return readline(tmem, stream) @inline; } +<* + Reads a string, see `readline`, the data is passed to an outstream + + @param out_stream : `The stream to write to` + @param in_stream : `The stream to read from.` + @require !($defined(&in_stream) &&& @is_instream(&in_stream)) : "The value for 'in_stream' should have been passed as a pointer and not as a value, please add '&'." + @require !($defined(&out_stream) &&& @is_outstream(&out_stream)) : "The value for 'out_stream' should have been passed as a pointer and not as a value, please add '&'." + @require @is_instream(in_stream) : `The in_stream must implement InStream.` + @require @is_outstream(out_stream) : `The out_stream must implement OutStream.` + @return `The number of bytes written` +*> +macro usz? readline_to_stream(out_stream, in_stream = io::stdin()) +{ + bool $is_stream = @typeis(in_stream, InStream); + $if $is_stream: + var func = &in_stream.read_byte; + char val = func((void*)in_stream)!; + $else + char val = in_stream.read_byte()!; + $endif + bool $is_out_stream = @typeis(out_stream, OutStream); + $if $is_out_stream: + var out_func = &out_stream.write_byte; + $endif + + if (val == '\n') return 0; + usz len; + if (val != '\r') + { + $if $is_out_stream: + out_func((void*)out_stream.ptr, val)!; + $else + out_stream.write_byte(val)!; + $endif + len++; + } + while (1) + { + $if $is_stream: + char? c = func((void*)in_stream); + $else + char? c = in_stream.read_byte(); + $endif + if (catch err = c) + { + if (err == io::EOF) break; + return err?; + } + if (c == '\r') continue; + if (c == '\n') break; + $if $is_out_stream: + out_func((void*)out_stream.ptr, c)!; + $else + out_stream.write_byte(c)!; + $endif + len++; + } + return len; +} <* Print a value to a stream. diff --git a/lib/std/os/macos/darwin.c3 b/lib/std/os/macos/darwin.c3 index 0ca2f97b2..b2b92c79c 100644 --- a/lib/std/os/macos/darwin.c3 +++ b/lib/std/os/macos/darwin.c3 @@ -88,8 +88,8 @@ fn String? executable_path() char[4096] buf; uint temp_len = buf.len; if (darwin_NSGetExecutablePath(&buf, &temp_len) < 0) return NOT_FOUND?; - path[:temp_len] = buf[:temp_len]; - len = temp_len; + path[:len] = buf[:len]; + len = (int)((ZString)&buf).len(); } return (String)path[:len]; } diff --git a/releasenotes.md b/releasenotes.md index 6ce94f5b4..eb70e5c1a 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -70,6 +70,7 @@ - A distinct type based on an array would yield .len == 0 - Overloading addition with a pointer would not work. - Copying const enums and regular enums incorrect #2313. +- Regression: Chaining an optional together with contracts could in some cases lose the optional. ### Stdlib changes - Improve contract for readline. #2280 @@ -81,6 +82,7 @@ - Added `WString.len`. - Added `@addr` macro. - Add `ConditionVariable.wait_until` and `ConditionVariable.wait_for` +- Added readline_to_stream that takes a stream. ## 0.7.3 Change list diff --git a/src/compiler/llvm_codegen_expr.c b/src/compiler/llvm_codegen_expr.c index 6704bcac1..abee7fdef 100644 --- a/src/compiler/llvm_codegen_expr.c +++ b/src/compiler/llvm_codegen_expr.c @@ -1359,14 +1359,13 @@ void llvm_emit_ignored_expr(GenContext *c, Expr *expr) LLVMBasicBlockRef discard_fail = llvm_basic_block_new(c, "voiderr"); PUSH_CATCH_VAR_BLOCK(NULL, discard_fail); llvm_emit_expr(c, &value, expr); - llvm_value_fold_optional(c, &value); EMIT_EXPR_LOC(c, expr); // We only optimize if there is no instruction the current block if (!LLVMGetFirstInstruction(c->current_block)) { llvm_prune_optional(c, discard_fail); } - else + else if (!llvm_basic_block_is_unused(discard_fail)) { llvm_emit_br(c, discard_fail); llvm_emit_block(c, discard_fail); diff --git a/src/compiler/llvm_codegen_stmt.c b/src/compiler/llvm_codegen_stmt.c index ef5fded38..a802008ba 100644 --- a/src/compiler/llvm_codegen_stmt.c +++ b/src/compiler/llvm_codegen_stmt.c @@ -142,9 +142,15 @@ void llvm_emit_local_decl(GenContext *c, Decl *decl, BEValue *value) if (init) { llvm_value_set_decl_address(c, value, decl); + // Pretend to be normal. value->kind = BE_ADDRESS; BEValue res = llvm_emit_assign_expr(c, value, decl->var.init_expr, decl->var.optional_ref, true); - if (!is_optional && res.value) *value = res; + if (!is_optional && res.value) + { + *value = res; + return; + } + if (is_optional) value->kind = BE_ADDRESS_OPTIONAL; return; } diff --git a/test/src/test_suite_runner.c3 b/test/src/test_suite_runner.c3 index da4331db4..b3ec3423e 100644 --- a/test/src/test_suite_runner.c3 +++ b/test/src/test_suite_runner.c3 @@ -11,7 +11,8 @@ Path test_dir; bool print_to_file; String stdlib; -fn void main(String[] args) + +fn int main(String[] args) { // Grab the name for `usage` String appname = args[0]; @@ -86,7 +87,7 @@ fn void main(String[] args) io::printfn("Found %d tests: %.1f%% (%d / %d) passed (%d skipped).", test_count, (100.0 * success_count) / math::max(1, test_count - skip_count), success_count, test_count - skip_count, skip_count); - os::exit(success_count == test_count - skip_count ? 0 : 1); + return success_count == test_count - skip_count ? 0 : 1; } struct Error diff --git a/test/test_suite/errors/rethrow_macro.c3 b/test/test_suite/errors/rethrow_macro.c3 index 2c3f381ce..59afdd8df 100644 --- a/test/test_suite/errors/rethrow_macro.c3 +++ b/test/test_suite/errors/rethrow_macro.c3 @@ -10,5 +10,5 @@ macro void read(src, Allocator allocator, n) fn void main() { ByteReader br; - read(&br, allocator::temp(), 10)!!; + read(&br, tmem, 10)!!; } \ No newline at end of file diff --git a/test/test_suite/expressions/casts/void_casting.c3t b/test/test_suite/expressions/casts/void_casting.c3t index eb0dfad43..f96ae0b90 100644 --- a/test/test_suite/expressions/casts/void_casting.c3t +++ b/test/test_suite/expressions/casts/void_casting.c3t @@ -42,7 +42,6 @@ entry: store i32 0, ptr %x, align 4 store i64 0, ptr %y.f, align 8 store i32 0, ptr %y, align 4 - %optval = load i64, ptr %y.f, align 8 %0 = call i64 @test.foo() br label %postfailed postfailed: ; preds = %entry diff --git a/test/test_suite/generic/generic_lambda_complex.c3t b/test/test_suite/generic/generic_lambda_complex.c3t index 0b835d973..d633bbbb0 100644 --- a/test/test_suite/generic/generic_lambda_complex.c3t +++ b/test/test_suite/generic/generic_lambda_complex.c3t @@ -159,7 +159,7 @@ fn void main() String foo_tmpl = "<<{{foo}} | {{bar}}>>"; FooTmpl ft; - ft.init(foo_tmpl, using: allocator::temp())!!; + ft.init(foo_tmpl, using: tmem)!!; defer ft.free()!!; diff --git a/test/test_suite/switch/switch_in_defer_macro.c3t b/test/test_suite/switch/switch_in_defer_macro.c3t index 14da8b8ae..5f2433cb2 100644 --- a/test/test_suite/switch/switch_in_defer_macro.c3t +++ b/test/test_suite/switch/switch_in_defer_macro.c3t @@ -477,7 +477,7 @@ struct TrieNode bool valid; } -fn void Trie.init(&self, usz initial_capacity = 8, Allocator using = allocator::heap()) +fn void Trie.init(&self, usz initial_capacity = 8, Allocator using = mem) { *self = {}; self.nodes.init(using, initial_capacity); diff --git a/test/unit/regression/castable_assignable.c3 b/test/unit/regression/castable_assignable.c3 index 73224337f..a88e7dfea 100644 --- a/test/unit/regression/castable_assignable.c3 +++ b/test/unit/regression/castable_assignable.c3 @@ -2,14 +2,14 @@ module castable @test; fn void assignable() { - assert($assignable(12.0, int) == false); - assert($assignable(12, int)); - assert(!$assignable("12", int)); - assert($assignable("12", String)); - assert($assignable("12", char*)); - assert($assignable("12", char[*])); - assert($assignable("12", char[2])); - assert($assignable("12", char[3])); + assert(@assignable_to(12.0, int) == false); + assert(@assignable_to(12, int)); + assert(!@assignable_to("12", int)); + assert(@assignable_to("12", String)); + assert(@assignable_to("12", char*)); + //assert($assignable("12", char[*])); + assert(@assignable_to("12", char[2])); + assert(@assignable_to("12", char[3])); } fn void castable() diff --git a/test/unit/stdlib/collections/copy_map.c3 b/test/unit/stdlib/collections/copy_map.c3 index 983261991..d3fdb0f33 100644 --- a/test/unit/stdlib/collections/copy_map.c3 +++ b/test/unit/stdlib/collections/copy_map.c3 @@ -6,7 +6,7 @@ alias IntMap = HashMap{String, int}; fn void copy_map() @test { TrackingAllocator alloc; - alloc.init(allocator::heap()); + alloc.init(mem); defer alloc.free(); assert(alloc.allocated() == 0); mem::@scoped(&alloc) diff --git a/test/unit/stdlib/collections/object.c3 b/test/unit/stdlib/collections/object.c3 index 280d452b4..19aa8cbbb 100644 --- a/test/unit/stdlib/collections/object.c3 +++ b/test/unit/stdlib/collections/object.c3 @@ -3,14 +3,14 @@ import std::collections::object; fn void test_general() { - Object* root = object::new_obj(allocator::heap()); + Object* root = object::new_obj(mem); defer root.free(); root.set("foo", 1); root.set("bar", "baz"); assert(root.get_int("foo")!! == 1); assert(root.get_string("bar")!! == "baz"); - Object* goo = root.set("goo", object::new_obj(allocator::heap())); + Object* goo = root.set("goo", object::new_obj(mem)); goo.push("hello"); goo.push(132); assert(root.get("goo").get_int_at(1)!! == 132); @@ -27,14 +27,14 @@ fn void test_general() fn void test_to_format_int() { { - Object* int_object = object::new_int(16, allocator::heap()); + Object* int_object = object::new_int(16, mem); defer int_object.free(); String s = string::format(mem, "%s", int_object); defer free(s); assert(s == "16"); } { - Object* int_object = object::new_int(-16, allocator::heap()); + Object* int_object = object::new_int(-16, mem); defer int_object.free(); String s = string::format(mem, "%s", int_object); defer free(s); diff --git a/test/unit/stdlib/core/mem_allocator.c3 b/test/unit/stdlib/core/mem_allocator.c3 index 24f93ad65..ee1f65fa4 100644 --- a/test/unit/stdlib/core/mem_allocator.c3 +++ b/test/unit/stdlib/core/mem_allocator.c3 @@ -17,9 +17,9 @@ struct Bar fn void test_new_aligned_compiles() @test { - Bar* bar2 = allocator::new_aligned(allocator::heap(), Bar)!!; - allocator::free_aligned(allocator::heap(), bar2); + Bar* bar2 = allocator::new_aligned(mem, Bar)!!; + allocator::free_aligned(mem, bar2); - Bar* bar = allocator::new_aligned(allocator::heap(), Bar, {.x = 1, .y = 2, .foos = {}})!!; - allocator::free_aligned(allocator::heap(), bar); + Bar* bar = allocator::new_aligned(mem, Bar, {.x = 1, .y = 2, .foos = {}})!!; + allocator::free_aligned(mem, bar); } diff --git a/test/unit/stdlib/io/path.c3 b/test/unit/stdlib/io/path.c3 index ebf8eefd3..d5c7503b3 100644 --- a/test/unit/stdlib/io/path.c3 +++ b/test/unit/stdlib/io/path.c3 @@ -36,7 +36,7 @@ fn void test_parent() p.free(); } -fn void test_path_normalized() => mem::@scoped(allocator::temp()) +fn void test_path_normalized() => mem::@scoped(tmem) { assert(path::new(mem, "", path_env: PathEnv.WIN32).str_view()!! == ""); assert(@catch(path::new(mem, "1:\\a\\b\\c.txt", path_env: PathEnv.WIN32))); @@ -212,7 +212,7 @@ fn void test_path_normalized() => mem::@scoped(allocator::temp()) } -fn void test_extension() => mem::@scoped(allocator::temp()) +fn void test_extension() => mem::@scoped(tmem) { assert(@catch(path::new(mem, `C:`, path_env: PathEnv.WIN32).extension())); assert(@catch(path::new(mem, `C:`, path_env: PathEnv.POSIX).extension())); @@ -246,7 +246,7 @@ fn void test_extension() => mem::@scoped(allocator::temp()) } -fn void test_has_extension() => mem::@scoped(allocator::temp()) +fn void test_has_extension() => mem::@scoped(tmem) { assert(!path::new(mem, `C:\temp\foo.bar\README`, path_env: PathEnv.WIN32)!!.has_extension(`bar\README`)); @@ -276,7 +276,7 @@ fn void test_has_extension() => mem::@scoped(allocator::temp()) } -fn void test_basename() => mem::@scoped(allocator::temp()) +fn void test_basename() => mem::@scoped(tmem) { assert(path::for_windows(mem, "file.txt").basename()!! == "file.txt"); assert(path::for_posix(mem, "file.txt").basename()!! == "file.txt"); @@ -303,7 +303,7 @@ fn void test_basename() => mem::@scoped(allocator::temp()) assert(path::for_posix(mem, `\\server\abc`).basename()!! == `\\server\abc`); } -fn void test_dirname() => mem::@scoped(allocator::temp()) +fn void test_dirname() => mem::@scoped(tmem) { assert(path::for_posix(mem, "").dirname()!! == "."); assert(path::for_posix(mem, "/file").dirname()!! == "/"); @@ -343,7 +343,7 @@ fn void test_dirname() => mem::@scoped(allocator::temp()) assert(path::for_posix(mem, `\\server\`).dirname()!! == `.`); } -fn void test_path_volume() => mem::@scoped(allocator::temp()) +fn void test_path_volume() => mem::@scoped(tmem) { assert(path::for_windows(mem, `C:\abs`).volume_name()!! == `C:`); assert(path::for_windows(mem, `C:abs`).volume_name()!! == `C:`); @@ -353,7 +353,7 @@ fn void test_path_volume() => mem::@scoped(allocator::temp()) assert(path::for_windows(mem, `\\server\foo\abc`).volume_name()!! == `\\server\foo`); } -fn void test_path_is_absolute() => mem::@scoped(allocator::temp()) +fn void test_path_is_absolute() => mem::@scoped(tmem) { assert(!path::for_posix(mem, "").is_absolute()!!); assert(path::for_posix(mem, "/").is_absolute()!!); @@ -367,7 +367,7 @@ fn void test_path_is_absolute() => mem::@scoped(allocator::temp()) assert(path::for_windows(mem, `\\server\foo\abc`).is_absolute()!!); } -fn void test_path_absolute() => mem::@scoped(allocator::temp()) +fn void test_path_absolute() => mem::@scoped(tmem) { $if env::WIN32: assert(path::for_windows(mem, `C:\abs`).absolute(mem, )!!.str_view() == `C:\abs`); diff --git a/test/unit/stdlib/io/stream.c3 b/test/unit/stdlib/io/stream.c3 index 74fe81d65..cbd85b0cb 100644 --- a/test/unit/stdlib/io/stream.c3 +++ b/test/unit/stdlib/io/stream.c3 @@ -75,7 +75,7 @@ fn void write_short_bytearray_test() fn void read_tiny_bytearray_test() { ByteReader reader = io::wrap_bytes(&&x'07aabbcc00112233'); - char[] read = io::read_tiny_bytearray(&reader, allocator: allocator::heap())!!; + char[] read = io::read_tiny_bytearray(&reader, allocator: mem)!!; assert(read == &&x'aabbcc00112233'); free(read); } @@ -83,7 +83,7 @@ fn void read_tiny_bytearray_test() fn void read_short_bytearray_test() { ByteReader reader = io::wrap_bytes(&&x'0007aabbcc00112233'); - char[] read = io::read_short_bytearray(&reader, allocator: allocator::heap())!!; + char[] read = io::read_short_bytearray(&reader, allocator: mem)!!; assert(read == &&x'aabbcc00112233'); free(read); } diff --git a/test/unit/stdlib/math/bigint.c3 b/test/unit/stdlib/math/bigint.c3 index c27c14bed..2bfb816e0 100644 --- a/test/unit/stdlib/math/bigint.c3 +++ b/test/unit/stdlib/math/bigint.c3 @@ -11,7 +11,7 @@ fn void init_with_array() BigInt bi @noinit; assert(bi.init_with_array({}).equals(ZERO)); assert(bi.init_with_array({0, 0, 0, 1}).equals(bigint::from_int(1))); - assert("100000000" == bi.init_with_array({1, 0}).to_string_with_radix(16, allocator::temp())); + assert("100000000" == bi.init_with_array({1, 0}).to_string_with_radix(16, tmem)); } fn void test_parse16() @@ -22,10 +22,10 @@ fn void test_parse16() fn void test_zero() { - assert(bigint::from_int(0).to_string(allocator::temp()) == "0"); + assert(bigint::from_int(0).to_string(tmem) == "0"); BigInt bi; bi.init_string_radix("00", 16)!!; - assert(bi.to_string(allocator::temp()) == "0"); + assert(bi.to_string(tmem) == "0"); } fn void test_plus() diff --git a/test/unit/stdlib/mem/temp_mem.c3 b/test/unit/stdlib/mem/temp_mem.c3 index d6a2d840f..57b6b4319 100644 --- a/test/unit/stdlib/mem/temp_mem.c3 +++ b/test/unit/stdlib/mem/temp_mem.c3 @@ -59,10 +59,10 @@ fn String inner4(String s, Allocator a) fn void test_temp_allocator() @test { - assert("foofoofoofoofoofooabcaaaaaa" == add("abc", allocator::temp(), 5), "was %s", add("abc", allocator::temp(), 5)); + assert("foofoofoofoofoofooabcaaaaaa" == add("abc", tmem, 5), "was %s", add("abc", tmem, 5)); } fn void test_temp_allocator2() @test { - assert("fooxyz0123456789xy**********" == breakit("xyz0123456789", allocator::temp())); + assert("fooxyz0123456789xy**********" == breakit("xyz0123456789", tmem)); }