Search code examples
javascriptecmascript-6typeerrorecmascript-5primitive

Why does `"foo".bar = 42;` throw `TypeError` in strict mode in ES6?


According to the ES5.1 spec, the program "use strict;" "foo".bar = 42; causes a String object to be created, assigns to a property on it, and then throws the object away, resulting in no observable effects - including any exceptions. (The absence of effect can be confirmed by trying it in an ES5-compatible JS implementation like that in Opera 12.)

In modern JS implementations, it throws a TypeError instead—try it:

"use strict"; "foo".bar = 42;

I am pretty sure the new behaviour is mandated by the ES6 spec, but despite reading the relevant sections several times I cannot see where it is specified that TypeError be thrown. In fact, the key parts appear to be unchanged:

6.2.3.2 PutValue (V, W)#

  1. ReturnIfAbrupt(V).
  2. ReturnIfAbrupt(W).
  3. If Type(V) is not Reference, throw a ReferenceError exception.
  4. Let base be GetBase(V).
  5. If IsUnresolvableReference(V) is true, then
  6. Else if IsPropertyReference(V) is true, then
    • a. If HasPrimitiveBase(V) is true, then
      • i. Assert: In this case, base will never be null or undefined.
      • ii. Set base to ToObject(base).
    • b. Let succeeded be ? base.[[Set]](GetReferencedName(V), W, GetThisValue(V)).
    • c. ReturnIfAbrupt(succeeded).
    • d. If succeeded is false and IsStrictReference(V) is true, throw a TypeError exception.
    • e. Return.

Where does the spec (ES6 or later) mandate throwing TypeError?


Solution

  • I guess it's here:

    http://www.ecma-international.org/ecma-262/7.0/#sec-ordinaryset

    9.1.9.1. OrdinarySet (O, P, V, Receiver)

    [...]

    4.b. If Type(Receiver) is not Object, return false.

    (Previously called [[Set]], in ES6 §9.1.9.)

    Although PutValue promotes the base to an object, it doesn't do the same with the receiver -- GetThisValue(V) is still called on the original V (with a primitive base). So, GetThisValue returns a primitive, OrdinarySet.4b fails to assign a freshly created ownDesc and returns false, and this in turn causes PutValue.6d to throw a TypeError, provided the reference is strict.

    The corresponding part of V8 seems to follow the same logic:

    Maybe<bool> Object::AddDataProperty(....
      if (!it->GetReceiver()->IsJSReceiver()) {
        return CannotCreateProperty(...
    

    https://github.com/v8/v8/blob/3b39fc4dcdb6593013c497fc9e28a1d73dbcba03/src/objects.cc#L5140