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.

This commit is contained in:
Christoffer Lerno
2025-07-21 03:20:40 +02:00
parent 382a65abcd
commit 869bcf8b2b
18 changed files with 116 additions and 69 deletions

View File

@@ -57,35 +57,16 @@ faultdef
*> *>
macro String? readline(Allocator allocator, stream = io::stdin()) macro String? readline(Allocator allocator, stream = io::stdin())
{ {
bool $is_stream = @typeis(stream, InStream); if (allocator == tmem)
$if $is_stream: {
$typeof(&stream.read_byte) func = &stream.read_byte; DString str = dstring::temp_with_capacity(256);
char val = func((void*)stream)!; readline_to_stream(&str, stream)!;
$else return str.str_view();
char val = stream.read_byte()!; }
$endif
if (val == '\n') return "";
@pool() @pool()
{ {
DString str = dstring::temp_with_capacity(256); DString str = dstring::temp_with_capacity(256);
if (val != '\r') str.append(val); readline_to_stream(&str, stream)!;
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);
}
return str.copy_str(allocator); return str.copy_str(allocator);
}; };
} }
@@ -104,6 +85,65 @@ macro String? treadline(stream = io::stdin())
return readline(tmem, stream) @inline; 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. Print a value to a stream.

View File

@@ -88,8 +88,8 @@ fn String? executable_path()
char[4096] buf; char[4096] buf;
uint temp_len = buf.len; uint temp_len = buf.len;
if (darwin_NSGetExecutablePath(&buf, &temp_len) < 0) return NOT_FOUND?; if (darwin_NSGetExecutablePath(&buf, &temp_len) < 0) return NOT_FOUND?;
path[:temp_len] = buf[:temp_len]; path[:len] = buf[:len];
len = temp_len; len = (int)((ZString)&buf).len();
} }
return (String)path[:len]; return (String)path[:len];
} }

View File

@@ -70,6 +70,7 @@
- A distinct type based on an array would yield .len == 0 - A distinct type based on an array would yield .len == 0
- Overloading addition with a pointer would not work. - Overloading addition with a pointer would not work.
- Copying const enums and regular enums incorrect #2313. - Copying const enums and regular enums incorrect #2313.
- Regression: Chaining an optional together with contracts could in some cases lose the optional.
### Stdlib changes ### Stdlib changes
- Improve contract for readline. #2280 - Improve contract for readline. #2280
@@ -81,6 +82,7 @@
- Added `WString.len`. - Added `WString.len`.
- Added `@addr` macro. - Added `@addr` macro.
- Add `ConditionVariable.wait_until` and `ConditionVariable.wait_for` - Add `ConditionVariable.wait_until` and `ConditionVariable.wait_for`
- Added readline_to_stream that takes a stream.
## 0.7.3 Change list ## 0.7.3 Change list

View File

