ir: dedicated TypeId.unresolved sentinel; kill inferred_type => .s64

An unannotated param resolving to a plausible .s64 was the classic
silent-default trap (root of the 2.5 multi-param-closure bug). Replace it
with a dedicated TypeId.unresolved at slot 0, so a zero-initialised or
forgotten TypeId trips the sentinel instead of masquerading as a real type.

- types.zig: TypeId.unresolved = 0 (void moves to 17); TypeInfo.unresolved;
  sizeOf/toLLVMType @panic on it (codegen tripwire); hash/eql/printer cover it.
- type_bridge: inferred_type => .unresolved (was .s64).
- resolveParamType: emit "parameter 'x' has no type annotation" for a
  genuinely-unannotated value param (comptime/variadic/pack params exempt --
  they resolve via per-call substitution).
- lowerLambda: resolve unannotated params from the target closure signature;
  otherwise emit "cannot infer type of lambda parameter".
- CLAUDE.md: .void documented as an UNACCEPTABLE failed-type sentinel (it
  conflates with a real, heavily-checked type); prescribe a distinct
  .unresolved-style value + codegen tripwire.

Snapshot churn: one .ir (ffi-objc-call-06) -- the runtime type-name table and
typeof match arms renumber by the new builtin slot; program output unchanged.
This commit is contained in:
agra
2026-05-29 22:25:45 +03:00
parent 5fd513466f
commit 55e62694d1
6 changed files with 575 additions and 509 deletions

View File

@@ -7101,13 +7101,22 @@ pub const Lowering = struct {
// User params follow the ctx (optional) + env slots in `params`.
const user_param_base: usize = (if (lambda_wants_ctx) @as(usize, 1) else 0) + 1;
for (lam.params, 0..) |p, pi| {
var pty = self.resolveParamType(&p);
// Infer param type from target closure type if no annotation
if (p.type_expr.data == .inferred_type and target_closure_params != null) {
if (pi < target_closure_params.?.len) {
pty = target_closure_params.?[pi];
const pty: TypeId = blk: {
// Unannotated lambda params take their type positionally from
// the target `Closure(T0, …)` signature. Resolve them here so
// `resolveParamType` (which would diagnose a missing annotation)
// is only called for params that carry one.
if (p.type_expr.data == .inferred_type) {
if (target_closure_params != null and pi < target_closure_params.?.len) {
break :blk target_closure_params.?[pi];
}
if (self.diagnostics) |d| {
d.addFmt(.err, p.type_expr.span, "cannot infer type of lambda parameter '{s}'; annotate it or use the lambda where a closure type is expected", .{p.name});
}
break :blk .unresolved;
}
}
break :blk self.resolveParamType(&p);
};
params.append(self.alloc, .{
.name = self.module.types.internString(p.name),
.ty = pty,
@@ -10743,6 +10752,18 @@ pub const Lowering = struct {
}
fn resolveParamType(self: *Lowering, p: *const ast.Param) TypeId {
// A plain value param with no annotation can only be typed from
// context (a lambda's target closure signature). When `resolveParamType`
// is reached for one, there is no such context — so it's a genuine
// "missing annotation" error, not an 8-byte-int guess. (Comptime/
// variadic pack params also carry `inferred_type` but get their types
// from per-call substitution, so they're exempt here.)
if (p.type_expr.data == .inferred_type and !p.is_comptime and !p.is_variadic and !p.is_pack) {
if (self.diagnostics) |d| {
d.addFmt(.err, p.type_expr.span, "parameter '{s}' has no type annotation", .{p.name});
}
return .unresolved;
}
const declared_ty = self.resolveTypeWithBindings(p.type_expr);
if (p.is_variadic) {
// Two surface forms: