Skip to content

Commit 7b026b9

Browse files
committed
Fix Vec/Option monomorphization and owned String push_str codegen, with regression tests.
1 parent 92d5d93 commit 7b026b9

11 files changed

Lines changed: 229 additions & 173 deletions

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
- **LSP**: diagnostics, hover, completion, go-to-definition; multi-error reporting; symbol table mirroring; diagnostics cleared on close; hover fixes for `let` bindings and module-qualified calls. Contiguous `//` doc comments attached to declarations (including `pub` items) for hover.
1010
- **Tooling**: GitHub Actions CI (Linux and Windows), pinned toolchain (1.96.0), `test_expectations.tsv` manifest, `--version`, line-numbered errors, Cursor agent skills. Split `tc` and `cgen` into submodules. `ion-build` driver and `ion.toml` manifests (`single`/`multi`, `out_dir`, `cflags`, `ldflags`, `stdlib_paths`, `emit_in_source`); per-example manifests and `build_hello`/`build_bad_main` harness smoke tests. Shared `discover_import_config` and stdlib search paths for `ion-compiler`, `ion-build`, and LSP. Integration harness precompiles `runtime/ion_runtime.c` once per run (`RUNTIME_OBJ`). `writing-ion-code` agent skill; `creating-ion-skills` examples index lists all eight project skills. Documented checked-in `examples/*.c` codegen snapshots in README and integration-test skill; regenerated example C output. Fixed `researching-pl-literature` skill `paper-seeds` reference formatting.
1111
- **Docs**: README, CONTRIBUTING, ION_SPEC, and agent skills aligned on project layout, `ion-build` workflow, `emit_in_source`, stdlib import order, LSP features and limitations, `src/build/` checklists, portable Git Bash paths for `test_runner.sh`, and rebuilding release binaries before harness or example C regen (stale `target/release/ion-compiler` note).
12-
- **Fixes**: match rvalue codegen, `Vec` struct drops, channel codegen, parser handling of `alias::call()`, scope-drop for moved-into-call bindings, HTTP server on Windows, integration harness on Windows. Cgen return-unwind: stop marking bindings dropped after inner-branch `return` (restores outer-path drops), dedupe `_`-prefixed unused silences, mark call arguments moved at emission to avoid double-free on unwind, and unify all function returns via `emit_function_return` (including diverging rvalue `match` arms). Cgen struct field move-out neutralizes owned fields after partial move, deferred to statement end when the move is a call argument; `Vec::push` passes address of struct lvalues; non-generic enums emit before structs; tuple IR uses resolved element types and `tuple_Vec_T` mangling with compound-zero `ret_val` for tuple returns (functions and fn literals). Integer indexing and `Vec<i32>` inference in the type checker. Match rvalue arms: reject diverging arms mixed with value arms; structural control-flow analysis for arm bodies (nested `if`/`else`, `loop` without `break`, `unsafe` blocks); reject mixed diverge and value-producing paths within one arm; cgen assigns through `if`/`else` value branches. `fmt::int_to_string` uses `String::push_byte` per digit instead of per-digit `push_str` branches; integration test asserts `0`, negatives, and `int::MIN`. `int::MIN`/`int::MAX` on integer primitives; `Vec::new()`/`with_capacity()` infer `T` from a `let` type annotation. Clippy fix in `portable_source_label`. `Vec::get`/`Vec::pop`: IR infers `Option<T>` from the vector argument; cgen dereferences `&mut Vec<T>` parameters, unpacks runtime `Option` via `ion_option_from_raw`, and uses a temp for struct-returning `Vec::push`/`Vec::set` call values. Grouped `match` switch arms: scope drops before the case `break` (GCC `-Wimplicit-fallthrough` under `-Werror`); guarded arms in a shared variant case break inside the guard `if` so fallback arms do not run. Cgen: enum literals lower to compound initializers (no per-variant `_new` helpers); unused bindings and parameters silenced with `(void)` at scope unwind instead of blanket `ION_MAYBE_UNUSED`.
12+
- **Fixes**: match rvalue codegen, `Vec` struct drops, channel codegen, parser handling of `alias::call()`, scope-drop for moved-into-call bindings, HTTP server on Windows, integration harness on Windows. Cgen return-unwind: stop marking bindings dropped after inner-branch `return` (restores outer-path drops), dedupe `_`-prefixed unused silences, mark call arguments moved at emission to avoid double-free on unwind, and unify all function returns via `emit_function_return` (including diverging rvalue `match` arms). Cgen struct field move-out neutralizes owned fields after partial move, deferred to statement end when the move is a call argument; `Vec::push` passes address of struct lvalues; non-generic enums emit before structs; tuple IR uses resolved element types and `tuple_Vec_T` mangling with compound-zero `ret_val` for tuple returns (functions and fn literals). Integer indexing and `Vec<i32>` inference in the type checker. Match rvalue arms: reject diverging arms mixed with value arms; structural control-flow analysis for arm bodies (nested `if`/`else`, `loop` without `break`, `unsafe` blocks); reject mixed diverge and value-producing paths within one arm; cgen assigns through `if`/`else` value branches. `fmt::int_to_string` uses `String::push_byte` per digit instead of per-digit `push_str` branches; integration test asserts `0`, negatives, and `int::MIN`. `int::MIN`/`int::MAX` on integer primitives; `Vec::new()`/`with_capacity()` infer `T` from a `let` type annotation. Clippy fix in `portable_source_label`. `Vec::get`/`Vec::pop`: IR infers `Option<T>` from the vector argument; cgen dereferences `&mut Vec<T>` parameters, unpacks runtime `Option` via `ion_option_from_raw`, and uses a temp for struct-returning `Vec::push`/`Vec::set` call values. Grouped `match` switch arms: scope drops before the case `break` (GCC `-Wimplicit-fallthrough` under `-Werror`); guarded arms in a shared variant case break inside the guard `if` so fallback arms do not run. Cgen: enum literals lower to compound initializers (no per-variant `_new` helpers); unused bindings and parameters silenced with `(void)` at scope unwind instead of blanket `ION_MAYBE_UNUSED`. Cgen monomorphization: `Vec<T>`/`Option<T>` typedefs mangle Ion type names (`Vec_String`, not C typedefs); `match Vec::get`/`Vec::pop` resolves `Option<T>` from return type or vector element type; `String::push_str` with owned `String` reads source `.data`/`.len`. Integration tests `test_vec_string_mangle`, `test_vec_get_multi_option`, `test_string_push_str_owned`.
1313
- **Examples**: `text_summary` (fixture file), `data_lib` (multi-module), `channel_worker` (flat single-file), `todo_demo` (interactive stdin, `Vec` of structs with `String`). Per-example `*.toml` manifests for `ion-build`. `http_server` accepts clients until stdin `quit`.
1414
- **Multi-file fixes**: merge private struct types for cross-module type checking; per-module C symbol prefixes in multi-file codegen (`io_print_int`); walk-up `runtime/` discovery for nested build directories. Integration tests `test_multi_struct`, `test_multi_fmt_io`. Example policy: each demo under `examples/<name>/` with `ion.toml`; build output under `target/` only (no committed `.c`).
1515

