Skip to content

narrow by instanceof: infer type parameters from original type #30161

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

Conversation

Nathan-Fenner
Copy link
Contributor

@Nathan-Fenner Nathan-Fenner commented Feb 28, 2019

class Parent<T> {
  item: T;
}

class Child<R> extends Parent<R> {
  other: R;
}

function narrow(p: Parent<number>) {
  if (p instanceof Child) {
    // what should p's type be?
    p;
    // before this change: Child<any>
    // after this change:  Child<number>
  }
}

This change fixes #28560 by inferring (where possible) the generic type parameters for a class when it's narrowed to by the instanceof operator.

The general process is to infer the parameters as though performing an assignability check of new Child(...) to the super type. This means that it handles e.g.

class Parent<A, B> { a: A; b: B }
class Child<B, A> extends Parent<A, B> { }

and anything else you might try to throw at it.

This is a breaking change. However, nothing in the test suite broke (the only changes would be in the newly-added test case that covers this behavior).

It therefore might be a good idea to put it behind a new strict flag, e.g. --stricterInstanceofNarrowing. However, depending on whether anyone out in the world breaks, that might not be needed.

Not all cases are fully covered (i.e. they still infer any for some/all type parameters), or otherwise behave in a (potentially) unexpected (albeit correct) manner:

class Parent<A, B> {
  a: A;
  b: C;
}

class Child<C> extends Parent<C, C> {
  c: C;
}

function example(p: Parent<string, number>) {
  if (p instanceof Child) {
    p; // Parent<string, number> & Child<string | number>
  }
}

the resulting type is the most-specific sound(ish) type. It's unlikely anyone deliberately will encounter this case (it should be the case that the Child type is uninhabited in that case).

@DanielRosenwasser
Copy link
Member

Just as a heads up, your commits don't seem to be associated with your GitHub account. While this isn't technically a problem, you might care if you want more appropriate attribution. You can either make sure your GitHub account is associated with the email address you're using for your commits, or rebase and amend your commits to fix the author name and email.

@RyanCavanaugh
Copy link
Member

@typescript-bot test this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Apr 25, 2019

Heya @RyanCavanaugh, I've started to run the extended test suite on this PR at f133aed. You can monitor the build here. It should now contribute to this PR's status checks.

@RyanCavanaugh
Copy link
Member

@typescript-bot test this new one

@typescript-bot
Copy link
Collaborator

typescript-bot commented Apr 25, 2019

Heya @RyanCavanaugh, I've started to run the extended test suite on this PR at 6443775. You can monitor the build here. It should now contribute to this PR's status checks.

@RyanCavanaugh
Copy link
Member

The bot lies; RWC is clean.

@typescript-bot perf test this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Apr 25, 2019

Heya @RyanCavanaugh, I've started to run the perf test suite on this PR at 6443775. You can monitor the build here. It should now contribute to this PR's status checks.

Update: The results are in!

@RyanCavanaugh
Copy link
Member

@ahejlsberg take a look? Interesting change

@typescript-bot
Copy link
Collaborator

@RyanCavanaugh
The results of the perf run you requested are in!

Here they are:

Comparison Report - master..30161

