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:
@@ -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()) {
|
||||
|
||||
Reference in New Issue
Block a user