-
Notifications
You must be signed in to change notification settings - Fork 13.1k
Description
π Search Terms
- infer
- constraint
- narrowing
- inference
β Viability Checklist
- 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, new syntax sugar for JS, etc.)
- This isn't a request to add a new utility type: https://github.com/microsoft/TypeScript/wiki/No-New-Utility-Types
- This feature would agree with the rest of our Design Goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals
β Suggestion
Currently infer usages are wider than they could be. I propose automatically constraining inferred types based on the structure of the pattern in which they appear.
π Motivating Example
type Res = {name: 'Dan'} extends infer Person
? Person['name'] // `Type '"name"' cannot be used to index type 'Person'.`
: never
;
type MapNames<T extends {name: string}[]> = T extends [infer Head, ...infer Tail]
? [Head['name'], ...MapNames<Tail>] // `Type '"name"' cannot be used to index type 'Head'.` & `Type 'Tail' does not satisfy the constraint '{ name: string; }[]'.`
: []
;π» Use Cases
- What do you want to use this for?
To automatically narrow allinferresults to the most specific shape already implied by the pattern in which they appear, so that inferred types can be used as if they carried their known structure (e.g. accessing properties, indexing tuples, calling methods) without requiring explicitextendsconstraints. - What shortcomings exist with current approaches?
As shown in the motivating example, inferred types could be inferred more strictly based on usage, but the compiler defaults them tounknownunless manually constrained. This forces repeated constraint duplication that could theoretically be inferred automatically. - What workarounds are you using in the meantime?
Inferred types can be constrained manually:
type Res = {name: 'Dan'} extends infer Person extends {name: 'Dan'}
? Person['name']
: never
;
type MapNames<T extends {name: string}[]> = T extends [
infer Head extends T[0],
...infer Tail extends T extends [infer _, ...infer Tail]
? Tail
: never
]
? [Head['name'], ...MapNames<Tail>]
: []
;While this works, it's very repetitious and verbose. Since generic types are automatically constrained by their definition, this can be abused to make inference less verbose:
type As<T, _Infer extends T> = unknown;
type AsLinked<
T extends unknown[],
_InferHead extends T[0],
_InferTail extends T extends [infer _, ...infer Tail]
? Tail
: never
> = T extends []
? never
: unknown
;
type Res = unknown extends As<{name: 'Dan'}, infer Person>
? Person['name']
: never
;
type MapNames<T extends {name: string}[]> = unknown extends AsLinked<T, infer Head, infer Tail>
? [Head['name'], MapNames<Tail>]
: []
;But this still requires an individual utility type for each unique inference usage and shouldn't be necessary.
Proposed Implementation
Compiler behavior
The compiler could automatically constrain inferred types based on the matched type's structure at the inference position:
type Res = {name: 'Dan'} extends infer Person extends ({name: 'Dan'} extends infer SELF ? SELF : never)
? Person['name']
: never
;
type MapNames<T extends {name: string}[]> = T extends [
infer Head extends (T extends [infer SELF, ...infer _] ? SELF : never),
...infer Tail extends (T extends [infer _, ...infer SELF] ? SELF : never)
]
? [Head['name'], ...MapNames<Tail>]
: []
;Potential breaking changes
Stricter default inference
Given that this currently fails because the user-provided constraint is not a subtype of the automatic constraint (unknown[]):
type Arr = [1,2,3] extends [...infer Elements extends unknown]
? Elements
: never
;This would imply that the following would fail for the same reason despite being valid currently:
type Num = 67 extends infer SixSeven extends number // 'Type 'number' is not assignable to type '67'.'
? SixSeven
: never
;Potential solution
The automatic constraint could be intersected with the user-defined one:
type Num = 67 extends infer SixSeven extends ((67 extends infer SELF ? SELF : never) & number)
? SixSeven // constrained as: 67
: never
;These ideas may fail for complex inference definitions and would require more thorough evaluation.
Next steps / suggested rollout
- Prototype as an opt-in compiler flag (e.g.
--strictInferConstraints) and add tests covering different use cases. - If the prototype is stable and performant, consider enabling it under
--strictor making it the default in a major release after broad testing.