Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Nullish Coalescing operator #6545

Merged
merged 2 commits into from
Dec 26, 2020
Merged

Conversation

rhuanjl
Copy link
Collaborator

@rhuanjl rhuanjl commented Dec 19, 2020

This PR takes work begun by @boingoing in #6254 and completes it to implement the stage 4 tc39 proposal for a nullish coalescing operator.

Proposal repository: https://github.com/tc39/proposal-nullish-coalescing
The implementations passes most relevant test262 test cases - exception being ones also involving tail call optimisations which is a separate JS feature that CC does not support.

A switch ESNullishCoalescingOperator is included but set to true by default.

The basic logic of this implementation is fairly simple - though one key complexity is that a ?? b || c is not permitted without brackets around one of the two operations, this required some extra tracking in the parser.

Within the bytecode emitter the noalish coalescer is de-sugered so that:
result = node1 ?? node2;
becomes

result = node1;
if (result == null) {
  result = node2;
}

@ppenzin @Fly-Style please could one of you take a look?

boingoing and others added 2 commits December 18, 2020 18:56
This operator is similar to the logical or operator (`||`) but is meant to avoid unintended behavior which might arise when using logical or. Problem with logical or is that the left argument is coerced to a boolean which means that if it is something falsy (like `0`, `''`, or `false`) then the right argument will be used instead.

The coalescing operator (`??`) is designed to solve this problem. It only assigns the right value when the left value is `null` or `undefined`.

```javascript
null || 'something'; // 'something'
undefined || 'something'; // 'something'
'' || 'something'; // 'something'
0 || 'something'; // 'something'
false || 'something'; // 'something'

null ?? 'something'; // 'something'
undefined ?? 'something'; // 'something'
'' ?? 'something'; // ''
0 ?? 'something'; // 0
false ?? 'something'; // false
```
@@ -279,6 +279,7 @@ LPCWSTR Parser::GetTokenString(tokens token)
case tkColon: return _u(":");
case tkLogOr: return _u("||");
case tkLogAnd: return _u("&&");
case tkCoalesce: return _u("??");
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This switch converts parser tokens back into strings for error messages.

@@ -3237,7 +3238,8 @@ ParseNodePtr Parser::ParseTerm(BOOL fAllowCall,
_Out_opt_ BOOL* pfCanAssign /*= nullptr*/,
_Inout_opt_ BOOL* pfLikelyPattern /*= nullptr*/,
_Out_opt_ bool* pfIsDotOrIndex /*= nullptr*/,
_Inout_opt_ charcount_t *plastRParen /*= nullptr*/)
_Inout_opt_ charcount_t *plastRParen /*= nullptr*/,
_Out_opt_ bool* looseCoalesce /*= nullptr*/)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The looseCoalesce out parameter enables tracking of unbracketed coalescing operators for the syntax error when they are combined with ?? or ||.


