Merge pull request #8527 from roc-lang/fix-str-split-on

Fix semantics for `Str.split_on`
This commit is contained in:
Luke Boswell 2025-12-02 07:47:06 +11:00 committed by GitHub
commit b9d7371cd0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 79 additions and 1 deletions

View file

@ -827,9 +827,9 @@ pub const Expr = union(enum) {
.str_reserve => &.{ .consume, .borrow },
.str_release_excess_capacity => &.{.consume},
.str_join_with => &.{ .consume, .borrow }, // list consumed, separator borrowed
.str_split_on => &.{ .consume, .borrow },
// String operations - borrowing with seamless slice result (incref internally)
.str_split_on => &.{ .borrow, .borrow },
.str_to_utf8 => &.{.borrow},
.str_drop_prefix, .str_drop_suffix => &.{ .borrow, .borrow },

View file

@ -771,3 +771,45 @@ test "string literal pattern matching" {
try testing.expect(has_alice);
try testing.expect(has_bob);
}
test "multiline string split_on" {
// Tests splitting a multiline string and iterating over the lines.
// This is a regression test to ensure split_on works correctly with
// multiline strings and doesn't cause memory issues.
const allocator = testing.allocator;
try ensureRocBinary(allocator);
const run_result = try std.process.Child.run(.{
.allocator = allocator,
.argv = &[_][]const u8{
"./zig-out/bin/roc",
"test/fx/multiline_split_leak.roc",
},
});
defer allocator.free(run_result.stdout);
defer allocator.free(run_result.stderr);
switch (run_result.term) {
.Exited => |code| {
if (code != 0) {
std.debug.print("Run failed with exit code {}\n", .{code});
std.debug.print("STDOUT: {s}\n", .{run_result.stdout});
std.debug.print("STDERR: {s}\n", .{run_result.stderr});
return error.RunFailed;
}
},
else => {
std.debug.print("Run terminated abnormally: {}\n", .{run_result.term});
std.debug.print("STDOUT: {s}\n", .{run_result.stdout});
std.debug.print("STDERR: {s}\n", .{run_result.stderr});
return error.RunFailed;
},
}
// Verify the output contains lines from the multiline string
try testing.expect(std.mem.indexOf(u8, run_result.stdout, "This is a longer line number one") != null);
try testing.expect(std.mem.indexOf(u8, run_result.stdout, "This is a longer line number two") != null);
try testing.expect(std.mem.indexOf(u8, run_result.stdout, "L68") != null);
try testing.expect(std.mem.indexOf(u8, run_result.stdout, "The last line is here") != null);
}

View file

@ -0,0 +1,20 @@
app [main!] { pf: platform "./platform/main.roc" }
import pf.Stdout
input =
\\This is a longer line number one
\\This is a longer line number two
\\L68
\\L30
\\R48
\\L5
\\R60
\\L55
\\L1
\\The last line is here
main! = || {
for line in input.split_on("\n")
Stdout.line!(line)
}

View file

@ -0,0 +1,16 @@
# META
~~~ini
description=Multiline string with 7 lines split - memory leak test
type=repl
~~~
# SOURCE
~~~roc
» input = "L68\nL30\nR48\nL5\nR60\nL55\nL1"
» input.split_on("\n")
~~~
# OUTPUT
assigned `input`
---
["L68", "L30", "R48", "L5", "R60", "L55", "L1"]
# PROBLEMS
NIL