Commit 3ec10365 authored by Joshua Litt's avatar Joshua Litt Committed by Commit Bot

[nullish] Add support for nullish operator

This CL implements the nullish operator in bytecode as defined by:
https://github.com/tc39/proposal-nullish-coalescing. It can be
enabled by passing '--harmony-nullish'.

Nullish is similar to logical operators, but instead of truthy/falsey
values, it short circuits when it evaluates a null or undefined value.


Bug: v8:9547
Change-Id: Ia0f55877fc2714482b5547942baef9733537d1b9
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1738568Reviewed-by: 's avatarSathya Gunasekaran  <gsathya@chromium.org>
Reviewed-by: 's avatarLeszek Swirski <leszeks@chromium.org>
Commit-Queue: Joshua Litt <joshualitt@chromium.org>
Cr-Commit-Position: refs/heads/master@{#63317}
parent 882f8c6b
...@@ -122,6 +122,10 @@ bool Expression::IsUndefinedLiteral() const { ...@@ -122,6 +122,10 @@ bool Expression::IsUndefinedLiteral() const {
var_proxy->raw_name()->IsOneByteEqualTo("undefined"); var_proxy->raw_name()->IsOneByteEqualTo("undefined");
} }
bool Expression::IsLiteralButNotNullOrUndefined() const {
return IsLiteral() && !IsNullOrUndefinedLiteral();
}
bool Expression::ToBooleanIsTrue() const { bool Expression::ToBooleanIsTrue() const {
return IsLiteral() && AsLiteral()->ToBooleanIsTrue(); return IsLiteral() && AsLiteral()->ToBooleanIsTrue();
} }
......
...@@ -246,6 +246,14 @@ class Expression : public AstNode { ...@@ -246,6 +246,14 @@ class Expression : public AstNode {
// that this also checks for loads of the global "undefined" variable. // that this also checks for loads of the global "undefined" variable.
bool IsUndefinedLiteral() const; bool IsUndefinedLiteral() const;
// True if either null literal or undefined literal.
inline bool IsNullOrUndefinedLiteral() const {
return IsNullLiteral() || IsUndefinedLiteral();
}
// True if a literal and not null or undefined.
bool IsLiteralButNotNullOrUndefined() const;
bool IsCompileTimeValue(); bool IsCompileTimeValue();
bool IsPattern() { bool IsPattern() {
......
...@@ -208,7 +208,8 @@ DEFINE_IMPLICATION(harmony_import_meta, harmony_dynamic_import) ...@@ -208,7 +208,8 @@ DEFINE_IMPLICATION(harmony_import_meta, harmony_dynamic_import)
V(harmony_regexp_sequence, "RegExp Unicode sequence properties") \ V(harmony_regexp_sequence, "RegExp Unicode sequence properties") \
V(harmony_weak_refs, "harmony weak references") \ V(harmony_weak_refs, "harmony weak references") \
V(harmony_optional_chaining, "harmony optional chaining syntax") \ V(harmony_optional_chaining, "harmony optional chaining syntax") \
V(harmony_regexp_match_indices, "harmony regexp match indices") V(harmony_regexp_match_indices, "harmony regexp match indices") \
V(harmony_nullish, "harmony nullish operator")
#ifdef V8_INTL_SUPPORT #ifdef V8_INTL_SUPPORT
#define HARMONY_INPROGRESS(V) \ #define HARMONY_INPROGRESS(V) \
......
...@@ -4263,6 +4263,7 @@ EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_dynamic_import) ...@@ -4263,6 +4263,7 @@ EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_dynamic_import)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_import_meta) EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_import_meta)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_regexp_sequence) EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_regexp_sequence)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_optional_chaining) EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_optional_chaining)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_nullish)
#ifdef V8_INTL_SUPPORT #ifdef V8_INTL_SUPPORT
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_intl_add_calendar_numbering_system) EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_intl_add_calendar_numbering_system)
......
...@@ -5111,6 +5111,9 @@ void BytecodeGenerator::VisitBinaryOperation(BinaryOperation* binop) { ...@@ -5111,6 +5111,9 @@ void BytecodeGenerator::VisitBinaryOperation(BinaryOperation* binop) {
case Token::AND: case Token::AND:
VisitLogicalAndExpression(binop); VisitLogicalAndExpression(binop);
break; break;
case Token::NULLISH:
VisitNullishExpression(binop);
break;
default: default:
VisitArithmeticExpression(binop); VisitArithmeticExpression(binop);
break; break;
...@@ -5128,6 +5131,9 @@ void BytecodeGenerator::VisitNaryOperation(NaryOperation* expr) { ...@@ -5128,6 +5131,9 @@ void BytecodeGenerator::VisitNaryOperation(NaryOperation* expr) {
case Token::AND: case Token::AND:
VisitNaryLogicalAndExpression(expr); VisitNaryLogicalAndExpression(expr);
break; break;
case Token::NULLISH:
VisitNaryNullishExpression(expr);
break;
default: default:
VisitNaryArithmeticExpression(expr); VisitNaryArithmeticExpression(expr);
break; break;
...@@ -5511,14 +5517,16 @@ void BytecodeGenerator::VisitNaryCommaExpression(NaryOperation* expr) { ...@@ -5511,14 +5517,16 @@ void BytecodeGenerator::VisitNaryCommaExpression(NaryOperation* expr) {
void BytecodeGenerator::VisitLogicalTestSubExpression( void BytecodeGenerator::VisitLogicalTestSubExpression(
Token::Value token, Expression* expr, BytecodeLabels* then_labels, Token::Value token, Expression* expr, BytecodeLabels* then_labels,
BytecodeLabels* else_labels, int coverage_slot) { BytecodeLabels* else_labels, int coverage_slot) {
DCHECK(token == Token::OR || token == Token::AND); DCHECK(token == Token::OR || token == Token::AND || token == Token::NULLISH);
BytecodeLabels test_next(zone()); BytecodeLabels test_next(zone());
if (token == Token::OR) { if (token == Token::OR) {
VisitForTest(expr, then_labels, &test_next, TestFallthrough::kElse); VisitForTest(expr, then_labels, &test_next, TestFallthrough::kElse);
} else { } else if (token == Token::AND) {
DCHECK_EQ(Token::AND, token);
VisitForTest(expr, &test_next, else_labels, TestFallthrough::kThen); VisitForTest(expr, &test_next, else_labels, TestFallthrough::kThen);
} else {
DCHECK_EQ(Token::NULLISH, token);
VisitForNullishTest(expr, then_labels, &test_next, else_labels);
} }
test_next.Bind(builder()); test_next.Bind(builder());
...@@ -5528,7 +5536,7 @@ void BytecodeGenerator::VisitLogicalTestSubExpression( ...@@ -5528,7 +5536,7 @@ void BytecodeGenerator::VisitLogicalTestSubExpression(
void BytecodeGenerator::VisitLogicalTest(Token::Value token, Expression* left, void BytecodeGenerator::VisitLogicalTest(Token::Value token, Expression* left,
Expression* right, Expression* right,
int right_coverage_slot) { int right_coverage_slot) {
DCHECK(token == Token::OR || token == Token::AND); DCHECK(token == Token::OR || token == Token::AND || token == Token::NULLISH);
TestResultScope* test_result = execution_result()->AsTest(); TestResultScope* test_result = execution_result()->AsTest();
BytecodeLabels* then_labels = test_result->then_labels(); BytecodeLabels* then_labels = test_result->then_labels();
BytecodeLabels* else_labels = test_result->else_labels(); BytecodeLabels* else_labels = test_result->else_labels();
...@@ -5543,7 +5551,7 @@ void BytecodeGenerator::VisitLogicalTest(Token::Value token, Expression* left, ...@@ -5543,7 +5551,7 @@ void BytecodeGenerator::VisitLogicalTest(Token::Value token, Expression* left,
void BytecodeGenerator::VisitNaryLogicalTest( void BytecodeGenerator::VisitNaryLogicalTest(
Token::Value token, NaryOperation* expr, Token::Value token, NaryOperation* expr,
const NaryCodeCoverageSlots* coverage_slots) { const NaryCodeCoverageSlots* coverage_slots) {
DCHECK(token == Token::OR || token == Token::AND); DCHECK(token == Token::OR || token == Token::AND || token == Token::NULLISH);
DCHECK_GT(expr->subsequent_length(), 0); DCHECK_GT(expr->subsequent_length(), 0);
TestResultScope* test_result = execution_result()->AsTest(); TestResultScope* test_result = execution_result()->AsTest();
...@@ -5599,6 +5607,27 @@ bool BytecodeGenerator::VisitLogicalAndSubExpression(Expression* expr, ...@@ -5599,6 +5607,27 @@ bool BytecodeGenerator::VisitLogicalAndSubExpression(Expression* expr,
return false; return false;
} }
bool BytecodeGenerator::VisitNullishSubExpression(Expression* expr,
BytecodeLabels* end_labels,
int coverage_slot) {
if (expr->IsLiteralButNotNullOrUndefined()) {
VisitForAccumulatorValue(expr);
end_labels->Bind(builder());
return true;
} else if (!expr->IsNullOrUndefinedLiteral()) {
VisitForAccumulatorValue(expr);
BytecodeLabel is_null_or_undefined;
builder()
->JumpIfUndefinedOrNull(&is_null_or_undefined)
.Jump(end_labels->New());
builder()->Bind(&is_null_or_undefined);
}
BuildIncrementBlockCoverageCounterIfEnabled(coverage_slot);
return false;
}
void BytecodeGenerator::VisitLogicalOrExpression(BinaryOperation* binop) { void BytecodeGenerator::VisitLogicalOrExpression(BinaryOperation* binop) {
Expression* left = binop->left(); Expression* left = binop->left();
Expression* right = binop->right(); Expression* right = binop->right();
...@@ -5721,6 +5750,68 @@ void BytecodeGenerator::VisitNaryLogicalAndExpression(NaryOperation* expr) { ...@@ -5721,6 +5750,68 @@ void BytecodeGenerator::VisitNaryLogicalAndExpression(NaryOperation* expr) {
} }
} }
void BytecodeGenerator::VisitNullishExpression(BinaryOperation* binop) {
Expression* left = binop->left();
Expression* right = binop->right();
int right_coverage_slot =
AllocateBlockCoverageSlotIfEnabled(binop, SourceRangeKind::kRight);
if (execution_result()->IsTest()) {
TestResultScope* test_result = execution_result()->AsTest();
if (left->IsLiteralButNotNullOrUndefined() && left->ToBooleanIsTrue()) {
builder()->Jump(test_result->NewThenLabel());
} else if (left->IsNullOrUndefinedLiteral() &&
right->IsNullOrUndefinedLiteral()) {
BuildIncrementBlockCoverageCounterIfEnabled(right_coverage_slot);
builder()->Jump(test_result->NewElseLabel());
} else {
VisitLogicalTest(Token::NULLISH, left, right, right_coverage_slot);
}
test_result->SetResultConsumedByTest();
} else {
BytecodeLabels end_labels(zone());
if (VisitNullishSubExpression(left, &end_labels, right_coverage_slot)) {
return;
}
VisitForAccumulatorValue(right);
end_labels.Bind(builder());
}
}
void BytecodeGenerator::VisitNaryNullishExpression(NaryOperation* expr) {
Expression* first = expr->first();
DCHECK_GT(expr->subsequent_length(), 0);
NaryCodeCoverageSlots coverage_slots(this, expr);
if (execution_result()->IsTest()) {
TestResultScope* test_result = execution_result()->AsTest();
if (first->IsLiteralButNotNullOrUndefined() && first->ToBooleanIsTrue()) {
builder()->Jump(test_result->NewThenLabel());
} else {
VisitNaryLogicalTest(Token::NULLISH, expr, &coverage_slots);
}
test_result->SetResultConsumedByTest();
} else {
BytecodeLabels end_labels(zone());
if (VisitNullishSubExpression(first, &end_labels,
coverage_slots.GetSlotFor(0))) {
return;
}
for (size_t i = 0; i < expr->subsequent_length() - 1; ++i) {
if (VisitNullishSubExpression(expr->subsequent(i), &end_labels,
coverage_slots.GetSlotFor(i + 1))) {
return;
}
}
// We have to visit the last value even if it's nullish, because we need its
// actual value.
VisitForAccumulatorValue(expr->subsequent(expr->subsequent_length() - 1));
end_labels.Bind(builder());
}
}
void BytecodeGenerator::BuildNewLocalActivationContext() { void BytecodeGenerator::BuildNewLocalActivationContext() {
ValueResultScope value_execution_result(this); ValueResultScope value_execution_result(this);
Scope* scope = closure_scope(); Scope* scope = closure_scope();
...@@ -6066,6 +6157,25 @@ void BytecodeGenerator::VisitForTest(Expression* expr, ...@@ -6066,6 +6157,25 @@ void BytecodeGenerator::VisitForTest(Expression* expr,
} }
} }
// Visits the expression |expr| for testing its nullish value and jumping to the
// |then| or |other| label depending on value and short-circuit semantics
void BytecodeGenerator::VisitForNullishTest(Expression* expr,
BytecodeLabels* then_labels,
BytecodeLabels* test_next_labels,
BytecodeLabels* else_labels) {
// Nullish short circuits on undefined or null, otherwise we fall back to
// BuildTest with no fallthrough.
// TODO(joshualitt): We should do this in a TestResultScope.
TypeHint type_hint = VisitForAccumulatorValue(expr);
ToBooleanMode mode = ToBooleanModeFromTypeHint(type_hint);
// Skip the nullish shortcircuit if we already have a boolean.
if (mode != ToBooleanMode::kAlreadyBoolean) {
builder()->JumpIfUndefinedOrNull(test_next_labels->New());
}
BuildTest(mode, then_labels, else_labels, TestFallthrough::kNone);
}
void BytecodeGenerator::VisitInSameTestExecutionScope(Expression* expr) { void BytecodeGenerator::VisitInSameTestExecutionScope(Expression* expr) {
DCHECK(execution_result()->IsTest()); DCHECK(execution_result()->IsTest());
{ {
......
...@@ -162,12 +162,14 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> { ...@@ -162,12 +162,14 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
void VisitCommaExpression(BinaryOperation* binop); void VisitCommaExpression(BinaryOperation* binop);
void VisitLogicalOrExpression(BinaryOperation* binop); void VisitLogicalOrExpression(BinaryOperation* binop);
void VisitLogicalAndExpression(BinaryOperation* binop); void VisitLogicalAndExpression(BinaryOperation* binop);
void VisitNullishExpression(BinaryOperation* binop);
// Dispatched from VisitNaryOperation. // Dispatched from VisitNaryOperation.
void VisitNaryArithmeticExpression(NaryOperation* expr); void VisitNaryArithmeticExpression(NaryOperation* expr);
void VisitNaryCommaExpression(NaryOperation* expr); void VisitNaryCommaExpression(NaryOperation* expr);
void VisitNaryLogicalOrExpression(NaryOperation* expr); void VisitNaryLogicalOrExpression(NaryOperation* expr);
void VisitNaryLogicalAndExpression(NaryOperation* expr); void VisitNaryLogicalAndExpression(NaryOperation* expr);
void VisitNaryNullishExpression(NaryOperation* expr);
// Dispatched from VisitUnaryOperation. // Dispatched from VisitUnaryOperation.
void VisitVoid(UnaryOperation* expr); void VisitVoid(UnaryOperation* expr);
...@@ -321,6 +323,7 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> { ...@@ -321,6 +323,7 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
int right_coverage_slot); int right_coverage_slot);
void VisitNaryLogicalTest(Token::Value token, NaryOperation* expr, void VisitNaryLogicalTest(Token::Value token, NaryOperation* expr,
const NaryCodeCoverageSlots* coverage_slots); const NaryCodeCoverageSlots* coverage_slots);
// Visit a (non-RHS) test for a logical op, which falls through if the test // Visit a (non-RHS) test for a logical op, which falls through if the test
// fails or jumps to the appropriate labels if it succeeds. // fails or jumps to the appropriate labels if it succeeds.
void VisitLogicalTestSubExpression(Token::Value token, Expression* expr, void VisitLogicalTestSubExpression(Token::Value token, Expression* expr,
...@@ -335,6 +338,10 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> { ...@@ -335,6 +338,10 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
BytecodeLabels* end_labels, BytecodeLabels* end_labels,
int coverage_slot); int coverage_slot);
// Helper for binary and nary nullish op value expressions.
bool VisitNullishSubExpression(Expression* expr, BytecodeLabels* end_labels,
int coverage_slot);
// Visit the body of a loop iteration. // Visit the body of a loop iteration.
void VisitIterationBody(IterationStatement* stmt, LoopBuilder* loop_builder); void VisitIterationBody(IterationStatement* stmt, LoopBuilder* loop_builder);
...@@ -376,6 +383,9 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> { ...@@ -376,6 +383,9 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
void VisitForEffect(Expression* expr); void VisitForEffect(Expression* expr);
void VisitForTest(Expression* expr, BytecodeLabels* then_labels, void VisitForTest(Expression* expr, BytecodeLabels* then_labels,
BytecodeLabels* else_labels, TestFallthrough fallthrough); BytecodeLabels* else_labels, TestFallthrough fallthrough);
void VisitForNullishTest(Expression* expr, BytecodeLabels* then_labels,
BytecodeLabels* test_next_labels,
BytecodeLabels* else_labels);
void VisitInSameTestExecutionScope(Expression* expr); void VisitInSameTestExecutionScope(Expression* expr);
......
...@@ -63,6 +63,7 @@ ParseInfo::ParseInfo(Isolate* isolate, AccountingAllocator* zone_allocator) ...@@ -63,6 +63,7 @@ ParseInfo::ParseInfo(Isolate* isolate, AccountingAllocator* zone_allocator)
set_allow_harmony_dynamic_import(FLAG_harmony_dynamic_import); set_allow_harmony_dynamic_import(FLAG_harmony_dynamic_import);
set_allow_harmony_import_meta(FLAG_harmony_import_meta); set_allow_harmony_import_meta(FLAG_harmony_import_meta);
set_allow_harmony_optional_chaining(FLAG_harmony_optional_chaining); set_allow_harmony_optional_chaining(FLAG_harmony_optional_chaining);
set_allow_harmony_nullish(FLAG_harmony_nullish);
set_allow_harmony_private_methods(FLAG_harmony_private_methods); set_allow_harmony_private_methods(FLAG_harmony_private_methods);
} }
......
...@@ -112,6 +112,9 @@ class V8_EXPORT_PRIVATE ParseInfo { ...@@ -112,6 +112,9 @@ class V8_EXPORT_PRIVATE ParseInfo {
FLAG_ACCESSOR(kIsOneshotIIFE, is_oneshot_iife, set_is_oneshot_iife) FLAG_ACCESSOR(kIsOneshotIIFE, is_oneshot_iife, set_is_oneshot_iife)
FLAG_ACCESSOR(kCollectSourcePositions, collect_source_positions, FLAG_ACCESSOR(kCollectSourcePositions, collect_source_positions,
set_collect_source_positions) set_collect_source_positions)
FLAG_ACCESSOR(kAllowHarmonyNullish, allow_harmony_nullish,
set_allow_harmony_nullish)
#undef FLAG_ACCESSOR #undef FLAG_ACCESSOR
void set_parse_restriction(ParseRestriction restriction) { void set_parse_restriction(ParseRestriction restriction) {
...@@ -277,7 +280,7 @@ class V8_EXPORT_PRIVATE ParseInfo { ...@@ -277,7 +280,7 @@ class V8_EXPORT_PRIVATE ParseInfo {
void SetFunctionInfo(T function); void SetFunctionInfo(T function);
// Various configuration flags for parsing. // Various configuration flags for parsing.
enum Flag { enum Flag : uint64_t {
// ---------- Input flags --------------------------- // ---------- Input flags ---------------------------
kToplevel = 1 << 0, kToplevel = 1 << 0,
kEager = 1 << 1, kEager = 1 << 1,
...@@ -310,12 +313,13 @@ class V8_EXPORT_PRIVATE ParseInfo { ...@@ -310,12 +313,13 @@ class V8_EXPORT_PRIVATE ParseInfo {
kAllowHarmonyPrivateFields = 1 << 28, kAllowHarmonyPrivateFields = 1 << 28,
kAllowHarmonyPrivateMethods = 1 << 29, kAllowHarmonyPrivateMethods = 1 << 29,
kIsOneshotIIFE = 1 << 30, kIsOneshotIIFE = 1 << 30,
kCollectSourcePositions = 1 << 31, kCollectSourcePositions = static_cast<uint64_t>(1) << 31,
kAllowHarmonyNullish = static_cast<uint64_t>(1) << 32,
}; };
//------------- Inputs to parsing and scope analysis ----------------------- //------------- Inputs to parsing and scope analysis -----------------------
std::unique_ptr<Zone> zone_; std::unique_ptr<Zone> zone_;
unsigned flags_; uint64_t flags_;
v8::Extension* extension_; v8::Extension* extension_;
DeclarationScope* script_scope_; DeclarationScope* script_scope_;
uintptr_t stack_limit_; uintptr_t stack_limit_;
......
...@@ -294,6 +294,14 @@ class ParserBase { ...@@ -294,6 +294,14 @@ class ParserBase {
scanner()->set_allow_harmony_optional_chaining(allow); scanner()->set_allow_harmony_optional_chaining(allow);
} }
bool allow_harmony_nullish() const {
return scanner()->allow_harmony_nullish();
}
void set_allow_harmony_nullish(bool allow) {
scanner()->set_allow_harmony_nullish(allow);
}
uintptr_t stack_limit() const { return stack_limit_; } uintptr_t stack_limit() const { return stack_limit_; }
void set_stack_limit(uintptr_t stack_limit) { stack_limit_ = stack_limit; } void set_stack_limit(uintptr_t stack_limit) { stack_limit_ = stack_limit; }
...@@ -1059,6 +1067,8 @@ class ParserBase { ...@@ -1059,6 +1067,8 @@ class ParserBase {
ExpressionT ParseYieldExpression(); ExpressionT ParseYieldExpression();
V8_INLINE ExpressionT ParseConditionalExpression(); V8_INLINE ExpressionT ParseConditionalExpression();
ExpressionT ParseConditionalContinuation(ExpressionT expression, int pos); ExpressionT ParseConditionalContinuation(ExpressionT expression, int pos);
ExpressionT ParseLogicalExpression();
ExpressionT ParseCoalesceExpression(ExpressionT expression);
ExpressionT ParseBinaryContinuation(ExpressionT x, int prec, int prec1); ExpressionT ParseBinaryContinuation(ExpressionT x, int prec, int prec1);
V8_INLINE ExpressionT ParseBinaryExpression(int prec); V8_INLINE ExpressionT ParseBinaryExpression(int prec);
ExpressionT ParseUnaryOrPrefixExpression(); ExpressionT ParseUnaryOrPrefixExpression();
...@@ -2800,17 +2810,70 @@ template <typename Impl> ...@@ -2800,17 +2810,70 @@ template <typename Impl>
typename ParserBase<Impl>::ExpressionT typename ParserBase<Impl>::ExpressionT
ParserBase<Impl>::ParseConditionalExpression() { ParserBase<Impl>::ParseConditionalExpression() {
// ConditionalExpression :: // ConditionalExpression ::
// LogicalOrExpression // LogicalExpression
// LogicalOrExpression '?' AssignmentExpression ':' AssignmentExpression // LogicalExpression '?' AssignmentExpression ':' AssignmentExpression
//
int pos = peek_position(); int pos = peek_position();
// We start using the binary expression parser for prec >= 4 only! ExpressionT expression = ParseLogicalExpression();
ExpressionT expression = ParseBinaryExpression(4);
return peek() == Token::CONDITIONAL return peek() == Token::CONDITIONAL
? ParseConditionalContinuation(expression, pos) ? ParseConditionalContinuation(expression, pos)
: expression; : expression;
} }
template <typename Impl>
typename ParserBase<Impl>::ExpressionT
ParserBase<Impl>::ParseLogicalExpression() {
// LogicalExpression ::
// LogicalORExpression
// CoalesceExpression
// Both LogicalORExpression and CoalesceExpression start with BitwiseOR.
// Parse for binary expressions >= 6 (BitwiseOR);
ExpressionT expression = ParseBinaryExpression(6);
if (peek() == Token::AND || peek() == Token::OR) {
// LogicalORExpression, pickup parsing where we left off.
int prec1 = Token::Precedence(peek(), accept_IN_);
expression = ParseBinaryContinuation(expression, 4, prec1);
} else if (V8_UNLIKELY(peek() == Token::NULLISH)) {
expression = ParseCoalesceExpression(expression);
}
return expression;
}
template <typename Impl>
typename ParserBase<Impl>::ExpressionT
ParserBase<Impl>::ParseCoalesceExpression(ExpressionT expression) {
// CoalesceExpression ::
// CoalesceExpressionHead ?? BitwiseORExpression
//
// CoalesceExpressionHead ::
// CoalesceExpression
// BitwiseORExpression
// We create a binary operation for the first nullish, otherwise collapse
// into an nary expresion.
bool first_nullish = true;
while (peek() == Token::NULLISH) {
SourceRange right_range;
SourceRangeScope right_range_scope(scanner(), &right_range);
Consume(Token::NULLISH);
int pos = peek_position();
// Parse BitwiseOR or higher.
ExpressionT y = ParseBinaryExpression(6);
if (first_nullish) {
expression =
factory()->NewBinaryOperation(Token::NULLISH, expression, y, pos);
impl()->RecordBinaryOperationSourceRange(expression, right_range);
first_nullish = false;
} else {
impl()->CollapseNaryExpression(&expression, y, Token::NULLISH, pos,
right_range);
}
}
return expression;
}
template <typename Impl> template <typename Impl>
typename ParserBase<Impl>::ExpressionT typename ParserBase<Impl>::ExpressionT
ParserBase<Impl>::ParseConditionalContinuation(ExpressionT expression, ParserBase<Impl>::ParseConditionalContinuation(ExpressionT expression,
......
...@@ -424,6 +424,7 @@ Parser::Parser(ParseInfo* info) ...@@ -424,6 +424,7 @@ Parser::Parser(ParseInfo* info)
set_allow_natives(info->allow_natives_syntax()); set_allow_natives(info->allow_natives_syntax());
set_allow_harmony_dynamic_import(info->allow_harmony_dynamic_import()); set_allow_harmony_dynamic_import(info->allow_harmony_dynamic_import());
set_allow_harmony_import_meta(info->allow_harmony_import_meta()); set_allow_harmony_import_meta(info->allow_harmony_import_meta());
set_allow_harmony_nullish(info->allow_harmony_nullish());
set_allow_harmony_optional_chaining(info->allow_harmony_optional_chaining()); set_allow_harmony_optional_chaining(info->allow_harmony_optional_chaining());
set_allow_harmony_private_methods(info->allow_harmony_private_methods()); set_allow_harmony_private_methods(info->allow_harmony_private_methods());
for (int feature = 0; feature < v8::Isolate::kUseCounterFeatureCount; for (int feature = 0; feature < v8::Isolate::kUseCounterFeatureCount;
......
...@@ -363,12 +363,14 @@ V8_INLINE Token::Value Scanner::ScanSingleToken() { ...@@ -363,12 +363,14 @@ V8_INLINE Token::Value Scanner::ScanSingleToken() {
return Select(token); return Select(token);
case Token::CONDITIONAL: case Token::CONDITIONAL:
// ? ?. // ? ?. ??
Advance(); Advance();
if (allow_harmony_optional_chaining() && c0_ == '.') { if (allow_harmony_optional_chaining() && c0_ == '.') {
Advance(); Advance();
if (!IsDecimalDigit(c0_)) return Token::QUESTION_PERIOD; if (!IsDecimalDigit(c0_)) return Token::QUESTION_PERIOD;
PushBack('.'); PushBack('.');
} else if (allow_harmony_nullish() && c0_ == '?') {
return Select(Token::NULLISH);
} }
return Token::CONDITIONAL; return Token::CONDITIONAL;
......
...@@ -93,6 +93,7 @@ Scanner::Scanner(Utf16CharacterStream* source, bool is_module) ...@@ -93,6 +93,7 @@ Scanner::Scanner(Utf16CharacterStream* source, bool is_module)
: source_(source), : source_(source),
found_html_comment_(false), found_html_comment_(false),
allow_harmony_optional_chaining_(false), allow_harmony_optional_chaining_(false),
allow_harmony_nullish_(false),
is_module_(is_module), is_module_(is_module),
octal_pos_(Location::invalid()), octal_pos_(Location::invalid()),
octal_message_(MessageTemplate::kNone) { octal_message_(MessageTemplate::kNone) {
......
...@@ -414,6 +414,10 @@ class V8_EXPORT_PRIVATE Scanner { ...@@ -414,6 +414,10 @@ class V8_EXPORT_PRIVATE Scanner {
allow_harmony_optional_chaining_ = allow; allow_harmony_optional_chaining_ = allow;
} }
bool allow_harmony_nullish() const { return allow_harmony_nullish_; }
void set_allow_harmony_nullish(bool allow) { allow_harmony_nullish_ = allow; }
const Utf16CharacterStream* stream() const { return source_; } const Utf16CharacterStream* stream() const { return source_; }
// If the next characters in the stream are "#!", the line is skipped. // If the next characters in the stream are "#!", the line is skipped.
...@@ -723,6 +727,7 @@ class V8_EXPORT_PRIVATE Scanner { ...@@ -723,6 +727,7 @@ class V8_EXPORT_PRIVATE Scanner {
// Harmony flags to allow ESNext features. // Harmony flags to allow ESNext features.
bool allow_harmony_optional_chaining_; bool allow_harmony_optional_chaining_;
bool allow_harmony_nullish_;
const bool is_module_; const bool is_module_;
......
...@@ -96,6 +96,7 @@ namespace internal { ...@@ -96,6 +96,7 @@ namespace internal {
/* IsBinaryOp() relies on this block of enum values */ \ /* IsBinaryOp() relies on this block of enum values */ \
/* being contiguous and sorted in the same order! */ \ /* being contiguous and sorted in the same order! */ \
T(COMMA, ",", 1) \ T(COMMA, ",", 1) \
T(NULLISH, "??", 3) \
T(OR, "||", 4) \ T(OR, "||", 4) \
T(AND, "&&", 5) \ T(AND, "&&", 5) \
\ \
......
...@@ -261,6 +261,7 @@ TEST(ArrowOrAssignmentOp) { ...@@ -261,6 +261,7 @@ TEST(ArrowOrAssignmentOp) {
bool TokenIsBinaryOp(Token::Value token) { bool TokenIsBinaryOp(Token::Value token) {
switch (token) { switch (token) {
case Token::COMMA: case Token::COMMA:
case Token::NULLISH:
case Token::OR: case Token::OR:
case Token::AND: case Token::AND:
#define T(name, string, precedence) case Token::name: #define T(name, string, precedence) case Token::name:
...@@ -1530,6 +1531,7 @@ enum ParserFlag { ...@@ -1530,6 +1531,7 @@ enum ParserFlag {
kAllowHarmonyPrivateMethods, kAllowHarmonyPrivateMethods,
kAllowHarmonyDynamicImport, kAllowHarmonyDynamicImport,
kAllowHarmonyImportMeta, kAllowHarmonyImportMeta,
kAllowHarmonyNullish,
kAllowHarmonyOptionalChaining, kAllowHarmonyOptionalChaining,
}; };
...@@ -1546,6 +1548,7 @@ void SetGlobalFlags(base::EnumSet<ParserFlag> flags) { ...@@ -1546,6 +1548,7 @@ void SetGlobalFlags(base::EnumSet<ParserFlag> flags) {
i::FLAG_harmony_import_meta = flags.contains(kAllowHarmonyImportMeta); i::FLAG_harmony_import_meta = flags.contains(kAllowHarmonyImportMeta);
i::FLAG_harmony_optional_chaining = i::FLAG_harmony_optional_chaining =
flags.contains(kAllowHarmonyOptionalChaining); flags.contains(kAllowHarmonyOptionalChaining);
i::FLAG_harmony_nullish = flags.contains(kAllowHarmonyNullish);
} }
void SetParserFlags(i::PreParser* parser, base::EnumSet<ParserFlag> flags) { void SetParserFlags(i::PreParser* parser, base::EnumSet<ParserFlag> flags) {
...@@ -1558,6 +1561,7 @@ void SetParserFlags(i::PreParser* parser, base::EnumSet<ParserFlag> flags) { ...@@ -1558,6 +1561,7 @@ void SetParserFlags(i::PreParser* parser, base::EnumSet<ParserFlag> flags) {
flags.contains(kAllowHarmonyImportMeta)); flags.contains(kAllowHarmonyImportMeta));
parser->set_allow_harmony_optional_chaining( parser->set_allow_harmony_optional_chaining(
flags.contains(kAllowHarmonyOptionalChaining)); flags.contains(kAllowHarmonyOptionalChaining));
parser->set_allow_harmony_nullish(flags.contains(kAllowHarmonyNullish));
} }
void TestParserSyncWithFlags(i::Handle<i::String> source, void TestParserSyncWithFlags(i::Handle<i::String> source,
...@@ -2009,6 +2013,41 @@ TEST(OptionalChainingTaggedError) { ...@@ -2009,6 +2013,41 @@ TEST(OptionalChainingTaggedError) {
RunParserSyncTest(context_data, statement_data, kError); RunParserSyncTest(context_data, statement_data, kError);
} }
TEST(Nullish) {
v8::HandleScope handles(CcTest::isolate());
v8::Local<v8::Context> context = v8::Context::New(CcTest::isolate());
v8::Context::Scope context_scope(context);
const char* context_data[][2] = {
{"", ""}, {"'use strict';", ""}, {nullptr, nullptr}};
const char* statement_data[] = {"a ?? b", "a ?? b ?? c",
"a ?? b ? c : d"
"a ?? b ?? c ? d : e",
nullptr};
static const ParserFlag flags[] = {kAllowHarmonyNullish};
RunParserSyncTest(context_data, statement_data, kSuccess, nullptr, 0, flags,
1, nullptr, 0, false, true, true);
RunParserSyncTest(context_data, statement_data, kError);
}
TEST(NullishNotContained) {
v8::HandleScope handles(CcTest::isolate());
v8::Local<v8::Context> context = v8::Context::New(CcTest::isolate());
v8::Context::Scope context_scope(context);
const char* context_data[][2] = {
{"", ""}, {"'use strict';", ""}, {nullptr, nullptr}};
const char* statement_data[] = {"a || b ?? c", "a ?? b || c",
"a && b ?? c"
"a ?? b && c",
nullptr};
static const ParserFlag flags[] = {kAllowHarmonyNullish};
RunParserSyncTest(context_data, statement_data, kError, nullptr, 0, flags, 1,
nullptr, 0, false, true, true);
}
TEST(ErrorsEvalAndArguments) { TEST(ErrorsEvalAndArguments) {
// Tests that both preparsing and parsing produce the right kind of errors for // Tests that both preparsing and parsing produce the right kind of errors for
// using "eval" and "arguments" as identifiers. Without the strict mode, it's // using "eval" and "arguments" as identifiers. Without the strict mode, it's
......
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Flags: --harmony-nullish
// Basic sanity checks.
assertTrue(true ?? false);
assertFalse(false ?? true);
assertTrue(undefined ?? true);
assertTrue(null ?? true);
assertEquals([], [] ?? true);
// Chaining nullish.
assertTrue(null ?? null ?? true);
assertTrue(null ?? null ?? true ?? null);
assertTrue(undefined ?? undefined ?? true);
assertTrue(undefined ?? undefined ?? true ?? undefined);
assertFalse(null ?? false ?? null);
assertFalse(undefined ?? false ?? undefined);
// Nullish and conditionals.
assertTrue(null ?? true ? true : false);
assertTrue(null ?? null ?? true ? true : false);
assertTrue(undefined ?? true ? true : false);
assertTrue(undefined ?? undefined ?? true ? true : false);
// Nullish mixed expressions.
assertTrue(null ?? 1 == 1);
assertTrue(undefined ?? 1 == 1);
assertTrue(null ?? null ?? 1 == 1);
assertTrue(undefined ??undefined ?? 1 == 1);
assertEquals(1, null ?? 1 | 0);
assertEquals(1, undefined ?? 1 | 0);
assertEquals(1, null ?? null ?? 1 | 0);
assertEquals(1, undefined ?? undefined ?? 1 | 0);
// Short circuit.
{
let ran = false;
let never_ran = () => { ran = true; }
let value = true ?? never_ran();
assertTrue(value);
assertFalse(ran);
}
{
let ran = false;
let never_ran = () => { ran = true; }
let value = undefined ?? true ?? never_ran();
assertTrue(value);
assertFalse(ran);
}
{
let ran = false;
let never_ran = () => { ran = true; }
let value = null ?? true ?? never_ran();
assertTrue(value);
assertFalse(ran);
}
// Nullish in tests evaluates only once.
{
let run_count = 0;
let run = () => { run_count++; return null; }
if (run() ?? true) {} else { assertUnreachable(); }
assertEquals(1, run_count);
}
// Nullish may not contain or be contained within || or &&.
assertThrows("true || true ?? true", SyntaxError);
assertThrows("true ?? true || true", SyntaxError);
assertThrows("true && true ?? true", SyntaxError);
assertThrows("true ?? true && true", SyntaxError);
// Test boolean expressions and nullish.
assertTrue((false || true) ?? false);
assertTrue(null ?? (false || true));
assertTrue((false || null) ?? true);
assertTrue((false || null) ?? (true && null) ?? true);
assertTrue((false || undefined) ?? true);
assertTrue((false || undefined) ?? (true && undefined) ?? true);
assertTrue(null ?? (false || true));
assertTrue(undefined ?? (false || true));
assertTrue(null ?? (false || null) ?? true);
assertTrue(undefined ?? (false || undefined) ?? true);
assertTrue(null ?? null ?? (false || true));
assertTrue(undefined ?? undefined ?? (false || true));
assertTrue((undefined ?? false) || true);
assertTrue((null ?? false) || true);
assertTrue((undefined ?? undefined ?? false) || false || true);
assertTrue((null ?? null ?? false) || false || true);
assertTrue(false || (undefined ?? true));
assertTrue(false || (null ?? true));
assertTrue(false || false || (undefined ?? undefined ?? true));
assertTrue(false || false || (null ?? null ?? true));
// Test use when test true.
if (undefined ?? true) {} else { assertUnreachable(); }
if (null ?? true) {} else { assertUnreachable(); }
if (undefined ?? undefined ?? true) {} else { assertUnreachable(); }
if (null ?? null ?? true) {} else { assertUnreachable(); }
// test use when test false
if (undefined ?? false) { assertUnreachable(); } else {}
if (null ?? false) { assertUnreachable(); } else {}
if (undefined ?? undefined ?? false) { assertUnreachable(); } else {}
if (null ?? null ?? false) { assertUnreachable(); } else {}
if (undefined ?? false ?? true) { assertUnreachable(); } else {}
if (null ?? false ?? true) { assertUnreachable(); } else {}
// Test use with nested boolean.
if ((false || undefined) ?? true) {} else { assertUnreachable(); }
if ((false || null) ?? true) {} else { assertUnreachable(); }
if ((false || undefined) ?? undefined ?? true) {} else { assertUnreachable(); }
if ((false || null) ?? null ?? true) {} else { assertUnreachable(); }
if (undefined ?? (false || true)) {} else { assertUnreachable(); }
if (null ?? (false || true)) {} else { assertUnreachable(); }
if (undefined ?? undefined ?? (false || true)) {} else { assertUnreachable(); }
if (null ?? null ?? (false || true)) {} else { assertUnreachable(); }
if (undefined ?? (false || undefined) ?? true) {} else { assertUnreachable(); }
if (null ?? (false || null) ?? true) {} else { assertUnreachable(); }
// Nested nullish.
if ((null ?? true) || false) {} else { assertUnreachable(); }
if ((null ?? null ?? true) || false) {} else { assertUnreachable(); }
if (false || (null ?? true)) {} else { assertUnreachable(); }
if (false || (null ?? null ?? true)) {} else { assertUnreachable(); }
if ((null ?? false) || false) { assertUnreachable(); } else {}
if ((null ?? null ?? false) || false) { assertUnreachable(); } else {}
if (false || (null ?? false)) { assertUnreachable(); } else {}
if (false || (null ?? null ?? false)) { assertUnreachable(); } else {}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment