Stream: beginners

Topic: Recursing in Task.loop breaks compiler expectation


view this post on Zulip Ian McLerran (Sep 24 2024 at 23:56):

When making a recursive call inside a Task.loop, the compiler crashes with:

An internal compiler expectation was broken.
This is definitely a compiler bug.
Please file an issue here: <https://github.com/roc-lang/roc/issues/new/choose>
I thought a non-nullable-unwrapped variant for a lambda set was impossible: how could such a lambda set be created without a base case?
Location: crates/compiler/mono/src/layout.rs:1705:61

Min repro:

app [main] {
    cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br",
}

main = Task.loop {} \{} -> Task.ok (Step (main!))

view this post on Zulip Ian McLerran (Sep 24 2024 at 23:58):

This is a reduction from the following, which produces the same error:

app [main] {
    cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br",
}

import cli.Stdout
import cli.Path exposing [Path]

main : Task {} _
main =
    str = fileTree! "."
    Stdout.line! str

fileTree : Str -> Task Str _
fileTree = \pathStr ->
    path = Path.fromStr pathStr
    fileTreeHelper path 0

fileTreeHelper : Path, U64 -> Task Str _
fileTreeHelper = \basePath, depth ->
    prependNewline = \str -> if Str.isEmpty str then str else Str.concat "\n" str
    appendNewline = \str -> if Str.isEmpty str then str else Str.concat str "\n"
    prefix = if depth == 0 then "" else (Str.repeat "  " (depth - 1)) |> Str.concat "- "
    contents = Path.listDir! basePath
    buildStr = \previous, current, subcontents -> "$(appendNewline previous)$(prefix)$(current)$(subcontents)"

    # yes, this would be cleaner with pure recursion... ๐Ÿ™„
    Task.loop { accumulation: "", paths: contents } \{ accumulation, paths } ->
        when paths is
            [] ->
                Task.ok (Done accumulation)

            [path, .. as pathsTail] ->
                if Path.isDir! path then
                    subcontents = fileTreeHelper! path (depth + 1) |> prependNewline
                    newString = buildStr accumulation (Path.display path) subcontents
                    Task.ok (Step { accumulation: newString, paths: pathsTail })
                else
                    newString = buildStr accumulation (Path.display path) ""
                    Task.ok (Step { accumulation: newString, paths: pathsTail })

view this post on Zulip Ian McLerran (Sep 25 2024 at 00:00):

Almost certainly unrelated to #beginners > Compiler panic with module params , but both bugs involve Task.loop.

view this post on Zulip Ian McLerran (Sep 25 2024 at 01:35):

Note that in the full code version, changing

Task.loop {accumulation: โ€œโ€, paths: contents } \{ accumulation, paths } ->

To

Task.loop {accumulation: โ€œโ€, paths: Path.listDir! basePath } \{ accumulation, paths } ->

Produces another error of its own.

view this post on Zulip Anton (Sep 26 2024 at 09:36):

Thanks @Ian McLerran, I added your reproduction to #6252


Last updated: Jul 06 2025 at 12:14 UTC