Commit ceb7bd59 authored by Gus Caplan's avatar Gus Caplan Committed by Commit Bot

Initial implementation of optional chaining

Each LHS expression that contains an optional chain of some form is
wrapped in an OptionalChain node. This root node allows us to use a
single jump location for every individual item in the chain,
improving the performance and simplifying the implementation.

Bug: v8:9553
Change-Id: I678563928b2dbfd6200bff55801919d4fd816962
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1723359
Commit-Queue: Adam Klein <adamk@chromium.org>
Reviewed-by: 's avatarAdam Klein <adamk@chromium.org>
Reviewed-by: 's avatarLeszek Swirski <leszeks@chromium.org>
Cr-Commit-Position: refs/heads/master@{#63120}
parent d109cdb1
...@@ -382,6 +382,12 @@ void AstTraversalVisitor<Subclass>::VisitThrow(Throw* expr) { ...@@ -382,6 +382,12 @@ void AstTraversalVisitor<Subclass>::VisitThrow(Throw* expr) {
RECURSE_EXPRESSION(Visit(expr->exception())); RECURSE_EXPRESSION(Visit(expr->exception()));
} }
template <class Subclass>
void AstTraversalVisitor<Subclass>::VisitOptionalChain(OptionalChain* expr) {
PROCESS_EXPRESSION(expr);
RECURSE_EXPRESSION(Visit(expr->expression()));
}
template <class Subclass> template <class Subclass>
void AstTraversalVisitor<Subclass>::VisitProperty(Property* expr) { void AstTraversalVisitor<Subclass>::VisitProperty(Property* expr) {
PROCESS_EXPRESSION(expr); PROCESS_EXPRESSION(expr);
......
...@@ -94,6 +94,7 @@ namespace internal { ...@@ -94,6 +94,7 @@ namespace internal {
V(ImportCallExpression) \ V(ImportCallExpression) \
V(Literal) \ V(Literal) \
V(NativeFunctionLiteral) \ V(NativeFunctionLiteral) \
V(OptionalChain) \
V(Property) \ V(Property) \
V(ResolvedProperty) \ V(ResolvedProperty) \
V(Spread) \ V(Spread) \
...@@ -1595,6 +1596,20 @@ class VariableProxy final : public Expression { ...@@ -1595,6 +1596,20 @@ class VariableProxy final : public Expression {
friend base::ThreadedListTraits<VariableProxy>; friend base::ThreadedListTraits<VariableProxy>;
}; };
// Wraps an optional chain to provide a wrapper for jump labels.
class OptionalChain final : public Expression {
public:
Expression* expression() const { return expression_; }
private:
friend class AstNodeFactory;
explicit OptionalChain(Expression* expression)
: Expression(0, kOptionalChain), expression_(expression) {}
Expression* expression_;
};
// Assignments to a property will use one of several types of property access. // Assignments to a property will use one of several types of property access.
// Otherwise, the assignment is to a non-property (a global, a local slot, a // Otherwise, the assignment is to a non-property (a global, a local slot, a
// parameter slot, or a destructuring pattern). // parameter slot, or a destructuring pattern).
...@@ -1612,6 +1627,10 @@ enum AssignType { ...@@ -1612,6 +1627,10 @@ enum AssignType {
class Property final : public Expression { class Property final : public Expression {
public: public:
bool is_optional_chain_link() const {
return IsOptionalChainLinkField::decode(bit_field_);
}
bool IsValidReferenceExpression() const { return true; } bool IsValidReferenceExpression() const { return true; }
Expression* obj() const { return obj_; } Expression* obj() const { return obj_; }
...@@ -1653,10 +1672,13 @@ class Property final : public Expression { ...@@ -1653,10 +1672,13 @@ class Property final : public Expression {
private: private:
friend class AstNodeFactory; friend class AstNodeFactory;
Property(Expression* obj, Expression* key, int pos) Property(Expression* obj, Expression* key, int pos, bool optional_chain)
: Expression(pos, kProperty), obj_(obj), key_(key) { : Expression(pos, kProperty), obj_(obj), key_(key) {
bit_field_ |= IsOptionalChainLinkField::encode(optional_chain);
} }
using IsOptionalChainLinkField = Expression::NextBitField<bool, 1>;
Expression* obj_; Expression* obj_;
Expression* key_; Expression* key_;
}; };
...@@ -1694,6 +1716,10 @@ class Call final : public Expression { ...@@ -1694,6 +1716,10 @@ class Call final : public Expression {
return IsTaggedTemplateField::decode(bit_field_); return IsTaggedTemplateField::decode(bit_field_);
} }
bool is_optional_chain_link() const {
return IsOptionalChainLinkField::decode(bit_field_);
}
bool only_last_arg_is_spread() { bool only_last_arg_is_spread() {
return !arguments_.is_empty() && arguments_.last()->IsSpread(); return !arguments_.is_empty() && arguments_.last()->IsSpread();
} }
...@@ -1726,13 +1752,14 @@ class Call final : public Expression { ...@@ -1726,13 +1752,14 @@ class Call final : public Expression {
Call(Zone* zone, Expression* expression, Call(Zone* zone, Expression* expression,
const ScopedPtrList<Expression>& arguments, int pos, const ScopedPtrList<Expression>& arguments, int pos,
PossiblyEval possibly_eval) PossiblyEval possibly_eval, bool optional_chain)
: Expression(pos, kCall), : Expression(pos, kCall),
expression_(expression), expression_(expression),
arguments_(0, nullptr) { arguments_(0, nullptr) {
bit_field_ |= bit_field_ |=
IsPossiblyEvalField::encode(possibly_eval == IS_POSSIBLY_EVAL) | IsPossiblyEvalField::encode(possibly_eval == IS_POSSIBLY_EVAL) |
IsTaggedTemplateField::encode(false); IsTaggedTemplateField::encode(false) |
IsOptionalChainLinkField::encode(optional_chain);
arguments.CopyTo(&arguments_, zone); arguments.CopyTo(&arguments_, zone);
} }
...@@ -1743,12 +1770,14 @@ class Call final : public Expression { ...@@ -1743,12 +1770,14 @@ class Call final : public Expression {
expression_(expression), expression_(expression),
arguments_(0, nullptr) { arguments_(0, nullptr) {
bit_field_ |= IsPossiblyEvalField::encode(false) | bit_field_ |= IsPossiblyEvalField::encode(false) |
IsTaggedTemplateField::encode(true); IsTaggedTemplateField::encode(true) |
IsOptionalChainLinkField::encode(false);
arguments.CopyTo(&arguments_, zone); arguments.CopyTo(&arguments_, zone);
} }
using IsPossiblyEvalField = Expression::NextBitField<bool, 1>; using IsPossiblyEvalField = Expression::NextBitField<bool, 1>;
using IsTaggedTemplateField = IsPossiblyEvalField::Next<bool, 1>; using IsTaggedTemplateField = IsPossiblyEvalField::Next<bool, 1>;
using IsOptionalChainLinkField = IsTaggedTemplateField::Next<bool, 1>;
Expression* expression_; Expression* expression_;
ZonePtrList<Expression> arguments_; ZonePtrList<Expression> arguments_;
...@@ -3040,8 +3069,13 @@ class AstNodeFactory final { ...@@ -3040,8 +3069,13 @@ class AstNodeFactory final {
return new (zone_) Variable(variable); return new (zone_) Variable(variable);
} }
Property* NewProperty(Expression* obj, Expression* key, int pos) { OptionalChain* NewOptionalChain(Expression* expression) {
return new (zone_) Property(obj, key, pos); return new (zone_) OptionalChain(expression);
}
Property* NewProperty(Expression* obj, Expression* key, int pos,
bool optional_chain = false) {
return new (zone_) Property(obj, key, pos, optional_chain);
} }
ResolvedProperty* NewResolvedProperty(VariableProxy* obj, ResolvedProperty* NewResolvedProperty(VariableProxy* obj,
...@@ -3052,8 +3086,10 @@ class AstNodeFactory final { ...@@ -3052,8 +3086,10 @@ class AstNodeFactory final {
Call* NewCall(Expression* expression, Call* NewCall(Expression* expression,
const ScopedPtrList<Expression>& arguments, int pos, const ScopedPtrList<Expression>& arguments, int pos,
Call::PossiblyEval possibly_eval = Call::NOT_EVAL) { Call::PossiblyEval possibly_eval = Call::NOT_EVAL,
return new (zone_) Call(zone_, expression, arguments, pos, possibly_eval); bool optional_chain = false) {
return new (zone_)
Call(zone_, expression, arguments, pos, possibly_eval, optional_chain);
} }
Call* NewTaggedTemplate(Expression* expression, Call* NewTaggedTemplate(Expression* expression,
......
...@@ -342,6 +342,9 @@ void CallPrinter::VisitAwait(Await* node) { Find(node->expression()); } ...@@ -342,6 +342,9 @@ void CallPrinter::VisitAwait(Await* node) { Find(node->expression()); }
void CallPrinter::VisitThrow(Throw* node) { Find(node->exception()); } void CallPrinter::VisitThrow(Throw* node) { Find(node->exception()); }
void CallPrinter::VisitOptionalChain(OptionalChain* node) {
Find(node->expression());
}
void CallPrinter::VisitProperty(Property* node) { void CallPrinter::VisitProperty(Property* node) {
Expression* key = node->key(); Expression* key = node->key();
...@@ -349,12 +352,18 @@ void CallPrinter::VisitProperty(Property* node) { ...@@ -349,12 +352,18 @@ void CallPrinter::VisitProperty(Property* node) {
if (literal != nullptr && if (literal != nullptr &&
literal->BuildValue(isolate_)->IsInternalizedString()) { literal->BuildValue(isolate_)->IsInternalizedString()) {
Find(node->obj(), true); Find(node->obj(), true);
if (node->is_optional_chain_link()) {
Print("?");
}
Print("."); Print(".");
// TODO(adamk): Teach Literal how to print its values without // TODO(adamk): Teach Literal how to print its values without
// allocating on the heap. // allocating on the heap.
PrintLiteral(literal->BuildValue(isolate_), false); PrintLiteral(literal->BuildValue(isolate_), false);
} else { } else {
Find(node->obj(), true); Find(node->obj(), true);
if (node->is_optional_chain_link()) {
Print("?.");
}
Print("["); Print("[");
Find(key, true); Find(key, true);
Print("]"); Print("]");
...@@ -1272,6 +1281,11 @@ void AstPrinter::VisitThrow(Throw* node) { ...@@ -1272,6 +1281,11 @@ void AstPrinter::VisitThrow(Throw* node) {
Visit(node->exception()); Visit(node->exception());
} }
void AstPrinter::VisitOptionalChain(OptionalChain* node) {
IndentedScope indent(this, "OPTIONAL_CHAIN", node->position());
Visit(node->expression());
}
void AstPrinter::VisitProperty(Property* node) { void AstPrinter::VisitProperty(Property* node) {
EmbeddedVector<char, 128> buf; EmbeddedVector<char, 128> buf;
SNPrintF(buf, "PROPERTY"); SNPrintF(buf, "PROPERTY");
......
...@@ -575,7 +575,10 @@ namespace internal { ...@@ -575,7 +575,10 @@ namespace internal {
"FinalizationGroup.prototype.register: target and holdings must not be " \ "FinalizationGroup.prototype.register: target and holdings must not be " \
"same") \ "same") \
T(WeakRefsWeakRefConstructorTargetMustBeObject, \ T(WeakRefsWeakRefConstructorTargetMustBeObject, \
"WeakRef: target must be an object") "WeakRef: target must be an object") \
T(OptionalChainingNoNew, "Invalid optional chain from new expression") \
T(OptionalChainingNoSuper, "Invalid optional chain from super property") \
T(OptionalChainingNoTemplate, "Invalid tagged template on optional chain")
enum class MessageTemplate { enum class MessageTemplate {
#define TEMPLATE(NAME, STRING) k##NAME, #define TEMPLATE(NAME, STRING) k##NAME,
......
...@@ -206,7 +206,8 @@ DEFINE_IMPLICATION(harmony_import_meta, harmony_dynamic_import) ...@@ -206,7 +206,8 @@ DEFINE_IMPLICATION(harmony_import_meta, harmony_dynamic_import)
#define HARMONY_INPROGRESS_BASE(V) \ #define HARMONY_INPROGRESS_BASE(V) \
V(harmony_private_methods, "harmony private methods in class literals") \ V(harmony_private_methods, "harmony private methods in class literals") \
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")
#ifdef V8_INTL_SUPPORT #ifdef V8_INTL_SUPPORT
#define HARMONY_INPROGRESS(V) \ #define HARMONY_INPROGRESS(V) \
......
...@@ -4260,6 +4260,7 @@ EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_private_methods) ...@@ -4260,6 +4260,7 @@ EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_private_methods)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_dynamic_import) 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)
#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)
......
...@@ -915,6 +915,27 @@ class BytecodeGenerator::IteratorRecord final { ...@@ -915,6 +915,27 @@ class BytecodeGenerator::IteratorRecord final {
Register next_; Register next_;
}; };
class BytecodeGenerator::OptionalChainNullLabelScope final {
public:
explicit OptionalChainNullLabelScope(BytecodeGenerator* bytecode_generator)
: bytecode_generator_(bytecode_generator),
labels_(bytecode_generator->zone()) {
prev_ = bytecode_generator_->optional_chaining_null_labels_;
bytecode_generator_->optional_chaining_null_labels_ = &labels_;
}
~OptionalChainNullLabelScope() {
bytecode_generator_->optional_chaining_null_labels_ = prev_;
}
BytecodeLabels* labels() { return &labels_; }
private:
BytecodeGenerator* bytecode_generator_;
BytecodeLabels labels_;
BytecodeLabels* prev_;
};
namespace { namespace {
// A map from property names to getter/setter pairs allocated in the zone that // A map from property names to getter/setter pairs allocated in the zone that
...@@ -994,6 +1015,7 @@ BytecodeGenerator::BytecodeGenerator( ...@@ -994,6 +1015,7 @@ BytecodeGenerator::BytecodeGenerator(
execution_context_(nullptr), execution_context_(nullptr),
execution_result_(nullptr), execution_result_(nullptr),
incoming_new_target_or_generator_(), incoming_new_target_or_generator_(),
optional_chaining_null_labels_(nullptr),
dummy_feedback_slot_(feedback_spec(), FeedbackSlotKind::kCompareOp), dummy_feedback_slot_(feedback_spec(), FeedbackSlotKind::kCompareOp),
generator_jump_table_(nullptr), generator_jump_table_(nullptr),
suspend_count_(0), suspend_count_(0),
...@@ -4318,7 +4340,17 @@ void BytecodeGenerator::VisitThrow(Throw* expr) { ...@@ -4318,7 +4340,17 @@ void BytecodeGenerator::VisitThrow(Throw* expr) {
} }
void BytecodeGenerator::VisitPropertyLoad(Register obj, Property* property) { void BytecodeGenerator::VisitPropertyLoad(Register obj, Property* property) {
if (property->is_optional_chain_link()) {
DCHECK_NOT_NULL(optional_chaining_null_labels_);
// TODO(ignition): Add a single opcode for JumpIfNullOrUndefined
builder()
->LoadAccumulatorWithRegister(obj)
.JumpIfUndefined(optional_chaining_null_labels_->New())
.JumpIfNull(optional_chaining_null_labels_->New());
}
AssignType property_kind = Property::GetAssignType(property); AssignType property_kind = Property::GetAssignType(property);
switch (property_kind) { switch (property_kind) {
case NON_PROPERTY: case NON_PROPERTY:
UNREACHABLE(); UNREACHABLE();
...@@ -4416,6 +4448,16 @@ void BytecodeGenerator::VisitKeyedSuperPropertyLoad(Property* property, ...@@ -4416,6 +4448,16 @@ void BytecodeGenerator::VisitKeyedSuperPropertyLoad(Property* property,
} }
} }
void BytecodeGenerator::VisitOptionalChain(OptionalChain* expr) {
BytecodeLabel done;
OptionalChainNullLabelScope label_scope(this);
VisitForAccumulatorValue(expr->expression());
builder()->Jump(&done);
label_scope.labels()->Bind(builder());
builder()->LoadUndefined();
builder()->Bind(&done);
}
void BytecodeGenerator::VisitProperty(Property* expr) { void BytecodeGenerator::VisitProperty(Property* expr) {
AssignType property_kind = Property::GetAssignType(expr); AssignType property_kind = Property::GetAssignType(expr);
if (property_kind != NAMED_SUPER_PROPERTY && if (property_kind != NAMED_SUPER_PROPERTY &&
...@@ -4549,6 +4591,15 @@ void BytecodeGenerator::VisitCall(Call* expr) { ...@@ -4549,6 +4591,15 @@ void BytecodeGenerator::VisitCall(Call* expr) {
UNREACHABLE(); UNREACHABLE();
} }
if (expr->is_optional_chain_link()) {
DCHECK_NOT_NULL(optional_chaining_null_labels_);
// TODO(ignition): Add a single opcode for JumpIfNullOrUndefined
builder()
->LoadAccumulatorWithRegister(callee)
.JumpIfUndefined(optional_chaining_null_labels_->New())
.JumpIfNull(optional_chaining_null_labels_->New());
}
// Evaluate all arguments to the function call and store in sequential args // Evaluate all arguments to the function call and store in sequential args
// registers. // registers.
VisitArguments(expr->arguments(), &args); VisitArguments(expr->arguments(), &args);
...@@ -4810,6 +4861,28 @@ void BytecodeGenerator::VisitDelete(UnaryOperation* unary) { ...@@ -4810,6 +4861,28 @@ void BytecodeGenerator::VisitDelete(UnaryOperation* unary) {
Register object = VisitForRegisterValue(property->obj()); Register object = VisitForRegisterValue(property->obj());
VisitForAccumulatorValue(property->key()); VisitForAccumulatorValue(property->key());
builder()->Delete(object, language_mode()); builder()->Delete(object, language_mode());
} else if (expr->IsOptionalChain()) {
Expression* expr_inner = expr->AsOptionalChain()->expression();
if (expr_inner->IsProperty()) {
Property* property = expr_inner->AsProperty();
DCHECK(!property->IsPrivateReference());
BytecodeLabel done;
OptionalChainNullLabelScope label_scope(this);
VisitForAccumulatorValue(property->obj());
builder()
->JumpIfUndefined(label_scope.labels()->New())
.JumpIfNull(label_scope.labels()->New());
Register object = register_allocator()->NewRegister();
builder()->StoreAccumulatorInRegister(object);
VisitForAccumulatorValue(property->key());
builder()->Delete(object, language_mode()).Jump(&done);
label_scope.labels()->Bind(builder());
builder()->LoadTrue();
builder()->Bind(&done);
} else {
VisitForEffect(expr);
builder()->LoadTrue();
}
} else if (expr->IsVariableProxy() && } else if (expr->IsVariableProxy() &&
!expr->AsVariableProxy()->is_new_target()) { !expr->AsVariableProxy()->is_new_target()) {
// Delete of an unqualified identifier is allowed in sloppy mode but is // Delete of an unqualified identifier is allowed in sloppy mode but is
......
...@@ -66,6 +66,7 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> { ...@@ -66,6 +66,7 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
class AccumulatorPreservingScope; class AccumulatorPreservingScope;
class TestResultScope; class TestResultScope;
class ValueResultScope; class ValueResultScope;
class OptionalChainNullLabelScope;
using ToBooleanMode = BytecodeArrayBuilder::ToBooleanMode; using ToBooleanMode = BytecodeArrayBuilder::ToBooleanMode;
...@@ -491,6 +492,8 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> { ...@@ -491,6 +492,8 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
Register incoming_new_target_or_generator_; Register incoming_new_target_or_generator_;
BytecodeLabels* optional_chaining_null_labels_;
// Dummy feedback slot for compare operations, where we don't care about // Dummy feedback slot for compare operations, where we don't care about
// feedback // feedback
SharedFeedbackSlot dummy_feedback_slot_; SharedFeedbackSlot dummy_feedback_slot_;
......
...@@ -62,6 +62,7 @@ ParseInfo::ParseInfo(Isolate* isolate, AccountingAllocator* zone_allocator) ...@@ -62,6 +62,7 @@ ParseInfo::ParseInfo(Isolate* isolate, AccountingAllocator* zone_allocator)
set_allow_natives_syntax(FLAG_allow_natives_syntax); set_allow_natives_syntax(FLAG_allow_natives_syntax);
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_private_methods(FLAG_harmony_private_methods); set_allow_harmony_private_methods(FLAG_harmony_private_methods);
} }
......
...@@ -105,6 +105,8 @@ class V8_EXPORT_PRIVATE ParseInfo { ...@@ -105,6 +105,8 @@ class V8_EXPORT_PRIVATE ParseInfo {
set_allow_harmony_dynamic_import) set_allow_harmony_dynamic_import)
FLAG_ACCESSOR(kAllowHarmonyImportMeta, allow_harmony_import_meta, FLAG_ACCESSOR(kAllowHarmonyImportMeta, allow_harmony_import_meta,
set_allow_harmony_import_meta) set_allow_harmony_import_meta)
FLAG_ACCESSOR(kAllowHarmonyOptionalChaining, allow_harmony_optional_chaining,
set_allow_harmony_optional_chaining)
FLAG_ACCESSOR(kAllowHarmonyPrivateMethods, allow_harmony_private_methods, FLAG_ACCESSOR(kAllowHarmonyPrivateMethods, allow_harmony_private_methods,
set_allow_harmony_private_methods) set_allow_harmony_private_methods)
FLAG_ACCESSOR(kIsOneshotIIFE, is_oneshot_iife, set_is_oneshot_iife) FLAG_ACCESSOR(kIsOneshotIIFE, is_oneshot_iife, set_is_oneshot_iife)
...@@ -304,10 +306,11 @@ class V8_EXPORT_PRIVATE ParseInfo { ...@@ -304,10 +306,11 @@ class V8_EXPORT_PRIVATE ParseInfo {
kAllowHarmonyStaticFields = 1 << 24, kAllowHarmonyStaticFields = 1 << 24,
kAllowHarmonyDynamicImport = 1 << 25, kAllowHarmonyDynamicImport = 1 << 25,
kAllowHarmonyImportMeta = 1 << 26, kAllowHarmonyImportMeta = 1 << 26,
kAllowHarmonyPrivateFields = 1 << 27, kAllowHarmonyOptionalChaining = 1 << 27,
kAllowHarmonyPrivateMethods = 1 << 28, kAllowHarmonyPrivateFields = 1 << 28,
kIsOneshotIIFE = 1 << 29, kAllowHarmonyPrivateMethods = 1 << 29,
kCollectSourcePositions = 1 << 30, kIsOneshotIIFE = 1 << 30,
kCollectSourcePositions = 1 << 31,
}; };
//------------- Inputs to parsing and scope analysis ----------------------- //------------- Inputs to parsing and scope analysis -----------------------
......
...@@ -286,6 +286,14 @@ class ParserBase { ...@@ -286,6 +286,14 @@ class ParserBase {
V8_INLINE bool has_error() const { return scanner()->has_parser_error(); } V8_INLINE bool has_error() const { return scanner()->has_parser_error(); }
bool allow_harmony_optional_chaining() const {
return scanner()->allow_harmony_optional_chaining();
}
void set_allow_harmony_optional_chaining(bool allow) {
scanner()->set_allow_harmony_optional_chaining(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; }
...@@ -3075,7 +3083,7 @@ ParserBase<Impl>::ParseLeftHandSideContinuation(ExpressionT result) { ...@@ -3075,7 +3083,7 @@ ParserBase<Impl>::ParseLeftHandSideContinuation(ExpressionT result) {
} }
if (has_spread) { if (has_spread) {
result = impl()->SpreadCall(result, args, pos, Call::NOT_EVAL); result = impl()->SpreadCall(result, args, pos, Call::NOT_EVAL, false);
} else { } else {
result = factory()->NewCall(result, args, pos, Call::NOT_EVAL); result = factory()->NewCall(result, args, pos, Call::NOT_EVAL);
} }
...@@ -3086,25 +3094,42 @@ ParserBase<Impl>::ParseLeftHandSideContinuation(ExpressionT result) { ...@@ -3086,25 +3094,42 @@ ParserBase<Impl>::ParseLeftHandSideContinuation(ExpressionT result) {
if (!Token::IsPropertyOrCall(peek())) return result; if (!Token::IsPropertyOrCall(peek())) return result;
} }
bool optional_chaining = false;
bool is_optional = false;
do { do {
switch (peek()) { switch (peek()) {
case Token::QUESTION_PERIOD: {
if (is_optional) {
ReportUnexpectedToken(peek());
return impl()->FailureExpression();
}
Consume(Token::QUESTION_PERIOD);
is_optional = true;
optional_chaining = true;
continue;
}
/* Property */ /* Property */
case Token::LBRACK: { case Token::LBRACK: {
Consume(Token::LBRACK); Consume(Token::LBRACK);
int pos = position(); int pos = position();
AcceptINScope scope(this, true); AcceptINScope scope(this, true);
ExpressionT index = ParseExpressionCoverGrammar(); ExpressionT index = ParseExpressionCoverGrammar();
result = factory()->NewProperty(result, index, pos); result = factory()->NewProperty(result, index, pos, is_optional);
Expect(Token::RBRACK); Expect(Token::RBRACK);
break; break;
} }
/* Property */ /* Property */
case Token::PERIOD: { case Token::PERIOD: {
if (is_optional) {
ReportUnexpectedToken(Next());
return impl()->FailureExpression();
}
Consume(Token::PERIOD); Consume(Token::PERIOD);
int pos = position(); int pos = position();
ExpressionT key = ParsePropertyOrPrivatePropertyName(); ExpressionT key = ParsePropertyOrPrivatePropertyName();
result = factory()->NewProperty(result, key, pos); result = factory()->NewProperty(result, key, pos, is_optional);
break; break;
} }
...@@ -3149,22 +3174,39 @@ ParserBase<Impl>::ParseLeftHandSideContinuation(ExpressionT result) { ...@@ -3149,22 +3174,39 @@ ParserBase<Impl>::ParseLeftHandSideContinuation(ExpressionT result) {
CheckPossibleEvalCall(result, scope()); CheckPossibleEvalCall(result, scope());
if (has_spread) { if (has_spread) {
result = impl()->SpreadCall(result, args, pos, is_possibly_eval); result = impl()->SpreadCall(result, args, pos, is_possibly_eval,
is_optional);
} else { } else {
result = factory()->NewCall(result, args, pos, is_possibly_eval); result = factory()->NewCall(result, args, pos, is_possibly_eval,
is_optional);
} }
fni_.RemoveLastFunction(); fni_.RemoveLastFunction();
break; break;
} }
/* Call */
default: default:
/* Optional Property */
if (is_optional) {
DCHECK_EQ(scanner()->current_token(), Token::QUESTION_PERIOD);
int pos = position();
ExpressionT key = ParsePropertyOrPrivatePropertyName();
result = factory()->NewProperty(result, key, pos, is_optional);
break;
}
if (optional_chaining) {
impl()->ReportMessageAt(scanner()->peek_location(),
MessageTemplate::kOptionalChainingNoTemplate);
return impl()->FailureExpression();
}
/* Tagged Template */
DCHECK(Token::IsTemplate(peek())); DCHECK(Token::IsTemplate(peek()));
result = ParseTemplateLiteral(result, position(), true); result = ParseTemplateLiteral(result, position(), true);
break; break;
} }
} while (Token::IsPropertyOrCall(peek())); is_optional = false;
} while (is_optional || Token::IsPropertyOrCall(peek()));
if (optional_chaining) return factory()->NewOptionalChain(result);
return result; return result;
} }
...@@ -3226,6 +3268,13 @@ ParserBase<Impl>::ParseMemberWithPresentNewPrefixesExpression() { ...@@ -3226,6 +3268,13 @@ ParserBase<Impl>::ParseMemberWithPresentNewPrefixesExpression() {
// The expression can still continue with . or [ after the arguments. // The expression can still continue with . or [ after the arguments.
return ParseMemberExpressionContinuation(result); return ParseMemberExpressionContinuation(result);
} }
if (peek() == Token::QUESTION_PERIOD) {
impl()->ReportMessageAt(scanner()->peek_location(),
MessageTemplate::kOptionalChainingNoNew);
return impl()->FailureExpression();
}
// NewExpression without arguments. // NewExpression without arguments.
ExpressionListT args(pointer_buffer()); ExpressionListT args(pointer_buffer());
return factory()->NewCallNew(result, args, new_pos); return factory()->NewCallNew(result, args, new_pos);
...@@ -3348,6 +3397,11 @@ typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParseSuperExpression( ...@@ -3348,6 +3397,11 @@ typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParseSuperExpression(
impl()->ReportMessage(MessageTemplate::kUnexpectedPrivateField); impl()->ReportMessage(MessageTemplate::kUnexpectedPrivateField);
return impl()->FailureExpression(); return impl()->FailureExpression();
} }
if (peek() == Token::QUESTION_PERIOD) {
Consume(Token::QUESTION_PERIOD);
impl()->ReportMessage(MessageTemplate::kOptionalChainingNoSuper);
return impl()->FailureExpression();
}
scope->RecordSuperPropertyUsage(); scope->RecordSuperPropertyUsage();
UseThis(); UseThis();
return impl()->NewSuperPropertyReference(pos); return impl()->NewSuperPropertyReference(pos);
......
...@@ -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_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;
++feature) { ++feature) {
...@@ -3162,10 +3163,12 @@ ArrayLiteral* Parser::ArrayLiteralFromListWithSpread( ...@@ -3162,10 +3163,12 @@ ArrayLiteral* Parser::ArrayLiteralFromListWithSpread(
Expression* Parser::SpreadCall(Expression* function, Expression* Parser::SpreadCall(Expression* function,
const ScopedPtrList<Expression>& args_list, const ScopedPtrList<Expression>& args_list,
int pos, Call::PossiblyEval is_possibly_eval) { int pos, Call::PossiblyEval is_possibly_eval,
bool optional_chain) {
// Handle this case in BytecodeGenerator. // Handle this case in BytecodeGenerator.
if (OnlyLastArgIsSpread(args_list) || function->IsSuperCallReference()) { if (OnlyLastArgIsSpread(args_list) || function->IsSuperCallReference()) {
return factory()->NewCall(function, args_list, pos); return factory()->NewCall(function, args_list, pos, Call::NOT_EVAL,
optional_chain);
} }
ScopedPtrList<Expression> args(pointer_buffer()); ScopedPtrList<Expression> args(pointer_buffer());
...@@ -3180,8 +3183,9 @@ Expression* Parser::SpreadCall(Expression* function, ...@@ -3180,8 +3183,9 @@ Expression* Parser::SpreadCall(Expression* function,
VariableProxy* obj = factory()->NewVariableProxy(temp); VariableProxy* obj = factory()->NewVariableProxy(temp);
Assignment* assign_obj = factory()->NewAssignment( Assignment* assign_obj = factory()->NewAssignment(
Token::ASSIGN, obj, function->AsProperty()->obj(), kNoSourcePosition); Token::ASSIGN, obj, function->AsProperty()->obj(), kNoSourcePosition);
function = factory()->NewProperty( function =
assign_obj, function->AsProperty()->key(), kNoSourcePosition); factory()->NewProperty(assign_obj, function->AsProperty()->key(),
kNoSourcePosition, optional_chain);
args.Add(function); args.Add(function);
obj = factory()->NewVariableProxy(temp); obj = factory()->NewVariableProxy(temp);
args.Add(obj); args.Add(obj);
......
...@@ -483,7 +483,8 @@ class V8_EXPORT_PRIVATE Parser : public NON_EXPORTED_BASE(ParserBase<Parser>) { ...@@ -483,7 +483,8 @@ class V8_EXPORT_PRIVATE Parser : public NON_EXPORTED_BASE(ParserBase<Parser>) {
const ScopedPtrList<Expression>& list); const ScopedPtrList<Expression>& list);
Expression* SpreadCall(Expression* function, Expression* SpreadCall(Expression* function,
const ScopedPtrList<Expression>& args, int pos, const ScopedPtrList<Expression>& args, int pos,
Call::PossiblyEval is_possibly_eval); Call::PossiblyEval is_possibly_eval,
bool optional_chain);
Expression* SpreadCallNew(Expression* function, Expression* SpreadCallNew(Expression* function,
const ScopedPtrList<Expression>& args, int pos); const ScopedPtrList<Expression>& args, int pos);
Expression* RewriteSuperCall(Expression* call_expression); Expression* RewriteSuperCall(Expression* call_expression);
......
...@@ -565,8 +565,13 @@ class PreParserFactory { ...@@ -565,8 +565,13 @@ class PreParserFactory {
return PreParserExpression::Default(); return PreParserExpression::Default();
} }
PreParserExpression NewOptionalChain(const PreParserExpression& expr) {
return PreParserExpression::Default();
}
PreParserExpression NewProperty(const PreParserExpression& obj, PreParserExpression NewProperty(const PreParserExpression& obj,
const PreParserExpression& key, int pos) { const PreParserExpression& key, int pos,
bool optional_chain = false) {
if (key.IsIdentifier() && key.AsIdentifier().IsPrivateName()) { if (key.IsIdentifier() && key.AsIdentifier().IsPrivateName()) {
if (obj.IsThis()) { if (obj.IsThis()) {
return PreParserExpression::ThisPrivateReference(); return PreParserExpression::ThisPrivateReference();
...@@ -625,9 +630,10 @@ class PreParserFactory { ...@@ -625,9 +630,10 @@ class PreParserFactory {
int pos) { int pos) {
return PreParserExpression::Default(); return PreParserExpression::Default();
} }
PreParserExpression NewCall( PreParserExpression NewCall(PreParserExpression expression,
PreParserExpression expression, const PreParserExpressionList& arguments, const PreParserExpressionList& arguments, int pos,
int pos, Call::PossiblyEval possibly_eval = Call::NOT_EVAL) { Call::PossiblyEval possibly_eval = Call::NOT_EVAL,
bool optional_chain = false) {
if (possibly_eval == Call::IS_POSSIBLY_EVAL) { if (possibly_eval == Call::IS_POSSIBLY_EVAL) {
DCHECK(expression.IsIdentifier() && expression.AsIdentifier().IsEval()); DCHECK(expression.IsIdentifier() && expression.AsIdentifier().IsEval());
return PreParserExpression::CallEval(); return PreParserExpression::CallEval();
...@@ -1052,7 +1058,8 @@ class PreParser : public ParserBase<PreParser> { ...@@ -1052,7 +1058,8 @@ class PreParser : public ParserBase<PreParser> {
V8_INLINE PreParserExpression SpreadCall(const PreParserExpression& function, V8_INLINE PreParserExpression SpreadCall(const PreParserExpression& function,
const PreParserExpressionList& args, const PreParserExpressionList& args,
int pos, int pos,
Call::PossiblyEval possibly_eval); Call::PossiblyEval possibly_eval,
bool optional_chain);
V8_INLINE PreParserExpression V8_INLINE PreParserExpression
SpreadCallNew(const PreParserExpression& function, SpreadCallNew(const PreParserExpression& function,
const PreParserExpressionList& args, int pos); const PreParserExpressionList& args, int pos);
...@@ -1692,8 +1699,9 @@ class PreParser : public ParserBase<PreParser> { ...@@ -1692,8 +1699,9 @@ class PreParser : public ParserBase<PreParser> {
PreParserExpression PreParser::SpreadCall(const PreParserExpression& function, PreParserExpression PreParser::SpreadCall(const PreParserExpression& function,
const PreParserExpressionList& args, const PreParserExpressionList& args,
int pos, int pos,
Call::PossiblyEval possibly_eval) { Call::PossiblyEval possibly_eval,
return factory()->NewCall(function, args, pos, possibly_eval); bool optional_chain) {
return factory()->NewCall(function, args, pos, possibly_eval, optional_chain);
} }
PreParserExpression PreParser::SpreadCallNew( PreParserExpression PreParser::SpreadCallNew(
......
...@@ -354,7 +354,6 @@ V8_INLINE Token::Value Scanner::ScanSingleToken() { ...@@ -354,7 +354,6 @@ V8_INLINE Token::Value Scanner::ScanSingleToken() {
case Token::RBRACE: case Token::RBRACE:
case Token::LBRACK: case Token::LBRACK:
case Token::RBRACK: case Token::RBRACK:
case Token::CONDITIONAL:
case Token::COLON: case Token::COLON:
case Token::SEMICOLON: case Token::SEMICOLON:
case Token::COMMA: case Token::COMMA:
...@@ -363,6 +362,16 @@ V8_INLINE Token::Value Scanner::ScanSingleToken() { ...@@ -363,6 +362,16 @@ V8_INLINE Token::Value Scanner::ScanSingleToken() {
// One character tokens. // One character tokens.
return Select(token); return Select(token);
case Token::CONDITIONAL:
// ? ?.
Advance();
if (allow_harmony_optional_chaining() && c0_ == '.') {
Advance();
if (!IsDecimalDigit(c0_)) return Token::QUESTION_PERIOD;
PushBack('.');
}
return Token::CONDITIONAL;
case Token::STRING: case Token::STRING:
return ScanString(); return ScanString();
......
...@@ -92,6 +92,7 @@ bool Scanner::BookmarkScope::HasBeenApplied() const { ...@@ -92,6 +92,7 @@ bool Scanner::BookmarkScope::HasBeenApplied() const {
Scanner::Scanner(Utf16CharacterStream* source, bool is_module) Scanner::Scanner(Utf16CharacterStream* source, bool is_module)
: source_(source), : source_(source),
found_html_comment_(false), found_html_comment_(false),
allow_harmony_optional_chaining_(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) {
......
...@@ -406,6 +406,14 @@ class V8_EXPORT_PRIVATE Scanner { ...@@ -406,6 +406,14 @@ class V8_EXPORT_PRIVATE Scanner {
bool FoundHtmlComment() const { return found_html_comment_; } bool FoundHtmlComment() const { return found_html_comment_; }
bool allow_harmony_optional_chaining() const {
return allow_harmony_optional_chaining_;
}
void set_allow_harmony_optional_chaining(bool allow) {
allow_harmony_optional_chaining_ = 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.
...@@ -713,6 +721,9 @@ class V8_EXPORT_PRIVATE Scanner { ...@@ -713,6 +721,9 @@ class V8_EXPORT_PRIVATE Scanner {
// Whether this scanner encountered an HTML comment. // Whether this scanner encountered an HTML comment.
bool found_html_comment_; bool found_html_comment_;
// Harmony flags to allow ESNext features.
bool allow_harmony_optional_chaining_;
const bool is_module_; const bool is_module_;
// Values parsed from magic comments. // Values parsed from magic comments.
......
...@@ -65,6 +65,7 @@ namespace internal { ...@@ -65,6 +65,7 @@ namespace internal {
T(LBRACK, "[", 0) \ T(LBRACK, "[", 0) \
/* END Property */ \ /* END Property */ \
/* END Member */ \ /* END Member */ \
T(QUESTION_PERIOD, "?.", 0) \
T(LPAREN, "(", 0) \ T(LPAREN, "(", 0) \
/* END PropertyOrCall */ \ /* END PropertyOrCall */ \
T(RPAREN, ")", 0) \ T(RPAREN, ")", 0) \
......
...@@ -390,6 +390,7 @@ bool TokenIsPropertyOrCall(Token::Value token) { ...@@ -390,6 +390,7 @@ bool TokenIsPropertyOrCall(Token::Value token) {
case Token::TEMPLATE_SPAN: case Token::TEMPLATE_SPAN:
case Token::TEMPLATE_TAIL: case Token::TEMPLATE_TAIL:
case Token::PERIOD: case Token::PERIOD:
case Token::QUESTION_PERIOD:
case Token::LBRACK: case Token::LBRACK:
case Token::LPAREN: case Token::LPAREN:
return true; return true;
...@@ -1529,6 +1530,7 @@ enum ParserFlag { ...@@ -1529,6 +1530,7 @@ enum ParserFlag {
kAllowHarmonyPrivateMethods, kAllowHarmonyPrivateMethods,
kAllowHarmonyDynamicImport, kAllowHarmonyDynamicImport,
kAllowHarmonyImportMeta, kAllowHarmonyImportMeta,
kAllowHarmonyOptionalChaining,
}; };
enum ParserSyncTestResult { enum ParserSyncTestResult {
...@@ -1542,6 +1544,8 @@ void SetGlobalFlags(base::EnumSet<ParserFlag> flags) { ...@@ -1542,6 +1544,8 @@ void SetGlobalFlags(base::EnumSet<ParserFlag> flags) {
i::FLAG_harmony_private_methods = flags.contains(kAllowHarmonyPrivateMethods); i::FLAG_harmony_private_methods = flags.contains(kAllowHarmonyPrivateMethods);
i::FLAG_harmony_dynamic_import = flags.contains(kAllowHarmonyDynamicImport); i::FLAG_harmony_dynamic_import = flags.contains(kAllowHarmonyDynamicImport);
i::FLAG_harmony_import_meta = flags.contains(kAllowHarmonyImportMeta); i::FLAG_harmony_import_meta = flags.contains(kAllowHarmonyImportMeta);
i::FLAG_harmony_optional_chaining =
flags.contains(kAllowHarmonyOptionalChaining);
} }
void SetParserFlags(i::PreParser* parser, base::EnumSet<ParserFlag> flags) { void SetParserFlags(i::PreParser* parser, base::EnumSet<ParserFlag> flags) {
...@@ -1552,6 +1556,8 @@ void SetParserFlags(i::PreParser* parser, base::EnumSet<ParserFlag> flags) { ...@@ -1552,6 +1556,8 @@ void SetParserFlags(i::PreParser* parser, base::EnumSet<ParserFlag> flags) {
flags.contains(kAllowHarmonyDynamicImport)); flags.contains(kAllowHarmonyDynamicImport));
parser->set_allow_harmony_import_meta( parser->set_allow_harmony_import_meta(
flags.contains(kAllowHarmonyImportMeta)); flags.contains(kAllowHarmonyImportMeta));
parser->set_allow_harmony_optional_chaining(
flags.contains(kAllowHarmonyOptionalChaining));
} }
void TestParserSyncWithFlags(i::Handle<i::String> source, void TestParserSyncWithFlags(i::Handle<i::String> source,
...@@ -1973,6 +1979,36 @@ TEST(NumericSeparatorUnicodeEscapeSequencesErrors) { ...@@ -1973,6 +1979,36 @@ TEST(NumericSeparatorUnicodeEscapeSequencesErrors) {
RunParserSyncTest(context_data, statement_data, kError); RunParserSyncTest(context_data, statement_data, kError);
} }
TEST(OptionalChaining) {
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']", "a?.()", nullptr};
static const ParserFlag flags[] = {kAllowHarmonyOptionalChaining};
RunParserSyncTest(context_data, statement_data, kSuccess, nullptr, 0, flags,
1, nullptr, 0, false, true, true);
RunParserSyncTest(context_data, statement_data, kError);
}
TEST(OptionalChainingTaggedError) {
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']``", "a?.()``", nullptr};
static const ParserFlag flags[] = {kAllowHarmonyOptionalChaining};
RunParserSyncTest(context_data, statement_data, kError, nullptr, 9, flags, 1,
nullptr, 0, false, true, true);
RunParserSyncTest(context_data, statement_data, kError);
}
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-optional-chaining
function shouldThrowSyntaxError(script) {
let error;
try {
eval(script);
} catch (e) {
error = e;
}
if (!(error instanceof SyntaxError)) {
throw new Error('Expected SyntaxError!');
}
}
assertEquals(undefined?.valueOf(), undefined);
assertEquals(null?.valueOf(), undefined);
assertEquals(true?.valueOf(), true);
assertEquals(false?.valueOf(), false);
assertEquals(0?.valueOf(), 0);
assertEquals(1?.valueOf(), 1);
assertEquals(''?.valueOf(), '');
assertEquals('hi'?.valueOf(), 'hi');
assertEquals(({})?.constructor, Object);
assertEquals(({ x: 'hi' })?.x, 'hi');
assertEquals([]?.length, 0);
assertEquals(['hi']?.length, 1);
assertEquals(undefined?.['valueOf'](), undefined);
assertEquals(null?.['valueOf'](), undefined);
assertEquals(true?.['valueOf'](), true);
assertEquals(false?.['valueOf'](), false);
assertEquals(0?.['valueOf'](), 0);
assertEquals(1?.['valueOf'](), 1);
assertEquals(''?.['valueOf'](), '');
assertEquals('hi'?.['valueOf'](), 'hi');
assertEquals(({})?.['constructor'], Object);
assertEquals(({ x: 'hi' })?.['x'], 'hi');
assertEquals([]?.['length'], 0);
assertEquals(['hi']?.[0], 'hi');
assertEquals(undefined?.(), undefined);
assertEquals(null?.(), undefined);
assertThrows(() => true?.(), TypeError);
assertThrows(() => false?.(), TypeError);
assertThrows(() => 0?.(), TypeError);
assertThrows(() => 1?.(), TypeError);
assertThrows(() => ''?.(), TypeError);
assertThrows(() => 'hi'?.(), TypeError);
assertThrows(() => ({})?.(), TypeError);
assertThrows(() => ({ x: 'hi' })?.(), TypeError);
assertThrows(() => []?.(), TypeError);
assertThrows(() => ['hi']?.(), TypeError);
assertThrows(() => ({})?.a['b'], TypeError);
assertEquals(({})?.a?.['b'], undefined);
assertEquals(null?.a['b']().c, undefined);
assertThrows(() => ({})?.['a'].b);
assertEquals(({})?.['a']?.b, undefined);
assertEquals(null?.['a'].b()['c'], undefined);
assertThrows(() => (() => {})?.()(), TypeError);
assertEquals((() => {})?.()?.(), undefined);
assertEquals(null?.()().a['b'], undefined);
assertEquals(delete undefined?.foo, true);
assertEquals(delete null?.foo, true);
assertEquals(delete undefined?.['foo'], true);
assertEquals(delete null?.['foo'], true);
assertEquals(delete undefined?.(), true);
assertEquals(delete null?.(), true);
assertEquals(undefined?.(...a), undefined);
assertEquals(null?.(1, ...a), undefined);
assertEquals(({}).a?.(...a), undefined);
assertEquals(({ a: null }).a?.(...a), undefined);
assertEquals(undefined?.(...a)?.(1, ...a), undefined);
assertThrows(() => 5?.(...[]), TypeError);
const o1 = { x: 0, y: 0, z() {} };
assertEquals(delete o1?.x, true);
assertEquals(o1.x, undefined);
assertEquals(delete o1?.x, true);
assertEquals(delete o1?.['y'], true);
assertEquals(o1.y, undefined);
assertEquals(delete o1?.['y'], true);
assertEquals(delete o1.z?.(), true);
shouldThrowSyntaxError('class C {} class D extends C { foo() { return super?.bar; } }');
shouldThrowSyntaxError('class C {} class D extends C { foo() { return super?.["bar"]; } }');
shouldThrowSyntaxError('class C {} class D extends C { constructor() { super?.(); } }');
shouldThrowSyntaxError('const o = { C: class {} }; new o?.C();');
shouldThrowSyntaxError('const o = { C: class {} }; new o?.["C"]();');
shouldThrowSyntaxError('class C {} new C?.();');
shouldThrowSyntaxError('function foo() { new?.target; }');
shouldThrowSyntaxError('function tag() {} tag?.``;');
shouldThrowSyntaxError('const o = { tag() {} }; o?.tag``;');
const o2 = {
count: 0,
get x() {
this.count += 1;
return () => {};
},
};
o2.x?.y;
assertEquals(o2.count, 1);
o2.x?.['y'];
assertEquals(o2.count, 2);
o2.x?.();
assertEquals(o2.count, 3);
assertEquals(true?.5:5, 0.5);
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