@@ -1359,14 +1359,13 @@ void llvm_emit_ignored_expr(GenContext *c, Expr *expr)
LLVMBasicBlockRef discard_fail = llvm_basic_block_new(c, "voiderr"); LLVMBasicBlockRef discard_fail = llvm_basic_block_new(c, "voiderr");
PUSH_CATCH_VAR_BLOCK(NULL, discard_fail); PUSH_CATCH_VAR_BLOCK(NULL, discard_fail);
llvm_emit_expr(c, &value, expr); llvm_emit_expr(c, &value, expr);
llvm_value_fold_optional(c, &value);
EMIT_EXPR_LOC(c, expr); EMIT_EXPR_LOC(c, expr);
// We only optimize if there is no instruction the current block // We only optimize if there is no instruction the current block
if (!LLVMGetFirstInstruction(c->current_block)) if (!LLVMGetFirstInstruction(c->current_block))
{ {
llvm_prune_optional(c, discard_fail); llvm_prune_optional(c, discard_fail);
} }
else else if (!llvm_basic_block_is_unused(discard_fail))
{ {
llvm_emit_br(c, discard_fail); llvm_emit_br(c, discard_fail);
llvm_emit_block(c, discard_fail); llvm_emit_block(c, discard_fail);

View File

@@ -142,9 +142,15 @@ void llvm_emit_local_decl(GenContext *c, Decl *decl, BEValue *value)
if (init) if (init)
{ {
llvm_value_set_decl_address(c, value, decl); llvm_value_set_decl_address(c, value, decl);
// Pretend to be normal.
value->kind = BE_ADDRESS; value->kind = BE_ADDRESS;
BEValue res = llvm_emit_assign_expr(c, value, decl->var.init_expr, decl->var.optional_ref, true); 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; return;
} }

View File

@@ -11,7 +11,8 @@ Path test_dir;
bool print_to_file; bool print_to_file;
String stdlib; String stdlib;
fn void main(String[] args)
fn int main(String[] args)
{ {
// Grab the name for `usage` // Grab the name for `usage`
String appname = args[0]; String appname = args[0];
@@ -86,7 +87,7 @@ fn void main(String[] args)
io::printfn("Found %d tests: %.1f%% (%d / %d) passed (%d skipped).", io::printfn("Found %d tests: %.1f%% (%d / %d) passed (%d skipped).",
test_count, (100.0 * success_count) / math::max(1, test_count - skip_count), test_count, (100.0 * success_count) / math::max(1, test_count - skip_count),
success_count, test_count - skip_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 struct Error

View File

@@ -10,5 +10,5 @@ macro void read(src, Allocator allocator, n)
fn void main() fn void main()
{ {
ByteReader br; ByteReader br;
read(&br, allocator::temp(), 10)!!; read(&br, tmem, 10)!!;
} }

View File

@@ -42,7 +42,6 @@ entry:
store i32 0, ptr %x, align 4 store i32 0, ptr %x, align 4
store i64 0, ptr %y.f, align 8 store i64 0, ptr %y.f, align 8
store i32 0, ptr %y, align 4 store i32 0, ptr %y, align 4
%optval = load i64, ptr %y.f, align 8
%0 = call i64 @test.foo() %0 = call i64 @test.foo()
br label %postfailed br label %postfailed
postfailed: ; preds = %entry postfailed: ; preds = %entry

View File

@@ -159,7 +159,7 @@ fn void main()
String foo_tmpl = "<<{{foo}} | {{bar}}>>"; String foo_tmpl = "<<{{foo}} | {{bar}}>>";
FooTmpl ft; FooTmpl ft;
ft.init(foo_tmpl, using: allocator::temp())!!; ft.init(foo_tmpl, using: tmem)!!;
defer ft.free()!!; defer ft.free()!!;

View File

@@ -477,7 +477,7 @@ struct TrieNode
bool valid; 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 = {};
self.nodes.init(using, initial_capacity); self.nodes.init(using, initial_capacity);

View File

@@ -2,14 +2,14 @@ module castable @test;
fn void assignable() fn void assignable()
{ {
assert($assignable(12.0, int) == false); assert(@assignable_to(12.0, int) == false);
assert($assignable(12, int)); assert(@assignable_to(12, int));
assert(!$assignable("12", int)); assert(!@assignable_to("12", int));
assert($assignable("12", String)); assert(@assignable_to("12", String));
assert($assignable("12", char*)); assert(@assignable_to("12", char*));
assert($assignable("12", char[*])); //assert($assignable("12", char[*]));
assert($assignable("12", char[2])); assert(@assignable_to("12", char[2]));
assert($assignable("12", char[3])); assert(@assignable_to("12", char[3]));
} }
fn void castable() fn void castable()

View File

@@ -6,7 +6,7 @@ alias IntMap = HashMap{String, int};
fn void copy_map() @test fn void copy_map() @test
{ {
TrackingAllocator alloc; TrackingAllocator alloc;
alloc.init(allocator::heap()); alloc.init(mem);
defer alloc.free(); defer alloc.free();
assert(alloc.allocated() == 0); assert(alloc.allocated() == 0);
mem::@scoped(&alloc) mem::@scoped(&alloc)

View File

@@ -3,14 +3,14 @@ import std::collections::object;
fn void test_general() fn void test_general()
{ {
Object* root = object::new_obj(allocator::heap()); Object* root = object::new_obj(mem);
defer root.free(); defer root.free();
root.set("foo", 1); root.set("foo", 1);
root.set("bar", "baz"); root.set("bar", "baz");
assert(root.get_int("foo")!! == 1); assert(root.get_int("foo")!! == 1);
assert(root.get_string("bar")!! == "baz"); 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("hello");
goo.push(132); goo.push(132);
assert(root.get("goo").get_int_at(1)!! == 132); assert(root.get("goo").get_int_at(1)!! == 132);
@@ -27,14 +27,14 @@ fn void test_general()
fn void test_to_format_int() 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(); defer int_object.free();
String s = string::format(mem, "%s", int_object); String s = string::format(mem, "%s", int_object);
defer free(s); defer free(s);
assert(s == "16"); assert(s == "16");
} }
{ {
Object* int_object = object::new_int(-16, allocator::heap()); Object* int_object = object::new_int(-16, mem);
defer int_object.free(); defer int_object.free();
String s = string::format(mem, "%s", int_object); String s = string::format(mem, "%s", int_object);
defer free(s); defer free(s);

View File

@@ -17,9 +17,9 @@ struct Bar
fn void test_new_aligned_compiles() @test fn void test_new_aligned_compiles() @test
{ {
Bar* bar2 = allocator::new_aligned(allocator::heap(), Bar)!!; Bar* bar2 = allocator::new_aligned(mem, Bar)!!;
allocator::free_aligned(allocator::heap(), bar2); allocator::free_aligned(mem, bar2);
Bar* bar = allocator::new_aligned(allocator::heap(), Bar, {.x = 1, .y = 2, .foos = {}})!!; Bar* bar = allocator::new_aligned(mem, Bar, {.x = 1, .y = 2, .foos = {}})!!;
allocator::free_aligned(allocator::heap(), bar); allocator::free_aligned(mem, bar);
} }

View File

@@ -36,7 +36,7 @@ fn void test_parent()
p.free(); 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(path::new(mem, "", path_env: PathEnv.WIN32).str_view()!! == "");
assert(@catch(path::new(mem, "1:\\a\\b\\c.txt", path_env: PathEnv.WIN32))); 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.WIN32).extension()));
assert(@catch(path::new(mem, `C:`, path_env: PathEnv.POSIX).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`)); 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_windows(mem, "file.txt").basename()!! == "file.txt");
assert(path::for_posix(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`); 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, "").dirname()!! == ".");
assert(path::for_posix(mem, "/file").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()!! == `.`); 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:`);
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`); 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()!!);
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()!!); 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: $if env::WIN32:
assert(path::for_windows(mem, `C:\abs`).absolute(mem, )!!.str_view() == `C:\abs`); assert(path::for_windows(mem, `C:\abs`).absolute(mem, )!!.str_view() == `C:\abs`);

View File

@@ -75,7 +75,7 @@ fn void write_short_bytearray_test()
fn void read_tiny_bytearray_test() fn void read_tiny_bytearray_test()
{ {
ByteReader reader = io::wrap_bytes(&&x'07aabbcc00112233'); 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'); assert(read == &&x'aabbcc00112233');
free(read); free(read);
} }
@@ -83,7 +83,7 @@ fn void read_tiny_bytearray_test()
fn void read_short_bytearray_test() fn void read_short_bytearray_test()
{ {
ByteReader reader = io::wrap_bytes(&&x'0007aabbcc00112233'); 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'); assert(read == &&x'aabbcc00112233');
free(read); free(read);
} }

View File

@@ -11,7 +11,7 @@ fn void init_with_array()
BigInt bi @noinit; BigInt bi @noinit;
assert(bi.init_with_array({}).equals(ZERO)); assert(bi.init_with_array({}).equals(ZERO));
assert(bi.init_with_array({0, 0, 0, 1}).equals(bigint::from_int(1))); 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() fn void test_parse16()
@@ -22,10 +22,10 @@ fn void test_parse16()
fn void test_zero() 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; BigInt bi;
bi.init_string_radix("00", 16)!!; bi.init_string_radix("00", 16)!!;
assert(bi.to_string(allocator::temp()) == "0"); assert(bi.to_string(tmem) == "0");
} }
fn void test_plus() fn void test_plus()

View File

@@ -59,10 +59,10 @@ fn String inner4(String s, Allocator a)
fn void test_temp_allocator() @test 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 fn void test_temp_allocator2() @test
{ {
assert("fooxyz0123456789xy**********" == breakit("xyz0123456789", allocator::temp())); assert("fooxyz0123456789xy**********" == breakit("xyz0123456789", tmem));
} }