From 0a1a68fe933ad36113cbcd1cf7734166daea9964 Mon Sep 17 00:00:00 2001 From: Mohammad Julfikar Date: Thu, 22 Jan 2026 15:42:11 +0800 Subject: [PATCH 1/3] feat(tier3): implement convenience features MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Type casting: string conversions with `as` operator (int↔string, float↔string, bytes↔string) - Range expressions: `0..10` (exclusive) and `0..=10` (inclusive) in for loops - String methods: `s.len()` and `s.char_at(index)` for character iteration - Printf format strings: `{}` placeholder support in print/println - Bytes type: heap-allocated byte arrays with string conversion - Fix simple.naml: remove async/await (unsupported), comment out advanced FP utilities Co-Authored-By: Claude Opus 4.5 --- examples/simple.naml | 136 +++--- examples/tier3_bytes.naml | 27 ++ examples/tier3_casting.naml | 44 ++ examples/tier3_mut_self.naml | 80 ++++ examples/tier3_printf.naml | 36 ++ examples/tier3_ranges.naml | 51 +++ examples/tier3_string_iter.naml | 45 ++ namlc/src/codegen/cranelift/mod.rs | 658 ++++++++++++++++++++++++----- namlc/src/runtime/bytes.rs | 182 ++++++++ namlc/src/runtime/mod.rs | 2 + namlc/src/runtime/value.rs | 65 +++ namlc/src/typechecker/infer.rs | 40 ++ 12 files changed, 1197 insertions(+), 169 deletions(-) create mode 100644 examples/tier3_bytes.naml create mode 100644 examples/tier3_casting.naml create mode 100644 examples/tier3_mut_self.naml create mode 100644 examples/tier3_printf.naml create mode 100644 examples/tier3_ranges.naml create mode 100644 examples/tier3_string_iter.naml create mode 100644 namlc/src/runtime/bytes.rs diff --git a/examples/simple.naml b/examples/simple.naml index f986806..01746d2 100644 --- a/examples/simple.naml +++ b/examples/simple.naml @@ -465,11 +465,11 @@ pub fn (self: HttpResponse) is_server_error() -> bool { } interface HttpClient { - async fn get(url: string) -> HttpResponse throws NetworkError; - async fn post(url: string, body: bytes) -> HttpResponse throws NetworkError; - async fn put(url: string, body: bytes) -> HttpResponse throws NetworkError; - async fn delete(url: string) -> HttpResponse throws NetworkError; - async fn request(req: HttpRequest) -> HttpResponse throws NetworkError; + fn get(url: string) -> HttpResponse throws NetworkError; + fn post(url: string, body: bytes) -> HttpResponse throws NetworkError; + fn put(url: string, body: bytes) -> HttpResponse throws NetworkError; + fn delete(url: string) -> HttpResponse throws NetworkError; + fn request(req: HttpRequest) -> HttpResponse throws NetworkError; } pub struct SimpleHttpClient implements HttpClient { @@ -480,7 +480,7 @@ pub struct SimpleHttpClient implements HttpClient { pub retry_delay_ms: int } -pub async fn (self: SimpleHttpClient) get(url: string) -> HttpResponse throws NetworkError { +pub fn (self: SimpleHttpClient) get(url: string) -> HttpResponse throws NetworkError { var req: HttpRequest = HttpRequest { method: "GET", url: self.base_url + url, @@ -488,10 +488,10 @@ pub async fn (self: SimpleHttpClient) get(url: string) -> HttpResponse throws Ne body: none, timeout_ms: self.timeout_ms }; - return await self.request(req); + return self.request(req); } -pub async fn (self: SimpleHttpClient) post(url: string, body: bytes) -> HttpResponse throws NetworkError { +pub fn (self: SimpleHttpClient) post(url: string, body: bytes) -> HttpResponse throws NetworkError { var req: HttpRequest = HttpRequest { method: "POST", url: self.base_url + url, @@ -499,10 +499,10 @@ pub async fn (self: SimpleHttpClient) post(url: string, body: bytes) -> HttpResp body: some(body), timeout_ms: self.timeout_ms }; - return await self.request(req); + return self.request(req); } -pub async fn (self: SimpleHttpClient) put(url: string, body: bytes) -> HttpResponse throws NetworkError { +pub fn (self: SimpleHttpClient) put(url: string, body: bytes) -> HttpResponse throws NetworkError { var req: HttpRequest = HttpRequest { method: "PUT", url: self.base_url + url, @@ -510,10 +510,10 @@ pub async fn (self: SimpleHttpClient) put(url: string, body: bytes) -> HttpRespo body: some(body), timeout_ms: self.timeout_ms }; - return await self.request(req); + return self.request(req); } -pub async fn (self: SimpleHttpClient) delete(url: string) -> HttpResponse throws NetworkError { +pub fn (self: SimpleHttpClient) delete(url: string) -> HttpResponse throws NetworkError { var req: HttpRequest = HttpRequest { method: "DELETE", url: self.base_url + url, @@ -521,13 +521,13 @@ pub async fn (self: SimpleHttpClient) delete(url: string) -> HttpResponse throws body: none, timeout_ms: self.timeout_ms }; - return await self.request(req); + return self.request(req); } -pub async fn (self: SimpleHttpClient) request(req: HttpRequest) -> HttpResponse throws NetworkError { +pub fn (self: SimpleHttpClient) request(req: HttpRequest) -> HttpResponse throws NetworkError { var attempts: int = 0; while (attempts < self.retry_count) { - var response: HttpResponse = await self.do_request(req); + var response: HttpResponse = self.do_request(req); if (response.is_success()) { return response; } @@ -540,7 +540,7 @@ pub async fn (self: SimpleHttpClient) request(req: HttpRequest) -> HttpResponse } attempts = attempts + 1; if (attempts < self.retry_count) { - await sleep(self.retry_delay_ms); + sleep(self.retry_delay_ms); } } throw NetworkError { @@ -550,7 +550,7 @@ pub async fn (self: SimpleHttpClient) request(req: HttpRequest) -> HttpResponse }; } -async fn (self: SimpleHttpClient) do_request(req: HttpRequest) -> HttpResponse { +fn (self: SimpleHttpClient) do_request(req: HttpRequest) -> HttpResponse { return HttpResponse { status_code: 200, status_text: "OK", @@ -567,12 +567,12 @@ extern fn sleep(ms: int) -> promise; // ============================================================================= interface Repository { - async fn find_by_id(id: ID) -> option throws DatabaseError; - async fn find_all() -> [T] throws DatabaseError; - async fn save(entity: T) -> T throws DatabaseError; - async fn delete(id: ID) -> bool throws DatabaseError; - async fn exists(id: ID) -> bool throws DatabaseError; - async fn count() -> int throws DatabaseError; + fn find_by_id(id: ID) -> option throws DatabaseError; + fn find_all() -> [T] throws DatabaseError; + fn save(entity: T) -> T throws DatabaseError; + fn delete(id: ID) -> bool throws DatabaseError; + fn exists(id: ID) -> bool throws DatabaseError; + fn count() -> int throws DatabaseError; } pub struct UserRepository implements Repository { @@ -580,18 +580,18 @@ pub struct UserRepository implements Repository { pub table_name: string } -pub async fn (self: UserRepository) find_by_id(id: UserId) -> option throws DatabaseError { +pub fn (self: UserRepository) find_by_id(id: UserId) -> option throws DatabaseError { var query: string = "SELECT * FROM " + self.table_name + " WHERE id = ?"; - var result: [map] = await self.execute_query(query, [id.value]); + var result: [map] = self.execute_query(query, [id.value]); if (result.length == 0) { return none; } return some(self.map_row_to_user(result[0])); } -pub async fn (self: UserRepository) find_all() -> [User] throws DatabaseError { +pub fn (self: UserRepository) find_all() -> [User] throws DatabaseError { var query: string = "SELECT * FROM " + self.table_name; - var result: [map] = await self.execute_query(query, []); + var result: [map] = self.execute_query(query, []); var users: [User] = []; for (row in result) { users.push(self.map_row_to_user(row)); @@ -599,51 +599,51 @@ pub async fn (self: UserRepository) find_all() -> [User] throws DatabaseError { return users; } -pub async fn (self: UserRepository) save(entity: User) -> User throws DatabaseError { - var existing: option = await self.find_by_id(entity.id); +pub fn (self: UserRepository) save(entity: User) -> User throws DatabaseError { + var existing: option = self.find_by_id(entity.id); if (existing.is_some()) { - return await self.update(entity); + return self.update(entity); } - return await self.insert(entity); + return self.insert(entity); } -async fn (self: UserRepository) insert(entity: User) -> User throws DatabaseError { +fn (self: UserRepository) insert(entity: User) -> User throws DatabaseError { var query: string = "INSERT INTO " + self.table_name + " (id, email, username) VALUES (?, ?, ?)"; - await self.execute_update(query, [entity.id.value, entity.email.to_string(), entity.username]); + self.execute_update(query, [entity.id.value, entity.email.to_string(), entity.username]); return entity; } -async fn (self: UserRepository) update(entity: User) -> User throws DatabaseError { +fn (self: UserRepository) update(entity: User) -> User throws DatabaseError { var query: string = "UPDATE " + self.table_name + " SET email = ?, username = ? WHERE id = ?"; - await self.execute_update(query, [entity.email.to_string(), entity.username, entity.id.value]); + self.execute_update(query, [entity.email.to_string(), entity.username, entity.id.value]); return entity; } -pub async fn (self: UserRepository) delete(id: UserId) -> bool throws DatabaseError { +pub fn (self: UserRepository) delete(id: UserId) -> bool throws DatabaseError { var query: string = "DELETE FROM " + self.table_name + " WHERE id = ?"; - var affected: int = await self.execute_update(query, [id.value]); + var affected: int = self.execute_update(query, [id.value]); return affected > 0; } -pub async fn (self: UserRepository) exists(id: UserId) -> bool throws DatabaseError { +pub fn (self: UserRepository) exists(id: UserId) -> bool throws DatabaseError { var query: string = "SELECT COUNT(*) FROM " + self.table_name + " WHERE id = ?"; - var result: [map] = await self.execute_query(query, [id.value]); + var result: [map] = self.execute_query(query, [id.value]); var count_str: string = result[0]["count"].or_default("0"); return count_str > "0"; } -pub async fn (self: UserRepository) count() -> int throws DatabaseError { +pub fn (self: UserRepository) count() -> int throws DatabaseError { var query: string = "SELECT COUNT(*) as cnt FROM " + self.table_name; - var result: [map] = await self.execute_query(query, []); + var result: [map] = self.execute_query(query, []); var cnt_str: string = result[0]["cnt"].or_default("0"); return cnt_str as int; } -async fn (self: UserRepository) execute_query(query: string, params: [string]) -> [map] throws DatabaseError { +fn (self: UserRepository) execute_query(query: string, params: [string]) -> [map] throws DatabaseError { return []; } -async fn (self: UserRepository) execute_update(query: string, params: [string]) -> int throws DatabaseError { +fn (self: UserRepository) execute_update(query: string, params: [string]) -> int throws DatabaseError { return 1; } @@ -679,12 +679,12 @@ interface Event { } interface EventHandler { - async fn handle(event: E) throws; + fn handle(event: E) throws; } interface EventBus { fn subscribe(handler: EventHandler); - async fn publish(event: E); + fn publish(event: E); } pub struct UserCreatedEvent implements Event { @@ -827,13 +827,14 @@ pub fn min_value>(a: T, b: T) -> T { return b; } -pub fn compose(f: fn(B) -> C, g: fn(A) -> B) -> fn(A) -> C { - return |a: A| f(g(a)); -} - -pub fn curry2(f: fn(A, B) -> C) -> fn(A) -> fn(B) -> C { - return |a: A| |b: B| f(a, b); -} +// Note: compose and curry2 require advanced function type syntax not yet supported +// pub fn compose(f: fn(B) -> C, g: fn(A) -> B) -> fn(A) -> C { +// return |a: A| f(g(a)); +// } +// +// pub fn curry2(f: fn(A, B) -> C) -> fn(A) -> fn(B) -> C { +// return |a: A| |b: B| f(a, b); +// } // ============================================================================= // Concurrency Patterns @@ -844,9 +845,9 @@ pub struct Mutex { pub locked: bool } -pub async fn (mut self: Mutex) lock() -> T { +pub fn (mut self: Mutex) lock() -> T { while (self.locked) { - await yield_now(); + yield_now(); } self.locked = true; return self.value; @@ -864,20 +865,20 @@ pub struct Channel { pub closed: bool } -pub async fn (mut self: Channel) send(value: T) -> bool { +pub fn (mut self: Channel) send(value: T) -> bool { if (self.closed) { return false; } while (self.buffer.length >= self.capacity) { - await yield_now(); + yield_now(); } self.buffer.push(value); return true; } -pub async fn (mut self: Channel) receive() -> option { +pub fn (mut self: Channel) receive() -> option { while (self.buffer.length == 0 && !self.closed) { - await yield_now(); + yield_now(); } if (self.buffer.length == 0) { return none; @@ -894,7 +895,7 @@ pub fn (mut self: Channel) close() { // Main Entry Point // ============================================================================= -async fn main() { +fn main() { var client: SimpleHttpClient = SimpleHttpClient { base_url: "https://api.example.com", default_headers: HttpHeaders { headers: { "Content-Type": "application/json" } }, @@ -910,7 +911,7 @@ async fn main() { var user_id: UserId = UserId { value: "user-123" }; - var user_opt: option = await user_repo.find_by_id(user_id); + var user_opt: option = user_repo.find_by_id(user_id); var user: User = user_opt else { printf("User not found"); @@ -918,7 +919,7 @@ async fn main() { } printf("Found user: {}", user.full_name()); - var response: HttpResponse = await client.get("/users/" + user_id.value); + var response: HttpResponse = client.get("/users/" + user_id.value); if (response.is_success()) { printf("API call successful: {} {}", response.status_code, response.status_text); } @@ -963,13 +964,10 @@ async fn main() { printf("Active admins: {}", active_admins.length); - var tasks: [promise] = []; - for (i in 0..10) { - tasks.push(client.get("/items/" + (i as string))); - } - - for (task in tasks) { - var response: HttpResponse = await task; - printf("Task completed: {}", response.status_code); + // Note: promise type and async task patterns removed as async/await is not supported + // Running synchronous requests instead + for (i: int in 0..10) { + var response: HttpResponse = client.get("/items/" + (i as string)); + println("Task completed: {}", response.status_code); } } diff --git a/examples/tier3_bytes.naml b/examples/tier3_bytes.naml new file mode 100644 index 0000000..98ce999 --- /dev/null +++ b/examples/tier3_bytes.naml @@ -0,0 +1,27 @@ +/// +/// Tier 3 Test: Bytes Type +/// + +fn main() { + println("=== Bytes Type Tests ==="); + + // Convert string to bytes + var s: string = "Hello"; + var b: bytes = s as bytes; + + print("String: "); + println(s); + + // Convert bytes back to string + var s2: string = b as string; + print("Bytes as string: "); + println(s2); + + // Test with different strings + var greeting: string = "World"; + var greeting_bytes: bytes = greeting as bytes; + print("As bytes: "); + println(greeting_bytes); + + println("=== Bytes Type Tests Complete ==="); +} diff --git a/examples/tier3_casting.naml b/examples/tier3_casting.naml new file mode 100644 index 0000000..3479cca --- /dev/null +++ b/examples/tier3_casting.naml @@ -0,0 +1,44 @@ +/// +/// Tier 3 Test: Type Casting +/// + +fn main() { + println("=== Type Casting Tests ==="); + + // int to string + var n: int = 42; + var s: string = n as string; + print("int 42 as string: "); + println(s); + + // float to string + var f: float = 3.14; + var fs: string = f as string; + print("float 3.14 as string: "); + println(fs); + + // string to int + var str_num: string = "123"; + var parsed: int = str_num as int; + print("string '123' as int: "); + println(parsed); + + // string to float + var str_float: string = "2.718"; + var parsed_f: float = str_float as float; + print("string '2.718' as float: "); + println(parsed_f); + + // Verify arithmetic works after conversion + print("Parsed int + 7 = "); + println(parsed + 7); + + // int to float to string roundtrip + var num: int = 100; + var as_float: float = num as float; + var as_str: string = as_float as string; + print("int 100 -> float -> string: "); + println(as_str); + + println("=== Casting Tests Complete ==="); +} diff --git a/examples/tier3_mut_self.naml b/examples/tier3_mut_self.naml new file mode 100644 index 0000000..7508f38 --- /dev/null +++ b/examples/tier3_mut_self.naml @@ -0,0 +1,80 @@ +/// +/// Tier 3 Test: Mutable Self Parameters +/// + +struct Counter { + value: int +} + +// Method with mutable self - can modify fields +pub fn (mut self: Counter) increment() { + self.value = self.value + 1; +} + +pub fn (mut self: Counter) add(amount: int) { + self.value = self.value + amount; +} + +// Method with immutable self - read only +pub fn (self: Counter) get_value() -> int { + return self.value; +} + +pub fn (self: Counter) is_zero() -> bool { + return self.value == 0; +} + +struct Point { + x: int, + y: int +} + +pub fn (mut self: Point) translate(dx: int, dy: int) { + self.x = self.x + dx; + self.y = self.y + dy; +} + +pub fn (self: Point) distance_squared() -> int { + return self.x * self.x + self.y * self.y; +} + +fn main() { + println("=== Mutable Self Tests ==="); + + var c: Counter = Counter { value: 0 }; + + print("Initial value: "); + println(c.get_value()); + + c.increment(); + print("After increment: "); + println(c.get_value()); + + c.add(5); + print("After add(5): "); + println(c.get_value()); + + c.increment(); + c.increment(); + print("After 2 more increments: "); + println(c.get_value()); + + print("Is zero? "); + if (c.is_zero()) { + println("yes"); + } else { + println("no"); + } + + var p: Point = Point { x: 3, y: 4 }; + print("Point distance squared: "); + println(p.distance_squared()); + + p.translate(1, 1); + print("After translate(1,1) - x: "); + print(p.x); + print(", y: "); + println(p.y); + + println("=== Mutable Self Tests Complete ==="); +} diff --git a/examples/tier3_printf.naml b/examples/tier3_printf.naml new file mode 100644 index 0000000..8a9ee27 --- /dev/null +++ b/examples/tier3_printf.naml @@ -0,0 +1,36 @@ +/// +/// Tier 3 Test: Printf Format Strings +/// + +fn main() { + println("=== Printf Format String Tests ==="); + + var name: string = "Alice"; + var age: int = 30; + var score: float = 95.5; + + // Basic format string + println("Hello, {}!", name); + + // Multiple placeholders + println("{} is {} years old", name, age); + + // Mixed types + println("{} scored {} points", name, score); + + // Numbers in format + println("Sum of {} + {} = {}", 10, 20, 30); + + // Without newline + print("Loading: {}%", 50); + println(" complete"); + + // Format string without placeholders still works + println("No placeholders here"); + + // Regular print without format string + print(name); + println(" says hi"); + + println("=== Printf Tests Complete ==="); +} diff --git a/examples/tier3_ranges.naml b/examples/tier3_ranges.naml new file mode 100644 index 0000000..7ef809b --- /dev/null +++ b/examples/tier3_ranges.naml @@ -0,0 +1,51 @@ +/// +/// Tier 3 Test: Range Expressions +/// + +fn main() { + println("=== Range Expression Tests ==="); + + // Exclusive range 0..5 + print("Exclusive range 0..5: "); + for (i in 0..5) { + print(i); + print(" "); + } + println(""); + + // Inclusive range 1..=5 + print("Inclusive range 1..=5: "); + for (i in 1..=5) { + print(i); + print(" "); + } + println(""); + + // Sum using range + var sum: int = 0; + for (n in 1..=10) { + sum = sum + n; + } + print("Sum of 1..=10: "); + println(sum); + + // Nested ranges + println("Multiplication table 1-3:"); + for (i in 1..=3) { + for (j in 1..=3) { + print(i * j); + print(" "); + } + println(""); + } + + // Range starting from non-zero + print("Range 5..8: "); + for (x in 5..8) { + print(x); + print(" "); + } + println(""); + + println("=== Range Tests Complete ==="); +} diff --git a/examples/tier3_string_iter.naml b/examples/tier3_string_iter.naml new file mode 100644 index 0000000..ee873ca --- /dev/null +++ b/examples/tier3_string_iter.naml @@ -0,0 +1,45 @@ +/// +/// Tier 3 Test: String Character Iteration +/// + +fn main() { + println("=== String Iteration Tests ==="); + + var s: string = "Hi"; + println("Got string"); + println(s); + println("Printed string"); + + // Test len method + println("Testing len..."); + var length: int = s.len(); + print("Length: "); + println(length); + + // Test char_at method + println("Testing char_at..."); + var first: int = s.char_at(0); + var second: int = s.char_at(1); + print("First char code: "); + println(first); + print("Second char code: "); + println(second); + + // Test longer string + var hello: string = "Hello"; + print("'Hello' length: "); + println(hello.len()); + + // Iterate via index + println("Characters via index:"); + var i: int = 0; + while (i < hello.len()) { + print(" Char "); + print(i); + print(": code "); + println(hello.char_at(i)); + i = i + 1; + } + + println("=== String Iteration Tests Complete ==="); +} diff --git a/namlc/src/codegen/cranelift/mod.rs b/namlc/src/codegen/cranelift/mod.rs index f391ccd..2585359 100644 --- a/namlc/src/codegen/cranelift/mod.rs +++ b/namlc/src/codegen/cranelift/mod.rs @@ -187,6 +187,25 @@ impl<'a> JitCompiler<'a> { builder.symbol("naml_string_eq", crate::runtime::naml_string_eq as *const u8); builder.symbol("naml_string_incref", crate::runtime::naml_string_incref as *const u8); builder.symbol("naml_string_decref", crate::runtime::naml_string_decref as *const u8); + builder.symbol("naml_string_char_at", crate::runtime::naml_string_char_at as *const u8); + builder.symbol("naml_string_char_len", crate::runtime::naml_string_char_len as *const u8); + + // Type conversion operations + builder.symbol("naml_int_to_string", crate::runtime::naml_int_to_string as *const u8); + builder.symbol("naml_float_to_string", crate::runtime::naml_float_to_string as *const u8); + builder.symbol("naml_string_to_int", crate::runtime::naml_string_to_int as *const u8); + builder.symbol("naml_string_to_float", crate::runtime::naml_string_to_float as *const u8); + + // Bytes operations + builder.symbol("naml_bytes_new", crate::runtime::naml_bytes_new as *const u8); + builder.symbol("naml_bytes_from", crate::runtime::naml_bytes_from as *const u8); + builder.symbol("naml_bytes_len", crate::runtime::naml_bytes_len as *const u8); + builder.symbol("naml_bytes_get", crate::runtime::naml_bytes_get as *const u8); + builder.symbol("naml_bytes_set", crate::runtime::naml_bytes_set as *const u8); + builder.symbol("naml_bytes_incref", crate::runtime::naml_bytes_incref as *const u8); + builder.symbol("naml_bytes_decref", crate::runtime::naml_bytes_decref as *const u8); + builder.symbol("naml_bytes_to_string", crate::runtime::naml_bytes_to_string as *const u8); + builder.symbol("naml_string_to_bytes", crate::runtime::naml_string_to_bytes as *const u8); let module = JITModule::new(builder); let ctx = module.make_context(); @@ -1714,86 +1733,276 @@ fn compile_statement( } Statement::For(for_stmt) => { - // Compile the iterable (should be an array) - let arr_ptr = compile_expression(ctx, builder, &for_stmt.iterable)?; + // Check if iterable is a range expression (binary op with Range or RangeIncl) + let range_info = match &for_stmt.iterable { + Expression::Binary(bin) if matches!(bin.op, BinaryOp::Range | BinaryOp::RangeIncl) => { + Some((bin.left, bin.right, matches!(bin.op, BinaryOp::RangeIncl))) + } + Expression::Range(range_expr) => { + // Handle Expression::Range if it exists + range_expr.start.zip(range_expr.end.as_ref()).map(|(s, e)| (s, *e, range_expr.inclusive)) + } + _ => None + }; - // Get array length - let len = call_array_len(ctx, builder, arr_ptr)?; + // Check if iterable is a string (via type annotation, string literal, or heap type) + let is_string_literal = matches!( + &for_stmt.iterable, + Expression::Literal(LiteralExpr { value: Literal::String(_), .. }) + ); - // Create index variable - let idx_var = Variable::new(ctx.var_counter); - ctx.var_counter += 1; - builder.declare_var(idx_var, cranelift::prelude::types::I64); - let zero = builder.ins().iconst(cranelift::prelude::types::I64, 0); - builder.def_var(idx_var, zero); + // Also check if it's a string variable by looking at var_heap_types + let is_string_var = if let Expression::Identifier(ident) = &for_stmt.iterable { + let var_name = ctx.interner.resolve(&ident.ident.symbol).to_string(); + matches!(ctx.var_heap_types.get(&var_name), Some(HeapType::String)) + } else { + false + }; - // Create value variable - let val_var = Variable::new(ctx.var_counter); - ctx.var_counter += 1; - builder.declare_var(val_var, cranelift::prelude::types::I64); - let val_name = ctx.interner.resolve(&for_stmt.value.symbol).to_string(); - ctx.variables.insert(val_name, val_var); - - // Optionally create index binding - if let Some(ref idx_ident) = for_stmt.index { - let idx_name = ctx.interner.resolve(&idx_ident.symbol).to_string(); - ctx.variables.insert(idx_name, idx_var); - } + let is_string = is_string_literal || is_string_var || matches!( + ctx.annotations.get_type(for_stmt.iterable.span()), + Some(Type::String) + ); - // Create blocks - let header_block = builder.create_block(); - let body_block = builder.create_block(); - let exit_block = builder.create_block(); + if let Some((start_expr, end_expr, inclusive)) = range_info { + // Handle range iteration directly without array allocation + // Get start and end values + let start = compile_expression(ctx, builder, start_expr)?; + let end = compile_expression(ctx, builder, end_expr)?; - // Store exit block for break statements - let prev_loop_exit = ctx.loop_exit_block.take(); - let prev_loop_header = ctx.loop_header_block.take(); - ctx.loop_exit_block = Some(exit_block); - ctx.loop_header_block = Some(header_block); + // Create index variable (this is both the loop counter and the value) + let idx_var = Variable::new(ctx.var_counter); + ctx.var_counter += 1; + builder.declare_var(idx_var, cranelift::prelude::types::I64); + builder.def_var(idx_var, start); + + // Bind the value variable to the same as index + let val_name = ctx.interner.resolve(&for_stmt.value.symbol).to_string(); + ctx.variables.insert(val_name, idx_var); + + // Optionally create separate index binding (for iteration count from 0) + let iter_var = if for_stmt.index.is_some() { + let iter_var = Variable::new(ctx.var_counter); + ctx.var_counter += 1; + builder.declare_var(iter_var, cranelift::prelude::types::I64); + let zero = builder.ins().iconst(cranelift::prelude::types::I64, 0); + builder.def_var(iter_var, zero); + if let Some(ref idx_ident) = for_stmt.index { + let idx_name = ctx.interner.resolve(&idx_ident.symbol).to_string(); + ctx.variables.insert(idx_name, iter_var); + } + Some(iter_var) + } else { + None + }; - builder.ins().jump(header_block, &[]); + let header_block = builder.create_block(); + let body_block = builder.create_block(); + let exit_block = builder.create_block(); - // Header: check if idx < len - builder.switch_to_block(header_block); - let idx_val = builder.use_var(idx_var); - let cond = builder.ins().icmp(IntCC::SignedLessThan, idx_val, len); - builder.ins().brif(cond, body_block, &[], exit_block, &[]); + let prev_loop_exit = ctx.loop_exit_block.take(); + let prev_loop_header = ctx.loop_header_block.take(); + ctx.loop_exit_block = Some(exit_block); + ctx.loop_header_block = Some(header_block); - // Body - builder.switch_to_block(body_block); - builder.seal_block(body_block); - ctx.block_terminated = false; + builder.ins().jump(header_block, &[]); - // Get current element - let idx_val = builder.use_var(idx_var); - let elem = call_array_get(ctx, builder, arr_ptr, idx_val)?; - builder.def_var(val_var, elem); + // Header: check if idx < end (or <= for inclusive) + builder.switch_to_block(header_block); + let idx_val = builder.use_var(idx_var); + let cond = if inclusive { + builder.ins().icmp(IntCC::SignedLessThanOrEqual, idx_val, end) + } else { + builder.ins().icmp(IntCC::SignedLessThan, idx_val, end) + }; + builder.ins().brif(cond, body_block, &[], exit_block, &[]); - // Compile body - for stmt in &for_stmt.body.statements { - compile_statement(ctx, builder, stmt)?; - if ctx.block_terminated { - break; + // Body + builder.switch_to_block(body_block); + builder.seal_block(body_block); + ctx.block_terminated = false; + + for stmt in &for_stmt.body.statements { + compile_statement(ctx, builder, stmt)?; + if ctx.block_terminated { + break; + } } - } - // Increment index - if !ctx.block_terminated { + // Increment index + if !ctx.block_terminated { + let idx_val = builder.use_var(idx_var); + let one = builder.ins().iconst(cranelift::prelude::types::I64, 1); + let next_idx = builder.ins().iadd(idx_val, one); + builder.def_var(idx_var, next_idx); + + // Also increment iteration counter if present + if let Some(iter_v) = iter_var { + let iter_val = builder.use_var(iter_v); + let next_iter = builder.ins().iadd(iter_val, one); + builder.def_var(iter_v, next_iter); + } + + builder.ins().jump(header_block, &[]); + } + + builder.seal_block(header_block); + builder.switch_to_block(exit_block); + builder.seal_block(exit_block); + ctx.block_terminated = false; + + ctx.loop_exit_block = prev_loop_exit; + ctx.loop_header_block = prev_loop_header; + } else if is_string { + // Handle string character iteration + let raw_str_ptr = compile_expression(ctx, builder, &for_stmt.iterable)?; + + // If the iterable is a string literal, convert it to NamlString* + let str_ptr = if matches!(&for_stmt.iterable, Expression::Literal(LiteralExpr { value: Literal::String(_), .. })) { + // Convert C string to NamlString* + call_string_from_cstr(ctx, builder, raw_str_ptr)? + } else { + raw_str_ptr + }; + + let len = call_string_char_len(ctx, builder, str_ptr)?; + + // Create index variable + let idx_var = Variable::new(ctx.var_counter); + ctx.var_counter += 1; + builder.declare_var(idx_var, cranelift::prelude::types::I64); + let zero = builder.ins().iconst(cranelift::prelude::types::I64, 0); + builder.def_var(idx_var, zero); + + // Create character variable (holds codepoint as int) + let char_var = Variable::new(ctx.var_counter); + ctx.var_counter += 1; + builder.declare_var(char_var, cranelift::prelude::types::I64); + let val_name = ctx.interner.resolve(&for_stmt.value.symbol).to_string(); + ctx.variables.insert(val_name, char_var); + + // Bind index if requested + if let Some(ref idx_ident) = for_stmt.index { + let idx_name = ctx.interner.resolve(&idx_ident.symbol).to_string(); + ctx.variables.insert(idx_name, idx_var); + } + + let header_block = builder.create_block(); + let body_block = builder.create_block(); + let exit_block = builder.create_block(); + + let prev_loop_exit = ctx.loop_exit_block.take(); + let prev_loop_header = ctx.loop_header_block.take(); + ctx.loop_exit_block = Some(exit_block); + ctx.loop_header_block = Some(header_block); + + builder.ins().jump(header_block, &[]); + + builder.switch_to_block(header_block); + let idx_val = builder.use_var(idx_var); + let cond = builder.ins().icmp(IntCC::SignedLessThan, idx_val, len); + builder.ins().brif(cond, body_block, &[], exit_block, &[]); + + builder.switch_to_block(body_block); + builder.seal_block(body_block); + ctx.block_terminated = false; + + // Get character at current index let idx_val = builder.use_var(idx_var); - let one = builder.ins().iconst(cranelift::prelude::types::I64, 1); - let next_idx = builder.ins().iadd(idx_val, one); - builder.def_var(idx_var, next_idx); + let char_code = call_string_char_at(ctx, builder, str_ptr, idx_val)?; + builder.def_var(char_var, char_code); + + for stmt in &for_stmt.body.statements { + compile_statement(ctx, builder, stmt)?; + if ctx.block_terminated { + break; + } + } + + if !ctx.block_terminated { + let idx_val = builder.use_var(idx_var); + let one = builder.ins().iconst(cranelift::prelude::types::I64, 1); + let next_idx = builder.ins().iadd(idx_val, one); + builder.def_var(idx_var, next_idx); + builder.ins().jump(header_block, &[]); + } + + builder.seal_block(header_block); + builder.switch_to_block(exit_block); + builder.seal_block(exit_block); + ctx.block_terminated = false; + + ctx.loop_exit_block = prev_loop_exit; + ctx.loop_header_block = prev_loop_header; + } else { + // Original array iteration code + let arr_ptr = compile_expression(ctx, builder, &for_stmt.iterable)?; + let len = call_array_len(ctx, builder, arr_ptr)?; + + let idx_var = Variable::new(ctx.var_counter); + ctx.var_counter += 1; + builder.declare_var(idx_var, cranelift::prelude::types::I64); + let zero = builder.ins().iconst(cranelift::prelude::types::I64, 0); + builder.def_var(idx_var, zero); + + let val_var = Variable::new(ctx.var_counter); + ctx.var_counter += 1; + builder.declare_var(val_var, cranelift::prelude::types::I64); + let val_name = ctx.interner.resolve(&for_stmt.value.symbol).to_string(); + ctx.variables.insert(val_name, val_var); + + if let Some(ref idx_ident) = for_stmt.index { + let idx_name = ctx.interner.resolve(&idx_ident.symbol).to_string(); + ctx.variables.insert(idx_name, idx_var); + } + + let header_block = builder.create_block(); + let body_block = builder.create_block(); + let exit_block = builder.create_block(); + + let prev_loop_exit = ctx.loop_exit_block.take(); + let prev_loop_header = ctx.loop_header_block.take(); + ctx.loop_exit_block = Some(exit_block); + ctx.loop_header_block = Some(header_block); + builder.ins().jump(header_block, &[]); - } - builder.seal_block(header_block); - builder.switch_to_block(exit_block); - builder.seal_block(exit_block); - ctx.block_terminated = false; + builder.switch_to_block(header_block); + let idx_val = builder.use_var(idx_var); + let cond = builder.ins().icmp(IntCC::SignedLessThan, idx_val, len); + builder.ins().brif(cond, body_block, &[], exit_block, &[]); - // Restore previous loop context - ctx.loop_exit_block = prev_loop_exit; - ctx.loop_header_block = prev_loop_header; + builder.switch_to_block(body_block); + builder.seal_block(body_block); + ctx.block_terminated = false; + + let idx_val = builder.use_var(idx_var); + let elem = call_array_get(ctx, builder, arr_ptr, idx_val)?; + builder.def_var(val_var, elem); + + for stmt in &for_stmt.body.statements { + compile_statement(ctx, builder, stmt)?; + if ctx.block_terminated { + break; + } + } + + if !ctx.block_terminated { + let idx_val = builder.use_var(idx_var); + let one = builder.ins().iconst(cranelift::prelude::types::I64, 1); + let next_idx = builder.ins().iadd(idx_val, one); + builder.def_var(idx_var, next_idx); + builder.ins().jump(header_block, &[]); + } + + builder.seal_block(header_block); + builder.switch_to_block(exit_block); + builder.seal_block(exit_block); + ctx.block_terminated = false; + + ctx.loop_exit_block = prev_loop_exit; + ctx.loop_header_block = prev_loop_header; + } } Statement::Loop(loop_stmt) => { @@ -2839,6 +3048,9 @@ fn compile_expression( Some(Type::Float) => { Ok(builder.ins().fcvt_to_sint(cranelift::prelude::types::I64, value)) } + Some(Type::String) => { + call_string_to_int(ctx, builder, value) + } Some(Type::Uint) | Some(Type::Int) => Ok(value), _ => Ok(value) } @@ -2860,10 +3072,37 @@ fn compile_expression( Some(Type::Uint) => { Ok(builder.ins().fcvt_from_uint(cranelift::prelude::types::F64, value)) } + Some(Type::String) => { + call_string_to_float(ctx, builder, value) + } Some(Type::Float) => Ok(value), _ => Ok(value) } } + NamlType::String => { + match source_type { + Some(Type::Int) | Some(Type::Uint) => { + call_int_to_string(ctx, builder, value) + } + Some(Type::Float) => { + call_float_to_string(ctx, builder, value) + } + Some(Type::Bytes) => { + call_bytes_to_string(ctx, builder, value) + } + Some(Type::String) => Ok(value), + _ => Ok(value) + } + } + NamlType::Bytes => { + match source_type { + Some(Type::String) => { + call_string_to_bytes(ctx, builder, value) + } + Some(Type::Bytes) => Ok(value), + _ => Ok(value) + } + } _ => { // For other casts, just pass through the value Ok(value) @@ -3035,45 +3274,50 @@ fn compile_print_call( return Ok(builder.ins().iconst(cranelift::prelude::types::I64, 0)); } - for (i, arg) in args.iter().enumerate() { - match arg { - Expression::Literal(LiteralExpr { value: Literal::String(spur), .. }) => { - let s = ctx.interner.resolve(spur); - let ptr = compile_string_literal(ctx, builder, s)?; - call_print_str(ctx, builder, ptr)?; - } - Expression::Literal(LiteralExpr { value: Literal::Int(n), .. }) => { - let val = builder.ins().iconst(cranelift::prelude::types::I64, *n); - call_print_int(ctx, builder, val)?; + // Check if first arg is a format string with {} + if let Expression::Literal(LiteralExpr { value: Literal::String(spur), .. }) = &args[0] { + let format_str = ctx.interner.resolve(spur); + if format_str.contains("{}") { + // Format string mode + let mut arg_idx = 1; + let mut last_end = 0; + + for (start, _) in format_str.match_indices("{}") { + // Print literal part before placeholder + if start > last_end { + let literal_part = &format_str[last_end..start]; + let ptr = compile_string_literal(ctx, builder, literal_part)?; + call_print_str(ctx, builder, ptr)?; + } + + // Print the argument + if arg_idx < args.len() { + let arg = &args[arg_idx]; + print_arg(ctx, builder, arg)?; + arg_idx += 1; + } + + last_end = start + 2; } - Expression::Literal(LiteralExpr { value: Literal::Float(f), .. }) => { - let val = builder.ins().f64const(*f); - call_print_float(ctx, builder, val)?; + + // Print remaining literal after last placeholder + if last_end < format_str.len() { + let remaining = &format_str[last_end..]; + let ptr = compile_string_literal(ctx, builder, remaining)?; + call_print_str(ctx, builder, ptr)?; } - _ => { - let val = compile_expression(ctx, builder, arg)?; - // Check type from annotations to call appropriate print function - let expr_type = ctx.annotations.get_type(arg.span()); - match expr_type { - Some(Type::String) => { - // String variables now hold NamlString* (boxed strings) - call_print_naml_string(ctx, builder, val)?; - } - Some(Type::Float) => { - call_print_float(ctx, builder, val)?; - } - _ => { - // Default: check Cranelift value type for F64, otherwise int - let val_type = builder.func.dfg.value_type(val); - if val_type == cranelift::prelude::types::F64 { - call_print_float(ctx, builder, val)?; - } else { - call_print_int(ctx, builder, val)?; - } - } - } + + if newline { + call_print_newline(ctx, builder)?; } + + return Ok(builder.ins().iconst(cranelift::prelude::types::I64, 0)); } + } + + // Original behavior for non-format strings + for (i, arg) in args.iter().enumerate() { + print_arg(ctx, builder, arg)?; if i < args.len() - 1 { let space = compile_string_literal(ctx, builder, " ")?; @@ -3088,6 +3332,52 @@ fn compile_print_call( Ok(builder.ins().iconst(cranelift::prelude::types::I64, 0)) } +fn print_arg( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + arg: &Expression<'_>, +) -> Result<(), CodegenError> { + match arg { + Expression::Literal(LiteralExpr { value: Literal::String(spur), .. }) => { + let s = ctx.interner.resolve(spur); + let ptr = compile_string_literal(ctx, builder, s)?; + call_print_str(ctx, builder, ptr)?; + } + Expression::Literal(LiteralExpr { value: Literal::Int(n), .. }) => { + let val = builder.ins().iconst(cranelift::prelude::types::I64, *n); + call_print_int(ctx, builder, val)?; + } + Expression::Literal(LiteralExpr { value: Literal::Float(f), .. }) => { + let val = builder.ins().f64const(*f); + call_print_float(ctx, builder, val)?; + } + _ => { + let val = compile_expression(ctx, builder, arg)?; + // Check type from annotations to call appropriate print function + let expr_type = ctx.annotations.get_type(arg.span()); + match expr_type { + Some(Type::String) => { + // String variables now hold NamlString* (boxed strings) + call_print_naml_string(ctx, builder, val)?; + } + Some(Type::Float) => { + call_print_float(ctx, builder, val)?; + } + _ => { + // Default: check Cranelift value type for F64, otherwise int + let val_type = builder.func.dfg.value_type(val); + if val_type == cranelift::prelude::types::F64 { + call_print_float(ctx, builder, val)?; + } else { + call_print_int(ctx, builder, val)?; + } + } + } + } + } + Ok(()) +} + fn emit_incref( ctx: &mut CompileContext<'_>, builder: &mut FunctionBuilder<'_>, @@ -3327,6 +3617,160 @@ fn call_string_equals( Ok(builder.inst_results(call)[0]) } +fn call_int_to_string( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + value: Value, +) -> Result { + let ptr_type = ctx.module.target_config().pointer_type(); + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(cranelift::prelude::types::I64)); + sig.returns.push(AbiParam::new(ptr_type)); + + let func_id = ctx.module + .declare_function("naml_int_to_string", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_int_to_string: {}", e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + let call = builder.ins().call(func_ref, &[value]); + Ok(builder.inst_results(call)[0]) +} + +fn call_float_to_string( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + value: Value, +) -> Result { + let ptr_type = ctx.module.target_config().pointer_type(); + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(cranelift::prelude::types::F64)); + sig.returns.push(AbiParam::new(ptr_type)); + + let func_id = ctx.module + .declare_function("naml_float_to_string", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_float_to_string: {}", e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + let call = builder.ins().call(func_ref, &[value]); + Ok(builder.inst_results(call)[0]) +} + +fn call_string_to_int( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + value: Value, +) -> Result { + let ptr_type = ctx.module.target_config().pointer_type(); + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(ptr_type)); + sig.returns.push(AbiParam::new(cranelift::prelude::types::I64)); + + let func_id = ctx.module + .declare_function("naml_string_to_int", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_string_to_int: {}", e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + let call = builder.ins().call(func_ref, &[value]); + Ok(builder.inst_results(call)[0]) +} + +fn call_string_to_float( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + value: Value, +) -> Result { + let ptr_type = ctx.module.target_config().pointer_type(); + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(ptr_type)); + sig.returns.push(AbiParam::new(cranelift::prelude::types::F64)); + + let func_id = ctx.module + .declare_function("naml_string_to_float", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_string_to_float: {}", e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + let call = builder.ins().call(func_ref, &[value]); + Ok(builder.inst_results(call)[0]) +} + +fn call_string_char_len( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + str_ptr: Value, +) -> Result { + let ptr_type = ctx.module.target_config().pointer_type(); + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(ptr_type)); + sig.returns.push(AbiParam::new(cranelift::prelude::types::I64)); + + let func_id = ctx.module + .declare_function("naml_string_char_len", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_string_char_len: {}", e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + let call = builder.ins().call(func_ref, &[str_ptr]); + Ok(builder.inst_results(call)[0]) +} + +fn call_string_char_at( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + str_ptr: Value, + index: Value, +) -> Result { + let ptr_type = ctx.module.target_config().pointer_type(); + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(ptr_type)); + sig.params.push(AbiParam::new(cranelift::prelude::types::I64)); + sig.returns.push(AbiParam::new(cranelift::prelude::types::I64)); + + let func_id = ctx.module + .declare_function("naml_string_char_at", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_string_char_at: {}", e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + let call = builder.ins().call(func_ref, &[str_ptr, index]); + Ok(builder.inst_results(call)[0]) +} + +fn call_string_to_bytes( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + str_ptr: Value, +) -> Result { + let ptr_type = ctx.module.target_config().pointer_type(); + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(ptr_type)); + sig.returns.push(AbiParam::new(ptr_type)); + + let func_id = ctx.module + .declare_function("naml_string_to_bytes", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_string_to_bytes: {}", e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + let call = builder.ins().call(func_ref, &[str_ptr]); + Ok(builder.inst_results(call)[0]) +} + +fn call_bytes_to_string( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + bytes_ptr: Value, +) -> Result { + let ptr_type = ctx.module.target_config().pointer_type(); + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(ptr_type)); + sig.returns.push(AbiParam::new(ptr_type)); + + let func_id = ctx.module + .declare_function("naml_bytes_to_string", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_bytes_to_string: {}", e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + let call = builder.ins().call(func_ref, &[bytes_ptr]); + Ok(builder.inst_results(call)[0]) +} + fn call_print_newline( ctx: &mut CompileContext<'_>, builder: &mut FunctionBuilder<'_>, @@ -3845,7 +4289,21 @@ fn compile_method_call( match method_name { "len" => { - call_array_len(ctx, builder, recv) + // Check receiver type to dispatch to correct len function + let receiver_type = ctx.annotations.get_type(receiver.span()); + if matches!(receiver_type, Some(Type::String)) { + call_string_char_len(ctx, builder, recv) + } else { + call_array_len(ctx, builder, recv) + } + } + "char_at" => { + // String char_at method + if args.is_empty() { + return Err(CodegenError::JitCompile("char_at requires an index argument".to_string())); + } + let idx = compile_expression(ctx, builder, &args[0])?; + call_string_char_at(ctx, builder, recv, idx) } "push" => { if args.is_empty() { diff --git a/namlc/src/runtime/bytes.rs b/namlc/src/runtime/bytes.rs new file mode 100644 index 0000000..5e23be9 --- /dev/null +++ b/namlc/src/runtime/bytes.rs @@ -0,0 +1,182 @@ +/// +/// Bytes Runtime Support +/// +/// Provides heap-allocated byte arrays with reference counting. +/// Similar to strings but for raw binary data. +/// + +use std::alloc::{alloc, dealloc, Layout}; +use super::value::{HeapHeader, HeapTag, NamlString, naml_string_new}; + +/// A heap-allocated byte array +#[repr(C)] +pub struct NamlBytes { + pub header: HeapHeader, + pub len: usize, + pub capacity: usize, + pub data: [u8; 0], +} + +/// Allocate new bytes with given capacity +#[unsafe(no_mangle)] +pub extern "C" fn naml_bytes_new(capacity: usize) -> *mut NamlBytes { + unsafe { + let cap = if capacity == 0 { 8 } else { capacity }; + let layout = Layout::from_size_align( + std::mem::size_of::() + cap, + std::mem::align_of::(), + ).unwrap(); + + let ptr = alloc(layout) as *mut NamlBytes; + if ptr.is_null() { + panic!("Failed to allocate bytes"); + } + + (*ptr).header = HeapHeader::new(HeapTag::String); // Reuse tag for simplicity + (*ptr).len = 0; + (*ptr).capacity = cap; + + ptr + } +} + +/// Create bytes from raw data +#[unsafe(no_mangle)] +pub extern "C" fn naml_bytes_from(data: *const u8, len: usize) -> *mut NamlBytes { + unsafe { + let cap = if len == 0 { 8 } else { len }; + let layout = Layout::from_size_align( + std::mem::size_of::() + cap, + std::mem::align_of::(), + ).unwrap(); + + let ptr = alloc(layout) as *mut NamlBytes; + if ptr.is_null() { + panic!("Failed to allocate bytes"); + } + + (*ptr).header = HeapHeader::new(HeapTag::String); + (*ptr).len = len; + (*ptr).capacity = cap; + + if !data.is_null() && len > 0 { + std::ptr::copy_nonoverlapping(data, (*ptr).data.as_mut_ptr(), len); + } + + ptr + } +} + +/// Get bytes length +#[unsafe(no_mangle)] +pub extern "C" fn naml_bytes_len(b: *const NamlBytes) -> i64 { + if b.is_null() { + 0 + } else { + unsafe { (*b).len as i64 } + } +} + +/// Get byte at index +#[unsafe(no_mangle)] +pub extern "C" fn naml_bytes_get(b: *const NamlBytes, index: i64) -> i64 { + if b.is_null() { + return 0; + } + unsafe { + if index < 0 || index as usize >= (*b).len { + return 0; + } + *(*b).data.as_ptr().add(index as usize) as i64 + } +} + +/// Set byte at index +#[unsafe(no_mangle)] +pub extern "C" fn naml_bytes_set(b: *mut NamlBytes, index: i64, value: i64) { + if b.is_null() { + return; + } + unsafe { + if index >= 0 && (index as usize) < (*b).len { + *(*b).data.as_mut_ptr().add(index as usize) = value as u8; + } + } +} + +/// Increment reference count +#[unsafe(no_mangle)] +pub extern "C" fn naml_bytes_incref(b: *mut NamlBytes) { + if !b.is_null() { + unsafe { (*b).header.incref(); } + } +} + +/// Decrement reference count and free if zero +#[unsafe(no_mangle)] +pub extern "C" fn naml_bytes_decref(b: *mut NamlBytes) { + if !b.is_null() { + unsafe { + if (*b).header.decref() { + let cap = (*b).capacity; + let layout = Layout::from_size_align( + std::mem::size_of::() + cap, + std::mem::align_of::(), + ).unwrap(); + dealloc(b as *mut u8, layout); + } + } + } +} + +/// Convert bytes to string (UTF-8) +#[unsafe(no_mangle)] +pub extern "C" fn naml_bytes_to_string(b: *const NamlBytes) -> *mut NamlString { + if b.is_null() { + return naml_string_new(std::ptr::null(), 0); + } + unsafe { + let slice = std::slice::from_raw_parts((*b).data.as_ptr(), (*b).len); + naml_string_new(slice.as_ptr(), slice.len()) + } +} + +/// Convert string to bytes +#[unsafe(no_mangle)] +pub extern "C" fn naml_string_to_bytes(s: *const NamlString) -> *mut NamlBytes { + if s.is_null() { + return naml_bytes_new(0); + } + unsafe { + let len = (*s).len; + naml_bytes_from((*s).data.as_ptr(), len) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_bytes_creation() { + let b = naml_bytes_new(16); + assert!(!b.is_null()); + unsafe { + assert_eq!((*b).len, 0); + assert_eq!((*b).capacity, 16); + naml_bytes_decref(b); + } + } + + #[test] + fn test_bytes_from() { + let data = b"hello"; + let b = naml_bytes_from(data.as_ptr(), data.len()); + unsafe { + assert_eq!((*b).len, 5); + assert_eq!(naml_bytes_get(b, 0), 'h' as i64); + assert_eq!(naml_bytes_get(b, 4), 'o' as i64); + naml_bytes_decref(b); + } + } +} diff --git a/namlc/src/runtime/mod.rs b/namlc/src/runtime/mod.rs index 5d3dd26..6357a1e 100644 --- a/namlc/src/runtime/mod.rs +++ b/namlc/src/runtime/mod.rs @@ -20,12 +20,14 @@ pub mod array; pub mod scheduler; pub mod channel; pub mod map; +pub mod bytes; pub use value::*; pub use array::*; pub use scheduler::*; pub use channel::*; pub use map::*; +pub use bytes::*; use std::cell::Cell; use std::io::Write; diff --git a/namlc/src/runtime/value.rs b/namlc/src/runtime/value.rs index 01db46f..9f518c6 100644 --- a/namlc/src/runtime/value.rs +++ b/namlc/src/runtime/value.rs @@ -227,6 +227,71 @@ pub extern "C" fn naml_string_from_cstr(cstr: *const i8) -> *mut NamlString { } } +/// Convert an integer to a string +#[unsafe(no_mangle)] +pub extern "C" fn naml_int_to_string(n: i64) -> *mut NamlString { + let s = n.to_string(); + naml_string_new(s.as_ptr(), s.len()) +} + +/// Convert a float to a string +#[unsafe(no_mangle)] +pub extern "C" fn naml_float_to_string(f: f64) -> *mut NamlString { + let s = f.to_string(); + naml_string_new(s.as_ptr(), s.len()) +} + +/// Convert a string to an integer (returns 0 on parse failure) +#[unsafe(no_mangle)] +pub extern "C" fn naml_string_to_int(s: *const NamlString) -> i64 { + if s.is_null() { + return 0; + } + unsafe { + let str_val = (*s).as_str(); + str_val.parse::().unwrap_or(0) + } +} + +/// Convert a string to a float (returns 0.0 on parse failure) +#[unsafe(no_mangle)] +pub extern "C" fn naml_string_to_float(s: *const NamlString) -> f64 { + if s.is_null() { + return 0.0; + } + unsafe { + let str_val = (*s).as_str(); + str_val.parse::().unwrap_or(0.0) + } +} + +/// Get character (as UTF-8 codepoint) at index +#[unsafe(no_mangle)] +pub extern "C" fn naml_string_char_at(s: *const NamlString, index: i64) -> i64 { + if s.is_null() { + return 0; + } + unsafe { + let str_val = (*s).as_str(); + if let Some(c) = str_val.chars().nth(index as usize) { + c as i64 + } else { + 0 + } + } +} + +/// Get string length in characters (not bytes) +#[unsafe(no_mangle)] +pub extern "C" fn naml_string_char_len(s: *const NamlString) -> i64 { + if s.is_null() { + return 0; + } + unsafe { + (*s).as_str().chars().count() as i64 + } +} + /// Allocate a new struct on the heap #[unsafe(no_mangle)] pub extern "C" fn naml_struct_new(type_id: u32, field_count: u32) -> *mut NamlStruct { diff --git a/namlc/src/typechecker/infer.rs b/namlc/src/typechecker/infer.rs index cc8021f..be5f49b 100644 --- a/namlc/src/typechecker/infer.rs +++ b/namlc/src/typechecker/infer.rs @@ -760,6 +760,46 @@ impl<'a> TypeInferrer<'a> { }; } + // Handle built-in string methods + if let Type::String = &resolved { + let method_name = self.interner.resolve(&call.method.symbol); + return match method_name { + "len" => { + if !call.args.is_empty() { + self.errors.push(TypeError::WrongArgCount { + expected: 0, + found: call.args.len(), + span: call.span, + }); + } + Type::Int + } + "char_at" => { + if call.args.len() != 1 { + self.errors.push(TypeError::WrongArgCount { + expected: 1, + found: call.args.len(), + span: call.span, + }); + return Type::Int; + } + let arg_ty = self.infer_expr(&call.args[0]); + if let Err(e) = unify(&arg_ty, &Type::Int, call.args[0].span()) { + self.errors.push(e); + } + Type::Int + } + _ => { + self.errors.push(TypeError::UndefinedMethod { + ty: resolved.to_string(), + method: method_name.to_string(), + span: call.span, + }); + Type::Error + } + }; + } + let type_name = match &resolved { Type::Struct(s) => s.name, Type::Enum(e) => e.name, From 9bc4591e4d0d76ca031b3c0609206c350862a1a7 Mon Sep 17 00:00:00 2001 From: Mohammad Julfikar Date: Thu, 22 Jan 2026 15:47:00 +0800 Subject: [PATCH 2/3] feat(generics): implement methods on generic type receivers - Support method receivers like `(self: LinkedList)` in codegen - Add type parameter substitution in method call inference - Generic method return types now correctly resolve (e.g., `Box.get_value()` returns string) Co-Authored-By: Claude Opus 4.5 --- examples/test_generic_methods.naml | 47 ++++++++++++++++++++++++++++++ namlc/src/codegen/cranelift/mod.rs | 8 +++-- namlc/src/typechecker/infer.rs | 23 +++++++++++++-- 3 files changed, 73 insertions(+), 5 deletions(-) create mode 100644 examples/test_generic_methods.naml diff --git a/examples/test_generic_methods.naml b/examples/test_generic_methods.naml new file mode 100644 index 0000000..0ee999c --- /dev/null +++ b/examples/test_generic_methods.naml @@ -0,0 +1,47 @@ +/// +/// Test: Generic Methods on Types +/// + +pub struct Box { + pub value: T +} + +pub fn (self: Box) get_value() -> T { + return self.value; +} + +pub struct Pair { + pub first: A, + pub second: B +} + +pub fn (self: Pair) get_first() -> A { + return self.first; +} + +pub fn (self: Pair) get_second() -> B { + return self.second; +} + +fn main() { + println("=== Generic Method Tests ==="); + + // Test Box + var int_box: Box = Box { value: 42 }; + print("Box.get_value(): "); + println(int_box.get_value()); + + // Test Box + var str_box: Box = Box { value: "hello" }; + print("Box.get_value(): "); + println(str_box.get_value()); + + // Test Pair + var pair: Pair = Pair { first: 1, second: "one" }; + print("Pair.get_first(): "); + println(pair.get_first()); + print("Pair.get_second(): "); + println(pair.get_second()); + + println("=== Generic Method Tests Complete ==="); +} diff --git a/namlc/src/codegen/cranelift/mod.rs b/namlc/src/codegen/cranelift/mod.rs index 2585359..fe11a6a 100644 --- a/namlc/src/codegen/cranelift/mod.rs +++ b/namlc/src/codegen/cranelift/mod.rs @@ -1240,10 +1240,11 @@ impl<'a> JitCompiler<'a> { let receiver = func.receiver.as_ref() .ok_or_else(|| CodegenError::JitCompile("Method must have receiver".to_string()))?; - // Get receiver type name + // Get receiver type name (handles both Named and Generic types) let receiver_type_name = match &receiver.ty { crate::ast::NamlType::Named(ident) => self.interner.resolve(&ident.symbol).to_string(), - _ => return Err(CodegenError::JitCompile("Method receiver must be a named type".to_string())), + crate::ast::NamlType::Generic(ident, _) => self.interner.resolve(&ident.symbol).to_string(), + _ => return Err(CodegenError::JitCompile("Method receiver must be a named or generic type".to_string())), }; let method_name = self.interner.resolve(&func.name.symbol); @@ -1280,7 +1281,8 @@ impl<'a> JitCompiler<'a> { let receiver_type_name = match &receiver.ty { crate::ast::NamlType::Named(ident) => self.interner.resolve(&ident.symbol).to_string(), - _ => return Err(CodegenError::JitCompile("Method receiver must be a named type".to_string())), + crate::ast::NamlType::Generic(ident, _) => self.interner.resolve(&ident.symbol).to_string(), + _ => return Err(CodegenError::JitCompile("Method receiver must be a named or generic type".to_string())), }; let method_name = self.interner.resolve(&func.name.symbol); diff --git a/namlc/src/typechecker/infer.rs b/namlc/src/typechecker/infer.rs index be5f49b..ed6bb67 100644 --- a/namlc/src/typechecker/infer.rs +++ b/namlc/src/typechecker/infer.rs @@ -826,14 +826,33 @@ impl<'a> TypeInferrer<'a> { return Type::Error; } + // Build substitution map for generic type arguments + use std::collections::HashMap; + let substitutions: HashMap = if let Type::Generic(_, type_args) = &resolved { + // Look up the struct definition to get type parameter names + if let Some(TypeDef::Struct(struct_def)) = self.symbols.get_type(type_name) { + struct_def.type_params.iter() + .zip(type_args.iter()) + .map(|(param, arg)| (param.name, arg.clone())) + .collect() + } else { + HashMap::new() + } + } else { + HashMap::new() + }; + for (arg, (_, param_ty)) in call.args.iter().zip(method.params.iter()) { let arg_ty = self.infer_expr(arg); - if let Err(e) = unify(&arg_ty, param_ty, arg.span()) { + // Substitute type parameters in the parameter type + let substituted_param_ty = param_ty.substitute(&substitutions); + if let Err(e) = unify(&arg_ty, &substituted_param_ty, arg.span()) { self.errors.push(e); } } - method.return_ty.clone() + // Substitute type parameters in the return type + method.return_ty.substitute(&substitutions) } else { let method = self.interner.resolve(&call.method.symbol).to_string(); self.errors.push(TypeError::UndefinedMethod { From 4093bea217d1ddac9a8ed56837fe338651aa2b41 Mon Sep 17 00:00:00 2001 From: Mohammad Julfikar Date: Thu, 22 Jan 2026 20:35:16 +0800 Subject: [PATCH 3/3] feat(generics): implement monomorphization for generic functions Implements monomorphization strategy for generic functions with trait bounds, enabling trait method dispatch on type parameters. Also fixes several related issues discovered during testing. Key changes: - Add monomorphization tracking in TypeAnnotations (typed_ast.rs) - Implement type mangling for generating specialized function names (infer.rs) - Add monomorphized function compilation in Cranelift codegen - Fix field assignment for generic struct types (Type::Generic case) - Fix while loop context for break/continue statements - Add var statement else block handling for option unwrap pattern - Fix unsafe block warnings in runtime tests (array, bytes, channel, value) Closes #17 Co-Authored-By: Claude Opus 4.5 --- examples/test_map.naml | 10 - examples/test_option.naml | 18 - namlc/src/ast/visitor.rs | 14 +- namlc/src/codegen/cranelift/mod.rs | 505 +++++++++++++++++++++-------- namlc/src/diagnostic.rs | 4 +- namlc/src/parser/combinators.rs | 4 +- namlc/src/parser/items.rs | 5 - namlc/src/parser/types.rs | 2 +- namlc/src/runtime/array.rs | 74 +++-- namlc/src/runtime/bytes.rs | 31 +- namlc/src/runtime/channel.rs | 44 +-- namlc/src/runtime/map.rs | 36 +- namlc/src/runtime/value.rs | 58 ++-- namlc/src/typechecker/env.rs | 7 +- namlc/src/typechecker/infer.rs | 173 ++++++---- namlc/src/typechecker/typed_ast.rs | 45 ++- namlc/src/typechecker/unify.rs | 5 +- 17 files changed, 670 insertions(+), 365 deletions(-) delete mode 100644 examples/test_map.naml delete mode 100644 examples/test_option.naml diff --git a/examples/test_map.naml b/examples/test_map.naml deleted file mode 100644 index c40f518..0000000 --- a/examples/test_map.naml +++ /dev/null @@ -1,10 +0,0 @@ -fn main() { - var m: map = {}; - m["one"] = 1; - m["two"] = 2; - m["three"] = 3; - - println(m["one"]); - println(m["two"]); - println(m["three"]); -} diff --git a/examples/test_option.naml b/examples/test_option.naml deleted file mode 100644 index ecc11e1..0000000 --- a/examples/test_option.naml +++ /dev/null @@ -1,18 +0,0 @@ -fn main() { - var x: option = some(42); - var y: option = none; - - if (x.is_some()) { - println("x has a value"); - } - - if (y.is_none()) { - println("y is none"); - } - - var val: int = x.or_default(0); - println(val); - - var default_val: int = y.or_default(100); - println(default_val); -} diff --git a/namlc/src/ast/visitor.rs b/namlc/src/ast/visitor.rs index bb99abf..1283c0a 100644 --- a/namlc/src/ast/visitor.rs +++ b/namlc/src/ast/visitor.rs @@ -333,7 +333,7 @@ pub fn walk_expr<'ast, V: Visitor<'ast>>(v: &mut V, expr: &Expression<'ast>) { for stmt in &e.then_branch.statements { v.visit_stmt(stmt); } - if let Some(ref tail) = e.then_branch.tail { + if let Some(tail) = e.then_branch.tail { v.visit_expr(tail); } if let Some(ref else_branch) = e.else_branch { @@ -345,7 +345,7 @@ pub fn walk_expr<'ast, V: Visitor<'ast>>(v: &mut V, expr: &Expression<'ast>) { for stmt in &block.statements { v.visit_stmt(stmt); } - if let Some(ref tail) = block.tail { + if let Some(tail) = block.tail { v.visit_expr(tail); } } @@ -356,7 +356,7 @@ pub fn walk_expr<'ast, V: Visitor<'ast>>(v: &mut V, expr: &Expression<'ast>) { for stmt in &e.statements { v.visit_stmt(stmt); } - if let Some(ref tail) = e.tail { + if let Some(tail) = e.tail { v.visit_expr(tail); } } @@ -376,7 +376,7 @@ pub fn walk_expr<'ast, V: Visitor<'ast>>(v: &mut V, expr: &Expression<'ast>) { for stmt in &e.body.statements { v.visit_stmt(stmt); } - if let Some(ref tail) = e.body.tail { + if let Some(tail) = e.body.tail { v.visit_expr(tail); } } @@ -389,7 +389,7 @@ pub fn walk_expr<'ast, V: Visitor<'ast>>(v: &mut V, expr: &Expression<'ast>) { for stmt in &e.handler.statements { v.visit_stmt(stmt); } - if let Some(ref tail) = e.handler.tail { + if let Some(tail) = e.handler.tail { v.visit_expr(tail); } } @@ -402,10 +402,10 @@ pub fn walk_expr<'ast, V: Visitor<'ast>>(v: &mut V, expr: &Expression<'ast>) { v.visit_type(&e.target_ty); } Expression::Range(e) => { - if let Some(ref start) = e.start { + if let Some(start) = e.start { v.visit_expr(start); } - if let Some(ref end) = e.end { + if let Some(end) = e.end { v.visit_expr(end); } } diff --git a/namlc/src/codegen/cranelift/mod.rs b/namlc/src/codegen/cranelift/mod.rs index fe11a6a..8f61676 100644 --- a/namlc/src/codegen/cranelift/mod.rs +++ b/namlc/src/codegen/cranelift/mod.rs @@ -76,9 +76,7 @@ unsafe impl Send for LambdaInfo {} pub struct JitCompiler<'a> { interner: &'a Rodeo, - #[allow(dead_code)] annotations: &'a TypeAnnotations, - #[allow(dead_code)] symbols: &'a SymbolTable, module: JITModule, ctx: codegen::Context, @@ -91,6 +89,7 @@ pub struct JitCompiler<'a> { spawn_blocks: HashMap, lambda_counter: u32, lambda_blocks: HashMap, + generic_functions: HashMap>, } impl<'a> JitCompiler<'a> { @@ -246,10 +245,11 @@ impl<'a> JitCompiler<'a> { spawn_blocks: HashMap::new(), lambda_counter: 0, lambda_blocks: HashMap::new(), + generic_functions: HashMap::new(), }) } - pub fn compile(&mut self, ast: &SourceFile<'_>) -> Result<(), CodegenError> { + pub fn compile(&mut self, ast: &'a SourceFile<'a>) -> Result<(), CodegenError> { // First pass: collect struct definitions with field heap types for item in &ast.items { if let crate::ast::Item::Struct(struct_item) = item { @@ -311,7 +311,7 @@ impl<'a> JitCompiler<'a> { } // Align to 8 bytes - let size = 8 + ((max_data_size + 7) / 8) * 8; + let size = 8 + max_data_size.div_ceil(8) * 8; self.enum_defs.insert(name.clone(), EnumDef { name, @@ -348,10 +348,9 @@ impl<'a> JitCompiler<'a> { // Scan for spawn blocks and collect captured variable info for item in &ast.items { - if let Item::Function(f) = item { - if let Some(ref body) = f.body { - self.scan_for_spawn_blocks(body)?; - } + if let Item::Function(f) = item + && let Some(ref body) = f.body { + self.scan_for_spawn_blocks(body)?; } } @@ -377,9 +376,15 @@ impl<'a> JitCompiler<'a> { } // Declare all functions first (standalone and methods) + // Skip generic functions - they will be monomorphized for item in &ast.items { if let Item::Function(f) = item { - if f.receiver.is_none() { + let is_generic = !f.generics.is_empty(); + if is_generic && f.receiver.is_none() { + // Store generic function for later monomorphization + let name = self.interner.resolve(&f.name.symbol).to_string(); + self.generic_functions.insert(name, f as *const _); + } else if f.receiver.is_none() { self.declare_function(f)?; } else { self.declare_method(f)?; @@ -387,22 +392,23 @@ impl<'a> JitCompiler<'a> { } } - // Compile standalone functions + // Process monomorphizations - declare and compile specialized versions + self.process_monomorphizations()?; + + // Compile standalone functions (skip generic functions) for item in &ast.items { - if let Item::Function(f) = item { - if f.receiver.is_none() && f.body.is_some() { + if let Item::Function(f) = item + && f.receiver.is_none() && f.body.is_some() && f.generics.is_empty() { self.compile_function(f)?; } - } } // Compile methods for item in &ast.items { - if let Item::Function(f) = item { - if f.receiver.is_some() && f.body.is_some() { + if let Item::Function(f) = item + && f.receiver.is_some() && f.body.is_some() { self.compile_method(f)?; } - } } Ok(()) @@ -533,16 +539,7 @@ impl<'a> JitCompiler<'a> { } } HeapType::Struct(None) => "naml_struct_decref", - HeapType::Struct(Some(nested_struct_name)) => { - if self.struct_defs.get(nested_struct_name) - .map(|def| def.field_heap_types.iter().any(|h| h.is_some())) - .unwrap_or(false) - { - "naml_struct_decref" // Fallback for now - } else { - "naml_struct_decref" - } - } + HeapType::Struct(Some(_)) => "naml_struct_decref" }; let decref_func_id = self.module @@ -582,6 +579,160 @@ impl<'a> JitCompiler<'a> { Ok(()) } + fn process_monomorphizations(&mut self) -> Result<(), CodegenError> { + let monomorphizations: Vec<_> = self.annotations + .get_monomorphizations() + .values() + .cloned() + .collect(); + + for mono_info in monomorphizations { + let func_name = self.interner.resolve(&mono_info.function_name).to_string(); + + // Get the generic function AST + let func_ptr = match self.generic_functions.get(&func_name) { + Some(ptr) => *ptr, + None => continue, // Skip if function not found (might be external) + }; + + // Build type substitution map: param_name -> concrete_type_name + let func = unsafe { &*func_ptr }; + let mut type_substitutions = HashMap::new(); + for (param, arg_ty) in func.generics.iter().zip(mono_info.type_args.iter()) { + let param_name = self.interner.resolve(¶m.name.symbol).to_string(); + let concrete_name = self.mangle_type_name(arg_ty); + type_substitutions.insert(param_name, concrete_name); + } + + // Declare the monomorphized function + self.declare_monomorphized_function(func, &mono_info.mangled_name)?; + + // Compile the monomorphized function with type substitutions + self.compile_monomorphized_function(func, &mono_info.mangled_name, type_substitutions)?; + } + + Ok(()) + } + + fn mangle_type_name(&self, ty: &Type) -> String { + match ty { + Type::Int => "int".to_string(), + Type::Uint => "uint".to_string(), + Type::Float => "float".to_string(), + Type::Bool => "bool".to_string(), + Type::String => "string".to_string(), + Type::Bytes => "bytes".to_string(), + Type::Unit => "unit".to_string(), + Type::Struct(s) => self.interner.resolve(&s.name).to_string(), + Type::Enum(e) => self.interner.resolve(&e.name).to_string(), + Type::Generic(name, _) => self.interner.resolve(name).to_string(), + _ => "unknown".to_string(), + } + } + + fn declare_monomorphized_function( + &mut self, + func: &FunctionItem<'_>, + mangled_name: &str, + ) -> Result { + let mut sig = self.module.make_signature(); + + for param in &func.params { + let ty = types::naml_to_cranelift(¶m.ty); + sig.params.push(AbiParam::new(ty)); + } + + if let Some(ref return_ty) = func.return_ty { + let ty = types::naml_to_cranelift(return_ty); + sig.returns.push(AbiParam::new(ty)); + } + + let func_id = self.module + .declare_function(mangled_name, Linkage::Export, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare monomorphized function '{}': {}", mangled_name, e)))?; + + self.functions.insert(mangled_name.to_string(), func_id); + + Ok(func_id) + } + + fn compile_monomorphized_function( + &mut self, + func: &FunctionItem<'_>, + mangled_name: &str, + type_substitutions: HashMap, + ) -> Result<(), CodegenError> { + let func_id = *self.functions.get(mangled_name) + .ok_or_else(|| CodegenError::JitCompile(format!("Monomorphized function '{}' not declared", mangled_name)))?; + + self.ctx.func.signature = self.module.declarations().get_function_decl(func_id).signature.clone(); + self.ctx.func.name = cranelift_codegen::ir::UserFuncName::user(0, func_id.as_u32()); + + let mut builder_ctx = FunctionBuilderContext::new(); + let mut builder = FunctionBuilder::new(&mut self.ctx.func, &mut builder_ctx); + + let entry_block = builder.create_block(); + builder.append_block_params_for_function_params(entry_block); + builder.switch_to_block(entry_block); + builder.seal_block(entry_block); + + let mut ctx = CompileContext { + interner: self.interner, + module: &mut self.module, + functions: &self.functions, + struct_defs: &self.struct_defs, + enum_defs: &self.enum_defs, + extern_fns: &self.extern_fns, + variables: HashMap::new(), + var_heap_types: HashMap::new(), + var_counter: 0, + block_terminated: false, + loop_exit_block: None, + loop_header_block: None, + spawn_blocks: &self.spawn_blocks, + current_spawn_id: 0, + lambda_blocks: &self.lambda_blocks, + current_lambda_id: 0, + annotations: self.annotations, + type_substitutions, + }; + + for (i, param) in func.params.iter().enumerate() { + let param_name = self.interner.resolve(¶m.name.symbol).to_string(); + let val = builder.block_params(entry_block)[i]; + let var = Variable::new(ctx.var_counter); + ctx.var_counter += 1; + let ty = types::naml_to_cranelift(¶m.ty); + builder.declare_var(var, ty); + builder.def_var(var, val); + ctx.variables.insert(param_name, var); + } + + if let Some(ref body) = func.body { + for stmt in &body.statements { + compile_statement(&mut ctx, &mut builder, stmt)?; + if ctx.block_terminated { + break; + } + } + } + + if !ctx.block_terminated && func.return_ty.is_none() { + emit_cleanup_all_vars(&mut ctx, &mut builder, None)?; + builder.ins().return_(&[]); + } + + builder.finalize(); + + self.module + .define_function(func_id, &mut self.ctx) + .map_err(|e| CodegenError::JitCompile(format!("Failed to define monomorphized function '{}': {}", mangled_name, e)))?; + + self.module.clear_context(&mut self.ctx); + + Ok(()) + } + fn scan_for_spawn_blocks(&mut self, block: &crate::ast::BlockStmt<'_>) -> Result<(), CodegenError> { for stmt in &block.statements { self.scan_statement_for_spawns(stmt)?; @@ -653,7 +804,7 @@ impl<'a> JitCompiler<'a> { match expr { Expression::Spawn(spawn_expr) => { // Found a spawn block - collect captured variables - let captured = self.collect_captured_vars_expr(&spawn_expr.body); + let captured = self.collect_captured_vars_expr(spawn_expr.body); let id = self.spawn_counter; self.spawn_counter += 1; let func_name = format!("__spawn_{}", id); @@ -661,6 +812,7 @@ impl<'a> JitCompiler<'a> { // Store raw pointer to body for deferred trampoline compilation // Safety: Only used within the same compile() call // Note: spawn_expr.body is already a &BlockExpr, so we cast it directly + #[allow(clippy::unnecessary_cast)] let body_ptr = spawn_expr.body as *const crate::ast::BlockExpr<'_> as *const crate::ast::BlockExpr<'static>; self.spawn_blocks.insert(id, SpawnBlockInfo { @@ -671,7 +823,7 @@ impl<'a> JitCompiler<'a> { }); // Also scan inside spawn block for nested spawns - self.scan_for_spawn_blocks_expr(&spawn_expr.body)?; + self.scan_for_spawn_blocks_expr(spawn_expr.body)?; } Expression::Lambda(lambda_expr) => { // Found a lambda - collect captured variables @@ -686,6 +838,7 @@ impl<'a> JitCompiler<'a> { .collect(); // Store raw pointer to body for deferred lambda compilation + #[allow(clippy::unnecessary_cast)] let body_ptr = lambda_expr.body as *const crate::ast::Expression<'_> as *const crate::ast::Expression<'static>; self.lambda_blocks.insert(id, LambdaInfo { @@ -697,30 +850,30 @@ impl<'a> JitCompiler<'a> { }); // Scan lambda body for nested spawns/lambdas - self.scan_expression_for_spawns(&lambda_expr.body)?; + self.scan_expression_for_spawns(lambda_expr.body)?; } Expression::Binary(bin) => { - self.scan_expression_for_spawns(&bin.left)?; - self.scan_expression_for_spawns(&bin.right)?; + self.scan_expression_for_spawns(bin.left)?; + self.scan_expression_for_spawns(bin.right)?; } Expression::Unary(un) => { - self.scan_expression_for_spawns(&un.operand)?; + self.scan_expression_for_spawns(un.operand)?; } Expression::Call(call) => { - self.scan_expression_for_spawns(&call.callee)?; + self.scan_expression_for_spawns(call.callee)?; for arg in &call.args { self.scan_expression_for_spawns(arg)?; } } Expression::MethodCall(method) => { - self.scan_expression_for_spawns(&method.receiver)?; + self.scan_expression_for_spawns(method.receiver)?; for arg in &method.args { self.scan_expression_for_spawns(arg)?; } } Expression::Index(idx) => { - self.scan_expression_for_spawns(&idx.base)?; - self.scan_expression_for_spawns(&idx.index)?; + self.scan_expression_for_spawns(idx.base)?; + self.scan_expression_for_spawns(idx.index)?; } Expression::Array(arr) => { for elem in &arr.elements { @@ -728,15 +881,15 @@ impl<'a> JitCompiler<'a> { } } Expression::If(if_expr) => { - self.scan_expression_for_spawns(&if_expr.condition)?; - self.scan_for_spawn_blocks_expr(&if_expr.then_branch)?; + self.scan_expression_for_spawns(if_expr.condition)?; + self.scan_for_spawn_blocks_expr(if_expr.then_branch)?; self.scan_else_branch_for_spawns(&if_expr.else_branch)?; } Expression::Block(block) => { self.scan_for_spawn_blocks_expr(block)?; } Expression::Grouped(grouped) => { - self.scan_expression_for_spawns(&grouped.inner)?; + self.scan_expression_for_spawns(grouped.inner)?; } _ => {} } @@ -757,8 +910,8 @@ impl<'a> JitCompiler<'a> { if let Some(branch) = else_branch { match branch { crate::ast::ElseExpr::ElseIf(elif) => { - self.scan_expression_for_spawns(&elif.condition)?; - self.scan_for_spawn_blocks_expr(&elif.then_branch)?; + self.scan_expression_for_spawns(elif.condition)?; + self.scan_for_spawn_blocks_expr(elif.then_branch)?; self.scan_else_branch_for_spawns(&elif.else_branch)?; } crate::ast::ElseExpr::Else(block) => { @@ -787,7 +940,7 @@ impl<'a> JitCompiler<'a> { } // Collect from body (which is an Expression - typically a Block) - self.collect_vars_in_expression(&lambda.body, &mut captured, &defined); + self.collect_vars_in_expression(lambda.body, &mut captured, &defined); captured } @@ -878,27 +1031,27 @@ impl<'a> JitCompiler<'a> { } } Expression::Binary(bin) => { - self.collect_vars_in_expression(&bin.left, captured, defined); - self.collect_vars_in_expression(&bin.right, captured, defined); + self.collect_vars_in_expression(bin.left, captured, defined); + self.collect_vars_in_expression(bin.right, captured, defined); } Expression::Unary(un) => { - self.collect_vars_in_expression(&un.operand, captured, defined); + self.collect_vars_in_expression(un.operand, captured, defined); } Expression::Call(call) => { - self.collect_vars_in_expression(&call.callee, captured, defined); + self.collect_vars_in_expression(call.callee, captured, defined); for arg in &call.args { self.collect_vars_in_expression(arg, captured, defined); } } Expression::MethodCall(method) => { - self.collect_vars_in_expression(&method.receiver, captured, defined); + self.collect_vars_in_expression(method.receiver, captured, defined); for arg in &method.args { self.collect_vars_in_expression(arg, captured, defined); } } Expression::Index(idx) => { - self.collect_vars_in_expression(&idx.base, captured, defined); - self.collect_vars_in_expression(&idx.index, captured, defined); + self.collect_vars_in_expression(idx.base, captured, defined); + self.collect_vars_in_expression(idx.index, captured, defined); } Expression::Array(arr) => { for elem in &arr.elements { @@ -906,7 +1059,7 @@ impl<'a> JitCompiler<'a> { } } Expression::Grouped(grouped) => { - self.collect_vars_in_expression(&grouped.inner, captured, defined); + self.collect_vars_in_expression(grouped.inner, captured, defined); } Expression::Block(block) => { // Create a new defined set for block scope @@ -925,7 +1078,7 @@ impl<'a> JitCompiler<'a> { let name = self.interner.resolve(¶m.name.symbol).to_string(); lambda_defined.insert(name); } - self.collect_vars_in_expression(&lambda.body, captured, &lambda_defined); + self.collect_vars_in_expression(lambda.body, captured, &lambda_defined); } _ => {} } @@ -981,6 +1134,7 @@ impl<'a> JitCompiler<'a> { lambda_blocks: &self.lambda_blocks, current_lambda_id: 0, annotations: self.annotations, + type_substitutions: HashMap::new(), }; // Load captured variables from closure data @@ -1088,6 +1242,7 @@ impl<'a> JitCompiler<'a> { lambda_blocks: &self.lambda_blocks, current_lambda_id: 0, annotations: self.annotations, + type_substitutions: HashMap::new(), }; // Load captured variables from closure data @@ -1197,6 +1352,7 @@ impl<'a> JitCompiler<'a> { lambda_blocks: &self.lambda_blocks, current_lambda_id: 0, annotations: self.annotations, + type_substitutions: HashMap::new(), }; for (i, param) in func.params.iter().enumerate() { @@ -1323,6 +1479,7 @@ impl<'a> JitCompiler<'a> { lambda_blocks: &self.lambda_blocks, current_lambda_id: 0, annotations: self.annotations, + type_substitutions: HashMap::new(), }; // Set up receiver variable (self) @@ -1457,6 +1614,7 @@ struct CompileContext<'a> { lambda_blocks: &'a HashMap, current_lambda_id: u32, annotations: &'a TypeAnnotations, + type_substitutions: HashMap, } fn compile_statement( @@ -1477,25 +1635,91 @@ fn compile_statement( let is_string_var = matches!(var_stmt.ty.as_ref(), Some(crate::ast::NamlType::String)); // Track heap type for cleanup - if let Some(ref naml_ty) = var_stmt.ty { - if let Some(heap_type) = get_heap_type(naml_ty) { + if let Some(ref naml_ty) = var_stmt.ty + && let Some(heap_type) = get_heap_type(naml_ty) { ctx.var_heap_types.insert(var_name.clone(), heap_type); } - } let var = Variable::new(ctx.var_counter); ctx.var_counter += 1; builder.declare_var(var, ty); - if let Some(ref init) = var_stmt.init { + // Handle else block for option unwrap pattern: var x = opt else { ... } + if let (Some(init), Some(else_block)) = (&var_stmt.init, &var_stmt.else_block) { + // This is an option unwrap with else block + // Compile the option expression + let option_ptr = compile_expression(ctx, builder, init)?; + + // Load the tag from offset 0 (0 = none, 1 = some) + let tag = builder.ins().load( + cranelift::prelude::types::I32, + MemFlags::new(), + option_ptr, + 0, + ); + + // Create blocks + let some_block = builder.create_block(); + let none_block = builder.create_block(); + let merge_block = builder.create_block(); + + // Branch based on tag (tag == 0 means none) + let is_none = builder.ins().icmp_imm(IntCC::Equal, tag, 0); + builder.ins().brif(is_none, none_block, &[], some_block, &[]); + + // None block: execute else block + builder.switch_to_block(none_block); + builder.seal_block(none_block); + + // Initialize variable with zero before else block (in case else doesn't exit) + let zero = builder.ins().iconst(ty, 0); + builder.def_var(var, zero); + + for else_stmt in &else_block.statements { + compile_statement(ctx, builder, else_stmt)?; + if ctx.block_terminated { + break; + } + } + + // If else block didn't terminate (return/break), jump to merge + if !ctx.block_terminated { + builder.ins().jump(merge_block, &[]); + } + ctx.block_terminated = false; + + // Some block: extract value and assign + builder.switch_to_block(some_block); + builder.seal_block(some_block); + + // Load value from offset 8 + let val = builder.ins().load( + cranelift::prelude::types::I64, + MemFlags::new(), + option_ptr, + 8, + ); + builder.def_var(var, val); + + // Incref the value + let heap_type_clone = ctx.var_heap_types.get(&var_name).cloned(); + if let Some(ref heap_type) = heap_type_clone { + emit_incref(ctx, builder, val, heap_type)?; + } + + builder.ins().jump(merge_block, &[]); + + // Merge block + builder.switch_to_block(merge_block); + builder.seal_block(merge_block); + } else if let Some(ref init) = var_stmt.init { let mut val = compile_expression(ctx, builder, init)?; // Box string literals as NamlString* for consistent memory management - if is_string_var { - if matches!(init, Expression::Literal(LiteralExpr { value: Literal::String(_), .. })) { + if is_string_var + && matches!(init, Expression::Literal(LiteralExpr { value: Literal::String(_), .. })) { val = call_string_from_cstr(ctx, builder, val)?; } - } builder.def_var(var, val); // Incref the value since we're storing a reference @@ -1529,11 +1753,10 @@ fn compile_statement( let mut val = compile_expression(ctx, builder, &assign.value)?; // Box string literals as NamlString* when assigning to string variables - if matches!(&heap_type_clone, Some(HeapType::String)) { - if matches!(&assign.value, Expression::Literal(LiteralExpr { value: Literal::String(_), .. })) { + if matches!(&heap_type_clone, Some(HeapType::String)) + && matches!(&assign.value, Expression::Literal(LiteralExpr { value: Literal::String(_), .. })) { val = call_string_from_cstr(ctx, builder, val)?; } - } builder.def_var(var, val); @@ -1546,37 +1769,37 @@ fn compile_statement( } } Expression::Index(index_expr) => { - let base = compile_expression(ctx, builder, &index_expr.base)?; + let base = compile_expression(ctx, builder, index_expr.base)?; let value = compile_expression(ctx, builder, &assign.value)?; // Check if index is a string literal - if so, use map_set with NamlString conversion - if let Expression::Literal(LiteralExpr { value: Literal::String(_), .. }) = &*index_expr.index { - let cstr_ptr = compile_expression(ctx, builder, &index_expr.index)?; + if let Expression::Literal(LiteralExpr { value: Literal::String(_), .. }) = index_expr.index { + let cstr_ptr = compile_expression(ctx, builder, index_expr.index)?; let naml_str = call_string_from_cstr(ctx, builder, cstr_ptr)?; call_map_set(ctx, builder, base, naml_str, value)?; } else { // Default to array set for integer indices - let index = compile_expression(ctx, builder, &index_expr.index)?; + let index = compile_expression(ctx, builder, index_expr.index)?; call_array_set(ctx, builder, base, index, value)?; } } Expression::Field(field_expr) => { // Field assignment: base.field = value // Get the base pointer (struct/exception) - let base_ptr = compile_expression(ctx, builder, &field_expr.base)?; + let base_ptr = compile_expression(ctx, builder, field_expr.base)?; let value = compile_expression(ctx, builder, &assign.value)?; let field_name = ctx.interner.resolve(&field_expr.field.symbol).to_string(); // Determine field offset based on struct type // For exceptions: offset 0 is message, then fields at 8, 16, etc. // For structs: fields at 0, 8, 16, etc. - if let Expression::Identifier(ident) = &*field_expr.base { + if let Expression::Identifier(ident) = field_expr.base { let _var_name = ctx.interner.resolve(&ident.ident.symbol).to_string(); // Get the type annotation to determine struct/exception type // Note: use ident.span (IdentExpr span), not ident.ident.span (Ident span) if let Some(type_ann) = ctx.annotations.get_type(ident.span) { if let crate::typechecker::Type::Exception(exc_name) = type_ann { - let exc_name_str = ctx.interner.resolve(&exc_name).to_string(); + let exc_name_str = ctx.interner.resolve(exc_name).to_string(); if let Some(struct_def) = ctx.struct_defs.get(&exc_name_str) { // Find field offset (message at 0, then fields at 8, 16, ...) let offset = if field_name == "message" { @@ -1591,13 +1814,21 @@ fn compile_statement( } } else if let crate::typechecker::Type::Struct(struct_type) = type_ann { let struct_name = ctx.interner.resolve(&struct_type.name).to_string(); - if let Some(struct_def) = ctx.struct_defs.get(&struct_name) { - if let Some(idx) = struct_def.fields.iter().position(|f| f == &field_name) { + if let Some(struct_def) = ctx.struct_defs.get(&struct_name) + && let Some(idx) = struct_def.fields.iter().position(|f| f == &field_name) { + let offset = (idx * 8) as i32; + builder.ins().store(MemFlags::new(), value, base_ptr, offset); + return Ok(()); + } + } else if let crate::typechecker::Type::Generic(name, _) = type_ann { + // Handle generic struct types like LinkedList + let struct_name = ctx.interner.resolve(name).to_string(); + if let Some(struct_def) = ctx.struct_defs.get(&struct_name) + && let Some(idx) = struct_def.fields.iter().position(|f| f == &field_name) { let offset = (idx * 8) as i32; builder.ins().store(MemFlags::new(), value, base_ptr, offset); return Ok(()); } - } } } } @@ -1618,11 +1849,10 @@ fn compile_statement( // Convert string literals to NamlString when returning let return_type = ctx.annotations.get_type(expr.span()); - if matches!(return_type, Some(Type::String)) { - if matches!(expr, Expression::Literal(LiteralExpr { value: Literal::String(_), .. })) { + if matches!(return_type, Some(Type::String)) + && matches!(expr, Expression::Literal(LiteralExpr { value: Literal::String(_), .. })) { val = call_string_from_cstr(ctx, builder, val)?; } - } // Determine if we're returning a local heap variable (ownership transfer) let returned_var = get_returned_var_name(expr, ctx.interner); @@ -1709,6 +1939,12 @@ fn compile_statement( let body_block = builder.create_block(); let exit_block = builder.create_block(); + // Save and set loop context for break/continue + let prev_loop_exit = ctx.loop_exit_block.take(); + let prev_loop_header = ctx.loop_header_block.take(); + ctx.loop_exit_block = Some(exit_block); + ctx.loop_header_block = Some(header_block); + builder.ins().jump(header_block, &[]); builder.switch_to_block(header_block); @@ -1732,6 +1968,10 @@ fn compile_statement( builder.switch_to_block(exit_block); builder.seal_block(exit_block); ctx.block_terminated = false; + + // Restore previous loop context + ctx.loop_exit_block = prev_loop_exit; + ctx.loop_header_block = prev_loop_header; } Statement::For(for_stmt) => { @@ -2173,12 +2413,6 @@ fn compile_statement( } } } - - _ => { - return Err(CodegenError::Unsupported( - format!("Statement type not yet implemented: {:?}", std::mem::discriminant(stmt)) - )); - } } Ok(()) @@ -2238,13 +2472,12 @@ fn compile_pattern_match( (enum_name, variant_name) }; - if let Some(enum_def) = ctx.enum_defs.get(&enum_name) { - if let Some(var_def) = enum_def.variants.iter().find(|v| v.name == variant_name) { + if let Some(enum_def) = ctx.enum_defs.get(&enum_name) + && let Some(var_def) = enum_def.variants.iter().find(|v| v.name == variant_name) { let tag = builder.ins().load(cranelift::prelude::types::I32, MemFlags::new(), scrutinee, 0); let expected_tag = builder.ins().iconst(cranelift::prelude::types::I32, var_def.tag as i64); return Ok(builder.ins().icmp(IntCC::Equal, tag, expected_tag)); } - } Err(CodegenError::JitCompile(format!( "Unknown enum variant: {}::{}", @@ -2295,8 +2528,8 @@ fn bind_pattern_vars( (enum_name, variant_name) }; - if let Some(enum_def) = ctx.enum_defs.get(&enum_name) { - if let Some(var_def) = enum_def.variants.iter().find(|v| v.name == variant_name) { + if let Some(enum_def) = ctx.enum_defs.get(&enum_name) + && let Some(var_def) = enum_def.variants.iter().find(|v| v.name == variant_name) { for (i, binding) in variant.bindings.iter().enumerate() { let binding_name = ctx.interner.resolve(&binding.symbol).to_string(); let offset = (var_def.data_offset + i * 8) as i32; @@ -2315,7 +2548,6 @@ fn bind_pattern_vars( ctx.variables.insert(binding_name, var); } } - } } Pattern::Identifier(ident) => { @@ -2364,8 +2596,8 @@ fn compile_expression( let enum_name = ctx.interner.resolve(&path_expr.segments[0].symbol).to_string(); let variant_name = ctx.interner.resolve(&path_expr.segments[1].symbol).to_string(); - if let Some(enum_def) = ctx.enum_defs.get(&enum_name) { - if let Some(variant) = enum_def.variants.iter().find(|v| v.name == variant_name) { + if let Some(enum_def) = ctx.enum_defs.get(&enum_name) + && let Some(variant) = enum_def.variants.iter().find(|v| v.name == variant_name) { // Unit variant - allocate stack slot and set tag let slot = builder.create_sized_stack_slot(StackSlotData::new( StackSlotKind::ExplicitSlot, @@ -2381,7 +2613,6 @@ fn compile_expression( // Return pointer to stack slot return Ok(slot_addr); } - } } Err(CodegenError::Unsupported(format!( @@ -2396,7 +2627,7 @@ fn compile_expression( // Handle null coalescing operator: lhs ?? rhs // Returns lhs if not null/none, otherwise rhs if bin.op == BinaryOp::NullCoalesce { - let lhs = compile_expression(ctx, builder, &bin.left)?; + let lhs = compile_expression(ctx, builder, bin.left)?; // Create blocks for branching let then_block = builder.create_block(); @@ -2419,7 +2650,7 @@ fn compile_expression( // Else block: lhs is null, evaluate and use rhs builder.switch_to_block(else_block); builder.seal_block(else_block); - let rhs = compile_expression(ctx, builder, &bin.right)?; + let rhs = compile_expression(ctx, builder, bin.right)?; builder.ins().jump(merge_block, &[rhs]); // Merge block: result is block parameter @@ -2432,16 +2663,16 @@ fn compile_expression( // Check if this is a string comparison (Eq/NotEq) let lhs_type = ctx.annotations.get_type(bin.left.span()); if matches!(lhs_type, Some(Type::String)) && matches!(bin.op, BinaryOp::Eq | BinaryOp::NotEq) { - let lhs = compile_expression(ctx, builder, &bin.left)?; - let rhs = compile_expression(ctx, builder, &bin.right)?; + let lhs = compile_expression(ctx, builder, bin.left)?; + let rhs = compile_expression(ctx, builder, bin.right)?; // Convert lhs to NamlString if it's a string literal - let lhs_str = if matches!(&*bin.left, Expression::Literal(LiteralExpr { value: Literal::String(_), .. })) { + let lhs_str = if matches!(bin.left, Expression::Literal(LiteralExpr { value: Literal::String(_), .. })) { call_string_from_cstr(ctx, builder, lhs)? } else { lhs }; // Convert rhs to NamlString if it's a string literal - let rhs_str = if matches!(&*bin.right, Expression::Literal(LiteralExpr { value: Literal::String(_), .. })) { + let rhs_str = if matches!(bin.right, Expression::Literal(LiteralExpr { value: Literal::String(_), .. })) { call_string_from_cstr(ctx, builder, rhs)? } else { rhs @@ -2454,13 +2685,13 @@ fn compile_expression( } return Ok(result); } - let lhs = compile_expression(ctx, builder, &bin.left)?; - let rhs = compile_expression(ctx, builder, &bin.right)?; + let lhs = compile_expression(ctx, builder, bin.left)?; + let rhs = compile_expression(ctx, builder, bin.right)?; compile_binary_op(builder, &bin.op, lhs, rhs) } Expression::Unary(unary) => { - let operand = compile_expression(ctx, builder, &unary.operand)?; + let operand = compile_expression(ctx, builder, unary.operand)?; compile_unary_op(builder, &unary.op, operand) } @@ -2493,8 +2724,16 @@ fn compile_expression( _ => {} } + // Check if this is a call to a generic function (monomorphized) + // First, check if the call span has a monomorphization recorded + let actual_func_name = if let Some(mangled_name) = ctx.annotations.get_call_instantiation(call.span) { + mangled_name.as_str() + } else { + func_name + }; + // Check for normal (naml) function - if let Some(&func_id) = ctx.functions.get(func_name) { + if let Some(&func_id) = ctx.functions.get(actual_func_name) { let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); let mut args = Vec::new(); @@ -2597,8 +2836,8 @@ fn compile_expression( let enum_name = ctx.interner.resolve(&path_expr.segments[0].symbol).to_string(); let variant_name = ctx.interner.resolve(&path_expr.segments[1].symbol).to_string(); - if let Some(enum_def) = ctx.enum_defs.get(&enum_name) { - if let Some(variant) = enum_def.variants.iter().find(|v| v.name == variant_name) { + if let Some(enum_def) = ctx.enum_defs.get(&enum_name) + && let Some(variant) = enum_def.variants.iter().find(|v| v.name == variant_name) { // Allocate stack slot for enum let slot = builder.create_sized_stack_slot(StackSlotData::new( StackSlotKind::ExplicitSlot, @@ -2627,7 +2866,6 @@ fn compile_expression( return Ok(slot_addr); } - } } Err(CodegenError::Unsupported(format!( @@ -2643,7 +2881,7 @@ fn compile_expression( } Expression::Grouped(grouped) => { - compile_expression(ctx, builder, &grouped.inner) + compile_expression(ctx, builder, grouped.inner) } Expression::Block(block) => { @@ -2681,23 +2919,23 @@ fn compile_expression( } Expression::Index(index_expr) => { - let base = compile_expression(ctx, builder, &index_expr.base)?; + let base = compile_expression(ctx, builder, index_expr.base)?; // Check if index is a string literal - if so, use map_get with NamlString conversion - if let Expression::Literal(LiteralExpr { value: Literal::String(_), .. }) = &*index_expr.index { - let cstr_ptr = compile_expression(ctx, builder, &index_expr.index)?; + if let Expression::Literal(LiteralExpr { value: Literal::String(_), .. }) = index_expr.index { + let cstr_ptr = compile_expression(ctx, builder, index_expr.index)?; let naml_str = call_string_from_cstr(ctx, builder, cstr_ptr)?; call_map_get(ctx, builder, base, naml_str) } else { // Default to array access for integer indices - let index = compile_expression(ctx, builder, &index_expr.index)?; + let index = compile_expression(ctx, builder, index_expr.index)?; call_array_get(ctx, builder, base, index) } } Expression::MethodCall(method_call) => { let method_name = ctx.interner.resolve(&method_call.method.symbol); - compile_method_call(ctx, builder, &method_call.receiver, method_name, &method_call.args) + compile_method_call(ctx, builder, method_call.receiver, method_name, &method_call.args) } Expression::StructLiteral(struct_lit) => { @@ -2723,11 +2961,10 @@ fn compile_expression( let mut value = compile_expression(ctx, builder, &field.value)?; // Convert string literals to NamlString - if let Some(Type::String) = ctx.annotations.get_type(field.value.span()) { - if matches!(&field.value, Expression::Literal(LiteralExpr { value: Literal::String(_), .. })) { + if let Some(Type::String) = ctx.annotations.get_type(field.value.span()) + && matches!(&field.value, Expression::Literal(LiteralExpr { value: Literal::String(_), .. })) { value = call_string_from_cstr(ctx, builder, value)?; } - } let idx_val = builder.ins().iconst(cranelift::prelude::types::I32, field_idx as i64); call_struct_set_field(ctx, builder, struct_ptr, idx_val, value)?; } @@ -2736,15 +2973,15 @@ fn compile_expression( } Expression::Field(field_expr) => { - let struct_ptr = compile_expression(ctx, builder, &field_expr.base)?; + let struct_ptr = compile_expression(ctx, builder, field_expr.base)?; let field_name = ctx.interner.resolve(&field_expr.field.symbol).to_string(); // Use type annotation to determine correct field offset // Note: use ident.span (IdentExpr span), not ident.ident.span (Ident span) - if let Expression::Identifier(ident) = &*field_expr.base { - if let Some(type_ann) = ctx.annotations.get_type(ident.span) { + if let Expression::Identifier(ident) = field_expr.base + && let Some(type_ann) = ctx.annotations.get_type(ident.span) { if let crate::typechecker::Type::Exception(exc_name) = type_ann { - let exc_name_str = ctx.interner.resolve(&exc_name).to_string(); + let exc_name_str = ctx.interner.resolve(exc_name).to_string(); if let Some(struct_def) = ctx.struct_defs.get(&exc_name_str) { // Exception: message at offset 0, fields at 8, 16, ... let offset = if field_name == "message" { @@ -2764,8 +3001,8 @@ fn compile_expression( } } else if let crate::typechecker::Type::Struct(struct_type) = type_ann { let struct_name = ctx.interner.resolve(&struct_type.name).to_string(); - if let Some(struct_def) = ctx.struct_defs.get(&struct_name) { - if let Some(idx) = struct_def.fields.iter().position(|f| f == &field_name) { + if let Some(struct_def) = ctx.struct_defs.get(&struct_name) + && let Some(idx) = struct_def.fields.iter().position(|f| f == &field_name) { let offset = (idx * 8) as i32; let value = builder.ins().load( cranelift::prelude::types::I64, @@ -2775,10 +3012,8 @@ fn compile_expression( ); return Ok(value); } - } } } - } // Fallback to runtime lookup for generic cases for (_, struct_def) in ctx.struct_defs.iter() { @@ -2837,7 +3072,7 @@ fn compile_expression( } Expression::Some(some_expr) => { - let inner_val = compile_expression(ctx, builder, &some_expr.value)?; + let inner_val = compile_expression(ctx, builder, some_expr.value)?; // Allocate option on stack let slot = builder.create_sized_stack_slot(StackSlotData::new( @@ -2918,12 +3153,12 @@ fn compile_expression( Expression::Try(try_expr) => { // For now, try just evaluates the expression // Full exception unwinding will be implemented later - compile_expression(ctx, builder, &try_expr.expr) + compile_expression(ctx, builder, try_expr.expr) } Expression::Catch(catch_expr) => { // Compile the expression that might throw - let result = compile_expression(ctx, builder, &catch_expr.expr)?; + let result = compile_expression(ctx, builder, catch_expr.expr)?; // Check if an exception occurred let has_exception = call_exception_check(ctx, builder)?; @@ -2990,7 +3225,7 @@ fn compile_expression( Expression::OrDefault(or_default_expr) => { // Compile the option expression (returns pointer to option struct) - let option_ptr = compile_expression(ctx, builder, &or_default_expr.expr)?; + let option_ptr = compile_expression(ctx, builder, or_default_expr.expr)?; // Load the tag from offset 0 (0 = none, 1 = some) let tag = builder.ins().load( @@ -3027,7 +3262,7 @@ fn compile_expression( // None block: compile and return the default value builder.switch_to_block(none_block); builder.seal_block(none_block); - let default_val = compile_expression(ctx, builder, &or_default_expr.default)?; + let default_val = compile_expression(ctx, builder, or_default_expr.default)?; builder.ins().jump(merge_block, &[default_val]); // Merge block: return the result @@ -3039,7 +3274,7 @@ fn compile_expression( Expression::Cast(cast_expr) => { // Evaluate the expression to cast - let value = compile_expression(ctx, builder, &cast_expr.expr)?; + let value = compile_expression(ctx, builder, cast_expr.expr)?; // Get source and target types let source_type = ctx.annotations.get_type(cast_expr.expr.span()); @@ -3505,11 +3740,10 @@ fn emit_cleanup_all_vars( let vars_to_cleanup: Vec<(String, Variable, HeapType)> = ctx.var_heap_types .iter() .filter_map(|(name, heap_type)| { - if let Some(excl) = exclude_var { - if name == excl { + if let Some(excl) = exclude_var + && name == excl { return None; } - } ctx.variables.get(name).map(|var| (name.clone(), *var, heap_type.clone())) }) .collect(); @@ -4390,7 +4624,20 @@ fn compile_method_call( let receiver_type = ctx.annotations.get_type(receiver.span()); let type_name = match receiver_type { Some(Type::Struct(s)) => Some(ctx.interner.resolve(&s.name).to_string()), - Some(Type::Generic(name, _)) => Some(ctx.interner.resolve(&name).to_string()), + Some(Type::Generic(name, type_args)) => { + let name_str = ctx.interner.resolve(name).to_string(); + // Check if this is a bare type parameter (no type args) + // If so, look up the concrete type from substitutions + if type_args.is_empty() { + if let Some(concrete_type) = ctx.type_substitutions.get(&name_str) { + Some(concrete_type.clone()) + } else { + Some(name_str) + } + } else { + Some(name_str) + } + } _ => None, }; diff --git a/namlc/src/diagnostic.rs b/namlc/src/diagnostic.rs index f985b91..2ddd5db 100644 --- a/namlc/src/diagnostic.rs +++ b/namlc/src/diagnostic.rs @@ -53,7 +53,7 @@ impl NamlDiagnostic { Self { message: format!("parse error at {}:{}", line, col), - src: NamedSource::new(source.name.to_string(), source.source.to_string()), + src: NamedSource::new(&source.name, source.source.to_string()), span: (span.start as usize, (span.end - span.start) as usize).into(), label: err.message.clone(), help_text: None, @@ -67,7 +67,7 @@ impl NamlDiagnostic { Self { message: format!("{} at {}:{}", message, line, col), - src: NamedSource::new(source.name.to_string(), source.source.to_string()), + src: NamedSource::new(&source.name, source.source.to_string()), span: (span.start as usize, (span.end - span.start) as usize).into(), label, help_text: help, diff --git a/namlc/src/parser/combinators.rs b/namlc/src/parser/combinators.rs index dae4007..49e96cc 100644 --- a/namlc/src/parser/combinators.rs +++ b/namlc/src/parser/combinators.rs @@ -51,7 +51,7 @@ pub fn token(kind: TokenKind) -> impl Fn(TokenStream) -> PResult { match input.first() { Some(tok) if tok.kind == kind => { let (rest, _) = input.take_split(1); - Ok((rest, tok.clone())) + Ok((rest, *tok)) } _ => Err(nom::Err::Error(PError { input, @@ -66,7 +66,7 @@ pub fn keyword(kw: Keyword) -> impl Fn(TokenStream) -> PResult { match input.first() { Some(tok) if tok.kind == TokenKind::Keyword(kw) => { let (rest, _) = input.take_split(1); - Ok((rest, tok.clone())) + Ok((rest, *tok)) } _ => Err(nom::Err::Error(PError { input, diff --git a/namlc/src/parser/items.rs b/namlc/src/parser/items.rs index d03e62e..6f48b78 100644 --- a/namlc/src/parser/items.rs +++ b/namlc/src/parser/items.rs @@ -459,8 +459,6 @@ fn parse_interface_methods<'a>(input: TokenStream<'a>) -> PResult<'a, Vec(input: TokenStream<'a>) -> PResult<'a, Vec(input: TokenStream<'a>) -> PResult<'a, Item<'ast>> { while check(TokenKind::Dot)(input) { let (new_input, _) = token(TokenKind::Dot)(input)?; - let new_input = new_input; if check(TokenKind::LBrace)(new_input) || check(TokenKind::Star)(new_input) { input = new_input; diff --git a/namlc/src/parser/types.rs b/namlc/src/parser/types.rs index 2b81d62..b46a115 100644 --- a/namlc/src/parser/types.rs +++ b/namlc/src/parser/types.rs @@ -156,7 +156,7 @@ fn parse_named_or_generic_type(input: TokenStream) -> PResult { } thread_local! { - static PENDING_GT: std::cell::Cell = std::cell::Cell::new(0); + static PENDING_GT: std::cell::Cell = const { std::cell::Cell::new(0) }; } pub fn parse_gt(input: TokenStream) -> PResult<()> { diff --git a/namlc/src/runtime/array.rs b/namlc/src/runtime/array.rs index 12ab81d..6106574 100644 --- a/namlc/src/runtime/array.rs +++ b/namlc/src/runtime/array.rs @@ -22,7 +22,7 @@ pub struct NamlArray { /// Create a new empty array with given initial capacity #[unsafe(no_mangle)] -pub extern "C" fn naml_array_new(capacity: usize) -> *mut NamlArray { +pub unsafe extern "C" fn naml_array_new(capacity: usize) -> *mut NamlArray { unsafe { let layout = Layout::new::(); let ptr = alloc(layout) as *mut NamlArray; @@ -49,7 +49,7 @@ pub extern "C" fn naml_array_new(capacity: usize) -> *mut NamlArray { /// Create an array from existing values #[unsafe(no_mangle)] -pub extern "C" fn naml_array_from(values: *const i64, len: usize) -> *mut NamlArray { +pub unsafe extern "C" fn naml_array_from(values: *const i64, len: usize) -> *mut NamlArray { unsafe { let arr = naml_array_new(len); if len > 0 && !values.is_null() { @@ -62,7 +62,7 @@ pub extern "C" fn naml_array_from(values: *const i64, len: usize) -> *mut NamlAr /// Increment reference count #[unsafe(no_mangle)] -pub extern "C" fn naml_array_incref(arr: *mut NamlArray) { +pub unsafe extern "C" fn naml_array_incref(arr: *mut NamlArray) { if !arr.is_null() { unsafe { (*arr).header.incref(); } } @@ -70,7 +70,7 @@ pub extern "C" fn naml_array_incref(arr: *mut NamlArray) { /// Decrement reference count and free if zero (for arrays of primitives) #[unsafe(no_mangle)] -pub extern "C" fn naml_array_decref(arr: *mut NamlArray) { +pub unsafe extern "C" fn naml_array_decref(arr: *mut NamlArray) { if !arr.is_null() { unsafe { if (*arr).header.decref() { @@ -86,7 +86,7 @@ pub extern "C" fn naml_array_decref(arr: *mut NamlArray) { /// Decrement reference count and free if zero, also decref string elements #[unsafe(no_mangle)] -pub extern "C" fn naml_array_decref_strings(arr: *mut NamlArray) { +pub unsafe extern "C" fn naml_array_decref_strings(arr: *mut NamlArray) { if !arr.is_null() { unsafe { if (*arr).header.decref() { @@ -110,7 +110,7 @@ pub extern "C" fn naml_array_decref_strings(arr: *mut NamlArray) { /// Decrement reference count and free if zero, also decref nested array elements #[unsafe(no_mangle)] -pub extern "C" fn naml_array_decref_arrays(arr: *mut NamlArray) { +pub unsafe extern "C" fn naml_array_decref_arrays(arr: *mut NamlArray) { if !arr.is_null() { unsafe { if (*arr).header.decref() { @@ -134,7 +134,7 @@ pub extern "C" fn naml_array_decref_arrays(arr: *mut NamlArray) { /// Decrement reference count and free if zero, also decref map elements #[unsafe(no_mangle)] -pub extern "C" fn naml_array_decref_maps(arr: *mut NamlArray) { +pub unsafe extern "C" fn naml_array_decref_maps(arr: *mut NamlArray) { if !arr.is_null() { unsafe { if (*arr).header.decref() { @@ -158,7 +158,7 @@ pub extern "C" fn naml_array_decref_maps(arr: *mut NamlArray) { /// Decrement reference count and free if zero, also decref struct elements #[unsafe(no_mangle)] -pub extern "C" fn naml_array_decref_structs(arr: *mut NamlArray) { +pub unsafe extern "C" fn naml_array_decref_structs(arr: *mut NamlArray) { if !arr.is_null() { unsafe { if (*arr).header.decref() { @@ -182,7 +182,7 @@ pub extern "C" fn naml_array_decref_structs(arr: *mut NamlArray) { /// Get array length #[unsafe(no_mangle)] -pub extern "C" fn naml_array_len(arr: *const NamlArray) -> i64 { +pub unsafe extern "C" fn naml_array_len(arr: *const NamlArray) -> i64 { if arr.is_null() { 0 } else { @@ -192,7 +192,7 @@ pub extern "C" fn naml_array_len(arr: *const NamlArray) -> i64 { /// Get element at index (returns 0 if out of bounds) #[unsafe(no_mangle)] -pub extern "C" fn naml_array_get(arr: *const NamlArray, index: i64) -> i64 { +pub unsafe extern "C" fn naml_array_get(arr: *const NamlArray, index: i64) -> i64 { if arr.is_null() { return 0; } @@ -208,7 +208,7 @@ pub extern "C" fn naml_array_get(arr: *const NamlArray, index: i64) -> i64 { /// Set element at index (no-op if out of bounds) #[unsafe(no_mangle)] -pub extern "C" fn naml_array_set(arr: *mut NamlArray, index: i64, value: i64) { +pub unsafe extern "C" fn naml_array_set(arr: *mut NamlArray, index: i64, value: i64) { if arr.is_null() { return; } @@ -223,7 +223,7 @@ pub extern "C" fn naml_array_set(arr: *mut NamlArray, index: i64, value: i64) { /// Push element to end of array #[unsafe(no_mangle)] -pub extern "C" fn naml_array_push(arr: *mut NamlArray, value: i64) { +pub unsafe extern "C" fn naml_array_push(arr: *mut NamlArray, value: i64) { if arr.is_null() { return; } @@ -251,7 +251,7 @@ pub extern "C" fn naml_array_push(arr: *mut NamlArray, value: i64) { /// Pop element from end of array (returns 0 if empty) #[unsafe(no_mangle)] -pub extern "C" fn naml_array_pop(arr: *mut NamlArray) -> i64 { +pub unsafe extern "C" fn naml_array_pop(arr: *mut NamlArray) -> i64 { if arr.is_null() { return 0; } @@ -268,7 +268,7 @@ pub extern "C" fn naml_array_pop(arr: *mut NamlArray) -> i64 { /// Check if array contains a value #[unsafe(no_mangle)] -pub extern "C" fn naml_array_contains(arr: *const NamlArray, value: i64) -> i64 { +pub unsafe extern "C" fn naml_array_contains(arr: *const NamlArray, value: i64) -> i64 { if arr.is_null() { return 0; } @@ -285,9 +285,9 @@ pub extern "C" fn naml_array_contains(arr: *const NamlArray, value: i64) -> i64 /// Create a copy of the array #[unsafe(no_mangle)] -pub extern "C" fn naml_array_clone(arr: *const NamlArray) -> *mut NamlArray { +pub unsafe extern "C" fn naml_array_clone(arr: *const NamlArray) -> *mut NamlArray { if arr.is_null() { - return naml_array_new(0); + return unsafe { naml_array_new(0) }; } unsafe { @@ -297,7 +297,7 @@ pub extern "C" fn naml_array_clone(arr: *const NamlArray) -> *mut NamlArray { /// Print array contents (for debugging) #[unsafe(no_mangle)] -pub extern "C" fn naml_array_print(arr: *const NamlArray) { +pub unsafe extern "C" fn naml_array_print(arr: *const NamlArray) { if arr.is_null() { print!("[]"); return; @@ -321,9 +321,9 @@ mod tests { #[test] fn test_array_creation() { - let arr = naml_array_new(10); - assert!(!arr.is_null()); unsafe { + let arr = naml_array_new(10); + assert!(!arr.is_null()); assert_eq!((*arr).len, 0); assert_eq!((*arr).capacity, 10); naml_array_decref(arr); @@ -332,29 +332,33 @@ mod tests { #[test] fn test_array_push_get() { - let arr = naml_array_new(2); - naml_array_push(arr, 10); - naml_array_push(arr, 20); - naml_array_push(arr, 30); // Triggers growth + unsafe { + let arr = naml_array_new(2); + naml_array_push(arr, 10); + naml_array_push(arr, 20); + naml_array_push(arr, 30); // Triggers growth - assert_eq!(naml_array_len(arr), 3); - assert_eq!(naml_array_get(arr, 0), 10); - assert_eq!(naml_array_get(arr, 1), 20); - assert_eq!(naml_array_get(arr, 2), 30); + assert_eq!(naml_array_len(arr), 3); + assert_eq!(naml_array_get(arr, 0), 10); + assert_eq!(naml_array_get(arr, 1), 20); + assert_eq!(naml_array_get(arr, 2), 30); - naml_array_decref(arr); + naml_array_decref(arr); + } } #[test] fn test_array_from() { - let values = [1i64, 2, 3, 4, 5]; - let arr = naml_array_from(values.as_ptr(), values.len()); + unsafe { + let values = [1i64, 2, 3, 4, 5]; + let arr = naml_array_from(values.as_ptr(), values.len()); - assert_eq!(naml_array_len(arr), 5); - for i in 0..5 { - assert_eq!(naml_array_get(arr, i as i64), (i + 1) as i64); - } + assert_eq!(naml_array_len(arr), 5); + for i in 0..5 { + assert_eq!(naml_array_get(arr, i as i64), (i + 1) as i64); + } - naml_array_decref(arr); + naml_array_decref(arr); + } } } diff --git a/namlc/src/runtime/bytes.rs b/namlc/src/runtime/bytes.rs index 5e23be9..04b6eb1 100644 --- a/namlc/src/runtime/bytes.rs +++ b/namlc/src/runtime/bytes.rs @@ -4,7 +4,6 @@ /// Provides heap-allocated byte arrays with reference counting. /// Similar to strings but for raw binary data. /// - use std::alloc::{alloc, dealloc, Layout}; use super::value::{HeapHeader, HeapTag, NamlString, naml_string_new}; @@ -19,7 +18,7 @@ pub struct NamlBytes { /// Allocate new bytes with given capacity #[unsafe(no_mangle)] -pub extern "C" fn naml_bytes_new(capacity: usize) -> *mut NamlBytes { +pub unsafe extern "C" fn naml_bytes_new(capacity: usize) -> *mut NamlBytes { unsafe { let cap = if capacity == 0 { 8 } else { capacity }; let layout = Layout::from_size_align( @@ -42,7 +41,7 @@ pub extern "C" fn naml_bytes_new(capacity: usize) -> *mut NamlBytes { /// Create bytes from raw data #[unsafe(no_mangle)] -pub extern "C" fn naml_bytes_from(data: *const u8, len: usize) -> *mut NamlBytes { +pub unsafe extern "C" fn naml_bytes_from(data: *const u8, len: usize) -> *mut NamlBytes { unsafe { let cap = if len == 0 { 8 } else { len }; let layout = Layout::from_size_align( @@ -69,7 +68,7 @@ pub extern "C" fn naml_bytes_from(data: *const u8, len: usize) -> *mut NamlBytes /// Get bytes length #[unsafe(no_mangle)] -pub extern "C" fn naml_bytes_len(b: *const NamlBytes) -> i64 { +pub unsafe extern "C" fn naml_bytes_len(b: *const NamlBytes) -> i64 { if b.is_null() { 0 } else { @@ -79,7 +78,7 @@ pub extern "C" fn naml_bytes_len(b: *const NamlBytes) -> i64 { /// Get byte at index #[unsafe(no_mangle)] -pub extern "C" fn naml_bytes_get(b: *const NamlBytes, index: i64) -> i64 { +pub unsafe extern "C" fn naml_bytes_get(b: *const NamlBytes, index: i64) -> i64 { if b.is_null() { return 0; } @@ -93,7 +92,7 @@ pub extern "C" fn naml_bytes_get(b: *const NamlBytes, index: i64) -> i64 { /// Set byte at index #[unsafe(no_mangle)] -pub extern "C" fn naml_bytes_set(b: *mut NamlBytes, index: i64, value: i64) { +pub unsafe extern "C" fn naml_bytes_set(b: *mut NamlBytes, index: i64, value: i64) { if b.is_null() { return; } @@ -106,7 +105,7 @@ pub extern "C" fn naml_bytes_set(b: *mut NamlBytes, index: i64, value: i64) { /// Increment reference count #[unsafe(no_mangle)] -pub extern "C" fn naml_bytes_incref(b: *mut NamlBytes) { +pub unsafe extern "C" fn naml_bytes_incref(b: *mut NamlBytes) { if !b.is_null() { unsafe { (*b).header.incref(); } } @@ -114,7 +113,7 @@ pub extern "C" fn naml_bytes_incref(b: *mut NamlBytes) { /// Decrement reference count and free if zero #[unsafe(no_mangle)] -pub extern "C" fn naml_bytes_decref(b: *mut NamlBytes) { +pub unsafe extern "C" fn naml_bytes_decref(b: *mut NamlBytes) { if !b.is_null() { unsafe { if (*b).header.decref() { @@ -131,9 +130,9 @@ pub extern "C" fn naml_bytes_decref(b: *mut NamlBytes) { /// Convert bytes to string (UTF-8) #[unsafe(no_mangle)] -pub extern "C" fn naml_bytes_to_string(b: *const NamlBytes) -> *mut NamlString { +pub unsafe extern "C" fn naml_bytes_to_string(b: *const NamlBytes) -> *mut NamlString { if b.is_null() { - return naml_string_new(std::ptr::null(), 0); + return unsafe { naml_string_new(std::ptr::null(), 0) }; } unsafe { let slice = std::slice::from_raw_parts((*b).data.as_ptr(), (*b).len); @@ -143,9 +142,9 @@ pub extern "C" fn naml_bytes_to_string(b: *const NamlBytes) -> *mut NamlString { /// Convert string to bytes #[unsafe(no_mangle)] -pub extern "C" fn naml_string_to_bytes(s: *const NamlString) -> *mut NamlBytes { +pub unsafe extern "C" fn naml_string_to_bytes(s: *const NamlString) -> *mut NamlBytes { if s.is_null() { - return naml_bytes_new(0); + return unsafe { naml_bytes_new(0) }; } unsafe { let len = (*s).len; @@ -159,9 +158,9 @@ mod tests { #[test] fn test_bytes_creation() { - let b = naml_bytes_new(16); - assert!(!b.is_null()); unsafe { + let b = naml_bytes_new(16); + assert!(!b.is_null()); assert_eq!((*b).len, 0); assert_eq!((*b).capacity, 16); naml_bytes_decref(b); @@ -170,9 +169,9 @@ mod tests { #[test] fn test_bytes_from() { - let data = b"hello"; - let b = naml_bytes_from(data.as_ptr(), data.len()); unsafe { + let data = b"hello"; + let b = naml_bytes_from(data.as_ptr(), data.len()); assert_eq!((*b).len, 5); assert_eq!(naml_bytes_get(b, 0), 'h' as i64); assert_eq!(naml_bytes_get(b, 4), 'o' as i64); diff --git a/namlc/src/runtime/channel.rs b/namlc/src/runtime/channel.rs index f232a84..5e0ba2f 100644 --- a/namlc/src/runtime/channel.rs +++ b/namlc/src/runtime/channel.rs @@ -29,7 +29,7 @@ struct ChannelInner { /// Create a new channel with the given capacity #[unsafe(no_mangle)] -pub extern "C" fn naml_channel_new(capacity: usize) -> *mut NamlChannel { +pub unsafe extern "C" fn naml_channel_new(capacity: usize) -> *mut NamlChannel { let cap = if capacity == 0 { 1 } else { capacity }; unsafe { @@ -56,7 +56,7 @@ pub extern "C" fn naml_channel_new(capacity: usize) -> *mut NamlChannel { /// Increment reference count #[unsafe(no_mangle)] -pub extern "C" fn naml_channel_incref(ch: *mut NamlChannel) { +pub unsafe extern "C" fn naml_channel_incref(ch: *mut NamlChannel) { if !ch.is_null() { unsafe { (*ch).header.incref(); } } @@ -64,7 +64,7 @@ pub extern "C" fn naml_channel_incref(ch: *mut NamlChannel) { /// Decrement reference count and free if zero #[unsafe(no_mangle)] -pub extern "C" fn naml_channel_decref(ch: *mut NamlChannel) { +pub unsafe extern "C" fn naml_channel_decref(ch: *mut NamlChannel) { if !ch.is_null() { unsafe { if (*ch).header.decref() { @@ -79,7 +79,7 @@ pub extern "C" fn naml_channel_decref(ch: *mut NamlChannel) { /// Send a value to the channel (blocks if full) /// Returns 1 on success, 0 if channel is closed #[unsafe(no_mangle)] -pub extern "C" fn naml_channel_send(ch: *mut NamlChannel, value: i64) -> i64 { +pub unsafe extern "C" fn naml_channel_send(ch: *mut NamlChannel, value: i64) -> i64 { if ch.is_null() { return 0; } @@ -106,7 +106,7 @@ pub extern "C" fn naml_channel_send(ch: *mut NamlChannel, value: i64) -> i64 { /// Receive a value from the channel (blocks if empty) /// Returns the value, or 0 if channel is closed and empty #[unsafe(no_mangle)] -pub extern "C" fn naml_channel_receive(ch: *mut NamlChannel) -> i64 { +pub unsafe extern "C" fn naml_channel_receive(ch: *mut NamlChannel) -> i64 { if ch.is_null() { return 0; } @@ -132,7 +132,7 @@ pub extern "C" fn naml_channel_receive(ch: *mut NamlChannel) -> i64 { /// Try to send without blocking /// Returns 1 on success, 0 if would block or closed #[unsafe(no_mangle)] -pub extern "C" fn naml_channel_try_send(ch: *mut NamlChannel, value: i64) -> i64 { +pub unsafe extern "C" fn naml_channel_try_send(ch: *mut NamlChannel, value: i64) -> i64 { if ch.is_null() { return 0; } @@ -155,7 +155,7 @@ pub extern "C" fn naml_channel_try_send(ch: *mut NamlChannel, value: i64) -> i64 /// Returns the value in the high bits and success (1) or failure (0) in low bit /// Use naml_channel_try_receive_value() and naml_channel_try_receive_ok() to extract #[unsafe(no_mangle)] -pub extern "C" fn naml_channel_try_receive(ch: *mut NamlChannel) -> i64 { +pub unsafe extern "C" fn naml_channel_try_receive(ch: *mut NamlChannel) -> i64 { if ch.is_null() { return 0; } @@ -177,7 +177,7 @@ pub extern "C" fn naml_channel_try_receive(ch: *mut NamlChannel) -> i64 { /// Close the channel #[unsafe(no_mangle)] -pub extern "C" fn naml_channel_close(ch: *mut NamlChannel) { +pub unsafe extern "C" fn naml_channel_close(ch: *mut NamlChannel) { if ch.is_null() { return; } @@ -193,7 +193,7 @@ pub extern "C" fn naml_channel_close(ch: *mut NamlChannel) { /// Check if channel is closed #[unsafe(no_mangle)] -pub extern "C" fn naml_channel_is_closed(ch: *mut NamlChannel) -> i64 { +pub unsafe extern "C" fn naml_channel_is_closed(ch: *mut NamlChannel) -> i64 { if ch.is_null() { return 1; } @@ -207,7 +207,7 @@ pub extern "C" fn naml_channel_is_closed(ch: *mut NamlChannel) -> i64 { /// Get number of items in channel buffer #[unsafe(no_mangle)] -pub extern "C" fn naml_channel_len(ch: *mut NamlChannel) -> i64 { +pub unsafe extern "C" fn naml_channel_len(ch: *mut NamlChannel) -> i64 { if ch.is_null() { return 0; } @@ -226,23 +226,25 @@ mod tests { #[test] fn test_channel_basic() { - let ch = naml_channel_new(2); - assert!(!ch.is_null()); + unsafe { + let ch = naml_channel_new(2); + assert!(!ch.is_null()); - assert_eq!(naml_channel_send(ch, 42), 1); - assert_eq!(naml_channel_send(ch, 43), 1); - assert_eq!(naml_channel_receive(ch), 42); - assert_eq!(naml_channel_receive(ch), 43); + assert_eq!(naml_channel_send(ch, 42), 1); + assert_eq!(naml_channel_send(ch, 43), 1); + assert_eq!(naml_channel_receive(ch), 42); + assert_eq!(naml_channel_receive(ch), 43); - naml_channel_decref(ch); + naml_channel_decref(ch); + } } #[test] fn test_channel_concurrent() { - let ch = naml_channel_new(10); + let ch = unsafe { naml_channel_new(10) }; let ch_send = ch as usize; - let sender = thread::spawn(move || { + let sender = thread::spawn(move || unsafe { let ch = ch_send as *mut NamlChannel; for i in 0..5 { naml_channel_send(ch, i); @@ -250,7 +252,7 @@ mod tests { }); let ch_recv = ch as usize; - let receiver = thread::spawn(move || { + let receiver = thread::spawn(move || unsafe { let ch = ch_recv as *mut NamlChannel; let mut sum = 0i64; for _ in 0..5 { @@ -263,6 +265,6 @@ mod tests { let sum = receiver.join().unwrap(); assert_eq!(sum, 0 + 1 + 2 + 3 + 4); - naml_channel_decref(ch); + unsafe { naml_channel_decref(ch); } } } diff --git a/namlc/src/runtime/map.rs b/namlc/src/runtime/map.rs index 6e5dad4..3148e55 100644 --- a/namlc/src/runtime/map.rs +++ b/namlc/src/runtime/map.rs @@ -21,17 +21,13 @@ pub struct NamlMap { #[repr(C)] #[derive(Clone, Copy)] +#[derive(Default)] pub struct MapEntry { pub key: i64, // Pointer to NamlString or 0 if empty pub value: i64, // The stored value pub occupied: bool, } -impl Default for MapEntry { - fn default() -> Self { - Self { key: 0, value: 0, occupied: false } - } -} fn hash_string(s: *const NamlString) -> u64 { if s.is_null() { return 0; } @@ -60,7 +56,7 @@ fn string_eq(a: *const NamlString, b: *const NamlString) -> bool { } #[unsafe(no_mangle)] -pub extern "C" fn naml_map_new(capacity: usize) -> *mut NamlMap { +pub unsafe extern "C" fn naml_map_new(capacity: usize) -> *mut NamlMap { let cap = if capacity < INITIAL_CAPACITY { INITIAL_CAPACITY } else { capacity }; unsafe { let map_layout = Layout::new::(); @@ -81,7 +77,7 @@ pub extern "C" fn naml_map_new(capacity: usize) -> *mut NamlMap { /// Set a primitive value in the map (no refcount management for values) #[unsafe(no_mangle)] -pub extern "C" fn naml_map_set(map: *mut NamlMap, key: i64, value: i64) { +pub unsafe extern "C" fn naml_map_set(map: *mut NamlMap, key: i64, value: i64) { if map.is_null() { return; } unsafe { if ((*map).length + 1) as f64 / (*map).capacity as f64 > LOAD_FACTOR { @@ -110,7 +106,7 @@ pub extern "C" fn naml_map_set(map: *mut NamlMap, key: i64, value: i64) { /// Set a string value in the map (decrefs old string value when updating) #[unsafe(no_mangle)] -pub extern "C" fn naml_map_set_string(map: *mut NamlMap, key: i64, value: i64) { +pub unsafe extern "C" fn naml_map_set_string(map: *mut NamlMap, key: i64, value: i64) { if map.is_null() { return; } unsafe { if ((*map).length + 1) as f64 / (*map).capacity as f64 > LOAD_FACTOR { @@ -143,7 +139,7 @@ pub extern "C" fn naml_map_set_string(map: *mut NamlMap, key: i64, value: i64) { /// Set an array value in the map (decrefs old array value when updating) #[unsafe(no_mangle)] -pub extern "C" fn naml_map_set_array(map: *mut NamlMap, key: i64, value: i64) { +pub unsafe extern "C" fn naml_map_set_array(map: *mut NamlMap, key: i64, value: i64) { if map.is_null() { return; } unsafe { if ((*map).length + 1) as f64 / (*map).capacity as f64 > LOAD_FACTOR { @@ -176,7 +172,7 @@ pub extern "C" fn naml_map_set_array(map: *mut NamlMap, key: i64, value: i64) { /// Set a map value in the map (decrefs old map value when updating) #[unsafe(no_mangle)] -pub extern "C" fn naml_map_set_map(map: *mut NamlMap, key: i64, value: i64) { +pub unsafe extern "C" fn naml_map_set_map(map: *mut NamlMap, key: i64, value: i64) { if map.is_null() { return; } unsafe { if ((*map).length + 1) as f64 / (*map).capacity as f64 > LOAD_FACTOR { @@ -209,7 +205,7 @@ pub extern "C" fn naml_map_set_map(map: *mut NamlMap, key: i64, value: i64) { /// Set a struct value in the map (decrefs old struct value when updating) #[unsafe(no_mangle)] -pub extern "C" fn naml_map_set_struct(map: *mut NamlMap, key: i64, value: i64) { +pub unsafe extern "C" fn naml_map_set_struct(map: *mut NamlMap, key: i64, value: i64) { if map.is_null() { return; } unsafe { if ((*map).length + 1) as f64 / (*map).capacity as f64 > LOAD_FACTOR { @@ -241,7 +237,7 @@ pub extern "C" fn naml_map_set_struct(map: *mut NamlMap, key: i64, value: i64) { } #[unsafe(no_mangle)] -pub extern "C" fn naml_map_get(map: *const NamlMap, key: i64) -> i64 { +pub unsafe extern "C" fn naml_map_get(map: *const NamlMap, key: i64) -> i64 { if map.is_null() { return 0; } unsafe { let hash = hash_string(key as *const NamlString); @@ -261,7 +257,7 @@ pub extern "C" fn naml_map_get(map: *const NamlMap, key: i64) -> i64 { } #[unsafe(no_mangle)] -pub extern "C" fn naml_map_contains(map: *const NamlMap, key: i64) -> i64 { +pub unsafe extern "C" fn naml_map_contains(map: *const NamlMap, key: i64) -> i64 { if map.is_null() { return 0; } unsafe { let hash = hash_string(key as *const NamlString); @@ -281,17 +277,17 @@ pub extern "C" fn naml_map_contains(map: *const NamlMap, key: i64) -> i64 { } #[unsafe(no_mangle)] -pub extern "C" fn naml_map_len(map: *const NamlMap) -> i64 { +pub unsafe extern "C" fn naml_map_len(map: *const NamlMap) -> i64 { if map.is_null() { 0 } else { unsafe { (*map).length as i64 } } } #[unsafe(no_mangle)] -pub extern "C" fn naml_map_incref(map: *mut NamlMap) { +pub unsafe extern "C" fn naml_map_incref(map: *mut NamlMap) { if !map.is_null() { unsafe { (*map).header.incref(); } } } #[unsafe(no_mangle)] -pub extern "C" fn naml_map_decref(map: *mut NamlMap) { +pub unsafe extern "C" fn naml_map_decref(map: *mut NamlMap) { if map.is_null() { return; } unsafe { if (*map).header.decref() { @@ -311,7 +307,7 @@ pub extern "C" fn naml_map_decref(map: *mut NamlMap) { /// Decrement map reference count and also decref string values #[unsafe(no_mangle)] -pub extern "C" fn naml_map_decref_strings(map: *mut NamlMap) { +pub unsafe extern "C" fn naml_map_decref_strings(map: *mut NamlMap) { if map.is_null() { return; } unsafe { if (*map).header.decref() { @@ -336,7 +332,7 @@ pub extern "C" fn naml_map_decref_strings(map: *mut NamlMap) { /// Decrement map reference count and also decref array values #[unsafe(no_mangle)] -pub extern "C" fn naml_map_decref_arrays(map: *mut NamlMap) { +pub unsafe extern "C" fn naml_map_decref_arrays(map: *mut NamlMap) { if map.is_null() { return; } unsafe { if (*map).header.decref() { @@ -361,7 +357,7 @@ pub extern "C" fn naml_map_decref_arrays(map: *mut NamlMap) { /// Decrement map reference count and also decref nested map values #[unsafe(no_mangle)] -pub extern "C" fn naml_map_decref_maps(map: *mut NamlMap) { +pub unsafe extern "C" fn naml_map_decref_maps(map: *mut NamlMap) { if map.is_null() { return; } unsafe { if (*map).header.decref() { @@ -386,7 +382,7 @@ pub extern "C" fn naml_map_decref_maps(map: *mut NamlMap) { /// Decrement map reference count and also decref struct values #[unsafe(no_mangle)] -pub extern "C" fn naml_map_decref_structs(map: *mut NamlMap) { +pub unsafe extern "C" fn naml_map_decref_structs(map: *mut NamlMap) { if map.is_null() { return; } unsafe { if (*map).header.decref() { diff --git a/namlc/src/runtime/value.rs b/namlc/src/runtime/value.rs index 9f518c6..43fbc69 100644 --- a/namlc/src/runtime/value.rs +++ b/namlc/src/runtime/value.rs @@ -91,7 +91,7 @@ pub struct NamlStruct { /// Allocate a new string on the heap #[unsafe(no_mangle)] -pub extern "C" fn naml_string_new(data: *const u8, len: usize) -> *mut NamlString { +pub unsafe extern "C" fn naml_string_new(data: *const u8, len: usize) -> *mut NamlString { unsafe { let layout = Layout::from_size_align( std::mem::size_of::() + len, @@ -116,7 +116,7 @@ pub extern "C" fn naml_string_new(data: *const u8, len: usize) -> *mut NamlStrin /// Increment reference count of a string #[unsafe(no_mangle)] -pub extern "C" fn naml_string_incref(s: *mut NamlString) { +pub unsafe extern "C" fn naml_string_incref(s: *mut NamlString) { if !s.is_null() { unsafe { (*s).header.incref(); } } @@ -124,7 +124,7 @@ pub extern "C" fn naml_string_incref(s: *mut NamlString) { /// Decrement reference count and free if zero #[unsafe(no_mangle)] -pub extern "C" fn naml_string_decref(s: *mut NamlString) { +pub unsafe extern "C" fn naml_string_decref(s: *mut NamlString) { if !s.is_null() { unsafe { if (*s).header.decref() { @@ -141,7 +141,7 @@ pub extern "C" fn naml_string_decref(s: *mut NamlString) { /// Get string length #[unsafe(no_mangle)] -pub extern "C" fn naml_string_len(s: *const NamlString) -> i64 { +pub unsafe extern "C" fn naml_string_len(s: *const NamlString) -> i64 { if s.is_null() { 0 } else { @@ -151,7 +151,7 @@ pub extern "C" fn naml_string_len(s: *const NamlString) -> i64 { /// Get pointer to string data (for printing) #[unsafe(no_mangle)] -pub extern "C" fn naml_string_data(s: *const NamlString) -> *const u8 { +pub unsafe extern "C" fn naml_string_data(s: *const NamlString) -> *const u8 { if s.is_null() { std::ptr::null() } else { @@ -161,7 +161,7 @@ pub extern "C" fn naml_string_data(s: *const NamlString) -> *const u8 { /// Concatenate two strings #[unsafe(no_mangle)] -pub extern "C" fn naml_string_concat(a: *const NamlString, b: *const NamlString) -> *mut NamlString { +pub unsafe extern "C" fn naml_string_concat(a: *const NamlString, b: *const NamlString) -> *mut NamlString { unsafe { let a_len = if a.is_null() { 0 } else { (*a).len }; let b_len = if b.is_null() { 0 } else { (*b).len }; @@ -182,7 +182,7 @@ pub extern "C" fn naml_string_concat(a: *const NamlString, b: *const NamlString) /// Compare two strings for equality #[unsafe(no_mangle)] -pub extern "C" fn naml_string_eq(a: *const NamlString, b: *const NamlString) -> i64 { +pub unsafe extern "C" fn naml_string_eq(a: *const NamlString, b: *const NamlString) -> i64 { unsafe { if a.is_null() && b.is_null() { return 1; @@ -203,7 +203,7 @@ pub extern "C" fn naml_string_eq(a: *const NamlString, b: *const NamlString) -> /// Print a NamlString (for debugging) #[unsafe(no_mangle)] -pub extern "C" fn naml_string_print(s: *const NamlString) { +pub unsafe extern "C" fn naml_string_print(s: *const NamlString) { if !s.is_null() { unsafe { let slice = std::slice::from_raw_parts((*s).data.as_ptr(), (*s).len); @@ -216,9 +216,9 @@ pub extern "C" fn naml_string_print(s: *const NamlString) { /// Create a NamlString from a null-terminated C string pointer #[unsafe(no_mangle)] -pub extern "C" fn naml_string_from_cstr(cstr: *const i8) -> *mut NamlString { +pub unsafe extern "C" fn naml_string_from_cstr(cstr: *const i8) -> *mut NamlString { if cstr.is_null() { - return naml_string_new(std::ptr::null(), 0); + return unsafe { naml_string_new(std::ptr::null(), 0) }; } unsafe { let c_str = std::ffi::CStr::from_ptr(cstr); @@ -231,19 +231,19 @@ pub extern "C" fn naml_string_from_cstr(cstr: *const i8) -> *mut NamlString { #[unsafe(no_mangle)] pub extern "C" fn naml_int_to_string(n: i64) -> *mut NamlString { let s = n.to_string(); - naml_string_new(s.as_ptr(), s.len()) + unsafe { naml_string_new(s.as_ptr(), s.len()) } } /// Convert a float to a string #[unsafe(no_mangle)] pub extern "C" fn naml_float_to_string(f: f64) -> *mut NamlString { let s = f.to_string(); - naml_string_new(s.as_ptr(), s.len()) + unsafe { naml_string_new(s.as_ptr(), s.len()) } } /// Convert a string to an integer (returns 0 on parse failure) #[unsafe(no_mangle)] -pub extern "C" fn naml_string_to_int(s: *const NamlString) -> i64 { +pub unsafe extern "C" fn naml_string_to_int(s: *const NamlString) -> i64 { if s.is_null() { return 0; } @@ -255,7 +255,7 @@ pub extern "C" fn naml_string_to_int(s: *const NamlString) -> i64 { /// Convert a string to a float (returns 0.0 on parse failure) #[unsafe(no_mangle)] -pub extern "C" fn naml_string_to_float(s: *const NamlString) -> f64 { +pub unsafe extern "C" fn naml_string_to_float(s: *const NamlString) -> f64 { if s.is_null() { return 0.0; } @@ -267,7 +267,7 @@ pub extern "C" fn naml_string_to_float(s: *const NamlString) -> f64 { /// Get character (as UTF-8 codepoint) at index #[unsafe(no_mangle)] -pub extern "C" fn naml_string_char_at(s: *const NamlString, index: i64) -> i64 { +pub unsafe extern "C" fn naml_string_char_at(s: *const NamlString, index: i64) -> i64 { if s.is_null() { return 0; } @@ -283,7 +283,7 @@ pub extern "C" fn naml_string_char_at(s: *const NamlString, index: i64) -> i64 { /// Get string length in characters (not bytes) #[unsafe(no_mangle)] -pub extern "C" fn naml_string_char_len(s: *const NamlString) -> i64 { +pub unsafe extern "C" fn naml_string_char_len(s: *const NamlString) -> i64 { if s.is_null() { return 0; } @@ -294,7 +294,7 @@ pub extern "C" fn naml_string_char_len(s: *const NamlString) -> i64 { /// Allocate a new struct on the heap #[unsafe(no_mangle)] -pub extern "C" fn naml_struct_new(type_id: u32, field_count: u32) -> *mut NamlStruct { +pub unsafe extern "C" fn naml_struct_new(type_id: u32, field_count: u32) -> *mut NamlStruct { unsafe { let layout = Layout::from_size_align( std::mem::size_of::() + (field_count as usize) * std::mem::size_of::(), @@ -322,7 +322,7 @@ pub extern "C" fn naml_struct_new(type_id: u32, field_count: u32) -> *mut NamlSt /// Increment reference count of a struct #[unsafe(no_mangle)] -pub extern "C" fn naml_struct_incref(s: *mut NamlStruct) { +pub unsafe extern "C" fn naml_struct_incref(s: *mut NamlStruct) { if !s.is_null() { unsafe { (*s).header.incref(); } } @@ -330,7 +330,7 @@ pub extern "C" fn naml_struct_incref(s: *mut NamlStruct) { /// Decrement reference count and free if zero (for structs with no heap fields) #[unsafe(no_mangle)] -pub extern "C" fn naml_struct_decref(s: *mut NamlStruct) { +pub unsafe extern "C" fn naml_struct_decref(s: *mut NamlStruct) { if !s.is_null() { unsafe { if (*s).header.decref() { @@ -348,7 +348,7 @@ pub extern "C" fn naml_struct_decref(s: *mut NamlStruct) { /// Free struct memory without refcount check (called by generated decref functions) /// Generated decref functions handle field cleanup before calling this. #[unsafe(no_mangle)] -pub extern "C" fn naml_struct_free(s: *mut NamlStruct) { +pub unsafe extern "C" fn naml_struct_free(s: *mut NamlStruct) { if !s.is_null() { unsafe { let field_count = (*s).field_count; @@ -363,7 +363,7 @@ pub extern "C" fn naml_struct_free(s: *mut NamlStruct) { /// Get field value by index #[unsafe(no_mangle)] -pub extern "C" fn naml_struct_get_field(s: *const NamlStruct, field_index: u32) -> i64 { +pub unsafe extern "C" fn naml_struct_get_field(s: *const NamlStruct, field_index: u32) -> i64 { if s.is_null() { return 0; } @@ -378,7 +378,7 @@ pub extern "C" fn naml_struct_get_field(s: *const NamlStruct, field_index: u32) /// Set field value by index #[unsafe(no_mangle)] -pub extern "C" fn naml_struct_set_field(s: *mut NamlStruct, field_index: u32, value: i64) { +pub unsafe extern "C" fn naml_struct_set_field(s: *mut NamlStruct, field_index: u32, value: i64) { if s.is_null() { return; } @@ -396,10 +396,10 @@ mod tests { #[test] fn test_string_creation() { - let data = b"hello"; - let s = naml_string_new(data.as_ptr(), data.len()); - assert!(!s.is_null()); unsafe { + let data = b"hello"; + let s = naml_string_new(data.as_ptr(), data.len()); + assert!(!s.is_null()); assert_eq!((*s).len, 5); assert_eq!((*s).header.refcount(), 1); naml_string_decref(s); @@ -408,11 +408,11 @@ mod tests { #[test] fn test_string_concat() { - let a = naml_string_new(b"hello ".as_ptr(), 6); - let b = naml_string_new(b"world".as_ptr(), 5); - let c = naml_string_concat(a, b); - unsafe { + let a = naml_string_new(b"hello ".as_ptr(), 6); + let b = naml_string_new(b"world ".as_ptr(), 5); + let c = naml_string_concat(a, b); + assert_eq!((*c).len, 11); assert_eq!((*c).as_str(), "hello world"); diff --git a/namlc/src/typechecker/env.rs b/namlc/src/typechecker/env.rs index ed40c5d..e6d1a07 100644 --- a/namlc/src/typechecker/env.rs +++ b/namlc/src/typechecker/env.rs @@ -146,7 +146,7 @@ impl TypeEnv { pub fn is_defined_in_current_scope(&self, name: Spur) -> bool { self.scopes .last() - .map_or(false, |scope| scope.get(name).is_some()) + .is_some_and(|scope| scope.get(name).is_some()) } pub fn enter_loop(&mut self) { @@ -201,11 +201,10 @@ impl TypeEnv { } pub fn bind_type_param(&mut self, name: Spur, concrete: Type) { - if let Some(func) = self.function_stack.last_mut() { - if let Some(binding) = func.type_params.get_mut(&name) { + if let Some(func) = self.function_stack.last_mut() + && let Some(binding) = func.type_params.get_mut(&name) { binding.concrete = Some(concrete); } - } } pub fn get_type_param_binding(&self, name: Spur) -> Option<&Type> { diff --git a/namlc/src/typechecker/infer.rs b/namlc/src/typechecker/infer.rs index ed6bb67..9a28bfb 100644 --- a/namlc/src/typechecker/infer.rs +++ b/namlc/src/typechecker/infer.rs @@ -61,7 +61,7 @@ impl<'a> TypeInferrer<'a> { Expression::OrDefault(or_default) => self.infer_or_default(or_default), Expression::Cast(cast) => self.infer_cast(cast), Expression::Range(range) => self.infer_range(range), - Expression::Grouped(grouped) => self.infer_expr(&grouped.inner), + Expression::Grouped(grouped) => self.infer_expr(grouped.inner), Expression::Some(some) => self.infer_some(some), }; @@ -108,14 +108,57 @@ impl<'a> TypeInferrer<'a> { let name = self.interner.resolve(&ident.ident.symbol); name == "self" } - Expression::Field(field) => self.involves_self(&field.base), - Expression::Index(idx) => self.involves_self(&idx.base), + Expression::Field(field) => self.involves_self(field.base), + Expression::Index(idx) => self.involves_self(idx.base), _ => false, } } + fn mangle_generic_function(&self, func_name: &str, type_args: &[Type]) -> String { + let mut mangled = func_name.to_string(); + for ty in type_args { + mangled.push('_'); + mangled.push_str(&self.mangle_type(ty)); + } + mangled + } + + fn mangle_type(&self, ty: &Type) -> String { + match ty { + Type::Int => "int".to_string(), + Type::Uint => "uint".to_string(), + Type::Float => "float".to_string(), + Type::Bool => "bool".to_string(), + Type::String => "string".to_string(), + Type::Bytes => "bytes".to_string(), + Type::Unit => "unit".to_string(), + Type::Array(inner) => format!("Array_{}", self.mangle_type(inner)), + Type::FixedArray(inner, size) => format!("FixedArray_{}_{}", self.mangle_type(inner), size), + Type::Option(inner) => format!("Option_{}", self.mangle_type(inner)), + Type::Map(k, v) => format!("Map_{}_{}", self.mangle_type(k), self.mangle_type(v)), + Type::Channel(inner) => format!("Channel_{}", self.mangle_type(inner)), + Type::Promise(inner) => format!("Promise_{}", self.mangle_type(inner)), + Type::Struct(s) => self.interner.resolve(&s.name).to_string(), + Type::Enum(e) => self.interner.resolve(&e.name).to_string(), + Type::Interface(i) => self.interner.resolve(&i.name).to_string(), + Type::Exception(name) => self.interner.resolve(name).to_string(), + Type::Function(_) => "fn".to_string(), + Type::TypeVar(tv) => format!("T{}", tv.id), + Type::Generic(name, args) => { + let mut s = self.interner.resolve(name).to_string(); + for arg in args { + s.push('_'); + s.push_str(&self.mangle_type(arg)); + } + s + } + Type::Error => "error".to_string(), + Type::Never => "never".to_string(), + } + } + fn infer_some(&mut self, some: &ast::SomeExpr) -> Type { - let inner_ty = self.infer_expr(&some.value); + let inner_ty = self.infer_expr(some.value); Type::Option(Box::new(inner_ty)) } @@ -220,12 +263,11 @@ impl<'a> TypeInferrer<'a> { // Handle `is` operator specially - RHS is a type name, not an expression if bin.op == Is { - let _left_ty = self.infer_expr(&bin.left); - if let ast::Expression::Identifier(ident) = &bin.right { - if self.symbols.get_type(ident.ident.symbol).is_some() { + let _left_ty = self.infer_expr(bin.left); + if let ast::Expression::Identifier(ident) = &bin.right + && self.symbols.get_type(ident.ident.symbol).is_some() { return Type::Bool; } - } self.errors.push(TypeError::Custom { message: "'is' operator requires a type name on the right side".to_string(), span: bin.right.span(), @@ -233,8 +275,8 @@ impl<'a> TypeInferrer<'a> { return Type::Bool; } - let left_ty = self.infer_expr(&bin.left); - let right_ty = self.infer_expr(&bin.right); + let left_ty = self.infer_expr(bin.left); + let right_ty = self.infer_expr(bin.right); match bin.op { Add => { @@ -245,7 +287,7 @@ impl<'a> TypeInferrer<'a> { (Type::String, Type::String) => Type::String, _ if left_resolved.is_numeric() || right_resolved.is_numeric() => { // Handle int/uint coercion for Add as well - let coerced = self.coerce_int_uint(&left_resolved, &right_resolved, &bin.left, &bin.right); + let coerced = self.coerce_int_uint(&left_resolved, &right_resolved, bin.left, bin.right); if let Some(result_ty) = coerced { return result_ty; } @@ -283,7 +325,7 @@ impl<'a> TypeInferrer<'a> { let right_resolved = right_ty.resolve(); // Handle int/uint coercion: if one is uint and other is int, prefer uint - let coerced = self.coerce_int_uint(&left_resolved, &right_resolved, &bin.left, &bin.right); + let coerced = self.coerce_int_uint(&left_resolved, &right_resolved, bin.left, bin.right); if let Some(result_ty) = coerced { return result_ty; } @@ -387,7 +429,7 @@ impl<'a> TypeInferrer<'a> { } fn infer_unary(&mut self, un: &ast::UnaryExpr) -> Type { - let operand_ty = self.infer_expr(&un.operand); + let operand_ty = self.infer_expr(un.operand); use ast::UnaryOp::*; match un.op { @@ -444,14 +486,13 @@ impl<'a> TypeInferrer<'a> { return Type::Exception(exc_def.name); } - if let Some(func_sig) = self.symbols.get_function(ident.ident.symbol) { - if !func_sig.type_params.is_empty() { + if let Some(func_sig) = self.symbols.get_function(ident.ident.symbol) + && !func_sig.type_params.is_empty() { return self.infer_generic_call(call, func_sig); } - } } - let callee_ty = self.infer_expr(&call.callee); + let callee_ty = self.infer_expr(call.callee); let resolved = callee_ty.resolve(); match resolved { @@ -546,12 +587,31 @@ impl<'a> TypeInferrer<'a> { } } + // Record monomorphization for codegen: resolve type vars to concrete types + let resolved_type_args: Vec = func_sig + .type_params + .iter() + .map(|tp| { + substitution + .get(&tp.name) + .cloned() + .unwrap_or(Type::Error) + .resolve() + }) + .collect(); + + // Generate mangled name: func_TypeArg1_TypeArg2 + let func_name = self.interner.resolve(&func_sig.name); + let mangled_name = self.mangle_generic_function(func_name, &resolved_type_args); + self.annotations + .record_monomorphization(call.span, func_sig.name, resolved_type_args, mangled_name); + // Return substituted return type func_sig.return_ty.substitute(&substitution) } fn infer_method_call(&mut self, call: &ast::MethodCallExpr) -> Type { - let receiver_ty = self.infer_expr(&call.receiver); + let receiver_ty = self.infer_expr(call.receiver); let resolved = receiver_ty.resolve(); // Handle built-in option methods @@ -706,8 +766,8 @@ impl<'a> TypeInferrer<'a> { // Check if receiver is a bare type parameter (T with no type args) // If so, look up methods from its bounds - if let Type::Generic(param_name, type_args) = &resolved { - if type_args.is_empty() { + if let Type::Generic(param_name, type_args) = &resolved + && type_args.is_empty() { // This might be a type parameter - check if it has bounds with this method if let Some(method_type) = super::generics::find_method_from_bounds(*param_name, call.method.symbol, self.env, self.symbols) @@ -733,7 +793,6 @@ impl<'a> TypeInferrer<'a> { return method_type.returns; } } - } // Handle built-in exception methods if let Type::Exception(_) = &resolved { @@ -865,8 +924,8 @@ impl<'a> TypeInferrer<'a> { } fn infer_index(&mut self, idx: &ast::IndexExpr) -> Type { - let base_ty = self.infer_expr(&idx.base); - let index_ty = self.infer_expr(&idx.index); + let base_ty = self.infer_expr(idx.base); + let index_ty = self.infer_expr(idx.index); let resolved = base_ty.resolve(); match resolved { @@ -901,7 +960,7 @@ impl<'a> TypeInferrer<'a> { } fn infer_field(&mut self, field: &ast::FieldExpr) -> Type { - let base_ty = self.infer_expr(&field.base); + let base_ty = self.infer_expr(field.base); let resolved = base_ty.resolve(); match resolved { @@ -915,7 +974,7 @@ impl<'a> TypeInferrer<'a> { field: field_name.to_string(), span: field.span, }); - return Type::Error; + Type::Error } Type::Struct(s) => { for f in &s.fields { @@ -1148,12 +1207,12 @@ impl<'a> TypeInferrer<'a> { } fn infer_if(&mut self, if_expr: &ast::IfExpr) -> Type { - let cond_ty = self.infer_expr(&if_expr.condition); + let cond_ty = self.infer_expr(if_expr.condition); if let Err(e) = unify(&cond_ty, &Type::Bool, if_expr.condition.span()) { self.errors.push(e); } - let then_ty = self.infer_block(&if_expr.then_branch); + let then_ty = self.infer_block(if_expr.then_branch); if let Some(else_branch) = &if_expr.else_branch { let else_ty = match else_branch { @@ -1209,23 +1268,20 @@ impl<'a> TypeInferrer<'a> { self.env .enter_function(expected_return_ty.clone(), vec![], &[]); - let body_ty = self.infer_expr(&lambda.body); + let body_ty = self.infer_expr(lambda.body); self.env.exit_function(); let return_ty = if lambda.return_ty.is_some() { - if let Err(e) = unify(&body_ty, &expected_return_ty, lambda.span) { - if !matches!(body_ty, Type::Unit) { + if let Err(e) = unify(&body_ty, &expected_return_ty, lambda.span) + && !matches!(body_ty, Type::Unit) { self.errors.push(e); } - } expected_return_ty + } else if unify(&body_ty, &expected_return_ty, lambda.span).is_err() { + body_ty } else { - if let Err(_) = unify(&body_ty, &expected_return_ty, lambda.span) { - body_ty - } else { - expected_return_ty.resolve() - } + expected_return_ty.resolve() }; self.env.pop_scope(); @@ -1239,20 +1295,20 @@ impl<'a> TypeInferrer<'a> { } fn infer_spawn(&mut self, spawn: &ast::SpawnExpr) -> Type { - let body_ty = self.infer_block(&spawn.body); + let body_ty = self.infer_block(spawn.body); Type::Promise(Box::new(body_ty)) } fn infer_try(&mut self, try_expr: &ast::TryExpr) -> Type { - let expr_ty = self.infer_expr(&try_expr.expr); - expr_ty + + self.infer_expr(try_expr.expr) } fn infer_catch(&mut self, catch: &ast::CatchExpr) -> Type { - let expr_ty = self.infer_expr(&catch.expr); + let expr_ty = self.infer_expr(catch.expr); // Determine the exception type from the expression being caught - let exception_ty = self.get_throws_type(&catch.expr); + let exception_ty = self.get_throws_type(catch.expr); self.env.push_scope(); @@ -1262,7 +1318,7 @@ impl<'a> TypeInferrer<'a> { for stmt in &catch.handler.statements { self.check_stmt(stmt); } - if let Some(ref tail) = catch.handler.tail { + if let Some(tail) = catch.handler.tail { self.infer_expr(tail); } @@ -1276,13 +1332,11 @@ impl<'a> TypeInferrer<'a> { match expr { Expression::Call(call) => { // Check if callee is a function with throws - if let Expression::Identifier(ident) = call.callee { - if let Some(func_sig) = self.symbols.get_function(ident.ident.symbol) { - if let Some(first_throw) = func_sig.throws.first() { + if let Expression::Identifier(ident) = call.callee + && let Some(func_sig) = self.symbols.get_function(ident.ident.symbol) + && let Some(first_throw) = func_sig.throws.first() { return first_throw.clone(); } - } - } Type::Error } Expression::MethodCall(_method_call) => { @@ -1295,8 +1349,8 @@ impl<'a> TypeInferrer<'a> { } fn infer_or_default(&mut self, or_default: &ast::OrDefaultExpr) -> Type { - let expr_ty = self.infer_expr(&or_default.expr); - let default_ty = self.infer_expr(&or_default.default); + let expr_ty = self.infer_expr(or_default.expr); + let default_ty = self.infer_expr(or_default.default); let inner_ty = match expr_ty.resolve() { Type::Option(inner) => *inner, @@ -1311,7 +1365,7 @@ impl<'a> TypeInferrer<'a> { } fn infer_cast(&mut self, cast: &ast::CastExpr) -> Type { - self.infer_expr(&cast.expr); + self.infer_expr(cast.expr); self.convert_ast_type(&cast.target_ty) } @@ -1511,11 +1565,10 @@ impl<'a> TypeInferrer<'a> { let init_ty = self.infer_expr(init); // Allow int literals to be assigned to uint (non-negative int → uint coercion) let should_unify = !self.is_int_to_uint_coercion(&init_ty, &ty, init); - if should_unify { - if let Err(e) = unify(&init_ty, &ty, init.span()) { + if should_unify + && let Err(e) = unify(&init_ty, &ty, init.span()) { self.errors.push(e); } - } } self.env.define(var.name.symbol, ty, var.mutable); @@ -1531,11 +1584,10 @@ impl<'a> TypeInferrer<'a> { let init_ty = self.infer_expr(&c.init); // Allow int literals to be assigned to uint (non-negative int → uint coercion) let should_unify = !self.is_int_to_uint_coercion(&init_ty, &ty, &c.init); - if should_unify { - if let Err(e) = unify(&init_ty, &ty, c.init.span()) { + if should_unify + && let Err(e) = unify(&init_ty, &ty, c.init.span()) { self.errors.push(e); } - } self.env.define(c.name.symbol, ty, false); } @@ -1543,8 +1595,8 @@ impl<'a> TypeInferrer<'a> { // Special case for map index assignment: map[key] = value // The value should be of type V, not option if let ast::Expression::Index(idx) = &assign.target { - let base_ty = self.infer_expr(&idx.base); - let index_ty = self.infer_expr(&idx.index); + let base_ty = self.infer_expr(idx.base); + let index_ty = self.infer_expr(idx.index); let resolved = base_ty.resolve(); if let Type::Map(key, val) = resolved { @@ -1571,11 +1623,10 @@ impl<'a> TypeInferrer<'a> { Return(ret) => { if let Some(value) = &ret.value { let ret_ty = self.infer_expr(value); - if let Some(expected) = self.env.expected_return_type() { - if let Err(e) = unify(&ret_ty, expected, value.span()) { + if let Some(expected) = self.env.expected_return_type() + && let Err(e) = unify(&ret_ty, expected, value.span()) { self.errors.push(e); } - } } } Throw(throw) => { diff --git a/namlc/src/typechecker/typed_ast.rs b/namlc/src/typechecker/typed_ast.rs index 68fe444..2bbc6f7 100644 --- a/namlc/src/typechecker/typed_ast.rs +++ b/namlc/src/typechecker/typed_ast.rs @@ -19,9 +19,18 @@ use std::collections::HashMap; +use lasso::Spur; + use crate::source::Span; use super::types::Type; +#[derive(Debug, Clone)] +pub struct MonomorphizationInfo { + pub function_name: Spur, + pub type_args: Vec, + pub mangled_name: String, +} + #[derive(Debug, Clone)] pub struct ExprTypeInfo { pub ty: Type, @@ -52,12 +61,16 @@ impl ExprTypeInfo { #[derive(Debug, Default)] pub struct TypeAnnotations { expr_types: HashMap, + monomorphizations: HashMap, + call_site_instantiations: HashMap, } impl TypeAnnotations { pub fn new() -> Self { Self { expr_types: HashMap::new(), + monomorphizations: HashMap::new(), + call_site_instantiations: HashMap::new(), } } @@ -80,13 +93,13 @@ impl TypeAnnotations { pub fn needs_clone(&self, span: Span) -> bool { self.expr_types .get(&span) - .map_or(false, |info| info.needs_clone) + .is_some_and(|info| info.needs_clone) } pub fn is_lvalue(&self, span: Span) -> bool { self.expr_types .get(&span) - .map_or(false, |info| info.is_lvalue) + .is_some_and(|info| info.is_lvalue) } pub fn len(&self) -> usize { @@ -96,6 +109,34 @@ impl TypeAnnotations { pub fn is_empty(&self) -> bool { self.expr_types.is_empty() } + + pub fn record_monomorphization( + &mut self, + call_span: Span, + function_name: Spur, + type_args: Vec, + mangled_name: String, + ) { + let info = MonomorphizationInfo { + function_name, + type_args, + mangled_name: mangled_name.clone(), + }; + self.monomorphizations.insert(mangled_name.clone(), info); + self.call_site_instantiations.insert(call_span, mangled_name); + } + + pub fn get_monomorphizations(&self) -> &HashMap { + &self.monomorphizations + } + + pub fn get_call_instantiation(&self, span: Span) -> Option<&String> { + self.call_site_instantiations.get(&span) + } + + pub fn get_monomorphization_info(&self, mangled_name: &str) -> Option<&MonomorphizationInfo> { + self.monomorphizations.get(mangled_name) + } } #[cfg(test)] diff --git a/namlc/src/typechecker/unify.rs b/namlc/src/typechecker/unify.rs index 71c5546..aab6118 100644 --- a/namlc/src/typechecker/unify.rs +++ b/namlc/src/typechecker/unify.rs @@ -37,11 +37,10 @@ pub fn unify(a: &Type, b: &Type, span: Span) -> TypeResult<()> { | (Type::Unit, Type::Unit) => Ok(()), (Type::TypeVar(var), other) | (other, Type::TypeVar(var)) => { - if let Type::TypeVar(other_var) = other { - if var.id == other_var.id { + if let Type::TypeVar(other_var) = other + && var.id == other_var.id { return Ok(()); } - } if other.contains_var(var.id) { return Err(TypeError::Custom {