-
-
Notifications
You must be signed in to change notification settings - Fork 367
Fix static dispatch crash with platform-exposed opaque types #8929
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
When checking deferred static dispatch constraints for opaque types from imported modules, the compiler was unable to find the module environment because it only checked if the type was from the current module or the builtin module. The fix adds support for types from other imported modules by searching the imported_modules list by module name when the type's origin_module doesn't match the current module or builtin module. This allows static dispatch (`.` syntax) to work correctly on opaque types exposed from platform modules. Fixes #8928 Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
|
Follow-up bug that is related enough that I hope to fix this also Bug Report: Runtime Crash on Static Dispatch with Effect MethodsSummaryAfter the fix for compile-time static dispatch crashes, there's now a runtime crash when calling effect methods on opaque types using static dispatch ( Environment
Minimal ReproductionNote: This bug specifically affects effect methods, so it's easier to demonstrate with the basic-cli platform which has effect methods on opaque types. The test/fx platform doesn't currently have opaque types with effect methods. Test with basic-cli platformLocation: app [main!] { pf: platform "./platform/main.roc" }
import pf.Cmd
import pf.Stdout
main! = |_args| {
# Build a command using static dispatch - THIS WORKS
cmd = Cmd.new("echo").args(["Hello"])
Stdout.line!("About to execute command...")
# Call effect method using static dispatch - THIS CRASHES
result = cmd.exec_exit_code!()
match result {
Ok(code) => Stdout.line!("Exit code: ${code.to_str()}")
Err(_) => Stdout.line!("Error")
}
Ok({})
}For reference, here's the Cmd module structure (simplified): Cmd := {
args : List(Str),
program : Str,
}.{
IOErr := [NotFound, PermissionDenied, ...]
# Regular methods - WORK with static dispatch
new : Str -> Cmd
arg : Cmd, Str -> Cmd
args : Cmd, List(Str) -> Cmd
# Effect methods - CRASH with static dispatch
exec_exit_code! : Cmd => Try(I32, [CmdErr(IOErr)])
}To Reproducecd /path/to/basic-cli
roc build test-effect-static.roc
./test-effect-staticThe test file is available at Expected BehaviorThe program should execute the command and print the exit code, just like it does with qualified function call syntax. Actual BehaviorThe program prints "About to execute command..." successfully, then crashes when the effect method is called via static dispatch. AnalysisWhat Works ✅
What Crashes ❌
PatternThe crash specifically affects:
Regular methods on the same opaque types work fine with static dispatch. WorkaroundUse static dispatch for builder methods, but use qualified names for effect methods: # Good: static dispatch for builders
cmd = Cmd.new("echo").args(["Hello"])
# Good: qualified call for effects
result = Cmd.exec_exit_code!(cmd)
# Bad: static dispatch for effects (crashes)
# result = cmd.exec_exit_code!() |
|
Aaaand another Bug Report: Effect Methods via Static Dispatch Trigger Interpreter ErrorSummaryWhen calling effect methods on opaque types using static dispatch ( Environment
StatusThis issue appeared after fixes for:
Minimal ReproductionPlatform Module Structureplatform/Cmd.roc (simplified): Cmd := {
args : List(Str),
program : Str,
}.{
IOErr := [NotFound, PermissionDenied, ...]
# Regular methods - WORK with static dispatch
new : Str -> Cmd
new = |program| {
args: [],
program,
}
args : Cmd, List(Str) -> Cmd
args = |cmd, new_args| {
args: List.concat(cmd.args, new_args),
program: cmd.program,
}
# Effect methods - CAUSE INTERPRETER ERROR with static dispatch
exec_exit_code! : Cmd => Try(I32, [CmdErr(IOErr)])
}Test File That Crashestest-effect-static.roc: app [main!] { pf: platform "./platform/main.roc" }
import pf.Cmd
import pf.Stdout
main! = |_args| {
# Build a command using static dispatch - THIS WORKS
cmd = Cmd.new("echo").args(["Hello"])
Stdout.line!("About to execute command...")
# Call effect method using static dispatch - THIS TRIGGERS INTERPRETER ERROR
result = cmd.exec_exit_code!()
match result {
Ok(code) => Stdout.line!("Exit code: ${code.to_str()}")
Err(_) => Stdout.line!("Error")
}
Ok({})
}To Reproducecd /path/to/basic-cli
# Rebuild platform
./build.sh
# Build test (succeeds)
roc build test-effect-static.roc
# Run test (crashes with interpreter error)
./test-effect-staticExpected BehaviorThe program should execute successfully, calling the hosted function through the compiled binary, just like it does when using qualified function call syntax. Actual BehaviorThe program:
AnalysisWhat Works ✅1. Regular methods with static dispatch: cmd = Cmd.new("echo").args(["Hello"]) # Perfect!2. Effect methods with qualified call: result = Cmd.exec_exit_code!(cmd) # Perfect!3. Complete example with qualified effect: $ roc build test-effect-qualified.roc && ./test-effect-qualified
Successfully built ./test-effect-qualified
About to execute command...
Hello
Exit code: 0What Crashes ❌1. Effect methods with static dispatch: result = cmd.exec_exit_code!() # Interpreter error!2. Any effect on opaque type via . syntax: cmd.exec_output!() # Crashes
cmd.exec_cmd!() # CrashesKey ObservationThe error message says "Hosted functions cannot be called in the interpreter" but:
Comparison with Working Code❌ Crashes - Static dispatch on effectcmd = Cmd.new("echo").args(["Hello"])
result = cmd.exec_exit_code!() # Interpreter error✅ Works - Qualified call for effectcmd = Cmd.new("echo").args(["Hello"])
result = Cmd.exec_exit_code!(cmd) # Works perfectlyThe only difference is:
Both should generate the same code since static dispatch is just syntactic sugar. HypothesisWhen an effect method is called via static dispatch (
Possible causes:
ImpactThis prevents using static dispatch for any effects on opaque types, forcing developers to:
Example of required mixed syntax: # Awkward: mixed syntax based on whether method is an effect
cmd = Cmd.new("echo").args(["Hello"]) # . syntax for regular methods
result = Cmd.exec_exit_code!(cmd) # qualified for effectsDesired uniform syntax: # Ideal: consistent syntax for all methods
result = Cmd.new("echo").args(["Hello"]).exec_exit_code!()Test FilesLocation:
WorkaroundUse static dispatch for regular methods, but qualified calls for effects: # Use . for builders
cmd = Cmd.new("echo").args(["Hello"])
# Use qualified call for effects
result = Cmd.exec_exit_code!(cmd)Related IssuesThis is the third stage of static dispatch bugs:
Each fix has revealed the next layer of the issue. |
Fixed multiple issues introduced in commit 66dbe03: 1. Fixed control flow bug where hosted lambda check was falling through instead of being in an else branch, causing TypeMismatch errors for normal lambdas (List.sum tests were failing). 2. Fixed closure capture lookup by reverting lambda_expr_idx back to expr_idx for regular closures. The change to cls.lambda_idx broke closure captures, causing "e_lookup_local: definition not found" crashes. 3. Updated tryInvokeHostedClosure to unwrap e_closure expressions to find the underlying lambda, allowing it to work with both expr_idx (regular closures) and cls.lambda_idx (hosted effect methods). 4. Fixed hostedBuilderPrintValue to use hostedStdoutLine for output instead of direct stdout.writeAll, so test mode IO tracking works correctly. Test results: 2280/2282 tests passing (99.9%) - The 1 failing test (aoc_day2.roc NotNumeric) also fails on origin/main, so it's a pre-existing bug unrelated to this PR.
When checking deferred static dispatch constraints for opaque types from imported modules, the compiler was unable to find the module environment because it only checked if the type was from the current module or the builtin module.
The fix adds support for types from other imported modules by searching the imported_modules list by module name when the type's origin_module doesn't match the current module or builtin module. This allows static dispatch (
.syntax) to work correctly on opaque types exposed from platform modules.Fixes #8928