Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
charles-toller opened this issue Jul 21, 2019 · 4 comments
Assignees
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed Domain: Indexed Access Types The issue relates to accessing subtypes via index access Duplicate An existing issue was already created

Comments

@charles-toller
Copy link

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)

@RyanCavanaugh RyanCavanaugh changed the title Undefined type narrowing not working with generics Narrowing of indexed access type only allows apparent type access; don't allow assignability Jul 31, 2019
@RyanCavanaugh RyanCavanaugh added Bug A bug in TypeScript Domain: Indexed Access Types The issue relates to accessing subtypes via index access labels Jul 31, 2019
@RyanCavanaugh RyanCavanaugh added this to the TypeScript 3.7.0 milestone Jul 31, 2019
@RyanCavanaugh
Copy link
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);
  }
}

@jack-williams
Copy link
Collaborator

jack-williams commented Aug 1, 2019

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
}

@ahejlsberg
Copy link
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.

@ahejlsberg ahejlsberg added Design Limitation Constraints of the existing architecture prevent this from being fixed Duplicate An existing issue was already created and removed Bug A bug in TypeScript labels Oct 20, 2019
@typescript-bot
Copy link
Collaborator

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed Domain: Indexed Access Types The issue relates to accessing subtypes via index access Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

5 participants