Skip to content

Narrowing of indexed access type only allows apparent type access; don't allow assignability #32502

Closed

Description

TypeScript Version: 3.6.0-dev.20190720

Search Terms: type narrowing undefined generics early return

Code

type BaseEvents = {[event: string]: Array<any>};
type Cbs<A extends BaseEvents> = {
    [P in keyof A]: Array<(...args: A[P]) => boolean | Promise<boolean>> | undefined;
}
export class Events<T extends BaseEvents> {
    private cbs: Cbs<T> = {} as any;
    async emit<P extends keyof T>(event: P, ...args: T[P]) {
        const arr = this.cbs[event]; // typeof arr = ((...args: T[P]) => boolean | Promise<boolean>)[] | undefined
        type NonNullArr = NonNullable<typeof arr>; // ((...args: T[P]) => boolean | Promise<boolean>)[]
        if (typeof arr === 'undefined') {
            return;
        }
        // typeof arr should now be ((...args: T[P]) => boolean | Promise<boolean>)[]
        const nonNullArr: NonNullArr = arr; // TS2322: Type '((...args: T[P]) => boolean | Promise<boolean>)[] | undefined' is not assignable to type 'NonNullable<Cbs<T>[P]>'.
                                            // Type 'undefined' is not assignable to type 'NonNullable<Cbs<T>[P]>'.
        for (const cb of arr) { // TS2488: Type 'Cbs<T>[P]' must have a '[Symbol.iterator]()' method that returns an iterator.

        }
        for (const cb2 of nonNullArr) { // No error

        }
    }
}

Expected behavior: Compiles without errors.

Actual behavior: Several errors relating to arr not narrowing out the undefined portion of the argument:

Error:(14, 15) TS2322: Type '((...args: T[P]) => boolean | Promise<boolean>)[] | undefined' is not assignable to type 'NonNullable<Cbs<T>[P]>'.
  Type 'undefined' is not assignable to type 'NonNullable<Cbs<T>[P]>'.
Error:(16, 26) TS2488: Type 'Cbs<T>[P]' must have a '[Symbol.iterator]()' method that returns an iterator.

Playground Link

Related Issues:

#1726 (closed by design, this is different because we can assume any array is not undefined)

#31456 (this example has a constraint)

Activity

  1. changed the title [-]Undefined type narrowing not working with generics[/-] [+]Narrowing of indexed access type only allows apparent type access; don't allow assignability[/+] on Jul 31, 2019
  2. RyanCavanaugh commented on Jul 31, 2019

    @RyanCavanaugh
    Member

    Example with fewer moving parts:

    type Obj<T> = {
      [K in keyof T]: string | undefined;
    }
    
    // 2 type parameter version
    function foo2<T, K extends keyof T>(obj: Obj<T>, arg: K) {
      const m = obj[arg];
      if (m === undefined) {
        m;
      } else {
        // Error
        const s: string = m;
        // No error (??)
        m.substr(0);
      }
    }
    
    // 1 type parameter version
    function foo1<T>(obj: Obj<T>, arg: keyof T) {
      const m = obj[arg];
      if (m === undefined) {
        m;
      } else {
        // Error
        const s: string = m;
        // No error (??)
        m.substr(0);
      }
    }
  3. jack-williams commented on Aug 1, 2019

    @jack-williams
    Collaborator

    Is this a duplicate of #31908? Duplicate is probably too strong, but maybe a fix here will work for both?

    Modified version from the linked issue that looks very similar.

    function getValue<T extends Record<keyof T, () => string>, K extends keyof T>(partial: Partial<T>, key: K): T[K] | null {
      const value = partial[key]
      if (value !== undefined) {
          value(); // no error
          return value // error:
      }
      return null
    }
  4. ahejlsberg commented on Oct 20, 2019

    @ahejlsberg
    Member

    This is a design limitation and effectively a duplicate of #31908. We'd like to do better here, but we don't know of a solution yet. Effectively, we need control flow analysis to use higher order types (such as NonNullable<T>) to reflect effects of constructs like value !== undefined, but this bring about a host of other issues as we've seen in #22348.

  5. added
    Design LimitationConstraints of the existing architecture prevent this from being fixed
    DuplicateAn existing issue was already created
    and removed
    BugA bug in TypeScript
    on Oct 20, 2019
  6. typescript-bot commented on Oct 22, 2019

    @typescript-bot
    Contributor

    This issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

  7. locked as resolved and limited conversation to collaborators on Oct 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Design LimitationConstraints of the existing architecture prevent this from being fixedDomain: Indexed Access TypesThe issue relates to accessing subtypes via index accessDuplicateAn existing issue was already created

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions