Skip to content

Commit 949daee

Browse files
committed
Use strict variance checks for strict subtype checks unconditionally
1 parent 5f9f9e3 commit 949daee

23 files changed

+770
-101
lines changed

src/compiler/checker.ts

+7-6
Original file line numberDiff line numberDiff line change
@@ -17681,7 +17681,7 @@ namespace ts {
1768117681
target: Signature,
1768217682
ignoreReturnTypes: boolean): boolean {
1768317683
return compareSignaturesRelated(source, target, ignoreReturnTypes ? SignatureCheckMode.IgnoreReturnTypes : 0, /*reportErrors*/ false,
17684-
/*errorReporter*/ undefined, /*errorReporter*/ undefined, compareTypesAssignable, /*reportUnreliableMarkers*/ undefined) !== Ternary.False;
17684+
/*errorReporter*/ undefined, /*errorReporter*/ undefined, compareTypesAssignable, /*reportUnreliableMarkers*/ undefined, assignableRelation) !== Ternary.False;
1768517685
}
1768617686

1768717687
type ErrorReporter = (message: DiagnosticMessage, arg0?: string, arg1?: string) => void;
@@ -17705,7 +17705,8 @@ namespace ts {
1770517705
errorReporter: ErrorReporter | undefined,
1770617706
incompatibleErrorReporter: ((source: Type, target: Type) => void) | undefined,
1770717707
compareTypes: TypeComparer,
17708-
reportUnreliableMarkers: TypeMapper | undefined): Ternary {
17708+
reportUnreliableMarkers: TypeMapper | undefined,
17709+
relation: typeof strictSubtypeRelation): Ternary {
1770917710
// TODO (drosen): De-duplicate code between related functions.
1771017711
if (source === target) {
1771117712
return Ternary.True;
@@ -17735,8 +17736,8 @@ namespace ts {
1773517736
}
1773617737

1773717738
const kind = target.declaration ? target.declaration.kind : SyntaxKind.Unknown;
17738-
const strictVariance = !(checkMode & SignatureCheckMode.Callback) && strictFunctionTypes && kind !== SyntaxKind.MethodDeclaration &&
17739-
kind !== SyntaxKind.MethodSignature && kind !== SyntaxKind.Constructor;
17739+
const strictVariance = relation === strictSubtypeRelation || (!(checkMode & SignatureCheckMode.Callback) && strictFunctionTypes && kind !== SyntaxKind.MethodDeclaration &&
17740+
kind !== SyntaxKind.MethodSignature && kind !== SyntaxKind.Constructor);
1774017741
let result = Ternary.True;
1774117742

1774217743
const sourceThisType = getThisTypeOfSignature(source);
@@ -17776,7 +17777,7 @@ namespace ts {
1777617777
const callbacks = sourceSig && targetSig && !getTypePredicateOfSignature(sourceSig) && !getTypePredicateOfSignature(targetSig) &&
1777717778
(getFalsyFlags(sourceType) & TypeFlags.Nullable) === (getFalsyFlags(targetType) & TypeFlags.Nullable);
1777817779
let related = callbacks ?
17779-
compareSignaturesRelated(targetSig, sourceSig, (checkMode & SignatureCheckMode.StrictArity) | (strictVariance ? SignatureCheckMode.StrictCallback : SignatureCheckMode.BivariantCallback), reportErrors, errorReporter, incompatibleErrorReporter, compareTypes, reportUnreliableMarkers) :
17780+
compareSignaturesRelated(targetSig, sourceSig, (checkMode & SignatureCheckMode.StrictArity) | (strictVariance ? SignatureCheckMode.StrictCallback : SignatureCheckMode.BivariantCallback), reportErrors, errorReporter, incompatibleErrorReporter, compareTypes, reportUnreliableMarkers, relation) :
1778017781
!(checkMode & SignatureCheckMode.Callback) && !strictVariance && compareTypes(sourceType, targetType, /*reportErrors*/ false) || compareTypes(targetType, sourceType, reportErrors);
1778117782
// With strict arity, (x: number | undefined) => void is a subtype of (x?: number | undefined) => void
1778217783
if (related && checkMode & SignatureCheckMode.StrictArity && i >= getMinArgumentCount(source) && i < getMinArgumentCount(target) && compareTypes(sourceType, targetType, /*reportErrors*/ false)) {
@@ -20173,7 +20174,7 @@ namespace ts {
2017320174
*/
2017420175
function signatureRelatedTo(source: Signature, target: Signature, erase: boolean, reportErrors: boolean, incompatibleReporter: (source: Type, target: Type) => void): Ternary {
2017520176
return compareSignaturesRelated(erase ? getErasedSignature(source) : source, erase ? getErasedSignature(target) : target,
20176-
relation === strictSubtypeRelation ? SignatureCheckMode.StrictArity : 0, reportErrors, reportError, incompatibleReporter, isRelatedToWorker, makeFunctionTypeMapper(reportUnreliableMarkers));
20177+
relation === strictSubtypeRelation ? SignatureCheckMode.StrictArity : 0, reportErrors, reportError, incompatibleReporter, isRelatedToWorker, makeFunctionTypeMapper(reportUnreliableMarkers), relation);
2017720178
}
2017820179

2017920180
function signaturesIdenticalTo(source: Type, target: Type, kind: SignatureKind): Ternary {

tests/baselines/reference/arrayLiteralWithMultipleBestCommonTypes.types

+2-2
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ var cs = [a, b, c]; // { x: number; y?: number };[]
3636
>c : { x: number; a?: number; }
3737

3838
var ds = [(x: Object) => 1, (x: string) => 2]; // { (x:Object) => number }[]
39-
>ds : ((x: Object) => number)[]
40-
>[(x: Object) => 1, (x: string) => 2] : ((x: Object) => number)[]
39+
>ds : ((x: string) => number)[]
40+
>[(x: Object) => 1, (x: string) => 2] : ((x: string) => number)[]
4141
>(x: Object) => 1 : (x: Object) => number
4242
>x : Object
4343
>1 : 1

tests/baselines/reference/arrayOfFunctionTypes3.types

+7-7
Original file line numberDiff line numberDiff line change
@@ -50,28 +50,28 @@ var c: { (x: number): number; (x: any): any; };
5050
>x : any
5151

5252
var z = [a, b, c];
53-
>z : { (x: number): number; (x: any): any; }[]
54-
>[a, b, c] : { (x: number): number; (x: any): any; }[]
53+
>z : ({ (x: number): number; (x: string): string; } | { (x: number): number; (x: any): any; })[]
54+
>[a, b, c] : ({ (x: number): number; (x: string): string; } | { (x: number): number; (x: any): any; })[]
5555
>a : { (x: number): number; (x: string): string; }
5656
>b : { (x: number): number; (x: string): string; }
5757
>c : { (x: number): number; (x: any): any; }
5858

5959
var r4 = z[0];
60-
>r4 : { (x: number): number; (x: any): any; }
61-
>z[0] : { (x: number): number; (x: any): any; }
62-
>z : { (x: number): number; (x: any): any; }[]
60+
>r4 : { (x: number): number; (x: string): string; } | { (x: number): number; (x: any): any; }
61+
>z[0] : { (x: number): number; (x: string): string; } | { (x: number): number; (x: any): any; }
62+
>z : ({ (x: number): number; (x: string): string; } | { (x: number): number; (x: any): any; })[]
6363
>0 : 0
6464

6565
var r5 = r4(''); // any not string
6666
>r5 : any
6767
>r4('') : any
68-
>r4 : { (x: number): number; (x: any): any; }
68+
>r4 : { (x: number): number; (x: string): string; } | { (x: number): number; (x: any): any; }
6969
>'' : ""
7070

7171
var r5b = r4(1);
7272
>r5b : number
7373
>r4(1) : number
74-
>r4 : { (x: number): number; (x: any): any; }
74+
>r4 : { (x: number): number; (x: string): string; } | { (x: number): number; (x: any): any; }
7575
>1 : 1
7676

7777
var a2: { <T>(x: T): number; (x: string): string;};

tests/baselines/reference/bestChoiceType.symbols

+6-12
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,12 @@
22
// Repro from #10041
33

44
(''.match(/ /) || []).map(s => s.toLowerCase());
5-
>(''.match(/ /) || []).map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
5+
>(''.match(/ /) || []).map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
66
>''.match : Symbol(String.match, Decl(lib.es5.d.ts, --, --))
77
>match : Symbol(String.match, Decl(lib.es5.d.ts, --, --))
8-
>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
8+
>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
99
>s : Symbol(s, Decl(bestChoiceType.ts, 2, 26))
10-
>s.toLowerCase : Symbol(String.toLowerCase, Decl(lib.es5.d.ts, --, --))
1110
>s : Symbol(s, Decl(bestChoiceType.ts, 2, 26))
12-
>toLowerCase : Symbol(String.toLowerCase, Decl(lib.es5.d.ts, --, --))
1311

1412
// Similar cases
1513

@@ -27,13 +25,11 @@ function f1() {
2725

2826
let z = y.map(s => s.toLowerCase());
2927
>z : Symbol(z, Decl(bestChoiceType.ts, 9, 7))
30-
>y.map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
28+
>y.map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
3129
>y : Symbol(y, Decl(bestChoiceType.ts, 8, 7))
32-
>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
30+
>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
3331
>s : Symbol(s, Decl(bestChoiceType.ts, 9, 18))
34-
>s.toLowerCase : Symbol(String.toLowerCase, Decl(lib.es5.d.ts, --, --))
3532
>s : Symbol(s, Decl(bestChoiceType.ts, 9, 18))
36-
>toLowerCase : Symbol(String.toLowerCase, Decl(lib.es5.d.ts, --, --))
3733
}
3834

3935
function f2() {
@@ -51,12 +47,10 @@ function f2() {
5147

5248
let z = y.map(s => s.toLowerCase());
5349
>z : Symbol(z, Decl(bestChoiceType.ts, 15, 7))
54-
>y.map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
50+
>y.map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
5551
>y : Symbol(y, Decl(bestChoiceType.ts, 14, 7))
56-
>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
52+
>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
5753
>s : Symbol(s, Decl(bestChoiceType.ts, 15, 18))
58-
>s.toLowerCase : Symbol(String.toLowerCase, Decl(lib.es5.d.ts, --, --))
5954
>s : Symbol(s, Decl(bestChoiceType.ts, 15, 18))
60-
>toLowerCase : Symbol(String.toLowerCase, Decl(lib.es5.d.ts, --, --))
6155
}
6256

tests/baselines/reference/bestChoiceType.types

+37-37
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,23 @@
22
// Repro from #10041
33

44
(''.match(/ /) || []).map(s => s.toLowerCase());
5-
>(''.match(/ /) || []).map(s => s.toLowerCase()) : string[]
6-
>(''.match(/ /) || []).map : <U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]
7-
>(''.match(/ /) || []) : RegExpMatchArray
8-
>''.match(/ /) || [] : RegExpMatchArray
5+
>(''.match(/ /) || []).map(s => s.toLowerCase()) : any[]
6+
>(''.match(/ /) || []).map : (<U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]) | (<U>(callbackfn: (value: never, index: number, array: never[]) => U, thisArg?: any) => U[])
7+
>(''.match(/ /) || []) : RegExpMatchArray | never[]
8+
>''.match(/ /) || [] : RegExpMatchArray | never[]
99
>''.match(/ /) : RegExpMatchArray | null
1010
>''.match : (regexp: string | RegExp) => RegExpMatchArray | null
1111
>'' : ""
1212
>match : (regexp: string | RegExp) => RegExpMatchArray | null
1313
>/ / : RegExp
1414
>[] : never[]
15-
>map : <U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]
16-
>s => s.toLowerCase() : (s: string) => string
17-
>s : string
18-
>s.toLowerCase() : string
19-
>s.toLowerCase : () => string
20-
>s : string
21-
>toLowerCase : () => string
15+
>map : (<U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]) | (<U>(callbackfn: (value: never, index: number, array: never[]) => U, thisArg?: any) => U[])
16+
>s => s.toLowerCase() : (s: any) => any
17+
>s : any
18+
>s.toLowerCase() : any
19+
>s.toLowerCase : any
20+
>s : any
21+
>toLowerCase : any
2222

2323
// Similar cases
2424

@@ -34,23 +34,23 @@ function f1() {
3434
>/ / : RegExp
3535

3636
let y = x || [];
37-
>y : RegExpMatchArray
38-
>x || [] : RegExpMatchArray
37+
>y : RegExpMatchArray | never[]
38+
>x || [] : RegExpMatchArray | never[]
3939
>x : RegExpMatchArray | null
4040
>[] : never[]
4141

4242
let z = y.map(s => s.toLowerCase());
43-
>z : string[]
44-
>y.map(s => s.toLowerCase()) : string[]
45-
>y.map : <U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]
46-
>y : RegExpMatchArray
47-
>map : <U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]
48-
>s => s.toLowerCase() : (s: string) => string
49-
>s : string
50-
>s.toLowerCase() : string
51-
>s.toLowerCase : () => string
52-
>s : string
53-
>toLowerCase : () => string
43+
>z : any[]
44+
>y.map(s => s.toLowerCase()) : any[]
45+
>y.map : (<U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]) | (<U>(callbackfn: (value: never, index: number, array: never[]) => U, thisArg?: any) => U[])
46+
>y : RegExpMatchArray | never[]
47+
>map : (<U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]) | (<U>(callbackfn: (value: never, index: number, array: never[]) => U, thisArg?: any) => U[])
48+
>s => s.toLowerCase() : (s: any) => any
49+
>s : any
50+
>s.toLowerCase() : any
51+
>s.toLowerCase : any
52+
>s : any
53+
>toLowerCase : any
5454
}
5555