ION_SPEC.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1049,7 +1049,7 @@ Note that:
10491049

10501050
- String literals can be directly assigned to `String` type: `let s: String = "hello";`
10511051
- `String::from()` creates a heap-allocated copy of a string literal.
1052-
- `String::push_str()` appends a string literal to an existing `String`.
1052+
- `String::push_str()` appends a string literal or an owned `String` (reads the source buffer).
10531053
- `String::push_byte()` appends a single byte to an existing `String`.
10541054
- `==` and `!=` compare UTF-8 byte content (value equality), not pointer identity.
10551055

docs/ABI.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ Stable beta expectations:
2626
- `String::new`, `String::from`, `String::len`, `String::push_str`, and
2727
`String::push_byte` preserve ownership of the `String` receiver unless a
2828
function explicitly consumes it.
29+
- `String::push_str` appends string literals or owned `String` values (the
30+
latter reads `.data`/`.len` from the source heap buffer).
2931
- Dropping a `String` releases its backing allocation once.
3032
- `String == String` and `String != String` compare byte contents.
3133

@@ -43,7 +45,11 @@ Stable beta expectations:
4345
trigger the runtime panic path for checked indexing.
4446
- `Vec::get` / `Vec::pop` return heap `Option` blobs from the runtime; generated
4547
C unpacks them with `ion_option_from_raw` into monomorphized `Option_T` stack
46-
values (tag plus payload at `data.variant_0.arg0`).
48+
values (tag plus payload at `data.variant_0.arg0`). `match` on `Vec::get` /
49+
`Vec::pop` resolves `Option<T>` from the call's return type or the vector
50+
argument's element type (not the first `Vec` in the program).
51+
- Monomorphized container typedefs use Ion type names (`Vec_String`,
52+
`Option_Customer`), not C runtime typedefs (`ion_string_t`, etc.).
4753
- `&mut Vec<T>` parameters codegen as `Vec_T**`; builtins dereference once when
4854
passing the receiver to `ion_vec_push`, `ion_vec_get`, and related helpers.
4955

