lang: opt-in UFCS — ufcs-marked fns + alias dot-dispatch, generic binding via receiver; one binding builder for plan-side generic returns

This commit is contained in:
agra
2026-06-11 17:04:51 +03:00
parent 84e0fb0752
commit a47ea1416e
27 changed files with 316 additions and 137 deletions

View File

@@ -263,53 +263,16 @@ pub const GenericResolver = struct {
pub fn inferGenericReturnType(self: GenericResolver, fd: *const ast.FnDecl, c: *const ast.Call) TypeId {
if (fd.return_type == null) return .void;
// Build ALL type bindings from call args before resolving return type
var tmp_bindings = std.StringHashMap(TypeId).init(self.l.alloc);
// ONE binding builder: the same `buildTypeBindings` the lowering /
// monomorphization path uses, so plan-side return typing can't
// disagree with the instance actually dispatched. (The previous
// local strategies only bound BARE `$T` value params — a structured
// param (`[]$T`, `*$T`) never bound, so the planned return type of
// e.g. `gfirst(xs: []$T) -> T` was the `T` stub and print's Any
// boxing mis-tagged the value.)
var tmp_bindings = self.buildTypeBindings(fd, c.args);
defer tmp_bindings.deinit();
for (fd.type_params) |tp| {
// Strategy 1: direct type param decl ($T: Type) — param.name == tp.name.
// Only fires when the caller actually supplied a type expression at
// that position; otherwise fall through to value-based inference.
var found = false;
for (fd.params, 0..) |param, pi| {
if (std.mem.eql(u8, param.name, tp.name)) {
if (pi < c.args.len and type_bridge.isTypeShapedAstNode(c.args[pi], &self.l.module.types)) {
const ty = self.l.resolveTypeArg(c.args[pi]);
tmp_bindings.put(tp.name, ty) catch {};
found = true;
}
break;
}
}
if (found) continue;
// Strategy 2: inferred from usage (a: $T, b: T) — check ALL matching params, pick widest
var inferred_ty: ?TypeId = null;
for (fd.params, 0..) |param, pi| {
if (param.type_expr.data == .type_expr) {
const te = param.type_expr.data.type_expr;
if (std.mem.eql(u8, te.name, tp.name)) {
if (pi < c.args.len) {
const arg_ty = self.l.inferExprType(c.args[pi]);
if (inferred_ty) |prev| {
if (arg_ty == .f64 and prev != .f64) {
inferred_ty = arg_ty;
} else if (arg_ty == .f32 and prev != .f64 and prev != .f32) {
inferred_ty = arg_ty;
}
} else {
inferred_ty = arg_ty;
}
}
}
}
}
if (inferred_ty) |ty| {
tmp_bindings.put(tp.name, ty) catch {};
}
}
// Resolve return type with whatever bindings we built. Even an
// empty `tmp_bindings` is a valid input — non-generic literal
// return types (e.g. `walk(..$args) -> string`) still need to