diff --git a/.cursor/skills/finding-ion-bugs/references/bug-hotspots.md b/.cursor/skills/finding-ion-bugs/references/bug-hotspots.md index 2e4f6d0..ecb8748 100644 --- a/.cursor/skills/finding-ion-bugs/references/bug-hotspots.md +++ b/.cursor/skills/finding-ion-bugs/references/bug-hotspots.md @@ -21,7 +21,8 @@ CLI errors use `TypeCheckError` Debug form (`UseAfterMove { ... }`). LSP reforma - Drop order and `ion_drop_*` for moved fields - **Struct field move-out**: owned fields null after partial move on the next statement (`board.items = NULL`; deferred when the move is a call argument) - **Vec::push lvalues**: struct variables and field paths use `&item`, not compound literal (`vec_push_struct_var_uses_address_of_lvalue`) -- **Enum emission order**: non-generic enums before structs in single-file C output. Generic enum instantiations (`Option_int`) must be complete types before tuple typedefs that store them by value (`test_tuple_option_none.ion`). `collect_generic_from_type` must walk `Type::Tuple` (and TupleLit `elem_types`); walking only nested exprs misses `Option::None` which has no payload expr. Multi-file `generate_module_source` still emits user structs before generic enum bodies (`examples/data_lib`). +- **Array typedef names**: `type_to_c` for `[T; N]` is the typedef name (`arr_int_2`), never `T[N]`. Emit `typedef T name[N]` innermost-first before Vec/Box/field uses. Nested arrays and `Box<[T;N]>` / `Vec<[T;N]>` need this (`test_nested_array.ion`, `test_box_array.ion`, `test_vec_array.ion`). C arrays are not assignable: Box/Vec copies and match payload bindings use memcpy. +- **Enum emission order**: collect generic instantiations from enum variant payloads and close generic struct/enum templates (substitute collected params into fields/payloads until fixpoint). Forward-declare non-generic structs and enums so `Option_ref_Op` can mention `Op*` (`test_vm_execute.ion`). Emit complete generic enums (`Option_int`) before non-generic enums that embed them (`Hold`). Same order in single-file `generate_c` and multi-file `generate_module_source`. Generic enums must also exist before tuple typedefs that store them by value (`test_tuple_option_none.ion`, `test_enum_option_payload_none.ion`, `test_generic_struct_option_none.ion`). Array typedefs of tuples wait until the tuple typedef exists (`test_array_of_tuples_option_none.ion`). `collect_generic_from_type` must walk `Type::Tuple` (and TupleLit `elem_types`); walking only nested exprs misses `Option::None` which has no payload expr. - **Nested enum compound literals**: GCC rejects `(Option_int){...}` as a designated field of a tuple or struct. Emit brace-only `{ .tag = N, .data = { } }` there. Call-site and let inits stay `(Option_int){...}` (`test_option_none_call_arg.ion` cgen `take((Option_int)`). - **Tuple mangle**: `tuple_type_name` sanitizes `*` and brackets when names include `Vec` types - **Match scrutinee move-out**: pattern payload bindings null `match_val_N.data.variant_*` fields when ownership transfers (`statement_match_payload_move_neutralizes_scrutinee`); whole-enum binding arms clear active variant payloads via `emit_match_scrutinee_whole_enum_moved_out` (`whole_enum_binding_neutralizes_scrutinee_payloads`). IR infers `enum_type` from the scrutinee when arms use binding/wildcard only (`infer_match_enum_name`). diff --git a/.cursor/skills/writing-ion-code/references/verified-patterns.md b/.cursor/skills/writing-ion-code/references/verified-patterns.md index b803638..4b4aace 100644 --- a/.cursor/skills/writing-ion-code/references/verified-patterns.md +++ b/.cursor/skills/writing-ion-code/references/verified-patterns.md @@ -22,7 +22,7 @@ let p: Point = Point { x: 1, y: 2 }; ## Enum variants -Tuple: `Option::Some(42)`, `Option::None`. `take(Option::None)` infers `T` from the parameter type ([tests/test_option_none_call_arg.ion](../../../../tests/test_option_none_call_arg.ion)); `send(&tx, Option::None)` infers from `Sender` ([tests/test_send_option_none.ion](../../../../tests/test_send_option_none.ion)); `Box::new(Option::None)` infers from an expected `Box>` ([tests/test_box_new_option_none.ion](../../../../tests/test_box_new_option_none.ion)); `[Option::None]` and `(Option::None, 1)` infer from an adjacent array or tuple type ([tests/test_array_option_none.ion](../../../../tests/test_array_option_none.ion), [tests/test_tuple_option_none.ion](../../../../tests/test_tuple_option_none.ion)); unannotated `let empty = Option::None` still needs an annotation. +Tuple: `Option::Some(42)`, `Option::None`. `take(Option::None)` infers `T` from the parameter type ([tests/test_option_none_call_arg.ion](../../../../tests/test_option_none_call_arg.ion)); `send(&tx, Option::None)` infers from `Sender` ([tests/test_send_option_none.ion](../../../../tests/test_send_option_none.ion)); `Box::new(Option::None)` infers from an expected `Box>` ([tests/test_box_new_option_none.ion](../../../../tests/test_box_new_option_none.ion)); `[Option::None]` and `(Option::None, 1)` infer from an adjacent array or tuple type ([tests/test_array_option_none.ion](../../../../tests/test_array_option_none.ion), [tests/test_tuple_option_none.ion](../../../../tests/test_tuple_option_none.ion)); `enum Hold { H(Option) }` with `Hold::H(Option::None)` and `Hold::H(Option::Some(n))` emits `Option_int` ([tests/test_enum_option_payload_none.ion](../../../../tests/test_enum_option_payload_none.ion), [tests/test_enum_option_payload_some.ion](../../../../tests/test_enum_option_payload_some.ion)); unannotated `let empty = Option::None` still needs an annotation. Struct: `Status::Ok { value: 10 }`. @@ -302,7 +302,7 @@ struct Node { ## Arrays and slices -Fixed arrays `[T; N]` and slices `[]T` / `&[]T` support bounds-checked indexing (panic on OOB). Query length with `Slice::len` (`s.len()`); field access `s.len` is not valid. For non-panicking element access use `Slice::get_ref` (`Option<&T>`, local only), including after `&[T; N]` -> `&[]T` coercion. See [tests/test_slice_len.ion](../../../../tests/test_slice_len.ion) and [tests/test_slice_get_ref_from_array.ion](../../../../tests/test_slice_get_ref_from_array.ion). +Fixed arrays `[T; N]` and slices `[]T` / `&[]T` support bounds-checked indexing (panic on OOB). Query length with `Slice::len` (`s.len()`); field access `s.len` is not valid. For non-panicking element access use `Slice::get_ref` (`Option<&T>`, local only), including after `&[T; N]` -> `&[]T` coercion. Nested arrays, `Box<[T; N]>`, and `Vec<[T; N]>` compile ([tests/test_nested_array.ion](../../../../tests/test_nested_array.ion), [tests/test_box_array.ion](../../../../tests/test_box_array.ion), [tests/test_vec_array.ion](../../../../tests/test_vec_array.ion)). See [tests/test_slice_len.ion](../../../../tests/test_slice_len.ion) and [tests/test_slice_get_ref_from_array.ion](../../../../tests/test_slice_get_ref_from_array.ion). ```ion let arr: [int; 3] = [1, 2, 3]; diff --git a/CHANGELOG.md b/CHANGELOG.md index 158f3c8..9ca933e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 0.1.22 - 2026-08-14 + +- **Codegen**: `[T; N]` lowers to a named C array typedef (`arr_int_2`) so nested arrays and `Box`/`Vec` of arrays are valid C. Functions still cannot return a C array type (pointer decay unchanged). This impacts nested `[int; N]` locals, fields, parameters, and returns, plus `Box<[T; N]>` and `Vec<[T; N]>`. +- **Codegen**: generic instantiations are collected from enum variant payloads and closed under generic struct/enum templates; `Option_int` is emitted before non-generic enums that embed it. This impacts `enum Hold { H(Option) }` with `Hold::H(Option::None)` and `Hold::H(Option::Some(n))`, and `struct S { x: Option }` with `S { x: Option::None }`. Unannotated `let empty = Option::None` still requires an annotation. +- **Tests**: `test_nested_array.ion`, `test_nested_array_field_param_return.ion`, `test_nested_array_3d.ion`, `test_box_array.ion`, `test_vec_array.ion`, `test_enum_option_payload_none.ion`, `test_enum_option_payload_some.ion`, `test_generic_struct_option_none.ion`. +- **Docs**: ION_SPEC ยง4.1.1, ABI arrays, bug hotspots, verified patterns. + ## 0.1.21 - 2026-08-14 - **Type checker / Codegen**: array, tuple, and `[value; N]` elements, assignment, returned compounds, and enum variant payloads now check against the adjacent expected type (same `expr_expected` helper as struct fields, call arguments, `send`, and `Box::new`). This impacts `let a: [Option; 1] = [Option::None]`, `(Option::None, 1)`, `[Option::Some(4)]` (emits `Option_int`, not bare `(Option)`), `x = Option::None`, `return [Option::None]`, and `Result::Err(Option::None)` as a compound element. Unannotated `let empty = Option::None` still requires an annotation. diff --git a/Cargo.lock b/Cargo.lock index 7b30ef2..fe11dc5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -323,7 +323,7 @@ dependencies = [ [[package]] name = "ion-compiler" -version = "0.1.21" +version = "0.1.22" dependencies = [ "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index de7d0bc..9c338e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ion-compiler" -version = "0.1.21" +version = "0.1.22" edition = "2024" [[bin]] diff --git a/ION_SPEC.md b/ION_SPEC.md index 553b95c..c093d09 100644 --- a/ION_SPEC.md +++ b/ION_SPEC.md @@ -515,6 +515,7 @@ Fixed-size arrays `[T; N]` have the following safety properties: - **Compile-time size**: Array size `N` must be a compile-time constant - **Stack allocation**: Arrays are allocated on the stack by default - **Index type**: The index expression may be any integer type (`int`, `i32`, `u32`, etc.). +- **C lowering**: `[T; N]` lowers to a named C array typedef (`typedef int arr_int_2[2];`, nested `typedef arr_int_2 arr_arr_int_2_3[3];`) so nested arrays and `Box<[T; N]>` / `Vec<[T; N]>` are valid C type specifiers. Indexing stays `a[i]`. Functions still cannot return a C array type; array returns decay to a pointer to the first element. **Safe array access:** ```ion diff --git a/docs/ABI.md b/docs/ABI.md index 1d24074..74063cc 100644 --- a/docs/ABI.md +++ b/docs/ABI.md @@ -85,9 +85,14 @@ Stable beta expectations: ## Arrays and slices -Fixed arrays `[T; N]` are inline values. Slices `[]T` are fat views carrying a -data pointer and length. Safe indexing emits runtime bounds checks; indexing -inside `unsafe` blocks may omit those checks. +Fixed arrays `[T; N]` are inline values with C-compatible layout. Generated C +names them `arr_{elem}_{N}` (`typedef int arr_int_2[2];`). Nested arrays compose +(`typedef arr_int_2 arr_arr_int_2_3[3];`). `Box<[T; N]>` is a pointer to that +array type (`arr_int_2*`). `Vec<[T; N]>` uses the typedef as the element type +(`Vec_arr_int_2`). Functions do not return C array types; returns decay to a +pointer to the first element. Slices `[]T` are fat views carrying a data pointer +and length. Safe indexing emits runtime bounds checks; indexing inside `unsafe` +blocks may omit those checks. - `Slice::len` returns the fat-pointer element count as `int` (empty is `0`). Method form `s.len()` desugars to `Slice::len`. `&[T; N]` may coerce to `&[]T` for this diff --git a/src/cgen/builtins.rs b/src/cgen/builtins.rs index 99dd0d9..e7a3064 100644 --- a/src/cgen/builtins.rs +++ b/src/cgen/builtins.rs @@ -24,20 +24,26 @@ impl Codegen { .unwrap_or(&Type::Int); let inner_c_type = self.type_to_c(inner_type); + let mut arg_code = String::new(); + let old_output = std::mem::replace(&mut self.output, arg_code); + self.generate_expr_with_type(&args[0], Some(inner_type)); + arg_code = std::mem::replace(&mut self.output, old_output); let mut code = String::new(); code.push_str("({ "); code.push_str(&format!( "{}* ptr = ({}*)ion_box_alloc(sizeof({}));", inner_c_type, inner_c_type, inner_c_type )); - code.push_str(" if (ptr) { *ptr = "); - // Generate the argument expression - let mut arg_code = String::new(); - let old_output = std::mem::replace(&mut self.output, arg_code); - self.generate_expr_with_type(&args[0], Some(inner_type)); - arg_code = std::mem::replace(&mut self.output, old_output); - code.push_str(&arg_code); - code.push_str("; } ptr; })"); + // C arrays are not assignable, even behind a typedef name. + if matches!(inner_type, Type::Array { .. }) { + code.push_str(" if (ptr) { "); + code.push_str(&memcpy_from_value("ptr", &inner_c_type, &arg_code)); + code.push_str("; } ptr; })"); + } else { + code.push_str(" if (ptr) { *ptr = "); + code.push_str(&arg_code); + code.push_str("; } ptr; })"); + } return Some(code); } @@ -54,11 +60,19 @@ impl Codegen { let old_output = std::mem::replace(&mut self.output, arg_code); self.generate_expr(&args[0]); arg_code = std::mem::replace(&mut self.output, old_output); - let code = format!( - "({{ {ty}* _box = {arg}; {ty} _val = *_box; ion_box_free(_box); _val; }})", - ty = inner_c_type, - arg = arg_code - ); + let code = if matches!(inner_type, Type::Array { .. }) { + format!( + "({{ {ty}* _box = {arg}; static {ty} _val; memcpy(&_val, _box, sizeof(_val)); ion_box_free(_box); _val; }})", + ty = inner_c_type, + arg = arg_code + ) + } else { + format!( + "({{ {ty}* _box = {arg}; {ty} _val = *_box; ion_box_free(_box); _val; }})", + ty = inner_c_type, + arg = arg_code + ) + }; return Some(code); } @@ -172,6 +186,7 @@ impl Codegen { | IREexpr::Var(_) | IREexpr::FieldAccess { .. } ); + let elem_is_array = matches!(elem_ty, Some(Type::Array { .. })); if value_is_lvalue { code.push_str("ion_vec_push((ion_vec_t*)("); code.push_str(&deref_vec); @@ -180,6 +195,14 @@ impl Codegen { code.push_str(", sizeof("); code.push_str(&elem_c_type); code.push_str("))"); + } else if elem_is_array { + code.push_str("ion_vec_push((ion_vec_t*)("); + code.push_str(&deref_vec); + code.push_str("), "); + code.push_str(&compound_literal_addr(&elem_c_type, &value_code)); + code.push_str(", sizeof("); + code.push_str(&elem_c_type); + code.push_str("))"); } else if matches!(args[1], IREexpr::Call { .. }) { code.push_str("({ "); code.push_str(&elem_c_type); @@ -798,3 +821,20 @@ impl Codegen { None } } + +/// Address of a value for memcpy / `ion_vec_push`. Brace lists become typed compound literals. +fn compound_literal_addr(c_ty: &str, value_code: &str) -> String { + let trimmed = value_code.trim_start(); + if trimmed.starts_with('{') { + format!("&(({c_ty}){trimmed})") + } else { + format!("&({value_code})") + } +} + +fn memcpy_from_value(dest_ptr: &str, c_ty: &str, value_code: &str) -> String { + format!( + "memcpy({dest_ptr}, {}, sizeof({c_ty}))", + compound_literal_addr(c_ty, value_code) + ) +} diff --git a/src/cgen/mod.rs b/src/cgen/mod.rs index e4b7412..447347d 100644 --- a/src/cgen/mod.rs +++ b/src/cgen/mod.rs @@ -3,9 +3,9 @@ mod drop; mod types; use self::types::{ - fn_type_to_c_decl, fn_type_to_c_function_header, format_ret_val_decl, mangle_module_callee, - mangle_type_name, resolve_type_alias, ret_val_decl, substitute_type_params, tuple_type_name, - type_to_c_impl, type_to_c_return_type, + array_type_name, fn_type_to_c_decl, fn_type_to_c_function_header, format_ret_val_decl, + mangle_module_callee, mangle_type_name, resolve_type_alias, ret_val_decl, + substitute_type_params, tuple_type_name, type_to_c_impl, type_to_c_return_type, }; use crate::ast::{ @@ -405,73 +405,19 @@ impl Codegen { self.generic_instantiations = resolved_instantiations; + let array_typedefs = collect_array_typedefs(program); + // Vec and slice typedefs must precede struct fields that reference them. // Tuple typedefs wait until generic enums (e.g. Option_int) are complete types. self.emit_vec_slice_typedefs(program); + self.emit_ready_array_typedefs(&array_typedefs); - // Enums before structs so struct fields can use enum types by value. - for e in &program.enums { - if e.generics.is_empty() { - self.generate_enum_type(e); - } - } - - // Forward-declare non-generic structs so Option> can mention Node* - // before the Node body exists. - for s in &program.structs { - if s.generics.is_empty() { - self.writeln(&format!("typedef struct {} {};", s.name, s.name)); - } - } - if program.structs.iter().any(|s| s.generics.is_empty()) { - self.writeln(""); - } + // Forwards so Option> / Option<&Op> can mention Node* / Op* + // before those bodies exist. + self.emit_non_generic_type_forwards(program); // Ensure Option template is available for monomorphization (builtin or user). - if !self.enum_map.contains_key("Option") { - let option_template = EnumDecl { - doc: None, - pub_: false, - name: "Option".to_string(), - generics: vec![TypeParam::simple("T")], - variants: vec![ - EnumVariant { - doc: None, - name: "Some".to_string(), - payload_types: vec![Type::Generic { - name: "T".to_string(), - params: vec![], - }], - named_fields: None, - span: Span { - start: 0, - end: 0, - line: 0, - column: 0, - }, - }, - EnumVariant { - doc: None, - name: "None".to_string(), - payload_types: vec![], - named_fields: None, - span: Span { - start: 0, - end: 0, - line: 0, - column: 0, - }, - }, - ], - span: Span { - start: 0, - end: 0, - line: 0, - column: 0, - }, - }; - self.enum_map.insert("Option".to_string(), option_template); - } + self.ensure_option_template(); self.emit_monomorphized_enum_forwards(); @@ -524,6 +470,17 @@ impl Codegen { self.generated_types.insert(key, true); } + self.emit_ready_array_typedefs(&array_typedefs); + + // Non-generic enums after Option_int and peers so payloads like Option + // are complete C types (Hold { H(Option) }). + for e in &program.enums { + if e.generics.is_empty() { + self.generate_enum_type(e); + } + } + self.emit_ready_array_typedefs(&array_typedefs); + self.emit_tuple_typedefs(program); for (decl, params) in struct_instantiations { @@ -533,6 +490,7 @@ impl Codegen { } self.emit_non_generic_struct_bodies(program); + self.emit_ready_array_typedefs(&array_typedefs); let mut late_pending: Vec<(EnumDecl, Vec)> = late_enum_instantiations; for (key, params) in late_option_types { @@ -543,6 +501,7 @@ impl Codegen { late_pending.push((option_decl, params)); } self.emit_enum_instantiations_ready_first(late_pending); + self.emit_ready_array_typedefs(&array_typedefs); // Collect and generate Vec type definitions let mut vec_types = std::collections::HashSet::new(); @@ -633,6 +592,8 @@ impl Codegen { } } + self.emit_ready_array_typedefs(&array_typedefs); + self.emit_named_drop_functions(); // Generate extern function prototypes @@ -759,11 +720,54 @@ impl Codegen { collect_generic_instantiations(program, &mut generic_instantiations_map); self.generic_instantiations = generic_instantiations_map.clone(); + let array_typedefs = collect_array_typedefs(program); + self.emit_vec_slice_typedefs(program); - self.emit_tuple_typedefs(program); + self.emit_ready_array_typedefs(&array_typedefs); + self.emit_non_generic_type_forwards(program); + self.ensure_option_template(); self.emit_monomorphized_enum_forwards(); + let mut instantiations_vec: Vec<_> = generic_instantiations_map.values().collect(); + instantiations_vec.sort_by_key(|(name, _)| name.clone()); + + let mut struct_instantiations: Vec<(StructDecl, Vec)> = Vec::new(); + let mut early_enum_instantiations: Vec<(EnumDecl, Vec)> = Vec::new(); + let mut late_enum_instantiations: Vec<(EnumDecl, Vec)> = Vec::new(); + + for (base_name, params) in instantiations_vec { + let base_name_clone = base_name.clone(); + let params_clone = params.clone(); + + if let Some(decl) = self.struct_map.get(&base_name_clone) { + struct_instantiations.push((decl.clone(), params_clone)); + } else if let Some(decl) = self.enum_map.get(&base_name_clone) { + if params_complete_with_struct_forwards(¶ms_clone) { + early_enum_instantiations.push((decl.clone(), params_clone)); + } else { + late_enum_instantiations.push((decl.clone(), params_clone)); + } + } + } + + for (decl, params) in &early_enum_instantiations { + self.generate_monomorphized_enum(decl, params); + let key = mangle_type_name(&decl.name, params); + self.generated_types.insert(key, true); + } + for (key, (base_name, params)) in &generic_instantiations_map.clone() { + if base_name == "Option" + && !self.generated_types.contains_key(key) + && params_complete_with_struct_forwards(params) + { + let option_decl = self.enum_map.get("Option").unwrap().clone(); + self.generate_monomorphized_enum(&option_decl, params); + self.generated_types.insert(key.clone(), true); + } + } + self.emit_ready_array_typedefs(&array_typedefs); + for s in &program.structs { if s.generics.is_empty() { self.write(&format!("typedef struct {} {{\n", s.name)); @@ -792,84 +796,27 @@ impl Codegen { self.generate_enum_type(e); } } + self.emit_ready_array_typedefs(&array_typedefs); - // Generate monomorphized struct/enum types - let mut instantiations_vec: Vec<_> = generic_instantiations_map.values().collect(); - instantiations_vec.sort_by_key(|(name, _)| name.clone()); - - let mut struct_instantiations: Vec<(StructDecl, Vec)> = Vec::new(); - let mut enum_instantiations: Vec<(EnumDecl, Vec)> = Vec::new(); - - for (base_name, params) in instantiations_vec { - let base_name_clone = base_name.clone(); - let params_clone = params.clone(); - - if let Some(decl) = self.struct_map.get(&base_name_clone) { - struct_instantiations.push((decl.clone(), params_clone)); - } else if let Some(decl) = self.enum_map.get(&base_name_clone) { - enum_instantiations.push((decl.clone(), params_clone)); - } - } + self.emit_tuple_typedefs(program); for (decl, params) in struct_instantiations { + let key = mangle_type_name(&decl.name, ¶ms); self.generate_monomorphized_struct(&decl, ¶ms); + self.generated_types.insert(key, true); } - for (decl, params) in enum_instantiations { - self.generate_monomorphized_enum(&decl, ¶ms); - } + self.emit_enum_instantiations_ready_first(late_enum_instantiations); + self.emit_ready_array_typedefs(&array_typedefs); - // Handle built-in generic enums like Option that aren't in enum_map - for (key, (base_name, params)) in &generic_instantiations_map { - if base_name == "Option" - && !self.enum_map.contains_key("Option") - && !self.generated_types.contains_key(key) - { - // Create a synthetic EnumDecl for Option - let option_decl = EnumDecl { - doc: None, - pub_: false, - name: "Option".to_string(), - generics: vec![TypeParam::simple("T")], - variants: vec![ - EnumVariant { - doc: None, - name: "Some".to_string(), - payload_types: vec![Type::Generic { - name: "T".to_string(), - params: vec![], - }], - named_fields: None, - span: Span { - start: 0, - end: 0, - line: 0, - column: 0, - }, - }, - EnumVariant { - doc: None, - name: "None".to_string(), - payload_types: vec![], - named_fields: None, - span: Span { - start: 0, - end: 0, - line: 0, - column: 0, - }, - }, - ], - span: Span { - start: 0, - end: 0, - line: 0, - column: 0, - }, - }; + // Remaining Option instantiations that needed complete struct payloads. + for (key, (base_name, params)) in &generic_instantiations_map.clone() { + if base_name == "Option" && !self.generated_types.contains_key(key) { + let option_decl = self.enum_map.get("Option").unwrap().clone(); self.generate_monomorphized_enum(&option_decl, params); self.generated_types.insert(key.clone(), true); } } + self.emit_ready_array_typedefs(&array_typedefs); // Collect and generate Vec type definitions let mut vec_types = std::collections::HashSet::new(); @@ -960,6 +907,8 @@ impl Codegen { } } + self.emit_ready_array_typedefs(&array_typedefs); + self.emit_named_drop_functions(); // Generate extern function prototypes (declarations only, implementations come from headers) @@ -1051,6 +1000,43 @@ impl Codegen { self.writeln("#include \"ion_runtime.h\""); self.writeln(""); + let mut header_arrays: HashMap = HashMap::new(); + for s in &program.structs { + if s.pub_ { + for field in &s.fields { + collect_array_from_type(&field.ty, &mut header_arrays); + } + } + } + for e in &program.enums { + if e.pub_ { + for variant in &e.variants { + for ty in &variant.payload_types { + collect_array_from_type(ty, &mut header_arrays); + } + if let Some(fields) = &variant.named_fields { + for (_, ty) in fields { + collect_array_from_type(ty, &mut header_arrays); + } + } + } + } + } + for f in &program.functions { + if !f.pub_ { + continue; + } + if let Some(ret) = &f.return_type { + collect_array_from_type(ret, &mut header_arrays); + } + for p in &f.params { + collect_array_from_type(&p.ty, &mut header_arrays); + } + } + let mut header_array_typedefs: Vec<(String, Type)> = header_arrays.into_iter().collect(); + header_array_typedefs.sort_by(|a, b| a.0.cmp(&b.0)); + self.emit_ready_array_typedefs(&header_array_typedefs); + // Generate public struct definitions for s in &program.structs { if s.pub_ && s.generics.is_empty() { @@ -3304,10 +3290,9 @@ impl Codegen { } if let Some(ref pty) = param_ty { if matches!(arg, IREexpr::ArrayLiteral { .. }) - && let Type::Array { inner, size } = pty + && let Type::Array { .. } = pty { - let elem_c = self.type_to_c(inner); - self.write(&format!("({}[{}])", elem_c, size)); + self.write(&format!("({})", self.type_to_c(pty))); } self.generate_expr_with_type(arg, Some(pty)); } else { @@ -3770,6 +3755,7 @@ impl Codegen { self.indent_level -= 1; self.writeln(&format!("}} {};", enum_name)); self.writeln(""); + self.generated_types.insert(enum_name.clone(), true); } fn generate_extern_block(&mut self, extern_block: &ExternBlock) { @@ -4273,14 +4259,14 @@ impl Codegen { // Extract field into binding variable // For struct variants, fields are stored in variant_N.field_name self.write_indent(); - self.write(&format!( - "{} {} = {}.data.variant_{}.{};", - self.type_to_c(&concrete_field_ty), + self.emit_binding_from_c_expr( + &concrete_field_ty, name, - match_var_name, - variant_idx, - field_name - )); + &format!( + "{}.data.variant_{}.{}", + match_var_name, variant_idx, field_name + ), + ); self.writeln(""); self.emit_match_scrutinee_payload_moved_out( match_var_name, @@ -4301,14 +4287,14 @@ impl Codegen { // Nested variant pattern - extract to temp let temp_name = format!("_field_{}", field_name); self.write_indent(); - self.write(&format!( - "{} {} = {}.data.variant_{}.{};", - self.type_to_c(&concrete_field_ty), - temp_name, - match_var_name, - variant_idx, - field_name - )); + self.emit_binding_from_c_expr( + &concrete_field_ty, + &temp_name, + &format!( + "{}.data.variant_{}.{}", + match_var_name, variant_idx, field_name + ), + ); self.writeln(""); self.emit_match_scrutinee_payload_moved_out( match_var_name, @@ -4367,14 +4353,14 @@ impl Codegen { self.scope_register_binding(name, inner); } } else { - self.write(&format!( - "{} {} = {}.data.variant_{}.{};", - self.type_to_c(&concrete_payload_ty), + self.emit_binding_from_c_expr( + &concrete_payload_ty, name, - match_var_name, - variant_idx, - payload_field - )); + &format!( + "{}.data.variant_{}.{}", + match_var_name, variant_idx, payload_field + ), + ); self.writeln(""); self.emit_match_scrutinee_payload_moved_out( match_var_name, @@ -4404,14 +4390,14 @@ impl Codegen { let payload_field = format!("arg{i}"); let temp_name = format!("_payload_{}", i); self.write_indent(); - self.write(&format!( - "{} {} = {}.data.variant_{}.{};", - self.type_to_c(&concrete_payload_ty), - temp_name, - match_var_name, - variant_idx, - payload_field - )); + self.emit_binding_from_c_expr( + &concrete_payload_ty, + &temp_name, + &format!( + "{}.data.variant_{}.{}", + match_var_name, variant_idx, payload_field + ), + ); self.writeln(""); self.emit_match_scrutinee_payload_moved_out( match_var_name, @@ -4429,6 +4415,19 @@ impl Codegen { } } + /// Bind `dest` from a C rvalue. Arrays are not assignable, so copy with memcpy. + fn emit_binding_from_c_expr(&mut self, ty: &Type, dest: &str, src: &str) { + let c_ty = self.type_to_c(ty); + if matches!(ty, Type::Array { .. }) { + self.write(&format!("{c_ty} {dest};")); + self.writeln(""); + self.write_indent(); + self.write(&format!("memcpy(&{dest}, &({src}), sizeof({dest}));")); + } else { + self.write(&format!("{c_ty} {dest} = {src};")); + } + } + #[allow(clippy::too_many_arguments)] fn generate_match_arm( &mut self, @@ -4614,6 +4613,254 @@ impl Codegen { } } +fn visit_enum_payload_types(program: &IRProgram, visit: &mut impl FnMut(&Type)) { + for e in &program.enums { + for variant in &e.variants { + for ty in &variant.payload_types { + visit(ty); + } + if let Some(fields) = &variant.named_fields { + for (_, ty) in fields { + visit(ty); + } + } + } + } +} + +fn collect_array_typedefs(program: &IRProgram) -> Vec<(String, Type)> { + let mut arrays: HashMap = HashMap::new(); + for function in &program.functions { + if let Some(ref ret_ty) = function.return_type { + collect_array_from_type(ret_ty, &mut arrays); + } + for param in &function.params { + collect_array_from_type(¶m.ty, &mut arrays); + } + for block in &function.blocks { + for stmt in &block.statements { + collect_array_from_stmt(stmt, &mut arrays); + } + } + } + for s in &program.structs { + for field in &s.fields { + collect_array_from_type(&field.ty, &mut arrays); + } + } + visit_enum_payload_types(program, &mut |ty| { + collect_array_from_type(ty, &mut arrays); + }); + let mut out: Vec<(String, Type)> = arrays.into_iter().collect(); + out.sort_by(|a, b| a.0.cmp(&b.0)); + out +} + +fn collect_array_from_type(ty: &Type, arrays: &mut HashMap) { + match ty { + Type::Array { inner, size } => { + arrays.insert(array_type_name(inner, *size), ty.clone()); + collect_array_from_type(inner, arrays); + } + Type::Ref { inner, .. } + | Type::RawPtr { inner } + | Type::Box { inner } + | Type::Slice { inner } => collect_array_from_type(inner, arrays), + Type::Vec { elem_type } + | Type::Channel { elem_type } + | Type::Sender { elem_type } + | Type::Receiver { elem_type } => collect_array_from_type(elem_type, arrays), + Type::Tuple { elements } => { + for elem in elements { + collect_array_from_type(elem, arrays); + } + } + Type::Generic { params, .. } => { + for param in params { + collect_array_from_type(param, arrays); + } + } + Type::Fn { + params, + return_type, + } => { + for param in params { + collect_array_from_type(param, arrays); + } + collect_array_from_type(return_type, arrays); + } + _ => {} + } +} + +fn collect_array_from_stmt(stmt: &IRStmt, arrays: &mut HashMap) { + match stmt { + IRStmt::Let(let_stmt) => { + collect_array_from_type(&let_stmt.ty, arrays); + if let Some(ref init) = let_stmt.init { + collect_array_from_expr(init, arrays); + } + } + IRStmt::Return(ret) => { + if let Some(ref value) = ret.value { + collect_array_from_expr(value, arrays); + } + } + IRStmt::Break | IRStmt::Continue => {} + IRStmt::Expr(expr) | IRStmt::Defer(expr) => collect_array_from_expr(expr, arrays), + IRStmt::If(ir_if) => { + collect_array_from_expr(&ir_if.cond, arrays); + for stmt in &ir_if.then_block.statements { + collect_array_from_stmt(stmt, arrays); + } + if let Some(ref else_block) = ir_if.else_block { + for stmt in &else_block.statements { + collect_array_from_stmt(stmt, arrays); + } + } + } + IRStmt::While(ir_while) => { + collect_array_from_expr(&ir_while.cond, arrays); + for stmt in &ir_while.body.statements { + collect_array_from_stmt(stmt, arrays); + } + if let Some(ref step) = ir_while.step { + for stmt in &step.statements { + collect_array_from_stmt(stmt, arrays); + } + } + } + IRStmt::Spawn(spawn) => { + for stmt in &spawn.body.statements { + collect_array_from_stmt(stmt, arrays); + } + } + IRStmt::UnsafeBlock(unsafe_block) => { + for stmt in &unsafe_block.body.statements { + collect_array_from_stmt(stmt, arrays); + } + } + } +} + +fn collect_array_from_expr(expr: &IREexpr, arrays: &mut HashMap) { + match expr { + IREexpr::Call { + return_type, args, .. + } => { + if let Some(ret_ty) = return_type { + collect_array_from_type(ret_ty, arrays); + } + for arg in args { + collect_array_from_expr(arg, arrays); + } + } + IREexpr::TupleLit { + elem_types, + elements, + .. + } => { + for ty in elem_types { + collect_array_from_type(ty, arrays); + } + for elem in elements { + collect_array_from_expr(elem, arrays); + } + } + IREexpr::Recv { + elem_type, channel, .. + } => { + collect_array_from_type(elem_type, arrays); + collect_array_from_expr(channel, arrays); + } + IREexpr::FnLiteral(lit) => { + for param in &lit.params { + collect_array_from_type(¶m.ty, arrays); + } + if let Some(ret) = &lit.return_type { + collect_array_from_type(ret, arrays); + } + for stmt in &lit.body.statements { + collect_array_from_stmt(stmt, arrays); + } + } + IREexpr::BinOp { left, right, .. } => { + collect_array_from_expr(left, arrays); + collect_array_from_expr(right, arrays); + } + IREexpr::UnOp { operand, .. } | IREexpr::AddressOf { inner: operand, .. } => { + collect_array_from_expr(operand, arrays); + } + IREexpr::Send { channel, value, .. } => { + collect_array_from_expr(channel, arrays); + collect_array_from_expr(value, arrays); + } + IREexpr::StructLit { fields, .. } => { + for field in fields { + collect_array_from_expr(&field.value, arrays); + } + } + IREexpr::FieldAccess { base, .. } => collect_array_from_expr(base, arrays), + IREexpr::EnumLit { + args, named_fields, .. + } => { + for arg in args { + collect_array_from_expr(arg, arrays); + } + if let Some(fields) = named_fields { + for (_, value) in fields { + collect_array_from_expr(value, arrays); + } + } + } + IREexpr::Match { expr, arms, .. } => { + collect_array_from_expr(expr, arrays); + for arm in arms { + for stmt in &arm.body.statements { + collect_array_from_stmt(stmt, arrays); + } + } + } + IREexpr::ArrayLiteral { + elements, repeat, .. + } => { + for elem in elements { + collect_array_from_expr(elem, arrays); + } + if let Some((value_expr, _)) = repeat { + collect_array_from_expr(value_expr, arrays); + } + } + IREexpr::Index { + target, + index, + target_type, + } => { + collect_array_from_expr(target, arrays); + collect_array_from_expr(index, arrays); + if let Some(ty) = target_type { + collect_array_from_type(ty, arrays); + } + } + IREexpr::Cast { expr, .. } => collect_array_from_expr(expr, arrays), + IREexpr::Assign { value, .. } => collect_array_from_expr(value, arrays), + IREexpr::AssignIndex { + target, + index, + value, + } => { + collect_array_from_expr(target, arrays); + collect_array_from_expr(index, arrays); + collect_array_from_expr(value, arrays); + } + IREexpr::AssignField { target, value } => { + collect_array_from_expr(target, arrays); + collect_array_from_expr(value, arrays); + } + _ => {} + } +} + fn collect_slice_types_impl( program: &IRProgram, slice_types: &mut std::collections::HashSet, @@ -4641,6 +4888,9 @@ fn collect_slice_types_impl( collect_slice_types_from_type(&field.ty, slice_types); } } + visit_enum_payload_types(program, &mut |ty| { + collect_slice_types_from_type(ty, slice_types); + }); } fn collect_slice_types_from_type(ty: &Type, slice_types: &mut std::collections::HashSet) { @@ -4897,6 +5147,9 @@ fn collect_tuple_types_impl( collect_tuple_types_from_type(&field.ty, tuple_types); } } + visit_enum_payload_types(program, &mut |ty| { + collect_tuple_types_from_type(ty, tuple_types); + }); } fn collect_tuple_types_from_type( @@ -5127,6 +5380,9 @@ fn collect_vec_types_impl(program: &IRProgram, vec_types: &mut std::collections: collect_vec_types_from_type(&field.ty, vec_types); } } + visit_enum_payload_types(program, &mut |ty| { + collect_vec_types_from_type(ty, vec_types); + }); } fn collect_vec_types_from_type(ty: &Type, vec_types: &mut std::collections::HashSet) { @@ -5154,6 +5410,9 @@ fn collect_vec_types_from_type(ty: &Type, vec_types: &mut std::collections::Hash Type::Channel { elem_type } => { collect_vec_types_from_type(elem_type, vec_types); } + Type::Array { inner, .. } | Type::Slice { inner } => { + collect_vec_types_from_type(inner, vec_types); + } Type::Tuple { elements } => { for elem in elements { collect_vec_types_from_type(elem, vec_types); @@ -5337,6 +5596,112 @@ impl Codegen { self.output.insert_str(insert_at, &forward_decls); } + fn ensure_option_template(&mut self) { + if self.enum_map.contains_key("Option") { + return; + } + let option_template = EnumDecl { + doc: None, + pub_: false, + name: "Option".to_string(), + generics: vec![TypeParam::simple("T")], + variants: vec![ + EnumVariant { + doc: None, + name: "Some".to_string(), + payload_types: vec![Type::Generic { + name: "T".to_string(), + params: vec![], + }], + named_fields: None, + span: Span { + start: 0, + end: 0, + line: 0, + column: 0, + }, + }, + EnumVariant { + doc: None, + name: "None".to_string(), + payload_types: vec![], + named_fields: None, + span: Span { + start: 0, + end: 0, + line: 0, + column: 0, + }, + }, + ], + span: Span { + start: 0, + end: 0, + line: 0, + column: 0, + }, + }; + self.enum_map.insert("Option".to_string(), option_template); + } + + fn emit_non_generic_type_forwards(&mut self, program: &IRProgram) { + let mut any = false; + for s in &program.structs { + if s.generics.is_empty() { + self.writeln(&format!("typedef struct {} {};", s.name, s.name)); + any = true; + } + } + for e in &program.enums { + if e.generics.is_empty() { + self.writeln(&format!("typedef struct {} {};", e.name, e.name)); + any = true; + } + } + if any { + self.writeln(""); + } + } + + fn emit_ready_array_typedefs(&mut self, arrays: &[(String, Type)]) { + let none: HashSet = HashSet::new(); + loop { + let mut progressed = false; + for (name, ty) in arrays { + if self.generated_types.contains_key(name) { + continue; + } + let Type::Array { inner, size } = ty else { + continue; + }; + if !type_ready_for_by_value(inner, &self.generated_types, &none) { + continue; + } + if let Type::Array { + inner: nested, + size: nested_size, + } = inner.as_ref() + && !self + .generated_types + .contains_key(&array_type_name(nested, *nested_size)) + { + continue; + } + self.writeln(&format!( + "typedef {} {}[{}];", + self.type_to_c(inner), + name, + size + )); + self.generated_types.insert(name.clone(), true); + progressed = true; + } + if !progressed { + break; + } + } + } + fn emit_vec_slice_typedefs(&mut self, program: &IRProgram) { let mut vec_types = std::collections::HashSet::new(); collect_vec_types_impl(program, &mut vec_types); @@ -5669,6 +6034,38 @@ fn substitute_generic_types(ty: &Type, substitutions: &HashMap) - Type::Channel { elem_type } => Type::Channel { elem_type: Box::new(substitute_generic_types(elem_type, substitutions)), }, + Type::Array { inner, size } => Type::Array { + inner: Box::new(substitute_generic_types(inner, substitutions)), + size: *size, + }, + Type::Slice { inner } => Type::Slice { + inner: Box::new(substitute_generic_types(inner, substitutions)), + }, + Type::RawPtr { inner } => Type::RawPtr { + inner: Box::new(substitute_generic_types(inner, substitutions)), + }, + Type::Sender { elem_type } => Type::Sender { + elem_type: Box::new(substitute_generic_types(elem_type, substitutions)), + }, + Type::Receiver { elem_type } => Type::Receiver { + elem_type: Box::new(substitute_generic_types(elem_type, substitutions)), + }, + Type::Tuple { elements } => Type::Tuple { + elements: elements + .iter() + .map(|e| substitute_generic_types(e, substitutions)) + .collect(), + }, + Type::Fn { + params, + return_type, + } => Type::Fn { + params: params + .iter() + .map(|p| substitute_generic_types(p, substitutions)) + .collect(), + return_type: Box::new(substitute_generic_types(return_type, substitutions)), + }, Type::Generic { name, params } => { // First check if the generic name itself is a generic parameter (e.g., T in Option) if let Some(substituted) = substitutions.get(name) { @@ -5759,10 +6156,26 @@ fn type_ready_for_by_value( Type::Channel { elem_type } | Type::Sender { elem_type } | Type::Receiver { elem_type } => { type_ready_for_by_value(elem_type, generated, user_structs) } - Type::Array { inner, .. } => type_ready_for_by_value(inner, generated, user_structs), - Type::Tuple { elements } => elements - .iter() - .all(|elem| type_ready_for_by_value(elem, generated, user_structs)), + Type::Array { inner, .. } => match inner.as_ref() { + Type::Array { + inner: nested, + size, + } => { + generated.contains_key(&array_type_name(nested, *size)) + && type_ready_for_by_value(inner, generated, user_structs) + } + Type::Tuple { elements } => { + generated.contains_key(&tuple_type_name(elements)) + && type_ready_for_by_value(inner, generated, user_structs) + } + _ => type_ready_for_by_value(inner, generated, user_structs), + }, + Type::Tuple { elements } => { + generated.contains_key(&tuple_type_name(elements)) + && elements + .iter() + .all(|elem| type_ready_for_by_value(elem, generated, user_structs)) + } Type::Struct(name) | Type::Enum(name) => { generated.contains_key(name) || user_structs.contains(name) } @@ -5796,11 +6209,79 @@ fn collect_generic_instantiations( collect_generic_from_type(&field.ty, instantiations); } } + visit_enum_payload_types(program, &mut |ty| { + collect_generic_from_type(ty, instantiations); + }); + close_generic_instantiations(program, instantiations); let known = known_type_names(program); instantiations.retain(|_, (_, params)| params_are_bound(params, &known)); } +fn close_generic_instantiations( + program: &IRProgram, + instantiations: &mut std::collections::HashMap)>, +) { + loop { + let current: Vec<(String, Vec)> = instantiations.values().cloned().collect(); + let before = instantiations.len(); + for (base_name, params) in current { + if !type_params_are_concrete(¶ms) { + continue; + } + if let Some(decl) = program.structs.iter().find(|s| s.name == base_name) + && !decl.generics.is_empty() + { + let owned: Vec<(String, Type)> = decl + .generics + .iter() + .zip(params.iter()) + .map(|(g, t)| (g.name.clone(), t.clone())) + .collect(); + let subst: HashMap = + owned.iter().map(|(n, t)| (n.clone(), t)).collect(); + for field in &decl.fields { + collect_generic_from_type( + &substitute_generic_types(&field.ty, &subst), + instantiations, + ); + } + } + if let Some(decl) = program.enums.iter().find(|e| e.name == base_name) + && !decl.generics.is_empty() + { + let owned: Vec<(String, Type)> = decl + .generics + .iter() + .zip(params.iter()) + .map(|(g, t)| (g.name.clone(), t.clone())) + .collect(); + let subst: HashMap = + owned.iter().map(|(n, t)| (n.clone(), t)).collect(); + for variant in &decl.variants { + for ty in &variant.payload_types { + collect_generic_from_type( + &substitute_generic_types(ty, &subst), + instantiations, + ); + } + if let Some(fields) = &variant.named_fields { + for (_, ty) in fields { + collect_generic_from_type( + &substitute_generic_types(ty, &subst), + instantiations, + ); + } + } + } + } + } + if instantiations.len() == before { + break; + } + } +} + fn known_type_names(program: &IRProgram) -> HashSet { let mut names: HashSet = ["Vec", "Box", "Option", "Result"] .into_iter() diff --git a/src/cgen/types.rs b/src/cgen/types.rs index 75ed7bc..efdb309 100644 --- a/src/cgen/types.rs +++ b/src/cgen/types.rs @@ -11,6 +11,11 @@ pub(crate) fn mangle_type_name(base: &str, params: &[Type]) -> String { } } +/// C typedef name for `[T; N]`, e.g. `[int; 2]` -> `arr_int_2`, `[[int; 2]; 3]` -> `arr_arr_int_2_3`. +pub(crate) fn array_type_name(inner: &Type, size: usize) -> String { + format!("arr_{}_{}", mangle_type_component(inner), size) +} + fn mangle_type_component(ty: &Type) -> String { match ty { Type::Int => "int".to_string(), @@ -22,6 +27,7 @@ fn mangle_type_component(ty: &Type) -> String { Type::Struct(name) | Type::Enum(name) => name.clone(), Type::Vec { elem_type } => format!("Vec_{}", mangle_type_component(elem_type)), Type::Box { inner } => format!("Box_{}", mangle_type_component(inner)), + Type::Array { inner, size } => array_type_name(inner, *size), Type::Generic { name, params } if name == "Vec" && params.len() == 1 => { format!("Vec_{}", mangle_type_component(¶ms[0])) } @@ -392,8 +398,9 @@ pub(crate) fn type_to_c_impl(ty: &Type) -> String { Type::String => "ion_string_t*".to_string(), Type::Str => "char".to_string(), Type::Array { inner, size } => { - // Fixed-size arrays: [T; N] -> T name[N] - format!("{}[{}]", type_to_c_impl(inner), size) + // Named typedef so the array can appear as a C type specifier + // (`Box<[T; N]>` -> `arr_T_N*`, nested arrays, `sizeof`). + array_type_name(inner, *size) } Type::Slice { inner } => { // Slices: []T -> ion_slice_T (fat pointer struct) diff --git a/tests/README.md b/tests/README.md index aaa348f..9044527 100644 --- a/tests/README.md +++ b/tests/README.md @@ -132,6 +132,14 @@ The test runner prints pass/fail counts when it finishes. Do not rely on hardcod - `test_array_result_err.ion` - `[Result::Err(Option::None)]` infers payload from the array element type (exit 10) - `test_assign_option_none.ion` - `x = Option::None` infers from the left-hand side type (exit 11) - `test_return_array_option_none.ion` - `return [Option::None]` infers from the function return type (exit 12) +- `test_nested_array.ion` - 2-D `[int; 2]` local, index, and index-assign (exit 17); cgen `typedef int arr_int_2[2];` +- `test_nested_array_field_param_return.ion` - 2-D array struct field, parameter, and return (exit 7) +- `test_nested_array_3d.ion` - 3-D array local (exit 6) +- `test_box_array.ion` - `Box<[int; 2]>` (exit 30); cgen `arr_int_2*` +- `test_vec_array.ion` - `Vec<[int; 2]>` push/get (exit 7); cgen `Vec_arr_int_2` +- `test_enum_option_payload_none.ion` - `Hold::H(Option::None)` emits `Option_int` (exit 13) +- `test_enum_option_payload_some.ion` - `Hold::H(Option::Some(14))` (exit 14) +- `test_generic_struct_option_none.ion` - `S { x: Option::None }` emits `Option_int` (exit 15) - `test_unannotated_let_non_int.ion` - unannotated `let q = p` / `let n = w.p` / `let v = origin()` keep struct types, not default int (exit 5); cgen asserts `Point q =` / `Point n =` / `Point v =` - `test_enum_generic.ion` - Generic enum types - `test_result_custom_enum.ion` - `Result` via `stdlib/result.ion` (Ok and Err, exit 0) diff --git a/tests/test_box_array.ion b/tests/test_box_array.ion new file mode 100644 index 0000000..ed73157 --- /dev/null +++ b/tests/test_box_array.ion @@ -0,0 +1,5 @@ +fn main() -> int { + let b: Box<[int; 2]> = Box::new([10, 20]); + let a = Box::unwrap(b); + return a[0] + a[1]; +} diff --git a/tests/test_enum_option_payload_none.ion b/tests/test_enum_option_payload_none.ion new file mode 100644 index 0000000..4d09b26 --- /dev/null +++ b/tests/test_enum_option_payload_none.ion @@ -0,0 +1,25 @@ +// Generic enum used only as another enum's payload must still emit Option_int. +enum Option { + Some(T); + None; +} + +enum Hold { + H(Option); +} + +fn main() -> int { + let x: Hold = Hold::H(Option::None); + match x { + Hold::H(inner) => { + match inner { + Option::None => { + return 13; + } + Option::Some(_) => { + return 1; + } + } + } + } +} diff --git a/tests/test_enum_option_payload_some.ion b/tests/test_enum_option_payload_some.ion new file mode 100644 index 0000000..102af87 --- /dev/null +++ b/tests/test_enum_option_payload_some.ion @@ -0,0 +1,24 @@ +enum Option { + Some(T); + None; +} + +enum Hold { + H(Option); +} + +fn main() -> int { + let x: Hold = Hold::H(Option::Some(14)); + match x { + Hold::H(inner) => { + match inner { + Option::Some(v) => { + return v; + } + Option::None => { + return 1; + } + } + } + } +} diff --git a/tests/test_expectations.tsv b/tests/test_expectations.tsv index 38ae677..1c051ea 100644 --- a/tests/test_expectations.tsv +++ b/tests/test_expectations.tsv @@ -79,6 +79,19 @@ test_tuple_of_arrays_option_none.ion run 9 test_array_result_err.ion run 10 test_assign_option_none.ion run 11 test_return_array_option_none.ion run 12 +test_nested_array.ion run 17 +test_nested_array.ion cgen typedef int arr_int_2[2]; +test_nested_array_field_param_return.ion run 7 +test_nested_array_3d.ion run 6 +test_box_array.ion run 30 +test_box_array.ion cgen arr_int_2* +test_vec_array.ion run 7 +test_vec_array.ion cgen Vec_arr_int_2 +test_enum_option_payload_none.ion run 13 +test_enum_option_payload_none.ion cgen Option_int +test_enum_option_payload_some.ion run 14 +test_generic_struct_option_none.ion run 15 +test_generic_struct_option_none.ion cgen Option_int test_vec_string_scope_drop.ion run 0 test_vec_string_scope_drop.ion cgen ion_string_free(((ion_string_t**)((v)->data)) test_vec_string_scope_drop.ion cgen ion_vec_free((ion_vec_t*)(v)) diff --git a/tests/test_generic_struct_option_none.ion b/tests/test_generic_struct_option_none.ion new file mode 100644 index 0000000..2c5f7d7 --- /dev/null +++ b/tests/test_generic_struct_option_none.ion @@ -0,0 +1,21 @@ +// Instantiating S must emit Option_int from the substituted field type. +enum Option { + Some(T); + None; +} + +struct S { + x: Option; +} + +fn main() -> int { + let s: S = S { x: Option::None }; + match s.x { + Option::None => { + return 15; + } + Option::Some(_) => { + return 1; + } + } +} diff --git a/tests/test_nested_array.ion b/tests/test_nested_array.ion new file mode 100644 index 0000000..d808547 --- /dev/null +++ b/tests/test_nested_array.ion @@ -0,0 +1,6 @@ +// Nested `[T; N]` locals, index, and index-assign need a C array typedef name. +fn main() -> int { + let mut grid: [[int; 2]; 2] = [[1, 2], [3, 4]]; + grid[0][1] = 9; + return grid[0][0] + grid[0][1] + grid[1][0] + grid[1][1]; +} diff --git a/tests/test_nested_array_3d.ion b/tests/test_nested_array_3d.ion new file mode 100644 index 0000000..fa85553 --- /dev/null +++ b/tests/test_nested_array_3d.ion @@ -0,0 +1,4 @@ +fn main() -> int { + let cube: [[[int; 2]; 2]; 2] = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]; + return cube[1][0][1]; +} diff --git a/tests/test_nested_array_field_param_return.ion b/tests/test_nested_array_field_param_return.ion new file mode 100644 index 0000000..889817d --- /dev/null +++ b/tests/test_nested_array_field_param_return.ion @@ -0,0 +1,20 @@ +// Nested arrays as struct fields, parameters, and return types. +struct Grid { + cells: [[int; 2]; 2]; +} + +fn corner_sum(g: [[int; 2]; 2]) -> int { + return g[0][0] + g[1][1]; +} + +fn make() -> [[int; 2]; 2] { + return [[1, 2], [3, 4]]; +} + +fn main() -> int { + let g: Grid = Grid { + cells: [[1, 2], [3, 4]], + }; + let built = make(); + return corner_sum(g.cells) + built[0][1]; +} diff --git a/tests/test_vec_array.ion b/tests/test_vec_array.ion new file mode 100644 index 0000000..8a7b75c --- /dev/null +++ b/tests/test_vec_array.ion @@ -0,0 +1,17 @@ +enum Option { + Some(T); + None; +} + +fn main() -> int { + let mut v: Vec<[int; 2]> = Vec::new(); + Vec::push(&mut v, [3, 4]); + match Vec::get(&mut v, 0) { + Option::Some(row) => { + return row[0] + row[1]; + } + Option::None => { + return 1; + } + } +}