Metric master 30161 Delta Best Worst
Angular - node (v6.5.0, x64)
Memory used 342,293k (± 0.40%) 340,214k (± 0.35%) -2,078k (- 0.61%) 339,339k 343,421k
Parse Time 1.63s (± 1.40%) 1.63s (± 0.90%) 0.00s ( 0.00%) 1.58s 1.65s
Bind Time 1.55s (± 1.97%) 1.56s (± 1.86%) +0.01s (+ 0.52%) 1.51s 1.65s
Check Time 4.85s (± 0.64%) 4.81s (± 0.95%) -0.04s (- 0.76%) 4.74s 4.96s
Emit Time 6.49s (± 1.63%) 6.34s (± 2.26%) -0.15s (- 2.30%) 6.20s 6.76s
Total Time 14.52s (± 0.80%) 14.34s (± 1.31%) -0.18s (- 1.25%) 14.16s 14.85s
Monaco - node (v6.5.0, x64)
Memory used 364,783k (± 0.14%) 364,381k (± 0.09%) -402k (- 0.11%) 364,109k 365,705k
Parse Time 1.28s (± 0.53%) 1.28s (± 0.59%) 0.00s ( 0.00%) 1.26s 1.29s
Bind Time 1.52s (± 0.39%) 1.53s (± 0.76%) +0.01s (+ 0.73%) 1.50s 1.55s
Check Time 5.69s (± 1.65%) 5.69s (± 1.21%) +0.01s (+ 0.12%) 5.55s 5.84s
Emit Time 3.50s (± 3.79%) 3.56s (± 2.64%) +0.06s (+ 1.69%) 3.28s 3.66s
Total Time 11.98s (± 1.85%) 12.05s (± 1.28%) +0.07s (+ 0.61%) 11.66s 12.30s
TFS - node (v6.5.0, x64)
Memory used 321,066k (± 0.01%) 321,108k (± 0.01%) +43k (+ 0.01%) 320,976k 321,156k
Parse Time 0.98s (± 0.37%) 0.99s (± 0.74%) +0.00s (+ 0.41%) 0.97s 1.00s
Bind Time 1.27s (± 0.73%) 1.28s (± 0.78%) +0.02s (+ 1.18%) 1.27s 1.31s
Check Time 4.40s (± 0.51%) 4.35s (± 0.79%) -0.05s (- 1.09%) 4.27s 4.44s
Emit Time 3.14s (± 0.64%) 3.12s (± 2.00%) -0.02s (- 0.54%) 2.91s 3.20s
Total Time 9.79s (± 0.34%) 9.74s (± 0.73%) -0.05s (- 0.48%) 9.56s 9.94s
Angular - node (v6.5.0, x86)
Memory used 191,010k (± 0.01%) 191,089k (± 0.01%) +79k (+ 0.04%) 191,067k 191,116k
Parse Time 1.46s (± 0.85%) 1.48s (± 1.23%) +0.02s (+ 1.03%) 1.45s 1.52s
Bind Time 1.51s (± 1.14%) 1.50s (± 0.55%) -0.01s (- 0.86%) 1.48s 1.52s
Check Time 4.76s (± 1.81%) 4.69s (± 0.48%) -0.07s (- 1.47%) 4.65s 4.73s
Emit Time 6.08s (± 1.04%) 6.01s (± 1.09%) -0.07s (- 1.22%) 5.84s 6.18s
Total Time 13.81s (± 0.98%) 13.67s (± 0.63%) -0.14s (- 1.04%) 13.46s 13.85s
Monaco - node (v6.5.0, x86)
Memory used 204,360k (± 0.01%) 204,379k (± 0.01%) +19k (+ 0.01%) 204,356k 204,412k
Parse Time 1.27s (± 0.60%) 1.27s (± 1.11%) +0.00s (+ 0.08%) 1.25s 1.30s
Bind Time 1.56s (± 0.80%) 1.55s (± 0.53%) -0.01s (- 0.38%) 1.54s 1.58s
Check Time 4.73s (± 2.03%) 4.61s (± 0.56%) -0.13s (- 2.66%) 4.55s 4.65s
Emit Time 3.12s (± 2.70%) 3.18s (± 1.36%) +0.07s (+ 2.12%) 3.02s 3.23s
Total Time 10.67s (± 0.34%) 10.61s (± 0.56%) -0.06s (- 0.58%) 10.45s 10.70s
TFS - node (v6.5.0, x86)
Memory used 180,335k (± 0.01%) 180,383k (± 0.01%) +49k (+ 0.03%) 180,340k 180,429k
Parse Time 0.98s (± 0.53%) 0.98s (± 0.63%) 0.00s ( 0.00%) 0.97s 0.99s
Bind Time 1.32s (± 0.63%) 1.32s (± 0.63%) -0.00s (- 0.08%) 1.30s 1.34s
Check Time 3.92s (± 0.45%) 3.88s (± 0.51%) -0.05s (- 1.17%) 3.84s 3.92s
Emit Time 2.56s (± 1.44%) 2.58s (± 0.63%) +0.01s (+ 0.51%) 2.54s 2.61s
Total Time 8.79s (± 0.42%) 8.75s (± 0.34%) -0.04s (- 0.43%) 8.69s 8.83s
Angular - node (v8.9.0, x64)
Memory used 330,687k (± 0.01%) 330,751k (± 0.02%) +64k (+ 0.02%) 330,606k 330,873k
Parse Time 1.79s (± 0.50%) 1.81s (± 0.42%) +0.01s (+ 0.61%) 1.79s 1.82s
Bind Time 1.36s (± 0.76%) 1.37s (± 0.78%) +0.00s (+ 0.29%) 1.34s 1.39s
Check Time 4.60s (± 0.96%) 4.63s (± 1.81%) +0.02s (+ 0.52%) 4.46s 4.76s
Emit Time 6.13s (± 1.66%) 5.95s (± 3.20%) -0.19s (- 3.05%) 5.62s 6.31s
Total Time 13.90s (± 0.57%) 13.75s (± 0.91%) -0.15s (- 1.09%) 13.51s 14.01s
Monaco - node (v8.9.0, x64)
Memory used 358,416k (± 0.02%) 358,471k (± 0.02%) +55k (+ 0.02%) 358,371k 358,589k
Parse Time 1.45s (± 0.60%) 1.45s (± 0.36%) +0.00s (+ 0.21%) 1.44s 1.46s
Bind Time 1.54s (± 0.95%) 1.54s (± 1.26%) +0.00s (+ 0.13%) 1.51s 1.60s
Check Time 4.77s (± 0.87%) 4.82s (± 1.96%) +0.05s (+ 1.11%) 4.65s 4.98s
Emit Time 3.25s (± 3.02%) 3.06s (± 5.86%) -0.19s (- 5.76%) 2.80s 3.33s
Total Time 11.01s (± 0.72%) 10.87s (± 0.90%) -0.13s (- 1.21%) 10.68s 11.05s
TFS - node (v8.9.0, x64)
Memory used 313,801k (± 0.01%) 313,879k (± 0.01%) +78k (+ 0.02%) 313,810k 313,950k
Parse Time 1.15s (± 0.29%) 1.15s (± 0.65%) -0.00s (- 0.26%) 1.14s 1.17s
Bind Time 1.23s (± 0.82%) 1.24s (± 0.94%) +0.01s (+ 0.90%) 1.22s 1.27s
Check Time 4.20s (± 0.36%) 4.27s (± 0.59%) +0.07s (+ 1.72%) 4.23s 4.34s
Emit Time 3.14s (± 0.56%) 3.12s (± 1.82%) -0.02s (- 0.64%) 2.96s 3.21s
Total Time 9.71s (± 0.31%) 9.77s (± 0.65%) +0.06s (+ 0.63%) 9.62s 9.90s
Angular - node (v8.9.0, x86)
Memory used 187,152k (± 0.03%) 187,222k (± 0.03%) +70k (+ 0.04%) 187,113k 187,360k
Parse Time 1.75s (± 0.47%) 1.77s (± 0.67%) +0.02s (+ 1.20%) 1.74s 1.79s
Bind Time 1.54s (± 0.90%) 1.53s (± 1.23%) -0.00s (- 0.07%) 1.50s 1.58s
Check Time 4.30s (± 0.44%) 4.26s (± 0.94%) -0.04s (- 0.91%) 4.18s 4.36s
Emit Time 5.66s (± 1.03%) 5.65s (± 1.52%) -0.00s (- 0.04%) 5.50s 5.85s
Total Time 13.24s (± 0.58%) 13.22s (± 0.78%) -0.02s (- 0.17%) 13.02s 13.42s
Monaco - node (v8.9.0, x86)
Memory used 199,783k (± 0.02%) 199,868k (± 0.02%) +85k (+ 0.04%) 199,758k 199,955k
Parse Time 1.50s (± 0.62%) 1.51s (± 0.59%) +0.01s (+ 0.80%) 1.49s 1.53s
Bind Time 1.40s (± 0.50%) 1.40s (± 0.29%) 0.00s ( 0.00%) 1.39s 1.41s
Check Time 4.61s (± 0.40%) 4.59s (± 0.48%) -0.02s (- 0.46%) 4.55s 4.64s
Emit Time 3.12s (± 0.70%) 3.08s (± 0.64%) -0.04s (- 1.22%) 3.05s 3.13s
Total Time 10.63s (± 0.33%) 10.59s (± 0.35%) -0.05s (- 0.42%) 10.52s 10.66s
TFS - node (v8.9.0, x86)
Memory used 175,872k (± 0.02%) 175,921k (± 0.01%) +50k (+ 0.03%) 175,846k 175,957k
Parse Time 1.22s (± 0.78%) 1.22s (± 0.87%) +0.00s (+ 0.08%) 1.20s 1.24s
Bind Time 1.24s (± 0.40%) 1.24s (± 1.17%) +0.00s (+ 0.32%) 1.22s 1.29s
Check Time 4.05s (± 0.69%) 4.08s (± 0.62%) +0.04s (+ 0.87%) 4.00s 4.14s
Emit Time 2.78s (± 0.66%) 2.77s (± 1.74%) -0.01s (- 0.32%) 2.65s 2.88s
Total Time 9.28s (± 0.37%) 9.32s (± 0.59%) +0.03s (+ 0.36%) 9.21s 9.45s
Angular - node (v9.0.0, x64)
Memory used 330,411k (± 0.02%) 330,507k (± 0.02%) +96k (+ 0.03%) 330,369k 330,693k
Parse Time 1.65s (± 0.58%) 1.65s (± 0.60%) +0.00s (+ 0.18%) 1.63s 1.67s
Bind Time 1.34s (± 0.77%) 1.34s (± 0.75%) +0.00s (+ 0.22%) 1.33s 1.37s
Check Time 4.39s (± 1.59%) 4.31s (± 0.36%) -0.08s (- 1.75%) 4.28s 4.34s
Emit Time 5.62s (± 1.70%) 5.74s (± 1.47%) +0.12s (+ 2.15%) 5.61s 5.98s
Total Time 13.00s (± 0.32%) 13.04s (± 0.72%) +0.05s (+ 0.38%) 12.89s 13.31s
Monaco - node (v9.0.0, x64)
Memory used 358,189k (± 0.01%) 358,217k (± 0.01%) +28k (+ 0.01%) 358,132k 358,265k
Parse Time 1.30s (± 0.45%) 1.30s (± 0.51%) +0.00s (+ 0.23%) 1.29s 1.32s
Bind Time 1.51s (± 0.71%) 1.51s (± 0.64%) -0.00s (- 0.13%) 1.49s 1.53s
Check Time 4.69s (± 0.26%) 4.66s (± 0.42%) -0.03s (- 0.70%) 4.59s 4.69s
Emit Time 3.22s (± 0.45%) 3.23s (± 0.62%) +0.01s (+ 0.44%) 3.18s 3.28s
Total Time 10.71s (± 0.21%) 10.70s (± 0.25%) -0.01s (- 0.12%) 10.64s 10.75s
TFS - node (v9.0.0, x64)
Memory used 313,768k (± 0.01%) 313,839k (± 0.02%) +71k (+ 0.02%) 313,711k 313,993k
Parse Time 1.02s (± 0.57%) 1.03s (± 0.65%) +0.00s (+ 0.39%) 1.01s 1.04s
Bind Time 1.21s (± 0.78%) 1.21s (± 0.77%) -0.00s (- 0.17%) 1.19s 1.22s
Check Time 4.26s (± 1.83%) 4.16s (± 1.77%) -0.11s (- 2.46%) 4.05s 4.36s
Emit Time 2.97s (± 3.17%) 3.06s (± 2.36%) +0.09s (+ 3.10%) 2.86s 3.16s
Total Time 9.46s (± 0.36%) 9.45s (± 0.34%) -0.01s (- 0.13%) 9.35s 9.51s
Angular - node (v9.0.0, x86)
Memory used 187,329k (± 0.03%) 187,356k (± 0.03%) +27k (+ 0.01%) 187,220k 187,512k
Parse Time 1.56s (± 0.42%) 1.57s (± 1.01%) +0.01s (+ 0.64%) 1.54s 1.61s
Bind Time 1.52s (± 0.45%) 1.51s (± 0.54%) -0.00s (- 0.20%) 1.50s 1.53s
Check Time 4.05s (± 0.61%) 4.00s (± 0.49%) -0.05s (- 1.19%) 3.96s 4.04s
Emit Time 5.33s (± 0.98%) 5.34s (± 0.65%) +0.01s (+ 0.21%) 5.27s 5.41s
Total Time 12.45s (± 0.52%) 12.42s (± 0.40%) -0.03s (- 0.24%) 12.29s 12.58s
Monaco - node (v9.0.0, x86)
Memory used 199,913k (± 0.03%) 199,946k (± 0.02%) +33k (+ 0.02%) 199,871k 200,031k
Parse Time 1.33s (± 0.36%) 1.33s (± 0.61%) +0.01s (+ 0.45%) 1.32s 1.35s
Bind Time 1.36s (± 0.48%) 1.39s (± 1.56%) +0.02s (+ 1.76%) 1.36s 1.45s
Check Time 4.49s (± 0.65%) 4.46s (± 0.59%) -0.03s (- 0.67%) 4.41s 4.52s
Emit Time 3.02s (± 0.57%) 3.00s (± 0.50%) -0.02s (- 0.50%) 2.97s 3.04s
Total Time 10.19s (± 0.23%) 10.18s (± 0.50%) -0.01s (- 0.10%) 10.08s 10.32s
TFS - node (v9.0.0, x86)
Memory used 175,995k (± 0.02%) 176,057k (± 0.01%) +62k (+ 0.04%) 176,016k 176,096k
Parse Time 1.04s (± 0.67%) 1.04s (± 0.85%) +0.00s (+ 0.29%) 1.03s 1.07s
Bind Time 1.23s (± 0.91%) 1.22s (± 0.65%) -0.00s (- 0.24%) 1.21s 1.24s
Check Time 3.93s (± 0.50%) 3.87s (± 0.72%) -0.07s (- 1.73%) 3.82s 3.94s
Emit Time 2.73s (± 0.95%) 2.69s (± 0.63%) -0.04s (- 1.35%) 2.65s 2.72s
Total Time 8.93s (± 0.32%) 8.83s (± 0.37%) -0.11s (- 1.21%) 8.72s 8.88s
System
Machine Namets-ci-ubuntu
Platformlinux 4.4.0-142-generic
Architecturex64
Available Memory16 GB
Available Memory1 GB
CPUs4 × Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz
Hosts
  • node (v6.5.0, x64)
  • node (v6.5.0, x86)
  • node (v8.9.0, x64)
  • node (v8.9.0, x86)
  • node (v9.0.0, x64)
  • node (v9.0.0, x86)
