This is a new issue to specifically propose and elaborate on a feature I raised in this comment on #30587.
While accessing or mutating an _existing_ object via the Object.protptype.__proto__ getter/setter is deprecated, to the best of my knowledge defining the prototype of a _new_ object via the object initializer __proto__ literal is very much encouraged.
The rules for specifying the prototype of a new object via these semantics are very well-specified and safe. See the relevant section of the spec here.
Given the following:
const foo = {
__proto__: { a: "a" },
b: "b"
};
Typescript currently thinks that foo is the following shape:
type foo = {
["__proto__"]: { a: string };
b: string;
};
when in reality, it is:
type foo = {
a: string;
b: string;
};
TypeScript should be able to correctly detect the type of this object initialization.
Additionally, TypeScript should prevent invalid __proto__ assignments that are "ignored" by the spec, and require all values to be null or an object. This should fail validation:
const invalid = { __proto__: "hello" }
It's important to note that per the spec, __proto__ literals are _not_ the same as regular property assignments.
This object initialization, for example:
const foo = {
__proto__: { a: "a" },
b: "b",
["__proto__"]: { c: "c"},
};
creates an object of the following shape:
type foo = {
a: string;
b: string;
["__proto__"]: { c: "c"};
};
Given this, I would recommend that a __proto__ literal be forbidden in _type/interface definitions_, such that this is considered a syntax error:
type foo = {
__proto__: { a: string }
}
while this is an allowable way to specify a property named __proto__ on the type foo.
type foo = {
["__proto__"]: string
}
This feature allows TypeScript to correctly understand the shape of objects defined with standard JS semantics. While this pattern isn't especially prevalent, it is an important feature of the language, and should be much more common in one particular use-case where TypeScript currently has a rather severe blind spot:
TypeScript currently _PREVENTS_ the creation of safe indexed objects derived from existing indexed objects. For example:
Given this object, and the goal of "spreading" it into a new map:
// All safe map objects MUST have a `null` prototype.
const someMapObject: { [key: string]: boolean } = Object.create(null);
The following is UNSAFE, and probably the most common approach I see people using. TypeScript _should_ catch this, and _should_ issue a compile-time error. See bug #37963.
const unsafeSpreadMapObject: { [key: string]: boolean | undefined } = {
...someMapObject,
foo: false
};
console.log(typeof unsafeSpreadMapObject["constructor"]);
// => function
console.log(typeof safeSpreadMapObject["foo"]);
// => boolean
The following is also UNSAFE. While using Object.assign and Object.create is a perfectly valid alternative, the any returned by Object.create propagates through the statement and breaks type safety. (Perhaps the result of Object.create should be unknown instead of any?)
const unsafeAssignMapObject: { [key: string]: boolean | undefined } = Object.assign(
Object.create(null),
{ foo: "this is not boolean" }
);
console.log(typeof safeSpreadMapObject["constructor"]);
// => undefined
console.log(typeof safeSpreadMapObject["foo"]);
// => string
This is the SAFE way to accomplish this while using object spreads, but TypeScript currently forbids it, since it lacks support for the __proto__ literal, and incorrectly believes a _property_ of type null is being defined:
const safeSpreadMapObject: { [key: string]: boolean | undefined } = {
__proto__: null,
...someMapObject,
foo: false
};
console.log(typeof safeSpreadMapObject["constructor"]);
// => undefined
console.log(typeof safeSpreadMapObject["foo"]);
// => boolean
My suggestion meets these guidelines:
Just ran across this and was surprised __proto__ in initialisers wasn't already supported.
{ __proto__: myPrototype } is much cleaner than Object.create(myPrototype), especially when you want to merge in additional properties at the same time.
I acknowledge the confusion that __proto__ in initialisers is pretty clear, but obj.__proto__ is confusing and should probably be discouraged, but I'd still love to see it supported.
Also just ran accross this issue.
FWIW: According to @rauschma [0]:
I recommend to avoid the pseudo-property __proto__: As we will see later, not all objects have it.
However, __proto__ in object literals is different. There, it is a built-in feature and always available.
[...]
- The best way to set a prototype is when creating an object – via __proto__ in an object literal or via:
Object.create(proto: Object) : Object
Also, the __proto__ initialiser in object literals is in the process of being moved out of Annex B: https://github.com/tc39/ecma262/pull/2125.
Most helpful comment
Also just ran accross this issue.
FWIW: According to @rauschma [0]: