Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
a5ba2c4
Add Decode and LineFmt modules to Builtin.roc
rtfeldman Jan 1, 2026
1a2c084
Merge origin/main into decode
rtfeldman Jan 1, 2026
3176f65
Fix polymorphic tag union layout mismatch in tag_construct
rtfeldman Jan 1, 2026
b64683b
Remove LineFmt from builtins, keep it only in tests
rtfeldman Jan 1, 2026
7bdb925
Merge remote-tracking branch 'origin/main' into decode
rtfeldman Jan 1, 2026
df15c0f
Remove separator comments that fail CI lint
rtfeldman Jan 1, 2026
7f3e1b5
Format Builtin.roc
rtfeldman Jan 1, 2026
96b4c39
Fix recursive type crashes by reverting problematic layout changes
rtfeldman Jan 1, 2026
7cffa3f
Remove defensive bounds checking that silently ignored errors
rtfeldman Jan 1, 2026
9e9faee
Remove LineFmt from non-test code, replace safeCopy with @memmove
rtfeldman Jan 2, 2026
39e6df3
Simplify Decoder type to parameterized record
rtfeldman Jan 2, 2026
3856d75
Make Decoder a top-level type, remove Decode wrapper
rtfeldman Jan 2, 2026
847bd62
Merge remote-tracking branch 'origin/main' into decode
rtfeldman Jan 2, 2026
a491a7c
Handle s_alias_decl in type declaration lookups
rtfeldman Jan 2, 2026
ef3e8bf
Merge remote-tracking branch 'origin/main' into decode
rtfeldman Jan 2, 2026
83c4a2c
Add decode method to numeric types using where clause pattern
rtfeldman Jan 3, 2026
2531581
Merge remote-tracking branch 'origin/main' into decode
rtfeldman Jan 3, 2026
c2c92a4
Remove unconditional debug prints that broke wasm32 build
rtfeldman Jan 3, 2026
0543a4e
Skip decode tests and remove forbidden string comparisons
rtfeldman Jan 3, 2026
f41654b
Merge remote-tracking branch 'origin/main' into decode
rtfeldman Jan 3, 2026
1275ed1
Fix e_tag regression for recursive nominal types
rtfeldman Jan 3, 2026
41f1bd6
Merge origin/main and fix layout_rt_var conflict
rtfeldman Jan 4, 2026
d3963f9
Type variable dispatch improvements and bounds check cleanup
rtfeldman Jan 4, 2026
8afc119
Merge origin/main, resolve captures_layout_idx conflict
rtfeldman Jan 4, 2026
11f1a14
Fix resolveTypeAnnoRef: single hop in production, debug validates ass…
rtfeldman Jan 4, 2026
4795b05
Remove useless resolveTypeAnnoRef function
rtfeldman Jan 4, 2026
d2269dc
Merge remote-tracking branch 'origin/main' into decode
rtfeldman Jan 4, 2026
21f4665
Fix structural empty record unification with nominal types
rtfeldman Jan 4, 2026
3d34113
Merge remote-tracking branch 'origin/main' into decode
rtfeldman Jan 4, 2026
6a60433
Remove dead createTagUnionLayout function
rtfeldman Jan 9, 2026
830e913
Merge origin/main into decode
rtfeldman Jan 9, 2026
9f9cc59
Merge origin/main into decode
rtfeldman Jan 10, 2026
b0ac688
Merge remote-tracking branch 'origin/main' into decode
rtfeldman Jan 10, 2026
b026af2
Remove implicit structural-to-nominal type conversion for method calls
rtfeldman Jan 11, 2026
8e0e544
Remove unused current_block_statements field
rtfeldman Jan 11, 2026
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
18 changes: 9 additions & 9 deletions src/build/builtin_compiler/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1824,16 +1824,16 @@ fn findTypeDeclaration(env: *const ModuleEnv, type_name: []const u8) !CIR.Statem
const all_stmts = env.store.sliceStatements(env.all_statements);
for (all_stmts) |stmt_idx| {
const stmt = env.store.getStatement(stmt_idx);
switch (stmt) {
.s_nominal_decl => |decl| {
const header = env.store.getTypeHeader(decl.header);
const ident_idx = header.name;
const ident_text = env.getIdentText(ident_idx);
if (std.mem.eql(u8, ident_text, qualified_name)) {
return stmt_idx;
}
},
const header_idx = switch (stmt) {
.s_nominal_decl => |decl| decl.header,
.s_alias_decl => |alias| alias.header,
else => continue,
};
const header = env.store.getTypeHeader(header_idx);
const ident_idx = header.name;
const ident_text = env.getIdentText(ident_idx);
if (std.mem.eql(u8, ident_text, qualified_name)) {
return stmt_idx;
}
}

Expand Down
119 changes: 119 additions & 0 deletions src/build/roc/Builtin.roc
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ Builtin :: [].{
encode = |self, format| {
format.encode_str(self)
}

decode : src, fmt -> (Try(Str, err), src)
where [fmt.decode_str : fmt, src -> (Try(Str, err), src)]
decode = |source, format| {
Fmt : fmt
Fmt.decode_str(format, source)
}
}

List(_item) :: [ProvidedByCompiler].{
Expand Down Expand Up @@ -269,6 +276,18 @@ Builtin :: [].{
format.encode_list(self, |elem, f| elem.encode(f))
}

# Decode a list using a format that provides decode_list
decode : src, fmt -> (Try(List(item), err), src)
where [
fmt.decode_list : fmt, src, (src, fmt -> (Try(item, err), src)) -> (Try(List(item), err), src),
item.decode : src, fmt -> (Try(item, err), src),
]
decode = |source, format| {
Fmt : fmt
Item : item
Fmt.decode_list(format, source, |s, f| Item.decode(s, f))
}

}

Bool := [False, True].{
Expand All @@ -286,6 +305,13 @@ Builtin :: [].{
encode = |self, format| {
format.encode_bool(self)
}

decode : src, fmt -> (Try(Bool, err), src)
where [fmt.decode_bool : fmt, src -> (Try(Bool, err), src)]
decode = |source, format| {
Fmt : fmt
Fmt.decode_bool(format, source)
}
}

Box(item) :: [ProvidedByCompiler].{
Expand Down Expand Up @@ -465,6 +491,13 @@ Builtin :: [].{
encode = |self, format| {
format.encode_u8(self)
}

decode : src, fmt -> (Try(U8, err), src)
where [fmt.decode_u8 : fmt, src -> (Try(U8, err), src)]
decode = |source, format| {
Fmt : fmt
Fmt.decode_u8(format, source)
}
}

I8 :: [].{
Expand Down Expand Up @@ -541,6 +574,13 @@ Builtin :: [].{
encode = |self, format| {
format.encode_i8(self)
}

decode : src, fmt -> (Try(I8, err), src)
where [fmt.decode_i8 : fmt, src -> (Try(I8, err), src)]
decode = |source, format| {
Fmt : fmt
Fmt.decode_i8(format, source)
}
}

U16 :: [].{
Expand Down Expand Up @@ -611,6 +651,13 @@ Builtin :: [].{
encode = |self, format| {
format.encode_u16(self)
}

decode : src, fmt -> (Try(U16, err), src)
where [fmt.decode_u16 : fmt, src -> (Try(U16, err), src)]
decode = |source, format| {
Fmt : fmt
Fmt.decode_u16(format, source)
}
}

I16 :: [].{
Expand Down Expand Up @@ -688,6 +735,13 @@ Builtin :: [].{
encode = |self, format| {
format.encode_i16(self)
}

decode : src, fmt -> (Try(I16, err), src)
where [fmt.decode_i16 : fmt, src -> (Try(I16, err), src)]
decode = |source, format| {
Fmt : fmt
Fmt.decode_i16(format, source)
}
}

U32 :: [].{
Expand Down Expand Up @@ -760,6 +814,13 @@ Builtin :: [].{
encode = |self, format| {
format.encode_u32(self)
}

decode : src, fmt -> (Try(U32, err), src)
where [fmt.decode_u32 : fmt, src -> (Try(U32, err), src)]
decode = |source, format| {
Fmt : fmt
Fmt.decode_u32(format, source)
}
}

I32 :: [].{
Expand Down Expand Up @@ -838,6 +899,14 @@ Builtin :: [].{
encode = |self, format| {
format.encode_i32(self)
}

# Decode an I32 using a format that provides decode_i32
decode : src, fmt -> (Try(I32, err), src)
where [fmt.decode_i32 : fmt, src -> (Try(I32, err), src)]
decode = |source, format| {
Fmt : fmt
Fmt.decode_i32(format, source)
}
}

U64 :: [].{
Expand Down Expand Up @@ -912,6 +981,13 @@ Builtin :: [].{
encode = |self, format| {
format.encode_u64(self)
}

decode : src, fmt -> (Try(U64, err), src)
where [fmt.decode_u64 : fmt, src -> (Try(U64, err), src)]
decode = |source, format| {
Fmt : fmt
Fmt.decode_u64(format, source)
}
}

I64 :: [].{
Expand Down Expand Up @@ -991,6 +1067,13 @@ Builtin :: [].{
encode = |self, format| {
format.encode_i64(self)
}

decode : src, fmt -> (Try(I64, err), src)
where [fmt.decode_i64 : fmt, src -> (Try(I64, err), src)]
decode = |source, format| {
Fmt : fmt
Fmt.decode_i64(format, source)
}
}

U128 :: [].{
Expand Down Expand Up @@ -1069,6 +1152,13 @@ Builtin :: [].{
encode = |self, format| {
format.encode_u128(self)
}

decode : src, fmt -> (Try(U128, err), src)
where [fmt.decode_u128 : fmt, src -> (Try(U128, err), src)]
decode = |source, format| {
Fmt : fmt
Fmt.decode_u128(format, source)
}
}

I128 :: [].{
Expand Down Expand Up @@ -1151,6 +1241,13 @@ Builtin :: [].{
encode = |self, format| {
format.encode_i128(self)
}

decode : src, fmt -> (Try(I128, err), src)
where [fmt.decode_i128 : fmt, src -> (Try(I128, err), src)]
decode = |source, format| {
Fmt : fmt
Fmt.decode_i128(format, source)
}
}

Dec :: [].{
Expand Down Expand Up @@ -1229,6 +1326,13 @@ Builtin :: [].{
encode = |self, format| {
format.encode_dec(self)
}

decode : src, fmt -> (Try(Dec, err), src)
where [fmt.decode_dec : fmt, src -> (Try(Dec, err), src)]
decode = |source, format| {
Fmt : fmt
Fmt.decode_dec(format, source)
}
}

F32 :: [].{
Expand Down Expand Up @@ -1292,6 +1396,13 @@ Builtin :: [].{
encode = |self, format| {
format.encode_f32(self)
}

decode : src, fmt -> (Try(F32, err), src)
where [fmt.decode_f32 : fmt, src -> (Try(F32, err), src)]
decode = |source, format| {
Fmt : fmt
Fmt.decode_f32(format, source)
}
}

F64 :: [].{
Expand Down Expand Up @@ -1365,8 +1476,16 @@ Builtin :: [].{
encode = |self, format| {
format.encode_f64(self)
}

decode : src, fmt -> (Try(F64, err), src)
where [fmt.decode_f64 : fmt, src -> (Try(F64, err), src)]
decode = |source, format| {
Fmt : fmt
Fmt.decode_f64(format, source)
}
}
}

}

range_to = |var $current, end| {
Expand Down
39 changes: 29 additions & 10 deletions src/canonicalize/Can.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4327,8 +4327,12 @@ pub fn canonicalizeExpr(
if (self.module_envs.?.get(module_alias)) |auto_imported_type_env| {
const module_env = auto_imported_type_env.env;

// Get the qualified name of the method (e.g., "Str.is_empty")
const qualified_text = self.env.getIdent(type_qualified_idx);
// Build the FULLY qualified method name using qualified_type_ident
// e.g., for I32.decode: "Builtin.Num.I32" + "decode" -> "Builtin.Num.I32.decode"
// e.g., for Str.concat: "Builtin.Str" + "concat" -> "Builtin.Str.concat"
const qualified_type_text = self.env.getIdent(auto_imported_type_env.qualified_type_ident);
const fully_qualified_idx = try self.env.insertQualifiedIdent(qualified_type_text, field_text);
const qualified_text = self.env.getIdent(fully_qualified_idx);

// Try to find the method in the Builtin module's exposed items
if (module_env.common.findIdent(qualified_text)) |qname_ident| {
Expand Down Expand Up @@ -11705,9 +11709,22 @@ fn tryModuleQualifiedLookup(self: *Self, field_access: AST.BinOp) std.mem.Alloca
const left_ident = left_expr.ident;
const module_alias = self.parse_ir.tokens.resolveIdentifier(left_ident.token) orelse return null;

// Check if this is a module alias
const module_info = self.scopeLookupModule(module_alias) orelse return null;
const module_name = module_info.module_name;
// Check if this is a module alias OR an auto-imported type
// Auto-imported types (like I32, Bool, Str) can have static methods called on them
const module_info = self.scopeLookupModule(module_alias);
const module_name = if (module_info) |info|
info.module_name
else blk: {
// Not a module alias - check if it's an auto-imported type in module_envs
if (self.module_envs) |envs_map| {
if (envs_map.contains(module_alias)) {
// This IS an auto-imported type - use the alias as the module_name
break :blk module_alias;
}
}
// Not a module alias and not an auto-imported type
return null;
};
const module_text = self.env.getIdent(module_name);

// Check if this module is imported in the current scope
Expand Down Expand Up @@ -11761,16 +11778,18 @@ fn tryModuleQualifiedLookup(self: *Self, field_access: AST.BinOp) std.mem.Alloca
if (self.module_envs) |envs_map| {
if (envs_map.get(module_name)) |auto_imported_type| {
if (auto_imported_type.statement_idx != null) {
// This is an imported type module (like Stdout)
// Look up the qualified method name (e.g., "Stdout.line!") in the module's exposed items
// This is an imported type module (like Stdout, I32, etc.)
// Look up the qualified method name (e.g., "Builtin.Num.I32.decode") in the module's exposed items
const module_env = auto_imported_type.env;
const module_name_text = module_env.module_name;
const auto_import_idx = try self.getOrCreateAutoImport(module_name_text);

// Build the qualified method name: "TypeName.method_name"
const type_name_text = self.env.getIdent(module_name);
// Build the FULLY qualified method name using qualified_type_ident
// e.g., for I32.decode: "Builtin.Num.I32" + "decode" -> "Builtin.Num.I32.decode"
// e.g., for Str.concat: "Builtin.Str" + "concat" -> "Builtin.Str.concat"
const qualified_type_text = self.env.getIdent(auto_imported_type.qualified_type_ident);
const method_name_text = self.env.getIdent(method_name);
const qualified_method_name = try self.env.insertQualifiedIdent(type_name_text, method_name_text);
const qualified_method_name = try self.env.insertQualifiedIdent(qualified_type_text, method_name_text);
const qualified_text = self.env.getIdent(qualified_method_name);

// Look up the qualified method in the module's exposed items
Expand Down
Loading
Loading