5656
function f2() {
@@ -65,23 +65,23 @@ function f2() {
6565
>/ / : RegExp
6666

6767
let y = x ? x : [];
68-
>y : RegExpMatchArray
69-
>x ? x : [] : RegExpMatchArray
68+
>y : RegExpMatchArray | never[]
69+
>x ? x : [] : RegExpMatchArray | never[]
7070
>x : RegExpMatchArray | null
7171
>x : RegExpMatchArray
7272
>[] : never[]
7373

7474
let z = y.map(s => s.toLowerCase());
75-
>z : string[]
76-
>y.map(s => s.toLowerCase()) : string[]
77-
>y.map : <U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]
78-
>y : RegExpMatchArray
79-
>map : <U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]
80-
>s => s.toLowerCase() : (s: string) => string
81-
>s : string
82-
>s.toLowerCase() : string
83-
>s.toLowerCase : () => string
84-
>s : string
85-
>toLowerCase : () => string
75+
>z : any[]
76+
>y.map(s => s.toLowerCase()) : any[]
77+
>y.map : (<U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]) | (<U>(callbackfn: (value: never, index: number, array: never[]) => U, thisArg?: any) => U[])
78+
>y : RegExpMatchArray | never[]
79+
>map : (<U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]) | (<U>(callbackfn: (value: never, index: number, array: never[]) => U, thisArg?: any) => U[])
80+
>s => s.toLowerCase() : (s: any) => any
81+
>s : any
82+
>s.toLowerCase() : any
83+
>s.toLowerCase : any
84+
>s : any
85+
>toLowerCase : any
8686
}
8787

