Stream: bugs

Topic: ✔ effectful functions cannot be stored in a record with PI


view this post on Zulip Ian McLerran (Jan 06 2025 at 19:46):

a Expr::TrySuffix expression was not completely removed in desugar_value_def_suffixed

So after stripping down all my effectful if then else blocks to dummy blocks to eliminate the stack overflow issue (seems to have been in the fileTreeHelper! if block), I have encountered a new bug, as listed at the top of this post.

Wondering if it could have to do with using ! at the end of a record field name? (I have records which contain one effectful function as a field.)

view this post on Zulip Ian McLerran (Jan 06 2025 at 19:49):

Can confirm. When I eliminate the records which contain the field name ending in !, this error is resolved.

view this post on Zulip Ian McLerran (Jan 06 2025 at 19:52):

The records look like this:

readFileContents : { name : Str, handler! : Str => Result Str *, tool : Tool }
readFileContents = {
    name: readFileContentsTool.function.name,
    handler!: readFileContentsHandler!,
    tool: readFileContentsTool,
}

view this post on Zulip Ian McLerran (Jan 06 2025 at 19:53):

However, if I simply rename the handler! field to handler, the same error is encountered, presumably due to assigning an effectful function to the field.

view this post on Zulip Ian McLerran (Jan 06 2025 at 19:54):

So this record still encounters the bug:

readFileContents : { name : Str, handler : Str => Result Str *, tool : Tool }
readFileContents = {
    name: readFileContentsTool.function.name,
    handler: readFileContentsHandler!,
    tool: readFileContentsTool,
}

view this post on Zulip Ian McLerran (Jan 06 2025 at 19:57):

https://github.com/imclerran/roc-ai/tree/desugar-suffixed-bug

view this post on Zulip Ian McLerran (Jan 06 2025 at 20:29):

Okay, while trying to make a min-repro, I have semi disproven my hypothesis for the cause of the bug...

view this post on Zulip Ian McLerran (Jan 06 2025 at 20:32):

Since the following works:

main! = \_args ->
    try effects.echo! "Hello, world!"
    Ok {}

effects : { echo!: Str => Result {} _ }
effects = {
    echo!: Stdout.line!,
}

view this post on Zulip Sam Mohr (Jan 06 2025 at 20:33):

We parse differently if the module is purity inference or Task based

view this post on Zulip Ian McLerran (Jan 06 2025 at 20:34):

Both of these examples should be PI based... unless there's something I'm missing in the original module that is triggering it to treat it as non-pi...

view this post on Zulip Ian McLerran (Jan 06 2025 at 20:35):

The code worked fine in my original non-pi version.

view this post on Zulip Sam Mohr (Jan 06 2025 at 20:35):

So if your module exposed an ident with a ! at the end, it parses as PI

view this post on Zulip Sam Mohr (Jan 06 2025 at 20:35):

If not, Task

view this post on Zulip Sam Mohr (Jan 06 2025 at 20:36):

So what does your module expose?

view this post on Zulip Ian McLerran (Jan 06 2025 at 20:36):

Ah.... it exposes a record which DOES NOT have a ! at the end, but the record contains an ident ending in !. That must be the issue.

view this post on Zulip Ian McLerran (Jan 06 2025 at 20:38):

My module exposes 4 records with the same type as:

readFileContents : { name : Str, handler! : Str => Result Str *, tool : Tool }

They all contain handler, but none of the record names themselves were named with a ! since the records have both the effectful field, and idents which are not effectful.

view this post on Zulip Ian McLerran (Jan 06 2025 at 20:39):

Let me just expose the handlers themselves as well. Sounds like that should trigger PI parsing.

view this post on Zulip Ian McLerran (Jan 06 2025 at 20:43):

Yep, if I expose the readFileContentsHandler! in addition to my records, the module parses without issue. I wonder if lack of PI parsing was the reason I hit the stack overflow in the last bug...? :thinking:

view this post on Zulip Sam Mohr (Jan 06 2025 at 20:47):

I'm 99% sure the answer is no

view this post on Zulip Ian McLerran (Jan 06 2025 at 20:48):

Well I was using PI syntax, and we know now it wasn't parsing as PI, so that was bound to cause some kind of issues...

view this post on Zulip Ian McLerran (Jan 06 2025 at 20:51):

Interesting. I created what I thought would be a min repro of this issue, but it parses correctly:
main.roc

app [main!] {
    cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.18.0/0APbwVN1_p1mJ96tXjaoiUCr8NBGamr8G8Ac_DrXR-o.tar.br",
}

import cli.Stdout
import Module { echo!: Stdout.line! } exposing [effects]

main! = \_args ->
    try effects.echo! "Hello, world!"
    Ok {}

Module.roc

module { stdout! } -> [ effects ]

effects : { echo!: Str => Result {} _ }
effects = {
    echo!: stdout!,
}

view this post on Zulip Ian McLerran (Jan 06 2025 at 20:52):

