+2015-05-01 Ryosuke Niwa <rniwa@webkit.org>
+
+ Class syntax should allow string and numeric identifiers for method names
+ https://bugs.webkit.org/show_bug.cgi?id=144254
+
+ Reviewed by Darin Adler.
+
+ Added a test and rebaselined other tests per syntax error message change.
+
+ * js/class-syntax-declaration-expected.txt:
+ * js/class-syntax-expression-expected.txt:
+ * js/class-syntax-string-and-numeric-names-expected.txt: Added.
+ * js/class-syntax-string-and-numeric-names.html: Added.
+ * js/class-syntax-super-expected.txt:
+ * js/script-tests/class-syntax-declaration.js:
+ * js/script-tests/class-syntax-expression.js:
+ * js/script-tests/class-syntax-string-and-numeric-names.js: Added.
+ * js/script-tests/class-syntax-super.js:
+
2015-05-01 Brent Fulgham <bfulgham@apple.com>
Create a set of initial scroll snap point tests
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 { ( } threw exception SyntaxError: Unexpected token '('.
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 { get constructor() {} } threw exception SyntaxError: Cannot declare a getter or setter named 'constructor'..
PASS A.prototype.constructor is A
PASS x = class threw exception SyntaxError: Unexpected end of script.
PASS x = class { threw exception SyntaxError: Unexpected end of script.
-PASS x = class { ( } threw exception SyntaxError: Unexpected token '('. Expected an identifier..
+PASS x = class { ( } threw exception SyntaxError: Unexpected token '('.
PASS x = class {} did not throw exception.
PASS x = class { constructor() {} constructor() {} } threw exception SyntaxError: Cannot declare multiple constructors in a single class..
PASS x = class { get constructor() {} } threw exception SyntaxError: Cannot declare a getter or setter named 'constructor'..
--- /dev/null
+Tests for string and numeric method names for ES6 class syntax
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS constructorCallCount = 0; new (class { "constructor"() { constructorCallCount++; } }); constructorCallCount is 1
+PASS class A { 'constructor'() { constructorCallCount++; } }; new A; constructorCallCount is 2
+PASS new (class { constructor() {} "constructor"() {} }); threw exception SyntaxError: Cannot declare multiple constructors in a single class..
+PASS new (class { constructor() {} static "prototype"() {} }); threw exception SyntaxError: Cannot declare a static method named 'prototype'..
+PASS class A { 'foo'() { return 3; } }; (new A).foo() is 3
+PASS class A { get 'foo'() { return 4; } }; (new A).foo is 4
+PASS class A { get 'foo'() { return 4; } }; A.foo is undefined
+PASS class A { static get 'foo'() { return 5; } }; A.foo is 5
+PASS class A { static get 'foo'() { return 5; } }; (new A).foo is undefined
+PASS fooValue = 0; X = class { set 'foo'(value) { fooValue = value; } }; (new X).foo = 6; fooValue is 6
+PASS X.foo = 7; fooValue is 6
+PASS fooValue = 0; X = class { static set 'foo'(value) { fooValue = value; } }; X.foo = 8; fooValue is 8
+PASS (new X).foo = 7; fooValue is 8
+PASS X = class { get 'foo'() { return 9 } set 'foo'(x) { } }; x = new X; x.foo is 9
+PASS X.foo is undefined
+PASS fooValue = 0; X = class { get 'foo'() { } set 'foo'(x) { fooValue = x } }; (new X).foo = 9; fooValue is 9
+PASS X.foo = 10; fooValue is 9
+PASS X = class { static set 'foo'(x) { } static get 'foo'() { return 10 } }; X.foo is 10
+PASS (new X).foo is undefined
+PASS fooValue = 0; X = class { static set 'foo'(x) { fooValue = x } static get 'foo'() { } }; X.foo = 11; fooValue is 11
+PASS (new X).foo = 12; fooValue is 11
+PASS class A { get 0() { return 20; } }; (new A)[0] is 20
+PASS class A { get 0() { return 20; } }; A[0] is undefined
+PASS class A { static get 1() { return 21; } }; A[1] is 21
+PASS class A { static get 1() { return 21; } }; (new A)[1] is undefined
+FAIL setterValue = 0; X = class { set 2(x) { setterValue = x; } }; (new X)[2] = 22; setterValue should be 22. Was 0.
+FAIL X[2] = 23; setterValue should be 22. Was 0.
+PASS setterValue = 0; X = class { static set 3(x) { setterValue = x; } }; X[3] = 23; setterValue is 23
+PASS (new X)[3] = 23; setterValue is 23
+PASS X = class { get 4() { return 24 } set 4(x) { } }; x = new X; x[4] is 24
+PASS X[4] is undefined
+FAIL setterValue = 0; X = class { get 5() { } set 5(x) { setterValue = x; } }; (new X)[5] = 25; setterValue should be 25. Was 0.
+FAIL X[5] = 26; setterValue should be 25. Was 0.
+PASS X = class { static set 6(x) { } static get 6() { return 26 } }; X[6] is 26
+PASS (new X)[6] is undefined
+PASS setterValue = 0; X = class { static set 7(x) { setterValue = x } static get 7() { } }; X[7] = 27; setterValue is 27
+PASS (new X)[7] = 28; setterValue is 27
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
--- /dev/null
+<!DOCTYPE html>
+<html>
+<body>
+<script src="../resources/js-test-pre.js"></script>
+<script src="script-tests/class-syntax-string-and-numeric-names.js"></script>
+<script src="../resources/js-test-post.js"></script>
+</body>
+</html>
PASS (new Derived).baseMethodInGetterSetter = 1; valueInSetter is (new Base).baseMethod
PASS Derived.staticMethod() is "base3"
PASS (new SecondDerived).chainMethod() is ["base", "derived", "secondDerived"]
-PASS x = class extends Base { constructor() { super(); } super() {} } threw exception SyntaxError: Unexpected keyword 'super'. Expected an identifier..
+PASS x = class extends Base { constructor() { super(); } super() {} } threw exception SyntaxError: Unexpected keyword 'super'.
PASS x = class extends Base { constructor() { super(); } method() { super() } } threw exception SyntaxError: Cannot call super() outside of a class constructor..
PASS x = class extends Base { constructor() { super(); } method() { super } } threw exception SyntaxError: Cannot reference super..
PASS x = class extends Base { constructor() { super(); } method() { return new super } } did not throw exception.
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.'");
+shouldThrow("class X { ( }", "'SyntaxError: Unexpected token \\'(\\''");
shouldNotThrow("class X {}");
shouldThrow("class X { constructor() {} constructor() {} }", "'SyntaxError: Cannot declare multiple constructors in a single class.'");
shouldThrow("x = class", "'SyntaxError: Unexpected end of script'");
shouldThrow("x = class {", "'SyntaxError: Unexpected end of script'");
-shouldThrow("x = class { ( }", "'SyntaxError: Unexpected token \\'(\\'. Expected an identifier.'");
+shouldThrow("x = class { ( }", "'SyntaxError: Unexpected token \\'(\\''");
shouldNotThrow("x = class {}");
shouldThrow("x = class { constructor() {} constructor() {} }", "'SyntaxError: Cannot declare multiple constructors in a single class.'");
--- /dev/null
+description('Tests for string and numeric method names for ES6 class syntax');
+
+shouldBe("constructorCallCount = 0; new (class { \"constructor\"() { constructorCallCount++; } }); constructorCallCount", "1");
+shouldBe("class A { 'constructor'() { constructorCallCount++; } }; new A; constructorCallCount", "2");
+shouldThrow("new (class { constructor() {} \"constructor\"() {} });", "'SyntaxError: Cannot declare multiple constructors in a single class.'");
+shouldThrow("new (class { constructor() {} static \"prototype\"() {} });", "'SyntaxError: Cannot declare a static method named \\'prototype\\'.'");
+
+shouldBe("class A { 'foo'() { return 3; } }; (new A).foo()", "3");
+
+shouldBe("class A { get 'foo'() { return 4; } }; (new A).foo", "4");
+shouldBe("class A { get 'foo'() { return 4; } }; A.foo", "undefined");
+shouldBe("class A { static get 'foo'() { return 5; } }; A.foo", "5");
+shouldBe("class A { static get 'foo'() { return 5; } }; (new A).foo", "undefined");
+
+shouldBe("fooValue = 0; X = class { set 'foo'(value) { fooValue = value; } }; (new X).foo = 6; fooValue", "6");
+shouldBe("X.foo = 7; fooValue", "6");
+shouldBe("fooValue = 0; X = class { static set 'foo'(value) { fooValue = value; } }; X.foo = 8; fooValue", "8");
+shouldBe("(new X).foo = 7; fooValue", "8");
+
+shouldBe("X = class { get 'foo'() { return 9 } set 'foo'(x) { } }; x = new X; x.foo", "9");
+shouldBe("X.foo", "undefined");
+shouldBe("fooValue = 0; X = class { get 'foo'() { } set 'foo'(x) { fooValue = x } }; (new X).foo = 9; fooValue", "9");
+shouldBe("X.foo = 10; fooValue", "9");
+
+shouldBe("X = class { static set 'foo'(x) { } static get 'foo'() { return 10 } }; X.foo", "10");
+shouldBe("(new X).foo", "undefined");
+shouldBe("fooValue = 0; X = class { static set 'foo'(x) { fooValue = x } static get 'foo'() { } }; X.foo = 11; fooValue", "11");
+shouldBe("(new X).foo = 12; fooValue", "11");
+
+
+shouldBe("class A { get 0() { return 20; } }; (new A)[0]", "20");
+shouldBe("class A { get 0() { return 20; } }; A[0]", "undefined");
+shouldBe("class A { static get 1() { return 21; } }; A[1]", "21");
+shouldBe("class A { static get 1() { return 21; } }; (new A)[1]", "undefined");
+
+// FIXME: This test case fails due to webkit.org/b/144252.
+shouldBe("setterValue = 0; X = class { set 2(x) { setterValue = x; } }; (new X)[2] = 22; setterValue", "22");
+shouldBe("X[2] = 23; setterValue", "22");
+
+shouldBe("setterValue = 0; X = class { static set 3(x) { setterValue = x; } }; X[3] = 23; setterValue", "23");
+shouldBe("(new X)[3] = 23; setterValue", "23");
+
+shouldBe("X = class { get 4() { return 24 } set 4(x) { } }; x = new X; x[4]", "24");
+shouldBe("X[4]", "undefined");
+
+// FIXME: This test case fails due to webkit.org/b/144252.
+shouldBe("setterValue = 0; X = class { get 5() { } set 5(x) { setterValue = x; } }; (new X)[5] = 25; setterValue", "25");
+shouldBe("X[5] = 26; setterValue", "25");
+
+shouldBe("X = class { static set 6(x) { } static get 6() { return 26 } }; X[6]", "26");
+shouldBe("(new X)[6]", "undefined");
+shouldBe("setterValue = 0; X = class { static set 7(x) { setterValue = x } static get 7() { } }; X[7] = 27; setterValue", "27");
+shouldBe("(new X)[7] = 28; setterValue", "27");
shouldBe('(new Derived).baseMethodInGetterSetter = 1; valueInSetter', '(new Base).baseMethod');
shouldBe('Derived.staticMethod()', '"base3"');
shouldBe('(new SecondDerived).chainMethod()', '["base", "derived", "secondDerived"]');
-shouldThrow('x = class extends Base { constructor() { super(); } super() {} }', '"SyntaxError: Unexpected keyword \'super\'. Expected an identifier."');
+shouldThrow('x = class extends Base { constructor() { super(); } super() {} }', '"SyntaxError: Unexpected keyword \'super\'"');
shouldThrow('x = class extends Base { constructor() { super(); } method() { super() } }',
'"SyntaxError: Cannot call super() outside of a class constructor."');
shouldThrow('x = class extends Base { constructor() { super(); } method() { super } }', '"SyntaxError: Cannot reference super."');
+2015-05-01 Ryosuke Niwa <rniwa@webkit.org>
+
+ Class syntax should allow string and numeric identifiers for method names
+ https://bugs.webkit.org/show_bug.cgi?id=144254
+
+ Reviewed by Darin Adler.
+
+ Added the support for string and numeric identifiers in class syntax.
+
+ * parser/Parser.cpp:
+ (JSC::Parser<LexerType>::parseFunctionInfo): Instead of using ConstructorKind to indicate whether we're
+ inside a class or not, use the newly added SuperBinding argument instead. ConstructorKind is now None
+ outside a class constructor as it should be.
+ (JSC::Parser<LexerType>::parseFunctionDeclaration):
+ (JSC::Parser<LexerType>::parseClass): No longer expects an identifier at the beginning of every class
+ element to allow numeric and string method names. For both of those method names, parse it here instead
+ of parseFunctionInfo since it doesn't support either type. Also pass in SuperBinding::Needed.
+ (JSC::Parser<LexerType>::parsePropertyMethod): Call parseFunctionInfo with SuperBinding::NotNeeded since
+ this function is never used to parse a class method.
+ (JSC::Parser<LexerType>::parseGetterSetter): Pass in superBinding argument to parseFunctionInfo.
+ (JSC::Parser<LexerType>::parsePrimaryExpression): Call parseFunctionInfo with SuperBinding::NotNeeded.
+ * parser/Parser.h:
+ * parser/SyntaxChecker.h:
+ (JSC::SyntaxChecker::createProperty):
+
2015-05-01 Filip Pizlo <fpizlo@apple.com>
FTL should use AI more
template <typename LexerType>
template <class TreeBuilder> bool Parser<LexerType>::parseFunctionInfo(TreeBuilder& context, FunctionRequirements requirements, FunctionParseMode mode,
- bool nameIsInContainingScope, ConstructorKind ownerClassKind, int functionKeywordStart, ParserFunctionInfo<TreeBuilder>& info)
+ bool nameIsInContainingScope, ConstructorKind constructorKind, SuperBinding expectedSuperBinding, int functionKeywordStart, ParserFunctionInfo<TreeBuilder>& info)
{
AutoPopScopeRef functionScope(this, pushScope());
functionScope->setIsFunction();
// BytecodeGenerator emits code to throw TypeError when a class constructor is "call"ed.
// Set ConstructorKind to None for non-constructor methods of classes.
- bool isClassConstructor = mode == MethodMode && info.name && *info.name == m_vm->propertyNames->constructor;
if (m_defaultConstructorKind != ConstructorKind::None) {
- ownerClassKind = m_defaultConstructorKind;
- isClassConstructor = true;
+ constructorKind = m_defaultConstructorKind;
+ expectedSuperBinding = m_defaultConstructorKind == ConstructorKind::Derived ? SuperBinding::Needed : SuperBinding::NotNeeded;
}
- ConstructorKind constructorKind = isClassConstructor ? ownerClassKind : ConstructorKind::None;
+ bool isClassConstructor = constructorKind != ConstructorKind::None;
info.openBraceOffset = m_token.m_data.offset;
info.bodyStartLine = tokenLine();
}
if (functionScope->hasDirectSuper()) {
semanticFailIfTrue(!isClassConstructor, "Cannot call super() outside of a class constructor");
- semanticFailIfTrue(ownerClassKind != ConstructorKind::Derived, "Cannot call super() in a base class constructor");
+ semanticFailIfTrue(constructorKind != ConstructorKind::Derived, "Cannot call super() in a base class constructor");
}
if (functionScope->needsSuperBinding())
- semanticFailIfTrue(ownerClassKind != ConstructorKind::Derived, "super can only be used in a method of a derived class");
+ semanticFailIfTrue(expectedSuperBinding == SuperBinding::NotNeeded, "super can only be used in a method of a derived class");
info.closeBraceOffset = m_token.m_data.offset;
unsigned closeBraceLine = m_token.m_data.line;
unsigned functionKeywordStart = tokenStart();
next();
ParserFunctionInfo<TreeBuilder> info;
- failIfFalse((parseFunctionInfo(context, FunctionNeedsName, FunctionMode, true, ConstructorKind::None, functionKeywordStart, info)), "Cannot parse this function");
+ failIfFalse((parseFunctionInfo(context, FunctionNeedsName, FunctionMode, true, ConstructorKind::None, SuperBinding::NotNeeded,
+ functionKeywordStart, info)), "Cannot parse this function");
failIfFalse(info.name, "Function statements must have a name");
failIfFalseIfStrict(declareVariable(info.name), "Cannot declare a function named '", info.name->impl(), "' in strict mode");
return context.createFuncDeclStatement(location, info);
if (isStaticMethod)
next();
- matchOrFail(IDENT, "Expected an identifier");
-
+ // FIXME: Figure out a way to share more code with parseProperty.
const CommonIdentifiers& propertyNames = *m_vm->propertyNames;
- const Identifier& ident = *m_token.m_data.ident;
- bool isGetter = ident == propertyNames.get;
- bool isSetter = ident == propertyNames.set;
+ const Identifier* ident = nullptr;
+ bool isGetter = false;
+ bool isSetter = false;
+ switch (m_token.m_type) {
+ case STRING:
+ ident = m_token.m_data.ident;
+ next();
+ break;
+ case IDENT:
+ ident = m_token.m_data.ident;
+ isGetter = *ident == propertyNames.get;
+ isSetter = *ident == propertyNames.set;
+ break;
+ case DOUBLE:
+ case INTEGER:
+ ident = &m_parserArena.identifierArena().makeNumericIdentifier(const_cast<VM*>(m_vm), m_token.m_data.doubleValue);
+ next();
+ break;
+ default:
+ failDueToUnexpectedToken();
+ }
TreeProperty property;
const bool alwaysStrictInsideClass = true;
if (isGetter || isSetter) {
nextExpectIdentifier(LexerFlagsIgnoreReservedWords);
- property = parseGetterSetter(context, alwaysStrictInsideClass, isGetter ? PropertyNode::Getter : PropertyNode::Setter, methodStart, constructorKind, SuperBinding::Needed);
+ property = parseGetterSetter(context, alwaysStrictInsideClass, isGetter ? PropertyNode::Getter : PropertyNode::Setter, methodStart,
+ ConstructorKind::None, SuperBinding::Needed);
failIfFalse(property, "Cannot parse this method");
} else {
ParserFunctionInfo<TreeBuilder> methodInfo;
- failIfFalse((parseFunctionInfo(context, FunctionNeedsName, isStaticMethod ? FunctionMode : MethodMode, false, constructorKind, methodStart, methodInfo)), "Cannot parse this method");
- failIfFalse(methodInfo.name, "method must have a name");
- failIfFalse(declareVariable(methodInfo.name), "Cannot declare a method named '", methodInfo.name->impl(), "'");
-
- bool isConstructor = !isStaticMethod && *methodInfo.name == propertyNames.constructor;
- if (isConstructor)
- methodInfo.name = className;
+ bool isConstructor = !isStaticMethod && *ident == propertyNames.constructor;
+ failIfFalse((parseFunctionInfo(context, FunctionNoRequirements, isStaticMethod ? FunctionMode : MethodMode, false,
+ isConstructor ? constructorKind : ConstructorKind::None, SuperBinding::Needed, methodStart, methodInfo)), "Cannot parse this method");
+ failIfFalse(ident && declareVariable(ident), "Cannot declare a method named '", methodInfo.name->impl(), "'");
+ methodInfo.name = isConstructor ? className : ident;
TreeExpression method = context.createFunctionExpr(methodLocation, methodInfo);
if (isConstructor) {
}
// FIXME: Syntax error when super() is called
- semanticFailIfTrue(isStaticMethod && *methodInfo.name == propertyNames.prototype,
+ semanticFailIfTrue(isStaticMethod && methodInfo.name && *methodInfo.name == propertyNames.prototype,
"Cannot declare a static method named 'prototype'");
property = context.createProperty(methodInfo.name, method, PropertyNode::Constant, PropertyNode::Unknown, alwaysStrictInsideClass, SuperBinding::Needed);
}
JSTokenLocation methodLocation(tokenLocation());
unsigned methodStart = tokenStart();
ParserFunctionInfo<TreeBuilder> methodInfo;
- failIfFalse((parseFunctionInfo(context, FunctionNoRequirements, MethodMode, false, ConstructorKind::None, methodStart, methodInfo)), "Cannot parse this method");
+ failIfFalse((parseFunctionInfo(context, FunctionNoRequirements, MethodMode, false, ConstructorKind::None, SuperBinding::NotNeeded,
+ methodStart, methodInfo)), "Cannot parse this method");
methodInfo.name = methodName;
return context.createFunctionExpr(methodLocation, methodInfo);
}
ParserFunctionInfo<TreeBuilder> info;
if (type == PropertyNode::Getter) {
failIfFalse(match(OPENPAREN), "Expected a parameter list for getter definition");
- failIfFalse((parseFunctionInfo(context, FunctionNoRequirements, GetterMode, false, constructorKind, getterOrSetterStartOffset, info)), "Cannot parse getter definition");
+ failIfFalse((parseFunctionInfo(context, FunctionNoRequirements, GetterMode, false, constructorKind, superBinding,
+ getterOrSetterStartOffset, info)), "Cannot parse getter definition");
} else {
failIfFalse(match(OPENPAREN), "Expected a parameter list for setter definition");
- failIfFalse((parseFunctionInfo(context, FunctionNoRequirements, SetterMode, false, constructorKind, getterOrSetterStartOffset, info)), "Cannot parse setter definition");
+ failIfFalse((parseFunctionInfo(context, FunctionNoRequirements, SetterMode, false, constructorKind, superBinding,
+ getterOrSetterStartOffset, info)), "Cannot parse setter definition");
}
if (stringPropertyName)
return context.createGetterOrSetterProperty(location, type, strict, stringPropertyName, info, superBinding);
next();
ParserFunctionInfo<TreeBuilder> info;
info.name = &m_vm->propertyNames->nullIdentifier;
- failIfFalse((parseFunctionInfo(context, FunctionNoRequirements, FunctionMode, false, ConstructorKind::None, functionKeywordStart, info)), "Cannot parse function expression");
+ failIfFalse((parseFunctionInfo(context, FunctionNoRequirements, FunctionMode, false, ConstructorKind::None, SuperBinding::NotNeeded,
+ functionKeywordStart, info)), "Cannot parse function expression");
return context.createFunctionExpr(location, info);
}
#if ENABLE(ES6_CLASS_SYNTAX)
template <class TreeBuilder> NEVER_INLINE TreeDeconstructionPattern parseDeconstructionPattern(TreeBuilder&, DeconstructionKind, int depth = 0);
template <class TreeBuilder> NEVER_INLINE TreeDeconstructionPattern tryParseDeconstructionPatternExpression(TreeBuilder&);
- template <class TreeBuilder> NEVER_INLINE bool parseFunctionInfo(TreeBuilder&, FunctionRequirements, FunctionParseMode, bool nameIsInContainingScope, ConstructorKind, int functionKeywordStart, ParserFunctionInfo<TreeBuilder>&);
+ template <class TreeBuilder> NEVER_INLINE bool parseFunctionInfo(TreeBuilder&, FunctionRequirements, FunctionParseMode, bool nameIsInContainingScope, ConstructorKind, SuperBinding, int functionKeywordStart, ParserFunctionInfo<TreeBuilder>&);
#if ENABLE(ES6_CLASS_SYNTAX)
template <class TreeBuilder> NEVER_INLINE TreeClassExpression parseClass(TreeBuilder&, FunctionRequirements, ParserClassInfo<TreeBuilder>&);
#endif