jashkenas/coffeescript

Bug: yield cannot be used in do -> expressions reliably?

borkdude opened this issue · 5 comments

I expect yield to be used anywhere in a function, but it seems do -> breaks this expectation since it's compiled to an IIFE.

Repro:

https://coffeescript.org/#try:perfectSquares%20%3D%20-%3E%0A%20%20num%20%3D%200%0A%20%20do%20-%3E%0A%20%20%20%20y%20%3D%201%0A%20%20%20%20yield%20y%3B%0A%20%20loop%0A%20%20%20%20num%20%2B%3D%201%0A%20%20%20%20if%20num%20%3E%2010%20%0A%20%20%20%20%20%20break%0A%20%20%20%20yield%20num%20*%20num%0A%20%20return%0A%0AglobalThis.foo%20%3D%20perfectSquares()%0A%0A

  • CoffeeScript version: playground's version

Note that I'm only theoretically interested in a solution as I'm writing my own to-JS transpiler and was wondering if CoffeeScript authors have thought about this more than I have. More info here.

I would not view this as a bug, but as intended semantics: do -> literally means to build a function (->) and call it (do) — very different from the TC39 do proposal. This functionality of do -> is useful, for example, if you want to build a generator right now. For example:

processGenerator do ->
  yield 1
  yield 2
  yield 3

Here's a more real-world example with async, where do enables await syntax without "infecting" the entire function:

Promise.all(
  for url of urls
    do -> await (await fetch url).json()
)

I think where CoffeeScript's behavior is less intuitive is when it happens with implicit IIFEs; see #5363 where I asked about return inside a for loop, but the same applies to yield within a for loop. We actually get my intended behavior from #5363 in Civet, but not when it's a for-loop expression which, as #5363 details, is hard to transpile. We've discussed this some in a Civet context, where we plan to do more thorough rewriting to handle cases like this, but haven't tackled it yet. See DanielXMoore/Civet#202 and DanielXMoore/Civet#381.

Makes sense, I interpreted do -> as the TC39 proposal. The babel transpiler for that proposal does the thing I would expect (for implicit IIFEs).

Cool, I didn't know about that plugin. I agree that it works well with yield, but it doesn't work well with return (compare with the proposal) and doesn't support break.

but it doesn't work well with return and doesn't support break

luckily for my language (squint) this isn't a problem since it is expression-oriented and therefore doesn't support explicitly returning things. I think it could be a reasonable trade-off for do expressions too since they have an implicit return value, maybe it should even barf on an explicit return

https://babeljs.io/repl#?browsers=defaults%2C%20not%20ie%2011%2C%20not%20ie_mob%2011&build=&builtIns=false&corejs=3.21&spec=false&loose=false&code_lz=GYVwdgxgLglg9mABMAFASkQbwFCMRBAZykQA9EBeRAEzi1z3yJIE9LEBGABgbwDYGAX2yCgA&debug=false&forceAllTransforms=false&modules=false&shippedProposals=false&circleciRepo=&evaluate=false&fileSize=false&timeTravel=false&sourceType=module&lineWrap=true&presets=env%2Creact%2Cstage-2&prettier=false&targets=&version=7.23.6&externalPlugins=%40babel%2Fplugin-proposal-do-expressions%407.23.3&assumptions=%7B%7D