Description
Search Terms
const generic inference nested
Suggestion
Let generic function signature specify that type parameters should be inferred as const
Use Cases
Allow writing library functions that specify how they want their generic parameters to be inferred. Right now, this is only possible with generics like <T extends string>(value: T) => ...
, which doesn't cover objects - only literals.
Examples
Let's say you have a basic runtime type-matching function:
const getMatcher = <T>(sample: T) => <U>(value: U): value is T & U => {
// implementation details not important - just a very basic deep-equals
if (sample && typeof sample === 'object') {
return Object.entries(sample).every(([k, v]) => k in value && getMatcher(v)((value as any)[k]))
}
return sample === (value as any)
}
If I try to use this to make a string matcher, it doesn't keep track of literal types:
const fooMatcher = getMatcher('foo') // T is inferred as `string`, not literal `"foo"`
const s = Math.random() < 0.5 ? 'foo' : 'bar'
if (fooMatcher(s)) {
const foo: 'foo' = s // Error: Type '"foo" | "bar"' is not assignable to type '"foo"'.
}
This makes sense as default behaviour, since 'foo'
was passed in as a string. And it can be fixed at the call site with getMatch('foo' as const)
. But this only works for typescript programs, and if we're writing a library, javascript users of that library won't get useful type inference. And it's not really intuitive. Even typescript users of the library will need to know somehow that this trick is available.
It could also be fixed by changing the function signature:
const getMatcher = <T extends string>(sample: T) => ...
But getMatcher
is now limited to only work with strings. This doesn't cover the case of deeply nested objects:
const commentMatcher = getMatcher({
type: 'comment',
payload: {sender: 'abc'},
})
As far as I know, there's no way to tweak the extends
constraint for T
which makes this keep track of the literal type: 'comment'
and sender: 'abc'
values. So this feature request is to allow us to effectively ask the compiler to add as const
to any inferred type parameters:
const getMatcher = <T as const>(sample: T) => <U>(value: U): value is T & U => {
...
}
(syntax is just an idea which reuses the as const
pattern in the generic typedef)
If we could do this, it'd make it easy to use the matcher to write useful type guards:
type Request =
| {type: 'comment'; payload: {sender: string; issueId: number; body: string}}
| {type: 'issue'; payload: {sender: string; id: number; title: string; body: string}}
const commentMatcher = getMatcher({
type: 'comment',
payload: {sender: 'abc'},
})
const handleRequest = (request: Request) => {
if (commentMatcher(request)) {
console.log('parent: ' + request.payload.issueId) // request would be correctly inferred as a comment, because `type: 'comment'` was inferred as a literal
}
}
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.