More investigation required. But I can say that in my large code file, simply exposing a single ident ending in ! resolves the issue.

view this post on Zulip Ian McLerran (Jan 06 2025 at 20:55):

Ah, but - if I simply roc check Module.roc I do hit a broken compiler expectation. However it is a different error than my original bug here.

view this post on Zulip Ian McLerran (Jan 06 2025 at 20:55):

Even though running main.roc in the min repro compiles and runs.

view this post on Zulip Sam Mohr (Jan 06 2025 at 20:56):

The code that caused the overflow is known to be fragile. If we weren't planning on removing it, I'd be trying to investigate it more

view this post on Zulip Sam Mohr (Jan 06 2025 at 20:56):

We really want to avoid ever crashing

view this post on Zulip Sam Mohr (Jan 06 2025 at 20:57):

Luckily, I don't think it's a problem outside of the case you're in

view this post on Zulip Ian McLerran (Jan 06 2025 at 21:08):

Well it turns out the only reason the stack overflow is hit at all is because the module isn't being parsed as PI. If the parser recognizes the file as PI, the code is now 100% correct.

view this post on Zulip Sam Mohr (Jan 06 2025 at 21:09):

Yes

view this post on Zulip Ian McLerran (Jan 06 2025 at 21:11):

Raises the question of why this parses correctly:

module { stdout! } -> [ effects ]

effects : { echo!: Str => Result {} _ }
effects = {
    echo!: stdout!,
}

but why I have to expose listDirectoryHandler! in FileSystem.roc for the code to parse as PI...

module { pathFromStr, pathToStr, listDir!, isDir!, readFile!, writeUtf8! } -> [
    listDirectory,
    #listDirectoryHandler!,
    listFileTree,
    readFileContents,
    writeFileContents,
]

listDirectory : { name : Str, handler! : Str => Result Str *, tool : Tool }
#...

view this post on Zulip Sam Mohr (Jan 06 2025 at 21:13):

It's not a parsing thing, it's a desugaring thing: https://github.com/roc-lang/roc/blob/a089cf2487d2cb5f1d977a2e01be5ab7b4de3275/crates/compiler/can/src/desugar.rs#L540

view this post on Zulip Sam Mohr (Jan 06 2025 at 21:13):

They parse the exact same

view this post on Zulip Ian McLerran (Jan 06 2025 at 21:14):

Yeah, but it shouldn't be desugaring the !. It should be parsing as PI, and not desugaring at all.

view this post on Zulip Sam Mohr (Jan 06 2025 at 21:14):

https://github.com/roc-lang/roc/blob/a089cf2487d2cb5f1d977a2e01be5ab7b4de3275/crates/compiler/load_internal/src/file.rs#L2332

view this post on Zulip Ian McLerran (Jan 06 2025 at 21:15):

If you just uncomment that listDirectoryHandler! in the module exports, doesn't try to desugar, because it parses the file as a PI module.

view this post on Zulip Ian McLerran (Jan 06 2025 at 21:15):

Maybe I'm getting my terminology mixed a little here, forgive me if I'm confusing things here between parsing and desugaring

view this post on Zulip Sam Mohr (Jan 06 2025 at 21:15):

You're totally fine

view this post on Zulip Sam Mohr (Jan 06 2025 at 21:16):

Let me try to say this:

view this post on Zulip Sam Mohr (Jan 06 2025 at 21:16):

The code is shitty and we are okay with that

view this post on Zulip Sam Mohr (Jan 06 2025 at 21:16):

Because it won't be shitty as soon as we permanently move to FxMode::PurityInference

view this post on Zulip Sam Mohr (Jan 06 2025 at 21:16):

And rip out the bad part

view this post on Zulip Sam Mohr (Jan 06 2025 at 21:17):

Is there something you think we should do on top of that?

view this post on Zulip Ian McLerran (Jan 06 2025 at 21:17):

But seems, from my limited understanding of the compiler, that it is trying to desugar ! to Task.await, because it thinks the code is not purity inference, because there is no ! ident exported.

view this post on Zulip Sam Mohr (Jan 06 2025 at 21:17):

You are correct

view this post on Zulip Sam Mohr (Jan 06 2025 at 21:17):

PI is not determined from the code you write, but only from the exports

view this post on Zulip Ian McLerran (Jan 06 2025 at 21:18):

I'm with ya. Since we won't have split modes of parsing forever, no need to perfectly determine PI, since eventually it will always be used, and that will solve the issue.

view this post on Zulip Notification Bot (Jan 06 2025 at 21:18):

Ian McLerran has marked this topic as resolved.

view this post on Zulip Sam Mohr (Jan 06 2025 at 21:18):

Also, no offense to you Agus if you're reading this, the use of FxMode to allow for a smooth transition from Task to PI has been a godsend

view this post on Zulip Agus Zubiaga (Jan 08 2025 at 14:17):

Yeah, none taken. Things can be shitty and useful at the same time :smiley:


Last updated: Jul 06 2025 at 12:14 UTC