sema/ir: kill remaining s64 fallbacks (sema Type + getRefType)

- types.Type: add dedicated `unresolved` variant (mirrors ir.TypeId.unresolved)
  with eql/displayName arms; bridgeType maps it to TypeId.unresolved.
- sema.inferExprType + signature/field resolution: every Type.fromTypeExpr /
  fromName / symbol lookup miss and call/field/index fallthrough now yields
  Type.unresolved instead of a fabricated s(64). A variadic `..xs: []T` slice
  element is taken from T, not a guessed "s32". Genuine literal defaults
  (int=>s64, float=>f32, .len=>s64) kept.
- Builder.getRefType: an unlocatable ref (no active function / out-of-range)
  returns .unresolved, not .s64 -- this is the accurate type source the pack
  mono / binop / null-cmp fixes rely on, so it must not fabricate.

236 examples + unit tests (incl sema) green.
This commit is contained in:
agra
2026-05-30 00:38:23 +03:00
parent 8bc2ed4c49
commit f21b99c811
4 changed files with 33 additions and 19 deletions

View File

@@ -321,9 +321,11 @@ pub const Builder = struct {
blk.first_ref = self.inst_counter;
}
/// Get the type of a previously emitted instruction Ref.
/// Get the type of a previously emitted instruction Ref. A ref that can't
/// be located (no active function, or an out-of-range ref) has no knowable
/// type — return the `.unresolved` sentinel rather than a fabricated `.s64`.
pub fn getRefType(self: *Builder, ref: Ref) TypeId {
if (self.func == null) return .s64;
if (self.func == null) return .unresolved;
const func = self.currentFunc();
const ref_idx = @intFromEnum(ref);
// Check function parameters first (refs 0..N-1)
@@ -336,7 +338,7 @@ pub const Builder = struct {
return block.insts.items[ref_idx - first].ty;
}
}
return .s64;
return .unresolved;
}
// ── Emit helpers ────────────────────────────────────────────────

View File

@@ -163,6 +163,7 @@ pub fn bridgeType(ty: sx_types.Type, table: *TypeTable) TypeId {
} });
},
.meta_type => .any, // meta types map to Any for now
.unresolved => .unresolved,
};
}

View File