if (nop == knopLogAnd || nop == knopLogOr)
{
if (localCoalesce || coalescing)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's the check for the syntax error - localCoalesce will be true if the left hand side of this operation is an unbracketed ??, coalescing will be true if the right hand side is.

Aware the variable names aren't the best - open to alternatives.

localCoalesce = true;
if (looseCoalesce != nullptr)
{
*looseCoalesce = true;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we mark that the current operation is a coalescing operation to feed up into the above check at a different level.

@@ -3440,6 +3442,11 @@ ParseNodePtr Parser::ParseTerm(BOOL fAllowCall,

ChkCurTok(tkRParen, ERRnoRparen);

if (looseCoalesce != nullptr)
{
*looseCoalesce = false;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The last passed statement was wrapped in brackets - therefore it is not a "loose" coalesce.

LSC_ERROR_MSG(1075, ERRInvalidAssignmentTarget, "Invalid destructuring assignment target")
LSC_ERROR_MSG(1076, ERRFormalSame, "Duplicate formal parameter names not allowed in this context")
LSC_ERROR_MSG(1077, ERRDestructNotInit, "Destructuring declarations cannot have an initializer")
LSC_ERROR_MSG(1078, ERRCoalesce, "Coalescing operator '\?\?' not permitted unparenthesized inside '||' or '&&' expressions")
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New error message for the new syntax error. The changes above in this file are just white space.

// In that case the right hand side is not evaluated
// If the left hand side is null or undefined it resolves to the right hand side
// PTNODE(knopCoalesce , "??" ,None ,Bin ,fnopBin)
case knopCoalesce:
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This case in the bytecode emitter is the run time implementation - the command is fully de-sugared here, so no new opcodes were needed.

@fatcerberus
Copy link
Contributor

Word of warning that per the DOM spec, document.all is falsy but is expected to be treated as truthy by operators like ?? - Edge doesn’t use Chakra anymore so this is less important but something to keep in mind since I don’t think embedders can emulate this behavior in userland.

@rhuanjl
Copy link
Collaborator Author

rhuanjl commented Dec 19, 2020

Word of warning that per the DOM spec, document.all is falsy but is expected to be treated as truthy by operators like ?? - Edge doesn’t use Chakra anymore so this is less important but something to keep in mind since I don’t think embedders can emulate this behavior in userland.

Falsely shouldn't be a problem - only if tests with == null if it does that's bizarre BUT I've implemented what the spec says and this isn't currently used in a browser anyway so I think we just roll with it.

@fatcerberus
Copy link
Contributor

Yeah, oddly enough document.all == null is actually true even though it’s an object, verified in Chrome console. Apparently it’s some compatibility baggage from ancient Internet Explorer.

I don’t think it’s a major issue in practice, like you said. Just wanted to state it for the record since embedders have no way to emulate this behavior.

@ppenzin ppenzin closed this Dec 20, 2020
Javascript Standards automation moved this from In progress to Done Dec 20, 2020
@ppenzin
Copy link
Member

ppenzin commented Dec 20, 2020

Doing the usual clopenning to clear persistent CI failure.

@ppenzin ppenzin reopened this Dec 20, 2020
Javascript Standards automation moved this from Done to In progress Dec 20, 2020
@rhuanjl
Copy link
Collaborator Author

rhuanjl commented Dec 20, 2020

Yeah, oddly enough document.all == null is actually true even though it’s an object, verified in Chrome console. Apparently it’s some compatibility baggage from ancient Internet Explorer.

I don’t think it’s a major issue in practice, like you said. Just wanted to state it for the record since embedders have no way to emulate this behavior.

Thinking about it - if any browser does embed this and if a developer wants to do document.all ?? something and is confused at getting something as the result that's on them - the sort of special casing required to avoid that is IMO not worth the performance cost it would entail for all other uses.

@fatcerberus
Copy link
Contributor

It turns out that the special case under discussion is actually formally encoded in the ES spec, see https://tc39.es/ecma262/#sec-IsHTMLDDA-internal-slot

@rhuanjl
Copy link
Collaborator Author

rhuanjl commented Dec 20, 2020

It turns out that the special case under discussion is actually formally encoded in the ES spec, see https://tc39.es/ecma262/#sec-IsHTMLDDA-internal-slot

Yeah I read it - and I don't think it's worth supporting for now, to handle it in this operation could either:
a) implement a whole new opcode OR
b) use two strict equal comparisons instead of one normal comparison

(a) would be a lot of extra code and (b) would be a performance hit - if anyone actually makes a new webbrowser with CC and wants spec perfect support for window.all we can revisit.

@fatcerberus
Copy link
Contributor

Alright, that makes sense. Just wanted to make sure everything was on the table first before a decision was made. I’ll duck out now 🦆

@ppenzin
Copy link
Member

ppenzin commented Dec 22, 2020

Closing and reopening in hopes to clear the CI failure.

CC @divmain ;)

@ppenzin ppenzin closed this Dec 22, 2020
Javascript Standards automation moved this from In progress to Done Dec 22, 2020
@ppenzin ppenzin reopened this Dec 22, 2020
Javascript Standards automation moved this from Done to In progress Dec 22, 2020
@rhuanjl
Copy link
Collaborator Author

rhuanjl commented Dec 24, 2020

@ppenzin interestingly there's an option to override the CI and merge - I'm guessing there's no way to retrigger it now the repository has been transferred - considering it passed everything other than windows 7 - think we should just merge it?

@ppenzin
Copy link
Member

ppenzin commented Dec 26, 2020

@rhuanjl agree - we should merge it.

@ppenzin ppenzin merged commit 052db4c into chakra-core:master Dec 26, 2020
Javascript Standards automation moved this from In progress to Done Dec 26, 2020
@rhuanjl rhuanjl deleted the nullish branch December 26, 2020 09:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

None yet

4 participants