Skip to content

Mark indexed access object type comparisons as unreliable #52106

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

Merged
merged 1 commit into from
Mar 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20893,6 +20893,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// Relate components directly before falling back to constraint relationships
// A type S[K] is related to a type T[J] if S is related to T and K is related to J.
if (result = isRelatedTo((source as IndexedAccessType).objectType, (target as IndexedAccessType).objectType, RecursionFlags.Both, reportErrors)) {
// This does _not_ generalize - specific instantiations of `S[K]` and `T[J]` may be related, even if the indexed accesses generally are not.
// For example, `S = {x: string, a: string}`, `T = {x: string, b: string}`, `K = J = "x"`. `S` and `T` are unrelated, but the result of executing
// `S["x"]` and `T["x"]` _are_. Given that, we have to flag the object type comparison here as "unreliable", since while the generic result can reliably
// be used in the affirmative case, it failing is not an indicator that the structural result will not succeed.
instantiateType((source as IndexedAccessType).objectType, reportUnreliableMapper);
result &= isRelatedTo((source as IndexedAccessType).indexType, (target as IndexedAccessType).indexType, RecursionFlags.Both, reportErrors);
}
if (result) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//// [genericIndexedAccessVarianceComparisonResultCorrect.ts]
class A {
x: string = 'A';
y: number = 0;
}

class B {
x: string = 'B';
z: boolean = true;
}

type T<X extends { x: any }> = Pick<X, 'x'>;

type C = T<A>;
type D = T<B>;

type C_extends_D = C extends D ? true : false; // true
type PickA_extends_PickB = Pick<A, 'x'> extends Pick<B, 'x'> ? true : false; // true
type TA_extends_TB = T<A> extends T<B> ? true : false; // should be true

declare let a: T<A>;
declare let b: T<B>;
declare let c: C;
declare let d: D;

b = a; // should be no error
c = d;

//// [genericIndexedAccessVarianceComparisonResultCorrect.js]
var A = /** @class */ (function () {
function A() {
this.x = 'A';
this.y = 0;
}
return A;
}());
var B = /** @class */ (function () {
function B() {
this.x = 'B';
this.z = true;
}
return B;
}());
b = a; // should be no error
c = d;
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
=== tests/cases/compiler/genericIndexedAccessVarianceComparisonResultCorrect.ts ===
class A {
>A : Symbol(A, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 0, 0))

x: string = 'A';
>x : Symbol(A.x, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 0, 9))

y: number = 0;
>y : Symbol(A.y, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 1, 20))
}

class B {
>B : Symbol(B, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 3, 1))

x: string = 'B';
>x : Symbol(B.x, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 5, 9))

z: boolean = true;
>z : Symbol(B.z, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 6, 20))
}

type T<X extends { x: any }> = Pick<X, 'x'>;
>T : Symbol(T, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 8, 1))
>X : Symbol(X, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 10, 7))
>x : Symbol(x, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 10, 18))
>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --))
>X : Symbol(X, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 10, 7))

type C = T<A>;
>C : Symbol(C, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 10, 44))
>T : Symbol(T, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 8, 1))
>A : Symbol(A, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 0, 0))

type D = T<B>;
>D : Symbol(D, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 12, 14))
>T : Symbol(T, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 8, 1))
>B : Symbol(B, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 3, 1))

type C_extends_D = C extends D ? true : false; // true
>C_extends_D : Symbol(C_extends_D, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 13, 14))
>C : Symbol(C, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 10, 44))
>D : Symbol(D, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 12, 14))

type PickA_extends_PickB = Pick<A, 'x'> extends Pick<B, 'x'> ? true : false; // true
>PickA_extends_PickB : Symbol(PickA_extends_PickB, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 15, 46))
>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --))
>A : Symbol(A, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 0, 0))
>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --))
>B : Symbol(B, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 3, 1))

type TA_extends_TB = T<A> extends T<B> ? true : false; // should be true
>TA_extends_TB : Symbol(TA_extends_TB, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 16, 76))
>T : Symbol(T, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 8, 1))
>A : Symbol(A, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 0, 0))
>T : Symbol(T, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 8, 1))
>B : Symbol(B, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 3, 1))

declare let a: T<A>;
>a : Symbol(a, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 19, 11))
>T : Symbol(T, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 8, 1))
>A : Symbol(A, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 0, 0))

declare let b: T<B>;
>b : Symbol(b, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 20, 11))
>T : Symbol(T, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 8, 1))
>B : Symbol(B, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 3, 1))

declare let c: C;
>c : Symbol(c, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 21, 11))
>C : Symbol(C, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 10, 44))

declare let d: D;
>d : Symbol(d, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 22, 11))
>D : Symbol(D, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 12, 14))

b = a; // should be no error
>b : Symbol(b, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 20, 11))
>a : Symbol(a, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 19, 11))

c = d;
>c : Symbol(c, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 21, 11))
>d : Symbol(d, Decl(genericIndexedAccessVarianceComparisonResultCorrect.ts, 22, 11))

Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
=== tests/cases/compiler/genericIndexedAccessVarianceComparisonResultCorrect.ts ===
class A {
>A : A

x: string = 'A';
>x : string
>'A' : "A"

y: number = 0;
>y : number
>0 : 0
}

class B {
>B : B

x: string = 'B';
>x : string
>'B' : "B"

z: boolean = true;
>z : boolean
>true : true
}

type T<X extends { x: any }> = Pick<X, 'x'>;
>T : T<X>
>x : any

type C = T<A>;
>C : { x: string; }

type D = T<B>;
>D : { x: string; }

type C_extends_D = C extends D ? true : false; // true
>C_extends_D : true
>true : true
>false : false

type PickA_extends_PickB = Pick<A, 'x'> extends Pick<B, 'x'> ? true : false; // true
>PickA_extends_PickB : true
>true : true
>false : false

type TA_extends_TB = T<A> extends T<B> ? true : false; // should be true
>TA_extends_TB : true
>true : true
>false : false

declare let a: T<A>;
>a : T<A>

declare let b: T<B>;
>b : T<B>

declare let c: C;
>c : C

declare let d: D;
>d : D

b = a; // should be no error
>b = a : T<A>
>b : T<B>
>a : T<A>

c = d;
>c = d : D
>c : C
>d : D

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
class A {
x: string = 'A';
y: number = 0;
}

class B {
x: string = 'B';
z: boolean = true;
}

type T<X extends { x: any }> = Pick<X, 'x'>;

type C = T<A>;
type D = T<B>;

type C_extends_D = C extends D ? true : false; // true
type PickA_extends_PickB = Pick<A, 'x'> extends Pick<B, 'x'> ? true : false; // true
type TA_extends_TB = T<A> extends T<B> ? true : false; // should be true

declare let a: T<A>;
declare let b: T<B>;
declare let c: C;
declare let d: D;

b = a; // should be no error
c = d;