Skip to content

[bug] Prevent generic type information loss from type narrowing #40436

Closed
@millsp

Description

@millsp

TypeScript Version: 4.0.2

Search Terms: type narrowing, intersection, property, any, type loss, property widening, generic type narrowing, instanceof, typeof

Code

This shows how ts fails to preserve generic types after narrowing the type with instanceof:

class A<T> {
  constructor(public value: T) {}
}

const fn = <T>(value: T) => {
  if (value instanceof A) {
    return value;
  }

  return undefined;
};

const fn = wrap(new A(42));

if (test) {
  type valueType = typeof test.value; // any
}

The inferred result of wrap is T & A<any>. Because of any's (or unknown's) widening nature, there is type information loss (widening to unsafe any on properties).

Expected behavior:

Instead of using & in the inferred result, could we use a conditional type instead? At the moment, I do this by hand:

class A<T> {
  constructor(public value: T) {}
}

const fn = <T>(value: T) => {
  if (value instanceof A) {
    return value as any as Extract<T, A<any>>;
  }

  return undefined;
};

const test = fn(new A(42));

if (test) {
  type valueType = typeof test.value; // number
}

Actual behavior:

TypeScript creates intersections, causing generic property type loss.

Playground Link: https://www.typescriptlang.org/play?jsx=0&ts=4.0.2#code/N4Ag9GIA4E4PYCMA2BTAtgWAFAhAYyQEMBnYkAQQB4AVAPhGG11zzgDtiAXGAVz07gwAFFB7IAlnhAA3Qkh4oAXCGoBKBgF8mILVm2sOnEAHcYhKCAC8IGrSGz5Slesv1GOZuIBmIe3IUg4oaEbHgocD7k6u7MzDAonDwwbDL+KADc2ri6WSDxickgPGwAJiheQSglmR4aNfrsXCCcKE3WpuZCbCjGFEIALABMqqr1Ht6+LVzRuZwAnlAoqY7UC0vW84sRza2cAHQOCungkCFz2ro5WKAQ0HCk4shLxHDynOLs+kSkFLYMuQYuLx+IIRGIkJJlgplGpNBdsA1DCYzBZrLY-I4YS43LkJhiAkEuCEwtsov8PLF8kkUocliQQGcGWQAKIAD24hH4NAANL8zrRaDVYldKQlqUVSuVKtULmMWI0jFMjO0UV0en0hiM5YEfEIlTMKc01lCUKtFlYjVsfEqDmljrc2Dw0AgUDB4VhdEA

Related Issues:
Related from far #37993

Metadata

Metadata

Assignees

No one assigned

    Labels

    Working as IntendedThe behavior described is the intended behavior; this is not a bug

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions