Commit 2dc0f2ec authored by marja's avatar marja Committed by Commit bot

[strong] Allow mutually recursive classes.

The previous restrictions were overshooting (didn't allow a class to refer to a
later class under any circumstances); after this CL we're undershooting (allow
referring to any class from inside a method).

Implementing the correct checks (allow referring only if the class declarations
are in a consecutive block and if there's no dependency cycle) will be
implemented as a follow up.

BUG=v8:3956
LOG=N

Review URL: https://codereview.chromium.org/1087543004

Cr-Commit-Position: refs/heads/master@{#27888}
parent a2baf44b
......@@ -568,14 +568,15 @@ class VariableDeclaration FINAL : public Declaration {
return mode() == VAR ? kCreatedInitialized : kNeedsInitialization;
}
bool is_class_declaration() const { return is_class_declaration_; }
protected:
VariableDeclaration(Zone* zone,
VariableProxy* proxy,
VariableMode mode,
Scope* scope,
int pos)
: Declaration(zone, proxy, mode, scope, pos) {
}
VariableDeclaration(Zone* zone, VariableProxy* proxy, VariableMode mode,
Scope* scope, int pos, bool is_class_declaration = false)
: Declaration(zone, proxy, mode, scope, pos),
is_class_declaration_(is_class_declaration) {}
bool is_class_declaration_;
};
......@@ -3215,11 +3216,11 @@ class AstNodeFactory FINAL BASE_EMBEDDED {
: zone_(ast_value_factory->zone()),
ast_value_factory_(ast_value_factory) {}
VariableDeclaration* NewVariableDeclaration(VariableProxy* proxy,
VariableMode mode,
Scope* scope,
int pos) {
return new (zone_) VariableDeclaration(zone_, proxy, mode, scope, pos);
VariableDeclaration* NewVariableDeclaration(
VariableProxy* proxy, VariableMode mode, Scope* scope, int pos,
bool is_class_declaration = false) {
return new (zone_) VariableDeclaration(zone_, proxy, mode, scope, pos,
is_class_declaration);
}
FunctionDeclaration* NewFunctionDeclaration(VariableProxy* proxy,
......
......@@ -1948,11 +1948,15 @@ Variable* Parser::Declare(Declaration* declaration, bool resolve, bool* ok) {
var = declaration_scope->LookupLocal(name);
if (var == NULL) {
// Declare the name.
Variable::Kind kind = Variable::NORMAL;
if (declaration->IsFunctionDeclaration()) {
kind = Variable::FUNCTION;
} else if (declaration->IsVariableDeclaration() &&
declaration->AsVariableDeclaration()->is_class_declaration()) {
kind = Variable::CLASS;
}
var = declaration_scope->DeclareLocal(
name, mode, declaration->initialization(),
declaration->IsFunctionDeclaration() ? Variable::FUNCTION
: Variable::NORMAL,
kNotAssigned);
name, mode, declaration->initialization(), kind, kNotAssigned);
} else if (IsLexicalVariableMode(mode) ||
IsLexicalVariableMode(var->mode()) ||
((mode == CONST_LEGACY || var->mode() == CONST_LEGACY) &&
......@@ -2170,8 +2174,9 @@ Statement* Parser::ParseClassDeclaration(ZoneList<const AstRawString*>* names,
VariableMode mode = is_strong(language_mode()) ? CONST : LET;
VariableProxy* proxy = NewUnresolved(name, mode);
Declaration* declaration =
factory()->NewVariableDeclaration(proxy, mode, scope_, pos);
const bool is_class_declaration = true;
Declaration* declaration = factory()->NewVariableDeclaration(
proxy, mode, scope_, pos, is_class_declaration);
Declare(declaration, true, CHECK_OK);
proxy->var()->set_initializer_position(position());
......
......@@ -1138,6 +1138,30 @@ bool Scope::CheckStrongModeDeclaration(VariableProxy* proxy, Variable* var) {
scope = scope->outer_scope();
}
// Allow references from methods to classes declared later, if we detect no
// problematic dependency cycles.
if (ClassVariableForMethod() && var->is_class()) {
// A method is referring to some other class, possibly declared
// later. Referring to a class declared earlier is always OK and covered by
// the code outside this if. Here we only need to allow special cases for
// referring to a class which is declared later.
// Referring to a class C declared later is OK under the following
// circumstances:
// 1. The class declarations are in a consecutive group with no other
// declarations or statements in between, and
// 2. There is no dependency cycle where the first edge is an initialization
// time dependency (computed property name or extends clause) from C to
// something that depends on this class directly or transitively.
// TODO(marja,rossberg): implement these checks. Here we undershoot the
// target and allow referring to any class.
return true;
}
// If both the use and the declaration are inside an eval scope (possibly
// indirectly), or one of them is, we need to check whether they are inside
// the same eval scope or different ones.
......
......@@ -18,7 +18,7 @@ namespace internal {
class Variable: public ZoneObject {
public:
enum Kind { NORMAL, FUNCTION, THIS, NEW_TARGET, ARGUMENTS };
enum Kind { NORMAL, FUNCTION, CLASS, THIS, NEW_TARGET, ARGUMENTS };
enum Location {
// Before and during variable allocation, a variable whose location is
......@@ -97,6 +97,7 @@ class Variable: public ZoneObject {
}
bool is_function() const { return kind_ == FUNCTION; }
bool is_class() const { return kind_ == CLASS; }
bool is_this() const { return kind_ == THIS; }
bool is_new_target() const { return kind_ == NEW_TARGET; }
bool is_arguments() const { return kind_ == ARGUMENTS; }
......
......@@ -33,6 +33,10 @@ function assertThrowsHelper(code) {
assertThrowsHelper("function f() { x; let x = 0; }");
assertThrowsHelper("function f() { x; } let x = 0;");
assertThrowsHelper("x; const x = 0;");
assertThrowsHelper("function f() { x; const x = 0; }");
assertThrowsHelper("function f() { x; } const x = 0;");
// These tests needs to be done a bit more manually, since var is not allowed
// in strong mode:
assertThrows(
......@@ -78,6 +82,16 @@ function assertThrowsHelper(code) {
(function DeclarationAfterUseInClasses() {
// Referring to a variable declared later
assertThrowsHelper("class C { m() { x; } } let x = 0;");
assertThrowsHelper("class C { static m() { x; } } let x = 0;");
assertThrowsHelper("class C { [x]() { } } let x = 0;");
assertThrowsHelper("class C { m() { x; } } const x = 0;");
assertThrowsHelper("class C { static m() { x; } } const x = 0;");
assertThrowsHelper("class C { [x]() { } } const x = 0;");
// Referring to the class name.
assertThrowsHelper("class C extends C { }");
assertThrowsHelper("let C = class C2 extends C { }");
assertThrowsHelper("let C = class C2 extends C2 { }");
......
// Copyright 2015 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: --strong-mode
// Note that it's essential for these tests that the reference is inside dead
// code (because we already produce ReferenceErrors for run-time unresolved
// variables and don't want to confuse those with strong mode errors). But the
// errors should *not* be inside lazy, unexecuted functions, since lazy parsing
// doesn't produce strong mode scoping errors).
// In addition, assertThrows will call eval and that changes variable binding
// types (see e.g., UNBOUND_EVAL_SHADOWED). We can avoid unwanted side effects
// by wrapping the code to be tested inside an outer function.
function assertThrowsHelper(code) {
"use strict";
let prologue_dead = "(function outer() { if (false) { ";
let epilogue_dead = " } })();";
assertThrows("'use strong'; " + prologue_dead + code + epilogue_dead, ReferenceError);
// Make sure the error happens only in strong mode (note that we need strict
// mode here because of let).
assertDoesNotThrow("'use strict'; " + prologue_dead + code + epilogue_dead);
// But if we don't put the references inside a dead code, it throws a run-time
// error (also in strict mode).
let prologue_live = "(function outer() { ";
let epilogue_live = "})();";
assertThrows("'use strong'; " + prologue_live + code + epilogue_live, ReferenceError);
assertThrows("'use strict'; " + prologue_live + code + epilogue_live, ReferenceError);
}
(function InitTimeReferenceForward() {
// It's never OK to have an init time reference to a class which hasn't been
// declared.
assertThrowsHelper(
`class A extends B { };
class B {}`);
assertThrowsHelper(
`class A {
[B.sm()]() { }
};
class B {
static sm() { return 0; }
}`);
})();
(function InitTimeReferenceBackward() {
// Backwards is of course fine.
"use strong";
class A {
static sm() { return 0; }
};
let i = "making these classes non-consecutive";
class B extends A {};
"by inserting statements and declarations in between";
class C {
[A.sm()]() { }
};
})();
(function BasicMutualRecursion() {
"use strong";
class A {
m() { B; }
static sm() { B; }
};
// No statements or declarations between the classes.
class B {
m() { A; }
static sm() { A; }
};
})();
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