Multiline control externs broken by JS semicolon insertion
When the user defines a JS extern with control effects like so:
extern control def foo(): Int =
js """
$effekt.pure(2)
"""
Then the call fails at runtime with an error like:
foo_2839().then((v_r_2852_3977) =>
^
TypeError: Cannot read properties of undefined (reading 'then')
at Object.head (/Users/gaisseml/dev/effekt/experiments/out/js_multiline_externs.js:828:15)
at apply (/Users/gaisseml/dev/effekt/experiments/out/js_multiline_externs.js:459:25)
at Object.apply (/Users/gaisseml/dev/effekt/experiments/out/js_multiline_externs.js:549:34)
at trampoline (/Users/gaisseml/dev/effekt/experiments/out/js_multiline_externs.js:435:19)
at Object.run (/Users/gaisseml/dev/effekt/experiments/out/js_multiline_externs.js:542:18)
at Object.main (/Users/gaisseml/dev/effekt/experiments/out/js_multiline_externs.js:836:27)
at Object.<anonymous> (/Users/gaisseml/dev/effekt/experiments/out/js_multiline_externs:2:79)
at Module._compile (node:internal/modules/cjs/loader:1480:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1564:10)
at Module.load (node:internal/modules/cjs/loader:1287:32)
Node.js v22.1.0
[error] Process exited with non-zero exit code 1.
Expected behaviour
This should return 2.
Why does this happen
The above definition gets translated to the following JS:
function foo_2839() {
return
$effekt.pure(2)
;
}
Semicolon insertion will insert a ; after the return, so this is equivalent to something like:
function foo_2839() {
return undefined; // early return
$effekt.pure(2); // dead code
;
}
Full example code
extern control def foo(): Int =
js """
$effekt.pure(2)
"""
extern control def bar(): Int =
js """$effekt.pure(1)"""
def main() = {
println(bar())
println(foo())
}
Possible solution ideas
- Drop leading (and potentially trailing, for aesthetics) whitespace when generating the code
- Insert parentheses (does this help?? does this still allow everything we want to allow?)
- Discovered by @phischu earlier today.
- @jiribenes do you think this will be a problem for your Effekt practical?
Would it solve the problem to turn the space after return into a nonbreaking one?
https://github.com/effekt-lang/effekt/blob/06dc86e678bef1b9ce3a7f9eda017103db7d0085/effekt/shared/src/main/scala/effekt/generator/js/PrettyPrinter.scala#L58
Edit: Ahh, I see, it's the whitespace of the extern...
Here is a proposal:
in the JS pretty printer strip whitespace and check that they only have one line or error otherwise.
This way we can see, whether we ever use them and react then.