lang F1 Phase 6: canonical heterogeneous map — $R inference through closure params
The full canonical `map` now compiles and runs (examples/213 → 42):
map :: (mapper: Closure(..sources.T) -> $R, ..sources: VL) -> VL($R)
Final piece: infer a pack-fn's generic return `$R` from a closure-typed
prefix param's lowered return type.
- collectGenericNames descends into closure_type_expr (params + return),
so `$R` in `Closure(..) -> $R` registers as a function type-param.
- matchTypeParam/extractTypeParam descend into closures: `$R` is extracted
from the lowered mapper's closure `.ret`.
- lowerPackFnCall infers type-param bindings from the lowered prefix args,
folds them into the mangle, and threads them into monomorphizePackFn,
which installs self.type_bindings for return-type resolution + body
lowering (`-> VL($R)` ⇒ VL(s64); `Combined($R, ..)` ⇒ Combined(s64, ..)).
s64-elimination follow-through:
- An unbound generic `$R` resolves to `.unresolved` in resolveTypeWithBindings
rather than fabricating an empty-struct stub (`R{}`).
- Lambda return-type inference skips an `.unresolved` target-closure ret and
infers from the body, so the concrete return drives `$R`.
- The `.unresolved` codegen tripwire then caught a latent bug: a generic-struct
source impl (`impl VL($R) for Combined($R, ..$Ts)`) was declaring its template
method `Combined.get` (`-> $R`) as a standalone IR function. Fixed: a
generic-struct source registers methods as TEMPLATES only (findable in
fn_ast_map for per-instance monomorphization via createProtocolThunk), never
declareFunction'd.
Feature 1 (heterogeneous variadic packs) all six phases complete.
248 examples + all unit tests green.
This commit is contained in:
36
examples/213-canonical-map.sx
Normal file
36
examples/213-canonical-map.sx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
// Phase 6 — the canonical heterogeneous `map`, end to end. A pack-fn whose
|
||||||
|
// return type `$R` is inferred from the mapper's closure return:
|
||||||
|
// - `mapper: Closure(..sources.T) -> $R` types the lambda's params from the
|
||||||
|
// projected pack element types, and its body (`a + b`) drives `$R`.
|
||||||
|
// - `$R` is inferred at the call site from the lowered mapper's closure ret,
|
||||||
|
// bound into the mono (`-> VL($R)` ⇒ `VL(s64)`, `Combined($R, ..)` ⇒
|
||||||
|
// `Combined(s64, ..)`), and folded into the mangle.
|
||||||
|
// - `(..sources)` materializes the pack into the `(..VL(Ts))` field (per-element
|
||||||
|
// erase) and `mapper(..sources.get)` projects+spreads; `xx c` erases the
|
||||||
|
// generic-struct instance to `VL(s64)` via the generic impl's monomorphized
|
||||||
|
// thunk.
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
VL :: protocol(T: Type) { get :: () -> T; }
|
||||||
|
IntCell :: struct { v: s64; }
|
||||||
|
impl VL(s64) for IntCell { get :: (self: *IntCell) -> s64 => self.v; }
|
||||||
|
|
||||||
|
Combined :: struct($R: Type, ..$Ts: []Type) {
|
||||||
|
sources: (..VL(Ts));
|
||||||
|
value: $R;
|
||||||
|
}
|
||||||
|
impl VL($R) for Combined($R, ..$Ts) { get :: (self: *Combined) -> $R => self.value; }
|
||||||
|
|
||||||
|
map :: (mapper: Closure(..sources.T) -> $R, ..sources: VL) -> VL($R) {
|
||||||
|
c : Combined($R, ..sources.T) = ---;
|
||||||
|
c.sources = (..sources);
|
||||||
|
c.value = mapper(..sources.get);
|
||||||
|
return xx c;
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: () -> s32 {
|
||||||
|
r := map((a, b) => a + b, IntCell.{ v = 40 }, IntCell.{ v = 2 });
|
||||||
|
print("{}\n", r.get()); // 42
|
||||||
|
0;
|
||||||
|
}
|
||||||
154
src/ir/lower.zig
154
src/ir/lower.zig
@@ -7334,17 +7334,20 @@ pub const Lowering = struct {
|
|||||||
if (lam.return_type) |rt| {
|
if (lam.return_type) |rt| {
|
||||||
break :blk type_bridge.resolveAstType(rt, &self.module.types);
|
break :blk type_bridge.resolveAstType(rt, &self.module.types);
|
||||||
}
|
}
|
||||||
// Use target closure return type if available
|
// Use target closure return type if available — but only when it's
|
||||||
|
// a resolved type. An `.unresolved` ret comes from an unbound
|
||||||
|
// generic (`Closure(..) -> $R`); fall through to infer it from the
|
||||||
|
// body so the concrete return drives `$R` inference at the call site.
|
||||||
if (self.target_type) |tt| {
|
if (self.target_type) |tt| {
|
||||||
if (!tt.isBuiltin()) {
|
if (!tt.isBuiltin()) {
|
||||||
const tti = self.module.types.get(tt);
|
const tti = self.module.types.get(tt);
|
||||||
if (tti == .closure) break :blk tti.closure.ret;
|
if (tti == .closure and tti.closure.ret != .unresolved) break :blk tti.closure.ret;
|
||||||
// Unwrap ?Closure(...) → Closure(...)
|
// Unwrap ?Closure(...) → Closure(...)
|
||||||
if (tti == .optional) {
|
if (tti == .optional) {
|
||||||
const inner = tti.optional.child;
|
const inner = tti.optional.child;
|
||||||
if (!inner.isBuiltin()) {
|
if (!inner.isBuiltin()) {
|
||||||
const inner_info = self.module.types.get(inner);
|
const inner_info = self.module.types.get(inner);
|
||||||
if (inner_info == .closure) break :blk inner_info.closure.ret;
|
if (inner_info == .closure and inner_info.closure.ret != .unresolved) break :blk inner_info.closure.ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9165,12 +9168,14 @@ pub const Lowering = struct {
|
|||||||
// comptime type-pack `..$args` has no constraint to check.
|
// comptime type-pack `..$args` has no constraint to check.
|
||||||
var pack_protocol: ?[]const u8 = null;
|
var pack_protocol: ?[]const u8 = null;
|
||||||
var pack_is_comptime = false;
|
var pack_is_comptime = false;
|
||||||
|
var pack_name: []const u8 = "";
|
||||||
{
|
{
|
||||||
var fi: usize = 0;
|
var fi: usize = 0;
|
||||||
for (fd.params) |p| {
|
for (fd.params) |p| {
|
||||||
if (isPackParam(p)) {
|
if (isPackParam(p)) {
|
||||||
pack_start = fi;
|
pack_start = fi;
|
||||||
pack_is_comptime = p.is_comptime;
|
pack_is_comptime = p.is_comptime;
|
||||||
|
pack_name = p.name;
|
||||||
if (p.is_pack and p.type_expr.data == .type_expr) {
|
if (p.is_pack and p.type_expr.data == .type_expr) {
|
||||||
pack_protocol = p.type_expr.data.type_expr.name;
|
pack_protocol = p.type_expr.data.type_expr.name;
|
||||||
}
|
}
|
||||||
@@ -9181,12 +9186,39 @@ pub const Lowering = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lower the runtime prefix args and the pack args up front, taking each
|
// Lower the PACK args first, taking each type from the lowered value
|
||||||
// pack type from the lowered value (`getRefType`) rather than a
|
// (`getRefType`) — never a pre-lowering `inferExprType` guess. Knowing
|
||||||
// pre-lowering `inferExprType` guess: a lowered value always has a
|
// the pack element types up front lets the prefix args (e.g.
|
||||||
// concrete type, so a monomorphised pack param can never end up
|
// `mapper: Closure(..sources.T) -> $R`) resolve against them, so a
|
||||||
// `.unresolved` from incomplete static inference. (A comptime `..$args`
|
// lambda arg types its params from the projected closure signature.
|
||||||
// pack still uses `inferExprType` — its args may be type-position.)
|
// (A comptime `..$args` pack keeps `inferExprType` — its args may be
|
||||||
|
// type-position.)
|
||||||
|
var pack_refs = std.ArrayList(Ref).empty;
|
||||||
|
defer pack_refs.deinit(self.alloc);
|
||||||
|
for (call_node.args[pack_start..]) |a| {
|
||||||
|
const r = self.lowerExpr(a);
|
||||||
|
pack_refs.append(self.alloc, r) catch return self.builder.constInt(0, .void);
|
||||||
|
if (pack_is_comptime) {
|
||||||
|
const it = self.inferExprType(a);
|
||||||
|
pack_arg_types.append(self.alloc, if (it == .unresolved) self.builder.getRefType(r) else it) catch return self.builder.constInt(0, .void);
|
||||||
|
} else {
|
||||||
|
pack_arg_types.append(self.alloc, self.builder.getRefType(r)) catch return self.builder.constInt(0, .void);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install the pack's element types + constraint so prefix-arg param
|
||||||
|
// types like `Closure(..sources.T)` resolve while lowering the prefix.
|
||||||
|
var pat_map = std.StringHashMap([]const TypeId).init(self.alloc);
|
||||||
|
defer pat_map.deinit();
|
||||||
|
pat_map.put(pack_name, pack_arg_types.items) catch {};
|
||||||
|
var pcon_map = std.StringHashMap([]const u8).init(self.alloc);
|
||||||
|
defer pcon_map.deinit();
|
||||||
|
if (pack_protocol) |proto| pcon_map.put(pack_name, proto) catch {};
|
||||||
|
const saved_pat = self.pack_arg_types;
|
||||||
|
const saved_pcon = self.pack_constraint;
|
||||||
|
self.pack_arg_types = pat_map;
|
||||||
|
if (pack_protocol != null) self.pack_constraint = pcon_map;
|
||||||
|
|
||||||
var args = std.ArrayList(Ref).empty;
|
var args = std.ArrayList(Ref).empty;
|
||||||
defer args.deinit(self.alloc);
|
defer args.deinit(self.alloc);
|
||||||
{
|
{
|
||||||
@@ -9206,25 +9238,37 @@ pub const Lowering = struct {
|
|||||||
ri += 1;
|
ri += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (call_node.args[pack_start..]) |a| {
|
self.pack_arg_types = saved_pat;
|
||||||
if (pack_is_comptime) {
|
self.pack_constraint = saved_pcon;
|
||||||
// A comptime `..$args` arg's intended type follows the
|
|
||||||
// language default (e.g. an int literal is s64) which
|
// Infer type-param bindings (e.g. `$R` in `mapper: Closure(..) -> $R`)
|
||||||
// `inferExprType` encodes; the lowered value may be narrower
|
// from the lowered prefix args. `args.items` holds the non-comptime
|
||||||
// (s32). Prefer inference; fall back to the lowered value's
|
// prefix refs in declaration order; match each prefix param's declared
|
||||||
// type only when inference genuinely can't tell.
|
// type against its arg's concrete type to bind the function's
|
||||||
const r = self.lowerExpr(a);
|
// type-params. These flow into the mangle and the mono's
|
||||||
args.append(self.alloc, r) catch return self.builder.constInt(0, .void);
|
// `self.type_bindings` so `-> VL($R)` / `Combined($R, ..)` resolve.
|
||||||
const it = self.inferExprType(a);
|
var tparam_bindings = std.StringHashMap(TypeId).init(self.alloc);
|
||||||
const ty = if (it == .unresolved) self.builder.getRefType(r) else it;
|
defer tparam_bindings.deinit();
|
||||||
pack_arg_types.append(self.alloc, ty) catch return self.builder.constInt(0, .void);
|
if (fd.type_params.len > 0) {
|
||||||
} else {
|
var pref_ref_idx: usize = 0;
|
||||||
const r = self.lowerExpr(a);
|
for (fd.params) |p| {
|
||||||
args.append(self.alloc, r) catch return self.builder.constInt(0, .void);
|
if (isPackParam(p)) break;
|
||||||
pack_arg_types.append(self.alloc, self.builder.getRefType(r)) catch return self.builder.constInt(0, .void);
|
if (p.is_comptime) continue;
|
||||||
|
if (pref_ref_idx >= args.items.len) break;
|
||||||
|
const arg_ty = self.builder.getRefType(args.items[pref_ref_idx]);
|
||||||
|
for (fd.type_params) |tp| {
|
||||||
|
if (tparam_bindings.contains(tp.name)) continue;
|
||||||
|
if (self.extractTypeParam(p.type_expr, arg_ty, tp.name)) |ety| {
|
||||||
|
if (ety != .unresolved) tparam_bindings.put(tp.name, ety) catch {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pref_ref_idx += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Append the (already-lowered) pack args after the prefix args.
|
||||||
|
for (pack_refs.items) |r| args.append(self.alloc, r) catch return self.builder.constInt(0, .void);
|
||||||
|
|
||||||
// Per-position conformance: each pack arg must impl the constraint
|
// Per-position conformance: each pack arg must impl the constraint
|
||||||
// protocol. Only enforced for a known protocol constraint — an unknown
|
// protocol. Only enforced for a known protocol constraint — an unknown
|
||||||
// name (e.g. a plain type used as a pack constraint) is left alone.
|
// name (e.g. a plain type used as a pack constraint) is left alone.
|
||||||
@@ -9258,6 +9302,13 @@ pub const Lowering = struct {
|
|||||||
}
|
}
|
||||||
ct_fi += 1;
|
ct_fi += 1;
|
||||||
}
|
}
|
||||||
|
// Inferred type-param bindings (deterministic by fd.type_params order).
|
||||||
|
for (fd.type_params) |tp| {
|
||||||
|
if (tparam_bindings.get(tp.name)) |ty| {
|
||||||
|
name_buf.appendSlice(self.alloc, "__tp_") catch return self.builder.constInt(0, .void);
|
||||||
|
name_buf.appendSlice(self.alloc, self.mangleTypeName(ty)) catch return self.builder.constInt(0, .void);
|
||||||
|
}
|
||||||
|
}
|
||||||
name_buf.appendSlice(self.alloc, "__pack") catch return self.builder.constInt(0, .void);
|
name_buf.appendSlice(self.alloc, "__pack") catch return self.builder.constInt(0, .void);
|
||||||
for (pack_arg_types.items) |t| {
|
for (pack_arg_types.items) |t| {
|
||||||
name_buf.append(self.alloc, '_') catch return self.builder.constInt(0, .void);
|
name_buf.append(self.alloc, '_') catch return self.builder.constInt(0, .void);
|
||||||
@@ -9266,7 +9317,7 @@ pub const Lowering = struct {
|
|||||||
const mangled = name_buf.items;
|
const mangled = name_buf.items;
|
||||||
|
|
||||||
if (!self.lowered_functions.contains(mangled)) {
|
if (!self.lowered_functions.contains(mangled)) {
|
||||||
self.monomorphizePackFn(fd, mangled, pack_arg_types.items, call_node);
|
self.monomorphizePackFn(fd, mangled, pack_arg_types.items, call_node, &tparam_bindings);
|
||||||
}
|
}
|
||||||
|
|
||||||
const fid = self.resolveFuncByName(mangled) orelse return self.builder.constInt(0, .void);
|
const fid = self.resolveFuncByName(mangled) orelse return self.builder.constInt(0, .void);
|
||||||
@@ -9325,6 +9376,7 @@ pub const Lowering = struct {
|
|||||||
mangled_name: []const u8,
|
mangled_name: []const u8,
|
||||||
arg_types: []const TypeId,
|
arg_types: []const TypeId,
|
||||||
call_node: *const ast.Call,
|
call_node: *const ast.Call,
|
||||||
|
type_bindings: *const std.StringHashMap(TypeId),
|
||||||
) void {
|
) void {
|
||||||
const owned_name = self.alloc.dupe(u8, mangled_name) catch return;
|
const owned_name = self.alloc.dupe(u8, mangled_name) catch return;
|
||||||
self.lowered_functions.put(owned_name, {}) catch {};
|
self.lowered_functions.put(owned_name, {}) catch {};
|
||||||
@@ -9362,10 +9414,16 @@ pub const Lowering = struct {
|
|||||||
const saved_pcon = self.pack_constraint;
|
const saved_pcon = self.pack_constraint;
|
||||||
const saved_iri = self.inline_return_target;
|
const saved_iri = self.inline_return_target;
|
||||||
const saved_ctx_ref = self.current_ctx_ref;
|
const saved_ctx_ref = self.current_ctx_ref;
|
||||||
|
const saved_type_bindings = self.type_bindings;
|
||||||
self.func_defer_base = self.defer_stack.items.len;
|
self.func_defer_base = self.defer_stack.items.len;
|
||||||
self.block_terminated = false;
|
self.block_terminated = false;
|
||||||
self.inline_return_target = null;
|
self.inline_return_target = null;
|
||||||
|
// Generic type-params inferred at the call site (e.g. `$R` from the
|
||||||
|
// mapper's closure return). Installed for the whole mono so
|
||||||
|
// return-type resolution and body lowering substitute them.
|
||||||
|
self.type_bindings = type_bindings.*;
|
||||||
defer {
|
defer {
|
||||||
|
self.type_bindings = saved_type_bindings;
|
||||||
self.scope = saved_scope;
|
self.scope = saved_scope;
|
||||||
self.func_defer_base = saved_defer_base;
|
self.func_defer_base = saved_defer_base;
|
||||||
self.block_terminated = saved_block_terminated;
|
self.block_terminated = saved_block_terminated;
|
||||||
@@ -10187,6 +10245,11 @@ pub const Lowering = struct {
|
|||||||
.many_pointer_type_expr => |mp| matchTypeParamStatic(mp.element_type, tp_name),
|
.many_pointer_type_expr => |mp| matchTypeParamStatic(mp.element_type, tp_name),
|
||||||
.optional_type_expr => |ot| matchTypeParamStatic(ot.inner_type, tp_name),
|
.optional_type_expr => |ot| matchTypeParamStatic(ot.inner_type, tp_name),
|
||||||
.array_type_expr => |at| matchTypeParamStatic(at.element_type, tp_name),
|
.array_type_expr => |at| matchTypeParamStatic(at.element_type, tp_name),
|
||||||
|
.closure_type_expr => |ct| blk: {
|
||||||
|
for (ct.param_types) |pt| if (matchTypeParamStatic(pt, tp_name)) break :blk true;
|
||||||
|
if (ct.return_type) |rt| if (matchTypeParamStatic(rt, tp_name)) break :blk true;
|
||||||
|
break :blk false;
|
||||||
|
},
|
||||||
else => false,
|
else => false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -10200,6 +10263,11 @@ pub const Lowering = struct {
|
|||||||
.many_pointer_type_expr => |mp| matchTypeParamStatic(mp.element_type, tp_name),
|
.many_pointer_type_expr => |mp| matchTypeParamStatic(mp.element_type, tp_name),
|
||||||
.optional_type_expr => |ot| matchTypeParamStatic(ot.inner_type, tp_name),
|
.optional_type_expr => |ot| matchTypeParamStatic(ot.inner_type, tp_name),
|
||||||
.array_type_expr => |at| matchTypeParamStatic(at.element_type, tp_name),
|
.array_type_expr => |at| matchTypeParamStatic(at.element_type, tp_name),
|
||||||
|
.closure_type_expr => |ct| blk: {
|
||||||
|
for (ct.param_types) |pt| if (matchTypeParamStatic(pt, tp_name)) break :blk true;
|
||||||
|
if (ct.return_type) |rt| if (matchTypeParamStatic(rt, tp_name)) break :blk true;
|
||||||
|
break :blk false;
|
||||||
|
},
|
||||||
else => false,
|
else => false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -10251,6 +10319,24 @@ pub const Lowering = struct {
|
|||||||
else => null,
|
else => null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
.closure_type_expr => |ct| blk: {
|
||||||
|
if (arg_ty.isBuiltin()) break :blk null;
|
||||||
|
const info = self.module.types.get(arg_ty);
|
||||||
|
const c_params: []const TypeId, const c_ret: TypeId = switch (info) {
|
||||||
|
.closure => |c| .{ c.params, c.ret },
|
||||||
|
.function => |f| .{ f.params, f.ret },
|
||||||
|
else => break :blk null,
|
||||||
|
};
|
||||||
|
// Prefer the return position (`Closure(...) -> $R`), then params.
|
||||||
|
if (ct.return_type) |rt| {
|
||||||
|
if (self.extractTypeParam(rt, c_ret, tp_name)) |ety| break :blk ety;
|
||||||
|
}
|
||||||
|
for (ct.param_types, 0..) |pt, i| {
|
||||||
|
if (i >= c_params.len) break;
|
||||||
|
if (self.extractTypeParam(pt, c_params[i], tp_name)) |ety| break :blk ety;
|
||||||
|
}
|
||||||
|
break :blk null;
|
||||||
|
},
|
||||||
else => null,
|
else => null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -11209,6 +11295,13 @@ pub const Lowering = struct {
|
|||||||
},
|
},
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
|
// An unbound generic type param (`$R` with no active binding) must not
|
||||||
|
// fabricate an empty-struct stub — that surfaces as `R{}` downstream.
|
||||||
|
// Return `.unresolved` so callers (e.g. lambda return-type inference,
|
||||||
|
// call-site `$R` inference) treat it as not-yet-known.
|
||||||
|
if (node.data == .type_expr and node.data.type_expr.is_generic) {
|
||||||
|
return .unresolved;
|
||||||
|
}
|
||||||
// Alias resolution (`ShaderHandle :: u32`, `Vec4 ::
|
// Alias resolution (`ShaderHandle :: u32`, `Vec4 ::
|
||||||
// Vector(4,f32)`) is now handled inside `resolveTypeName`
|
// Vector(4,f32)`) is now handled inside `resolveTypeName`
|
||||||
// via the `TypeTable.aliases` borrow loaned at lowerRoot.
|
// via the `TypeTable.aliases` borrow loaned at lowerRoot.
|
||||||
@@ -12657,12 +12750,19 @@ pub const Lowering = struct {
|
|||||||
const si = table.get(src_ty);
|
const si = table.get(src_ty);
|
||||||
if (!src_ty.isBuiltin() and si == .@"struct") {
|
if (!src_ty.isBuiltin() and si == .@"struct") {
|
||||||
const src_name = self.formatTypeName(src_ty);
|
const src_name = self.formatTypeName(src_ty);
|
||||||
|
// A generic-struct source (`impl VL($R) for Combined($R, ..$Ts)`)
|
||||||
|
// registers each method as a TEMPLATE only: its signature
|
||||||
|
// references unbound type params (`-> $R`), so declaring it as a
|
||||||
|
// standalone function would emit garbage (an unresolved return
|
||||||
|
// type). Concrete instances are monomorphized per-erasure by
|
||||||
|
// createProtocolThunk via this same fn_ast_map entry.
|
||||||
|
const is_generic_src = self.struct_template_map.contains(src_name);
|
||||||
for (methods.items) |mfd| {
|
for (methods.items) |mfd| {
|
||||||
const q = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ src_name, mfd.name }) catch continue;
|
const q = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ src_name, mfd.name }) catch continue;
|
||||||
if (self.fn_ast_map.contains(q)) continue; // first impl wins
|
if (self.fn_ast_map.contains(q)) continue; // first impl wins
|
||||||
self.fn_ast_map.put(q, mfd) catch {};
|
self.fn_ast_map.put(q, mfd) catch {};
|
||||||
self.import_flags.put(q, is_imported) catch {};
|
self.import_flags.put(q, is_imported) catch {};
|
||||||
self.declareFunction(mfd, q);
|
if (!is_generic_src) self.declareFunction(mfd, q);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1742,6 +1742,10 @@ pub const Parser = struct {
|
|||||||
.parameterized_type_expr => |pte| {
|
.parameterized_type_expr => |pte| {
|
||||||
for (pte.args) |arg| collectGenericNames(arg, list, allocator);
|
for (pte.args) |arg| collectGenericNames(arg, list, allocator);
|
||||||
},
|
},
|
||||||
|
.closure_type_expr => |cte| {
|
||||||
|
for (cte.param_types) |pt| collectGenericNames(pt, list, allocator);
|
||||||
|
if (cte.return_type) |rt| collectGenericNames(rt, list, allocator);
|
||||||
|
},
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
tests/expected/213-canonical-map.exit
Normal file
1
tests/expected/213-canonical-map.exit
Normal file
@@ -0,0 +1 @@
|
|||||||
|
0
|
||||||
1
tests/expected/213-canonical-map.txt
Normal file
1
tests/expected/213-canonical-map.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
42
|
||||||
Reference in New Issue
Block a user