Scenarios
  • Angular - node (v6.5.0, x64)
  • Angular - node (v6.5.0, x86)
  • Angular - node (v8.9.0, x64)
  • Angular - node (v8.9.0, x86)
  • Angular - node (v9.0.0, x64)
  • Angular - node (v9.0.0, x86)
  • Monaco - node (v6.5.0, x64)
  • Monaco - node (v6.5.0, x86)
  • Monaco - node (v8.9.0, x64)
  • Monaco - node (v8.9.0, x86)
  • Monaco - node (v9.0.0, x64)
  • Monaco - node (v9.0.0, x86)
  • TFS - node (v6.5.0, x64)
  • TFS - node (v6.5.0, x86)
  • TFS - node (v8.9.0, x64)
  • TFS - node (v8.9.0, x86)
  • TFS - node (v9.0.0, x64)
  • TFS - node (v9.0.0, x86)
Benchmark Name Iterations
Current 30161 10
Baseline master 10

@fatcerberus
Copy link

Will this also fix this case:

class Query<T> {}
function f<T>(q: T[] | Query<T>) {
    if (Array.isArray(q)) {
        q;  // T[], so far so good
    } else if (q instanceof Query) {
        q;  // Query<any>, wtpf
    }
}

In my case there's no inheritance. I'm literally only checking if it's one of the types already named in the union and it goes and widens it from T to any. 🙁 I can work around it by moving the Query<T> case into an else { } but that's not ideal.

