fix(stdlib/E4): carry full author outcome through the bare-TYPE gate (ambiguity at every site)
attempt-4 gated every bare-type-reference site for VISIBILITY via a boolean
leak-check that only caught not-visible and DROPPED the ambiguous outcome, so two
DIRECT flat same-name type authors (the 0755/0105 ambiguity case) fell through to
a global findByName / struct_template_map pick at the non-leaf sites.
Unified author-outcome fix (one path, every site consumes it):
- flatTypeAuthorCount: ≥2 distinct flat authors that do NOT all collapse onto one
shared TypeId are now `.ambiguous` even when none carries a concrete TypeId yet —
two same-name GENERIC TEMPLATES (template name registered in no findByName slot)
are a genuine collision, exactly like two registered structs. Identical-target
authors (diamond import / two aliases onto the same target) still collapse to
`.one`, so all valid cases stay byte-identical.
- headTypeGate: the complete source-aware author outcome (.proceed / .resolved /
.ambiguous / .not_visible) for an unqualified bare TYPE head, emitting the loud
ambiguity diagnostic (consistent with the leaf / 0755) or the not-visible
diagnostic. headTypeLeak is now its poison-vs-proceed projection, so every head /
instantiation / alias-decl / match site poisons on ambiguity with the right
message. Reflection / type-arg and array/vector-literal identifier heads consume
`.resolved` to use the source-keyed TypeId, never a global findByName pick.
Regression examples/0767: size_of(Thing) / Nums.[1,2] / Box(s64) / t:Type=Thing /
case Thing: with two direct flat same-name authors each emit the ambiguity
diagnostic, exit 1 (fail-before on bb8f7dc: exit 0 / cascade). 0763/0764/0765/0766
/0755/0706/0544/0105 + FFI byte-identical. README: bare-type ambiguity is enforced
at every reference site.
This commit is contained in:
39
examples/0767-modules-ambiguous-bare-type-forms.sx
Normal file
39
examples/0767-modules-ambiguous-bare-type-forms.sx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
// Bare-TYPE references are NON-transitive AND ambiguity-checked at every site,
|
||||||
|
// not just the nominal leaf annotation (0755). `main` flat-imports two modules
|
||||||
|
// that each author a same-name `Thing` / `Box` / `Nums` and authors none itself,
|
||||||
|
// so EACH of the following bare forms is a genuine collision the source cannot
|
||||||
|
// disambiguate — and each must emit the LOUD "type ... is ambiguous" diagnostic
|
||||||
|
// (consistent with the leaf, 0755) and poison the result, NEVER silently pick a
|
||||||
|
// global `findByName` / `struct_template_map` author:
|
||||||
|
//
|
||||||
|
// - reflection / type-arg slot `size_of(Thing)`
|
||||||
|
// - typed array/vector-literal `Nums.[1, 2]`
|
||||||
|
// - parameterized generic head `Box(s64)`
|
||||||
|
// - type-as-value `t : Type = Thing`
|
||||||
|
// - type-category match arm `case Thing:`
|
||||||
|
//
|
||||||
|
// Regression (Phase E4 attempt-5): before the bare-type gate carried the full
|
||||||
|
// source-aware author outcome, these non-leaf sites used a boolean leak-check
|
||||||
|
// that dropped the AMBIGUOUS outcome — two direct flat same-name authors fell
|
||||||
|
// through to a global pick (exit 0 / cascade) instead of the loud diagnostic.
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
#import "0767-modules-ambiguous-bare-type-forms/a.sx";
|
||||||
|
#import "0767-modules-ambiguous-bare-type-forms/b.sx";
|
||||||
|
|
||||||
|
describe :: ($T: Type) -> s32 {
|
||||||
|
r := if T == {
|
||||||
|
case Thing: 1;
|
||||||
|
else: 0;
|
||||||
|
}
|
||||||
|
r
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: () -> s32 {
|
||||||
|
sz := size_of(Thing);
|
||||||
|
xs := Nums.[1, 2];
|
||||||
|
x : Box(s64) = .{ v = 3 };
|
||||||
|
t : Type = Thing;
|
||||||
|
d := describe(s64);
|
||||||
|
0
|
||||||
|
}
|
||||||
6
examples/0767-modules-ambiguous-bare-type-forms/a.sx
Normal file
6
examples/0767-modules-ambiguous-bare-type-forms/a.sx
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
// One of two flat-imported authors of same-name types `Thing` / `Box` / `Nums`.
|
||||||
|
// With both modules flat-visible from a file that authors none itself, every
|
||||||
|
// bare reference to these names is genuinely ambiguous.
|
||||||
|
Thing :: struct { a: s64; }
|
||||||
|
Box :: struct($T: Type) { v: T; }
|
||||||
|
Nums :: [2]s64;
|
||||||
7
examples/0767-modules-ambiguous-bare-type-forms/b.sx
Normal file
7
examples/0767-modules-ambiguous-bare-type-forms/b.sx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// The second flat-imported author of same-name `Thing` / `Box` / `Nums`. The
|
||||||
|
// distinct shapes (`Thing` a separate nominal identity, `Box` a separate generic
|
||||||
|
// template, `Nums` aliased to a different element width) make each bare
|
||||||
|
// reference a real collision the importing source cannot disambiguate.
|
||||||
|
Thing :: struct { a: s64; }
|
||||||
|
Box :: struct($T: Type) { v: T; }
|
||||||
|
Nums :: [2]s32;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
1
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
error: type 'Thing' is ambiguous: it is declared in multiple flat-imported modules; qualify the reference or remove the duplicate import
|
||||||
|
--> examples/0767-modules-ambiguous-bare-type-forms.sx:33:19
|
||||||
|
|
|
||||||
|
33 | sz := size_of(Thing);
|
||||||
|
| ^^^^^
|
||||||
|
|
||||||
|
error: type 'Nums' is ambiguous: it is declared in multiple flat-imported modules; qualify the reference or remove the duplicate import
|
||||||
|
--> examples/0767-modules-ambiguous-bare-type-forms.sx:34:11
|
||||||
|
|
|
||||||
|
34 | xs := Nums.[1, 2];
|
||||||
|
| ^^^^
|
||||||
|
|
||||||
|
error: type 'Box' is ambiguous: it is declared in multiple flat-imported modules; qualify the reference or remove the duplicate import
|
||||||
|
--> examples/0767-modules-ambiguous-bare-type-forms.sx:35:9
|
||||||
|
|
|
||||||
|
35 | x : Box(s64) = .{ v = 3 };
|
||||||
|
| ^^^^^^^^
|
||||||
|
|
||||||
|
error: type 'Thing' is ambiguous: it is declared in multiple flat-imported modules; qualify the reference or remove the duplicate import
|
||||||
|
--> examples/0767-modules-ambiguous-bare-type-forms.sx:36:16
|
||||||
|
|
|
||||||
|
36 | t : Type = Thing;
|
||||||
|
| ^^^^^
|
||||||
|
|
||||||
|
error: type 'Thing' is ambiguous: it is declared in multiple flat-imported modules; qualify the reference or remove the duplicate import
|
||||||
|
--> examples/0767-modules-ambiguous-bare-type-forms.sx:26:14
|
||||||
|
|
|
||||||
|
26 | case Thing: 1;
|
||||||
|
| ^^^^^
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -414,7 +414,12 @@ generic head) — is likewise not visible and is rejected (`type 'X' is not visi
|
|||||||
#import the module that declares it`); qualify it as `m.name`. The type gate holds
|
#import the module that declares it`); qualify it as `m.name`. The type gate holds
|
||||||
wherever a bare type name is named — a value/field annotation, a reflection /
|
wherever a bare type name is named — a value/field annotation, a reflection /
|
||||||
type-arg slot (`size_of(T)`, `size_of(*T)`), a typed array-literal head (`T.[…]`),
|
type-arg slot (`size_of(T)`, `size_of(*T)`), a typed array-literal head (`T.[…]`),
|
||||||
or a type-as-value / type-match arm — not just plain annotations. (A library's own *internal* type references still resolve: a generic
|
a parameterized head (`Box(s64)`), or a type-as-value / type-match arm — not just
|
||||||
|
plain annotations. Ambiguity is enforced at every one of those sites too, exactly
|
||||||
|
like a bare call: a bare type that two or more flat imports each declare is
|
||||||
|
**ambiguous and rejected** (`type 'X' is ambiguous: it is declared in multiple
|
||||||
|
flat-imported modules; qualify the reference or remove the duplicate import`) — never
|
||||||
|
a silent pick of one author. (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.)
|
||||||
|
|||||||
167
src/ir/lower.zig
167
src/ir/lower.zig
@@ -2147,14 +2147,20 @@ pub const Lowering = struct {
|
|||||||
/// (own-wins), so this surveys only the cross-module direct-flat authors:
|
/// (own-wins), so this surveys only the cross-module direct-flat authors:
|
||||||
/// - `.ambiguous` — ≥2 DISTINCT resolved TypeIds (issue 0105 case 4);
|
/// - `.ambiguous` — ≥2 DISTINCT resolved TypeIds (issue 0105 case 4);
|
||||||
/// - `.one` — exactly one distinct resolved TypeId;
|
/// - `.one` — exactly one distinct resolved TypeId;
|
||||||
/// - `.unregistered` — ≥1 flat author found but none resolves to a TypeId
|
/// - `.unregistered` — exactly ONE flat author found and it does not resolve
|
||||||
/// yet (a forward reference, or a foreign/lazily-registered author with no
|
/// to a TypeId yet (a forward reference, or a foreign/lazily-registered
|
||||||
/// `findByName` slot) → the caller yields the legacy stub, NOT a leak;
|
/// author with no `findByName` slot) → the caller yields the legacy stub,
|
||||||
|
/// NOT a leak;
|
||||||
/// - `.none` — no flat author at all → the caller proceeds to the
|
/// - `.none` — no flat author at all → the caller proceeds to the
|
||||||
/// local / leak / forward-alias arms.
|
/// local / leak / forward-alias arms.
|
||||||
/// Distinctness is BY TypeId: each distinct author holds a distinct
|
/// Distinctness is BY TypeId: each distinct author holds a distinct
|
||||||
/// `nominal_id` TypeId, while a diamond import of the SAME module yields the
|
/// `nominal_id` TypeId, while a diamond import of the SAME module yields the
|
||||||
/// same TypeId, so byte-identical de-dup falls out. A library template's
|
/// same TypeId, so byte-identical de-dup falls out. ≥2 distinct flat authors
|
||||||
|
/// that do NOT all collapse onto one shared TypeId are `.ambiguous` even when
|
||||||
|
/// none carries a concrete TypeId yet — two same-name GENERIC TEMPLATES (whose
|
||||||
|
/// template name is registered in no `findByName` slot) are a genuine
|
||||||
|
/// collision the source cannot disambiguate, exactly like two registered
|
||||||
|
/// structs (issue 0105 case 4). A library template's
|
||||||
/// INTERNAL bare-TYPE refs (a 2-flat-hop type like `List(T).append`'s
|
/// INTERNAL bare-TYPE refs (a 2-flat-hop type like `List(T).append`'s
|
||||||
/// `alloc: Allocator`) stay resolvable because instantiation is source-pinned
|
/// `alloc: Allocator`) stay resolvable because instantiation is source-pinned
|
||||||
/// to the template's defining module (E4 #1), so the query originates THERE —
|
/// to the template's defining module (E4 #1), so the query originates THERE —
|
||||||
@@ -2166,21 +2172,33 @@ pub const Lowering = struct {
|
|||||||
const graph = self.program_index.flat_import_graph orelse return .none;
|
const graph = self.program_index.flat_import_graph orelse return .none;
|
||||||
const direct = graph.get(from) orelse return .none;
|
const direct = graph.get(from) orelse return .none;
|
||||||
var found: ?TypeId = null;
|
var found: ?TypeId = null;
|
||||||
var saw_author = false;
|
var authors: usize = 0;
|
||||||
|
var tid_authors: usize = 0;
|
||||||
var it = direct.iterator();
|
var it = direct.iterator();
|
||||||
while (it.next()) |kv| {
|
while (it.next()) |kv| {
|
||||||
const dep = kv.key_ptr.*;
|
const dep = kv.key_ptr.*;
|
||||||
if (self.moduleTypeAuthor(dep, name) != null) {
|
if (self.moduleTypeAuthor(dep, name) != null) {
|
||||||
saw_author = true;
|
authors += 1;
|
||||||
if (self.moduleTypeAuthorTid(dep, name)) |tid| {
|
if (self.moduleTypeAuthorTid(dep, name)) |tid| {
|
||||||
|
tid_authors += 1;
|
||||||
if (found) |f| {
|
if (found) |f| {
|
||||||
if (tid != f) return .ambiguous;
|
if (tid != f) return .ambiguous;
|
||||||
} else found = tid;
|
} else found = tid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (authors == 0) return .none;
|
||||||
|
// ≥2 distinct flat authors that do NOT all collapse onto a single shared
|
||||||
|
// TypeId are genuinely ambiguous: two same-name GENERIC TEMPLATES (neither
|
||||||
|
// carries a concrete TypeId, `tid_authors == 0`), a registered author
|
||||||
|
// colliding with a same-name forward / template author (`tid_authors <
|
||||||
|
// authors`), or — caught above by the per-TypeId early return — two
|
||||||
|
// distinct registered TypeIds. Only when EVERY author resolved to ONE
|
||||||
|
// shared TypeId (a diamond import, or two aliases onto the same target)
|
||||||
|
// does it collapse to `.one`.
|
||||||
|
if (authors >= 2 and !(tid_authors == authors and found != null)) return .ambiguous;
|
||||||
if (found) |t| return .{ .one = t };
|
if (found) |t| return .{ .one = t };
|
||||||
return if (saw_author) .unregistered else .none;
|
return .unregistered;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TRUE iff `name` is authored as a TYPE — a NAMED type OR a type ALIAS — in
|
/// TRUE iff `name` is authored as a TYPE — a NAMED type OR a type ALIAS — in
|
||||||
@@ -6876,10 +6894,17 @@ pub const Lowering = struct {
|
|||||||
},
|
},
|
||||||
.parameterized_type_expr => |pt| return self.resolveParameterizedWithBindings(&pt, te.span),
|
.parameterized_type_expr => |pt| return self.resolveParameterizedWithBindings(&pt, te.span),
|
||||||
.identifier => |id| {
|
.identifier => |id| {
|
||||||
// E4 single-hop visibility gate: a 2-flat-hop bare type name in a
|
// E4 single-hop visibility + ambiguity gate: a 2-flat-hop bare type
|
||||||
// typed array/vector-literal annotation (`Nums.[1, 2]`) is not
|
// name in a typed array/vector-literal annotation (`Nums.[1, 2]`) is
|
||||||
// bare-visible (consistent with annotations / 0763).
|
// not bare-visible (consistent with annotations / 0763); ≥2 direct
|
||||||
if (self.headTypeLeak(id.name, te.span)) return .unresolved;
|
// flat same-name authors are ambiguous (loud diagnostic, consistent
|
||||||
|
// with the leaf / 0755); a single source-keyed author resolves to
|
||||||
|
// ITS TypeId instead of a global `findByName` first-/last-wins pick.
|
||||||
|
switch (self.headTypeGate(id.name, te.span)) {
|
||||||
|
.ambiguous, .not_visible => return .unresolved,
|
||||||
|
.resolved => |tid| return tid,
|
||||||
|
.proceed => {},
|
||||||
|
}
|
||||||
const name_id = self.module.types.internString(id.name);
|
const name_id = self.module.types.internString(id.name);
|
||||||
return self.module.types.findByName(name_id) orelse .unresolved;
|
return self.module.types.findByName(name_id) orelse .unresolved;
|
||||||
},
|
},
|
||||||
@@ -12410,12 +12435,20 @@ pub const Lowering = struct {
|
|||||||
if (self.type_bindings) |tb| {
|
if (self.type_bindings) |tb| {
|
||||||
if (tb.get(id.name)) |ty| return ty;
|
if (tb.get(id.name)) |ty| return ty;
|
||||||
}
|
}
|
||||||
// E4 single-hop visibility gate: a bare type name reachable only
|
// E4 single-hop visibility + ambiguity gate: a bare type name
|
||||||
// over 2+ flat hops is not bare-visible in a reflection / type-arg
|
// reachable only over 2+ flat hops is not bare-visible in a
|
||||||
// slot either (consistent with normal annotations / 0763). A
|
// reflection / type-arg slot (consistent with normal annotations /
|
||||||
// genuinely-undeclared name is NOT authored as a type anywhere, so
|
// 0763); ≥2 direct flat same-name authors are ambiguous (loud
|
||||||
// the gate falls through to the "unresolved type" diagnostic below.
|
// diagnostic, consistent with the leaf / 0755) instead of a global
|
||||||
if (self.headTypeLeak(id.name, node.span)) return .unresolved;
|
// first-/last-wins pick; a single source-keyed author resolves to
|
||||||
|
// ITS TypeId. A genuinely-undeclared name is NOT authored as a type
|
||||||
|
// anywhere → `.proceed`, falling to the "unresolved type"
|
||||||
|
// diagnostic below.
|
||||||
|
switch (self.headTypeGate(id.name, node.span)) {
|
||||||
|
.ambiguous, .not_visible => return .unresolved,
|
||||||
|
.resolved => |tid| return tid,
|
||||||
|
.proceed => {},
|
||||||
|
}
|
||||||
if (self.program_index.type_alias_map.get(id.name)) |alias_ty| return alias_ty;
|
if (self.program_index.type_alias_map.get(id.name)) |alias_ty| return alias_ty;
|
||||||
const name_id = self.module.types.internString(id.name);
|
const name_id = self.module.types.internString(id.name);
|
||||||
if (self.module.types.findByName(name_id)) |t| return t;
|
if (self.module.types.findByName(name_id)) |t| return t;
|
||||||
@@ -13885,39 +13918,79 @@ 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
|
/// The poison-vs-proceed projection of `headTypeGate` for an UNQUALIFIED
|
||||||
/// type HEAD that names a generic STRUCT or a parameterized PROTOCOL
|
/// parameterized type HEAD that names a generic STRUCT, a parameterized
|
||||||
/// (`Box(s64)`, `VL(s64)`) — the constructor-head analog of the bare-leaf
|
/// PROTOCOL, or a type-returning function used as a head (`Box(s64)`,
|
||||||
/// type gate (E4). A head is visible iff a TYPE author for `name` is reachable
|
/// `VL(s64)`) — and the alias-registration / type-match sites that likewise
|
||||||
/// from the USE site over its OWN declaration or a DIRECT flat-import edge —
|
/// only need "poison or proceed". Returns TRUE (the gate's loud diagnostic is
|
||||||
/// the SAME single-hop set the bare leaf / value / fn leaves use (0706), NOT
|
/// already emitted) when the head is `.not_visible` (a 2-flat-hop leak) or
|
||||||
/// the transitive closure. Emits the leak diagnostic + returns TRUE when the
|
/// `.ambiguous` (≥2 direct flat same-name authors — consistent with the leaf /
|
||||||
/// head is a real type author somewhere but NOT reachable here (a 2-flat-hop
|
/// 0755); FALSE when it resolves or falls open. See `headTypeGate` for the full
|
||||||
/// leak), so the caller poisons with `.unresolved`. Falls open (FALSE, no
|
/// non-transitive visibility + ambiguity model and the fall-open conditions.
|
||||||
/// 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 {
|
fn headTypeLeak(self: *Lowering, name: []const u8, span: ?ast.Span) bool {
|
||||||
if (self.emitting_default_context) return false;
|
// A head site INSTANTIATES (template / type-fn) rather than substituting a
|
||||||
if (self.program_index.module_decls == null or self.program_index.flat_import_graph == null) return false;
|
// nominal TypeId, so it consumes only the poison-vs-proceed bit of the
|
||||||
const from = self.current_source_file orelse return false;
|
// full author outcome: `.ambiguous` / `.not_visible` (loud diagnostic
|
||||||
// Reachable as a TYPE author over own / direct-flat edges → visible.
|
// already emitted by `headTypeGate`) poison; `.resolved` / `.proceed`
|
||||||
if (self.moduleTypeAuthor(from, name) != null) return false;
|
// proceed to instantiation.
|
||||||
if (self.flatTypeAuthorCount(name, from) != .none) return false;
|
return switch (self.headTypeGate(name, span)) {
|
||||||
// A block-local generic declared in THIS source is visible here.
|
.ambiguous, .not_visible => true,
|
||||||
if (self.localTypeInSource(from, name)) return false;
|
.proceed, .resolved => false,
|
||||||
// Authored as a TYPE somewhere but unreachable from `from` → a leak.
|
};
|
||||||
if (!self.nameAuthoredAsTypeAnywhere(name)) return false;
|
}
|
||||||
|
|
||||||
|
/// The complete source-aware author outcome of an UNQUALIFIED bare TYPE head —
|
||||||
|
/// the unified non-transitive visibility + ambiguity gate every bare-type-
|
||||||
|
/// reference site OUTSIDE the nominal leaf routes through (E4 attempt-5):
|
||||||
|
/// reflection / type-arg slots, typed array/vector-literal heads, parameterized
|
||||||
|
/// generic / protocol / type-fn heads, type-as-value, and type-category match
|
||||||
|
/// arms. Mirrors `selectNominalLeaf`'s author model so a 2-flat-hop type is
|
||||||
|
/// `.not_visible`, ≥2 direct flat same-name authors are `.ambiguous` (the LOUD
|
||||||
|
/// diagnostic, consistent with the leaf / 0755 — never a silent global
|
||||||
|
/// `findByName` / `struct_template_map` first-/last-wins pick), and a single
|
||||||
|
/// direct flat author resolves to ITS source-keyed TypeId. Falls open
|
||||||
|
/// (`.proceed`) when import facts are unwired, the source context is absent,
|
||||||
|
/// the default-Context emitter is running (built-in infrastructure resolves
|
||||||
|
/// independent of the user's import style, F1), the querying source is the OWN
|
||||||
|
/// author, a single flat author is not registered yet (a forward / foreign /
|
||||||
|
/// generic template — the caller instantiates it), or `name` is a block-local
|
||||||
|
/// of this source / no type author at all. 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. A namespaced `ns.Box(..)` head is an explicit qualified reach
|
||||||
|
/// and is exempt (the caller skips this gate).
|
||||||
|
const HeadTypeGate = union(enum) {
|
||||||
|
proceed,
|
||||||
|
resolved: TypeId,
|
||||||
|
ambiguous,
|
||||||
|
not_visible,
|
||||||
|
};
|
||||||
|
fn headTypeGate(self: *Lowering, name: []const u8, span: ?ast.Span) HeadTypeGate {
|
||||||
|
if (self.emitting_default_context) return .proceed;
|
||||||
|
if (self.program_index.module_decls == null or self.program_index.flat_import_graph == null) return .proceed;
|
||||||
|
const from = self.current_source_file orelse return .proceed;
|
||||||
|
// The querying source's OWN author binds through the existing path.
|
||||||
|
if (self.moduleTypeAuthor(from, name) != null) return .proceed;
|
||||||
|
switch (self.flatTypeAuthorCount(name, from)) {
|
||||||
|
.none => {},
|
||||||
|
.one => |tid| return .{ .resolved = tid },
|
||||||
|
.ambiguous => {
|
||||||
|
if (self.diagnostics) |d|
|
||||||
|
d.addFmt(.err, span, "type '{s}' is ambiguous: it is declared in multiple flat-imported modules; qualify the reference or remove the duplicate import", .{name});
|
||||||
|
return .ambiguous;
|
||||||
|
},
|
||||||
|
// A single flat author exists but its TypeId is not registered yet (a
|
||||||
|
// forward reference, a foreign / lazily-registered class, or a generic
|
||||||
|
// template) — fall open so the caller instantiates / stubs it.
|
||||||
|
.unregistered => return .proceed,
|
||||||
|
}
|
||||||
|
// A block-local type / generic declared in THIS source is visible here.
|
||||||
|
if (self.localTypeInSource(from, name)) return .proceed;
|
||||||
|
// Not a cross-module type author at all → nothing to gate.
|
||||||
|
if (!self.nameAuthoredAsTypeAnywhere(name)) return .proceed;
|
||||||
if (self.diagnostics) |d|
|
if (self.diagnostics) |d|
|
||||||
d.addFmt(.err, span, "type '{s}' is not visible; #import the module that declares it", .{name});
|
d.addFmt(.err, span, "type '{s}' is not visible; #import the module that declares it", .{name});
|
||||||
return true;
|
return .not_visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Single-hop non-transitive visibility gate for an UNQUALIFIED type-returning
|
/// Single-hop non-transitive visibility gate for an UNQUALIFIED type-returning
|
||||||
|
|||||||
Reference in New Issue
Block a user