Skip to content
This repository has been archived by the owner on Jan 28, 2023. It is now read-only.

Precedence #15

Closed
littledan opened this issue Oct 16, 2017 · 26 comments
Closed

Precedence #15

littledan opened this issue Oct 16, 2017 · 26 comments

Comments

@littledan
Copy link
Member

The current draft spec gives ?? precedence and parsing in general exactly equal to ||. I wrote the spec this way because I was imagining people upgrading code from || to ??, and thought that the parsing shouldn't change. However, maybe a lower precedence would make more sense, to be "the lowest expression precedence". Any opinions?

@ljharb
Copy link
Member

ljharb commented Oct 16, 2017

Can you provide some code examples that would behave differently with this change?

@littledan
Copy link
Member Author

x ?? y || z would bracket as (x ?? y) || z currently (as if ?? were ||), or x ?? (y || z) with a change to a lower precedence (with the logic that ?? is the default operator, not involved in calculations as || is).

@ljharb
Copy link
Member

ljharb commented Oct 17, 2017

What would be the motivation? The current precedence seems sensible to me as it allows for LTR reading; the latter seems like a footgun.

@littledan
Copy link
Member Author

The motivation would be that || as a default operator is just weird tradition, and really, you should be able to use any other kind of expression as the arguments. I don't really see why either would be a terrible footgun; I can imagine people being tripped up either way it's decided.

@jridgewell
Copy link
Member

Mine is mainly it feels weird that && and || don't bind the same (which can't change for legacy reasons). But now it's even more apparent because of the third logical operator ??:

x ?? y || z === (x ?? y) || z
x ?? y && z === x ?? (y && z)

Both versions of y OP z feel like the right hand size to me.

@claudepache
Copy link

claudepache commented Oct 18, 2017

Here are what some other languages use for the precedence of ??:

Between || and ? :

Same level as || — but with some associativity rule I’m not sure to correctly guess (my guess is that the evaluation of the expression would stop as soon as short-circuiting occurs somewhere):

Higher precedence than comparison operators:

@claudepache
Copy link

I wrote the spec this way because I was imagining people upgrading code from || to ??, and thought that the parsing shouldn't change.

If we want to preserve expectation (rather than parsing), we must give ?? a lower precedence than || (as C# does). Indeed, consider:

a || b || c    // equivalent to: a ? a : b ? b : c
(a || b) || c  // ditto
a || (b || c)  // ditto

The expectation is: the first of a, b or c that is not falsy. Note that grouping parentheses, although changing the parsing, does not change the result.

Compare with:

a ?? b || c   // expection: a != null ? a : b ? b : c
(a ?? b) || c // equivalent to: a != null ? (a ? a : c) : b ? b : c
a ?? (b || c) // equivalent to: a != null ? a : b ? b : c

The expectation is: the first of a, b or c that is not nully (if followed by ??) resp. falsy (if followed by ||).

The issue is that “a is not nully” is strictly weaker than “a is not falsy”, and we should give the former condition more weight by assigning to ?? a lower precedence than ||.


On the other hand, my experience of that operator in PHP shows that a higher precedence than comparison operators (à la Swift) is probably what is wanted when seeking to minimise the use of parentheses; because I write often something similar to:

if (($expression ?? 'default') === 'value') { ... }

My conclusion is:

  • If we want to minimise hazards when people upgrade their code, we should pick C# semantics.
  • If we want to minimise the need of parentheses, Swift semantics may be interesting.

@ljharb
Copy link
Member

ljharb commented Oct 19, 2017

Since switching to ?? from || will always be hazardous unless the dev knows the LHS is either truthy or nullish, I'm not sure we'd be minimizing hazards with either approach - which is why i think I lean towards swift semantics.

@littledan
Copy link
Member Author

The issue is that “a is not nully” is strictly weaker than “a is not falsy”, and we should give the former condition more weight by assigning to ?? a lower precedence than ||.

I don't really understand this "strictly weaker" idea. If you have a chain of ?? and || mixed together, doesn't it make sense to treat them linearly? The distinction is just about how you treat the immediately preceding operand, but the logic to fall to the next one is the same. For this reason, I'm increasingly convinced that keeping the same precedence is the right option.

@rattrayalex
Copy link

I'm curious; what's the current thinking here? Undecided?

@littledan
Copy link
Member Author

We previously decided on giving ?? the same precedence as ||; you can see this in the current draft specification. However, @waldemarhorwat is expressing concerns in #26, so we might reconsider.

@rattrayalex
Copy link

Thanks! Looks like the summary quote is:

If ?? has lower precedence than ||, then the semantics of a ?? b || c would be a ?? (b || c)

With that being desired behavior (I think I agree fwiw).

@ljharb
Copy link
Member

ljharb commented Mar 15, 2018

So between "lower" and "same", does that mean that the following holds for a ?? b || c?

nullish a non-nullish a
lower: a ?? (b || c) b || c a
same: (a ?? b) || c b || c a || c

@jridgewell
Copy link
Member

Your headings are backwards, but yes.

@ljharb
Copy link
Member

ljharb commented Mar 15, 2018

Backwards how?

@jridgewell
Copy link
Member

a ?? (b || c), when a is nullish, equates to b || c. When a is non-nullish, equates to a.

@ljharb
Copy link
Member

ljharb commented Mar 15, 2018

ah yes ty, I’ll fix the table.

@hax
Copy link
Member

hax commented Mar 16, 2018

@ljharb Your previous comment is very reasonable for me, but it seems you change your mind?

@ljharb
Copy link
Member

ljharb commented Mar 16, 2018

I’m not sure if i have changed my mind :-) based on the table, i think I’d prefer the row i marked “same”.

