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) {
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>
void AstTraversalVisitor<Subclass>::VisitProperty(Property* expr) {
PROCESS_EXPRESSION(expr);
......
......@@ -94,6 +94,7 @@ namespace internal {
V(ImportCallExpression) \
V(Literal) \
V(NativeFunctionLiteral) \
V(OptionalChain) \
V(Property) \
V(ResolvedProperty) \
V(Spread) \
......@@ -1595,6 +1596,20 @@ class VariableProxy final : public Expression {
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.
// Otherwise, the assignment is to a non-property (a global, a local slot, a
// parameter slot, or a destructuring pattern).
......@@ -1612,6 +1627,10 @@ enum AssignType {
class Property final : public Expression {
public:
bool is_optional_chain_link() const {
return IsOptionalChainLinkField::decode(bit_field_);
}
bool IsValidReferenceExpression() const { return true; }
Expression* obj() const { return obj_; }
......@@ -1653,10 +1672,13 @@ class Property final : public Expression {
private:
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) {
bit_field_ |= IsOptionalChainLinkField::encode(optional_chain);
}
using IsOptionalChainLinkField = Expression::NextBitField<bool, 1>;
Expression* obj_;
Expression* key_;
};
......@@ -1694,6 +1716,10 @@ class Call final : public Expression {
return IsTaggedTemplateField::decode(bit_field_);
}
bool is_optional_chain_link() const {
return IsOptionalChainLinkField::decode(bit_field_);
}
bool only_last_arg_is_spread() {
return !arguments_.is_empty() && arguments_.last()->IsSpread();
}
......@@ -1726,13 +1752,14 @@ class Call final : public Expression {
Call(Zone* zone, Expression* expression,
const ScopedPtrList<Expression>& arguments, int pos,
PossiblyEval possibly_eval)
PossiblyEval possibly_eval, bool optional_chain)
: Expression(pos, kCall),
expression_(expression),
arguments_(0, nullptr) {
bit_field_ |=
IsPossiblyEvalField::encode(possibly_eval == IS_POSSIBLY_EVAL) |
IsTaggedTemplateField::encode(false);
IsTaggedTemplateField::encode(false) |
IsOptionalChainLinkField::encode(optional_chain);
arguments.CopyTo(&arguments_, zone);
}
......@@ -1743,12 +1770,14 @@ class Call final : public Expression {
expression_(expression),
arguments_(0, nullptr) {
bit_field_ |= IsPossiblyEvalField::encode(false) |
IsTaggedTemplateField::encode(true);
IsTaggedTemplateField::encode(true) |
IsOptionalChainLinkField::encode(false);
arguments.CopyTo(&arguments_, zone);
}
using IsPossiblyEvalField = Expression::NextBitField<bool, 1>;
using IsTaggedTemplateField = IsPossiblyEvalField::Next<bool, 1>;
using IsOptionalChainLinkField = IsTaggedTemplateField::Next<bool, 1>;
Expression* expression_;
ZonePtrList<Expression> arguments_;
......@@ -3040,8 +3069,13 @@ class AstNodeFactory final {
return new (zone_) Variable(variable);
}
Property* NewProperty(Expression* obj, Expression* key, int pos) {
return new (zone_) Property(obj, key, pos);
OptionalChain* NewOptionalChain(Expression* expression) {
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,
......@@ -3052,8 +3086,10 @@ class AstNodeFactory final {
Call* NewCall(Expression* expression,
const ScopedPtrList<Expression>& arguments, int pos,
Call::PossiblyEval possibly_eval = Call::NOT_EVAL) {
return new (zone_) Call(zone_, expression, arguments, pos, possibly_eval);
Call::PossiblyEval possibly_eval = Call::NOT_EVAL,
bool optional_chain = false) {
return new (zone_)
Call(zone_, expression, arguments, pos, possibly_eval, optional_chain);
}
Call* NewTaggedTemplate(Expression* expression,
......
......@@ -342,6 +342,9 @@ void CallPrinter::VisitAwait(Await* node) { Find(node->expression()); }
void CallPrinter::VisitThrow(Throw* node) { Find(node->exception()); }
void CallPrinter::VisitOptionalChain(OptionalChain* node) {
Find(node->expression());
}
void CallPrinter::VisitProperty(Property* node) {
Expression* key = node->key();
......@@ -349,12 +352,18 @@ void CallPrinter::VisitProperty(Property* node) {
if (literal != nullptr &&
literal->BuildValue(isolate_)->IsInternalizedString()) {
Find(node->obj(), true);
if (node->is_optional_chain_link()) {
Print("?");
}
Print(".");
// TODO(adamk): Teach Literal how to print its values without
// allocating on the heap.
PrintLiteral(literal->BuildValue(isolate_), false);
} else {
Find(node->obj(), true);
if (node->is_optional_chain_link()) {
Print("?.");
}
Print("[");
Find(key, true);
Print("]");
......@@ -1272,6 +1281,11 @@ void AstPrinter::VisitThrow(Throw* node) {
Visit(node->exception());
}
void AstPrinter::VisitOptionalChain(OptionalChain* node) {
IndentedScope indent(this, "OPTIONAL_CHAIN", node->position());
Visit(node->expression());
}
void AstPrinter::VisitProperty(Property* node) {
EmbeddedVector<char, 128> buf;
SNPrintF(buf, "PROPERTY");
......
......@@ -575,7 +575,10 @@ namespace internal {
"FinalizationGroup.prototype.register: target and holdings must not be " \
"same") \
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 {
#define TEMPLATE(NAME, STRING) k##NAME,
......
......@@ -206,7 +206,8 @@ DEFINE_IMPLICATION(harmony_import_meta, harmony_dynamic_import)
#define HARMONY_INPROGRESS_BASE(V) \
V(harmony_private_methods, "harmony private methods in class literals") \
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
#define HARMONY_INPROGRESS(V) \
......
......@@ -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_import_meta)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_regexp_sequence)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_optional_chaining)
#ifdef V8_INTL_SUPPORT
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_intl_add_calendar_numbering_system)
......
......@@ -915,6 +915,27 @@ class BytecodeGenerator::IteratorRecord final {
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 {
// A map from property names to getter/setter pairs allocated in the zone that
......@@ -994,6 +1015,7 @@ BytecodeGenerator::BytecodeGenerator(
execution_context_(nullptr),
execution_result_(nullptr),
incoming_new_target_or_generator_(),
optional_chaining_null_labels_(nullptr),
dummy_feedback_slot_(feedback_spec(), FeedbackSlotKind::kCompareOp),
generator_jump_table_(nullptr),
suspend_count_(0),
......@@ -4318,7 +4340,17 @@ void BytecodeGenerator::VisitThrow(Throw* expr) {
}
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);
switch (property_kind) {
case NON_PROPERTY:
UNREACHABLE();
......@@ -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) {
AssignType property_kind = Property::GetAssignType(expr);
if (property_kind != NAMED_SUPER_PROPERTY &&
......@@ -4549,6 +4591,15 @@ void BytecodeGenerator::VisitCall(Call* expr) {
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
// registers.
VisitArguments(expr->arguments(), &args);
......@@ -4810,6 +4861,28 @@ void BytecodeGenerator::VisitDelete(UnaryOperation* unary) {
Register object = VisitForRegisterValue(property->obj());
VisitForAccumulatorValue(property->key());
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() &&
!expr->AsVariableProxy()->is_new_target()) {
// Delete of an unqualified identifier is allowed in sloppy mode but is
......
......@@ -66,6 +66,7 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
class AccumulatorPreservingScope;
class TestResultScope;
class ValueResultScope;
class OptionalChainNullLabelScope;
using ToBooleanMode = BytecodeArrayBuilder::ToBooleanMode;
......@@ -491,6 +492,8 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
Register incoming_new_target_or_generator_;
BytecodeLabels* optional_chaining_null_labels_;
// Dummy feedback slot for compare operations, where we don't care about
// feedback
SharedFeedbackSlot dummy_feedback_slot_;
......
......@@ -62,6 +62,7 @@ ParseInfo::ParseInfo(Isolate* isolate, AccountingAllocator* zone_allocator)
set_allow_natives_syntax(FLAG_allow_natives_syntax);
set_allow_harmony_dynamic_import(FLAG_harmony_dynamic_import);
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);
}
......
......@@ -105,6 +105,8 @@ class V8_EXPORT_PRIVATE ParseInfo {
set_allow_harmony_dynamic_import)
FLAG_ACCESSOR(kAllowHarmonyImportMeta, 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,
set_allow_harmony_private_methods)
FLAG_ACCESSOR(kIsOneshotIIFE, is_oneshot_iife, set_is_oneshot_iife)
......@@ -304,10 +306,11 @@ class V8_EXPORT_PRIVATE ParseInfo {
kAllowHarmonyStaticFields = 1 << 24,
kAllowHarmonyDynamicImport = 1 << 25,
kAllowHarmonyImportMeta = 1 << 26,
kAllowHarmonyPrivateFields = 1 << 27,
kAllowHarmonyPrivateMethods = 1 << 28,
kIsOneshotIIFE = 1 << 29,
kCollectSourcePositions = 1 << 30,
kAllowHarmonyOptionalChaining = 1 << 27,
kAllowHarmonyPrivateFields = 1 << 28,
kAllowHarmonyPrivateMethods = 1 << 29,
kIsOneshotIIFE = 1 << 30,
kCollectSourcePositions = 1 << 31,
};
//------------- Inputs to parsing and scope analysis -----------------------
......
......@@ -286,6 +286,14 @@ class ParserBase {
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_; }
void set_stack_limit(uintptr_t stack_limit) { stack_limit_ = stack_limit; }
......@@ -3075,7 +3083,7 @@ ParserBase<Impl>::ParseLeftHandSideContinuation(ExpressionT result) {
}
if (has_spread) {
result = impl()->SpreadCall(result, args, pos, Call::NOT_EVAL);
result = impl()->SpreadCall(result, args, pos, Call::NOT_EVAL, false);
} else {
result = factory()->NewCall(result, args, pos, Call::NOT_EVAL);
}
......@@ -3086,25 +3094,42 @@ ParserBase<Impl>::ParseLeftHandSideContinuation(ExpressionT result) {
if (!Token::IsPropertyOrCall(peek())) return result;
}
bool optional_chaining = false;
bool is_optional = false;
do {
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 */
case Token::LBRACK: {
Consume(Token::LBRACK);
int pos = position();
AcceptINScope scope(this, true);
ExpressionT index = ParseExpressionCoverGrammar();
result = factory()->NewProperty(result, index, pos);
result = factory()->NewProperty(result, index, pos, is_optional);
Expect(Token::RBRACK);
break;
}
/* Property */
case Token::PERIOD: {
if (is_optional) {
ReportUnexpectedToken(Next());
return impl()->FailureExpression();
}
Consume(Token::PERIOD);
int pos = position();
ExpressionT key = ParsePropertyOrPrivatePropertyName();
result = factory()->NewProperty(result, key, pos);
result = factory()->NewProperty(result, key, pos, is_optional);
break;
}
......@@ -3149,22 +3174,39 @@ ParserBase<Impl>::ParseLeftHandSideContinuation(ExpressionT result) {
CheckPossibleEvalCall(result, scope());
if (has_spread) {
result = impl()->SpreadCall(result, args, pos, is_possibly_eval);
result = impl()->SpreadCall(result, args, pos, is_possibly_eval,
is_optional);
} else {
result = factory()->NewCall(result, args, pos, is_possibly_eval);
result = factory()->NewCall(result, args, pos, is_possibly_eval,
is_optional);
}
fni_.RemoveLastFunction();
break;
}
/* Call */
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()));
result = ParseTemplateLiteral(result, position(), true);
break;
}
} while (Token::IsPropertyOrCall(peek()));
is_optional = false;
} while (is_optional || Token::IsPropertyOrCall(peek()));
if (optional_chaining) return factory()->NewOptionalChain(result);
return result;
}
......@@ -3226,6 +3268,13 @@ ParserBase<Impl>::ParseMemberWithPresentNewPrefixesExpression() {
// The expression can still continue with . or [ after the arguments.
return ParseMemberExpressionContinuation(result);
}
if (peek() == Token::QUESTION_PERIOD) {
impl()->ReportMessageAt(scanner()->peek_location(),
MessageTemplate::kOptionalChainingNoNew);
return impl()->FailureExpression();
}
// NewExpression without arguments.
ExpressionListT args(pointer_buffer());
return factory()->NewCallNew(result, args, new_pos);
......@@ -3348,6 +3397,11 @@ typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParseSuperExpression(
impl()->ReportMessage(MessageTemplate::kUnexpectedPrivateField);
return impl()->FailureExpression();
}
if (peek() == Token::QUESTION_PERIOD) {
Consume(Token::QUESTION_PERIOD);
impl()->ReportMessage(MessageTemplate::kOptionalChainingNoSuper);
return impl()->FailureExpression();
}
scope->RecordSuperPropertyUsage();
UseThis();
return impl()->NewSuperPropertyReference(pos);
......
......@@ -424,6 +424,7 @@ Parser::Parser(ParseInfo* info)
set_allow_natives(info->allow_natives_syntax());
set_allow_harmony_dynamic_import(info->allow_harmony_dynamic_import());
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());
for (int feature = 0; feature < v8::Isolate::kUseCounterFeatureCount;
++feature) {
......@@ -3162,10 +3163,12 @@ ArrayLiteral* Parser::ArrayLiteralFromListWithSpread(
Expression* Parser::SpreadCall(Expression* function,
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.
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());
......@@ -3180,8 +3183,9 @@ Expression* Parser::SpreadCall(Expression* function,
VariableProxy* obj = factory()->NewVariableProxy(temp);
Assignment* assign_obj = factory()->NewAssignment(
Token::ASSIGN, obj, function->AsProperty()->obj(), kNoSourcePosition);
function = factory()->NewProperty(
assign_obj, function->AsProperty()->key(), kNoSourcePosition);
function =
factory()->NewProperty(assign_obj, function->AsProperty()->key(),
kNoSourcePosition, optional_chain);
args.Add(function);
obj = factory()->NewVariableProxy(temp);
args.Add(obj);
......
......@@ -483,7 +483,8 @@ class V8_EXPORT_PRIVATE Parser : public NON_EXPORTED_BASE(ParserBase<Parser>) {
const ScopedPtrList<Expression>& list);
Expression* SpreadCall(Expression* function,
const ScopedPtrList<Expression>& args, int pos,
Call::PossiblyEval is_possibly_eval);
Call::PossiblyEval is_possibly_eval,
bool optional_chain);
Expression* SpreadCallNew(Expression* function,
const ScopedPtrList<Expression>& args, int pos);
Expression* RewriteSuperCall(Expression* call_expression);
......
......@@ -565,8 +565,13 @@ class PreParserFactory {
return PreParserExpression::Default();
}
PreParserExpression NewOptionalChain(const PreParserExpression& expr) {
return PreParserExpression::Default();
}
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 (obj.IsThis()) {
return PreParserExpression::ThisPrivateReference();
......@@ -625,9 +630,10 @@ class PreParserFactory {
int pos) {
return PreParserExpression::Default();
}
PreParserExpression NewCall(
PreParserExpression expression, const PreParserExpressionList& arguments,
int pos, Call::PossiblyEval possibly_eval = Call::NOT_EVAL) {
PreParserExpression NewCall(PreParserExpression expression,
const PreParserExpressionList& arguments, int pos,
Call::PossiblyEval possibly_eval = Call::NOT_EVAL,
bool optional_chain = false) {
if (possibly_eval == Call::IS_POSSIBLY_EVAL) {
DCHECK(expression.IsIdentifier() && expression.AsIdentifier().IsEval());
return PreParserExpression::CallEval();
......@@ -1052,7 +1058,8 @@ class PreParser : public ParserBase<PreParser> {
V8_INLINE PreParserExpression SpreadCall(const PreParserExpression& function,
const PreParserExpressionList& args,
int pos,
Call::PossiblyEval possibly_eval);
Call::PossiblyEval possibly_eval,
bool optional_chain);
V8_INLINE PreParserExpression
SpreadCallNew(const PreParserExpression& function,
const PreParserExpressionList& args, int pos);
......@@ -1692,8 +1699,9 @@ class PreParser : public ParserBase<PreParser> {
PreParserExpression PreParser::SpreadCall(const PreParserExpression& function,
const PreParserExpressionList& args,
int pos,
Call::PossiblyEval possibly_eval) {
return factory()->NewCall(function, args, pos, possibly_eval);
Call::PossiblyEval possibly_eval,
bool optional_chain) {
return factory()->NewCall(function, args, pos, possibly_eval, optional_chain);
}
PreParserExpression PreParser::SpreadCallNew(
......
......@@ -354,7 +354,6 @@ V8_INLINE Token::Value Scanner::ScanSingleToken() {
case Token::RBRACE:
case Token::LBRACK:
case Token::RBRACK:
case Token::CONDITIONAL:
case Token::COLON:
case Token::SEMICOLON:
case Token::COMMA:
......@@ -363,6 +362,16 @@ V8_INLINE Token::Value Scanner::ScanSingleToken() {
// One character tokens.
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:
return ScanString();
......
......@@ -92,6 +92,7 @@ bool Scanner::BookmarkScope::HasBeenApplied() const {
Scanner::Scanner(Utf16CharacterStream* source, bool is_module)
: source_(source),
found_html_comment_(false),
allow_harmony_optional_chaining_(false),
is_module_(is_module),
octal_pos_(Location::invalid()),
octal_message_(MessageTemplate::kNone) {
......
......@@ -406,6 +406,14 @@ class V8_EXPORT_PRIVATE Scanner {
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_; }
// If the next characters in the stream are "#!", the line is skipped.
......@@ -713,6 +721,9 @@ class V8_EXPORT_PRIVATE Scanner {
// Whether this scanner encountered an HTML comment.
bool found_html_comment_;
// Harmony flags to allow ESNext features.
bool allow_harmony_optional_chaining_;
const bool is_module_;
// Values parsed from magic comments.
......
......@@ -65,6 +65,7 @@ namespace internal {
T(LBRACK, "[", 0) \
/* END Property */ \
/* END Member */ \
T(QUESTION_PERIOD, "?.", 0) \
T(LPAREN, "(", 0) \
/* END PropertyOrCall */ \
T(RPAREN, ")", 0) \
......
......@@ -390,6 +390,7 @@ bool TokenIsPropertyOrCall(Token::Value token) {
case Token::TEMPLATE_SPAN:
case Token::TEMPLATE_TAIL:
case Token::PERIOD:
case Token::QUESTION_PERIOD:
case Token::LBRACK:
case Token::LPAREN:
return true;
......@@ -1529,6 +1530,7 @@ enum ParserFlag {
kAllowHarmonyPrivateMethods,
kAllowHarmonyDynamicImport,
kAllowHarmonyImportMeta,
kAllowHarmonyOptionalChaining,
};
enum ParserSyncTestResult {
......@@ -1542,6 +1544,8 @@ void SetGlobalFlags(base::EnumSet<ParserFlag> flags) {
i::FLAG_harmony_private_methods = flags.contains(kAllowHarmonyPrivateMethods);
i::FLAG_harmony_dynamic_import = flags.contains(kAllowHarmonyDynamicImport);
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) {
......@@ -1552,6 +1556,8 @@ void SetParserFlags(i::PreParser* parser, base::EnumSet<ParserFlag> flags) {
flags.contains(kAllowHarmonyDynamicImport));
parser->set_allow_harmony_import_meta(
flags.contains(kAllowHarmonyImportMeta));
parser->set_allow_harmony_optional_chaining(
flags.contains(kAllowHarmonyOptionalChaining));
}
void TestParserSyncWithFlags(i::Handle<i::String> source,
......@@ -1973,6 +1979,36 @@ TEST(NumericSeparatorUnicodeEscapeSequencesErrors) {
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) {
// 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
......
// 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