Commit 91f08378 authored by Joyee Cheung's avatar Joyee Cheung Committed by V8 LUCI CQ

[class] implement reparsing of class instance member initializers

Previously, since the source code for the synthetic class instance
member initializer function was recorded as the span from the first
initializer to the last initializer, there was no way to reparse the
class and recompile the initializer function. It was working for
most use cases because the code for the initializer function was
generated eagarly and it was usually alive as long as the class was
alive, so the initializer wouldn't normally be lazily parsed. This
didn't work, however, when the class was snapshotted with
v8::SnapshotCreator::FunctionCodeHandling::kClear,
becuase then we needed to recompile the initializer when the class
was instantiated. This patch implements the reparsing so that
these classes can work with FunctionCodeHandling::kClear.

This patch refactors ParserBase::ParseClassLiteral() so that we can
reuse it for both parsing the class body normally and reparsing it
to collect initializers. When reparsing the synthetic initializer
function, we rewind the scanner to the beginning of the class, and
parse the class body to collect the initializers. During the
reparsing, field initializers are parsed with the full parser while
methods of the class are pre-parsed.

A few notable changes:

- Extended the source range of the initializer function to cover the
  entire class so that we can rewind the scanner to parse the class
  body to collect initializers (previously, it starts from the first
  field initializer and ends at the last initializer). This resulted
  some expectation changes in the debugger tests, though the
  initializers remain debuggable.
- A temporary ClassScope is created during reparsing. After the class
  is reparsed, we use the information from the ScopeInfo to update
  the allocated indices of the variables in the ClassScope.

