Commit 2aab10f5 authored by bakkot's avatar bakkot Committed by Commit bot

[parser] Simplify parse-time function name inference for properties

Move the code to perform function name inference for properties into
parsing the properties themselves, instead of the containing object.

This allows us to avoid unnecessary calls when parsing shorthand
properties and methods and simplifies the logic in the remaining cases.

Also fixes an edge case bug: inferring the name of the getter in
`class { static get constructor(){} }`.

Review-Url: https://codereview.chromium.org/2313723005
Cr-Commit-Position: refs/heads/master@{#39222}
parent 11f74547
...@@ -1103,10 +1103,9 @@ class ParserBase { ...@@ -1103,10 +1103,9 @@ class ParserBase {
ExpressionT ParseObjectLiteral(bool* ok); ExpressionT ParseObjectLiteral(bool* ok);
ClassLiteralPropertyT ParseClassPropertyDefinition( ClassLiteralPropertyT ParseClassPropertyDefinition(
ClassLiteralChecker* checker, bool has_extends, bool* is_computed_name, ClassLiteralChecker* checker, bool has_extends, bool* is_computed_name,
bool* has_seen_constructor, IdentifierT* name, bool* ok); bool* has_seen_constructor, bool* ok);
ObjectLiteralPropertyT ParseObjectPropertyDefinition( ObjectLiteralPropertyT ParseObjectPropertyDefinition(
ObjectLiteralChecker* checker, bool* is_computed_name, IdentifierT* name, ObjectLiteralChecker* checker, bool* is_computed_name, bool* ok);
bool* ok);
ExpressionListT ParseArguments(Scanner::Location* first_spread_pos, ExpressionListT ParseArguments(Scanner::Location* first_spread_pos,
bool maybe_arrow, bool* ok); bool maybe_arrow, bool* ok);
ExpressionListT ParseArguments(Scanner::Location* first_spread_pos, ExpressionListT ParseArguments(Scanner::Location* first_spread_pos,
...@@ -2009,7 +2008,7 @@ ParserBase<Impl>::ParseClassPropertyDefinition(ClassLiteralChecker* checker, ...@@ -2009,7 +2008,7 @@ ParserBase<Impl>::ParseClassPropertyDefinition(ClassLiteralChecker* checker,
bool has_extends, bool has_extends,
bool* is_computed_name, bool* is_computed_name,
bool* has_seen_constructor, bool* has_seen_constructor,
IdentifierT* name, bool* ok) { bool* ok) {
DCHECK(has_seen_constructor != nullptr); DCHECK(has_seen_constructor != nullptr);
bool is_get = false; bool is_get = false;
bool is_set = false; bool is_set = false;
...@@ -2020,22 +2019,23 @@ ParserBase<Impl>::ParseClassPropertyDefinition(ClassLiteralChecker* checker, ...@@ -2020,22 +2019,23 @@ ParserBase<Impl>::ParseClassPropertyDefinition(ClassLiteralChecker* checker,
Token::Value name_token = peek(); Token::Value name_token = peek();
IdentifierT name = impl()->EmptyIdentifier();
ExpressionT name_expression; ExpressionT name_expression;
if (name_token == Token::STATIC) { if (name_token == Token::STATIC) {
Consume(Token::STATIC); Consume(Token::STATIC);
if (peek() == Token::LPAREN) { if (peek() == Token::LPAREN) {
kind = PropertyKind::kMethodProperty; kind = PropertyKind::kMethodProperty;
*name = impl()->GetSymbol(); // TODO(bakkot) specialize on 'static' name = impl()->GetSymbol(); // TODO(bakkot) specialize on 'static'
name_expression = factory()->NewStringLiteral(*name, position()); name_expression = factory()->NewStringLiteral(name, position());
} else { } else {
is_static = true; is_static = true;
name_expression = ParsePropertyName( name_expression = ParsePropertyName(
name, &kind, &is_generator, &is_get, &is_set, &is_async, &name, &kind, &is_generator, &is_get, &is_set, &is_async,
is_computed_name, CHECK_OK_CUSTOM(EmptyClassLiteralProperty)); is_computed_name, CHECK_OK_CUSTOM(EmptyClassLiteralProperty));
} }
} else { } else {
name_expression = ParsePropertyName( name_expression = ParsePropertyName(
name, &kind, &is_generator, &is_get, &is_set, &is_async, &name, &kind, &is_generator, &is_get, &is_set, &is_async,
is_computed_name, CHECK_OK_CUSTOM(EmptyClassLiteralProperty)); is_computed_name, CHECK_OK_CUSTOM(EmptyClassLiteralProperty));
} }
...@@ -2064,14 +2064,14 @@ ParserBase<Impl>::ParseClassPropertyDefinition(ClassLiteralChecker* checker, ...@@ -2064,14 +2064,14 @@ ParserBase<Impl>::ParseClassPropertyDefinition(ClassLiteralChecker* checker,
: is_async ? FunctionKind::kAsyncConciseMethod : is_async ? FunctionKind::kAsyncConciseMethod
: FunctionKind::kConciseMethod; : FunctionKind::kConciseMethod;
if (!is_static && impl()->IsConstructor(*name)) { if (!is_static && impl()->IsConstructor(name)) {
*has_seen_constructor = true; *has_seen_constructor = true;
kind = has_extends ? FunctionKind::kSubclassConstructor kind = has_extends ? FunctionKind::kSubclassConstructor
: FunctionKind::kBaseConstructor; : FunctionKind::kBaseConstructor;
} }
ExpressionT value = impl()->ParseFunctionLiteral( ExpressionT value = impl()->ParseFunctionLiteral(
*name, scanner()->location(), kSkipFunctionNameCheck, kind, name, scanner()->location(), kSkipFunctionNameCheck, kind,
kNoSourcePosition, FunctionLiteral::kAccessorOrMethod, kNoSourcePosition, FunctionLiteral::kAccessorOrMethod,
language_mode(), CHECK_OK_CUSTOM(EmptyClassLiteralProperty)); language_mode(), CHECK_OK_CUSTOM(EmptyClassLiteralProperty));
...@@ -2091,17 +2091,21 @@ ParserBase<Impl>::ParseClassPropertyDefinition(ClassLiteralChecker* checker, ...@@ -2091,17 +2091,21 @@ ParserBase<Impl>::ParseClassPropertyDefinition(ClassLiteralChecker* checker,
// Runtime_DefineAccessorPropertyUnchecked and since we can determine // Runtime_DefineAccessorPropertyUnchecked and since we can determine
// this statically we can skip the extra runtime check. // this statically we can skip the extra runtime check.
name_expression = name_expression =
factory()->NewStringLiteral(*name, name_expression->position()); factory()->NewStringLiteral(name, name_expression->position());
} }
FunctionKind kind = is_get ? FunctionKind::kGetterFunction FunctionKind kind = is_get ? FunctionKind::kGetterFunction
: FunctionKind::kSetterFunction; : FunctionKind::kSetterFunction;
ExpressionT value = impl()->ParseFunctionLiteral( FunctionLiteralT value = impl()->ParseFunctionLiteral(
*name, scanner()->location(), kSkipFunctionNameCheck, kind, name, scanner()->location(), kSkipFunctionNameCheck, kind,
kNoSourcePosition, FunctionLiteral::kAccessorOrMethod, kNoSourcePosition, FunctionLiteral::kAccessorOrMethod,
language_mode(), CHECK_OK_CUSTOM(EmptyClassLiteralProperty)); language_mode(), CHECK_OK_CUSTOM(EmptyClassLiteralProperty));
if (!*is_computed_name) {
impl()->AddAccessorPrefixToFunctionName(is_get, value, name);
}
return factory()->NewClassLiteralProperty( return factory()->NewClassLiteralProperty(
name_expression, value, name_expression, value,
is_get ? ClassLiteralProperty::GETTER : ClassLiteralProperty::SETTER, is_get ? ClassLiteralProperty::GETTER : ClassLiteralProperty::SETTER,
...@@ -2121,20 +2125,21 @@ template <typename Impl> ...@@ -2121,20 +2125,21 @@ template <typename Impl>
typename ParserBase<Impl>::ObjectLiteralPropertyT typename ParserBase<Impl>::ObjectLiteralPropertyT
ParserBase<Impl>::ParseObjectPropertyDefinition(ObjectLiteralChecker* checker, ParserBase<Impl>::ParseObjectPropertyDefinition(ObjectLiteralChecker* checker,
bool* is_computed_name, bool* is_computed_name,
IdentifierT* name, bool* ok) { bool* ok) {
bool is_get = false; bool is_get = false;
bool is_set = false; bool is_set = false;
bool is_generator = false; bool is_generator = false;
bool is_async = false; bool is_async = false;
PropertyKind kind = PropertyKind::kNotSet; PropertyKind kind = PropertyKind::kNotSet;
IdentifierT name = impl()->EmptyIdentifier();
Token::Value name_token = peek(); Token::Value name_token = peek();
int next_beg_pos = scanner()->peek_location().beg_pos; int next_beg_pos = scanner()->peek_location().beg_pos;
int next_end_pos = scanner()->peek_location().end_pos; int next_end_pos = scanner()->peek_location().end_pos;
ExpressionT name_expression = ParsePropertyName( ExpressionT name_expression = ParsePropertyName(
name, &kind, &is_generator, &is_get, &is_set, &is_async, is_computed_name, &name, &kind, &is_generator, &is_get, &is_set, &is_async,
CHECK_OK_CUSTOM(EmptyObjectLiteralProperty)); is_computed_name, CHECK_OK_CUSTOM(EmptyObjectLiteralProperty));
switch (kind) { switch (kind) {
case PropertyKind::kValueProperty: { case PropertyKind::kValueProperty: {
...@@ -2149,8 +2154,14 @@ ParserBase<Impl>::ParseObjectPropertyDefinition(ObjectLiteralChecker* checker, ...@@ -2149,8 +2154,14 @@ ParserBase<Impl>::ParseObjectPropertyDefinition(ObjectLiteralChecker* checker,
true, CHECK_OK_CUSTOM(EmptyObjectLiteralProperty)); true, CHECK_OK_CUSTOM(EmptyObjectLiteralProperty));
CheckDestructuringElement(value, beg_pos, scanner()->location().end_pos); CheckDestructuringElement(value, beg_pos, scanner()->location().end_pos);
return factory()->NewObjectLiteralProperty(name_expression, value, ObjectLiteralPropertyT result = factory()->NewObjectLiteralProperty(
*is_computed_name); name_expression, value, *is_computed_name);
if (!*is_computed_name) {
impl()->SetFunctionNameFromPropertyName(result, name);
}
return result;
} }
case PropertyKind::kShorthandProperty: { case PropertyKind::kShorthandProperty: {
...@@ -2178,7 +2189,7 @@ ParserBase<Impl>::ParseObjectPropertyDefinition(ObjectLiteralChecker* checker, ...@@ -2178,7 +2189,7 @@ ParserBase<Impl>::ParseObjectPropertyDefinition(ObjectLiteralChecker* checker,
scanner()->location()); scanner()->location());
} }
if (impl()->IsEvalOrArguments(*name) && is_strict(language_mode())) { if (impl()->IsEvalOrArguments(name) && is_strict(language_mode())) {
classifier()->RecordBindingPatternError( classifier()->RecordBindingPatternError(
scanner()->location(), MessageTemplate::kStrictEvalArguments); scanner()->location(), MessageTemplate::kStrictEvalArguments);
} }
...@@ -2194,7 +2205,7 @@ ParserBase<Impl>::ParseObjectPropertyDefinition(ObjectLiteralChecker* checker, ...@@ -2194,7 +2205,7 @@ ParserBase<Impl>::ParseObjectPropertyDefinition(ObjectLiteralChecker* checker,
MessageTemplate::kAwaitBindingIdentifier); MessageTemplate::kAwaitBindingIdentifier);
} }
ExpressionT lhs = ExpressionT lhs =
impl()->ExpressionFromIdentifier(*name, next_beg_pos, next_end_pos); impl()->ExpressionFromIdentifier(name, next_beg_pos, next_end_pos);
CheckDestructuringElement(lhs, next_beg_pos, next_end_pos); CheckDestructuringElement(lhs, next_beg_pos, next_end_pos);
ExpressionT value; ExpressionT value;
...@@ -2237,7 +2248,7 @@ ParserBase<Impl>::ParseObjectPropertyDefinition(ObjectLiteralChecker* checker, ...@@ -2237,7 +2248,7 @@ ParserBase<Impl>::ParseObjectPropertyDefinition(ObjectLiteralChecker* checker,
: FunctionKind::kConciseMethod; : FunctionKind::kConciseMethod;
ExpressionT value = impl()->ParseFunctionLiteral( ExpressionT value = impl()->ParseFunctionLiteral(
*name, scanner()->location(), kSkipFunctionNameCheck, kind, name, scanner()->location(), kSkipFunctionNameCheck, kind,
kNoSourcePosition, FunctionLiteral::kAccessorOrMethod, kNoSourcePosition, FunctionLiteral::kAccessorOrMethod,
language_mode(), CHECK_OK_CUSTOM(EmptyObjectLiteralProperty)); language_mode(), CHECK_OK_CUSTOM(EmptyObjectLiteralProperty));
...@@ -2259,17 +2270,21 @@ ParserBase<Impl>::ParseObjectPropertyDefinition(ObjectLiteralChecker* checker, ...@@ -2259,17 +2270,21 @@ ParserBase<Impl>::ParseObjectPropertyDefinition(ObjectLiteralChecker* checker,
// Runtime_DefineAccessorPropertyUnchecked and since we can determine // Runtime_DefineAccessorPropertyUnchecked and since we can determine
// this statically we can skip the extra runtime check. // this statically we can skip the extra runtime check.
name_expression = name_expression =
factory()->NewStringLiteral(*name, name_expression->position()); factory()->NewStringLiteral(name, name_expression->position());
} }
FunctionKind kind = is_get ? FunctionKind::kGetterFunction FunctionKind kind = is_get ? FunctionKind::kGetterFunction
: FunctionKind::kSetterFunction; : FunctionKind::kSetterFunction;
ExpressionT value = impl()->ParseFunctionLiteral( FunctionLiteralT value = impl()->ParseFunctionLiteral(
*name, scanner()->location(), kSkipFunctionNameCheck, kind, name, scanner()->location(), kSkipFunctionNameCheck, kind,
kNoSourcePosition, FunctionLiteral::kAccessorOrMethod, kNoSourcePosition, FunctionLiteral::kAccessorOrMethod,
language_mode(), CHECK_OK_CUSTOM(EmptyObjectLiteralProperty)); language_mode(), CHECK_OK_CUSTOM(EmptyObjectLiteralProperty));
if (!*is_computed_name) {
impl()->AddAccessorPrefixToFunctionName(is_get, value, name);
}
return factory()->NewObjectLiteralProperty( return factory()->NewObjectLiteralProperty(
name_expression, value, is_get ? ObjectLiteralProperty::GETTER name_expression, value, is_get ? ObjectLiteralProperty::GETTER
: ObjectLiteralProperty::SETTER, : ObjectLiteralProperty::SETTER,
...@@ -2303,9 +2318,8 @@ typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParseObjectLiteral( ...@@ -2303,9 +2318,8 @@ typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParseObjectLiteral(
FuncNameInferrer::State fni_state(fni_); FuncNameInferrer::State fni_state(fni_);
bool is_computed_name = false; bool is_computed_name = false;
IdentifierT name = impl()->EmptyIdentifier(); ObjectLiteralPropertyT property =
ObjectLiteralPropertyT property = ParseObjectPropertyDefinition( ParseObjectPropertyDefinition(&checker, &is_computed_name, CHECK_OK);
&checker, &is_computed_name, &name, CHECK_OK);
if (is_computed_name) { if (is_computed_name) {
has_computed_names = true; has_computed_names = true;
...@@ -2323,8 +2337,6 @@ typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParseObjectLiteral( ...@@ -2323,8 +2337,6 @@ typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParseObjectLiteral(
} }
if (fni_ != nullptr) fni_->Infer(); if (fni_ != nullptr) fni_->Infer();
impl()->SetFunctionNameFromPropertyName(property, name);
} }
Expect(Token::RBRACE, CHECK_OK); Expect(Token::RBRACE, CHECK_OK);
......
...@@ -4249,10 +4249,9 @@ Expression* Parser::ParseClassLiteral(const AstRawString* name, ...@@ -4249,10 +4249,9 @@ Expression* Parser::ParseClassLiteral(const AstRawString* name,
bool is_computed_name = false; // Classes do not care about computed bool is_computed_name = false; // Classes do not care about computed
// property names here. // property names here.
ExpressionClassifier property_classifier(this); ExpressionClassifier property_classifier(this);
const AstRawString* property_name = nullptr; ClassLiteral::Property* property =
ClassLiteral::Property* property = ParseClassPropertyDefinition( ParseClassPropertyDefinition(&checker, has_extends, &is_computed_name,
&checker, has_extends, &is_computed_name, &has_seen_constructor, &has_seen_constructor, CHECK_OK);
&property_name, CHECK_OK);
RewriteNonPattern(CHECK_OK); RewriteNonPattern(CHECK_OK);
impl()->AccumulateFormalParameterContainmentErrors(); impl()->AccumulateFormalParameterContainmentErrors();
...@@ -4267,10 +4266,6 @@ Expression* Parser::ParseClassLiteral(const AstRawString* name, ...@@ -4267,10 +4266,6 @@ Expression* Parser::ParseClassLiteral(const AstRawString* name,
DCHECK_NOT_NULL(fni_); DCHECK_NOT_NULL(fni_);
fni_->Infer(); fni_->Infer();
if (property_name != ast_value_factory()->constructor_string()) {
SetFunctionNameFromPropertyName(property, property_name);
}
} }
Expect(Token::RBRACE, CHECK_OK); Expect(Token::RBRACE, CHECK_OK);
...@@ -5209,63 +5204,31 @@ void Parser::QueueNonPatternForRewriting(Expression* expr, bool* ok) { ...@@ -5209,63 +5204,31 @@ void Parser::QueueNonPatternForRewriting(Expression* expr, bool* ok) {
function_state_->AddNonPatternForRewriting(expr, ok); function_state_->AddNonPatternForRewriting(expr, ok);
} }
void Parser::AddAccessorPrefixToFunctionName(bool is_get,
FunctionLiteral* function,
const AstRawString* name) {
DCHECK_NOT_NULL(name);
const AstRawString* prefix = is_get ? ast_value_factory()->get_space_string()
: ast_value_factory()->set_space_string();
function->set_raw_name(ast_value_factory()->NewConsString(prefix, name));
}
void Parser::SetFunctionNameFromPropertyName(ObjectLiteralProperty* property, void Parser::SetFunctionNameFromPropertyName(ObjectLiteralProperty* property,
const AstRawString* name) { const AstRawString* name) {
Expression* value = property->value(); DCHECK(property->kind() != ObjectLiteralProperty::GETTER);
DCHECK(property->kind() != ObjectLiteralProperty::SETTER);
// Computed name setting must happen at runtime. // Computed name setting must happen at runtime.
if (property->is_computed_name()) return; DCHECK(!property->is_computed_name());
// Getter and setter names are handled here because their names
// change in ES2015, even though they are not anonymous.
auto function = value->AsFunctionLiteral();
if (function != nullptr) {
bool is_getter = property->kind() == ObjectLiteralProperty::GETTER;
bool is_setter = property->kind() == ObjectLiteralProperty::SETTER;
if (is_getter || is_setter) {
DCHECK_NOT_NULL(name);
const AstRawString* prefix =
is_getter ? ast_value_factory()->get_space_string()
: ast_value_factory()->set_space_string();
function->set_raw_name(ast_value_factory()->NewConsString(prefix, name));
return;
}
}
// Ignore "__proto__" as a name when it's being used to set the [[Prototype]] // Ignore "__proto__" as a name when it's being used to set the [[Prototype]]
// of an object literal. // of an object literal.
if (property->kind() == ObjectLiteralProperty::PROTOTYPE) return; if (property->kind() == ObjectLiteralProperty::PROTOTYPE) return;
DCHECK(!value->IsAnonymousFunctionDefinition() ||
property->kind() == ObjectLiteralProperty::COMPUTED);
SetFunctionName(value, name);
}
void Parser::SetFunctionNameFromPropertyName(ClassLiteralProperty* property,
const AstRawString* name) {
// TODO(bakkot) move this logic into Parse{Object,Class}PropertyDefinition and
// clean it up.
Expression* value = property->value(); Expression* value = property->value();
// Computed name setting must happen at runtime. DCHECK(!value->IsAnonymousFunctionDefinition() ||
if (property->is_computed_name()) return; property->kind() == ObjectLiteralProperty::COMPUTED);
// Getter and setter names are handled here because their names
// change in ES2015, even though they are not anonymous.
auto function = value->AsFunctionLiteral();
DCHECK_NOT_NULL(function);
bool is_getter = property->kind() == ClassLiteralProperty::GETTER;
bool is_setter = property->kind() == ClassLiteralProperty::SETTER;
if (is_getter || is_setter) {
DCHECK_NOT_NULL(name);
const AstRawString* prefix = is_getter
? ast_value_factory()->get_space_string()
: ast_value_factory()->set_space_string();
function->set_raw_name(ast_value_factory()->NewConsString(prefix, name));
return;
}
SetFunctionName(value, name); SetFunctionName(value, name);
} }
......
...@@ -1017,9 +1017,10 @@ class Parser : public ParserBase<Parser> { ...@@ -1017,9 +1017,10 @@ class Parser : public ParserBase<Parser> {
Expression* ExpressionListToExpression(ZoneList<Expression*>* args); Expression* ExpressionListToExpression(ZoneList<Expression*>* args);
void SetFunctionNameFromPropertyName(ObjectLiteralProperty* property, void AddAccessorPrefixToFunctionName(bool is_get, FunctionLiteral* function,
const AstRawString* name); const AstRawString* name);
void SetFunctionNameFromPropertyName(ClassLiteralProperty* property,
void SetFunctionNameFromPropertyName(ObjectLiteralProperty* property,
const AstRawString* name); const AstRawString* name);
void SetFunctionNameFromIdentifierRef(Expression* value, void SetFunctionNameFromIdentifierRef(Expression* value,
......
...@@ -856,10 +856,9 @@ PreParserExpression PreParser::ParseClassLiteral( ...@@ -856,10 +856,9 @@ PreParserExpression PreParser::ParseClassLiteral(
if (Check(Token::SEMICOLON)) continue; if (Check(Token::SEMICOLON)) continue;
bool is_computed_name = false; // Classes do not care about computed bool is_computed_name = false; // Classes do not care about computed
// property names here. // property names here.
Identifier name;
ExpressionClassifier property_classifier(this); ExpressionClassifier property_classifier(this);
ParseClassPropertyDefinition(&checker, has_extends, &is_computed_name, ParseClassPropertyDefinition(&checker, has_extends, &is_computed_name,
&has_seen_constructor, &name, CHECK_OK); &has_seen_constructor, CHECK_OK);
ValidateExpression(CHECK_OK); ValidateExpression(CHECK_OK);
impl()->AccumulateFormalParameterContainmentErrors(); impl()->AccumulateFormalParameterContainmentErrors();
} }
......
...@@ -1223,6 +1223,9 @@ class PreParser : public ParserBase<PreParser> { ...@@ -1223,6 +1223,9 @@ class PreParser : public ParserBase<PreParser> {
return PreParserExpression::Default(); return PreParserExpression::Default();
} }
V8_INLINE void AddAccessorPrefixToFunctionName(bool is_get,
PreParserExpression function,
PreParserIdentifier name) {}
V8_INLINE void SetFunctionNameFromPropertyName(PreParserExpression property, V8_INLINE void SetFunctionNameFromPropertyName(PreParserExpression property,
PreParserIdentifier name) {} PreParserIdentifier name) {}
V8_INLINE void SetFunctionNameFromIdentifierRef( V8_INLINE void SetFunctionNameFromIdentifierRef(
......
...@@ -73,6 +73,8 @@ ...@@ -73,6 +73,8 @@
static 43() { } static 43() { }
get 44() { } get 44() { }
set 44(val) { } set 44(val) { }
static get constructor() { }
static set constructor(val) { }
}; };
assertEquals('a', C.prototype.a.name); assertEquals('a', C.prototype.a.name);
...@@ -85,6 +87,9 @@ ...@@ -85,6 +87,9 @@
var descriptor = Object.getOwnPropertyDescriptor(C.prototype, '44'); var descriptor = Object.getOwnPropertyDescriptor(C.prototype, '44');
assertEquals('get 44', descriptor.get.name); assertEquals('get 44', descriptor.get.name);
assertEquals('set 44', descriptor.set.name); assertEquals('set 44', descriptor.set.name);
var descriptor = Object.getOwnPropertyDescriptor(C, 'constructor');
assertEquals('get constructor', descriptor.get.name);
assertEquals('set constructor', descriptor.set.name);
})(); })();
(function testComputedProperties() { (function testComputedProperties() {
......
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