Repository: TypeScript
Issue: RFC: Support __proto__ literal in object initializers
Labels: Suggestion,Awaiting More Feedback

This is a new issue to specifically propose and elaborate on a feature I raised [in this comment](https://github.com/microsoft/TypeScript/issues/30587#issuecomment-614855795) on #30587.

## Search Terms

- \_\_proto__
- prototype
- object literal
- object spread
- object initializer

## Suggestion
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.](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-__proto__-property-names-in-object-initializers)


### Basic support
Given the following:
```ts
const foo = {
  __proto__: { a: "a" },
  b: "b"
};
```

Typescript currently thinks that `foo` is the following shape:
```ts
type foo = {
  ["__proto__"]: { a: string };
  b: string;
};
```

when in reality, it is:
```ts
type foo = {
  a: string;
  b: string;
};
```

TypeScript should be able to correctly detect the type of this object initialization.

### Strict validity checks
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:

```ts
const invalid = { __proto__: "hello" }
```

### Correct handling of computed properties

It's important to note that per the spec, `__proto__` literals are _not_ the same as regular property assignments.

This object initialization, for example:

```ts
const foo = {
  __proto__: { a: "a" },
  b: "b",
  ["__proto__"]: { c: "c"},
};
```
creates an object of the following shape:
```ts
type foo = {
  a: string;
  b: string;
  ["__proto__"]: { c: string};
};
```

Given this, I would recommend that a `__proto__` literal be forbidden in _type/interface definitions_, such that this is considered a syntax error:
```ts
type foo = {
  __proto__: { a: string }
}
```
while this is an allowable way to specify a **property** named `__proto__` on the type `foo`.
```ts
type foo = {
  ["__proto__"]: string
}
```

## Use-Cases & Examples

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:
```ts
// 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. 

```ts
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`?)

```ts
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:
```ts
const safeSpreadMapObject: { [key: string]: boolean | undefined } = {
  __proto__: null,
  ...someMapObject,
  foo: false
};

console.log(typeof safeSpreadMapObject["constructor"]);
// => undefined

console.log(typeof safeSpreadMapObject["foo"]);
// => boolean
```


## Checklist

My suggestion meets these guidelines:

* [x] This wouldn't be a breaking change in existing TypeScript/JavaScript code
* [x] This wouldn't change the runtime behavior of existing JavaScript code
* [x] This could be implemented without emitting different JS based on the types of the expressions
* [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
* [x] This feature would agree with the rest of [TypeScript's Design Goals](https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals).


## References
- [Object initialization: Prototype mutation (MDN)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Prototype_mutation)
- [\_\_proto__ Property Names in Object Initializers (ECMAScript Spec)](https://tc39.es/ecma262/#sec-__proto__-property-names-in-object-initializers)