RFC: Support __proto__ literal in object initializers

未关闭
#38,385 17 条评论 77 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

评估

难度
5/5
预计耗时
一周以上
新手友好度
35/100
Issue 类型
功能
描述清晰度
描述清楚
活跃度
停滞
领域
compilers

调研方向

该 issue 未指出任何仓库文件、测试或入口点。请先查看 issue 中链接的对象初始化器和 proto 语义,然后定位相关的 TypeScript checker 测试;done 应涵盖原型推断、有效性检查、计算属性以及所描述的示例。

由索引模型根据 Issue 内容生成。

描述

Awaiting More Feedback Suggestion

This is a new issue to specifically propose and elaborate on a feature I raised in this comment 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.

Basic support

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.

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:

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:

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: string};
};

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
}

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:

// 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

Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

References

主要语言
Go
星标
111k
派生
14.4k
平均合并
1 天 19 小时
30 天内合并 PR
117

贡献指南

打开贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

microsoft/TypeScript 的其他 Issue

查看 microsoft/TypeScript 的全部 Issue

相似的 Issue

更多 Go Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。