lang: qualified namespace members in value position + alias carry
Two coupled capabilities on the road to the std restructure (current/PLAN-STDLIB.md, issue 0114): 1. alias.Type.method() / alias.Type as a call head, alias.CONST, and alias.Enum.variant now resolve — previously only alias.fn() and type-position alias.Type worked. objectIsValue treats an alias-rooted field_access as a type head; the call path strips the alias to the existing Type.method machinery; lowerFieldAccess resolves alias.CONST pinned to the target module and alias.Enum.x as a typed enum literal; resolveTypeWithBindings resolves qualified type_exprs pinned to the target. 2. The carry rule: namespaceAliasTarget resolves an alias from the file's own edges first, then from DIRECT flat imports (one level), diagnosing two distinct carried targets as ambiguous. All qualified shapes work through a carried alias — the std.sx namespace tail (mem.GPA.init() etc.) is now expressible. Regression: examples/0831-modules-namespace-alias-carry.sx (direct + carried, all seven shapes).
This commit is contained in:
20
examples/0831-modules-namespace-alias-carry.sx
Normal file
20
examples/0831-modules-namespace-alias-carry.sx
Normal file
@@ -0,0 +1,20 @@
|
||||
// Namespace aliases are module surface: a `ns :: #import` declared by a
|
||||
// module is usable by that module's DIRECT flat importers (the carry rule —
|
||||
// no `pub` keyword). Covers every qualified shape through BOTH a direct and
|
||||
// a carried alias: plain fn, struct static method + instance method, type
|
||||
// annotation, enum variant, module const, generic struct.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "0831-modules-namespace-alias-carry/facade.sx";
|
||||
|
||||
main :: () {
|
||||
print("{} ", r.helper()); // plain fn (carried)
|
||||
t := r.Thing.init(); // static method
|
||||
print("{} ", t.get());
|
||||
x : r.Thing = r.Thing.init(); // type annotation
|
||||
print("{} ", x.v);
|
||||
print("{} ", r.LIMIT); // module const
|
||||
print("{} ", r.Color.green); // enum variant
|
||||
b := r.Box(s64).{ item = 3 }; // generic struct
|
||||
print("{}\n", b.item);
|
||||
}
|
||||
1
examples/0831-modules-namespace-alias-carry/facade.sx
Normal file
1
examples/0831-modules-namespace-alias-carry/facade.sx
Normal file
@@ -0,0 +1 @@
|
||||
r :: #import "rich.sx";
|
||||
9
examples/0831-modules-namespace-alias-carry/rich.sx
Normal file
9
examples/0831-modules-namespace-alias-carry/rich.sx
Normal file
@@ -0,0 +1,9 @@
|
||||
Thing :: struct {
|
||||
v: s64;
|
||||
init :: () -> Thing { Thing.{ v = 5 } }
|
||||
get :: (self: *Thing) -> s64 { self.v }
|
||||
}
|
||||
Color :: enum { red; green; }
|
||||
LIMIT :s64: 99;
|
||||
Box :: struct ($T: Type) { item: T; }
|
||||
helper :: () -> s64 { 7 }
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
7 5 5 99 .green 3
|
||||
@@ -503,6 +503,9 @@ pub const CallResolver = struct {
|
||||
const obj_name: []const u8 = switch (obj.data) {
|
||||
.identifier => |id| id.name,
|
||||
.type_expr => |te| te.name,
|
||||
// `alias.Type` (namespace-rooted prefix) is a type head, not a
|
||||
// value — `mem.GPA.init()` must take the namespace-call path.
|
||||
.field_access => return self.l.namespaceRootedMember(obj) == null,
|
||||
else => return true,
|
||||
};
|
||||
if (self.l.scope) |scope| {
|
||||
|
||||
@@ -892,7 +892,20 @@ pub const Lowering = struct {
|
||||
// type decls, error types) still route through type_bridge, which reads
|
||||
// the global compat maps (cut over in a later phase).
|
||||
switch (node.data) {
|
||||
.type_expr => |te| return self.resolveNominalLeaf(te.name, te.is_raw, node.span),
|
||||
.type_expr => |te| {
|
||||
// Qualified `alias.Type` (incl. a carried alias): resolve the
|
||||
// base name pinned to the alias's target module.
|
||||
if (std.mem.lastIndexOfScalar(u8, te.name, '.')) |dot| {
|
||||
if (self.namespaceAliasTarget(te.name[0..dot], node.span)) |target| {
|
||||
const saved = self.current_source_file;
|
||||
self.setCurrentSourceFile(target.target_module_path);
|
||||
const ty = self.resolveNominalLeaf(te.name[dot + 1 ..], te.is_raw, node.span);
|
||||
self.setCurrentSourceFile(saved);
|
||||
return ty;
|
||||
}
|
||||
}
|
||||
return self.resolveNominalLeaf(te.name, te.is_raw, node.span);
|
||||
},
|
||||
.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
|
||||
// literal (`(s32, s32)`); validate its elements are types and reject
|
||||
@@ -1118,6 +1131,55 @@ pub const Lowering = struct {
|
||||
};
|
||||
}
|
||||
|
||||
/// Resolve a namespace alias visible from the current source file under
|
||||
/// the carry rule: the file's OWN `ns :: #import` edge wins; otherwise an
|
||||
/// alias declared by a DIRECT flat import is carried (one level — flat
|
||||
/// edges of flat edges do not chain). Two distinct carried targets for
|
||||
/// the same alias diagnose as ambiguous and resolve to null.
|
||||
pub fn namespaceAliasTarget(self: *Lowering, alias: []const u8, span: ?ast.Span) ?imports_mod.NamespaceTarget {
|
||||
const edges = self.program_index.namespace_edges orelse return null;
|
||||
const from = self.current_source_file orelse return null;
|
||||
if (edges.getPtr(from)) |own| {
|
||||
if (own.get(alias)) |t| return t;
|
||||
}
|
||||
const flat = self.program_index.flat_import_graph orelse return null;
|
||||
const direct = flat.get(from) orelse return null;
|
||||
var found: ?imports_mod.NamespaceTarget = null;
|
||||
var it = direct.keyIterator();
|
||||
while (it.next()) |dep| {
|
||||
const dep_edges = edges.getPtr(dep.*) orelse continue;
|
||||
const t = dep_edges.get(alias) orelse continue;
|
||||
if (found) |f| {
|
||||
if (!std.mem.eql(u8, f.target_module_path, t.target_module_path)) {
|
||||
if (self.diagnostics) |d| {
|
||||
d.addFmt(.err, span, "namespace '{s}' is ambiguous: aliases from multiple flat-imported modules point at different targets; declare the alias locally", .{alias});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
} else found = t;
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
/// The inner member name when `node` is a namespace-rooted prefix
|
||||
/// (`alias.Member`) — the shape a qualified type/static head takes after
|
||||
/// stripping the alias. Null when `node` isn't that shape.
|
||||
pub fn namespaceRootedMember(self: *Lowering, node: *const Node) ?[]const u8 {
|
||||
if (node.data != .field_access) return null;
|
||||
const fa = node.data.field_access;
|
||||
const root = switch (fa.object.data) {
|
||||
.identifier => |id| id.name,
|
||||
else => return null,
|
||||
};
|
||||
// A value binding shadows a same-named namespace alias.
|
||||
if (self.scope) |s| {
|
||||
if (s.lookup(root) != null) return null;
|
||||
}
|
||||
if (self.program_index.global_names.contains(root)) return null;
|
||||
if (self.namespaceAliasTarget(root, node.span) == null) return null;
|
||||
return fa.field;
|
||||
}
|
||||
|
||||
pub fn isIntEx(self: *Lowering, ty: TypeId) bool {
|
||||
if (isInt(ty)) return true;
|
||||
if (!ty.isBuiltin()) {
|
||||
|
||||
@@ -699,6 +699,9 @@ pub fn lowerCall(self: *Lowering, c_in: *const ast.Call) Ref {
|
||||
const ns_name: ?[]const u8 = switch (fa.object.data) {
|
||||
.identifier => |id| id.name,
|
||||
.type_expr => |te| te.name,
|
||||
// `alias.Type.method()` — strip the alias so the existing
|
||||
// `Type.method` qualified machinery resolves the static.
|
||||
.field_access => self.namespaceRootedMember(fa.object),
|
||||
else => null,
|
||||
};
|
||||
const qualified_name = if (ns_name) |n|
|
||||
|
||||
@@ -415,6 +415,65 @@ pub fn lowerFieldAccess(self: *Lowering, fa: *const ast.FieldAccess, span: ast.S
|
||||
return self.lowerErrorTagLiteral(fa.field, span);
|
||||
}
|
||||
|
||||
// Namespace-alias stripping in value position. The target module's
|
||||
// declarations register under their bare names, so `alias.Member`
|
||||
// re-enters as `Member` (`r.LIMIT`, and `r.Color` as the receiver of
|
||||
// `r.Color.green`); `alias.Type.field` re-enters as `Type.field`.
|
||||
if (self.namespaceRootedMember(fa.object)) |inner| {
|
||||
const root = fa.object.data.field_access.object.data.identifier.name;
|
||||
if (self.namespaceAliasTarget(root, span)) |target| {
|
||||
// Resolve the inner name as a TYPE in the target's context
|
||||
// (the alias edge authorizes the reach).
|
||||
const saved_src = self.current_source_file;
|
||||
self.setCurrentSourceFile(target.target_module_path);
|
||||
const ty = self.resolveNominalLeaf(inner, false, span);
|
||||
self.setCurrentSourceFile(saved_src);
|
||||
if (ty != .unresolved and !ty.isBuiltin()) {
|
||||
const info = self.module.types.get(ty);
|
||||
if (info == .@"enum" or info == .tagged_union) {
|
||||
// `alias.Enum.variant` — a typed enum literal.
|
||||
const synth = self.alloc.create(Node) catch null;
|
||||
if (synth) |n| {
|
||||
n.* = .{ .span = span, .data = .{ .enum_literal = .{ .name = fa.field } } };
|
||||
const saved_tt = self.target_type;
|
||||
self.target_type = ty;
|
||||
const ref = self.lowerExpr(n);
|
||||
self.target_type = saved_tt;
|
||||
return ref;
|
||||
}
|
||||
}
|
||||
}
|
||||
// `alias.Type.member` (struct constants etc.) — strip the alias;
|
||||
// the type's members register under the bare type name globally.
|
||||
const synth = self.alloc.create(Node) catch null;
|
||||
if (synth) |n| {
|
||||
n.* = .{ .span = fa.object.span, .data = .{ .identifier = .{ .name = inner } } };
|
||||
const stripped = ast.FieldAccess{ .object = n, .field = fa.field, .is_optional = fa.is_optional };
|
||||
return self.lowerFieldAccess(&stripped, span);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fa.object.data == .identifier) {
|
||||
const oname = fa.object.data.identifier.name;
|
||||
const shadowed = if (self.scope) |s| s.lookup(oname) != null else false;
|
||||
if (!shadowed and !self.program_index.global_names.contains(oname)) {
|
||||
if (self.namespaceAliasTarget(oname, span)) |target| {
|
||||
const synth = self.alloc.create(Node) catch null;
|
||||
if (synth) |n| {
|
||||
n.* = .{ .span = span, .data = .{ .identifier = .{ .name = fa.field } } };
|
||||
// Lower in the TARGET module's context: the alias edge
|
||||
// authorizes the member, so the bare-visibility gate must
|
||||
// judge it as the target's own name, not the caller's.
|
||||
const saved_src = self.current_source_file;
|
||||
self.setCurrentSourceFile(target.target_module_path);
|
||||
const ref = self.lowerExpr(n);
|
||||
self.setCurrentSourceFile(saved_src);
|
||||
return ref;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pack-arity intercept: `<pack_name>.len` in a pack-fn mono's
|
||||
// body resolves to the comptime-known N. The mono doesn't
|
||||
// materialise the `[]Any` slice that the inline path used, so
|
||||
|
||||
@@ -360,10 +360,7 @@ pub fn buildGenericStructTemplate(self: *Lowering, sd: *const ast.StructDecl, so
|
||||
/// module authors no such generic struct — the caller then falls back to the
|
||||
/// legacy bare lookup.
|
||||
pub fn qualifiedStructTemplate(self: *Lowering, alias: []const u8, member: []const u8) ?StructTemplate {
|
||||
const edges = self.program_index.namespace_edges orelse return null;
|
||||
const from = self.current_source_file orelse return null;
|
||||
const alias_map = edges.getPtr(from) orelse return null;
|
||||
const target = alias_map.get(alias) orelse return null;
|
||||
const target = self.namespaceAliasTarget(alias, null) orelse return null;
|
||||
for (target.own_decls) |decl| {
|
||||
// A top-level struct is authored either as a bare `struct_decl` node
|
||||
// or a `const_decl` whose value is one (`Box :: struct($T){...}`).
|
||||
@@ -389,10 +386,7 @@ pub fn qualifiedStructTemplate(self: *Lowering, alias: []const u8, member: []con
|
||||
/// generic struct → `qualifiedStructTemplate` already selected it; any other
|
||||
/// kind → the type-fn / named-type arms handle it).
|
||||
pub fn qualifiedMemberMissing(self: *Lowering, alias: []const u8, member: []const u8) bool {
|
||||
const edges = self.program_index.namespace_edges orelse return false;
|
||||
const from = self.current_source_file orelse return false;
|
||||
const alias_map = edges.getPtr(from) orelse return false;
|
||||
const target = alias_map.get(alias) orelse return false;
|
||||
const target = self.namespaceAliasTarget(alias, null) orelse return false;
|
||||
for (target.own_decls) |decl| {
|
||||
const dn = decl.data.declName() orelse continue;
|
||||
if (std.mem.eql(u8, dn, member)) return false;
|
||||
|
||||
Reference in New Issue
Block a user