tests/baselines/reference/contextualTypingArrayOfLambdas.types

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ class C extends A {
2323
}
2424

2525
var xs = [(x: A) => { }, (x: B) => { }, (x: C) => { }];
26-
>xs : ((x: A) => void)[]
27-
>[(x: A) => { }, (x: B) => { }, (x: C) => { }] : ((x: A) => void)[]
26+
>xs : (((x: B) => void) | ((x: C) => void))[]
27+
>[(x: A) => { }, (x: B) => { }, (x: C) => { }] : (((x: B) => void) | ((x: C) => void))[]
2828
>(x: A) => { } : (x: A) => void
2929
>x : A
3030
>(x: B) => { } : (x: B) => void

tests/baselines/reference/flatArrayNoExcessiveStackDepth.types

+3-3
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ const repro_43249 = (value: unknown) => {
3636
>"No" : "No"
3737
}
3838
const match = value.match(/anything/) || [];
39-
>match : RegExpMatchArray
40-
>value.match(/anything/) || [] : RegExpMatchArray
39+
>match : RegExpMatchArray | never[]
40+
>value.match(/anything/) || [] : RegExpMatchArray | never[]
4141
>value.match(/anything/) : RegExpMatchArray | null
4242
>value.match : { (regexp: string | RegExp): RegExpMatchArray | null; (matcher: { [Symbol.match](string: string): RegExpMatchArray | null; }): RegExpMatchArray | null; }
4343
>value : string
@@ -48,7 +48,7 @@ const repro_43249 = (value: unknown) => {
4848
const [, extracted] = match;
4949
> : undefined
5050
>extracted : string
51-
>match : RegExpMatchArray
51+
>match : RegExpMatchArray | never[]
5252

5353
};
5454

tests/baselines/reference/initializedDestructuringAssignmentTypes.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const [, a = ''] = ''.match('') || [];
33
> : undefined
44
>a : string
55
>'' : ""
6-
>''.match('') || [] : RegExpMatchArray
6+
>''.match('') || [] : RegExpMatchArray | undefined[]
77
>''.match('') : RegExpMatchArray
88
>''.match : (regexp: string | RegExp) => RegExpMatchArray
99
>'' : ""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
//// [strictSubtypeReduction.ts]
2+
// Repro from #41977
3+
4+
class S1 {
5+
static f(a: number | string): void { }
6+
}
7+
8+
class S2 {
9+
static f(a: number): void { }
10+
static g(a: number): void { }
11+
}
12+
13+
function f(a: number): void { }
14+
function g(a: number): void { }
15+
16+
// Declaring the following type aliases should have no effect
17+
18+
type T1 = typeof S2.g;
19+
type T2 = typeof g;
20+
21+
// All should have type ((a: number) => void)[]
22+
23+
const y1 = [S1.f, f];
24+
const y2 = [S1.f, g];
25+
const y3 = [S1.f, S2.f];
26+
const y4 = [S1.f, S2.g];
27+
28+
// All assignments should be errors in strict mode, but won't be without strict function types on
29+
30+
const x1: ((ctrl: number | string) => void)[] = y1;
31+
const x2: ((ctrl: number | string) => void)[] = y2;
32+
const x3: ((ctrl: number | string) => void)[] = y3;
33+
const x4: ((ctrl: number | string) => void)[] = y4;
34+
35+
//// [strictSubtypeReduction.js]
36+
// Repro from #41977
37+
var S1 = /** @class */ (function () {
38+
function S1() {
39+
}
40+
S1.f = function (a) { };
41+
return S1;
42+
}());
43+
var S2 = /** @class */ (function () {
44+
function S2() {
45+
}
46+
S2.f = function (a) { };
47+
S2.g = function (a) { };
48+
return S2;
49+
}());
50+
function f(a) { }
51+
function g(a) { }
52+
// All should have type ((a: number) => void)[]
53+
var y1 = [S1.f, f];
54+
var y2 = [S1.f, g];
55+
var y3 = [S1.f, S2.f];
56+
var y4 = [S1.f, S2.g];
57+
// All assignments should be errors in strict mode, but won't be without strict function types on
58+
var x1 = y1;
59+
var x2 = y2;
60+
var x3 = y3;
61+
var x4 = y4;

0 commit comments

Comments
 (0)