feat(resolver): complete source-aware nominal-TYPE leaf — bare ns-only types not visible [stdlib E1 attempt-2]
Completes the F1 deliverable the reviewer flagged: the bare TYPE leaf still
returned the global `findByName` match BEFORE any visibility check, so a type
declared only behind a namespaced import leaked bare. Now the registered-type
branch of `selectNominalLeaf` is gated on bare-flat visibility (the type analog
of Phase B's value/function tightening): a bare reference to a namespaced-only
import's TYPE errors ("type 'X' is not visible; #import the module that declares
it") and poisons to `.unresolved` — never the leaked global match, never a
silent empty-struct stub.
Visibility gate is the TRANSITIVE flat-import closure (`typeBareVisible`), not
the single-hop `collectVisibleAuthors`/`isNameVisible`: a flat import is
transitive for resolution, so a type two flat hops away (`CAllocator`, via
`main → std.sx → allocators.sx`) stays bare-visible while a namespaced-only type
(reached solely over a namespace edge) does not. The gate applies ONLY to a
TOP-LEVEL author (`module_decls`) — a LOCAL type / generic-param / fabricated
empty-struct stub is findByName-registered but authored in no module, so it
resolves ungated and byte-identically (its own diagnostics still fire). The
compiler-synthesized default-Context emission falls open (`CAllocator` is
infrastructure, independent of the program's import style). The closure walk
lives in lower.zig, so resolver.zig keeps its single graph-walk.
A namespaced callee's declared return type now resolves in the callee's own
module context (`resolveTypeInSource` over `qualified_fn_source`) — a `Value`
returned by `json.parse` is visible inside `json.sx`, not at the call site
(issue-0100-F1 source-pin analog).
Migrates 0719 (flat-imports `cli` for its types, keeps `cli` namespaced for the
same-name `cli.parse`). Adds 0743 (bare ns-only struct → not visible) and 0744
(bare ns-only enum → not visible) regressions. 0742 (ns-only const) + 0210
(generics stay legacy) unchanged. readme updated.
Gate: zig build / zig build test (LSP sweep, no crash) / run_examples 481/0;
m3te ios-sim exit 0; 0743/0744 fail-before on 7cd12b0 (compiled, no diagnostic)
/ pass-after (clean "not visible").
This commit is contained in:
@@ -12,6 +12,14 @@
|
|||||||
// independent identities.
|
// independent identities.
|
||||||
|
|
||||||
#import "modules/std.sx";
|
#import "modules/std.sx";
|
||||||
|
// `cli` is imported BOTH flat (so its types — `FlagSpec` / `Command` / `Diag` —
|
||||||
|
// are bare-visible) AND namespaced (so the same-name `cli.parse` stays a
|
||||||
|
// distinct qualified identity from `json.parse`). Post-E1 a bare reference to a
|
||||||
|
// namespaced-ONLY type is a "not visible" error, so the flat import is what makes
|
||||||
|
// the bare type names below resolve; `json` stays namespaced-only (its `Value`
|
||||||
|
// reaches `main` only as `json.parse`'s return type, resolved in `json.sx`'s own
|
||||||
|
// context).
|
||||||
|
#import "modules/std/cli.sx";
|
||||||
cli :: #import "modules/std/cli.sx";
|
cli :: #import "modules/std/cli.sx";
|
||||||
json :: #import "modules/std/json.sx";
|
json :: #import "modules/std/json.sx";
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
// Bare TYPE visibility under a NAMESPACED-only import — the struct sibling of
|
||||||
|
// 0736 (bare call) and 0742 (bare const), and the core of the source-aware
|
||||||
|
// nominal leaf (Phase E1). `dep.sx` is imported only as `dep :: #import`, so its
|
||||||
|
// top-level `Secret` struct is reachable ONLY as `dep.Secret`. A BARE `Secret`
|
||||||
|
// in a type position must NOT resolve: bare-TYPE visibility joins over the FLAT
|
||||||
|
// import edges (`flat_import_graph`, transitively), and a namespaced alias is not
|
||||||
|
// a flat edge. Before the fix the bare type leaked through the global
|
||||||
|
// `findByName` first-match (the leaf returned it ahead of the visibility check),
|
||||||
|
// so `s.x` compiled and ran. The qualified form `dep.Secret` stays the supported
|
||||||
|
// spelling (its member resolution lands fully in Phase F).
|
||||||
|
dep :: #import "0743-modules-namespaced-only-bare-type-not-visible/dep.sx";
|
||||||
|
|
||||||
|
main :: () -> s32 {
|
||||||
|
s : Secret = .{ x = 5, y = 6 };
|
||||||
|
s.x
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
Secret :: struct {
|
||||||
|
x: s32;
|
||||||
|
y: s32;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// Bare TYPE visibility under a NAMESPACED-only import — the ENUM sibling of 0743
|
||||||
|
// (struct), covering the second registered nominal kind for the source-aware
|
||||||
|
// nominal leaf (Phase E1). `dep.sx` is imported only as `dep :: #import`, so its
|
||||||
|
// top-level `Color` enum is reachable ONLY as `dep.Color`. A BARE `Color` in a
|
||||||
|
// type position must NOT resolve — bare-TYPE visibility joins over the FLAT
|
||||||
|
// import edges, and a namespaced alias is not a flat edge. Before the fix the
|
||||||
|
// bare enum leaked through the global `findByName` first-match.
|
||||||
|
dep :: #import "0744-modules-namespaced-only-bare-enum-not-visible/dep.sx";
|
||||||
|
|
||||||
|
main :: () -> s32 {
|
||||||
|
c : Color = .green;
|
||||||
|
if c == .green { 0 } else { 9 }
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
Color :: enum {
|
||||||
|
red;
|
||||||
|
green;
|
||||||
|
blue;
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
1
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
error: type 'Secret' is not visible; #import the module that declares it
|
||||||
|
--> examples/0743-modules-namespaced-only-bare-type-not-visible.sx:14:9
|
||||||
|
|
|
||||||
|
14 | s : Secret = .{ x = 5, y = 6 };
|
||||||
|
| ^^^^^^
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
1
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
error: type 'Color' is not visible; #import the module that declares it
|
||||||
|
--> examples/0744-modules-namespaced-only-bare-enum-not-visible.sx:11:9
|
||||||
|
|
|
||||||
|
11 | c : Color = .green;
|
||||||
|
| ^^^^^
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
11
readme.md
11
readme.md
@@ -400,11 +400,12 @@ A bare call to a name that two or more flat imports both provide is ambiguous an
|
|||||||
is rejected; qualify it with a namespaced import (`m :: #import …; m.fn()`).
|
is rejected; qualify it with a namespaced import (`m :: #import …; m.fn()`).
|
||||||
|
|
||||||
A **namespaced** import only binds its alias: reach the module's members as
|
A **namespaced** import only binds its alias: reach the module's members as
|
||||||
`m.name`. Bare-name visibility joins over flat (`#import "…"`) imports only,
|
`m.name`. Bare-name visibility joins over flat (`#import "…"`) imports only —
|
||||||
never over a namespaced alias. Bare references to a namespaced-only import's
|
transitively (a flat import of a flat import is visible) — never over a
|
||||||
members are being phased out as the resolver migration lands and do not yet
|
namespaced alias. A bare reference to a namespaced-only import's member —
|
||||||
resolve uniformly across name kinds — qualify them as `m.name` to stay correct
|
function, module constant, or **type** — is not visible and is rejected (`type
|
||||||
across releases.
|
'X' is not visible; #import the module that declares it`); qualify it as
|
||||||
|
`m.name`.
|
||||||
|
|
||||||
### Implicit Context
|
### Implicit Context
|
||||||
|
|
||||||
|
|||||||
@@ -409,7 +409,7 @@ pub const CallResolver = struct {
|
|||||||
if (self.l.program_index.fn_ast_map.get(qualified)) |qfd| {
|
if (self.l.program_index.fn_ast_map.get(qualified)) |qfd| {
|
||||||
return .{
|
return .{
|
||||||
.kind = .namespace_fn,
|
.kind = .namespace_fn,
|
||||||
.return_type = if (qfd.return_type) |rt| self.l.resolveType(rt) else .void,
|
.return_type = if (qfd.return_type) |rt| self.l.resolveTypeInSource(self.l.program_index.qualified_fn_source.get(qualified), rt) else .void,
|
||||||
.target = .{ .named = qualified },
|
.target = .{ .named = qualified },
|
||||||
.expands_defaults = defaultsFor(qfd, c.args.len),
|
.expands_defaults = defaultsFor(qfd, c.args.len),
|
||||||
};
|
};
|
||||||
@@ -419,7 +419,7 @@ pub const CallResolver = struct {
|
|||||||
if (self.l.program_index.fn_ast_map.get(cfa.field)) |bfd| {
|
if (self.l.program_index.fn_ast_map.get(cfa.field)) |bfd| {
|
||||||
return .{
|
return .{
|
||||||
.kind = .namespace_fn,
|
.kind = .namespace_fn,
|
||||||
.return_type = if (bfd.return_type) |rt| self.l.resolveType(rt) else .void,
|
.return_type = if (bfd.return_type) |rt| self.l.resolveTypeInSource(self.l.program_index.qualified_fn_source.get(qualified), rt) else .void,
|
||||||
.target = .{ .named = cfa.field },
|
.target = .{ .named = cfa.field },
|
||||||
.expands_defaults = defaultsFor(bfd, c.args.len),
|
.expands_defaults = defaultsFor(bfd, c.args.len),
|
||||||
};
|
};
|
||||||
|
|||||||
163
src/ir/lower.zig
163
src/ir/lower.zig
@@ -13,6 +13,7 @@ const errors = @import("../errors.zig");
|
|||||||
const jni_descriptor = @import("jni_descriptor.zig");
|
const jni_descriptor = @import("jni_descriptor.zig");
|
||||||
const program_index_mod = @import("program_index.zig");
|
const program_index_mod = @import("program_index.zig");
|
||||||
const resolver_mod = @import("resolver.zig");
|
const resolver_mod = @import("resolver.zig");
|
||||||
|
const imports_mod = @import("../imports.zig");
|
||||||
const ProgramIndex = program_index_mod.ProgramIndex;
|
const ProgramIndex = program_index_mod.ProgramIndex;
|
||||||
const GlobalInfo = program_index_mod.GlobalInfo;
|
const GlobalInfo = program_index_mod.GlobalInfo;
|
||||||
const StructTemplate = program_index_mod.StructTemplate;
|
const StructTemplate = program_index_mod.StructTemplate;
|
||||||
@@ -200,6 +201,13 @@ pub const Lowering = struct {
|
|||||||
func_defer_base: usize = 0, // defer stack base for current function (lowerReturn drains to this)
|
func_defer_base: usize = 0, // defer stack base for current function (lowerReturn drains to this)
|
||||||
deferred_type_fns: std.ArrayList([]const u8) = std.ArrayList([]const u8).empty, // functions deferred until all types registered
|
deferred_type_fns: std.ArrayList([]const u8) = std.ArrayList([]const u8).empty, // functions deferred until all types registered
|
||||||
processing_deferred: bool = false, // true when processing deferred functions (prevents re-deferral)
|
processing_deferred: bool = false, // true when processing deferred functions (prevents re-deferral)
|
||||||
|
/// True while emitting the compiler-synthesized default-Context global
|
||||||
|
/// (`emitDefaultContextGlobal`). The built-in allocator infrastructure
|
||||||
|
/// (`CAllocator`/`Allocator`/`Context`) is resolved as compiler internals,
|
||||||
|
/// independent of the user program's import STYLE (a `std :: #import` puts
|
||||||
|
/// `CAllocator` behind a namespace edge from `main`, so the user-visibility
|
||||||
|
/// gate would reject it) — so the bare TYPE leaf falls open here (F1).
|
||||||
|
emitting_default_context: bool = false,
|
||||||
struct_defaults_map: std.StringHashMap([]const ?*const Node) = std.StringHashMap([]const ?*const Node).init(std.heap.page_allocator), // struct name → field defaults
|
struct_defaults_map: std.StringHashMap([]const ?*const Node) = std.StringHashMap([]const ?*const Node).init(std.heap.page_allocator), // struct name → field defaults
|
||||||
struct_instance_bindings: std.StringHashMap(std.StringHashMap(TypeId)) = std.StringHashMap(std.StringHashMap(TypeId)).init(std.heap.page_allocator), // mangled struct name → type param bindings
|
struct_instance_bindings: std.StringHashMap(std.StringHashMap(TypeId)) = std.StringHashMap(std.StringHashMap(TypeId)).init(std.heap.page_allocator), // mangled struct name → type param bindings
|
||||||
struct_instance_template: std.StringHashMap([]const u8) = std.StringHashMap([]const u8).init(std.heap.page_allocator), // mangled struct name → template name
|
struct_instance_template: std.StringHashMap([]const u8) = std.StringHashMap([]const u8).init(std.heap.page_allocator), // mangled struct name → template name
|
||||||
@@ -1651,6 +1659,15 @@ pub const Lowering = struct {
|
|||||||
/// E1 keeps the existing empty-struct stub; E3 turns this into the
|
/// E1 keeps the existing empty-struct stub; E3 turns this into the
|
||||||
/// `.unresolved` sentinel + a diagnostic.
|
/// `.unresolved` sentinel + a diagnostic.
|
||||||
undeclared,
|
undeclared,
|
||||||
|
/// `name` IS a registered named type, but it is reachable from the
|
||||||
|
/// querying module ONLY through a namespaced import — not bare-visible
|
||||||
|
/// over the transitive flat-import closure (the type analog of Phase B's
|
||||||
|
/// bare-call tightening, F1). The user must qualify it (`ns.Type`).
|
||||||
|
/// `resolveNominalLeaf` surfaces the "not visible" diagnostic and returns
|
||||||
|
/// the `.unresolved` poison sentinel — NEVER the global `findByName` match
|
||||||
|
/// (which would leak the type) and NEVER a silent empty-struct stub (which
|
||||||
|
/// would mis-size it).
|
||||||
|
not_visible,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// THE plain bare-name call selector (fix-0102c, R5 §C). `resolveBareCallee`'s
|
/// THE plain bare-name call selector (fix-0102c, R5 §C). `resolveBareCallee`'s
|
||||||
@@ -1762,14 +1779,43 @@ pub const Lowering = struct {
|
|||||||
if (name.len > 0 and (name[0] == '[' or name[0] == '*' or name[0] == '?')) {
|
if (name.len > 0 and (name[0] == '[' or name[0] == '*' or name[0] == '?')) {
|
||||||
return .{ .resolved = self.typeResolver().resolveName(name, raw) };
|
return .{ .resolved = self.typeResolver().resolveName(name, raw) };
|
||||||
}
|
}
|
||||||
// Registered named type. Single-author (E1): its unique registered
|
// Registered named type — gated on BARE-FLAT visibility (F1, the type
|
||||||
// TypeId. `findByName` stays the byte-identical resolver here — it also
|
// analog of Phase B's value/function tightening). A namespaced-only type
|
||||||
// reaches a namespaced-only type referenced bare (the global leak 0719
|
// is registered GLOBALLY yet is reachable from the querying module only
|
||||||
// relies on); E2 routes this through the collector-selected author's
|
// over a namespace edge, so without this gate its bare reference leaked
|
||||||
// per-source `nominal_id` once same-name type shadows register, and E3
|
// through the global `findByName` first-match. The gate is the TRANSITIVE
|
||||||
// turns a true miss into the `.unresolved` sentinel + a diagnostic.
|
// flat-import reachability `typeBareVisible` — NOT `collectVisibleAuthors`,
|
||||||
|
// which walks each module's OWN decls single-hop and would false-negative
|
||||||
|
// a type two flat hops away (e.g. `CAllocator`, reached `main → std.sx →
|
||||||
|
// allocators.sx` over two flat edges). Single-author (E1): the unique
|
||||||
|
// `findByName` match IS the one bare-visible author's TypeId, so a
|
||||||
|
// bare-visible name resolves byte-identically; E2 routes this through the
|
||||||
|
// collector-selected author's per-source `nominal_id` once same-name type
|
||||||
|
// shadows register.
|
||||||
const name_id = table.internString(name);
|
const name_id = table.internString(name);
|
||||||
if (table.findByName(name_id)) |existing| return .{ .resolved = existing };
|
if (table.findByName(name_id)) |existing| {
|
||||||
|
// Compiler-synthesized default-Context emission resolves the built-in
|
||||||
|
// allocator types as infrastructure — fall open (the gate is for
|
||||||
|
// USER bare references, not compiler internals).
|
||||||
|
if (self.emitting_default_context) return .{ .resolved = existing };
|
||||||
|
// The gate applies ONLY to a TOP-LEVEL type author — a `name` declared
|
||||||
|
// in some module's raw facts (`module_decls`). A LOCAL type (declared
|
||||||
|
// inside a fn / init block), a generic type-param, and a fabricated
|
||||||
|
// empty-struct stub are all findByName-registered yet authored in NO
|
||||||
|
// `module_decls`; they are not bare cross-module references, so they
|
||||||
|
// resolve ungated and byte-identically (their own diagnostics —
|
||||||
|
// unknown-type / value-param — still fire in the dedicated pass).
|
||||||
|
if (self.nameAuthoredAnywhere(name)) {
|
||||||
|
if (self.typeBareVisible(name, from)) return .{ .resolved = existing };
|
||||||
|
// Registered top-level type reachable ONLY through a namespaced
|
||||||
|
// import: a named type is never a `const`, so the alias path
|
||||||
|
// cannot apply — return `.not_visible` so the leaf does not leak
|
||||||
|
// the global match; `resolveNominalLeaf` surfaces the diagnostic
|
||||||
|
// and the `.unresolved` sentinel (qualify it `ns.Type`, Phase F).
|
||||||
|
return .not_visible;
|
||||||
|
}
|
||||||
|
return .{ .resolved = existing };
|
||||||
|
}
|
||||||
// Type alias `A :: B`. Select the alias author over the ONE graph-walk
|
// Type alias `A :: B`. Select the alias author over the ONE graph-walk
|
||||||
// collector and read its target from the source-keyed cache, keyed by
|
// collector and read its target from the source-keyed cache, keyed by
|
||||||
// the author's OWN declaring source (E0's write side) — this is where the
|
// the author's OWN declaring source (E0's write side) — this is where the
|
||||||
@@ -1798,13 +1844,74 @@ pub const Lowering = struct {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// TRUE iff bare `name` is reachable from `from` over the TRANSITIVE
|
||||||
|
/// flat-import closure (own decls ∪ every transitively flat-imported module's
|
||||||
|
/// own decls). The correct `.user_bare_flat` reachability for the TYPE leaf
|
||||||
|
/// (F1): a flat import is transitive for resolution — the global decl list a
|
||||||
|
/// module lowers against is the FULL transitive flat list — so a type two flat
|
||||||
|
/// hops away (`CAllocator`, reached `main → std.sx → allocators.sx`) IS
|
||||||
|
/// bare-visible, while a namespaced-only type (reached solely over a namespace
|
||||||
|
/// edge, never recorded in `flat_import_graph`) is NOT. The single-hop
|
||||||
|
/// predicates (`isNameVisible` / `collectVisibleAuthors`, own ∪ DIRECT flat
|
||||||
|
/// deps) would false-negate the transitive case. This closure walk lives in
|
||||||
|
/// `lower.zig`, NOT `resolver.zig`, so the single-graph-walk invariant (one
|
||||||
|
/// `flat_import_graph` iterator in `resolver.zig`) is untouched. Falls open
|
||||||
|
/// (visible) when the scoping facts are unwired (comptime / registration).
|
||||||
|
fn typeBareVisible(self: *Lowering, name: []const u8, from: []const u8) bool {
|
||||||
|
const decls = self.program_index.module_decls orelse return true;
|
||||||
|
const graph = self.program_index.flat_import_graph orelse return true;
|
||||||
|
if (moduleAuthorsName(decls, from, name)) return true;
|
||||||
|
var visited = std.StringHashMap(void).init(self.alloc);
|
||||||
|
defer visited.deinit();
|
||||||
|
var queue = std.ArrayList([]const u8).empty;
|
||||||
|
defer queue.deinit(self.alloc);
|
||||||
|
visited.put(from, {}) catch return true;
|
||||||
|
queue.append(self.alloc, from) catch return true;
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < queue.items.len) : (i += 1) {
|
||||||
|
const deps = graph.get(queue.items[i]) orelse continue;
|
||||||
|
var it = deps.iterator();
|
||||||
|
while (it.next()) |kv| {
|
||||||
|
const dep = kv.key_ptr.*;
|
||||||
|
if (visited.contains(dep)) continue;
|
||||||
|
visited.put(dep, {}) catch continue;
|
||||||
|
if (moduleAuthorsName(decls, dep, name)) return true;
|
||||||
|
queue.append(self.alloc, dep) catch continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TRUE iff module `path` authors a top-level decl named `name` (the Phase A
|
||||||
|
/// raw-fact membership — own decls only, the per-module leaf of the closure
|
||||||
|
/// walk in `typeBareVisible`).
|
||||||
|
fn moduleAuthorsName(decls: *imports_mod.ModuleDecls, path: []const u8, name: []const u8) bool {
|
||||||
|
const m = decls.get(path) orelse return false;
|
||||||
|
return m.names.contains(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TRUE iff `name` is authored as a TOP-LEVEL decl in ANY module's raw facts.
|
||||||
|
/// Distinguishes a real cross-module type author (the only thing the bare-flat
|
||||||
|
/// visibility gate polices) from a LOCAL type / generic-param / fabricated
|
||||||
|
/// empty-struct stub, which are findByName-registered but authored in no
|
||||||
|
/// `module_decls`. Unwired facts → false (nothing to gate; resolve ungated).
|
||||||
|
fn nameAuthoredAnywhere(self: *Lowering, name: []const u8) bool {
|
||||||
|
const decls = self.program_index.module_decls orelse return false;
|
||||||
|
var it = decls.valueIterator();
|
||||||
|
while (it.next()) |m| if (m.names.contains(name)) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/// Resolve the bare TYPE leaf to a `TypeId` for `resolveTypeWithBindings`.
|
/// Resolve the bare TYPE leaf to a `TypeId` for `resolveTypeWithBindings`.
|
||||||
/// Routes through the source-aware `selectNominalLeaf`; `.pending` /
|
/// Routes through the source-aware `selectNominalLeaf`; `.pending` /
|
||||||
/// `.undeclared` keep the legacy empty-struct stub (E3 turns these into the
|
/// `.undeclared` keep the legacy empty-struct stub (E3 turns these into the
|
||||||
/// `.unresolved` sentinel + a diagnostic). When the source context is unwired
|
/// `.unresolved` sentinel + a diagnostic). `.not_visible` (a registered type
|
||||||
/// (`current_source_file` null — comptime / registration callers), there is
|
/// reachable only through a namespaced import) surfaces the "not visible"
|
||||||
/// no querying module to collect from, so fall open to the legacy namer.
|
/// diagnostic and the `.unresolved` poison sentinel — a real error, never a
|
||||||
fn resolveNominalLeaf(self: *Lowering, name: []const u8, raw: bool) TypeId {
|
/// silent stub (F1). When the source context is unwired (`current_source_file`
|
||||||
|
/// null — comptime / registration callers), there is no querying module to
|
||||||
|
/// collect from, so fall open to the legacy namer.
|
||||||
|
fn resolveNominalLeaf(self: *Lowering, name: []const u8, raw: bool, span: ?ast.Span) TypeId {
|
||||||
const from = self.current_source_file orelse
|
const from = self.current_source_file orelse
|
||||||
return self.typeResolver().resolveName(name, raw);
|
return self.typeResolver().resolveName(name, raw);
|
||||||
return switch (self.selectNominalLeaf(name, from, raw)) {
|
return switch (self.selectNominalLeaf(name, from, raw)) {
|
||||||
@@ -1816,6 +1923,16 @@ pub const Lowering = struct {
|
|||||||
.name = self.module.types.internString(name),
|
.name = self.module.types.internString(name),
|
||||||
.fields = &.{},
|
.fields = &.{},
|
||||||
} }),
|
} }),
|
||||||
|
// Registered, but reachable only through a namespaced import: emit the
|
||||||
|
// diagnostic at the reference and poison the result so no downstream
|
||||||
|
// check (field access, size) trusts a leaked / mis-sized type.
|
||||||
|
// `.unresolved` is poison-suppressed, so there is no secondary
|
||||||
|
// "field not found" cascade.
|
||||||
|
.not_visible => {
|
||||||
|
if (self.diagnostics) |d|
|
||||||
|
d.addFmt(.err, span, "type '{s}' is not visible; #import the module that declares it", .{name});
|
||||||
|
return .unresolved;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -12873,6 +12990,23 @@ pub const Lowering = struct {
|
|||||||
return self.resolveTypeWithBindings(type_ann);
|
return self.resolveTypeWithBindings(type_ann);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolve a type node with the visibility context pinned to `src`, the
|
||||||
|
/// DEFINING module of a namespaced callee, restoring the caller's context
|
||||||
|
/// after. A namespaced callee's declared return type may name a type that is
|
||||||
|
/// bare-visible only inside the callee's own module — namespaced-only from the
|
||||||
|
/// call site's view. Post-E1 the bare leaf is source-aware, so resolving that
|
||||||
|
/// return type in the CALL SITE's context would wrongly reject it (the type
|
||||||
|
/// analog of the issue-0100-F1 source pin that lowers a namespaced fn body in
|
||||||
|
/// its own module's context). `src == null` falls back to the call site's
|
||||||
|
/// context unchanged.
|
||||||
|
pub fn resolveTypeInSource(self: *Lowering, src: ?[]const u8, type_ann: *const Node) TypeId {
|
||||||
|
const pinned = src orelse return self.resolveType(type_ann);
|
||||||
|
const saved = self.current_source_file;
|
||||||
|
defer self.setCurrentSourceFile(saved);
|
||||||
|
self.setCurrentSourceFile(pinned);
|
||||||
|
return self.resolveType(type_ann);
|
||||||
|
}
|
||||||
|
|
||||||
/// Construct a `TypeResolver` view over the current lowering state (borrows
|
/// Construct a `TypeResolver` view over the current lowering state (borrows
|
||||||
/// only; cheap by-value, reflects current `diagnostics` / `program_index`).
|
/// only; cheap by-value, reflects current `diagnostics` / `program_index`).
|
||||||
fn typeResolver(self: *Lowering) TypeResolver {
|
fn typeResolver(self: *Lowering) TypeResolver {
|
||||||
@@ -13120,8 +13254,8 @@ pub const Lowering = struct {
|
|||||||
// type decls, error types) still route through type_bridge, which reads
|
// type decls, error types) still route through type_bridge, which reads
|
||||||
// the global compat maps (cut over in a later phase).
|
// the global compat maps (cut over in a later phase).
|
||||||
switch (node.data) {
|
switch (node.data) {
|
||||||
.type_expr => |te| return self.resolveNominalLeaf(te.name, te.is_raw),
|
.type_expr => |te| return self.resolveNominalLeaf(te.name, te.is_raw, node.span),
|
||||||
.identifier => |id| return self.resolveNominalLeaf(id.name, id.is_raw),
|
.identifier => |id| return self.resolveNominalLeaf(id.name, id.is_raw, node.span),
|
||||||
// A non-spread tuple literal in a type position is a tuple-type
|
// A non-spread tuple literal in a type position is a tuple-type
|
||||||
// literal (`(s32, s32)`); validate its elements are types and reject
|
// literal (`(s32, s32)`); validate its elements are types and reject
|
||||||
// non-type elements loudly (issue 0067).
|
// non-type elements loudly (issue 0067).
|
||||||
@@ -14434,6 +14568,9 @@ pub const Lowering = struct {
|
|||||||
/// `std.sx` — without that, Context / Allocator / CAllocator aren't
|
/// `std.sx` — without that, Context / Allocator / CAllocator aren't
|
||||||
/// registered and the global has no purpose.
|
/// registered and the global has no purpose.
|
||||||
fn emitDefaultContextGlobal(self: *Lowering) void {
|
fn emitDefaultContextGlobal(self: *Lowering) void {
|
||||||
|
const saved_edc = self.emitting_default_context;
|
||||||
|
self.emitting_default_context = true;
|
||||||
|
defer self.emitting_default_context = saved_edc;
|
||||||
const tbl = &self.module.types;
|
const tbl = &self.module.types;
|
||||||
const ctx_name_id = tbl.internString("Context");
|
const ctx_name_id = tbl.internString("Context");
|
||||||
const ctx_ty = tbl.findByName(ctx_name_id) orelse return;
|
const ctx_ty = tbl.findByName(ctx_name_id) orelse return;
|
||||||
|
|||||||
Reference in New Issue
Block a user