@@ -136,11 +136,16 @@ pub const Analyzer = struct {
var param_types = std.ArrayList(Type).empty;
var has_variadic = false;
for (fd.params) |param| {
const pt = Type.fromTypeExpr(param.type_expr) orelse Type.s(64);
const pt = Type.fromTypeExpr(param.type_expr) orelse Type.unresolved;
if (param.is_variadic) {
has_variadic = true;
// Variadic param becomes a slice type
const elem_name = if (param.type_expr.data == .type_expr) param.type_expr.data.type_expr.name else "s32";
const elem_name = switch (param.type_expr.data) {
.type_expr => |te| te.name,
// `..xs: []T` — the element is T, not a guessed s32.
.slice_type_expr => |st| if (st.element_type.data == .type_expr) st.element_type.data.type_expr.name else "<unresolved>",
else => "<unresolved>",
};
try param_types.append(self.allocator, .{ .slice_type = .{ .element_name = elem_name } });
} else {
try param_types.append(self.allocator, pt);
@@ -169,7 +174,7 @@ pub const Analyzer = struct {
const lam = cd.value.data.lambda;
var param_types = std.ArrayList(Type).empty;
for (lam.params) |param| {
const pt = Type.fromTypeExpr(param.type_expr) orelse Type.s(64);
const pt = Type.fromTypeExpr(param.type_expr) orelse Type.unresolved;
try param_types.append(self.allocator, pt);
}
const ret = if (lam.return_type) |rt|
@@ -222,7 +227,7 @@ pub const Analyzer = struct {
}
if (i < sd.field_names.len) {
try all_names.append(self.allocator, sd.field_names[i]);
const resolved = Type.fromTypeExpr(sd.field_types[i]) orelse Type.s(64);
const resolved = Type.fromTypeExpr(sd.field_types[i]) orelse Type.unresolved;
try all_types.append(self.allocator, resolved);
}
}
@@ -233,7 +238,7 @@ pub const Analyzer = struct {
} else {
var field_types = std.ArrayList(Type).empty;
for (sd.field_types) |ft| {
const resolved = Type.fromTypeExpr(ft) orelse Type.s(64);
const resolved = Type.fromTypeExpr(ft) orelse Type.unresolved;
try field_types.append(self.allocator, resolved);
}
try self.struct_types.put(sd.name, .{
@@ -369,11 +374,11 @@ pub const Analyzer = struct {
j -= 1;
const sym = self.symbols.items[indices.items[j]];
if (sym.scope_depth <= self.scope_depth) {
return sym.ty orelse Type.s(64);
return sym.ty orelse Type.unresolved;
}
}
}
return Type.s(64);
return Type.unresolved;
},
.if_expr => |ie| {
return self.inferExprType(ie.then_branch);
@@ -391,7 +396,7 @@ pub const Analyzer = struct {
return .void_type;
},
.call => |call_node| {
const callee_name = self.resolveCalleeName(call_node) orelse return Type.s(64);
const callee_name = self.resolveCalleeName(call_node) orelse return Type.unresolved;
// Check fn_signatures registry
if (self.fn_signatures.get(callee_name)) |sig| {
return sig.return_type;
@@ -405,7 +410,7 @@ pub const Analyzer = struct {
if (call_node.args.len > 0) return self.inferExprType(call_node.args[0]);
return .f32;
}
return Type.s(64);
return Type.unresolved;
},
.unary_op => |unop| {
return self.inferExprType(unop.operand);
@@ -426,17 +431,17 @@ pub const Analyzer = struct {
}
}
if (obj_ty.isArray()) {
return Type.fromName(obj_ty.array_type.element_name) orelse Type.s(64);
return Type.fromName(obj_ty.array_type.element_name) orelse Type.unresolved;
}
return Type.s(64);
return Type.unresolved;
},
.index_expr => |ie| {
const obj_ty = self.inferExprType(ie.object);
if (obj_ty == .string_type) return Type.u(8);
if (obj_ty.isArray()) {
return Type.fromName(obj_ty.array_type.element_name) orelse Type.s(64);
return Type.fromName(obj_ty.array_type.element_name) orelse Type.unresolved;
}
return Type.s(64);
return Type.unresolved;
},
.slice_expr => |se| {
const obj_ty = self.inferExprType(se.object);
@@ -677,7 +682,7 @@ pub const Analyzer = struct {
{
var param_types = std.ArrayList(Type).empty;
for (fd.params) |param| {
const pt = Type.fromTypeExpr(param.type_expr) orelse Type.s(64);
const pt = Type.fromTypeExpr(param.type_expr) orelse Type.unresolved;
try param_types.append(self.allocator, pt);
}
try self.fn_signatures.put(fd.name, .{
@@ -1026,7 +1031,7 @@ pub const Analyzer = struct {
fn inferFnReturnType(self: *Analyzer, params: []const ast.Param, body: *const Node) ?Type {
self.pushScope() catch return null;
for (params) |param| {
const pt = Type.fromTypeExpr(param.type_expr) orelse Type.s(64);
const pt = Type.fromTypeExpr(param.type_expr) orelse Type.unresolved;
self.addSymbol(param.name, .param, pt, param.name_span) catch {};
}
// Arrow fn_decl wraps body in block{[expr]} — unwrap to inner expression

View File

@@ -29,6 +29,11 @@ pub const Type = union(enum) {
optional_type: OptionalTypeInfo,
meta_type: MetaTypeInfo,
tuple_type: TupleTypeInfo,
/// Type resolution failed (sema couldn't infer/resolve). A dedicated
/// sentinel — never a legitimate type — so callers can't mistake it for a
/// real result the way a fabricated `s(64)` would be. Mirrors
/// `ir.TypeId.unresolved`.
unresolved,
pub const SliceTypeInfo = struct {
element_name: []const u8,
@@ -84,7 +89,7 @@ pub const Type = union(enum) {
return switch (self) {
.signed => |w| w == other.signed,
.unsigned => |w| w == other.unsigned,
.f32, .f64, .void_type, .boolean, .string_type, .any_type, .usize_type, .isize_type => true,
.f32, .f64, .void_type, .boolean, .string_type, .any_type, .usize_type, .isize_type, .unresolved => true,
.enum_type => |n| std.mem.eql(u8, n, other.enum_type),
.struct_type => |n| std.mem.eql(u8, n, other.struct_type),
.union_type => |n| std.mem.eql(u8, n, other.union_type),
@@ -555,6 +560,7 @@ pub const Type = union(enum) {
.any_type => "Any",
.usize_type => "usize",
.isize_type => "isize",
.unresolved => "<unresolved>",
.enum_type => |name| name,
.struct_type => |name| name,
.union_type => |name| name,