@Nathan-Fenner
Copy link
Contributor Author

@fatcerberus I think that it should handle that case, but I'm not sure whether the custom type guard in isArray causes problems. I can add that to the test suite to find out.

@fatcerberus
Copy link

The isArray check actually doesn’t have anything to do with it - the issue is specifically when there’s an instanceof C check after the type has already been narrowed down to C<T>. Then the instanceof widens it to C<any>.

@Nathan-Fenner
Copy link
Contributor Author

This does fix the unnecessary widening issue.

@Nathan-Fenner
Copy link
Contributor Author

Nathan-Fenner commented May 7, 2019

I'm going to update the test to better organize the various facets of this feature that are being tested. Here are a few samples (commented annotations are taken mechanically from the generated .types file):

function dontWidenPointlessly() {
  class Query<T> {
    uses: T;
  }
  function f<T>(p: T[] | Query<T>) {
    if (Array.isArray(p)) {
      p; // p: T[]
    } else if (q instanceof Query) {
      p; // p: Query<T>
    }
  }
}

I've identified some bugs/limitations that need to be corrected before moving forward:

function union() {
  class Parent<A> {
    a: A;
  }
  class Child<B> extends Parent<B> {
    b: B;
  }
  function multipleParents(
    p: Parent<number> | Parent<string> | Parent<boolean>
  ) {
    if (p instanceof Child) {
      p; // p: Child<number> | Child<string> | Child<boolean>
    } else {
      p; // p: Parent<number> | Parent<string> | Parent<boolean>
    }
  }
  function mixedChildren(p: Parent<number> | Child<string>) {
    if (p instanceof Child) {
      p; // p: Child<string>
    } else {
      p; // p: Parent<number>
    }
  }
  function imcompatibleOptions(
    p: Parent<number> | Parent<string> | { foo: boolean }
  ) {
    if (p instanceof Child) {
      p; // p: Child<any>
    } else {
      p; // p: Parent<number> | Parent<string> | { foo: boolean; }
    }
  }
}

