Skip to content

Suggestion: Automatic Structural Constraints for infer TypesΒ #62817

@codpro2005

Description

@codpro2005

πŸ” Search Terms

  • infer
  • constraint
  • narrowing
  • inference

βœ… Viability Checklist

⭐ 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

  1. What do you want to use this for?
    To automatically narrow all infer results 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 explicit extends constraints.
  2. 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 to unknown unless manually constrained. This forces repeated constraint duplication that could theoretically be inferred automatically.
  3. 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 --strict or making it the default in a major release after broad testing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions