Skip to content

Canonicalizing exposes for modules #7968

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Jul 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/base/ModuleEnv.zig
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ idents: Ident.Store = .{},
ident_ids_for_slicing: collections.SafeList(Ident.Idx),
strings: StringLiteral.Store,
types: types_mod.Store,
/// Map of exposed items by their string representation (not interned)
/// This is built during canonicalization and preserved for later use
exposed_by_str: std.StringHashMapUnmanaged(void) = .{},
/// Map of exposed item names to their CIR node indices (stored as u16)
/// This is populated during canonicalization to allow cross-module lookups
exposed_nodes: std.StringHashMapUnmanaged(u16) = .{},

/// Line starts for error reporting. We retain only start and offset positions in the IR
/// and then use these line starts to calculate the line number and column number as required.
Expand Down Expand Up @@ -47,6 +53,8 @@ pub fn deinit(self: *Self) void {
self.strings.deinit(self.gpa);
self.types.deinit();
self.line_starts.deinit();
self.exposed_by_str.deinit(self.gpa);
self.exposed_nodes.deinit(self.gpa);
}

/// Calculate and store line starts from the source text
Expand Down
523 changes: 393 additions & 130 deletions src/check/canonicalize.zig

Large diffs are not rendered by default.

125 changes: 125 additions & 0 deletions src/check/canonicalize/CIR.zig
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ all_defs: Def.Span,
all_statements: Statement.Span,
/// All external declarations referenced in this module
external_decls: std.ArrayList(ExternalDecl),
/// Store for interned module imports
imports: Import.Store,

/// Initialize the IR for a module's canonicalization info.
///
Expand All @@ -77,13 +79,15 @@ pub fn init(env: *ModuleEnv) CIR {
.all_defs = .{ .span = .{ .start = 0, .len = 0 } },
.all_statements = .{ .span = .{ .start = 0, .len = 0 } },
.external_decls = std.ArrayList(ExternalDecl).init(env.gpa),
.imports = Import.Store.init(),
};
}

/// Deinit the IR's memory.
pub fn deinit(self: *CIR) void {
self.store.deinit();
self.external_decls.deinit();
self.imports.deinit(self.env.gpa);
}

/// Records a diagnostic error during canonicalization without blocking compilation.
Expand Down Expand Up @@ -159,6 +163,28 @@ pub fn diagnosticToReport(self: *CIR, diagnostic: Diagnostic, allocator: std.mem
const feature_text = self.env.strings.get(data.feature);
break :blk Diagnostic.buildNotImplementedReport(allocator, feature_text);
},
.exposed_but_not_implemented => |data| blk: {
const ident_name = self.env.idents.getText(data.ident);
const region_info = self.calcRegionInfo(data.region);
break :blk Diagnostic.buildExposedButNotImplementedReport(
allocator,
ident_name,
region_info,
filename,
);
},
.redundant_exposed => |data| blk: {
const ident_name = self.env.idents.getText(data.ident);
const region_info = self.calcRegionInfo(data.region);
const original_region_info = self.calcRegionInfo(data.original_region);
break :blk Diagnostic.buildRedundantExposedReport(
allocator,
ident_name,
region_info,
original_region_info,
filename,
);
},
.invalid_num_literal => |data| blk: {
break :blk Diagnostic.buildInvalidNumLiteralReport(
allocator,
Expand Down Expand Up @@ -248,6 +274,59 @@ pub fn diagnosticToReport(self: *CIR, diagnostic: Diagnostic, allocator: std.mem
);
},
.tuple_elem_not_canonicalized => Diagnostic.buildTupleElemNotCanonicalizedReport(allocator),
.module_not_found => |data| blk: {
const module_name = self.env.idents.getText(data.module_name);
const region_info = self.calcRegionInfo(data.region);
break :blk Diagnostic.buildModuleNotFoundReport(
allocator,
module_name,
region_info,
filename,
);
},
.value_not_exposed => |data| blk: {
const module_name = self.env.idents.getText(data.module_name);
const value_name = self.env.idents.getText(data.value_name);
const region_info = self.calcRegionInfo(data.region);
break :blk Diagnostic.buildValueNotExposedReport(
allocator,
module_name,
value_name,
region_info,
filename,
);
},
.type_not_exposed => |data| blk: {
const module_name = self.env.idents.getText(data.module_name);
const type_name = self.env.idents.getText(data.type_name);
const region_info = self.calcRegionInfo(data.region);
break :blk Diagnostic.buildTypeNotExposedReport(
allocator,
module_name,
type_name,
region_info,
filename,
);
},
.module_not_imported => |data| blk: {
const module_name = self.env.idents.getText(data.module_name);
const region_info = self.calcRegionInfo(data.region);
break :blk Diagnostic.buildModuleNotImportedReport(
allocator,
module_name,
region_info,
filename,
);
},
.too_many_exports => |data| blk: {
const region_info = self.calcRegionInfo(data.region);
break :blk Diagnostic.buildTooManyExportsReport(
allocator,
data.count,
region_info,
filename,
);
},
.undeclared_type => |data| blk: {
const type_name = self.env.idents.getText(data.name);
const region_info = self.calcRegionInfo(data.region);
Expand Down Expand Up @@ -693,6 +772,52 @@ pub const ExposedItem = struct {
}
};

/// An imported module
pub const Import = struct {
pub const Idx = enum(u16) { _ };

/// A store for interning imported module names
pub const Store = struct {
/// Map from module name string to Import.Idx
map: std.StringHashMapUnmanaged(Import.Idx) = .{},
/// List of imports indexed by Import.Idx
imports: std.ArrayListUnmanaged([]u8) = .{},
/// Storage for module name strings
strings: std.ArrayListUnmanaged(u8) = .{},

pub fn init() Store {
return .{};
}

pub fn deinit(self: *Store, gpa: std.mem.Allocator) void {
self.map.deinit(gpa);
self.imports.deinit(gpa);
self.strings.deinit(gpa);
}

/// Get or create an Import.Idx for a module name
pub fn getOrPut(self: *Store, gpa: std.mem.Allocator, module_name: []const u8) !Import.Idx {
const gop = try self.map.getOrPut(gpa, module_name);
if (!gop.found_existing) {
// Store the string
const start = self.strings.items.len;
try self.strings.appendSlice(gpa, module_name);
const stored_name = self.strings.items[start..];

const import_idx: Import.Idx = @enumFromInt(self.imports.items.len);
try self.imports.append(gpa, stored_name);
gop.value_ptr.* = import_idx;
}
return gop.value_ptr.*;
}

/// Get the module name for an Import.Idx
pub fn getModuleName(self: *const Store, idx: Import.Idx) []const u8 {
return self.imports.items[@intFromEnum(idx)];
}
};
};

/// A file of any type that has been ingested into a Roc module
/// as raw data, e.g. `import "lookups.txt" as lookups : Str`.
///
Expand Down
Loading
Loading