tc39 / proposal-do-expressions

Proposal for `do` expressions
MIT License
1.12k stars 14 forks source link

isLast #61

Open waldemarhorwat opened 3 years ago

waldemarhorwat commented 3 years ago

What is the purpose of the isLast parameter in the spec? It adds a fair bit of complexity to the spec by trying (and ultimately failing) to detect completion values such as loops followed by a break.

If I read the spec correctly, isLast disallows this construct:

a = do {
  lbl: {
    while (f())
      g();
    break lbl;
    44;
  }
};

But it allows this one:

a = do {
  lbl: {
    while (f())
      g();
    if (true) break lbl;
    44;
  }
};

and this one:

a = do {
  lbl: {
    while (f())
      break lbl;
    44;
  }
};

Given it's hard-to-fathom semantics, I'm not sure the effort to detect such things before a break is worth it.

bakkot commented 3 years ago

The purpose is to prevent observing completion values from loops, declarations, or if-without-else, even in the presence of break.

Regarding your examples:

a = do {
  lbl: {
    while (f())
      g();
    if (true) break lbl;
    44;
  }
};

This is allowed because the completion value of if (true) break lbl; is undefined, not ~empty~: that is, regardless of what came before it, a do ending in that statement will evaluate to undefined. So it does not observe the completion value of the preceding loop.

a = do {
  lbl: {
    while (f())
      break lbl;
    44;
  }
};

Ah, sorry, that's a bug. I just pushed a fix. (Edit: though that fix itself has a bug involving _labelSet_; I'll fix it when I get a moment.)

That said, there's another bug I'm aware of, caused by https://github.com/tc39/proposal-do-expressions/pull/59, which leads to

a = do {
  lbl: {
    while (f()) {
      stuff();
    }
    if (do { break lbl; });
    44;
  }
};

being legal and observing the completion value of the while. I intend to fix it as well.

bakkot commented 3 years ago

I think it's important that we prevent observing completion values from loops, declarations, or if-without-else. (Well, I don't care about if-without-else, but you requested that one.) That said, I am open to other ways of accomplishing this. For example, the spec would be simplified somewhat if we chose to disallow labeled statements as the final statement in a do, though it would still require some nontrivial logic to handle the final statement being switch.

waldemarhorwat commented 3 years ago

I didn't realize that if differs from {} in setting the prior completion value to undefined instead of empty. But we can modify the second example to, say:

a = do {
  lbl: {
    while (f())
      g();
    {break lbl;}
    44;
  }
};
bakkot commented 3 years ago

That example is disallowed, or at least should be: that's what IsBreak is for.