@hax
Copy link
Member

hax commented Mar 19, 2018

I think

a ?? b || c
a || b ?? c
a ?? b && c
a && b ?? c

are all confused though they are a little artificial because I suppose most common use cases like a && a.b || c would be upgraded to a?.b ?? c.

What I think a important case is like a == b ?? c after ?? land. It's very likely someone will consider ?? has a higher precedence than == > <. This is why I think Swift's way is much ok in long-term.

But we may meet a && a.b || c upgraded to a && a.b ?? c if we can't land ?. with ?? . Could we just force parens (throw SyntaxError) in such cases? Just like we not allow -1 ** 2. After all, it seems mix ?? with || and && will always confused and need parens.

@leobalter
Copy link
Member

I just need to confirm this is now a SyntaxError as it's not allowed to have ?? w/ || or && blended together in any position.

Please let me know if anything changes to update the tests.

@jridgewell
Copy link
Member

Correct, it’s a syntax error. Parens are required.

@chharvey
Copy link

Is there any way of putting an informal note in the spec indicating that the precedence of ?? is yet to be determined? For the longest time I was scratching my head wondering why the decision was made to write this grammar:

LogicalORExpression :
	LogicalANDExpression
	LogicalORExpression "||" LogicalANDExpression
CoalesceExpression :
	CoalesceExpressionHead "??" BitwiseORExpression
CoalesceExpressionHead :
	CoalesceExpression
	BitwiseORExpression
ShortCircuitExpression :
	LogicalORExpression
	CoalesceExpression

and not simply this:

ShortCircuitExpression :
	LogicalANDExpression
	ShortCircuitExpression "||" LogicalANDExpression
	ShortCircuitExpression "??" LogicalANDExpression

It wasn’t until browsing through the issues that I found out the reason was to postpone the issue of precedence, and to expedite the candidate to Stage 4. Putting a small note in the spec explaining the motivation would make things more clear. There is already precedent (pun intended) for this, in the conditional operator section:

Note: … The motivation for this difference in ECMAScript is to allow an assignment expression to be governed by either arm of a conditional …

@littledan
Copy link
Member Author

There is nothing yet-to-be determined about the precedence; the current spec text is at Stage 4 and on track to be part of ES2020. Following the discussion on this and other theads, it was determined that ?? cannot be used without parentheses in the same expression as || or &&, to save confusion. However, when those are absent, ?? associates at the same level as ||, so it could be swapped in.

@FFdhorkin
Copy link

This particular discussion was around || vs ??, and barely touched on other operators.

For example, if you have foo = 10, then foo ?? 1 + 3 evaluates to 10. I would expect it to evaluate to 13. It seems very strange to have nullish coelescing be near the bottom of the order-of-operations list. Worse, foo ?? 0 >= 5 evaluates to 10, when I think it's pretty clear the intent was probably to produce a boolean (i.e. (foo ?? 0) >= 5).

What I think a important case is like a == b ?? c after ?? land. It's very likely someone will consider ?? has a higher precedence than == > <. This is why I think Swift's way is much ok in long-term.

I mentioned this sort of case here: microsoft/TypeScript#39096 and it was resolved as "working as intended." That said, MDN's table does show ?? being lower in order of operations than +. I think this is bizarre, but I guess there's nothing that can be done at this point?

@hax
Copy link
Member

hax commented Jul 8, 2020

@FFdhorkin Sorry, I've tried to advocate higher the precedence or at least postpone the decision in the meeting of advancing it to stage 4, but I was too late and can't convince the committee (I still think we were too hurry to push it to stage 4, and did not have chance to collect enough feedback from developers on precedence issue). The only thing we can do now is push the tools (IDE/linters/transpilers, etc.) to add checks/rules/warnings to protect the developers.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants