TypeScript 无标识/品牌键禁用记录值类型的检查/推断,

vs3odd8k  于 2个月前  发布在  TypeScript
关注(0)|答案(5)|浏览(51)

Bug报告

🔎 搜索词

名义,记录,品牌

🕗 版本与回归信息

3.3.3 至 4.3.2

  • 这是我在每个版本中尝试的行为,我查阅了关于名义类型和记录类型的FAQ条目

⏯ Playground链接

带有相关代码的Playground链接

💻 代码

type Id = string & { __id: 'id' }

type User = {
  id: Id;
  name: string;
}

type Users = Record<Id, User>;

type Users2 = Map<Id, User>;

type Users3 = Record<string, User>;

// This will not produce an error
// @ts-expect-error No name should be an error!
const users: Users = { id0: { id: 'id', } }

// type: [string, unknown][]
const entries = Object.entries(users);

const users2: Users2 = new Map();
// @ts-expect-error No name produces an error
users2.set('id' as Id, { id: 'id' as Id, })

// @ts-expect-error No name produces an error
const users3: Users3 = { id0: { id: 'id' as Id, } }

// type: [string, User][]
const entries3 = Object.entries(users3);

🙁 实际行为

使用名义/品牌类型作为记录类型的键会导致 value 类型的类型检查被跳过。此外,推理也会失败并回退到 unknown ,例如使用 Object.entries 。使用 Map 可以按预期工作,但不能使用 Record 。我知道 Map(真实)和 Record(类型)之间的运行时行为完全不同,我只是提供一个例子。

🙂 预期行为

我希望在使用名义/品牌类型作为记录类型的键时,类型能够得到保留和检查。

2izufjch

2izufjch1#

在这里的行为并不明显;如果你写了

const p: Users = { "foo": SomeUser }

,这似乎不符合对象契约,因为 "foo" 不是一个 Id ,但你实际上无法构造那种对象。字符串对象的交集已经有点滥用了,可以认为当前行为是正确的。
我怀疑改变这种行为会是一个破坏性的变化。

nfeuvbwi

nfeuvbwi2#

感谢您的解释!这确实有道理。

不过我担心的是,在您的示例中,SomeUser 可以是任何东西,TS 无法捕获它:

const SomeUser = false
const p: Users = { "foo": SomeUser } // no error

TS 至少不应该反对 Users 类型上没有 string: boolean 索引签名吗?

niwlg2el

niwlg2el3#

我注意到在我检查的每个编译器版本中,品牌字符串键记录都被错误地分配给了彼此:

type FooString = string & { __foo: any };
type BarString = string & { __bar: any };

declare let fooKeyed: Record<FooString, unknown>;
declare let barKeyed: Record<BarString, unknown>;

fooKeyed = barKeyed;

Playground Link
我认为这是这里讨论的相同问题,但我不确定。这也可能是一个 #15746 或一些新的东西。
几年前,我注意到对品牌字符串键的支持得到了改进(尽管我不记得是哪个版本了,而且路线图也没有激发我的记忆),所以我发现这个漏洞令人惊讶。

nfzehxib

nfzehxib4#

@RyanCavanaugh,虽然这是真的,但这仍然是string & { __id?: 'id' }的问题-几乎所有的string都可以分配给。

wnvonmuf

wnvonmuf5#

另一个同样问题的变体在这里展示:

// Variant of https://www.typescriptlang.org/play?#code/FAFwngDgpgBAQgJwIYDsAmMC8MDOIECWKA5jAGQwDeMA+jSEsQFwwDkYA9qzAL6iSwAgliowUSALZQWeQiV7B+0GAFkkEAPIAzQThEAlKAGMOCNAB5EqNABoYggHyKTKPDC1ICAGyhoA0lBgAMwirACM3Eh6VujAAPRxMACiAB7QRiC+8YkAAiA4ALRQacYgRQgIpiwAKgDKAExB9fUsGgBGAFalMF4EmcheMBJIYDAcKF6jOOkEWqMA1igcAO4oMBCV0AggBFA4dtZs80Ro3Ggce2IcIDDFBG5EMODKrIKswC5uHt6+cFHSqnU2l0ImoAG1vj5-IEggBdFjUY7oFisLwcABuUG4PAUH3GbnmgTCoQiMCi8GQsU+Nza-xYak0Oj02HBhLAYXhonEUhRAAsoF40djcdSYGz6qF6pFopS0NkYAA5a63EoZLIJGB5QrFdJlKAVKowOqNZqtTrdXr9JCDYajcaTXAzOZipardabfU7PYHdBsZYgLTVXn3ACSOCVIG01QEbxg50uSxudwea2esFe71FwwgoJgADoC7ScFA7GDxZzqP7A8GcGGI1GYywUABXQW8PhAA

type Brand = string & { __tag: 'yo' }
type A = { name: string }

type MapOfAs = Record<Brand, A>

const failedKey3 = '1' as Brand
// Fails as expected
// @ts-expect-error: TS2322: Object literal may only specify known properties, and 'kind' does not exist in type 'A'
const failedBase: MapOfAs = { [failedKey3]: { kind: 'love' } }

const key1 = '1' as Brand
const base: MapOfAs = { [key1]: { name: 'hello' } }

const key2 = '2' as Brand
// Does not fail as expected
// @ts-expect-error: TS2322: Object literal may only specify known properties, and 'wtfThisIsNotOfTypeA' does not exist in type 'A'
const map = { ...base, [key2]: { wtfThisIsNotOfTypeA: null }}

相关问题