Title: [181973] trunk
Revision
181973
Author
joep...@webkit.org
Date
2015-03-25 14:33:59 -0700 (Wed, 25 Mar 2015)

Log Message

ES6: Classes: Program level class statement throws exception in strict mode
https://bugs.webkit.org/show_bug.cgi?id=143038

Reviewed by Ryosuke Niwa.

Source/_javascript_Core:

Classes expose a name to the current lexical environment. This treats
"class X {}" like "var X = class X {}". Ideally it would be "let X = class X {}".
Also, improve error messages for class statements where the class is missing a name.

* parser/Parser.h:
* parser/Parser.cpp:
(JSC::Parser<LexerType>::parseClass):
Fill name in info parameter if needed. Better error message if name is needed and missing.

(JSC::Parser<LexerType>::parseClassDeclaration):
Pass info parameter to get name, and expose the name as a variable name.

(JSC::Parser<LexerType>::parsePrimaryExpression):
Pass info parameter that is ignored.

* parser/ParserFunctionInfo.h:
Add a parser info for class, to extract the name.

LayoutTests:

This updates a number of existing tests that were relying on
poor behavior. `shouldBe` and friends use eval within a function
not at the global scope. This means `shouldBe('class X { ... }')`
behaves like `shouldBe('var x = ...')` not `shouldBe('x = ...')`.
This means `x` will not be available in the next `shouldBe` call.

Add a test specifically to cover the scoping of the class name
in regular and strict mode code. Currently we treat it like var
with one failing test that would pass when we treat it like let.

* js/class-syntax-name.html: Added.
* js/script-tests/class-syntax-name.js: Added.
(runTestShouldBe):
(runTestShouldBeTrue):
(runTestShouldThrow):
(runTestShouldNotThrow):
Test class name scoping.

* js/class-syntax-call-expected.txt:
* js/class-syntax-declaration-expected.txt:
* js/class-syntax-default-constructor-expected.txt:
* js/class-syntax-name-expected.txt: Added.
* js/script-tests/class-syntax-call.js:
* js/script-tests/class-syntax-declaration.js:
* js/script-tests/class-syntax-default-constructor.js:

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (181972 => 181973)


--- trunk/LayoutTests/ChangeLog	2015-03-25 21:19:05 UTC (rev 181972)
+++ trunk/LayoutTests/ChangeLog	2015-03-25 21:33:59 UTC (rev 181973)
@@ -1,3 +1,36 @@
+2015-03-25  Joseph Pecoraro  <pecor...@apple.com>
+
+        ES6: Classes: Program level class statement throws exception in strict mode
+        https://bugs.webkit.org/show_bug.cgi?id=143038
+
+        Reviewed by Ryosuke Niwa.
+
+        This updates a number of existing tests that were relying on
+        poor behavior. `shouldBe` and friends use eval within a function
+        not at the global scope. This means `shouldBe('class X { ... }')`
+        behaves like `shouldBe('var x = ...')` not `shouldBe('x = ...')`.
+        This means `x` will not be available in the next `shouldBe` call.
+
+        Add a test specifically to cover the scoping of the class name
+        in regular and strict mode code. Currently we treat it like var
+        with one failing test that would pass when we treat it like let.
+
+        * js/class-syntax-name.html: Added.
+        * js/script-tests/class-syntax-name.js: Added.
+        (runTestShouldBe):
+        (runTestShouldBeTrue):
+        (runTestShouldThrow):
+        (runTestShouldNotThrow):
+        Test class name scoping.
+
+        * js/class-syntax-call-expected.txt:
+        * js/class-syntax-declaration-expected.txt:
+        * js/class-syntax-default-constructor-expected.txt:
+        * js/class-syntax-name-expected.txt: Added.
+        * js/script-tests/class-syntax-call.js:
+        * js/script-tests/class-syntax-declaration.js:
+        * js/script-tests/class-syntax-default-constructor.js:
+
 2015-03-25  Mark Lam  <mark....@apple.com>
 
         Gardening: rebaseline after r181907.

Modified: trunk/LayoutTests/js/class-syntax-call-expected.txt (181972 => 181973)


--- trunk/LayoutTests/js/class-syntax-call-expected.txt	2015-03-25 21:19:05 UTC (rev 181972)
+++ trunk/LayoutTests/js/class-syntax-call-expected.txt	2015-03-25 21:33:59 UTC (rev 181973)
@@ -3,9 +3,9 @@
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
 
-PASS class A { constructor() {} }; new A did not throw exception.
+PASS new A did not throw exception.
 PASS A() threw exception TypeError: Cannot call a class constructor.
