Microsoft/TypeScript

`keyof T['prop']` becomes never when the object contains a context-sensitive function

Open

#55.039 aberto em 17 de jul. de 2023

Ver no GitHub
 (5 comments) (1 reaction) (0 assignees)TypeScript (6.726 forks)batch import
BugDomain: Indexed Access TypesHelp Wanted

Métricas do repositório

Stars
 (48.455 stars)
Métricas de merge de PR
 (Mesclagem média 6d 17h) (9 fundiu PRs em 30d)

Description

Bug Report

🔎 Search Terms

Not sure how to put this but:

  • recursive type
  • function
  • parameter
  • generics

🕗 Version & Regression Information

Tested on these versions:

  • typescript@5.1.3
  • typescript@5.2.0-dev.20230716 dev

⏯ Playground Link

Playground Link

Code

type Type<
  TAcceptedKeys extends string,
  TType extends Type<TAcceptedKeys>=any
> = {
  /**
   * We're using the keys of this property, that is why
   * we use a recursive type.
   */
  obj?: Record<TAcceptedKeys, 1>
  /**
   * Just for the sake of illustration. 
   * In the circumstances of the bug this will be never[].
   */
  keys: (keyof TType['obj'])[]
  /**
   * A function with a parameter inside the recursive type.
   * This triggers the bug.
   */
  triggerFn?: (param: number) => void
  /**
   * This will always be TType, regardless of whether
   * a parameter is declared in `triggerFn` or not.
   */
  shouldBeTType?: TType
  /**
   * This will be `keyof TType` as expected if a parameter
   * is not declared in triggerFn, but it will turn to `undefined`
   * as soon as a parameter is declared in that function.
   *
   * If the below property is passed to defineType
   * the type will be as expected:
   * triggerFn: () => {}
   *
   * However this will trigger the bug:
   * triggerFn: (param) => {}
   */
  shouldBeKeyofTType?: keyof TType
}

const defineType = <TAcceptedKeys extends string>() =>
  <const TType extends Type<TAcceptedKeys, TType>>(type: TType) => type

defineType<'apple' | 'banana'>()({
  obj: {
    apple: 1,
    banana: 1
  },
  keys: [
    'apple'
  ],

  /**
   * Toggle these comments to see how the bug works.
   */
  triggerFn: () => {},      // won't produce diagnostics
  // triggerFn: (param) => {}, // type 'string' is not assignable to type 'never'

  /**
   * Toggle these comments to see diagnostics information.
   */
  // shouldBeTType: null,
  // shouldBeKeyofTType: null
})

🙁 Actual behavior

The presence or absence of a parameter in an object member function has effect on this object type in certain circumstances.

🙂 Expected behavior

It shouldn't.

Guia do colaborador