Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 473
cpcallen Avatar asked Apr 05 '18 22:04

cpcallen


2 Answers

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

like image 104
georg Avatar answered Oct 19 '22 04:10

georg


@georg’s answer seems to be the right ES6+ interpretation, but it looks like the behaviour isn’t new, either. From ES5.1 PutValue:

  1. Else if IsPropertyReference(V), then

    a. If HasPrimitiveBase(V) is false, then let put be the [[Put]] internal method of base, otherwise let put be the special [[Put]] internal method defined below.

    b. Call the put internal method using base as its this value, and passing GetReferencedName(V) for the property name, W for the value, and IsStrictReference(V) for the Throw flag.

and in the referenced [[Put]]:

  1. Else, this is a request to create an own property on the transient object O

    a. If Throw is true, then throw a TypeError exception.

It feels like I’m probably misreading something… but what else could the rather pointed “this is a request to create an own property on the transient object O” be referring to?

like image 43
Ry- Avatar answered Oct 19 '22 03:10

Ry-