TypeScript Version: [email protected]
Search Terms: generic computed properties
Code
type withKey<K> = {
[K]: boolean
}
const X: withKey<'foo'> = {
foo: true
}
Expected behavior:
I want to be able to type objects that expect a certain key known at compile time. This is useful when writing methods that connect a data source to let's say a React view e.g.
connectToApi(View, API, 'prop')
// 'prop' should be in the interface of `View`
Actual behavior:
Compiler fails with:
A computed property name in a type literal must refer to an expression
whose type is a literal type or a 'unique symbol' type.
Playground Link: https://agentcooper.github.io/typescript-play/#code/C4TwDgpgBA7glsAFgaQiAPMgfFAvFAbwCgpSoBtZAXQC4oAjAe0YBsIBDAOyIF8iiAxo04BnYFAAadeElQYA5ADNm8nPmJkoyxnWAAnAK4Re-IaPEBNaQhRp0SlWsIky9dnt2HjfIA
Related Issues: Don't know if this is related https://github.com/Microsoft/TypeScript/issues/13948
Maybe?
type withKey<K extends string> = Record<K, boolean>
This is already possible:
type withKey<K extends string | number | symbol> = {
[k in K]: boolean
}
Perfect, thank you very much!
@gigobyte any idea why your example doesn't work if the K part is inferred as follows:
abstract class Message<T extends string> {}
type MessageString<T> = T extends Message<infer S> ? S : never
type WithKey<M> = {
[m in MessageString<M>]: boolean
}
class M1 extends Message<'M1'>{}
class C1 implements WithKey<M1>{} // ISSUE: 'M1' property is not enforced here
I figured out that the issue has to do with the inference being made to String as opposed to a Literal; hardcodedly inferring the correct literal type fixes the issue:
abstract class Message<T extends string> {}
type MessageString<T> = T extends Message<infer S> ? 'M1' : never // infer 'M1' instead of string
type WithKey<M> = {
[m in MessageString<M>]: boolean
}
class Base {[index:string]: boolean }
class M1 extends Message<'M1'>{}
class C1 extends Base implements WithKey<M1>{} // NON-ISSUE: 'M1' property is enforced here
The question now is how does one arrange for a literal inference?
related issue: https://github.com/microsoft/TypeScript/issues/27704
Resolution:
abstract class Message<T extends string> {}
type MessageString<T> = T extends Message<infer S> ? S : never
type WithKey<M> = {
[m in MessageString<M>]: boolean
}
class Base {[index:string]: boolean }
//class M1 extends Message<'M1'>{}
type M1 = Message<'M1'> // use a *simple* type; no union or intersection
class C1 extends Base implements WithKey<M1>{} // NON-ISSUE: 'M1' property is enforced here
Most helpful comment
This is already possible: