Przeglądaj źródła

Merge pull request #6545 from rhuanjl/nullish

Implement 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.
Petr Penzin 5 lat temu
rodzic
commit
052db4c502

+ 4 - 0
lib/Common/ConfigFlagsList.h

@@ -670,6 +670,7 @@ PHASE(All)
 #define DEFAULT_CONFIG_ESNumericSeparator      (true)
 #define DEFAULT_CONFIG_ESHashbang              (true)
 #define DEFAULT_CONFIG_ESSymbolDescription     (true)
+#define DEFAULT_CONFIG_ESNullishCoalescingOperator (true)
 #define DEFAULT_CONFIG_ESGlobalThis            (true)
 #ifdef COMPILE_DISABLE_ES6RegExPrototypeProperties
     // If ES6RegExPrototypeProperties needs to be disabled by compile flag, DEFAULT_CONFIG_ES6RegExPrototypeProperties should be false
@@ -1201,6 +1202,9 @@ FLAGR(Boolean, ESBigInt, "Enable ESBigInt flag", DEFAULT_CONFIG_ESBigInt)
 // ES Numeric Separator support for numeric constants
 FLAGR(Boolean, ESNumericSeparator, "Enable Numeric Separator flag", DEFAULT_CONFIG_ESNumericSeparator)
 
+// ES Nullish coalescing operator support (??)
+FLAGR(Boolean, ESNullishCoalescingOperator, "Enable nullish coalescing operator", DEFAULT_CONFIG_ESNullishCoalescingOperator)
+
 // ES Hashbang support for interpreter directive syntax
 FLAGR(Boolean, ESHashbang, "Enable Hashbang syntax", DEFAULT_CONFIG_ESHashbang)
 

+ 44 - 5
lib/Parser/Parse.cpp

@@ -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("??");
     case tkOr: return _u("|");
     case tkXor: return _u("^");
     case tkAnd: return _u("&");
@@ -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*/)
 {
     ParseNodePtr pnode = nullptr;
     PidRefStack *savedTopAsyncRef = nullptr;
@@ -3430,7 +3432,7 @@ ParseNodePtr Parser::ParseTerm(BOOL fAllowCall,
         AutoDeferErrorsRestore deferErrorRestore(this);
 
         this->m_funcParenExprDepth++;
-        pnode = ParseExpr<buildAST>(koplNo, &fCanAssign, TRUE, FALSE, nullptr, nullptr /*nameLength*/, nullptr  /*pShortNameOffset*/, &term, true, nullptr, plastRParen);
+        pnode = ParseExpr<buildAST>(koplNo, &fCanAssign, TRUE, FALSE, nullptr, nullptr /*nameLength*/, nullptr  /*pShortNameOffset*/, &term, true, nullptr, plastRParen, looseCoalesce);
         this->m_funcParenExprDepth--;
 
         if (buildAST && plastRParen)
@@ -3440,6 +3442,11 @@ ParseNodePtr Parser::ParseTerm(BOOL fAllowCall,
 
         ChkCurTok(tkRParen, ERRnoRparen);
 
+        if (looseCoalesce != nullptr)
+        {
+            *looseCoalesce = false;
+        }
+
         GetCurrentBlock()->blockId = saveCurrBlockId;
         if (m_token.tk == tkDArrow)
         {
@@ -8806,7 +8813,8 @@ ParseNodePtr Parser::ParseExpr(int oplMin,
     _Inout_opt_ IdentToken* pToken,
     bool fUnaryOrParen,
     _Inout_opt_ bool* pfLikelyPattern,
-    _Inout_opt_ charcount_t *plastRParen)
+    _Inout_opt_ charcount_t *plastRParen,
+    _Out_opt_ bool* looseCoalesce)
 {
     Assert(pToken == nullptr || pToken->tk == tkNone); // Must be empty initially
     int opl;
@@ -8822,6 +8830,7 @@ ParseNodePtr Parser::ParseExpr(int oplMin,
     uint32 hintLength = 0;
     uint32 hintOffset = 0;
     BOOL fLikelyPattern = FALSE;
+    bool localCoalesce = false;
 
     ParserState parserState;
 
@@ -9037,7 +9046,12 @@ ParseNodePtr Parser::ParseExpr(int oplMin,
     else
     {
         ichMin = this->GetScanner()->IchMinTok();
-        pnode = ParseTerm<buildAST>(TRUE, pNameHint, &hintLength, &hintOffset, &term, fUnaryOrParen, TRUE, &fCanAssign, &fLikelyPattern, &fIsDotOrIndex, plastRParen);
+        pnode = ParseTerm<buildAST>(TRUE, pNameHint, &hintLength, &hintOffset, &term, fUnaryOrParen, TRUE, &fCanAssign, &fLikelyPattern, &fIsDotOrIndex, plastRParen, &localCoalesce);
+        if (looseCoalesce != nullptr)
+        {
+            *looseCoalesce = localCoalesce;
+        }
+
         if (pfLikelyPattern != nullptr)
         {
             *pfLikelyPattern = !!fLikelyPattern;
@@ -9299,8 +9313,26 @@ ParseNodePtr Parser::ParseExpr(int oplMin,
 
             // Parse the operand, make a new node, and look for more
             IdentToken token;
+            bool coalescing = false;
+
             ParseNode* pnode2 = ParseExpr<buildAST>(
-                opl, nullptr, fAllowIn, FALSE, pNameHint, &hintLength, &hintOffset, &token, false, nullptr, plastRParen);
+                opl, nullptr, fAllowIn, FALSE, pNameHint, &hintLength, &hintOffset, &token, false, nullptr, plastRParen, &coalescing);
+
+            if (nop == knopLogAnd || nop == knopLogOr)
+            {
+                if (localCoalesce || coalescing)
+                {
+                    Error(ERRCoalesce);
+                }
+            }
+            else if (nop == knopCoalesce)
+            {
+                localCoalesce = true;
+                if (looseCoalesce != nullptr)
+                {
+                    *looseCoalesce = true;
+                }
+            }
 
             // Detect nested function escapes of the pattern "o.f = function(){...}" or "o[s] = function(){...}".
             // Doing so in the parser allows us to disable stack-nested-functions in common cases where an escape
@@ -12707,6 +12739,7 @@ ParseNode* Parser::CopyPnode(ParseNode *pnode) {
     case knopComma:
     case knopLogOr:
     case knopLogAnd:
+    case knopCoalesce:
     case knopLsh:
     case knopRsh:
     case knopRs2:
@@ -13995,6 +14028,12 @@ void PrintPnodeWIndent(ParseNode *pnode, int indentAmt) {
         PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE);
         PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE);
         break;
+    case knopCoalesce:
+        Indent(indentAmt);
+        Output::Print(_u("??\n"));
+        PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE);
+        PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE);
+        break;
         //PTNODE(knopLsh        , "<<"        ,Lsh     ,Bin  ,fnopBin)
     case knopLsh:
         Indent(indentAmt);

+ 5 - 2
lib/Parser/Parse.h

@@ -22,6 +22,7 @@ enum
     koplQue,    // ?:
     koplLor,    // ||
     koplLan,    // &&
+    koplLco,    // ??
     koplBor,    // |
     koplXor,    // ^
     koplBan,    // &
@@ -941,7 +942,8 @@ private:
         _Inout_opt_ IdentToken* pToken = NULL,
         bool fUnaryOrParen = false,
         _Inout_opt_ bool* pfLikelyPattern = nullptr,
-        _Inout_opt_ charcount_t *plastRParen = nullptr);
+        _Inout_opt_ charcount_t *plastRParen = nullptr,
+        _Out_opt_ bool* looseCoalesce = nullptr);
 
     template<bool buildAST> ParseNodePtr ParseTerm(
         BOOL fAllowCall = TRUE,
@@ -954,7 +956,8 @@ private:
         _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);
 
     template<bool buildAST> ParseNodePtr ParsePostfixOperators(
         ParseNodePtr pnode,

+ 12 - 1
lib/Parser/Scan.cpp

@@ -1770,7 +1770,18 @@ LEof:
         case '[': Assert(chType == _C_LBR); token = tkLBrack; break;
         case ']': Assert(chType == _C_RBR); token = tkRBrack; break;
         case '~': Assert(chType == _C_TIL); token = tkTilde;  break;
-        case '?': Assert(chType == _C_QUE); token = tkQMark;  break;
+
+        case '?':
+            Assert(chType == _C_QUE);
+            token = tkQMark;
+            if (m_scriptContext->GetConfig()->IsESNullishCoalescingOperatorEnabled() && this->PeekFirst(p, last) == '?')
+            {
+                p++;
+                token = tkCoalesce;
+                break;
+            }
+            break;
+
         case '{': Assert(chType == _C_LC);  token = tkLCurly; break;
 
         // ES 2015 11.3 Line Terminators

+ 1 - 0
lib/Parser/kwd-lsc.h

@@ -145,6 +145,7 @@ TOK_DCL(tkQMark         ,Que, knopQmark  , No, knopNone   ) // ?
 TOK_DCL(tkColon         , No, knopNone   , No, knopNone   ) // :
 TOK_DCL(tkLogOr         ,Lor, knopLogOr  , No, knopNone   ) // ||
 TOK_DCL(tkLogAnd        ,Lan, knopLogAnd , No, knopNone   ) // &&
+TOK_DCL(tkCoalesce      ,Lco, knopCoalesce,No, knopNone   ) // ??
 TOK_DCL(tkOr            ,Bor, knopOr     , No, knopNone   ) // |
 TOK_DCL(tkXor           ,Xor, knopXor    , No, knopNone   ) // ^
 TOK_DCL(tkAnd           ,Ban, knopAnd    , No, knopNone   ) // &

+ 70 - 70
lib/Parser/perrors.h

@@ -6,85 +6,85 @@
 // NOTE: the error numbers should not change from version to version.
 // Error numbers MUST be sorted.
 
-LSC_ERROR_MSG( 1001, ERRnoMemory      , "Out of memory")
-LSC_ERROR_MSG( 1002, ERRsyntax        , "Syntax error")
-LSC_ERROR_MSG( 1003, ERRnoColon       , "Expected ':'")
-LSC_ERROR_MSG( 1004, ERRnoSemic       , "Expected ';'")
-LSC_ERROR_MSG( 1005, ERRnoLparen      , "Expected '('")
-LSC_ERROR_MSG( 1006, ERRnoRparen      , "Expected ')'")
-LSC_ERROR_MSG( 1007, ERRnoRbrack      , "Expected ']'")
-LSC_ERROR_MSG( 1008, ERRnoLcurly      , "Expected '{'")
-LSC_ERROR_MSG( 1009, ERRnoRcurly      , "Expected '}'")
-LSC_ERROR_MSG( 1010, ERRnoIdent       , "Expected identifier")
-LSC_ERROR_MSG( 1011, ERRnoEq          , "Expected '='")
-LSC_ERROR_MSG( 1012, ERRnoSlash       , "Expected '/'")
-LSC_ERROR_MSG( 1013, ERRbadNumber     , "Invalid number")
-LSC_ERROR_MSG( 1014, ERRillegalChar   , "Invalid character")
-LSC_ERROR_MSG( 1015, ERRnoStrEnd      , "Unterminated string constant")
-LSC_ERROR_MSG( 1016, ERRnoCmtEnd      , "Unterminated comment")
-LSC_ERROR_MSG( 1017, ERRIdAfterLit    , "Unexpected identifier after numeric literal")
+LSC_ERROR_MSG(1001, ERRnoMemory      , "Out of memory")
+LSC_ERROR_MSG(1002, ERRsyntax        , "Syntax error")
+LSC_ERROR_MSG(1003, ERRnoColon       , "Expected ':'")
+LSC_ERROR_MSG(1004, ERRnoSemic       , "Expected ';'")
+LSC_ERROR_MSG(1005, ERRnoLparen      , "Expected '('")
+LSC_ERROR_MSG(1006, ERRnoRparen      , "Expected ')'")
+LSC_ERROR_MSG(1007, ERRnoRbrack      , "Expected ']'")
+LSC_ERROR_MSG(1008, ERRnoLcurly      , "Expected '{'")
+LSC_ERROR_MSG(1009, ERRnoRcurly      , "Expected '}'")
+LSC_ERROR_MSG(1010, ERRnoIdent       , "Expected identifier")
+LSC_ERROR_MSG(1011, ERRnoEq          , "Expected '='")
+LSC_ERROR_MSG(1012, ERRnoSlash       , "Expected '/'")
+LSC_ERROR_MSG(1013, ERRbadNumber     , "Invalid number")
+LSC_ERROR_MSG(1014, ERRillegalChar   , "Invalid character")
+LSC_ERROR_MSG(1015, ERRnoStrEnd      , "Unterminated string constant")
+LSC_ERROR_MSG(1016, ERRnoCmtEnd      , "Unterminated comment")
+LSC_ERROR_MSG(1017, ERRIdAfterLit    , "Unexpected identifier after numeric literal")
 
-LSC_ERROR_MSG( 1018, ERRbadReturn     , "'return' statement outside of function")
-LSC_ERROR_MSG( 1019, ERRbadBreak      , "Can't have 'break' outside of loop")
-LSC_ERROR_MSG( 1020, ERRbadContinue   , "Can't have 'continue' outside of loop")
+LSC_ERROR_MSG(1018, ERRbadReturn     , "'return' statement outside of function")
+LSC_ERROR_MSG(1019, ERRbadBreak      , "Can't have 'break' outside of loop")
+LSC_ERROR_MSG(1020, ERRbadContinue   , "Can't have 'continue' outside of loop")
 
-LSC_ERROR_MSG( 1023, ERRbadHexDigit   , "Expected hexadecimal digit")
-LSC_ERROR_MSG( 1024, ERRnoWhile       , "Expected 'while'")
-LSC_ERROR_MSG( 1025, ERRbadLabel      , "Label redefined")
-LSC_ERROR_MSG( 1026, ERRnoLabel       , "Label not found")
-LSC_ERROR_MSG( 1027, ERRdupDefault    , "'default' can only appear once in a 'switch' statement")
-LSC_ERROR_MSG( 1028, ERRnoMemberIdent , "Expected identifier, string or number")
-LSC_ERROR_MSG( 1029, ERRTooManyArgs   , "Too many arguments")
+LSC_ERROR_MSG(1023, ERRbadHexDigit   , "Expected hexadecimal digit")
+LSC_ERROR_MSG(1024, ERRnoWhile       , "Expected 'while'")
+LSC_ERROR_MSG(1025, ERRbadLabel      , "Label redefined")
+LSC_ERROR_MSG(1026, ERRnoLabel       , "Label not found")
+LSC_ERROR_MSG(1027, ERRdupDefault    , "'default' can only appear once in a 'switch' statement")
+LSC_ERROR_MSG(1028, ERRnoMemberIdent , "Expected identifier, string or number")
+LSC_ERROR_MSG(1029, ERRTooManyArgs   , "Too many arguments")
 // RETIRED Cc no longer supported ;; LSC_ERROR_MSG( 1030, ERRccOff         , "Conditional compilation is turned off")
-LSC_ERROR_MSG( 1031, ERRnotConst      , "Expected constant")
+LSC_ERROR_MSG(1031, ERRnotConst      , "Expected constant")
 // RETIRED Cc no longer supported ;; LSC_ERROR_MSG( 1032, ERRnoAt          , "Expected '@'")
-LSC_ERROR_MSG( 1033, ERRnoCatch       , "Expected 'catch'")
-LSC_ERROR_MSG( 1034, ERRnoVar         , "Expected 'var'")
-LSC_ERROR_MSG( 1035, ERRdanglingThrow , "'throw' must be followed by an expression on the same source line")
+LSC_ERROR_MSG(1033, ERRnoCatch       , "Expected 'catch'")
+LSC_ERROR_MSG(1034, ERRnoVar         , "Expected 'var'")
+LSC_ERROR_MSG(1035, ERRdanglingThrow , "'throw' must be followed by an expression on the same source line")
 // RETIRED ECMACP removed ;; LSC_ERROR_MSG( 1036, ERRWithNotInCP   , "'with' not available in the ECMA 327 Compact Profile")
 
-LSC_ERROR_MSG( 1037, ERRES5NoWith     , "'with' statements are not allowed in strict mode") // string 8
-LSC_ERROR_MSG( 1038, ERRES5ArgSame    , "Duplicate formal parameter names not allowed in strict mode") // string 9
-LSC_ERROR_MSG( 1039, ERRES5NoOctal    , "Octal numeric literals and escape characters not allowed in strict mode") // string 1
-LSC_ERROR_MSG( 1041, ERREvalUsage     , "Invalid usage of 'eval' in strict mode") // string 3
-LSC_ERROR_MSG( 1042, ERRArgsUsage     , "Invalid usage of 'arguments' in strict mode") // string 3
-LSC_ERROR_MSG( 1045, ERRInvalidDelete , "Calling delete on expression not allowed in strict mode") //string 4
-LSC_ERROR_MSG( 1046, ERRDupeObjLit    , "Multiple definitions of a property not allowed in strict mode") //string 7
-LSC_ERROR_MSG( 1047, ERRFncDeclNotSourceElement, "In strict mode, function declarations cannot be nested inside a statement or block. They may only appear at the top level or directly inside a function body.")
-LSC_ERROR_MSG( 1048, ERRKeywordNotId  , "The use of a keyword for an identifier is invalid")
-LSC_ERROR_MSG( 1049, ERRFutureReservedWordNotId, "The use of a future reserved word for an identifier is invalid")
-LSC_ERROR_MSG( 1050, ERRFutureReservedWordInStrictModeNotId, "The use of a future reserved word for an identifier is invalid. The identifier name is reserved in strict mode.")
-LSC_ERROR_MSG( 1051, ERRSetterMustHaveOneParameter, "Setter functions must have exactly one parameter")
-LSC_ERROR_MSG( 1052, ERRRedeclaration  , "Let/Const redeclaration") // "var x; let x;" is also a redeclaration
-LSC_ERROR_MSG( 1053, ERRUninitializedConst  , "Const must be initialized")
-LSC_ERROR_MSG( 1054, ERRDeclOutOfStmt  , "Declaration outside statement context")
-LSC_ERROR_MSG( 1055, ERRAssignmentToConst  , "Assignment to const")
-LSC_ERROR_MSG( 1056, ERRUnicodeOutOfRange  , "Unicode escape sequence value is higher than 0x10FFFF")
-LSC_ERROR_MSG( 1057, ERRInvalidSpreadUse   , "Invalid use of the ... operator. Spread can only be used in call arguments or an array literal.")
-LSC_ERROR_MSG( 1058, ERRInvalidSuper        , "Invalid use of the 'super' keyword")
-LSC_ERROR_MSG( 1059, ERRInvalidSuperScope   , "The 'super' keyword cannot be used at global scope")
-LSC_ERROR_MSG( 1060, ERRSuperInIndirectEval , "The 'super' keyword cannot be used in an indirect eval() call")
-LSC_ERROR_MSG( 1061, ERRSuperInGlobalEval   , "The 'super' keyword cannot be used in a globally scoped eval() call")
-LSC_ERROR_MSG( 1062, ERRnoDArrow      , "Expected '=>'")
+LSC_ERROR_MSG(1037, ERRES5NoWith     , "'with' statements are not allowed in strict mode") // string 8
+LSC_ERROR_MSG(1038, ERRES5ArgSame    , "Duplicate formal parameter names not allowed in strict mode") // string 9
+LSC_ERROR_MSG(1039, ERRES5NoOctal    , "Octal numeric literals and escape characters not allowed in strict mode") // string 1
+LSC_ERROR_MSG(1041, ERREvalUsage     , "Invalid usage of 'eval' in strict mode") // string 3
+LSC_ERROR_MSG(1042, ERRArgsUsage     , "Invalid usage of 'arguments' in strict mode") // string 3
+LSC_ERROR_MSG(1045, ERRInvalidDelete , "Calling delete on expression not allowed in strict mode") //string 4
+LSC_ERROR_MSG(1046, ERRDupeObjLit    , "Multiple definitions of a property not allowed in strict mode") //string 7
+LSC_ERROR_MSG(1047, ERRFncDeclNotSourceElement, "In strict mode, function declarations cannot be nested inside a statement or block. They may only appear at the top level or directly inside a function body.")
+LSC_ERROR_MSG(1048, ERRKeywordNotId  , "The use of a keyword for an identifier is invalid")
+LSC_ERROR_MSG(1049, ERRFutureReservedWordNotId, "The use of a future reserved word for an identifier is invalid")
+LSC_ERROR_MSG(1050, ERRFutureReservedWordInStrictModeNotId, "The use of a future reserved word for an identifier is invalid. The identifier name is reserved in strict mode.")
+LSC_ERROR_MSG(1051, ERRSetterMustHaveOneParameter, "Setter functions must have exactly one parameter")
+LSC_ERROR_MSG(1052, ERRRedeclaration  , "Let/Const redeclaration") // "var x; let x;" is also a redeclaration
+LSC_ERROR_MSG(1053, ERRUninitializedConst  , "Const must be initialized")
+LSC_ERROR_MSG(1054, ERRDeclOutOfStmt  , "Declaration outside statement context")
+LSC_ERROR_MSG(1055, ERRAssignmentToConst  , "Assignment to const")
+LSC_ERROR_MSG(1056, ERRUnicodeOutOfRange  , "Unicode escape sequence value is higher than 0x10FFFF")
+LSC_ERROR_MSG(1057, ERRInvalidSpreadUse   , "Invalid use of the ... operator. Spread can only be used in call arguments or an array literal.")
+LSC_ERROR_MSG(1058, ERRInvalidSuper        , "Invalid use of the 'super' keyword")
+LSC_ERROR_MSG(1059, ERRInvalidSuperScope   , "The 'super' keyword cannot be used at global scope")
+LSC_ERROR_MSG(1060, ERRSuperInIndirectEval , "The 'super' keyword cannot be used in an indirect eval() call")
+LSC_ERROR_MSG(1061, ERRSuperInGlobalEval   , "The 'super' keyword cannot be used in a globally scoped eval() call")
+LSC_ERROR_MSG(1062, ERRnoDArrow      , "Expected '=>'")
 
-LSC_ERROR_MSG( 1063, ERRInvalidCodePoint      , "Invalid codepoint value in the escape sequence.")
-LSC_ERROR_MSG( 1064, ERRMissingCurlyBrace      , "Closing curly brace ('}') expected.")
-LSC_ERROR_MSG( 1065, ERRRestLastArg, "The rest parameter must be the last parameter in a formals list.")
-LSC_ERROR_MSG( 1066, ERRRestWithDefault, "The rest parameter cannot have a default initializer.")
-LSC_ERROR_MSG( 1067, ERRUnexpectedEllipsis, "Unexpected ... operator")
+LSC_ERROR_MSG(1063, ERRInvalidCodePoint      , "Invalid codepoint value in the escape sequence.")
+LSC_ERROR_MSG(1064, ERRMissingCurlyBrace      , "Closing curly brace ('}') expected.")
+LSC_ERROR_MSG(1065, ERRRestLastArg, "The rest parameter must be the last parameter in a formals list.")
+LSC_ERROR_MSG(1066, ERRRestWithDefault, "The rest parameter cannot have a default initializer.")
+LSC_ERROR_MSG(1067, ERRUnexpectedEllipsis, "Unexpected ... operator")
 
-LSC_ERROR_MSG( 1068, ERRDestructInit, "Destructuring declarations must have an initializer")
-LSC_ERROR_MSG( 1069, ERRDestructRestLast, "Destructuring rest variables must be in the last position of the expression")
-LSC_ERROR_MSG( 1070, ERRUnexpectedDefault, "Unexpected default initializer")
-LSC_ERROR_MSG( 1071, ERRDestructNoOper, "Unexpected operator in destructuring expression")
-LSC_ERROR_MSG( 1072, ERRDestructIDRef, "Destructuring expressions can only have identifier references")
+LSC_ERROR_MSG(1068, ERRDestructInit, "Destructuring declarations must have an initializer")
+LSC_ERROR_MSG(1069, ERRDestructRestLast, "Destructuring rest variables must be in the last position of the expression")
+LSC_ERROR_MSG(1070, ERRUnexpectedDefault, "Unexpected default initializer")
+LSC_ERROR_MSG(1071, ERRDestructNoOper, "Unexpected operator in destructuring expression")
+LSC_ERROR_MSG(1072, ERRDestructIDRef, "Destructuring expressions can only have identifier references")
 
-LSC_ERROR_MSG( 1073, ERRYieldInTryCatchOrFinally, "'yield' expressions are not allowed in 'try', 'catch', or 'finally' blocks")
-LSC_ERROR_MSG( 1074, ERRConstructorCannotBeGenerator, "Class constructor may not be a generator")
-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")
-// 1078 -- removed
+LSC_ERROR_MSG(1073, ERRYieldInTryCatchOrFinally, "'yield' expressions are not allowed in 'try', 'catch', or 'finally' blocks")
+LSC_ERROR_MSG(1074, ERRConstructorCannotBeGenerator, "Class constructor may not be a generator")
+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")
 LSC_ERROR_MSG(1079, ERRInvalidNewTarget, "Invalid use of the 'new.target' keyword")
 LSC_ERROR_MSG(1080, ERRForInNoInitAllowed, "for-in loop head declarations cannot have an initializer")
 LSC_ERROR_MSG(1081, ERRForOfNoInitAllowed, "for-of loop head declarations cannot have an initializer")

+ 1 - 0
lib/Parser/ptlist.h

@@ -88,6 +88,7 @@ PTNODE(knopNEqv       , "!=="              , OP(SrNeq), Bin         , fnopBin|fn
 PTNODE(knopComma      , ","                , Nop      , Bin         , fnopBin               , "CommaOper"                      )
 PTNODE(knopLogOr      , "||"               , Nop      , Bin         , fnopBin               , "LogOrOper"                      )
 PTNODE(knopLogAnd     , "&&"               , Nop      , Bin         , fnopBin               , "LogAndOper"                     )
+PTNODE(knopCoalesce   , "??"               , Nop      , Bin         , fnopBin               , "NullishCoalescingOper"          )
 PTNODE(knopLsh        , "<<"               , Shl_A    , Bin         , fnopBin               , "LeftShiftOper"                  )
 PTNODE(knopRsh        , ">>"               , Shr_A    , Bin         , fnopBin               , "RightShiftOper"                 )
 PTNODE(knopRs2        , ">>>"              , ShrU_A   , Bin         , fnopBin               , "UnsignedRightShiftOper"         )

+ 1 - 0
lib/Runtime/Base/ThreadConfigFlagsList.h

@@ -44,6 +44,7 @@ FLAG_RELEASE(IsESImportMetaEnabled, ESImportMeta)
 FLAG_RELEASE(IsESBigIntEnabled, ESBigInt)
 FLAG_RELEASE(IsESNumericSeparatorEnabled, ESNumericSeparator)
 FLAG_RELEASE(IsESHashbangEnabled, ESHashbang)
+FLAG_RELEASE(IsESNullishCoalescingOperatorEnabled, ESNullishCoalescingOperator)
 FLAG_RELEASE(IsESExportNsAsEnabled, ESExportNsAs)
 FLAG_RELEASE(IsESSymbolDescriptionEnabled, ESSymbolDescription)
 FLAG_RELEASE(IsESGlobalThisEnabled, ESGlobalThis)

+ 26 - 0
lib/Runtime/ByteCode/ByteCodeEmitter.cpp

@@ -11724,6 +11724,32 @@ void Emit(ParseNode* pnode, ByteCodeGenerator* byteCodeGenerator, FuncInfo* func
         ENDSTATEMENET_IFTOPLEVEL(isTopLevel, pnode);
         break;
     }
+    // The Coalescing operator resolves to the left hand side if it is not null or undefined
+    // 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:
+    {
+        STARTSTATEMENET_IFTOPLEVEL(isTopLevel, pnode);
+        Js::ByteCodeLabel doneLabel = byteCodeGenerator->Writer()->DefineLabel();
+        funcInfo->AcquireLoc(pnode);
+
+        // LHS
+        Emit(pnode->AsParseNodeBin()->pnode1, byteCodeGenerator, funcInfo, false);
+        byteCodeGenerator->Writer()->Reg2(Js::OpCode::Ld_A, pnode->location, pnode->AsParseNodeBin()->pnode1->location);
+        funcInfo->ReleaseLoc(pnode->AsParseNodeBin()->pnode1);
+        // check for null/undefined with != null
+        byteCodeGenerator->Writer()->BrReg2(Js::OpCode::BrNeq_A, doneLabel, pnode->location, funcInfo->nullConstantRegister);
+
+        // RHS
+        Emit(pnode->AsParseNodeBin()->pnode2, byteCodeGenerator, funcInfo, false);
+        byteCodeGenerator->Writer()->Reg2(Js::OpCode::Ld_A_ReuseLoc, pnode->location, pnode->AsParseNodeBin()->pnode2->location);
+        funcInfo->ReleaseLoc(pnode->AsParseNodeBin()->pnode2);
+
+        byteCodeGenerator->Writer()->MarkLabel(doneLabel);
+        ENDSTATEMENET_IFTOPLEVEL(isTopLevel, pnode);
+        break;
+    }
     // PTNODE(knopQmark      , "?"            ,None    ,Tri  ,fnopBin)
     case knopQmark:
     {

+ 1 - 0
lib/Runtime/ByteCode/ByteCodeGenerator.cpp

@@ -4955,6 +4955,7 @@ void AssignRegisters(ParseNode *pnode, ByteCodeGenerator *byteCodeGenerator)
         byteCodeGenerator->EnregisterConstant(1);
         CheckMaybeEscapedUse(pnode->AsParseNodeUni()->pnode1, byteCodeGenerator);
         break;
+    case knopCoalesce:
     case knopObject:
         byteCodeGenerator->AssignNullConstRegister();
         break;

+ 125 - 0
test/es7/nullish.js

@@ -0,0 +1,125 @@
+//-------------------------------------------------------------------------------------------------------
+// Copyright (C) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
+//-------------------------------------------------------------------------------------------------------
+
+WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
+
+const o = {
+    get prop() {
+        assert.fail("Should not be called");
+    }
+}
+
+var tests = [
+    {
+        name: "Object properties used as left-side for nullish operator",
+        body () {
+            const obj = {
+                nullvalue: null,
+                undefinedvalue: undefined,
+                number: 123,
+                zero: 0,
+                falsevalue: false,
+                emptystring: ''
+            };
+            const other = 'other';
+
+            assert.areEqual(other, obj.nullvalue ?? other);
+            assert.areEqual(other, obj.undefinedvalue ?? other);
+            assert.areEqual(other, obj.notdefinedvalue ?? other);
+
+            assert.areEqual(obj.number, obj.number ?? other);
+            assert.areEqual(obj.zero, obj.zero ?? other);
+            assert.areEqual(obj.falsevalue, obj.falsevalue ?? other);
+            assert.areEqual(obj.emptystring, obj.emptystring ?? other);
+        }
+    },
+    {
+        name: "Primitive values used as left-side for nullish operator",
+        body () {
+            const other = 'other';
+
+            assert.areEqual(other, null ?? other);
+            assert.areEqual(other, undefined ?? other);
+
+            assert.areEqual(123, 123 ?? other);
+            assert.areEqual(0, 0 ?? other);
+            assert.areEqual(false, false ?? other);
+            assert.areEqual('', '' ?? other);
+        }
+    },
+    {
+        name: "Names used as left-side for nullish operator",
+        body () {
+            const nullvalue = null;
+            const undefinedvalue = undefined;
+            const number = 123;
+            const zero = 0;
+            const falsevalue = false;
+            const emptystring = '';
+            const other = 'other';
+
+            assert.areEqual(other, nullvalue ?? other);
+            assert.areEqual(other, undefinedvalue ?? other);
+
+            assert.areEqual(number, number ?? other);
+            assert.areEqual(zero, zero ?? other);
+            assert.areEqual(falsevalue, falsevalue ?? other);
+            assert.areEqual(emptystring, emptystring ?? other);
+        }
+    },
+    {
+        name: "Right-hand side is evaluated only if needed (short-circuiting)",
+        body () {
+            assert.areEqual('not null', 'not null' ?? o.prop);
+        }
+    },
+    {
+        name : "?? interaction with ||",
+        body () {
+            assert.areEqual(5, null ?? (5 || 4));
+            assert.areEqual(5, 5 ?? (1 || 4));
+            assert.areEqual(5, (null ?? 5) || 4);
+            assert.areEqual(5, (5 ?? null) || 4);
+            assert.areEqual(5, (5 ?? null) || o.prop);
+        }
+    },
+    {
+        name : "?? interaction with &&",
+        body() {
+            assert.areEqual(5, null ?? (2 && 5));
+            assert.areEqual(5, (null ?? 2) && 5);
+            assert.areEqual(5, 2 && (5 ?? o.prop ?? o.prop));
+            assert.areEqual(5, 2 && (3 ?? o.prop) && 5);
+            assert.areEqual(null, null && (null ?? 5));
+        }  
+    },
+    {
+        name: "?? cannot be used within || or && without parentheses",
+        body() {
+            function parse (code) {
+                try {
+                    eval ("function test() {" + code + "}");
+                    return true;
+                } catch {
+                    return false;
+                }
+            }
+            assert.isFalse(parse("a ?? b || c"));
+            assert.isFalse(parse("a || b ?? c"));
+            assert.isFalse(parse("a ?? b && c"));
+            assert.isFalse(parse("a && b ?? c"));
+            assert.isTrue(parse("a && (b ?? c)"));
+            assert.isTrue(parse("(a && b) ?? c"));
+            assert.isTrue(parse("a && (b ?? c)"));
+            assert.isTrue(parse("a || (b ?? c)"));
+            assert.isTrue(parse("(a || b) ?? c"));
+            assert.isTrue(parse("a || (b ?? c)"));
+            assert.isFalse(parse("a ?? (b ?? c) || a"));
+            assert.isTrue(parse("(a ?? b ?? c) || a"));
+        }
+    },
+];
+
+testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });

+ 6 - 0
test/es7/rlexe.xml

@@ -143,4 +143,10 @@
       <compile-flags>-force:deferparse -args summary -endargs</compile-flags>
     </default>
   </test>
+  <test>
+    <default>
+      <files>nullish.js</files>
+      <compile-flags>-args summary -endargs</compile-flags>
+    </default>
+  </test>
 </regress-exe>