Skip to content

Commit 8f0b2f4

Browse files
authored
Merge pull request #8324 from roc-lang/builtin-str2
Add support for builtin Str
2 parents f62ebe3 + 4f21143 commit 8f0b2f4

File tree

102 files changed

+1029
-341
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

102 files changed

+1029
-341
lines changed

.github/workflows/ci_zig.yml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,7 @@ jobs:
111111
- name: Run Test Platforms (Unix)
112112
if: runner.os != 'Windows'
113113
run: |
114-
./zig-out/bin/roc --no-cache test/str/app.roc
115-
./zig-out/bin/roc --no-cache test/int/app.roc
114+
zig build test-cli
116115
117116
- name: Setup MSVC (Windows)
118117
if: runner.os == 'Windows'
@@ -123,8 +122,7 @@ jobs:
123122
- name: Run Test Platforms (Windows)
124123
if: runner.os == 'Windows'
125124
run: |
126-
zig-out\bin\roc.exe --no-cache test/str/app.roc
127-
zig-out\bin\roc.exe --no-cache test/int/app.roc
125+
zig build test-cli
128126
129127
- name: Build Test Platforms (cross-compile)
130128
if: runner.os != 'Windows'

build.zig

Lines changed: 49 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ pub fn build(b: *std.Build) void {
6363
const playground_step = b.step("playground", "Build the WASM playground");
6464
const playground_test_step = b.step("test-playground", "Build the integration test suite for the WASM playground");
6565
const serialization_size_step = b.step("test-serialization-sizes", "Verify Serialized types have platform-independent sizes");
66+
const test_cli_step = b.step("test-cli", "Test the roc CLI by running test programs");
6667

6768
// general configuration
6869
const target = blk: {
@@ -132,10 +133,6 @@ pub fn build(b: *std.Build) void {
132133

133134
const roc_modules = modules.RocModules.create(b, build_options, zstd);
134135

135-
const roc_exe = addMainExe(b, roc_modules, target, optimize, strip, enable_llvm, use_system_llvm, user_llvm_path, flag_enable_tracy, zstd) orelse return;
136-
roc_modules.addAll(roc_exe);
137-
install_and_run(b, no_bin, roc_exe, roc_step, run_step, run_args);
138-
139136
// Build-time compiler for builtin .roc modules with caching
140137
//
141138
// Changes to .roc files in src/build/roc/ are automatically detected and trigger recompilation.
@@ -176,6 +173,7 @@ pub fn build(b: *std.Build) void {
176173

177174
const write_compiled_builtins = b.addWriteFiles();
178175

176+
// Regenerate .bin files if necessary
179177
if (should_rebuild_builtins) {
180178
// Build and run the compiler
181179
const builtin_compiler_exe = b.addExecutable(.{
@@ -211,30 +209,25 @@ pub fn build(b: *std.Build) void {
211209
}
212210

213211
write_compiled_builtins.step.dependOn(&run_builtin_compiler.step);
212+
}
214213

215-
// Copy all generated .bin files from zig-out to build cache
216-
for (roc_files) |roc_path| {
217-
const roc_basename = std.fs.path.basename(roc_path);
218-
const name_without_ext = roc_basename[0 .. roc_basename.len - 4];
219-
const bin_filename = b.fmt("{s}.bin", .{name_without_ext});
214+
// Use .bin files from zig-out/builtins/ (whether they were just regenerated or
215+
// already there from a previous run).
216+
for (roc_files) |roc_path| {
217+
const roc_basename = std.fs.path.basename(roc_path);
218+
const name_without_ext = roc_basename[0 .. roc_basename.len - 4];
219+
const bin_filename = b.fmt("{s}.bin", .{name_without_ext});
220220

221-
_ = write_compiled_builtins.addCopyFile(
222-
.{ .cwd_relative = b.fmt("zig-out/builtins/{s}", .{bin_filename}) },
223-
bin_filename,
224-
);
225-
}
226-
} else {
227-
// Use existing .bin files from zig-out/builtins/
228-
for (roc_files) |roc_path| {
229-
const roc_basename = std.fs.path.basename(roc_path);
230-
const name_without_ext = roc_basename[0 .. roc_basename.len - 4];
231-
const bin_filename = b.fmt("{s}.bin", .{name_without_ext});
221+
_ = write_compiled_builtins.addCopyFile(
222+
.{ .cwd_relative = b.fmt("zig-out/builtins/{s}", .{bin_filename}) },
223+
bin_filename,
224+
);
232225

233-
_ = write_compiled_builtins.addCopyFile(
234-
.{ .cwd_relative = b.fmt("zig-out/builtins/{s}", .{bin_filename}) },
235-
bin_filename,
236-
);
237-
}
226+
// Also copy the source .roc file for embedding
227+
_ = write_compiled_builtins.addCopyFile(
228+
b.path(roc_path),
229+
roc_basename,
230+
);
238231
}
239232

240233
// Also copy builtin_indices.bin
@@ -260,6 +253,26 @@ pub fn build(b: *std.Build) void {
260253

261254
roc_modules.repl.addImport("compiled_builtins", compiled_builtins_module);
262255
roc_modules.compile.addImport("compiled_builtins", compiled_builtins_module);
256+
roc_modules.eval.addImport("compiled_builtins", compiled_builtins_module);
257+
258+
const roc_exe = addMainExe(b, roc_modules, target, optimize, strip, enable_llvm, use_system_llvm, user_llvm_path, flag_enable_tracy, zstd, compiled_builtins_module, write_compiled_builtins) orelse return;
259+
roc_modules.addAll(roc_exe);
260+
install_and_run(b, no_bin, roc_exe, roc_step, run_step, run_args);
261+
262+
// CLI integration tests - run actual roc programs like CI does
263+
if (!no_bin) {
264+
const install = b.addInstallArtifact(roc_exe, .{});
265+
266+
// Test int platform
267+
const test_int = b.addSystemCommand(&.{ b.getInstallPath(.bin, "roc"), "--no-cache", "test/int/app.roc" });
268+
test_int.step.dependOn(&install.step);
269+
test_cli_step.dependOn(&test_int.step);
270+
271+
// Test str platform
272+
const test_str = b.addSystemCommand(&.{ b.getInstallPath(.bin, "roc"), "--no-cache", "test/str/app.roc" });
273+
test_str.step.dependOn(&install.step);
274+
test_cli_step.dependOn(&test_str.step);
275+
}
263276

264277
// Manual rebuild command: zig build rebuild-builtins
265278
// Use this after making compiler changes to ensure those changes are reflected in builtins
@@ -647,6 +660,12 @@ fn generateCompiledBuiltinsSource(b: *std.Build, roc_files: []const []const u8)
647660
lower_name,
648661
name_without_ext,
649662
});
663+
664+
// Also embed the source .roc file
665+
try writer.print("pub const {s}_source = @embedFile(\"{s}\");\n", .{
666+
lower_name,
667+
roc_basename,
668+
});
650669
}
651670

652671
// Also embed builtin_indices.bin
@@ -728,6 +747,8 @@ fn addMainExe(
728747
user_llvm_path: ?[]const u8,
729748
tracy: ?[]const u8,
730749
zstd: *Dependency,
750+
compiled_builtins_module: *std.Build.Module,
751+
write_compiled_builtins: *Step.WriteFile,
731752
) ?*Step.Compile {
732753
const exe = b.addExecutable(.{
733754
.name = "roc",
@@ -859,6 +880,9 @@ fn addMainExe(
859880
configureBackend(shim_lib, target);
860881
// Add all modules from roc_modules that the shim needs
861882
roc_modules.addAll(shim_lib);
883+
// Add compiled builtins module for loading builtin types
884+
shim_lib.root_module.addImport("compiled_builtins", compiled_builtins_module);
885+
shim_lib.step.dependOn(&write_compiled_builtins.step);
862886
// Link against the pre-built builtins library
863887
shim_lib.addObject(builtins_obj);
864888
// Bundle compiler-rt for our math symbols

src/build/builtin_compiler/main.zig

Lines changed: 106 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,73 @@ const BuiltinIndices = struct {
2626
bool_type: CIR.Statement.Idx,
2727
/// Statement index of Result type declaration within Result module
2828
result_type: CIR.Statement.Idx,
29+
/// Statement index of Dict type declaration within Dict module
30+
dict_type: CIR.Statement.Idx,
31+
/// Statement index of Set type declaration within Set module
32+
set_type: CIR.Statement.Idx,
33+
/// Statement index of Str type declaration within Str module
34+
str_type: CIR.Statement.Idx,
2935
};
3036

37+
/// Transform all Str nominal types to .str primitive types in a module.
38+
/// This is necessary because the interpreter needs .str to be a primitive type,
39+
/// but we define methods on Str as a nominal type in Str.roc for ergonomics.
40+
///
41+
/// This transformation happens after type-checking but before serialization,
42+
/// ensuring that the serialized .bin file contains methods associated with
43+
/// the .str primitive type rather than a nominal Str type.
44+
fn transformStrNominalToPrimitive(env: *ModuleEnv) !void {
45+
const types_mod = @import("types");
46+
const Content = types_mod.Content;
47+
const FlatType = types_mod.FlatType;
48+
49+
// Get the Str identifier in this module
50+
const str_ident_opt = env.common.findIdent("Str");
51+
if (str_ident_opt == null) {
52+
// No Str ident found, nothing to transform
53+
return;
54+
}
55+
const str_ident = str_ident_opt.?;
56+
57+
// Iterate through all slots in the type store
58+
var i: u32 = 0;
59+
while (i < env.types.len()) : (i += 1) {
60+
const var_idx = @as(types_mod.Var, @enumFromInt(i));
61+
62+
// Skip redirects, only process roots
63+
if (env.types.isRedirect(var_idx)) {
64+
continue;
65+
}
66+
67+
const resolved = env.types.resolveVar(var_idx);
68+
const desc = resolved.desc;
69+
70+
// Check if this descriptor contains a nominal type
71+
switch (desc.content) {
72+
.structure => |structure| {
73+
switch (structure) {
74+
.nominal_type => |nominal| {
75+
// Check if this is the Str nominal type
76+
// TypeIdent has an ident_idx field that references the identifier
77+
if (nominal.ident.ident_idx == str_ident) {
78+
// Replace with .str primitive type
79+
const new_content = Content{ .structure = FlatType.str };
80+
const new_desc = types_mod.Descriptor{
81+
.content = new_content,
82+
.rank = desc.rank,
83+
.mark = desc.mark,
84+
};
85+
try env.types.setVarDesc(var_idx, new_desc);
86+
}
87+
},
88+
else => {},
89+
}
90+
},
91+
else => {},
92+
}
93+
}
94+
}
95+
3196
/// Build-time compiler that compiles builtin .roc sources into serialized ModuleEnvs.
3297
/// This runs during `zig build` on the host machine to generate .bin files
3398
/// that get embedded into the final roc executable.
@@ -56,6 +121,8 @@ pub fn main() !void {
56121

57122
const set_roc_source = try std.fs.cwd().readFileAlloc(gpa, "src/build/roc/Set.roc", 1024 * 1024);
58123

124+
const str_roc_source = try std.fs.cwd().readFileAlloc(gpa, "src/build/roc/Str.roc", 1024 * 1024);
125+
59126
// Compile Bool.roc (it's completely self-contained, doesn't use Bool or Result types)
60127
const bool_env = try compileModule(
61128
gpa,
@@ -124,6 +191,37 @@ pub fn main() !void {
124191
gpa.free(set_roc_source);
125192
}
126193

194+
// Find Dict type declaration via string lookup
195+
const dict_type_idx = try findTypeDeclaration(dict_env, "Dict");
196+
197+
// Find Set type declaration via string lookup
198+
const set_type_idx = try findTypeDeclaration(set_env, "Set");
199+
200+
// Compile Str.roc (uses Bool type in method signatures)
201+
const str_env = try compileModule(
202+
gpa,
203+
"Str",
204+
str_roc_source,
205+
&[_]ModuleDep{
206+
.{ .name = "Bool", .env = bool_env },
207+
},
208+
bool_type_idx, // Provide Bool type index
209+
result_type_idx, // Provide Result type index
210+
);
211+
defer {
212+
str_env.deinit();
213+
gpa.destroy(str_env);
214+
gpa.free(str_roc_source);
215+
}
216+
217+
// Find Str type declaration via string lookup
218+
const str_type_idx = try findTypeDeclaration(str_env, "Str");
219+
220+
// Transform Str nominal types to .str primitive types
221+
// This must happen BEFORE serialization to ensure the .bin file contains
222+
// methods associated with the .str primitive, not a nominal type
223+
try transformStrNominalToPrimitive(str_env);
224+
127225
// Create output directory
128226
try std.fs.cwd().makePath("zig-out/builtins");
129227

@@ -132,11 +230,15 @@ pub fn main() !void {
132230
try serializeModuleEnv(gpa, result_env, "zig-out/builtins/Result.bin");
133231
try serializeModuleEnv(gpa, dict_env, "zig-out/builtins/Dict.bin");
134232
try serializeModuleEnv(gpa, set_env, "zig-out/builtins/Set.bin");
233+
try serializeModuleEnv(gpa, str_env, "zig-out/builtins/Str.bin");
135234

136235
// Create and serialize builtin indices
137236
const builtin_indices = BuiltinIndices{
138237
.bool_type = bool_type_idx,
139238
.result_type = result_type_idx,
239+
.dict_type = dict_type_idx,
240+
.set_type = set_type_idx,
241+
.str_type = str_type_idx,
140242
};
141243
try serializeBuiltinIndices(builtin_indices, "zig-out/builtins/builtin_indices.bin");
142244
}
@@ -210,13 +312,11 @@ fn compileModule(
210312
var module_envs = std.AutoHashMap(base.Ident.Idx, Can.AutoImportedType).init(gpa);
211313
defer module_envs.deinit();
212314

213-
// Create temporary ident store for module name lookup
214-
var temp_idents = try base.Ident.Store.initCapacity(gpa, 16);
215-
defer temp_idents.deinit(gpa);
216-
217-
// Add dependencies (e.g., Dict for Set)
315+
// Add dependencies (e.g., Dict for Set, Bool for Str)
316+
// IMPORTANT: Use the module's own ident store, not a temporary one,
317+
// because auto-import lookups will use the module's ident store
218318
for (deps) |dep| {
219-
const dep_ident = try temp_idents.insert(gpa, base.Ident.for_text(dep.name));
319+
const dep_ident = try module_env.insertIdent(base.Ident.for_text(dep.name));
220320
try module_envs.put(dep_ident, .{ .env = dep.env });
221321
}
222322

src/build/roc/Str.roc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Str := [ProvidedByCompiler].{
2+
is_empty : Str -> Bool
3+
is_empty = |_str| False
4+
5+
contains : Str, Str -> Bool
6+
contains = |_str, _other| True
7+
}

src/canonicalize/CIR.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ pub const BuiltinIndices = struct {
3838
dict_type: Statement.Idx,
3939
/// Statement index of Set type declaration within Set module
4040
set_type: Statement.Idx,
41+
/// Statement index of Str type declaration within Str module
42+
str_type: Statement.Idx,
4143
};
4244

4345
// Type definitions for module compilation

src/canonicalize/Can.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ pub fn init(
244244
};
245245

246246
// Only auto-import known builtin modules
247-
const builtin_modules = [_][]const u8{ "Bool", "Result" };
247+
const builtin_modules = [_][]const u8{ "Bool", "Result", "Str" };
248248
for (builtin_modules) |module_name_text| {
249249
const module_name_ident = try env.insertIdent(base.Ident.for_text(module_name_text));
250250
if (envs_map.get(module_name_ident)) |_| {

src/canonicalize/TypeAnnotation.zig

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,6 @@ pub const TypeAnno = union(enum) {
406406

407407
/// Convert a type name string to the corresponding builtin type
408408
pub fn fromBytes(bytes: []const u8) ?@This() {
409-
if (std.mem.eql(u8, bytes, "Str")) return .str;
410409
if (std.mem.eql(u8, bytes, "List")) return .list;
411410
if (std.mem.eql(u8, bytes, "Num")) return .num;
412411
if (std.mem.eql(u8, bytes, "Frac")) return .frac;

0 commit comments

Comments
 (0)