src/cgen/builtins.rs

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use super::*;
2+
use crate::cgen::types::mangle_type_name;
23

34
impl Codegen {
45
/// Generate code for built-in function calls.
@@ -68,12 +69,7 @@ impl Codegen {
6869
.unwrap_or(&Type::Int);
6970

7071
let elem_c_type = self.type_to_c(elem_type);
71-
// Vec::new returns ion_vec_t*, but we need Vec_T*
72-
// Cast ion_vec_t* to Vec_T*
73-
// mangle_type_name expects a base name and params, but elem_c_type is already a C type name
74-
// So we just use it directly
75-
let vec_type_name =
76-
format!("Vec_{}", elem_c_type.replace(' ', "_").replace('*', "ptr"));
72+
let vec_type_name = mangle_type_name("Vec", std::slice::from_ref(elem_type));
7773
let code = format!(
7874
"(({}*)(ion_vec_new(sizeof({}))))",
7975
vec_type_name, elem_c_type
@@ -94,8 +90,7 @@ impl Codegen {
9490
.unwrap_or(&Type::Int);
9591

9692
let elem_c_type = self.type_to_c(elem_type);
97-
let vec_type_name =
98-
format!("Vec_{}", elem_c_type.replace(' ', "_").replace('*', "ptr"));
93+
let vec_type_name = mangle_type_name("Vec", std::slice::from_ref(elem_type));
9994
let mut code = String::new();
10095
code.push_str(&format!(
10196
"(({}*)(ion_vec_with_capacity(sizeof(",
@@ -369,25 +364,24 @@ impl Codegen {
369364
self.generate_expr(&args[1]);
370365
other_code = std::mem::replace(&mut self.output, old_output);
371366

372-
code.push_str("ion_string_push_str(");
373-
code.push_str(deref_str);
374-
code.push_str(", ");
375367
if other_code.starts_with('"') {
368+
code.push_str("ion_string_push_str(");
369+
code.push_str(deref_str);
370+
code.push_str(", ");
376371
let len = other_code.len() - 2;
377372
code.push_str(&other_code);
378373
code.push_str(", ");
379374
code.push_str(&len.to_string());
375+
code.push(')');
380376
} else {
381-
// For String type, remove & if present
377+
// Owned String or &String: append using heap buffer (.data/.len).
382378
let deref_other = other_code.strip_prefix('&').unwrap_or(&other_code);
379+
code.push_str("({ ion_string_t* _ion_push_other = ");
383380
code.push_str(deref_other);
384-
code.push_str(", (");
385-
code.push_str(deref_other);
386-
code.push_str(") ? (");
387-
code.push_str(deref_other);
388-
code.push_str(")->len : 0");
381+
code.push_str("; ion_string_push_str(");
382+
code.push_str(deref_str);
383+
code.push_str(", ((_ion_push_other != NULL && _ion_push_other->data != NULL) ? (const char*)_ion_push_other->data : \"\"), (_ion_push_other != NULL ? _ion_push_other->len : (size_t)0)); })");
389384
}
390-
code.push(')');
391385
return Some(code);
392386
}
393387

0 commit comments

Comments
 (0)