Skip to content

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

Closed
Nathan Fenner (Nathan-Fenner) wants to merge 6 commits into
microsoft:mainfrom
Nathan-Fenner:narrow-type-by-instanceof-with-generic-subclass
Closed

narrow by instanceof: infer type parameters from original type#30161
Nathan Fenner (Nathan-Fenner) wants to merge 6 commits into
microsoft:mainfrom
Nathan-Fenner:narrow-type-by-instanceof-with-generic-subclass

Conversation

@Nathan-Fenner

@Nathan-FennerNathan Fenner (Nathan-Fenner) commented Feb 28, 2019

Copy link
Copy Markdown
Contributor
classParent<T>{item: T;}classChild<R>extendsParent<R>{other: R;}functionnarrow(p: Parent<number>){if(pinstanceofChild){// 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.

classParent<A,B>{a: A;b: B}classChild<B,A>extendsParent<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:

classParent<A,B>{a: A;b: C;}classChild<C>extendsParent<C,C>{c: C;}functionexample(p: Parent<string,number>){if(pinstanceofChild){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
Copy Markdown
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
Copy Markdown
Member

TypeScript Bot (@typescript-bot) test this

@typescript-bot

TypeScript Bot (typescript-bot) commented Apr 25, 2019

Copy link
Copy Markdown
Contributor

Heya Ryan Cavanaugh (@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
Copy Markdown
Member

TypeScript Bot (@typescript-bot) test this new one

@typescript-bot

TypeScript Bot (typescript-bot) commented Apr 25, 2019

Copy link
Copy Markdown
Contributor

Heya Ryan Cavanaugh (@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
Copy Markdown
Member

The bot lies; RWC is clean.

TypeScript Bot (@typescript-bot) perf test this

@typescript-bot

TypeScript Bot (typescript-bot) commented Apr 25, 2019

Copy link
Copy Markdown
Contributor

Heya Ryan Cavanaugh (@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
Copy Markdown
Member

Anders Hejlsberg (@ahejlsberg) take a look? Interesting change

@typescript-bot

Copy link
Copy Markdown
Contributor

Ryan Cavanaugh (@RyanCavanaugh)
The results of the perf run you requested are in!

Here they are:

Comparison Report - master..30161

Metricmaster30161DeltaBestWorst
Angular - node (v6.5.0, x64)
Memory used342,293k (± 0.40%)340,214k (± 0.35%)-2,078k (- 0.61%)339,339k343,421k
Parse Time1.63s (± 1.40%)1.63s (± 0.90%)0.00s ( 0.00%)1.58s1.65s
Bind Time1.55s (± 1.97%)1.56s (± 1.86%)+0.01s (+ 0.52%)1.51s1.65s
Check Time4.85s (± 0.64%)4.81s (± 0.95%)-0.04s (- 0.76%)4.74s4.96s
Emit Time6.49s (± 1.63%)6.34s (± 2.26%)-0.15s (- 2.30%)6.20s6.76s
Total Time14.52s (± 0.80%)14.34s (± 1.31%)-0.18s (- 1.25%)14.16s14.85s
Monaco - node (v6.5.0, x64)
Memory used364,783k (± 0.14%)364,381k (± 0.09%)-402k (- 0.11%)364,109k365,705k
Parse Time1.28s (± 0.53%)1.28s (± 0.59%)0.00s ( 0.00%)1.26s1.29s
Bind Time1.52s (± 0.39%)1.53s (± 0.76%)+0.01s (+ 0.73%)1.50s1.55s
Check Time5.69s (± 1.65%)5.69s (± 1.21%)+0.01s (+ 0.12%)5.55s5.84s
Emit Time3.50s (± 3.79%)3.56s (± 2.64%)+0.06s (+ 1.69%)3.28s3.66s
Total Time11.98s (± 1.85%)12.05s (± 1.28%)+0.07s (+ 0.61%)11.66s12.30s
TFS - node (v6.5.0, x64)
Memory used321,066k (± 0.01%)321,108k (± 0.01%)+43k (+ 0.01%)320,976k321,156k
Parse Time0.98s (± 0.37%)0.99s (± 0.74%)+0.00s (+ 0.41%)0.97s1.00s
Bind Time1.27s (± 0.73%)1.28s (± 0.78%)+0.02s (+ 1.18%)1.27s1.31s
Check Time4.40s (± 0.51%)4.35s (± 0.79%)-0.05s (- 1.09%)4.27s4.44s
Emit Time3.14s (± 0.64%)3.12s (± 2.00%)-0.02s (- 0.54%)2.91s3.20s
Total Time9.79s (± 0.34%)9.74s (± 0.73%)-0.05s (- 0.48%)9.56s9.94s
Angular - node (v6.5.0, x86)
Memory used191,010k (± 0.01%)191,089k (± 0.01%)+79k (+ 0.04%)191,067k191,116k
Parse Time1.46s (± 0.85%)1.48s (± 1.23%)+0.02s (+ 1.03%)1.45s1.52s
Bind Time1.51s (± 1.14%)1.50s (± 0.55%)-0.01s (- 0.86%)1.48s1.52s
Check Time4.76s (± 1.81%)4.69s (± 0.48%)-0.07s (- 1.47%)4.65s4.73s
Emit Time6.08s (± 1.04%)6.01s (± 1.09%)-0.07s (- 1.22%)5.84s6.18s
Total Time13.81s (± 0.98%)13.67s (± 0.63%)-0.14s (- 1.04%)13.46s13.85s
Monaco - node (v6.5.0, x86)
Memory used204,360k (± 0.01%)204,379k (± 0.01%)+19k (+ 0.01%)204,356k204,412k
Parse Time1.27s (± 0.60%)1.27s (± 1.11%)+0.00s (+ 0.08%)1.25s1.30s
Bind Time1.56s (± 0.80%)1.55s (± 0.53%)-0.01s (- 0.38%)1.54s1.58s
Check Time4.73s (± 2.03%)4.61s (± 0.56%)-0.13s (- 2.66%)4.55s4.65s
Emit Time3.12s (± 2.70%)3.18s (± 1.36%)+0.07s (+ 2.12%)3.02s3.23s
Total Time10.67s (± 0.34%)10.61s (± 0.56%)-0.06s (- 0.58%)10.45s10.70s
TFS - node (v6.5.0, x86)
Memory used180,335k (± 0.01%)180,383k (± 0.01%)+49k (+ 0.03%)180,340k180,429k
Parse Time0.98s (± 0.53%)0.98s (± 0.63%)0.00s ( 0.00%)0.97s0.99s
Bind Time1.32s (± 0.63%)1.32s (± 0.63%)-0.00s (- 0.08%)1.30s1.34s
Check Time3.92s (± 0.45%)3.88s (± 0.51%)-0.05s (- 1.17%)3.84s3.92s
Emit Time2.56s (± 1.44%)2.58s (± 0.63%)+0.01s (+ 0.51%)2.54s2.61s
Total Time8.79s (± 0.42%)8.75s (± 0.34%)-0.04s (- 0.43%)8.69s8.83s
Angular - node (v8.9.0, x64)
Memory used330,687k (± 0.01%)330,751k (± 0.02%)+64k (+ 0.02%)330,606k330,873k
Parse Time1.79s (± 0.50%)1.81s (± 0.42%)+0.01s (+ 0.61%)1.79s1.82s
Bind Time1.36s (± 0.76%)1.37s (± 0.78%)+0.00s (+ 0.29%)1.34s1.39s
Check Time4.60s (± 0.96%)4.63s (± 1.81%)+0.02s (+ 0.52%)4.46s4.76s
Emit Time6.13s (± 1.66%)5.95s (± 3.20%)-0.19s (- 3.05%)5.62s6.31s
Total Time13.90s (± 0.57%)13.75s (± 0.91%)-0.15s (- 1.09%)13.51s14.01s
Monaco - node (v8.9.0, x64)
Memory used358,416k (± 0.02%)358,471k (± 0.02%)+55k (+ 0.02%)358,371k358,589k
Parse Time1.45s (± 0.60%)1.45s (± 0.36%)+0.00s (+ 0.21%)1.44s1.46s
Bind Time1.54s (± 0.95%)1.54s (± 1.26%)+0.00s (+ 0.13%)1.51s1.60s
Check Time4.77s (± 0.87%)4.82s (± 1.96%)+0.05s (+ 1.11%)4.65s4.98s
Emit Time3.25s (± 3.02%)3.06s (± 5.86%)-0.19s (- 5.76%)2.80s3.33s
Total Time11.01s (± 0.72%)10.87s (± 0.90%)-0.13s (- 1.21%)10.68s11.05s
TFS - node (v8.9.0, x64)
Memory used313,801k (± 0.01%)313,879k (± 0.01%)+78k (+ 0.02%)313,810k313,950k
Parse Time1.15s (± 0.29%)1.15s (± 0.65%)-0.00s (- 0.26%)1.14s1.17s
Bind Time1.23s (± 0.82%)1.24s (± 0.94%)+0.01s (+ 0.90%)1.22s1.27s
Check Time4.20s (± 0.36%)4.27s (± 0.59%)+0.07s (+ 1.72%)4.23s4.34s
Emit Time3.14s (± 0.56%)3.12s (± 1.82%)-0.02s (- 0.64%)2.96s3.21s
Total Time9.71s (± 0.31%)9.77s (± 0.65%)+0.06s (+ 0.63%)9.62s9.90s
Angular - node (v8.9.0, x86)
Memory used187,152k (± 0.03%)187,222k (± 0.03%)+70k (+ 0.04%)187,113k187,360k
Parse Time1.75s (± 0.47%)1.77s (± 0.67%)+0.02s (+ 1.20%)1.74s1.79s
Bind Time1.54s (± 0.90%)1.53s (± 1.23%)-0.00s (- 0.07%)1.50s1.58s
Check Time4.30s (± 0.44%)4.26s (± 0.94%)-0.04s (- 0.91%)4.18s4.36s
Emit Time5.66s (± 1.03%)5.65s (± 1.52%)-0.00s (- 0.04%)5.50s5.85s
Total Time13.24s (± 0.58%)13.22s (± 0.78%)-0.02s (- 0.17%)13.02s13.42s
Monaco - node (v8.9.0, x86)
Memory used199,783k (± 0.02%)199,868k (± 0.02%)+85k (+ 0.04%)199,758k199,955k
Parse Time1.50s (± 0.62%)1.51s (± 0.59%)+0.01s (+ 0.80%)1.49s1.53s
Bind Time1.40s (± 0.50%)1.40s (± 0.29%)0.00s ( 0.00%)1.39s1.41s
Check Time4.61s (± 0.40%)4.59s (± 0.48%)-0.02s (- 0.46%)4.55s4.64s
Emit Time3.12s (± 0.70%)3.08s (± 0.64%)-0.04s (- 1.22%)3.05s3.13s
Total Time10.63s (± 0.33%)10.59s (± 0.35%)-0.05s (- 0.42%)10.52s10.66s
TFS - node (v8.9.0, x86)
Memory used175,872k (± 0.02%)175,921k (± 0.01%)+50k (+ 0.03%)175,846k175,957k
Parse Time1.22s (± 0.78%)1.22s (± 0.87%)+0.00s (+ 0.08%)1.20s1.24s
Bind Time1.24s (± 0.40%)1.24s (± 1.17%)+0.00s (+ 0.32%)1.22s1.29s
Check Time4.05s (± 0.69%)4.08s (± 0.62%)+0.04s (+ 0.87%)4.00s4.14s
Emit Time2.78s (± 0.66%)2.77s (± 1.74%)-0.01s (- 0.32%)2.65s2.88s
Total Time9.28s (± 0.37%)9.32s (± 0.59%)+0.03s (+ 0.36%)9.21s9.45s
Angular - node (v9.0.0, x64)
Memory used330,411k (± 0.02%)330,507k (± 0.02%)+96k (+ 0.03%)330,369k330,693k
Parse Time1.65s (± 0.58%)1.65s (± 0.60%)+0.00s (+ 0.18%)1.63s1.67s
Bind Time1.34s (± 0.77%)1.34s (± 0.75%)+0.00s (+ 0.22%)1.33s1.37s
Check Time4.39s (± 1.59%)4.31s (± 0.36%)-0.08s (- 1.75%)4.28s4.34s
Emit Time5.62s (± 1.70%)5.74s (± 1.47%)+0.12s (+ 2.15%)5.61s5.98s
Total Time13.00s (± 0.32%)13.04s (± 0.72%)+0.05s (+ 0.38%)12.89s13.31s
Monaco - node (v9.0.0, x64)
Memory used358,189k (± 0.01%)358,217k (± 0.01%)+28k (+ 0.01%)358,132k358,265k
Parse Time1.30s (± 0.45%)1.30s (± 0.51%)+0.00s (+ 0.23%)1.29s1.32s
Bind Time1.51s (± 0.71%)1.51s (± 0.64%)-0.00s (- 0.13%)1.49s1.53s
Check Time4.69s (± 0.26%)4.66s (± 0.42%)-0.03s (- 0.70%)4.59s4.69s
Emit Time3.22s (± 0.45%)3.23s (± 0.62%)+0.01s (+ 0.44%)3.18s3.28s
Total Time10.71s (± 0.21%)10.70s (± 0.25%)-0.01s (- 0.12%)10.64s10.75s
TFS - node (v9.0.0, x64)
Memory used313,768k (± 0.01%)313,839k (± 0.02%)+71k (+ 0.02%)313,711k313,993k
Parse Time1.02s (± 0.57%)1.03s (± 0.65%)+0.00s (+ 0.39%)1.01s1.04s
Bind Time1.21s (± 0.78%)1.21s (± 0.77%)-0.00s (- 0.17%)1.19s1.22s
Check Time4.26s (± 1.83%)4.16s (± 1.77%)-0.11s (- 2.46%)4.05s4.36s
Emit Time2.97s (± 3.17%)3.06s (± 2.36%)+0.09s (+ 3.10%)2.86s3.16s
Total Time9.46s (± 0.36%)9.45s (± 0.34%)-0.01s (- 0.13%)9.35s9.51s
Angular - node (v9.0.0, x86)
Memory used187,329k (± 0.03%)187,356k (± 0.03%)+27k (+ 0.01%)187,220k187,512k
Parse Time1.56s (± 0.42%)1.57s (± 1.01%)+0.01s (+ 0.64%)1.54s1.61s
Bind Time1.52s (± 0.45%)1.51s (± 0.54%)-0.00s (- 0.20%)1.50s1.53s
Check Time4.05s (± 0.61%)4.00s (± 0.49%)-0.05s (- 1.19%)3.96s4.04s
Emit Time5.33s (± 0.98%)5.34s (± 0.65%)+0.01s (+ 0.21%)5.27s5.41s
Total Time12.45s (± 0.52%)12.42s (± 0.40%)-0.03s (- 0.24%)12.29s12.58s
Monaco - node (v9.0.0, x86)
Memory used199,913k (± 0.03%)199,946k (± 0.02%)+33k (+ 0.02%)199,871k200,031k
Parse Time1.33s (± 0.36%)1.33s (± 0.61%)+0.01s (+ 0.45%)1.32s1.35s
Bind Time1.36s (± 0.48%)1.39s (± 1.56%)+0.02s (+ 1.76%)1.36s1.45s
Check Time4.49s (± 0.65%)4.46s (± 0.59%)-0.03s (- 0.67%)4.41s4.52s
Emit Time3.02s (± 0.57%)3.00s (± 0.50%)-0.02s (- 0.50%)2.97s3.04s
Total Time10.19s (± 0.23%)10.18s (± 0.50%)-0.01s (- 0.10%)10.08s10.32s
TFS - node (v9.0.0, x86)
Memory used175,995k (± 0.02%)176,057k (± 0.01%)+62k (+ 0.04%)176,016k176,096k
Parse Time1.04s (± 0.67%)1.04s (± 0.85%)+0.00s (+ 0.29%)1.03s1.07s
Bind Time1.23s (± 0.91%)1.22s (± 0.65%)-0.00s (- 0.24%)1.21s1.24s
Check Time3.93s (± 0.50%)3.87s (± 0.72%)-0.07s (- 1.73%)3.82s3.94s
Emit Time2.73s (± 0.95%)2.69s (± 0.63%)-0.04s (- 1.35%)2.65s2.72s
Total Time8.93s (± 0.32%)8.83s (± 0.37%)-0.11s (- 1.21%)8.72s8.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)
BenchmarkNameIterations
Current3016110
Baselinemaster10

@fatcerberus

Copy link
Copy Markdown

Will this also fix this case:

classQuery<T>{}functionf<T>(q: T[]|Query<T>){if(Array.isArray(q)){q;// T[], so far so good}elseif(qinstanceofQuery){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
Copy Markdown
ContributorAuthor

Bruce Pascoe (@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
Copy Markdown

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
Copy Markdown
ContributorAuthor

This does fix the unnecessary widening issue.

@Nathan-Fenner

Nathan Fenner (Nathan-Fenner) commented May 7, 2019

Copy link
Copy Markdown
ContributorAuthor

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):

functiondontWidenPointlessly(){classQuery<T>{uses: T;}functionf<T>(p: T[]|Query<T>){if(Array.isArray(p)){p;// p: T[]}elseif(qinstanceofQuery){p;// p: Query<T>}}}

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

functionunion(){classParent<A>{a: A;}classChild<B>extendsParent<B>{b: B;}functionmultipleParents(p: Parent<number>|Parent<string>|Parent<boolean>){if(pinstanceofChild){p;// p: Child<number> | Child<string> | Child<boolean>}else{p;// p: Parent<number> | Parent<string> | Parent<boolean>}}functionmixedChildren(p: Parent<number>|Child<string>){if(pinstanceofChild){p;// p: Child<string>}else{p;// p: Parent<number>}}functionimcompatibleOptions(p: Parent<number>|Parent<string>|{foo: boolean}){if(pinstanceofChild){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
Copy Markdown
ContributorAuthor

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

letx: T;if(xinstanceofK){
...
}

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<t1extendsC1,t2extendsC2>inK<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:

consta: {f:1|2}={f: 1};constb: {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:

consta: {readonlyf : 1|2}&{writeonlyf: 1|2}={f: 1};constb: {readonlyf : number}&{writeonlyf : 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
Copy Markdown
consta: {f:1|2}={f: 1};constb: {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
Copy Markdown
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.

@typescript-bot

Copy link
Copy Markdown
Contributor

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.

@microsoftMicrosoft (microsoft) locked as resolved and limited conversation to collaborators Oct 21, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

For Backlog BugPRs 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

@Nathan-Fenner@DanielRosenwasser@RyanCavanaugh@typescript-bot@fatcerberus@sandersn@ahejlsberg