-PASS class B extends A { constructor() { super() } }; new B did not throw exception.
+PASS new B did not throw exception.
 PASS B() threw exception TypeError: Cannot call a class constructor.
 PASS new (class { constructor() {} })() did not throw exception.
 PASS (class { constructor() {} })() threw exception TypeError: Cannot call a class constructor.

Modified: trunk/LayoutTests/js/class-syntax-declaration-expected.txt (181972 => 181973)


--- trunk/LayoutTests/js/class-syntax-declaration-expected.txt	2015-03-25 21:19:05 UTC (rev 181972)
+++ trunk/LayoutTests/js/class-syntax-declaration-expected.txt	2015-03-25 21:33:59 UTC (rev 181973)
@@ -16,15 +16,17 @@
 PASS (new A).__proto__ is A.prototype
 PASS A.prototype.constructor is A
 PASS class threw exception SyntaxError: Unexpected end of script.
+PASS class [ threw exception SyntaxError: Unexpected token '['.
+PASS class { threw exception SyntaxError: Class statements must have a name..
 PASS class X { threw exception SyntaxError: Unexpected end of script.
 PASS class X { ( } threw exception SyntaxError: Unexpected token '('. Expected an identifier..
 PASS class X {} did not throw exception.
 PASS class X { constructor() {} constructor() {} } threw exception SyntaxError: Cannot declare multiple constructors in a single class..
 PASS class X { constructor() {} static constructor() { return staticMethodValue; } } did not throw exception.
-PASS X.constructor() is staticMethodValue
+PASS class X { constructor() {} static constructor() { return staticMethodValue; } }; X.constructor() is staticMethodValue
 PASS class X { constructor() {} static prototype() {} } threw exception SyntaxError: Cannot declare a static method named 'prototype'..
 PASS class X { constructor() {} prototype() { return instanceMethodValue; } } did not throw exception.
-PASS (new X).prototype() is instanceMethodValue
+PASS class X { constructor() {} prototype() { return instanceMethodValue; } }; (new X).prototype() is instanceMethodValue
 PASS class X { constructor() {} set foo(a) {} } did not throw exception.
 PASS class X { constructor() {} set foo({x, y}) {} } did not throw exception.
 PASS class X { constructor() {} set foo() {} } threw exception SyntaxError: Unexpected token ')'. setter functions must have one parameter..

Modified: trunk/LayoutTests/js/class-syntax-default-constructor-expected.txt (181972 => 181973)


--- trunk/LayoutTests/js/class-syntax-default-constructor-expected.txt	2015-03-25 21:19:05 UTC (rev 181972)
+++ trunk/LayoutTests/js/class-syntax-default-constructor-expected.txt	2015-03-25 21:33:59 UTC (rev 181973)
@@ -3,11 +3,11 @@
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
 
-PASS class A { }; new A instanceof A is true
+PASS new A instanceof A is true
 PASS A() threw exception TypeError: Cannot call a class constructor.
 PASS A.prototype.constructor instanceof Function is true
 PASS A.prototype.constructor.name is "A"
-PASS class B extends A { }; new B instanceof A; new B instanceof A is true
+PASS new B instanceof A; new B instanceof A is true
 PASS B() threw exception TypeError: Cannot call a class constructor.
 PASS B.prototype.constructor.name is "B"
 PASS A !== B is true

Added: trunk/LayoutTests/js/class-syntax-name-expected.txt (0 => 181973)


--- trunk/LayoutTests/js/class-syntax-name-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/js/class-syntax-name-expected.txt	2015-03-25 21:33:59 UTC (rev 181973)
@@ -0,0 +1,122 @@
+Tests for ES6 class name semantics in class statements and expressions
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+Class statement
+PASS A threw exception ReferenceError: Can't find variable: A.
+PASS 'use strict'; A threw exception ReferenceError: Can't find variable: A.
+PASS class {} threw exception SyntaxError: Class statements must have a name..
+PASS 'use strict'; class {} threw exception SyntaxError: Class statements must have a name..
+PASS class { constructor() {} } threw exception SyntaxError: Class statements must have a name..
+PASS 'use strict'; class { constructor() {} } threw exception SyntaxError: Class statements must have a name..
+PASS class A { constructor() {} } did not throw exception.
+PASS 'use strict'; class A { constructor() {} } did not throw exception.
+PASS class A { constructor() {} }; A.toString() is 'function A() {}'
+PASS 'use strict'; class A { constructor() {} }; A.toString() is 'function A() {}'
+PASS class A { constructor() {} }; (new A) instanceof A is true
+PASS 'use strict'; class A { constructor() {} }; (new A) instanceof A is true
+PASS class A { constructor() { this.base = A; } }; (new A).base.toString() is 'function A() { this.base = A; }'
+PASS 'use strict'; class A { constructor() { this.base = A; } }; (new A).base.toString() is 'function A() { this.base = A; }'
+PASS class A { constructor() {} }; class B extends A {}; did not throw exception.
+PASS 'use strict'; class A { constructor() {} }; class B extends A {}; did not throw exception.
+PASS class A { constructor() {} }; class B extends A { constructor() {} }; B.toString() is 'function B() {}'
+PASS 'use strict'; class A { constructor() {} }; class B extends A { constructor() {} }; B.toString() is 'function B() {}'
+PASS class A { constructor() {} }; class B extends A {}; (new B) instanceof A is true
+PASS 'use strict'; class A { constructor() {} }; class B extends A {}; (new B) instanceof A is true
+PASS class A { constructor() {} }; class B extends A {}; (new B) instanceof B is true
+PASS 'use strict'; class A { constructor() {} }; class B extends A {}; (new B) instanceof B is true
+PASS class A { constructor() {} }; class B extends A { constructor() { super(); this.base = A; this.derived = B; } }; (new B).base.toString() is 'function A() {}'
+PASS 'use strict'; class A { constructor() {} }; class B extends A { constructor() { super(); this.base = A; this.derived = B; } }; (new B).base.toString() is 'function A() {}'
+PASS class A { constructor() {} }; class B extends A { constructor() { super(); this.base = A; this.derived = B; } }; (new B).derived.toString() is 'function B() { super(); this.base = A; this.derived = B; }'
+PASS 'use strict'; class A { constructor() {} }; class B extends A { constructor() { super(); this.base = A; this.derived = B; } }; (new B).derived.toString() is 'function B() { super(); this.base = A; this.derived = B; }'
+
+Class _expression_
+PASS A threw exception ReferenceError: Can't find variable: A.
+PASS 'use strict'; A threw exception ReferenceError: Can't find variable: A.
+PASS (class {}) did not throw exception.
+PASS 'use strict'; (class {}) did not throw exception.
+PASS (class { constructor(){} }) did not throw exception.
+PASS 'use strict'; (class { constructor(){} }) did not throw exception.
+PASS typeof (class {}) is "function"
+PASS 'use strict'; typeof (class {}) is "function"
+PASS (class A {}) did not throw exception.
+PASS 'use strict'; (class A {}) did not throw exception.
+PASS typeof (class A {}) is "function"
+PASS 'use strict'; typeof (class A {}) is "function"
+PASS (class A {}); A threw exception ReferenceError: Can't find variable: A.
+PASS 'use strict'; (class A {}); A threw exception ReferenceError: Can't find variable: A.
+PASS new (class A {}) did not throw exception.
+PASS 'use strict'; new (class A {}) did not throw exception.
+PASS typeof (new (class A {})) is "object"
+PASS 'use strict'; typeof (new (class A {})) is "object"
+PASS (new (class A { constructor() { this.base = A; } })).base did not throw exception.
+PASS 'use strict'; (new (class A { constructor() { this.base = A; } })).base did not throw exception.
+PASS (new (class A { constructor() { this.base = A; } })).base.toString() is "function A() { this.base = A; }"
+PASS 'use strict'; (new (class A { constructor() { this.base = A; } })).base.toString() is "function A() { this.base = A; }"
+PASS class A {}; (class B extends A {}) did not throw exception.
+PASS 'use strict'; class A {}; (class B extends A {}) did not throw exception.
+PASS class A {}; (class B extends A {}); B threw exception ReferenceError: Can't find variable: B.
+PASS 'use strict'; class A {}; (class B extends A {}); B threw exception ReferenceError: Can't find variable: B.
+PASS class A {}; new (class B extends A {}) did not throw exception.
+PASS 'use strict'; class A {}; new (class B extends A {}) did not throw exception.
+PASS class A {}; new (class B extends A { constructor() { super(); this.base = A; this.derived = B; } }) did not throw exception.
+PASS 'use strict'; class A {}; new (class B extends A { constructor() { super(); this.base = A; this.derived = B; } }) did not throw exception.
+PASS class A {}; (new (class B extends A { constructor() { super(); this.base = A; this.derived = B; } })) instanceof A is true
+PASS 'use strict'; class A {}; (new (class B extends A { constructor() { super(); this.base = A; this.derived = B; } })) instanceof A is true
+PASS class A { constructor() {} }; (new (class B extends A { constructor() { super(); this.base = A; this.derived = B; } })).base.toString() is 'function A() {}'
+PASS 'use strict'; class A { constructor() {} }; (new (class B extends A { constructor() { super(); this.base = A; this.derived = B; } })).base.toString() is 'function A() {}'
+PASS class A { constructor() {} }; (new (class B extends A { constructor() { super(); this.base = A; this.derived = B; } })).derived.toString() is 'function B() { super(); this.base = A; this.derived = B; }'
+PASS 'use strict'; class A { constructor() {} }; (new (class B extends A { constructor() { super(); this.base = A; this.derived = B; } })).derived.toString() is 'function B() { super(); this.base = A; this.derived = B; }'
+
+Class _expression_ assignment to variable
+PASS A threw exception ReferenceError: Can't find variable: A.
+PASS 'use strict'; A threw exception ReferenceError: Can't find variable: A.
+PASS var VarA = class {} did not throw exception.
+PASS 'use strict'; var VarA = class {} did not throw exception.
+PASS var VarA = class { constructor() {} }; VarA.toString() is 'function () {}'
+PASS 'use strict'; var VarA = class { constructor() {} }; VarA.toString() is 'function () {}'
+PASS VarA threw exception ReferenceError: Can't find variable: VarA.
+PASS 'use strict'; VarA threw exception ReferenceError: Can't find variable: VarA.
+PASS var VarA = class A { constructor() {} } did not throw exception.
+PASS 'use strict'; var VarA = class A { constructor() {} } did not throw exception.
+PASS var VarA = class A { constructor() {} }; VarA.toString() is 'function A() {}'
+PASS 'use strict'; var VarA = class A { constructor() {} }; VarA.toString() is 'function A() {}'
+PASS var VarA = class A { constructor() {} }; A.toString() threw exception ReferenceError: Can't find variable: A.
+PASS 'use strict'; var VarA = class A { constructor() {} }; A.toString() threw exception ReferenceError: Can't find variable: A.
+PASS var VarA = class A { constructor() {} }; (new VarA) instanceof VarA is true
+PASS 'use strict'; var VarA = class A { constructor() {} }; (new VarA) instanceof VarA is true
+PASS var VarA = class A { constructor() { this.base = A; } }; (new VarA).base.toString() is 'function A() { this.base = A; }'
+PASS 'use strict'; var VarA = class A { constructor() { this.base = A; } }; (new VarA).base.toString() is 'function A() { this.base = A; }'
+PASS var VarA = class A { constructor() {} }; var VarB = class B extends VarA { constructor() {} }; did not throw exception.
+PASS 'use strict'; var VarA = class A { constructor() {} }; var VarB = class B extends VarA { constructor() {} }; did not throw exception.
+PASS var VarA = class A { constructor() {} }; var VarB = class B extends VarA { constructor() {} }; B threw exception ReferenceError: Can't find variable: B.
+PASS 'use strict'; var VarA = class A { constructor() {} }; var VarB = class B extends VarA { constructor() {} }; B threw exception ReferenceError: Can't find variable: B.
+PASS var VarA = class A { constructor() {} }; var VarB = class B extends VarA { constructor() {} }; VarB.toString() is 'function B() {}'
+PASS 'use strict'; var VarA = class A { constructor() {} }; var VarB = class B extends VarA { constructor() {} }; VarB.toString() is 'function B() {}'
+PASS var VarA = class A { constructor() {} }; var VarB = class B extends VarA { }; (new VarB) instanceof VarA is true
+PASS 'use strict'; var VarA = class A { constructor() {} }; var VarB = class B extends VarA { }; (new VarB) instanceof VarA is true
+PASS var VarA = class A { constructor() {} }; var VarB = class B extends VarA { }; (new VarB) instanceof VarB is true
+PASS 'use strict'; var VarA = class A { constructor() {} }; var VarB = class B extends VarA { }; (new VarB) instanceof VarB is true
+PASS var VarA = class A { constructor() {} }; var VarB = class B extends VarA { constructor() { super(); this.base = VarA; this.derived = B; this.derivedVar = VarB; } }; (new VarB).base === VarA is true
+PASS 'use strict'; var VarA = class A { constructor() {} }; var VarB = class B extends VarA { constructor() { super(); this.base = VarA; this.derived = B; this.derivedVar = VarB; } }; (new VarB).base === VarA is true
+PASS var VarA = class A { constructor() {} }; var VarB = class B extends VarA { constructor() { super(); this.base = VarA; this.derived = B; this.derivedVar = VarB; } }; (new VarB).derived === VarB is true
+PASS 'use strict'; var VarA = class A { constructor() {} }; var VarB = class B extends VarA { constructor() { super(); this.base = VarA; this.derived = B; this.derivedVar = VarB; } }; (new VarB).derived === VarB is true
+PASS var VarA = class A { constructor() {} }; var VarB = class B extends VarA { constructor() { super(); this.base = VarA; this.derived = B; this.derivedVar = VarB; } }; (new VarB).derivedVar === VarB is true
+PASS 'use strict'; var VarA = class A { constructor() {} }; var VarB = class B extends VarA { constructor() { super(); this.base = VarA; this.derived = B; this.derivedVar = VarB; } }; (new VarB).derivedVar === VarB is true
+
+Class statement binding in other circumstances
+PASS var result = A; result threw exception ReferenceError: Can't find variable: A.
+PASS 'use strict'; var result = A; result threw exception ReferenceError: Can't find variable: A.
+FAIL var result = A; class A {}; result should throw an exception. Was undefined.
+FAIL 'use strict'; var result = A; class A {}; result should throw an exception. Was undefined.
+PASS class A {}; var result = A; result did not throw exception.
+PASS 'use strict'; class A {}; var result = A; result did not throw exception.
+PASS eval('var Foo = 10'); Foo is 10
+PASS 'use strict'; eval('var Foo = 10'); Foo threw exception ReferenceError: Can't find variable: Foo.
+PASS eval('class Bar { constructor() {} }'); Bar.toString() is 'function Bar() {}'
+PASS 'use strict'; eval('class Bar { constructor() {} }'); Bar.toString() threw exception ReferenceError: Can't find variable: Bar.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/js/class-syntax-name.html (0 => 181973)


--- trunk/LayoutTests/js/class-syntax-name.html	                        (rev 0)
+++ trunk/LayoutTests/js/class-syntax-name.html	2015-03-25 21:33:59 UTC (rev 181973)
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script src=""
+<script src=""
+<script src=""
+</body>
+</html>

Modified: trunk/LayoutTests/js/script-tests/class-syntax-call.js (181972 => 181973)


--- trunk/LayoutTests/js/script-tests/class-syntax-call.js	2015-03-25 21:19:05 UTC (rev 181972)
+++ trunk/LayoutTests/js/script-tests/class-syntax-call.js	2015-03-25 21:33:59 UTC (rev 181973)
@@ -1,8 +1,11 @@
 description('Tests for calling the constructors of ES6 classes');
 
-shouldNotThrow('class A { constructor() {} }; new A');
+class A { constructor() {} };
+class B extends A { constructor() { super() } };
+
+shouldNotThrow('new A');
 shouldThrow('A()', '"TypeError: Cannot call a class constructor"');
-shouldNotThrow('class B extends A { constructor() { super() } }; new B');
+shouldNotThrow('new B');
 shouldThrow('B()', '"TypeError: Cannot call a class constructor"');
 shouldNotThrow('new (class { constructor() {} })()');
 shouldThrow('(class { constructor() {} })()', '"TypeError: Cannot call a class constructor"');

Modified: trunk/LayoutTests/js/script-tests/class-syntax-declaration.js (181972 => 181973)


--- trunk/LayoutTests/js/script-tests/class-syntax-declaration.js	2015-03-25 21:19:05 UTC (rev 181972)
+++ trunk/LayoutTests/js/script-tests/class-syntax-declaration.js	2015-03-25 21:33:59 UTC (rev 181973)
@@ -28,15 +28,17 @@
 shouldBe("A.prototype.constructor", "A");
 
 shouldThrow("class", "'SyntaxError: Unexpected end of script'");
+shouldThrow("class [", "'SyntaxError: Unexpected token \\'[\\''");
+shouldThrow("class {", "'SyntaxError: Class statements must have a name.'");
 shouldThrow("class X {", "'SyntaxError: Unexpected end of script'");
 shouldThrow("class X { ( }", "'SyntaxError: Unexpected token \\'(\\'. Expected an identifier.'");
 shouldNotThrow("class X {}");
 shouldThrow("class X { constructor() {} constructor() {} }", "'SyntaxError: Cannot declare multiple constructors in a single class.'");
 shouldNotThrow("class X { constructor() {} static constructor() { return staticMethodValue; } }");
-shouldBe("X.constructor()", "staticMethodValue");
+shouldBe("class X { constructor() {} static constructor() { return staticMethodValue; } }; X.constructor()", "staticMethodValue");
 shouldThrow("class X { constructor() {} static prototype() {} }", "'SyntaxError: Cannot declare a static method named \\'prototype\\'.'");
 shouldNotThrow("class X { constructor() {} prototype() { return instanceMethodValue; } }");
-shouldBe("(new X).prototype()", "instanceMethodValue");
+shouldBe("class X { constructor() {} prototype() { return instanceMethodValue; } }; (new X).prototype()", "instanceMethodValue");
 
 shouldNotThrow("class X { constructor() {} set foo(a) {} }");
 shouldNotThrow("class X { constructor() {} set foo({x, y}) {} }");

Modified: trunk/LayoutTests/js/script-tests/class-syntax-default-constructor.js (181972 => 181973)


--- trunk/LayoutTests/js/script-tests/class-syntax-default-constructor.js	2015-03-25 21:19:05 UTC (rev 181972)
+++ trunk/LayoutTests/js/script-tests/class-syntax-default-constructor.js	2015-03-25 21:33:59 UTC (rev 181973)
@@ -1,11 +1,14 @@
 
 description('Tests for ES6 class syntax default constructor');
 
-shouldBeTrue('class A { }; new A instanceof A');
+class A { };
+class B extends A { };
+
+shouldBeTrue('new A instanceof A');
 shouldThrow('A()', '"TypeError: Cannot call a class constructor"');
 shouldBeTrue('A.prototype.constructor instanceof Function');
 shouldBe('A.prototype.constructor.name', '"A"');
-shouldBeTrue('class B extends A { }; new B instanceof A; new B instanceof A');
+shouldBeTrue('new B instanceof A; new B instanceof A');
 shouldThrow('B()', '"TypeError: Cannot call a class constructor"');
 shouldBe('B.prototype.constructor.name', '"B"');
 shouldBeTrue('A !== B');

Added: trunk/LayoutTests/js/script-tests/class-syntax-name.js (0 => 181973)


--- trunk/LayoutTests/js/script-tests/class-syntax-name.js	                        (rev 0)
+++ trunk/LayoutTests/js/script-tests/class-syntax-name.js	2015-03-25 21:33:59 UTC (rev 181973)
@@ -0,0 +1,88 @@
+description('Tests for ES6 class name semantics in class statements and expressions');
+
+function runTestShouldBe(statement, result) {
+    shouldBe(statement, result);
+    shouldBe("'use strict'; " + statement, result);
+}
+
+function runTestShouldBeTrue(statement) {
+    shouldBeTrue(statement);
+    shouldBeTrue("'use strict'; " + statement);
+}
+
+function runTestShouldThrow(statement) {
+    shouldThrow(statement);
+    shouldThrow("'use strict'; " + statement);
+}
+
+function runTestShouldNotThrow(statement) {
+    shouldNotThrow(statement);
+    shouldNotThrow("'use strict'; " + statement);
+}
+
+// Class statement. Class name added to global scope. Class name is available inside class scope and in global scope.
+debug('Class statement');
+runTestShouldThrow("A");
+runTestShouldThrow("class {}");
+runTestShouldThrow("class { constructor() {} }");
+runTestShouldNotThrow("class A { constructor() {} }");
+runTestShouldBe("class A { constructor() {} }; A.toString()", "'function A() {}'");
+runTestShouldBeTrue("class A { constructor() {} }; (new A) instanceof A");
+runTestShouldBe("class A { constructor() { this.base = A; } }; (new A).base.toString()", "'function A() { this.base = A; }'");
+runTestShouldNotThrow("class A { constructor() {} }; class B extends A {};");
+runTestShouldBe("class A { constructor() {} }; class B extends A { constructor() {} }; B.toString()", "'function B() {}'");
+runTestShouldBeTrue("class A { constructor() {} }; class B extends A {}; (new B) instanceof A");
+runTestShouldBeTrue("class A { constructor() {} }; class B extends A {}; (new B) instanceof B");
+runTestShouldBe("class A { constructor() {} }; class B extends A { constructor() { super(); this.base = A; this.derived = B; } }; (new B).base.toString()", "'function A() {}'");
+runTestShouldBe("class A { constructor() {} }; class B extends A { constructor() { super(); this.base = A; this.derived = B; } }; (new B).derived.toString()", "'function B() { super(); this.base = A; this.derived = B; }'");
+
+// Class _expression_. Class name not added to scope. Class name is available inside class scope.
+debug(''); debug('Class _expression_');
+runTestShouldThrow("A");
+runTestShouldNotThrow("(class {})");
+runTestShouldNotThrow("(class { constructor(){} })");
+runTestShouldBe("typeof (class {})", '"function"');
+runTestShouldNotThrow("(class A {})");
+runTestShouldBe("typeof (class A {})", '"function"');
+runTestShouldThrow("(class A {}); A");
+runTestShouldNotThrow("new (class A {})");
+runTestShouldBe("typeof (new (class A {}))", '"object"');
+runTestShouldNotThrow("(new (class A { constructor() { this.base = A; } })).base");
+runTestShouldBe("(new (class A { constructor() { this.base = A; } })).base.toString()", '"function A() { this.base = A; }"');
+runTestShouldNotThrow("class A {}; (class B extends A {})");
+runTestShouldThrow("class A {}; (class B extends A {}); B");
+runTestShouldNotThrow("class A {}; new (class B extends A {})");
+runTestShouldNotThrow("class A {}; new (class B extends A { constructor() { super(); this.base = A; this.derived = B; } })");
+runTestShouldBeTrue("class A {}; (new (class B extends A { constructor() { super(); this.base = A; this.derived = B; } })) instanceof A");
+runTestShouldBe("class A { constructor() {} }; (new (class B extends A { constructor() { super(); this.base = A; this.derived = B; } })).base.toString()", "'function A() {}'");
+runTestShouldBe("class A { constructor() {} }; (new (class B extends A { constructor() { super(); this.base = A; this.derived = B; } })).derived.toString()", "'function B() { super(); this.base = A; this.derived = B; }'");
+
+// Assignment of a class _expression_ to a variable. Variable name available in scope, class name is not. Class name is available inside class scope.
+debug(''); debug('Class _expression_ assignment to variable');
+runTestShouldThrow("A");
+runTestShouldNotThrow("var VarA = class {}");
+runTestShouldBe("var VarA = class { constructor() {} }; VarA.toString()", "'function () {}'");
+runTestShouldThrow("VarA");
+runTestShouldNotThrow("var VarA = class A { constructor() {} }");
+runTestShouldBe("var VarA = class A { constructor() {} }; VarA.toString()", "'function A() {}'");
+runTestShouldThrow("var VarA = class A { constructor() {} }; A.toString()");
+runTestShouldBeTrue("var VarA = class A { constructor() {} }; (new VarA) instanceof VarA");
+runTestShouldBe("var VarA = class A { constructor() { this.base = A; } }; (new VarA).base.toString()", "'function A() { this.base = A; }'");
+runTestShouldNotThrow("var VarA = class A { constructor() {} }; var VarB = class B extends VarA { constructor() {} };");
+runTestShouldThrow("var VarA = class A { constructor() {} }; var VarB = class B extends VarA { constructor() {} }; B");
+runTestShouldBe("var VarA = class A { constructor() {} }; var VarB = class B extends VarA { constructor() {} }; VarB.toString()", "'function B() {}'");
+runTestShouldBeTrue("var VarA = class A { constructor() {} }; var VarB = class B extends VarA { }; (new VarB) instanceof VarA");
+runTestShouldBeTrue("var VarA = class A { constructor() {} }; var VarB = class B extends VarA { }; (new VarB) instanceof VarB");
+runTestShouldBeTrue("var VarA = class A { constructor() {} }; var VarB = class B extends VarA { constructor() { super(); this.base = VarA; this.derived = B; this.derivedVar = VarB; } }; (new VarB).base === VarA");
+runTestShouldBeTrue("var VarA = class A { constructor() {} }; var VarB = class B extends VarA { constructor() { super(); this.base = VarA; this.derived = B; this.derivedVar = VarB; } }; (new VarB).derived === VarB");
+runTestShouldBeTrue("var VarA = class A { constructor() {} }; var VarB = class B extends VarA { constructor() { super(); this.base = VarA; this.derived = B; this.derivedVar = VarB; } }; (new VarB).derivedVar === VarB");
+
+// FIXME: Class statement binding should be like `let`, not `var`.
+debug(''); debug('Class statement binding in other circumstances');
+runTestShouldThrow("var result = A; result");
+runTestShouldThrow("var result = A; class A {}; result");
+runTestShouldNotThrow("class A {}; var result = A; result");
+shouldBe("eval('var Foo = 10'); Foo", "10");
+shouldThrow("'use strict'; eval('var Foo = 10'); Foo");
+shouldBe("eval('class Bar { constructor() {} }'); Bar.toString()", "'function Bar() {}'");
+shouldThrow("'use strict'; eval('class Bar { constructor() {} }'); Bar.toString()");

Modified: trunk/Source/_javascript_Core/ChangeLog (181972 => 181973)


--- trunk/Source/_javascript_Core/ChangeLog	2015-03-25 21:19:05 UTC (rev 181972)
+++ trunk/Source/_javascript_Core/ChangeLog	2015-03-25 21:33:59 UTC (rev 181973)
@@ -1,3 +1,28 @@
+2015-03-25  Joseph Pecoraro  <pecor...@apple.com>
+
+        ES6: Classes: Program level class statement throws exception in strict mode
+        https://bugs.webkit.org/show_bug.cgi?id=143038
+
+        Reviewed by Ryosuke Niwa.
+
+        Classes expose a name to the current lexical environment. This treats
+        "class X {}" like "var X = class X {}". Ideally it would be "let X = class X {}".
+        Also, improve error messages for class statements where the class is missing a name.
+
+        * parser/Parser.h:
+        * parser/Parser.cpp:
+        (JSC::Parser<LexerType>::parseClass):
+        Fill name in info parameter if needed. Better error message if name is needed and missing.
+
+        (JSC::Parser<LexerType>::parseClassDeclaration):
+        Pass info parameter to get name, and expose the name as a variable name.
+
+        (JSC::Parser<LexerType>::parsePrimaryExpression):
+        Pass info parameter that is ignored.
+
+        * parser/ParserFunctionInfo.h:
+        Add a parser info for class, to extract the name.
+
 2015-03-25  Yusuke Suzuki  <utatane....@gmail.com>
 
         New map and set modification tests in r181922 fails

Modified: trunk/Source/_javascript_Core/parser/Parser.cpp (181972 => 181973)


--- trunk/Source/_javascript_Core/parser/Parser.cpp	2015-03-25 21:19:05 UTC (rev 181972)
+++ trunk/Source/_javascript_Core/parser/Parser.cpp	2015-03-25 21:33:59 UTC (rev 181973)
@@ -1469,9 +1469,14 @@
     JSTextPosition classStart = tokenStartPosition();
     unsigned classStartLine = tokenLine();
 
-    TreeClassExpression classExpr = parseClass(context, FunctionNeedsName);
+    ParserClassInfo<TreeBuilder> info;
+    TreeClassExpression classExpr = parseClass(context, FunctionNeedsName, info);
     failIfFalse(classExpr, "Failed to parse class");
+    declareVariable(info.className);
 
+    // FIXME: This should be like `let`, not `var`.
+    context.addVar(info.className, DeclarationStacks::HasInitializer);
+
     JSTextPosition classEnd = lastTokenEndPosition();
     unsigned classEndLine = tokenLine();
 
@@ -1479,7 +1484,7 @@
 }
 
 template <typename LexerType>
-template <class TreeBuilder> TreeClassExpression Parser<LexerType>::parseClass(TreeBuilder& context, FunctionRequirements requirements)
+template <class TreeBuilder> TreeClassExpression Parser<LexerType>::parseClass(TreeBuilder& context, FunctionRequirements requirements, ParserClassInfo<TreeBuilder>& info)
 {
     ASSERT(match(CLASSTOKEN));
     JSTokenLocation location(tokenLocation());
@@ -1491,9 +1496,12 @@
     const Identifier* className = nullptr;
     if (match(IDENT)) {
         className = m_token.m_data.ident;
+        info.className = className;
         next();
         failIfFalse(classScope->declareVariable(className), "'", className->impl(), "' is not a valid class name");
     } else if (requirements == FunctionNeedsName) {
+        if (match(OPENBRACE))
+            semanticFail("Class statements must have a name");
         semanticFailureDueToKeyword("class name");
         failDueToUnexpectedToken();
     } else
@@ -2263,8 +2271,10 @@
         return context.createFunctionExpr(location, info);
     }
 #if ENABLE(ES6_CLASS_SYNTAX)
-    case CLASSTOKEN:
-        return parseClass(context, FunctionNoRequirements);
+    case CLASSTOKEN: {
+        ParserClassInfo<TreeBuilder> info;
+        return parseClass(context, FunctionNoRequirements, info);
+    }
 #endif
     case OPENBRACE:
         if (strictMode())

Modified: trunk/Source/_javascript_Core/parser/Parser.h (181972 => 181973)


--- trunk/Source/_javascript_Core/parser/Parser.h	2015-03-25 21:19:05 UTC (rev 181972)
+++ trunk/Source/_javascript_Core/parser/Parser.h	2015-03-25 21:33:59 UTC (rev 181973)
@@ -774,7 +774,7 @@
 
     template <class TreeBuilder> NEVER_INLINE bool parseFunctionInfo(TreeBuilder&, FunctionRequirements, FunctionParseMode, bool nameIsInContainingScope, ConstructorKind, int functionKeywordStart, ParserFunctionInfo<TreeBuilder>&);
 #if ENABLE(ES6_CLASS_SYNTAX)
-    template <class TreeBuilder> NEVER_INLINE TreeClassExpression parseClass(TreeBuilder&, FunctionRequirements);
+    template <class TreeBuilder> NEVER_INLINE TreeClassExpression parseClass(TreeBuilder&, FunctionRequirements, ParserClassInfo<TreeBuilder>&);
 #endif
 
     ALWAYS_INLINE int isBinaryOperator(JSTokenType);

Modified: trunk/Source/_javascript_Core/parser/ParserFunctionInfo.h (181972 => 181973)


--- trunk/Source/_javascript_Core/parser/ParserFunctionInfo.h	2015-03-25 21:19:05 UTC (rev 181972)
+++ trunk/Source/_javascript_Core/parser/ParserFunctionInfo.h	2015-03-25 21:33:59 UTC (rev 181973)
@@ -40,6 +40,13 @@
     unsigned bodyStartColumn = 0;
 };
 
+#if ENABLE(ES6_CLASS_SYNTAX)
+template <class TreeBuilder>
+struct ParserClassInfo {
+    const Identifier* className = 0;
+};
+#endif
+
 }
 
 #endif
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to