Fix more type stuff

This commit is contained in:
Richard Feldman 2025-12-09 18:42:24 -05:00
parent 5a3f900b6f
commit 07782257a1
No known key found for this signature in database
6 changed files with 64 additions and 38 deletions

View file

@ -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);
}

View file

@ -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;

View file

@ -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

View file

@ -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,
},
});

View file

@ -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 => {},
}

View file

@ -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 }`
///
/// ---
///