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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user