Bug: v8:10704
Change-Id: Ifb6431a1447d8844f2a548283d59158742fe9027
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2988830Reviewed-by: 's avatarLeszek Swirski <leszeks@chromium.org>
Reviewed-by: 's avatarToon Verwaest <verwaest@chromium.org>
Commit-Queue: Joyee Cheung <joyee@igalia.com>
Cr-Commit-Position: refs/heads/main@{#78299}
parent c8f651b8
......@@ -521,6 +521,15 @@ template EXPORT_TEMPLATE_DEFINE(V8_EXPORT_PRIVATE)
DeclarationScope* script_scope, AstValueFactory* ast_value_factory,
DeserializationMode deserialization_mode);
#ifdef DEBUG
bool Scope::IsReparsedMemberInitializerScope() const {
return is_declaration_scope() &&
IsClassMembersInitializerFunction(
AsDeclarationScope()->function_kind()) &&
outer_scope()->AsClassScope()->is_reparsed_class_scope();
}
#endif
DeclarationScope* Scope::AsDeclarationScope() {
DCHECK(is_declaration_scope());
return static_cast<DeclarationScope*>(this);
......@@ -664,8 +673,10 @@ bool DeclarationScope::Analyze(ParseInfo* info) {
// We are compiling one of four cases:
// 1) top-level code,
// 2) a function/eval/module on the top-level
// 3) a function/eval in a scope that was already resolved.
// 4) a class member initializer function scope
// 3) 4 function/eval in a scope that was already resolved.
DCHECK(scope->is_script_scope() || scope->outer_scope()->is_script_scope() ||
scope->IsReparsedMemberInitializerScope() ||
scope->outer_scope()->already_resolved_);
// The outer scope is never lazy.
......@@ -1893,6 +1904,8 @@ void Scope::Print(int n) {
if (scope->needs_private_name_context_chain_recalc()) {
Indent(n1, "// needs #-name context chain recalc\n");
}
Indent(n1, "// ");
PrintF("%s\n", FunctionKind2String(scope->function_kind()));
}
if (num_stack_slots_ > 0) {
Indent(n1, "// ");
......@@ -2682,6 +2695,55 @@ bool IsComplementaryAccessorPair(VariableMode a, VariableMode b) {
}
}
void ClassScope::ReplaceReparsedClassScope(Isolate* isolate,
AstValueFactory* ast_value_factory,
ClassScope* old_scope) {
DCHECK_EQ(outer_scope_, old_scope->outer_scope());
Scope* outer = outer_scope_;
outer->RemoveInnerScope(old_scope);
// The outer scope should only have this deserialized inner scope,
// otherwise we have to update the sibling scopes.
DCHECK_EQ(outer->inner_scope_, this);
DCHECK_NULL(sibling_);
DCHECK_NULL(old_scope->inner_scope_);
Handle<ScopeInfo> scope_info = old_scope->scope_info_;
DCHECK(!scope_info.is_null());
DCHECK(!scope_info->IsEmpty());
// Restore variable allocation results for context-allocated variables in
// the class scope from ScopeInfo, so that we don't need to run
// resolution and allocation on these variables again when generating
// code for the initializer function.
int context_local_count = scope_info->ContextLocalCount();
int context_header_length = scope_info->ContextHeaderLength();
DisallowGarbageCollection no_gc;
for (int i = 0; i < context_local_count; ++i) {
int slot_index = context_header_length + i;
DCHECK_LT(slot_index, scope_info->ContextLength());
String name = scope_info->ContextLocalName(i);
const AstRawString* string = ast_value_factory->GetString(
name, SharedStringAccessGuardIfNeeded(isolate));
Variable* var = nullptr;
var = string->IsPrivateName() ? LookupLocalPrivateName(string)
: LookupLocal(string);
DCHECK_NOT_NULL(var);
var->AllocateTo(VariableLocation::CONTEXT, slot_index);
}
scope_info_ = scope_info;
// Set this bit so that DelcarationScope::Analyze recognizes
// the reparsed instance member initializer scope.
#ifdef DEBUG
is_reparsed_class_scope_ = true;
#endif
}
Variable* ClassScope::DeclarePrivateName(const AstRawString* name,
VariableMode mode,
IsStaticFlag is_static_flag,
......
......@@ -424,6 +424,9 @@ class V8_EXPORT_PRIVATE Scope : public NON_EXPORTED_BASE(ZoneObject) {
return num_heap_slots() > 0;
}
#ifdef DEBUG
bool IsReparsedMemberInitializerScope() const;
#endif
// Use Scope::ForEach for depth first traversal of scopes.
// Before:
// void Scope::VisitRecursively() {
......@@ -1474,6 +1477,13 @@ class V8_EXPORT_PRIVATE ClassScope : public Scope {
should_save_class_variable_index_ = true;
}
void ReplaceReparsedClassScope(Isolate* isolate,
AstValueFactory* ast_value_factory,
ClassScope* old_scope);
#ifdef DEBUG
bool is_reparsed_class_scope() const { return is_reparsed_class_scope_; }
#endif
private:
friend class Scope;
friend class PrivateNameScopeIterator;
......@@ -1519,6 +1529,9 @@ class V8_EXPORT_PRIVATE ClassScope : public Scope {
// This is only maintained during reparsing, restored from the
// preparsed data.
bool should_save_class_variable_index_ = false;
#ifdef DEBUG
bool is_reparsed_class_scope_ = false;
#endif
};
// Iterate over the private name scope chain. The iteration proceeds from the
......
......@@ -196,7 +196,7 @@
V(_, dot_home_object_string, ".home_object") \
V(_, dot_result_string, ".result") \
V(_, dot_repl_result_string, ".repl_result") \
V(_, dot_static_home_object_string, "._static_home_object") \
V(_, dot_static_home_object_string, ".static_home_object") \
V(_, dot_string, ".") \
V(_, dot_switch_tag_string, ".switch_tag") \
V(_, dotAll_string, "dotAll") \
......
......@@ -2813,6 +2813,7 @@ void BytecodeGenerator::BuildClassProperty(ClassLiteral::Property* property) {
// Private methods are not initialized in BuildClassProperty.
DCHECK_IMPLIES(property->is_private(),
property->kind() == ClassLiteral::Property::FIELD);
builder()->SetExpressionPosition(property->key());
bool is_literal_store = property->key()->IsPropertyName() &&
!property->is_computed_name() &&
......
......@@ -1238,6 +1238,10 @@ class ParserBase {
Scanner::Location class_name_location,
bool name_is_strict_reserved,
int class_token_pos);
ExpressionT DoParseClassLiteral(ClassScope* class_scope, IdentifierT name,
Scanner::Location class_name_location,
bool is_anonymous, int class_token_pos);
ExpressionT ParseTemplateLiteral(ExpressionT tag, int start, bool tagged);
ExpressionT ParseSuperExpression();
ExpressionT ParseImportExpressions();
......@@ -2522,8 +2526,6 @@ typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParseMemberInitializer(
if (initializer_scope == nullptr) {
initializer_scope = NewFunctionScope(function_kind);
// TODO(gsathya): Make scopes be non contiguous.
initializer_scope->set_start_position(beg_pos);
initializer_scope->SetLanguageMode(LanguageMode::kStrict);
}
......@@ -2538,8 +2540,13 @@ typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParseMemberInitializer(
initializer = factory()->NewUndefinedLiteral(kNoSourcePosition);
}
initializer_scope->set_end_position(end_position());
if (is_static) {
// For the instance initializer, we will save the positions
// later with the positions of the class body so that we can reparse
// it later.
// TODO(joyee): Make scopes be non contiguous.
initializer_scope->set_start_position(beg_pos);
initializer_scope->set_end_position(end_position());
class_info->static_elements_scope = initializer_scope;
class_info->has_static_elements = true;
} else {
......@@ -4681,6 +4688,15 @@ typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParseClassLiteral(
}
ClassScope* class_scope = NewClassScope(scope(), is_anonymous);
return DoParseClassLiteral(class_scope, name, class_name_location,
is_anonymous, class_token_pos);
}
template <typename Impl>
typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::DoParseClassLiteral(
ClassScope* class_scope, IdentifierT name,
Scanner::Location class_name_location, bool is_anonymous,
int class_token_pos) {
BlockState block_state(&scope_, class_scope);
RaiseLanguageMode(LanguageMode::kStrict);
......@@ -4767,6 +4783,12 @@ typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParseClassLiteral(
Expect(Token::RBRACE);
int end_pos = end_position();
class_scope->set_end_position(end_pos);
if (class_info.instance_members_scope != nullptr) {
// Use the positions of the class body for the instance initializer
// function so that we can reparse it later.
class_info.instance_members_scope->set_start_position(class_token_pos);
class_info.instance_members_scope->set_end_position(end_pos);
}
VariableProxy* unresolvable = class_scope->ResolvePrivateNamesPartially();
if (unresolvable != nullptr) {
......
......@@ -863,10 +863,12 @@ void Parser::ParseFunction(Isolate* isolate, ParseInfo* info,
// function is in heritage position. Otherwise the function scope's skip bit
// will be correctly inherited from the outer scope.
ClassScope::HeritageParsingScope heritage(original_scope_->AsClassScope());
result = DoParseFunction(isolate, info, start_position, end_position,
result = DoParseDeserializedFunction(
isolate, shared_info, info, start_position, end_position,
function_literal_id, info->function_name());
} else {
result = DoParseFunction(isolate, info, start_position, end_position,
result = DoParseDeserializedFunction(
isolate, shared_info, info, start_position, end_position,
function_literal_id, info->function_name());
}
MaybeProcessSourceRanges(info, result, stack_limit_);
......@@ -1030,6 +1032,103 @@ FunctionLiteral* Parser::DoParseFunction(Isolate* isolate, ParseInfo* info,
return result;
}
FunctionLiteral* Parser::DoParseDeserializedFunction(
Isolate* isolate, Handle<SharedFunctionInfo> shared_info, ParseInfo* info,
int start_position, int end_position, int function_literal_id,
const AstRawString* raw_name) {
if (flags().function_kind() !=
FunctionKind::kClassMembersInitializerFunction) {
return DoParseFunction(isolate, info, start_position, end_position,
function_literal_id, raw_name);
}
// Reparse the outer class while skipping the non-fields to get a list of
// ClassLiteralProperty and create a InitializeClassMembersStatement for
// the synthetic instance initializer function.
FunctionLiteral* result = ParseClassForInstanceMemberInitialization(
isolate, original_scope_->AsClassScope(), start_position,
function_literal_id);
DCHECK_EQ(result->kind(), FunctionKind::kClassMembersInitializerFunction);
DCHECK_EQ(result->function_literal_id(), function_literal_id);
DCHECK_EQ(result->end_position(), shared_info->EndPosition());
// The private_name_lookup_skips_outer_class bit should be set by
// PostProcessParseResult() during scope analysis later.
return result;
}
FunctionLiteral* Parser::ParseClassForInstanceMemberInitialization(
Isolate* isolate, ClassScope* original_scope, int initializer_pos,
int initializer_id) {
int class_token_pos = initializer_pos;
// Insert a FunctionState with the closest outer Declaration scope
DeclarationScope* nearest_decl_scope = original_scope->GetDeclarationScope();
DCHECK_NOT_NULL(nearest_decl_scope);
FunctionState function_state(&function_state_, &scope_, nearest_decl_scope);
// We will reindex the function literals later.
ResetFunctionLiteralId();
// We preparse the class members that are not fields with initializers
// in order to collect the function literal ids.
ParsingModeScope mode(this, PARSE_LAZILY);
ExpressionParsingScope no_expression_scope(impl());
// We will reparse the entire class because we want to know if
// the class is anonymous.
// When the function is a kClassMembersInitializerFunction, we record the
// source range of the entire class as its positions in its SFI, so at this
// point the scanner should be rewound to the position of the class token.
DCHECK_EQ(peek(), Token::CLASS);
Expect(Token::CLASS);
const AstRawString* class_name = NullIdentifier();
const AstRawString* variable_name = NullIdentifier();
// It's a reparse so we don't need to check for default export or
// whether the names are reserved.
if (peek() == Token::EXTENDS || peek() == Token::LBRACE) {
GetDefaultStrings(&class_name, &variable_name);
} else {
class_name = ParseIdentifier();
variable_name = class_name;
}
bool is_anonymous = class_name == nullptr || class_name->IsEmpty();
// Create a new ClassScope for the parser to create the inner scopes,
// the variable resolution would be done in the original scope, however.
// TODO(joyee): see if we can reset the original scope to a state that
// can be reused directly and avoid creating this temporary scope.
ClassScope* reparsed_scope =
NewClassScope(original_scope->outer_scope(), is_anonymous);
#ifdef DEBUG
original_scope->SetScopeName(class_name);
#endif
Expression* expr =
DoParseClassLiteral(reparsed_scope, class_name, scanner()->location(),
is_anonymous, class_token_pos);
DCHECK(expr->IsClassLiteral());
ClassLiteral* literal = expr->AsClassLiteral();
FunctionLiteral* initializer =
literal->instance_members_initializer_function();
// Reindex so that the function literal ids match.
AstFunctionLiteralIdReindexer reindexer(
stack_limit_, initializer_id - initializer->function_literal_id());
reindexer.Reindex(expr);
no_expression_scope.ValidateExpression();
// Fix up the scope chain and the references used by the instance member
// initializer.
reparsed_scope->ReplaceReparsedClassScope(isolate, ast_value_factory(),
original_scope);
original_scope_ = reparsed_scope;
return initializer;
}
Statement* Parser::ParseModuleItem() {
// ecma262/#prod-ModuleItem
// ModuleItem :
......@@ -3122,7 +3221,9 @@ FunctionLiteral* Parser::CreateInitializerFunction(
FunctionSyntaxKind::kAccessorOrMethod,
FunctionLiteral::kShouldEagerCompile, scope->start_position(), false,
GetNextFunctionLiteralId());
#ifdef DEBUG
scope->SetScopeName(ast_value_factory()->GetOneByteString(name));
#endif
RecordFunctionLiteralSourceRange(result);
return result;
......
......@@ -234,6 +234,15 @@ class V8_EXPORT_PRIVATE Parser : public NON_EXPORTED_BASE(ParserBase<Parser>) {
int function_literal_id,
const AstRawString* raw_name);
FunctionLiteral* DoParseDeserializedFunction(
Isolate* isolate, Handle<SharedFunctionInfo> shared_info, ParseInfo* info,
int start_position, int end_position, int function_literal_id,
const AstRawString* raw_name);
FunctionLiteral* ParseClassForInstanceMemberInitialization(
Isolate* isolate, ClassScope* scope, int initializer_pos,
int initializer_id);
// Called by ParseProgram after setting up the scanner.
FunctionLiteral* DoParseProgram(Isolate* isolate, ParseInfo* info);
......
This diff is collapsed.
......@@ -22,41 +22,41 @@ var b1, b2, b3;
// y = [B1]2;
// z = [B2]3;
// }
b1 = Debug.setBreakPoint(initializer, 0, 0);
assertTrue(Debug.showBreakPoints(initializer).indexOf("x = [B0]1;") === 0);
b1 = Debug.setBreakPoint(initializer, 1, 0);
assertTrue(Debug.showBreakPoints(initializer).indexOf("x = [B0]1;") > 0);
Debug.clearBreakPoint(b1);
assertTrue(Debug.showBreakPoints(initializer).indexOf("x = [B0]1;") === -1);
b2 = Debug.setBreakPoint(initializer, 1, 0);
b2 = Debug.setBreakPoint(initializer, 2, 0);
assertTrue(Debug.showBreakPoints(initializer).indexOf("y = [B0]2;") > 0);
Debug.clearBreakPoint(b2);
assertTrue(Debug.showBreakPoints(initializer).indexOf("y = [B0]2;") === -1);
b3 = Debug.setBreakPoint(initializer, 2, 0);
b3 = Debug.setBreakPoint(initializer, 3, 0);
assertTrue(Debug.showBreakPoints(initializer).indexOf("z = [B0]3") > 0);
Debug.clearBreakPoint(b3);
assertTrue(Debug.showBreakPoints(initializer).indexOf("z = [B0]3") === -1);
b1 = Debug.setBreakPoint(initializer, 0, 0);
b2 = Debug.setBreakPoint(initializer, 1, 0);
assertTrue(Debug.showBreakPoints(initializer).indexOf("x = [B0]1;") === 0);
b1 = Debug.setBreakPoint(initializer, 1, 0);
b2 = Debug.setBreakPoint(initializer, 2, 0);
assertTrue(Debug.showBreakPoints(initializer).indexOf("x = [B0]1;") > 0);
assertTrue(Debug.showBreakPoints(initializer).indexOf("y = [B1]2;") > 0);
Debug.clearBreakPoint(b1);
assertTrue(Debug.showBreakPoints(initializer).indexOf("x = [B0]1;") === -1);
Debug.clearBreakPoint(b2);
assertTrue(Debug.showBreakPoints(initializer).indexOf("y = [B1]2;") === -1);
b1 = Debug.setBreakPoint(initializer, 0, 0);
b3 = Debug.setBreakPoint(initializer, 2, 0);
assertTrue(Debug.showBreakPoints(initializer).indexOf("x = [B0]1;") === 0);
b1 = Debug.setBreakPoint(initializer, 1, 0);
b3 = Debug.setBreakPoint(initializer, 3, 0);
assertTrue(Debug.showBreakPoints(initializer).indexOf("x = [B0]1;") > 0);
assertTrue(Debug.showBreakPoints(initializer).indexOf("z = [B1]3") > 0);
Debug.clearBreakPoint(b1);
assertTrue(Debug.showBreakPoints(initializer).indexOf("x = [B0]1;") === -1);
Debug.clearBreakPoint(b3);
assertTrue(Debug.showBreakPoints(initializer).indexOf("z = [B1]3") === -1);
b2 = Debug.setBreakPoint(initializer, 1, 0);
b3 = Debug.setBreakPoint(initializer, 2, 0);
b2 = Debug.setBreakPoint(initializer, 2, 0);
b3 = Debug.setBreakPoint(initializer, 3, 0);
assertTrue(Debug.showBreakPoints(initializer).indexOf("y = [B0]2;") > 0);
assertTrue(Debug.showBreakPoints(initializer).indexOf("z = [B1]3") > 0);
Debug.clearBreakPoint(b2);
......@@ -83,11 +83,11 @@ class X {
// }
initializer = %GetInitializerFunction(X);
b1 = Debug.setBreakPoint(initializer, 0, 0);
assertTrue(Debug.showBreakPoints(initializer).indexOf('[foo()] = 1;') === 0);
b1 = Debug.setBreakPoint(initializer, 1, 0);
assertTrue(Debug.showBreakPoints(initializer).indexOf('[foo()] = 1;') > 0);
Debug.clearBreakPoint(b1);
b1 = Debug.setBreakPoint(initializer, 1, 0);
b1 = Debug.setBreakPoint(initializer, 2, 0);
assertTrue(Debug.showBreakPoints(initializer).indexOf('baz = [B0]foo()') > 0);
Debug.clearBreakPoint(b1);
......
......@@ -90,8 +90,8 @@ Running test: testScopesPaused
scopeChain : [
[0] : {
endLocation : {
columnNumber : 13
lineNumber : 14
columnNumber : 3
lineNumber : 15
scriptId : <scriptId>
}
name : run
......@@ -102,8 +102,8 @@ Running test: testScopesPaused
type : object
}
startLocation : {
columnNumber : 4
lineNumber : 12
columnNumber : 2
lineNumber : 11
scriptId : <scriptId>
}
type : local
......@@ -183,8 +183,8 @@ Running test: testScopesPaused
[0] : {
callFrameId : <callFrameId>
functionLocation : {
columnNumber : 4
lineNumber : 12
columnNumber : 2
lineNumber : 11
scriptId : <scriptId>
}
functionName : <instance_members_initializer>
......@@ -364,8 +364,8 @@ Running test: testScopesPaused
[0] : {
callFrameId : <callFrameId>
functionLocation : {
columnNumber : 4
lineNumber : 12
columnNumber : 2
lineNumber : 11
scriptId : <scriptId>
}
functionName : <instance_members_initializer>
......@@ -545,8 +545,8 @@ Running test: testScopesPaused
[0] : {
callFrameId : <callFrameId>
functionLocation : {
columnNumber : 4
lineNumber : 12
columnNumber : 2
lineNumber : 11
scriptId : <scriptId>
}
functionName : <instance_members_initializer>
......@@ -798,8 +798,8 @@ Running test: testScopesPaused
[1] : {
callFrameId : <callFrameId>
functionLocation : {
columnNumber : 4
lineNumber : 12
columnNumber : 2
lineNumber : 11
scriptId : <scriptId>
}
functionName : <instance_members_initializer>
......
......@@ -7,23 +7,23 @@ let x = |R|class {}
|_|x = |R|class {
x = |_|1;
y = |_|2|R|;
}
y = |_|2;
}|R|
|_|x = |R|class {
x = |C|foo();
y = |_|2;
z = |C|bar()|R|;
}
z = |C|bar();
}|R|
|_|x = class {
x = |C|foo();
y = |_|2;
z = |C|bar()|R|;
z = |C|bar();
constructor() {
this.|_|x;
|R|}
}
}|R|
|_|x = class {
x = |C|foo();
......@@ -31,8 +31,8 @@ let x = |R|class {}
constructor() {
this.|_|x;
|R|}
z = |C|bar()|R|;
}
z = |C|bar();
}|R|
|_|x = class {
x = |C|foo();
......@@ -40,14 +40,14 @@ let x = |R|class {}
constructor() {
this.|_|x;
|R|}
z = |C|bar()|R|;
}
z = |C|bar();
}|R|
|_|x = |R|class {
x = |_|1;
foo() {|R|}
y = |_|2|R|;
}
y = |_|2;
}|R|
|_|x = |R|class {
x = (function() {
......@@ -55,54 +55,54 @@ let x = |R|class {}
|R|})|C|();
y = (() => {
|C|bar();
|R|})|C|()|R|;
}
|R|})|C|();
}|R|
|_|x = |R|class {
x = |_|function() {
|C|foo();
|R|}|R|;
}
|R|};
}|R|
|_|x = |R|class {
x = |_|async function() {
|_|await |C|foo();
|R|}|R|;
}
|R|};
}|R|
|_|x = |R|class {
x = |_|() => {
|C|foo();
|R|};
y = |_|() => |C|bar()|R|;
}
}|R|
|_|x = |R|class {
x = |_|async () => {
|_|await |C|foo();
|R|};
y = |_|async () => |_|await |C|bar()|R|;
}
}|R|
|_|x = |R|class {
[|_|x] = |_|1;
[|C|foo()] = |_|2|R|;
}
[|C|foo()] = |_|2;
}|R|
|_|x = |R|class {
[|_|x] = |_|[...this]|R|;
}
[|_|x] = |_|[...this];
}|R|
|_|x = |R|class {
x;
[|C|foo()]|R|;
}
[|C|foo()];
}|R|
|_|x = |R|class {
x = |_|function*|_|() {
|_|yield 1;
|R|}|R|;
}
|R|};
}|R|
|_|x = |R|class {
static x = |_|1;
......@@ -201,6 +201,6 @@ let x = |R|class {}
static [|_|z] = |_|3;
[|_|p] = |_|4;
static [|C|foo()] = |_|5|R|;
[|C|bar()] = |_|6|R|;
}
[|C|bar()] = |_|6;
}|R|
|R|
......@@ -25,7 +25,7 @@ class X { // 000
}; // 100
`,
[{"start":0,"end":149,"count":1},
{"start":52,"end":70,"count":0}]
{"start":0,"end":101,"count":0}]
);
TestCoverage(
......@@ -37,7 +37,7 @@ class X { // 000
let x = new X(); // 150
`,
[{"start":0,"end":199,"count":1},
{"start":52,"end":70,"count":1},
{"start":0,"end":101,"count":1},
{"start":56,"end":70,"count":0}]
);
......@@ -51,7 +51,7 @@ let x = new X(); // 150
x.x(); // 200
`,
[{"start":0,"end":249,"count":1},
{"start":52,"end":70,"count":1},
{"start":0,"end":101,"count":1},
{"start":56,"end":70,"count":1}]
);
......@@ -68,7 +68,7 @@ x.x(); // 300
x.y(); // 350
`,
[{"start":0,"end":399,"count":1},
{"start":52,"end":169,"count":1},
{"start":0,"end":201,"count":1},
{"start":56,"end":70,"count":1},
{"start":102,"end":111,"count":0},
{"start":156,"end":169,"count":1}]
......@@ -88,7 +88,7 @@ x.y(); // 350
x.foo(); // 400
`,
[{"start":0,"end":449,"count":1},
{"start":52,"end":169,"count":1},
{"start":0,"end":201,"count":1},
{"start":56,"end":70,"count":1},
{"start":102,"end":111,"count":1},
{"start":156,"end":169,"count":1}]
......@@ -103,7 +103,7 @@ class X { // 000
let x = new X(); // 150
`,
[{"start":0,"end":199,"count":1},
{"start":52,"end":74,"count":1},
{"start":0,"end":101,"count":1},
{"start":57,"end":71,"count":1}]
);
......@@ -118,7 +118,7 @@ let x = new X(); // 200
`,
[{"start":0,"end":249,"count":1},
{"start":0,"end":15,"count":1},
{"start":102,"end":128,"count":1},
{"start":50,"end":151,"count":1},
{"start":111,"end":125,"count":1}]
);
......
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