Skip to content

Commit 862246f

Browse files
committed
Change how exposing works in type modules
1 parent 7f6502e commit 862246f

File tree

2 files changed

+108
-19
lines changed

2 files changed

+108
-19
lines changed

src/canonicalize/Can.zig

Lines changed: 65 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,12 @@ fn processTypeDeclFirstPass(
561561
if (!defer_associated_blocks) {
562562
if (type_decl.associated) |assoc| {
563563
try self.processAssociatedBlock(qualified_name_idx, type_header.name, assoc);
564+
565+
// For type modules, expose all associated items after processing
566+
// This populates exposed_items with fully qualified names
567+
if (self.env.module_kind == .type_module) {
568+
try self.exposeAssociatedItems(qualified_name_idx, type_decl);
569+
}
564570
}
565571
}
566572
}
@@ -916,6 +922,12 @@ fn processAssociatedItemsSecondPass(
916922
const def_idx_u16: u16 = @intCast(@intFromEnum(def_idx));
917923
try self.env.setExposedNodeIndexById(decl_ident, def_idx_u16);
918924

925+
// Also register by the type-qualified name for type modules
926+
// This is needed so lookups like "Builtin.Str.is_empty" work
927+
if (self.env.module_kind == .type_module) {
928+
try self.env.setExposedNodeIndexById(qualified_idx, def_idx_u16);
929+
}
930+
919931
// Make the real pattern available in current scope (replaces placeholder)
920932
// We already added unqualified and type-qualified names earlier,
921933
// but need to update them to point to the real pattern instead of placeholder.
@@ -962,6 +974,12 @@ fn processAssociatedItemsSecondPass(
962974
const def_idx_u16: u16 = @intCast(@intFromEnum(def_idx));
963975
try self.env.setExposedNodeIndexById(name_ident, def_idx_u16);
964976

977+
// Also register by the type-qualified name for type modules
978+
// This is needed so lookups like "Builtin.Str.is_empty" work
979+
if (self.env.module_kind == .type_module) {
980+
try self.env.setExposedNodeIndexById(qualified_idx, def_idx_u16);
981+
}
982+
965983
// Make the real pattern available in current scope (replaces placeholder)
966984
const def_cir = self.env.store.getDef(def_idx);
967985
const pattern_idx = def_cir.pattern;
@@ -1908,7 +1926,25 @@ pub fn validateForChecking(self: *Self) std.mem.Allocator.Error!void {
19081926
// Store the matching type ident in module_kind if found
19091927
if (matching_type_ident) |type_ident| {
19101928
main_type_ident.* = type_ident;
1911-
// The main type and associated items are already exposed in canonicalize()
1929+
1930+
// Expose all associated items for the main type
1931+
// This populates exposed_items with fully qualified names like "Builtin.Str.is_empty"
1932+
const file = self.parse_ir.store.getFile();
1933+
for (self.parse_ir.store.statementSlice(file.statements)) |stmt_id| {
1934+
const stmt = self.parse_ir.store.getStatement(stmt_id);
1935+
if (stmt == .type_decl) {
1936+
const type_decl = stmt.type_decl;
1937+
const header = self.parse_ir.store.getTypeHeader(type_decl.header) catch continue;
1938+
const type_name_ident = self.parse_ir.tokens.resolveIdentifier(header.name) orelse continue;
1939+
1940+
// Check if this is the main type
1941+
if (@as(u32, @bitCast(type_name_ident)) == @as(u32, @bitCast(type_ident))) {
1942+
// Expose all associated items of the main type
1943+
try self.exposeAssociatedItems(type_ident, type_decl);
1944+
break;
1945+
}
1946+
}
1947+
}
19121948
}
19131949

19141950
// Valid if either we have a valid main! or a matching type declaration
@@ -2275,8 +2311,13 @@ fn populateExports(self: *Self) std.mem.Allocator.Error!void {
22752311
const pattern = self.env.store.getPattern(def.pattern);
22762312

22772313
if (pattern == .assign) {
2278-
const is_exposed = self.env.common.exposed_items.containsById(self.env.gpa, @bitCast(pattern.assign.ident));
2279-
// Check if this definition's identifier is in the exposed items
2314+
// For type modules, use the deterministic "nested under main type" rule
2315+
// For regular modules, check the exposed_items map
2316+
const is_exposed = if (self.env.module_kind == .type_module)
2317+
self.env.isExposedInTypeModule(pattern.assign.ident)
2318+
else
2319+
self.env.common.exposed_items.containsById(self.env.gpa, @bitCast(pattern.assign.ident));
2320+
22802321
if (is_exposed) {
22812322
// Add this definition to the exports scratch space
22822323
try self.env.store.addScratchDef(def_idx);
@@ -3498,25 +3539,23 @@ pub fn canonicalizeExpr(
34983539
if (envs_map.get(module_name)) |auto_imported_type| {
34993540
const module_env = auto_imported_type.env;
35003541

3501-
// For nested types (e.g., Bool inside Builtin), build the full qualified name
3502-
// For regular module imports (e.g., A), just use the field name directly
3542+
// For auto-imported nested types (e.g., Bool, Str inside Builtin),
3543+
// use fully qualified names because they share a CommonEnv.
3544+
// For user-defined type modules, use unqualified names.
35033545
const lookup_name: []const u8 = if (auto_imported_type.statement_idx) |_| blk_name: {
3504-
// Auto-imported nested type: build "Builtin.Bool.not" using the module's actual name
3505-
// Associated items are stored with the full qualified parent type name
3546+
// Auto-imported nested type: build "Builtin.Str.is_empty"
3547+
// This prevents collisions in the shared exposed_items map
35063548
const parent_qualified_idx = try self.env.insertQualifiedIdent(module_env.module_name, module_text);
35073549
const parent_qualified_text = self.env.getIdent(parent_qualified_idx);
35083550
const fully_qualified_idx = try self.env.insertQualifiedIdent(parent_qualified_text, field_text);
35093551
break :blk_name self.env.getIdent(fully_qualified_idx);
35103552
} else field_text;
35113553

3512-
// Look up the associated item by its qualified name
3513-
const qname_ident = module_env.common.findIdent(lookup_name) orelse {
3514-
// Identifier not found - just return null
3515-
// The error will be handled by the code below that checks target_node_idx_opt
3554+
if (module_env.common.findIdent(lookup_name)) |target_ident| {
3555+
break :blk module_env.getExposedNodeIndexById(target_ident);
3556+
} else {
35163557
break :blk null;
3517-
};
3518-
const node_idx = module_env.getExposedNodeIndexById(qname_ident);
3519-
break :blk node_idx;
3558+
}
35203559
} else {
35213560
break :blk null;
35223561
}
@@ -8565,7 +8604,14 @@ fn checkScopeForUnusedVariables(self: *Self, scope: *const Scope) std.mem.Alloca
85658604
}
85668605

85678606
// Skip if this identifier is exposed (implicitly used in type modules)
8568-
if (self.env.common.exposed_items.containsById(self.env.gpa, @bitCast(ident_idx))) {
8607+
// For type modules, use the deterministic "nested under main type" rule
8608+
// For regular modules, check the exposed_items map
8609+
const is_exposed = if (self.env.module_kind == .type_module)
8610+
self.env.isExposedInTypeModule(ident_idx)
8611+
else
8612+
self.env.common.exposed_items.containsById(self.env.gpa, @bitCast(ident_idx));
8613+
8614+
if (is_exposed) {
85698615
continue;
85708616
}
85718617

@@ -9663,7 +9709,8 @@ fn exposeAssociatedItems(self: *Self, parent_name: Ident.Idx, type_decl: std.met
96639709
const decl_text = self.env.getIdent(decl_ident);
96649710
const qualified_idx = try self.env.insertQualifiedIdent(parent_text, decl_text);
96659711

9666-
// Expose the declaration
9712+
// Node index was already set during definition processing
9713+
// Just ensure qualified name is in exposed_items
96679714
try self.env.addExposedById(qualified_idx);
96689715
}
96699716
}
@@ -9676,9 +9723,8 @@ fn exposeAssociatedItems(self: *Self, parent_name: Ident.Idx, type_decl: std.met
96769723
const anno_text = self.env.getIdent(anno_ident);
96779724
const qualified_idx = try self.env.insertQualifiedIdent(parent_text, anno_text);
96789725

9679-
// Expose the qualified name
9680-
// The unqualified name is added to scope but doesn't need to be in exposed_items
9681-
// because lookups use the qualified name
9726+
// Node index was already set during definition processing
9727+
// Just ensure qualified name is in exposed_items
96829728
try self.env.addExposedById(qualified_idx);
96839729
}
96849730
},

src/canonicalize/ModuleEnv.zig

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1680,6 +1680,49 @@ pub fn containsExposedById(self: *const Self, ident_idx: Ident.Idx) bool {
16801680
return self.common.exposed_items.containsById(self.gpa, @bitCast(ident_idx));
16811681
}
16821682

1683+
/// Checks whether the given identifier is exposed in a type module using the deterministic rule:
1684+
/// "anything nested under MainType.* is exposed by definition"
1685+
/// For example, in Builtin module: "Builtin", "Builtin.Bool", "Builtin.Bool.not" are all exposed.
1686+
pub fn isExposedInTypeModule(self: *const Self, ident_idx: Ident.Idx) bool {
1687+
// Only applies to type modules
1688+
switch (self.module_kind) {
1689+
.type_module => {
1690+
// For type modules, the main type name is the same as the module name
1691+
// Use module_name_idx which is always properly initialized
1692+
const main_type_name_idx = self.module_name_idx;
1693+
1694+
// The main type itself is always exposed
1695+
const ident_u32: u32 = @bitCast(ident_idx);
1696+
const main_type_u32: u32 = @bitCast(main_type_name_idx);
1697+
if (ident_u32 == main_type_u32) {
1698+
return true;
1699+
}
1700+
1701+
// Check if identifier starts with "MainType."
1702+
const main_type_name = self.getIdent(main_type_name_idx);
1703+
const ident_name = self.getIdent(ident_idx);
1704+
1705+
// Must be at least "MainType.x" in length
1706+
if (ident_name.len <= main_type_name.len + 1) {
1707+
return false;
1708+
}
1709+
1710+
// Check prefix match
1711+
if (!std.mem.startsWith(u8, ident_name, main_type_name)) {
1712+
return false;
1713+
}
1714+
1715+
// Check for dot separator
1716+
if (ident_name[main_type_name.len] != '.') {
1717+
return false;
1718+
}
1719+
1720+
return true;
1721+
},
1722+
else => return false,
1723+
}
1724+
}
1725+
16831726
/// Assert that nodes and regions are in sync
16841727
pub inline fn debugAssertArraysInSync(self: *const Self) void {
16851728
if (builtin.mode == .Debug) {

0 commit comments

Comments
 (0)