fix(stdlib/E4): gate unqualified parameterized type heads non-transitively
attempt-3: extend the E4 single-hop bare-TYPE gate to parameterized type
HEADS (the constructor-head analog of the bare-leaf gate). Before this, the
head lookup hit the global struct_template_map / protocol_ast_map /
fn_ast_map *before* any source-aware visibility check, so a 2-flat-hop
imported generic struct/protocol/type-fn remained bare-visible (e.g.
`Box(s64)` when main imports only b.sx and b.sx imports c.sx).
- headTypeLeak: generic-struct / parameterized-protocol heads use the same
type-author single-hop model as the bare-leaf gate (moduleTypeAuthor +
flatTypeAuthorCount + localTypeInSource + nameAuthoredAsTypeAnywhere).
- headFnLeak: type-returning-function heads use single-hop function
visibility (isNameVisible), exempting scope-local mangled type-fns.
- Gated at every unqualified head site: resolveParameterizedWithBindings,
resolveTypeCallWithBindings, the scanDecls alias-decl dispatch (poisoning
the alias with .unresolved on leak), resolveArrayLiteralType, and the
generic-static-method call path. Namespaced (`ns.Box(..)`) heads are an
explicit qualified reach and stay exempt. Source-pinned instantiation
(E3/E4) is preserved, so library-internal heads still resolve where they
are visible.
Regression: examples/0764-modules-import-generic-head-non-transitive
(2-hop `Box(s64)` -> "type 'Box' is not visible", exit 1; direct #import
resolves). Fails-before on a250964 (printed 3), passes-after.
README: note the non-transitive rule covers parameterized type heads.
Gate: zig build 0, zig build test 0 (LSP 522, 423/423), run_examples
505/0, FFI 12xx/13xx/14xx green, 0706/0763/0544/0105 green & byte-identical,
m3te ios-sim build+launch exit 0.
This commit is contained in:
24
examples/0764-modules-import-generic-head-non-transitive.sx
Normal file
24
examples/0764-modules-import-generic-head-non-transitive.sx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
// `#import` is non-transitive for a PARAMETERIZED TYPE HEAD (a generic-struct
|
||||||
|
// constructor like `Box(s64)`), exactly like a bare leaf type (0763) and like
|
||||||
|
// values/functions (0706): when A imports B and B imports C, A must NOT see C's
|
||||||
|
// top-level generic type `Box`. This file imports `b.sx` (which imports `c.sx`)
|
||||||
|
// and instantiates C's generic `Box(s64)` directly — the compiler rejects the
|
||||||
|
// head with a "type ... is not visible; #import the module that declares it"
|
||||||
|
// diagnostic, BEFORE instantiating the template.
|
||||||
|
//
|
||||||
|
// `b.sx` ↔ `c.sx` together still compile: `b_make`'s `Box(s64)` resolves because
|
||||||
|
// b.sx directly imports c.sx (the head is one flat hop away there, two from a
|
||||||
|
// file that imports b.sx).
|
||||||
|
//
|
||||||
|
// Regression (Phase E4): before the bare-head gate went single-hop this 2-flat-
|
||||||
|
// hop generic head was wrongly visible — the head lookup hit the global
|
||||||
|
// `struct_template_map` before any source-aware visibility check.
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
#import "0764-modules-import-generic-head-non-transitive/b.sx";
|
||||||
|
|
||||||
|
main :: () -> s32 {
|
||||||
|
x : Box(s64) = .{ v = 3 };
|
||||||
|
print("{}\n", x.v);
|
||||||
|
0
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#import "c.sx";
|
||||||
|
|
||||||
|
// b.sx directly imports c.sx, so it CAN instantiate `Box(s64)` — proving the
|
||||||
|
// generic head is only one flat hop away here, two hops from a file that
|
||||||
|
// imports b.sx.
|
||||||
|
b_make :: () -> Box(s64) {
|
||||||
|
.{ v = 99 }
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Box :: struct($T: Type) { v: T; }
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
1
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
error: type 'Box' is not visible; #import the module that declares it
|
||||||
|
--> /Users/agra/projects/sx/examples/0764-modules-import-generic-head-non-transitive.sx:21:9
|
||||||
|
|
|
||||||
|
21 | x : Box(s64) = .{ v = 3 };
|
||||||
|
| ^^^^^^^^
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
11
readme.md
11
readme.md
@@ -405,10 +405,13 @@ a namespaced alias. That join is **non-transitive for every bare member kind —
|
|||||||
functions, constants, AND types alike**: a flat import of a flat import is NOT
|
functions, constants, AND types alike**: a flat import of a flat import is NOT
|
||||||
bare-visible (when `A` imports `B` and `B` imports `C`, `A` does not see `C`'s
|
bare-visible (when `A` imports `B` and `B` imports `C`, `A` does not see `C`'s
|
||||||
top-level names — including its types — so qualify them, or `#import "C"` directly
|
top-level names — including its types — so qualify them, or `#import "C"` directly
|
||||||
if you reference them). A bare reference to a namespaced-only import's member —
|
if you reference them). This holds for a *parameterized* type head too: a generic
|
||||||
function, module constant, or **type** — is likewise not visible and is rejected
|
struct / parameterized protocol / type-returning function used as `Box(s64)` is
|
||||||
(`type 'X' is not visible; #import the module that declares it`); qualify it as
|
gated exactly like a bare leaf type — the constructor head must be reachable over
|
||||||
`m.name`. (A library's own *internal* type references still resolve: a generic
|
your own or a direct flat import, not two hops away. A bare reference to a
|
||||||
|
namespaced-only import's member — function, module constant, or **type** (leaf or
|
||||||
|
generic head) — is likewise not visible and is rejected (`type 'X' is not visible;
|
||||||
|
#import the module that declares it`); qualify it as `m.name`. (A library's own *internal* type references still resolve: a generic
|
||||||
struct / pack fn / protocol body is instantiated in the module that defines it, so
|
struct / pack fn / protocol body is instantiated in the module that defines it, so
|
||||||
e.g. `List(T).append`'s `alloc: Allocator` is visible there regardless of the call
|
e.g. `List(T).append`'s `alloc: Allocator` is visible there regardless of the call
|
||||||
site.)
|
site.)
|
||||||
|
|||||||
@@ -1019,8 +1019,19 @@ pub const Lowering = struct {
|
|||||||
.field_access => |fa| fa.field,
|
.field_access => |fa| fa.field,
|
||||||
else => "",
|
else => "",
|
||||||
};
|
};
|
||||||
|
// A namespaced callee (`ns.Box(..)`) is an explicit qualified
|
||||||
|
// reach, exempt from the bare-head visibility gate (E4).
|
||||||
|
const head_qualified = call_data.callee.data == .field_access;
|
||||||
if (callee_name.len > 0) {
|
if (callee_name.len > 0) {
|
||||||
if (self.program_index.struct_template_map.getPtr(callee_name)) |tmpl| {
|
if (self.program_index.struct_template_map.getPtr(callee_name)) |tmpl| reg: {
|
||||||
|
// 2-hop generic-struct head leak: poison the alias with
|
||||||
|
// `.unresolved` (suppressed downstream) so the use site
|
||||||
|
// sees no fabricated-stub cascade, only the loud
|
||||||
|
// "not visible" diagnostic the gate already emitted.
|
||||||
|
if (!head_qualified and self.headTypeLeak(callee_name, call_data.callee.span)) {
|
||||||
|
self.putTypeAlias(self.current_source_file, cd.name, .unresolved);
|
||||||
|
break :reg;
|
||||||
|
}
|
||||||
const inst_id = self.instantiateGenericStruct(tmpl, call_data.args);
|
const inst_id = self.instantiateGenericStruct(tmpl, call_data.args);
|
||||||
// Register under the alias name
|
// Register under the alias name
|
||||||
const alias_name_id = self.module.types.internString(cd.name);
|
const alias_name_id = self.module.types.internString(cd.name);
|
||||||
@@ -1055,7 +1066,9 @@ pub const Lowering = struct {
|
|||||||
} else if (self.program_index.fn_ast_map.get(callee_name)) |fd| {
|
} else if (self.program_index.fn_ast_map.get(callee_name)) |fd| {
|
||||||
// Type-returning function: Foo :: Complex(u32)
|
// Type-returning function: Foo :: Complex(u32)
|
||||||
if (fd.type_params.len > 0) {
|
if (fd.type_params.len > 0) {
|
||||||
if (self.instantiateTypeFunction(cd.name, callee_name, fd, call_data.args)) |result_ty| {
|
if (!head_qualified and self.headFnLeak(callee_name, call_data.callee.span)) {
|
||||||
|
self.putTypeAlias(self.current_source_file, cd.name, .unresolved);
|
||||||
|
} else if (self.instantiateTypeFunction(cd.name, callee_name, fd, call_data.args)) |result_ty| {
|
||||||
self.putTypeAlias(self.current_source_file, cd.name, result_ty);
|
self.putTypeAlias(self.current_source_file, cd.name, result_ty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1065,7 +1078,12 @@ pub const Lowering = struct {
|
|||||||
// Type alias for generic struct (from type_bridge path)
|
// Type alias for generic struct (from type_bridge path)
|
||||||
const pt = &cd.value.data.parameterized_type_expr;
|
const pt = &cd.value.data.parameterized_type_expr;
|
||||||
const base_name = if (std.mem.lastIndexOfScalar(u8, pt.name, '.')) |dot| pt.name[dot + 1 ..] else pt.name;
|
const base_name = if (std.mem.lastIndexOfScalar(u8, pt.name, '.')) |dot| pt.name[dot + 1 ..] else pt.name;
|
||||||
if (self.program_index.struct_template_map.getPtr(base_name)) |tmpl| {
|
const pt_qualified = std.mem.indexOfScalar(u8, pt.name, '.') != null;
|
||||||
|
if (self.program_index.struct_template_map.getPtr(base_name)) |tmpl| reg: {
|
||||||
|
if (!pt_qualified and self.headTypeLeak(base_name, cd.value.span)) {
|
||||||
|
self.putTypeAlias(self.current_source_file, cd.name, .unresolved);
|
||||||
|
break :reg;
|
||||||
|
}
|
||||||
const inst_id = self.instantiateGenericStruct(tmpl, pt.args);
|
const inst_id = self.instantiateGenericStruct(tmpl, pt.args);
|
||||||
const alias_name_id = self.module.types.internString(cd.name);
|
const alias_name_id = self.module.types.internString(cd.name);
|
||||||
const inst_info = self.module.types.get(inst_id);
|
const inst_info = self.module.types.get(inst_id);
|
||||||
@@ -6837,6 +6855,7 @@ pub const Lowering = struct {
|
|||||||
}
|
}
|
||||||
// Try as generic struct
|
// Try as generic struct
|
||||||
if (self.program_index.struct_template_map.getPtr(callee_name)) |tmpl| {
|
if (self.program_index.struct_template_map.getPtr(callee_name)) |tmpl| {
|
||||||
|
if (cl.callee.data != .field_access and self.headTypeLeak(callee_name, cl.callee.span)) return .unresolved;
|
||||||
return self.instantiateGenericStruct(tmpl, cl.args);
|
return self.instantiateGenericStruct(tmpl, cl.args);
|
||||||
}
|
}
|
||||||
return .unresolved;
|
return .unresolved;
|
||||||
@@ -8575,8 +8594,11 @@ pub const Lowering = struct {
|
|||||||
const inner_name = inner_call.callee.data.identifier.name;
|
const inner_name = inner_call.callee.data.identifier.name;
|
||||||
const resolved = if (self.scope) |scope| (scope.lookupFn(inner_name) orelse inner_name) else inner_name;
|
const resolved = if (self.scope) |scope| (scope.lookupFn(inner_name) orelse inner_name) else inner_name;
|
||||||
|
|
||||||
// Generic struct static method: Animated(Size).make(...)
|
// Generic struct static method: Animated(Size).make(...).
|
||||||
|
// `inner_call.callee` is an identifier here (guarded above), so
|
||||||
|
// the head is unqualified and subject to the bare-head gate (E4).
|
||||||
if (self.program_index.struct_template_map.getPtr(resolved)) |tmpl| {
|
if (self.program_index.struct_template_map.getPtr(resolved)) |tmpl| {
|
||||||
|
if (self.headTypeLeak(inner_name, inner_call.callee.span)) return Ref.none;
|
||||||
const inst_ty = self.instantiateGenericStruct(tmpl, inner_call.args);
|
const inst_ty = self.instantiateGenericStruct(tmpl, inner_call.args);
|
||||||
const inst_name = self.formatTypeName(inst_ty);
|
const inst_name = self.formatTypeName(inst_ty);
|
||||||
// Look up template method, monomorphize, and call
|
// Look up template method, monomorphize, and call
|
||||||
@@ -8601,6 +8623,7 @@ pub const Lowering = struct {
|
|||||||
|
|
||||||
if (self.program_index.fn_ast_map.get(resolved)) |fd| {
|
if (self.program_index.fn_ast_map.get(resolved)) |fd| {
|
||||||
if (fd.type_params.len > 0) {
|
if (fd.type_params.len > 0) {
|
||||||
|
if (self.headFnLeak(inner_name, inner_call.callee.span)) return Ref.none;
|
||||||
// Try instantiate as type function
|
// Try instantiate as type function
|
||||||
if (self.instantiateTypeFunction(inner_name, inner_name, fd, inner_call.args)) |result_ty| {
|
if (self.instantiateTypeFunction(inner_name, inner_name, fd, inner_call.args)) |result_ty| {
|
||||||
const type_info = self.module.types.get(result_ty);
|
const type_info = self.module.types.get(result_ty);
|
||||||
@@ -13821,8 +13844,65 @@ pub const Lowering = struct {
|
|||||||
d.addFmt(.err, arg_node.span, "value {} does not fit in {s} parameter {s}", .{ value, type_name, param_name });
|
d.addFmt(.err, arg_node.span, "value {} does not fit in {s} parameter {s}", .{ value, type_name, param_name });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Single-hop non-transitive visibility gate for an UNQUALIFIED parameterized
|
||||||
|
/// type HEAD that names a generic STRUCT or a parameterized PROTOCOL
|
||||||
|
/// (`Box(s64)`, `VL(s64)`) — the constructor-head analog of the bare-leaf
|
||||||
|
/// type gate (E4). A head is visible iff a TYPE author for `name` is reachable
|
||||||
|
/// from the USE site over its OWN declaration or a DIRECT flat-import edge —
|
||||||
|
/// the SAME single-hop set the bare leaf / value / fn leaves use (0706), NOT
|
||||||
|
/// the transitive closure. Emits the leak diagnostic + returns TRUE when the
|
||||||
|
/// head is a real type author somewhere but NOT reachable here (a 2-flat-hop
|
||||||
|
/// leak), so the caller poisons with `.unresolved`. Falls open (FALSE, no
|
||||||
|
/// diagnostic) when import facts are unwired (registration / comptime — no
|
||||||
|
/// querying module), the source context is absent, or the compiler-synthesized
|
||||||
|
/// default-Context emitter is running (built-in infrastructure resolves
|
||||||
|
/// independent of the user program's import style, F1). A block-local generic
|
||||||
|
/// of THIS source is visible in its own scope. Library-internal heads stay
|
||||||
|
/// visible because every instantiation kind is source-pinned to the template's
|
||||||
|
/// defining module (E3/E4 #1): the query originates THERE, where the head is a
|
||||||
|
/// direct flat import — not at the cross-module call site. Only the bare
|
||||||
|
/// (identifier-callee / dotless) form is gated; a namespaced `ns.Box(..)` head
|
||||||
|
/// is an explicit qualified reach and is exempt (the caller skips this gate).
|
||||||
|
fn headTypeLeak(self: *Lowering, name: []const u8, span: ?ast.Span) bool {
|
||||||
|
if (self.emitting_default_context) return false;
|
||||||
|
if (self.program_index.module_decls == null or self.program_index.flat_import_graph == null) return false;
|
||||||
|
const from = self.current_source_file orelse return false;
|
||||||
|
// Reachable as a TYPE author over own / direct-flat edges → visible.
|
||||||
|
if (self.moduleTypeAuthor(from, name) != null) return false;
|
||||||
|
if (self.flatTypeAuthorCount(name, from) != .none) return false;
|
||||||
|
// A block-local generic declared in THIS source is visible here.
|
||||||
|
if (self.localTypeInSource(from, name)) return false;
|
||||||
|
// Authored as a TYPE somewhere but unreachable from `from` → a leak.
|
||||||
|
if (!self.nameAuthoredAsTypeAnywhere(name)) return false;
|
||||||
|
if (self.diagnostics) |d|
|
||||||
|
d.addFmt(.err, span, "type '{s}' is not visible; #import the module that declares it", .{name});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Single-hop non-transitive visibility gate for an UNQUALIFIED type-returning
|
||||||
|
/// FUNCTION head used as a type (`Make(N, T)` where `Make :: ($K, $T) -> Type`).
|
||||||
|
/// A type-fn is a `fn_decl`, so its visibility is FUNCTION visibility
|
||||||
|
/// (`isNameVisible`, the single-hop flat name set) — NOT the type-author model.
|
||||||
|
/// Emits + returns TRUE when the fn is authored somewhere but not reachable
|
||||||
|
/// from the use site (a 2-flat-hop leak). A scope-local (mangled) type-fn is
|
||||||
|
/// visible in its own scope and exempt; falls open when unwired / default-
|
||||||
|
/// context. Diagnostic mirrors the type form (the head IS used as a type here).
|
||||||
|
fn headFnLeak(self: *Lowering, name: []const u8, span: ?ast.Span) bool {
|
||||||
|
if (self.emitting_default_context) return false;
|
||||||
|
if (self.current_source_file == null) return false;
|
||||||
|
if (self.scope) |s| if (s.lookupFn(name) != null) return false;
|
||||||
|
if (self.isNameVisible(name)) return false;
|
||||||
|
if (self.diagnostics) |d|
|
||||||
|
d.addFmt(.err, span, "type '{s}' is not visible; #import the module that declares it", .{name});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// Resolve a .call node that represents a type constructor (e.g., List(T), Vector(N, T)).
|
/// Resolve a .call node that represents a type constructor (e.g., List(T), Vector(N, T)).
|
||||||
fn resolveTypeCallWithBindings(self: *Lowering, cl: *const ast.Call) TypeId {
|
fn resolveTypeCallWithBindings(self: *Lowering, cl: *const ast.Call) TypeId {
|
||||||
|
// A namespaced callee (`ns.Box(..)`) is an explicit qualified reach and is
|
||||||
|
// exempt from the bare-head visibility gate; only a plain identifier head
|
||||||
|
// is policed (E4).
|
||||||
|
const is_qualified = cl.callee.data == .field_access;
|
||||||
const callee_name: []const u8 = switch (cl.callee.data) {
|
const callee_name: []const u8 = switch (cl.callee.data) {
|
||||||
.identifier => |id| id.name,
|
.identifier => |id| id.name,
|
||||||
.field_access => |fa| fa.field,
|
.field_access => |fa| fa.field,
|
||||||
@@ -13836,6 +13916,7 @@ pub const Lowering = struct {
|
|||||||
}
|
}
|
||||||
// User-defined generic struct
|
// User-defined generic struct
|
||||||
if (self.program_index.struct_template_map.getPtr(callee_name)) |tmpl| {
|
if (self.program_index.struct_template_map.getPtr(callee_name)) |tmpl| {
|
||||||
|
if (!is_qualified and self.headTypeLeak(callee_name, cl.callee.span)) return .unresolved;
|
||||||
return self.instantiateGenericStruct(tmpl, cl.args);
|
return self.instantiateGenericStruct(tmpl, cl.args);
|
||||||
}
|
}
|
||||||
// User-defined type-returning function: Complex(u32), Sx(f32)
|
// User-defined type-returning function: Complex(u32), Sx(f32)
|
||||||
@@ -13843,6 +13924,7 @@ pub const Lowering = struct {
|
|||||||
const resolved_name = if (self.scope) |scope| (scope.lookupFn(callee_name) orelse callee_name) else callee_name;
|
const resolved_name = if (self.scope) |scope| (scope.lookupFn(callee_name) orelse callee_name) else callee_name;
|
||||||
if (self.program_index.fn_ast_map.get(resolved_name)) |fd| {
|
if (self.program_index.fn_ast_map.get(resolved_name)) |fd| {
|
||||||
if (fd.type_params.len > 0) {
|
if (fd.type_params.len > 0) {
|
||||||
|
if (!is_qualified and self.headFnLeak(callee_name, cl.callee.span)) return .unresolved;
|
||||||
if (self.instantiateTypeFunction(callee_name, callee_name, fd, cl.args)) |ty| {
|
if (self.instantiateTypeFunction(callee_name, callee_name, fd, cl.args)) |ty| {
|
||||||
return ty;
|
return ty;
|
||||||
}
|
}
|
||||||
@@ -13859,6 +13941,10 @@ pub const Lowering = struct {
|
|||||||
fn resolveParameterizedWithBindings(self: *Lowering, pt: *const ast.ParameterizedTypeExpr, span: ?ast.Span) TypeId {
|
fn resolveParameterizedWithBindings(self: *Lowering, pt: *const ast.ParameterizedTypeExpr, span: ?ast.Span) TypeId {
|
||||||
const base_name = if (std.mem.lastIndexOfScalar(u8, pt.name, '.')) |dot| pt.name[dot + 1 ..] else pt.name;
|
const base_name = if (std.mem.lastIndexOfScalar(u8, pt.name, '.')) |dot| pt.name[dot + 1 ..] else pt.name;
|
||||||
const table = &self.module.types;
|
const table = &self.module.types;
|
||||||
|
// A namespaced base (`ns.Box(..)`) is an explicit qualified reach and is
|
||||||
|
// exempt from the bare-head visibility gate; only a dotless head is
|
||||||
|
// policed (E4).
|
||||||
|
const is_qualified = std.mem.indexOfScalar(u8, pt.name, '.') != null;
|
||||||
|
|
||||||
// Vector(N, T) — built-in parameterized type. A backtick raw base
|
// Vector(N, T) — built-in parameterized type. A backtick raw base
|
||||||
// (`` `Vector(…) ``) is the LITERAL user type named `Vector`, so it
|
// (`` `Vector(…) ``) is the LITERAL user type named `Vector`, so it
|
||||||
@@ -13873,6 +13959,7 @@ pub const Lowering = struct {
|
|||||||
|
|
||||||
// User-defined generic struct: look up template and instantiate
|
// User-defined generic struct: look up template and instantiate
|
||||||
if (self.program_index.struct_template_map.getPtr(base_name)) |tmpl| {
|
if (self.program_index.struct_template_map.getPtr(base_name)) |tmpl| {
|
||||||
|
if (!is_qualified and self.headTypeLeak(base_name, span)) return .unresolved;
|
||||||
return self.instantiateGenericStruct(tmpl, pt.args);
|
return self.instantiateGenericStruct(tmpl, pt.args);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -13880,6 +13967,7 @@ pub const Lowering = struct {
|
|||||||
// 16-byte protocol value with the type-arg bound (not a 0-field stub).
|
// 16-byte protocol value with the type-arg bound (not a 0-field stub).
|
||||||
if (self.program_index.protocol_ast_map.get(base_name)) |pd| {
|
if (self.program_index.protocol_ast_map.get(base_name)) |pd| {
|
||||||
if (pd.type_params.len > 0) {
|
if (pd.type_params.len > 0) {
|
||||||
|
if (!is_qualified and self.headTypeLeak(base_name, span)) return .unresolved;
|
||||||
return self.instantiateParamProtocol(pd, pt.args);
|
return self.instantiateParamProtocol(pd, pt.args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13892,6 +13980,7 @@ pub const Lowering = struct {
|
|||||||
const resolved_name = if (self.scope) |scope| (scope.lookupFn(base_name) orelse base_name) else base_name;
|
const resolved_name = if (self.scope) |scope| (scope.lookupFn(base_name) orelse base_name) else base_name;
|
||||||
if (self.program_index.fn_ast_map.get(resolved_name)) |fd| {
|
if (self.program_index.fn_ast_map.get(resolved_name)) |fd| {
|
||||||
if (fd.type_params.len > 0) {
|
if (fd.type_params.len > 0) {
|
||||||
|
if (!is_qualified and self.headFnLeak(base_name, span)) return .unresolved;
|
||||||
if (self.instantiateTypeFunction(base_name, base_name, fd, pt.args)) |ty| {
|
if (self.instantiateTypeFunction(base_name, base_name, fd, pt.args)) |ty| {
|
||||||
return ty;
|
return ty;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user