From 07782257a11f9b6342cd0d69bbc54091d8f9ef24 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 9 Dec 2025 18:42:24 -0500 Subject: [PATCH] Fix more type stuff --- src/canonicalize/Can.zig | 3 ++ src/canonicalize/NodeStore.zig | 9 ++-- src/canonicalize/TypeAnnotation.zig | 1 + src/canonicalize/test/node_store_test.zig | 1 + src/check/Check.zig | 50 +++++++++++++++-------- src/check/unify.zig | 38 ++++++++--------- 6 files changed, 64 insertions(+), 38 deletions(-) diff --git a/src/canonicalize/Can.zig b/src/canonicalize/Can.zig index d2e0b4c531..aecda7f6c9 100644 --- a/src/canonicalize/Can.zig +++ b/src/canonicalize/Can.zig @@ -8303,8 +8303,11 @@ fn canonicalizeTypeAnnoRecord( const record_fields_scratch = self.scratch_record_fields.sliceFromStart(scratch_record_fields_top); std.mem.sort(types.RecordField, record_fields_scratch, self.env.common.getIdentStore(), comptime types.RecordField.sortByNameAsc); + // TODO: Support record extension syntax in the parser (e.g., `{ name: Str, ..others }`) + // For now, all records are closed (ext = null) return try self.env.addTypeAnno(.{ .record = .{ .fields = field_anno_idxs, + .ext = null, } }, region); } diff --git a/src/canonicalize/NodeStore.zig b/src/canonicalize/NodeStore.zig index 5ec8869c3e..bac13b3bc1 100644 --- a/src/canonicalize/NodeStore.zig +++ b/src/canonicalize/NodeStore.zig @@ -1214,9 +1214,12 @@ pub fn getTypeAnno(store: *const NodeStore, typeAnno: CIR.TypeAnno.Idx) CIR.Type .ty_tuple => return CIR.TypeAnno{ .tuple = .{ .elems = .{ .span = .{ .start = node.data_1, .len = node.data_2 } }, } }, - .ty_record => return CIR.TypeAnno{ .record = .{ - .fields = .{ .span = .{ .start = node.data_1, .len = node.data_2 } }, - } }, + .ty_record => return CIR.TypeAnno{ + .record = .{ + .fields = .{ .span = .{ .start = node.data_1, .len = node.data_2 } }, + .ext = null, // TODO: Support record extension in serialization + }, + }, .ty_fn => { const extra_data_idx = node.data_3; const effectful = store.extra_data.items.items[extra_data_idx] != 0; diff --git a/src/canonicalize/TypeAnnotation.zig b/src/canonicalize/TypeAnnotation.zig index e8fcd025a4..66fb9e0621 100644 --- a/src/canonicalize/TypeAnnotation.zig +++ b/src/canonicalize/TypeAnnotation.zig @@ -365,6 +365,7 @@ pub const TypeAnno = union(enum) { /// A record in a type annotation pub const Record = struct { fields: RecordField.Span, // The field definitions + ext: ?TypeAnno.Idx, // Optional extension variable for open records }; /// A tag union in a type annotation diff --git a/src/canonicalize/test/node_store_test.zig b/src/canonicalize/test/node_store_test.zig index 3f655bbb82..70f8555a91 100644 --- a/src/canonicalize/test/node_store_test.zig +++ b/src/canonicalize/test/node_store_test.zig @@ -968,6 +968,7 @@ test "NodeStore round trip - TypeAnno" { try type_annos.append(gpa, CIR.TypeAnno{ .record = .{ .fields = CIR.TypeAnno.RecordField.Span{ .span = rand_span() }, + .ext = null, }, }); diff --git a/src/check/Check.zig b/src/check/Check.zig index dd6885e36a..745ecda20a 100644 --- a/src/check/Check.zig +++ b/src/check/Check.zig @@ -1001,9 +1001,8 @@ pub fn checkFile(self: *Self) std.mem.Allocator.Error!void { .s_runtime_error => { try self.unifyWith(stmt_var, .err, &env); }, - .s_type_anno => |_| { - // TODO: Handle standalone type annotations - try self.unifyWith(stmt_var, .err, &env); + .s_type_anno => |type_anno| { + try self.generateStandaloneTypeAnno(stmt_var, type_anno, &env); }, else => { // All other stmt types are invalid at the top level @@ -1375,6 +1374,25 @@ fn generateNominalDecl( ); } +/// Generate types for a standalone type annotation (one without a corresponding definition). +/// These are typically used for FFI function declarations or forward declarations. +fn generateStandaloneTypeAnno( + self: *Self, + stmt_var: Var, + type_anno: std.meta.fieldInfo(CIR.Statement, .s_type_anno).type, + env: *Env, +) std.mem.Allocator.Error!void { + // Generate the type from the annotation + self.seen_annos.clearRetainingCapacity(); + const anno_var: Var = ModuleEnv.varFrom(type_anno.anno); + try self.generateAnnoTypeInPlace(type_anno.anno, env, .annotation); + + // Unify the statement variable with the generated annotation type + _ = try self.unify(stmt_var, anno_var, env); + + // TODO: Handle where clauses if present (type_anno.where) +} + /// Generate types for type anno args fn generateHeaderVars( self: *Self, @@ -1952,17 +1970,13 @@ fn generateAnnoTypeInPlace(self: *Self, anno_idx: CIR.TypeAnno.Idx, env: *Env, c std.mem.sort(types_mod.RecordField, record_fields_slice, self.cir.common.getIdentStore(), comptime types_mod.RecordField.sortByNameAsc); const fields_type_range = try self.types.appendRecordFields(record_fields_slice); - // Process the ext if it exists. Absence means it's a closed union - // TODO: Capture ext in record field CIR - // const ext_var = inner_blk: { - // if (rec.ext) |ext_anno_idx| { - // try self.generateAnnoType(rigid_vars_ctx, ext_anno_idx); - // break :inner_blk ModuleEnv.varFrom(ext_anno_idx); - // } else { - // break :inner_blk try self.freshFromContent(.{ .structure = .empty_record }, rank, anno_region); - // } - // }; - const ext_var = try self.freshFromContent(.{ .structure = .empty_record }, env, anno_region); + // Process the ext if it exists. Absence (null) means it's a closed record. + const ext_var = if (rec.ext) |ext_anno_idx| blk: { + try self.generateAnnoTypeInPlace(ext_anno_idx, env, ctx); + break :blk ModuleEnv.varFrom(ext_anno_idx); + } else blk: { + break :blk try self.freshFromContent(.{ .structure = .empty_record }, env, anno_region); + }; // Create the type for the anno in the store try self.unifyWith( @@ -2853,7 +2867,9 @@ fn checkExpr(self: *Self, expr_idx: CIR.Expr.Idx, env: *Env, expected: Expected) try self.checkDef(processing_def.def_idx, &sub_env); }, .processing => { - // TODO: Handle recursive defs + // Recursive reference - the pattern variable is still at + // top_level rank (not generalized), so the code below will + // unify directly with it, which is the correct behavior. }, .processed => {}, } @@ -5024,7 +5040,9 @@ fn checkDeferredStaticDispatchConstraints(self: *Self, env: *Env) std.mem.Alloca try self.checkDef(def_idx, &sub_env); }, .processing => { - // TODO: Handle recursive defs + // Recursive reference during static dispatch resolution. + // The def is still being processed, so we'll use its + // current (non-generalized) type. }, .processed => {}, } diff --git a/src/check/unify.zig b/src/check/unify.zig index 6aebf9df22..10e6afba5d 100644 --- a/src/check/unify.zig +++ b/src/check/unify.zig @@ -1272,54 +1272,54 @@ const Unifier = struct { /// /// **Case 1: Exactly the Same Fields** /// - /// a = { x, y }ext_a - /// b = { x, y }ext_b + /// a = { x, y, ..others_a } + /// b = { x, y, ..others_b } /// /// - All fields are shared - /// - We unify `ext_a ~ ext_b` + /// - We unify `others_a ~ others_b` /// - Then unify each shared field pair /// /// --- /// /// **Case 2: `a` Extends `b`** /// - /// a = { x, y, z }ext_a - /// b = { x, y }ext_b + /// a = { x, y, z, ..others_a } + /// b = { x, y, ..others_b } /// /// - `a` has additional fields not in `b` - /// - We generate a new var `only_in_a_var = { z }ext_a` - /// - Unify `only_in_a_var ~ ext_b` + /// - We generate a new var `only_in_a_var = { z, ..others_a }` + /// - Unify `only_in_a_var ~ others_b` /// - Then unify shared fields /// /// --- /// /// **Case 3: `b` Extends `a`** /// - /// a = { x, y }ext_a - /// b = { x, y, z }ext_b + /// a = { x, y, ..others_a } + /// b = { x, y, z, ..others_b } /// /// - Same as Case 2, but reversed /// - `b` has additional fields not in `a` - /// - We generate a new var `only_in_b_var = { z }ext_b` - /// - Unify `ext_a ~ only_in_b_var` + /// - We generate a new var `only_in_b_var = { z, ..others_b }` + /// - Unify `others_a ~ only_in_b_var` /// - Then unify shared fields /// /// --- /// /// **Case 4: Both Extend Each Other** /// - /// a = { x, y, z }ext_a - /// b = { x, y, w }ext_b + /// a = { x, y, z, ..others_a } + /// b = { x, y, w, ..others_b } /// /// - Each has unique fields the other lacks /// - Generate: - /// - shared_ext = fresh flex_var - /// - only_in_a_var = { z }shared_ext - /// - only_in_b_var = { w }shared_ext + /// - shared_others = fresh flex_var + /// - only_in_a_var = { z, ..shared_others } + /// - only_in_b_var = { w, ..shared_others } /// - Unify: - /// - `ext_a ~ only_in_b_var` - /// - `only_in_a_var ~ ext_b` - /// - Then unify shared fields into `{ x, y }shared_ext` + /// - `others_a ~ only_in_b_var` + /// - `only_in_a_var ~ others_b` + /// - Then unify shared fields into `{ x, y, ..shared_others }` /// /// --- ///