09.01.11

New ES5 requirement: getters and setters in object literals must not conflict with each other or with data properties, even outside strict mode

Conflicting properties in object literals

Object literals in ECMAScript can contain the same property multiple times:

var obj = { prop: 42, prop: 17 };

How does this behave? The object, when fully initialized, has that property with its last assigned value:

var obj = { prop: 17 }; // same effect

The expression 42 is still evaluated in source order, but that value isn’t found in the final object when construction and initialization completes.

Are conflicting properties desirable?

Duplicating property names is at best innocuous, but at worst it’s the source of bugs. Repeated assignment of the same side effect-free expression is aesthetically unpleasing but harmless. But what if that expression has side effects? Or what if the two expressions are ever made to differ? (This needn’t be purely human error. For example, a conflict might be the result of a bad merge of your changes with changes made by others.) What if a developer accidentally changes the first instance of a property but doesn’t notice the second? You can see how this might cause bugs.

Compatibility

ES5 generally avoids breaking compatibility with ES3. For the sake of existing code, duplicate property names are a syntax error only in strict mode code.

function good() { return { p: 1, p: 2 }; } // okay
function bad() { "use strict"; return { p: 1, p: 2 }; } // ERROR

What about getters and setters?

ES5 standardizes syntax for getters and setters in object literals. Using getters and setters you can write properties which lazily compute their values only when asked. You can also write properties which post-process values assigned to them: to validate them, to transform them at time of assignment, and so on. Getters and setters are new in ES5, so they don’t present compatibility concerns.

Conflicts with accessors are worse than conflicts with data properties. What if a setter and a data property conflict? Properties in an initializer don’t invoke setters, so a conflicting data property might blow away an accessor pair entirely! Also, since getters and setters quite often involve side effects, or reliance on object structure and internals, errant fixes of one of a pair of getters or setters are likely to cause worse problems than conflicting data properties.

Therefore ES5 prohibits conflicting property getters and setters, either with each other or with existing data properties. You can’t have both an accessor and a data property, and you can’t have multiple getters or multiple setters for the same property. This applies even outside strict mode!

/* syntax errors in any code */
({ p: 1, get p() { } });
({ get p() { }, p: 1 });
({ p: 1, set p(v) { } });
({ set p(v) { }, p: 1 });
({ get p() { }, get p() { } });
({ set p(v) { }, set p(v) { } });
({ get p() { }, set p(v) { }, get p() { } });
({ set p(v) { }, get p() { }, set p(v) { } });

/* syntax error only in strict mode code */
function fail() { "use strict"; ({ p: 1, p: 2 }); }

SpiderMonkey and Firefox no longer permit conflicts involving accessor properties in object literals

Firefox 4 nightlies now reject any property-name conflicts in object literals. The only exception is when the object literal is outside strict mode and all assignments are for data properties. Previously we implemented accessor conflict detection only in strict mode, but now Firefox 4 fully conforms to the ES5 specification when parsing object literals. (While I’m here let me give a brief hat-tip to the ECMAScript 5 Conformance Suite for revealing this mistake, the result of spec misreading by multiple SpiderMonkey hackers.)

If you ever have conflicting properties in an object literal, odds are they were a mistake. If you’ve done this only with data properties, no sweat now — but you’ll have to fix that if you ever opt your code into strict mode. If you’ve done this with accessor properties (previously a non-standard, implementation-specific feature), you’ll need to change your code to eliminate the conflict. Conflicts are reported as syntax errors (but note the bug that syntax errors aren’t reported for JavaScript XPCOM components), and they should be easy to fix.

Conclusion

Object literals containing the same property multiple times are bug-prone, as only one of the properties will actually be respected when the code executes. That mistake can’t be fixed for data properties in normal code, but ES5 can prohibit new conflicts involving accessor properties; Firefox now properly treats such conflicts as syntax errors. You can experiment with a version of Firefox with these changes by downloading a nightly build. (Don’t forget to use the profile manager if you want to keep the settings you use with your primary Firefox installation pristine.)

22.08.10

Incompatible ES5 change: literal getter and setter functions must now have exactly zero or one arguments

ECMAScript accessor syntax in SpiderMonkey

For quite some time SpiderMonkey and Mozilla-based browsers have supported user-defined getter and setter functions (collectively, accessors), both programmatically and syntactically. The syntaxes for accessors were once legion, but SpiderMonkey has pared them back almost to the syntax recently codified in ES5 (and added new syntax where required by ES5).

// All valid in ES5
var a = { get x() { } };
var b = { get "y"() { } };
var c = { get 2() { } };

var e = { set x(v) { } };
var f = { set "y"(v) { } };
var g = { set 2(v) { } };

SpiderMonkey has historically parsed literal accessors using a slightly-tweaked version of its function parsing code. Therefore, as previously explained SpiderMonkey would accept essentially anything which could follow function in a function expression as valid accessor syntax in object literals.

ES5 requires accessors have exact numbers of arguments

A consequence of parsing accessors using generalized function parsing is that SpiderMonkey accepted some nonsensicalities, such as no-argument setters or multiple-argument getters or setters:

var o1 = { get p(a, b, c, d, e, f, g) { /* why have any arguments? */ } };
var o2 = { set p() { /* to what value? */ } };
var o3 = { set p(a, b, c) { /* why more than one? */ } };

ES5 accessor syntax sensibly deems such constructs errors: a conforming ES5 implementation would reject all of the above statements.

SpiderMonkey is changing to follow ES5: getters require no arguments, setters require one argument

SpiderMonkey has now been changed to follow ES5. There seemed little to no gain in continuing to support bizarre numbers of arguments when the spec counseled otherwise, and any code which does end up broken is easily fixed.

As always, you can experiment with a version of Firefox with these changes to accessor syntax by downloading a nightly from nightly.mozilla.org. (Don’t forget to use the profile manager if you want to keep the settings you use with your primary Firefox installation pristine.)