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_;
};
: 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,
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) &&
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());
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.
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
}
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; }
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(
(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 { }");
--- /dev/null
+// 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; }
+ };
+})();