Skip to content
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
120 changes: 120 additions & 0 deletions src/canonicalize/Can.zig
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,14 @@ pub fn canonicalizeFile(
// but we need to track the provided functions for export
try self.createExposedScope(h.provides);
},
.type_module => {
// Type modules don't have an exposes list
// We'll validate the type name matches the module name after processing types
},
.default_app => {
// Default app modules don't have an exposes list
// They have a main! function that will be validated
},
.malformed => {
// Skip malformed headers
},
Expand Down Expand Up @@ -509,6 +517,29 @@ pub fn canonicalizeFile(
}
}

// After processing all type declarations, validate type module constraints
switch (header) {
.type_module => {
// First check if there's a main! function (for default_app modules)
const main_result = try self.findMainFunction();
switch (main_result) {
.valid => |main_info| {
// This is actually a default_app module - main! was found with correct arity
// Store the main function index in the header
_ = main_info; // TODO: update header with main_fn_idx
},
.wrong_arity => {
// main! was found but with wrong arity - error already reported
},
.not_found => {
// No main! found, so validate as a regular type module
try self.validateTypeModuleName();
},
}
},
else => {},
}

// Second pass: Process all other statements
const ast_stmt_idxs = self.parse_ir.store.statementSlice(file.statements);
var i: usize = 0;
Expand Down Expand Up @@ -6951,6 +6982,95 @@ fn createUnknownIdent(self: *Self) std.mem.Allocator.Error!Ident.Idx {
return try self.env.insertIdent(base.Ident.for_text("unknown"));
}

/// Check if this module has a main! function suitable for default_app
/// Returns the def index if found and valid, null otherwise
const MainFunctionResult = union(enum) {
valid: struct { def_idx: CIR.Def.Idx, region: Region },
wrong_arity: struct { arity: u32, region: Region },
not_found,
};

fn findMainFunction(self: *Self) std.mem.Allocator.Error!MainFunctionResult {
const file = self.parse_ir.store.getFile();

// Look through all statements for a definition of main!
for (self.parse_ir.store.statementSlice(file.statements)) |stmt_id| {
const stmt = self.parse_ir.store.getStatement(stmt_id);
if (stmt == .decl) {
const decl = stmt.decl;
// Check if this is a definition with name "main!"
const pattern = self.parse_ir.store.getPattern(decl.pattern);
if (pattern == .ident) {
const ident_token = pattern.ident.ident_tok;
const ident_idx = self.parse_ir.tokens.resolveIdentifier(ident_token) orelse continue;
const ident_text = self.env.getIdent(ident_idx);

if (std.mem.eql(u8, ident_text, "main!")) {
const region = self.parse_ir.tokenizedRegionToRegion(decl.region);

// Found main! - now check if it's a lambda with exactly 1 parameter
const expr = self.parse_ir.store.getExpr(decl.body);
if (expr == .lambda) {
const lambda = expr.lambda;
const params = self.parse_ir.store.patternSlice(lambda.args);

if (params.len == 1) {
// Valid main! function found
return .{ .valid = .{ .def_idx = @enumFromInt(0), .region = region } }; // TODO: get actual CIR def idx
} else {
// main! found but with wrong arity
try self.env.pushDiagnostic(Diagnostic{ .default_app_wrong_arity = .{
.arity = @intCast(params.len),
.region = region,
} });
return .{ .wrong_arity = .{ .arity = @intCast(params.len), .region = region } };
}
}
}
}
}
}

return .not_found;
}

/// Validate that a type module has a type declaration matching the module name.
/// For example, if the module is named "Foo", there must be a `Foo := ...` or `Foo : ...` at the top level.
fn validateTypeModuleName(self: *Self) std.mem.Allocator.Error!void {
const trace = tracy.trace(@src());
defer trace.end();

const file = self.parse_ir.store.getFile();
const module_name_text = self.env.module_name;
const module_name_ident = try self.env.insertIdent(base.Ident.for_text(module_name_text));

// Look through all statements for a type declaration matching the module name
for (self.parse_ir.store.statementSlice(file.statements)) |stmt_id| {
const stmt = self.parse_ir.store.getStatement(stmt_id);
if (stmt == .type_decl) {
const type_decl = stmt.type_decl;
// Get the type name from the header
const header = self.parse_ir.store.getTypeHeader(type_decl.header) catch continue;
const type_name_ident = self.parse_ir.tokens.resolveIdentifier(header.name) orelse continue;
const type_name_text = self.env.getIdent(type_name_ident);

if (std.mem.eql(u8, type_name_text, module_name_text)) {
// Found a matching type declaration!
return;
}
}
}

// No matching type declaration found - report error
const file_region = self.parse_ir.tokenizedRegionToRegion(file.region);
try self.env.pushDiagnostic(Diagnostic{
.type_module_missing_matching_type = .{
.module_name = module_name_ident,
.region = file_region,
},
});
}

// We write out this giant literal because it's actually annoying to try to
// take std.math.minInt(i128), drop the minus sign, and convert it to u128
// all at comptime. Instead we just have a test that verifies its correctness.
Expand Down
30 changes: 30 additions & 0 deletions src/canonicalize/Diagnostic.zig
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,30 @@ pub const Diagnostic = union(enum) {
crash_expects_string: struct {
region: Region,
},
type_module_missing_matching_type: struct {
module_name: Ident.Idx,
region: Region,
},
default_app_missing_main: struct {
module_name: Ident.Idx,
region: Region,
},
default_app_wrong_arity: struct {
arity: u32,
region: Region,
},
cannot_import_default_app: struct {
module_name: Ident.Idx,
region: Region,
},
execution_requires_app_or_default_app: struct {
region: Region,
},
type_name_case_mismatch: struct {
module_name: Ident.Idx,
type_name: Ident.Idx,
region: Region,
},
type_alias_redeclared: struct {
name: Ident.Idx,
original_region: Region,
Expand Down Expand Up @@ -233,6 +257,12 @@ pub const Diagnostic = union(enum) {
.undeclared_type_var => |d| d.region,
.type_alias_but_needed_nominal => |d| d.region,
.crash_expects_string => |d| d.region,
.type_module_missing_matching_type => |d| d.region,
.default_app_missing_main => |d| d.region,
.default_app_wrong_arity => |d| d.region,
.cannot_import_default_app => |d| d.region,
.execution_requires_app_or_default_app => |d| d.region,
.type_name_case_mismatch => |d| d.region,
.type_alias_redeclared => |d| d.redeclared_region,
.nominal_type_redeclared => |d| d.redeclared_region,
.type_shadowed_warning => |d| d.region,
Expand Down
Loading