@@ -133,7 +133,7 @@ const RecordField = CIR.RecordField;
133133const SeenRecordField = struct { ident : base.Ident.Idx , region : base .Region };
134134
135135/// The idx of the builtin Bool
136- pub const BUILTIN_BOOL : Statement.Idx = @enumFromInt (4 );
136+ pub const BUILTIN_BOOL : Statement.Idx = @enumFromInt (2 );
137137/// The idx of the builtin Result
138138pub const BUILTIN_RESULT : Statement.Idx = @enumFromInt (13 );
139139
@@ -171,10 +171,22 @@ pub fn deinit(
171171 self .scratch_free_vars .deinit (gpa );
172172}
173173
174+ /// Options for initializing the canonicalizer.
175+ /// Controls which built-in types are injected into the module's scope.
176+ pub const InitOptions = struct {
177+ /// Whether to inject the Bool type declaration (`Bool := [True, False]`).
178+ /// Set to false when compiling Bool.roc itself to avoid duplication.
179+ inject_bool : bool = true ,
180+ /// Whether to inject the Result type declaration (`Result(ok, err) := [Ok(ok), Err(err)]`).
181+ /// Set to false when compiling Result.roc itself (if it exists).
182+ inject_result : bool = true ,
183+ };
184+
174185pub fn init (
175186 env : * ModuleEnv ,
176187 parse_ir : * AST ,
177188 module_envs : ? * const std .StringHashMap (* const ModuleEnv ),
189+ options : InitOptions ,
178190) std .mem .Allocator .Error ! Self {
179191 const gpa = env .gpa ;
180192
@@ -207,16 +219,28 @@ pub fn init(
207219
208220 const scratch_statements_start = result .env .store .scratch_statements .top ();
209221
210- // Simulate the builtins by add type declarations
222+ // Inject built-in type declarations that aren't defined in this module's source
211223 // TODO: These should ultimately come from the platform/builtin files rather than being hardcoded
212- try result .addBuiltinTypeBool (env );
213- try result .addBuiltinTypeResult (env );
224+ if (options .inject_bool ) {
225+ const bool_idx = try result .addBuiltinTypeBool (env );
226+ try result .env .store .addScratchStatement (bool_idx );
227+ }
228+ if (options .inject_result ) {
229+ const result_idx = try result .addBuiltinTypeResult (env );
230+ try result .env .store .addScratchStatement (result_idx );
231+ }
214232
215- // Add builtins to builtin stmts
216- try result .env .store .addScratchStatement (BUILTIN_BOOL );
217- try result .env .store .addScratchStatement (BUILTIN_RESULT );
218233 result .env .builtin_statements = try result .env .store .statementSpanFrom (scratch_statements_start );
219234
235+ // Debug assertion: When Bool is injected, it must be the first builtin statement
236+ if (std .debug .runtime_safety and options .inject_bool ) {
237+ const builtin_stmts = result .env .store .sliceStatements (result .env .builtin_statements );
238+ std .debug .assert (builtin_stmts .len >= 1 ); // Must have at least Bool
239+ // Verify first builtin is Bool by checking it's a nominal_decl
240+ const first_stmt = result .env .store .getStatement (builtin_stmts [0 ]);
241+ std .debug .assert (first_stmt == .s_nominal_decl );
242+ }
243+
220244 // Assert that the node store is completely empty
221245 env .debugAssertArraysInSync ();
222246
@@ -226,7 +250,8 @@ pub fn init(
226250// builtins //
227251
228252/// Creates `Bool := [True, False]`
229- fn addBuiltinTypeBool (self : * Self , ir : * ModuleEnv ) std.mem.Allocator.Error ! void {
253+ /// Returns the statement index where Bool was created
254+ fn addBuiltinTypeBool (self : * Self , ir : * ModuleEnv ) std.mem.Allocator.Error ! Statement.Idx {
230255 const gpa = ir .gpa ;
231256 const type_ident = try ir .insertIdent (base .Ident .for_text ("Bool" ));
232257 const true_ident = try ir .insertIdent (base .Ident .for_text ("True" ));
@@ -268,8 +293,10 @@ fn addBuiltinTypeBool(self: *Self, ir: *ModuleEnv) std.mem.Allocator.Error!void
268293 .s_nominal_decl = .{ .header = header_idx , .anno = tag_union_anno_idx },
269294 }, .err , Region .zero ());
270295
271- // Assert that this is the first stmt in the file
272- std .debug .assert (type_decl_idx == BUILTIN_BOOL );
296+ // Note: When Bool.roc is compiled without injecting builtins, Bool is at absolute index 2 (BUILTIN_BOOL).
297+ // This is verified at build time by the builtin_compiler.
298+ // When builtins are injected into other modules, Bool is always the FIRST builtin (builtin_statements[0]),
299+ // though its absolute statement index may differ from BUILTIN_BOOL.
273300
274301 // Introduce to scope
275302 const current_scope = & self .scopes .items [self .scopes .items .len - 1 ];
@@ -279,10 +306,13 @@ fn addBuiltinTypeBool(self: *Self, ir: *ModuleEnv) std.mem.Allocator.Error!void
279306 // TODO: in the future, we should have hardcoded constants for these.
280307 try self .unqualified_nominal_tags .put (gpa , "True" , type_decl_idx );
281308 try self .unqualified_nominal_tags .put (gpa , "False" , type_decl_idx );
309+
310+ return type_decl_idx ;
282311}
283312
284313/// Creates `Result(ok, err) := [Ok(ok), Err(err)]`
285- fn addBuiltinTypeResult (self : * Self , ir : * ModuleEnv ) std.mem.Allocator.Error ! void {
314+ /// Returns the statement index where Result was created
315+ fn addBuiltinTypeResult (self : * Self , ir : * ModuleEnv ) std.mem.Allocator.Error ! Statement.Idx {
286316 const gpa = ir .gpa ;
287317 const type_ident = try ir .insertIdent (base .Ident .for_text ("Result" ));
288318 const ok_tag_ident = try ir .insertIdent (base .Ident .for_text ("Ok" ));
@@ -357,17 +387,19 @@ fn addBuiltinTypeResult(self: *Self, ir: *ModuleEnv) std.mem.Allocator.Error!voi
357387 Region .zero (),
358388 );
359389
360- // Assert that this is the first stmt in the file
361- std .debug .assert (type_decl_idx == BUILTIN_RESULT );
390+ // Note: When Result.roc is compiled without injecting builtins, Result ends up at index 13 (BUILTIN_RESULT)
391+ // This is verified during build time.
392+ // When builtins are injected into other modules (Dict, Set), Result can be at any index.
362393
363394 // Add to scope
364395 const current_scope = & self .scopes .items [self .scopes .items .len - 1 ];
365396 try current_scope .put (gpa , .type_decl , type_ident , type_decl_idx );
366397
367- // Add True and False to unqualified_nominal_tags
368- // TODO: in the future, we should have hardcoded constants for these.
398+ // Add Ok and Err to unqualified_nominal_tags
369399 try self .unqualified_nominal_tags .put (gpa , "Ok" , type_decl_idx );
370400 try self .unqualified_nominal_tags .put (gpa , "Err" , type_decl_idx );
401+
402+ return type_decl_idx ;
371403}
372404
373405// canonicalize //
0 commit comments