There are several problems visible here:

  • in mixedChildren, Child<string> is preserved but Parent<number> was thrown out entirely! We should expect to see Child<number> | Child<string> in the then-case
  • in incompatibleOptions the presence of the {foo: boolean} object causes the current heuristic check to fail, and therefore the prior fallback behavior of using Child<any> occurs

@Nathan-Fenner
Copy link
Contributor Author

It occurs to me that the theory behind the current implementation is not terribly sound. At the same time, it's not entirely obvious to me how this can be corrected.

When we have

let x: T;
if (x instanceof K) {
   ...
}

and K is some generic type e.g. K<T1 extends C1, T2 extends C2> then what we "really" know inside the if is that

x: T & (exists<t1 extends C1, t2 extends C2> in K<t1, t2>)

That is, we know that it's still a T and we also know that there are some parameters that make it into a K<t1, t2> (because otherwise, it's not a "real" K). The exists<T> in S syntax is a strawman for hypothetical existential types in TypeScript.

The trick is that in many (but not obviously all) cases we can simplify this to remove the existential, producing a type that can be expressed in TS today, and is functionally the same.

For example, exists<S extends number> S is functionally identical to number, since any value that could be read from it is a number, and any value that's already a number also satisfies that type trivially.

On the other hand, exists<S extends number> {num: S} is not the same as {num: number}, although it's close, and TS today currently blurs this concept. This is because TS usually types object assignability as though fields were readonly even when they're not. The following is legal today in TS, even though it's not sound:

