effekt-lang / effekt

A language with lexical effect handlers and lightweight effect polymorphism
https://effekt-lang.org
MIT License
327 stars 23 forks source link

Multiline control externs broken by JS semicolon insertion #569

Open marzipankaiser opened 2 months ago

marzipankaiser commented 2 months ago

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())
}
marzipankaiser commented 2 months ago

Possible solution ideas

marzipankaiser commented 2 months ago
b-studios commented 2 months ago

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...

b-studios commented 2 months ago

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.