Typescript: Mapped type over string enum keys should have quick info showing computed enum members as keys, not identifiers/strings

Created on 7 Nov 2019  Â·  4Comments  Â·  Source: microsoft/TypeScript


TypeScript Version: 3.5.3 - 3.7.2


Search Terms: enum, Record, Pick

Code

enum MyTypes {
  a = 'a',
  b = 'b',
}

type TypeMap = Record<MyTypes, string>;
// the language server tells us that this type is actually the same as the following annotationed type
// but, if I use the following type instead, then no error will occur
// type TypeMap = {
//   a: string;
//   b: string;
// };

const map: TypeMap = {
  a: 'text a',
  b: 'text b',
}

//   error TS2344: Type '"a"' does not satisfy the constraint 'MyTypes'.
type NewTypeMap = Pick<TypeMap, 'a'>;

const newMap: NewTypeMap = {
  a: 'abc',
}

Expected behavior:

Type inference works as expected, no error occurred.

Actual behavior:

 error TS2344: Type '"a"' does not satisfy the constraint 'MyTypes'.

Playground Link:

https://www.typescriptlang.org/play/?ssl=21&ssc=2&pln=1&pc=1#code/KYOwrgtgBAsgngFTgB2AZygbwFBSgQygF4oByfUgGlygCNizarsBfbbAFxWCiVRnzIGAJWABjAPYAnACYAeeH3SUoaDlICWIAOYA+ANzYA9EahdUvbgKEkcJvAQBcq9Vu2H7eWs7WadH0xZDbEkQNSgIQWclawYcPHxnUg5gAA8OAmYvJJT0umY2Tm4oADlgAHcYwQYABQ0xAGs5KuQVclIDdlDwkArrZzLKq2rbGkSyfFoxAqA

Related Issues:

Bug Quick Info

All 4 comments

String enums are designed to enforce that you generally _use_ the enum instead of its string literal member values. The idea is that the values should be treated as opaque and not relied upon directly. This makes future refactoring easier. This can be most easily observed by:

enum MyTypes {
  a = 'a',
  b = 'b',
}

let a: MyTypes.a = 'a';
//  ^ Type '"a"' is not assignable to type 'MyTypes.a'. (2322)

Essentially this is a duplicate of https://github.com/microsoft/TypeScript/issues/33283, though it’s not immediately obvious that the question is the same.


the language server tells us that this type is actually the same

I think you could argue that this is a bug, or non-ideal. The quick info for TypeMap in type TypeMap = Record<MyTypes, string> should be

type TypeMap = {
  [MyTypes.a]: string;
  [MyTypes.b]: string;
};

This fits with the opacity that we normally give string enum members. I don’t think we really _guarantee_ that quick info has the exact semantics of source code (we don’t even guarantee that it’s valid syntax), but since a better alternative is readily available, I could see this as a bug. I’m going to re-title for that. Thanks!

@andrewbranch Thanks for you explanation! I read chapter enum in typescript handbook and now understand more about this. Also it is intended to keep opacity for values, the behavior is still somehow confusing, since we thought that typescript is “duck typing” or “structural subtyping”.

That’s generally true, but traces of nominality may show up here and there. The type system is 99% structural because we think that’s a good conceptual model of how people write JavaScript, but we don’t guarantee that literally every assignability rule will forever be purely structural—if nominality is a good fit for a certain pattern, we’ll use it there (classes with private members are another example). That said, I wasn’t on the team when string enums were designed, so I don’t have the historical context around that decision.

@andrewbranch I got it, thanks for your detailed and beneficial explanation!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

fwanicka picture fwanicka  Â·  3Comments

dlaberge picture dlaberge  Â·  3Comments

MartynasZilinskas picture MartynasZilinskas  Â·  3Comments

weswigham picture weswigham  Â·  3Comments

wmaurer picture wmaurer  Â·  3Comments