const a: {f:1|2} = {f: 1};
const b: {f:number} = a;
b.f = 3;

The way this could be done soundly is by annotating every field with a read bound and a write bound; {f: T} can be treated as shorthand for {readonly f: T} & {writeonly f: T}. Then in the above we'd have:

const a: {readonly f : 1 | 2} & {writeonly f: 1 | 2} = {f: 1};
const b: {readonly f : number} & {writeonly f : 1 | 2} = a;
b.f = 3; // error, f is only writeonly with 1 | 2

writeonly fields are contravariant, so attempting to specify b as merely {writeonly f: number} would fail, since number is not a subtype of 1 | 2.

Moving back to the original problem, we would like to say that exists<S extends number> {num: S} is the same thing as {num: number}. This is true in the same sense that {num: 1 | 2} is a subtype of {num: number}; it is, provided that we neglect writable values imposing contravariance constraints.

What this suggests as an alternative, sound(ish) procedure is to therefore consider the type T & exists<S> K<S>, and repeatedly simplify it until it can be expressed entirely in TypeScript without any exists operator (alternatively, add an exists type operator to TypeScript, and just perform simplification while doing the above).

This partly simplifies the view of what must be done- many easy cases fall out immediately. The main problem are object and intersection types (since they have fields, and those fields must be compared against the existential) whereas all other instances are either never or the most general value possible (i.e. what you get when you plug in unknown or the generic constraints for the type, assuming it's covariant, or never if it's contravariant).

@fatcerberus
Copy link

const a: {f:1|2} = {f: 1};
const b: {f:number} = a;
b.f = 3;

This kind of blew my mind. Thinking about it, I guess enforcing the contravariance here might be too restrictive in practice; it would effectively make all objects invariant unless they consisted of nothing but readonly or writeonly properties—the latter of which doesn’t even exist in TS.

Still thought-provoking, though.

@sandersn
Copy link
Member

Unfortunately, we never finished reviewing this PR. It is pretty old now, so I'm going to close it to reduce the number of open PRs.

@sandersn sandersn closed this May 24, 2022
@typescript-bot
Copy link
Collaborator

The TypeScript team hasn't accepted the linked issue #28560. If you can get it accepted, this PR will have a better chance of being reviewed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
For Backlog Bug PRs that fix a backlog bug
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

instanceof narrowing should preserve generic types from super to child type
7 participants