Skip to content

Commit cef42c9

Browse files
authored
Merge pull request #15104 from Microsoft/covariantCallbacks
Covariant checking for callback parameters
2 parents 60fe5a8 + 3cda0ea commit cef42c9

21 files changed

+727
-154
lines changed

Diff for: src/compiler/checker.ts

+26-6
Original file line numberDiff line numberDiff line change
@@ -8206,7 +8206,8 @@ namespace ts {
82068206
function isSignatureAssignableTo(source: Signature,
82078207
target: Signature,
82088208
ignoreReturnTypes: boolean): boolean {
8209-
return compareSignaturesRelated(source, target, ignoreReturnTypes, /*reportErrors*/ false, /*errorReporter*/ undefined, compareTypesAssignable) !== Ternary.False;
8209+
return compareSignaturesRelated(source, target, /*checkAsCallback*/ false, ignoreReturnTypes, /*reportErrors*/ false,
8210+
/*errorReporter*/ undefined, compareTypesAssignable) !== Ternary.False;
82108211
}
82118212

82128213
type ErrorReporter = (message: DiagnosticMessage, arg0?: string, arg1?: string) => void;
@@ -8216,6 +8217,7 @@ namespace ts {
82168217
*/
82178218
function compareSignaturesRelated(source: Signature,
82188219
target: Signature,
8220+
checkAsCallback: boolean,
82198221
ignoreReturnTypes: boolean,
82208222
reportErrors: boolean,
82218223
errorReporter: ErrorReporter,
@@ -8258,9 +8260,23 @@ namespace ts {
82588260
const sourceParams = source.parameters;
82598261
const targetParams = target.parameters;
82608262
for (let i = 0; i < checkCount; i++) {
8261-
const s = i < sourceMax ? getTypeOfParameter(sourceParams[i]) : getRestTypeOfSignature(source);
8262-
const t = i < targetMax ? getTypeOfParameter(targetParams[i]) : getRestTypeOfSignature(target);
8263-
const related = compareTypes(s, t, /*reportErrors*/ false) || compareTypes(t, s, reportErrors);
8263+
const sourceType = i < sourceMax ? getTypeOfParameter(sourceParams[i]) : getRestTypeOfSignature(source);
8264+
const targetType = i < targetMax ? getTypeOfParameter(targetParams[i]) : getRestTypeOfSignature(target);
8265+
const sourceSig = getSingleCallSignature(getNonNullableType(sourceType));
8266+
const targetSig = getSingleCallSignature(getNonNullableType(targetType));
8267+
// In order to ensure that any generic type Foo<T> is at least co-variant with respect to T no matter
8268+
// how Foo uses T, we need to relate parameters bi-variantly (given that parameters are input positions,
8269+
// they naturally relate only contra-variantly). However, if the source and target parameters both have
8270+
// function types with a single call signature, we known we are relating two callback parameters. In
8271+
// that case it is sufficient to only relate the parameters of the signatures co-variantly because,
8272+
// similar to return values, callback parameters are output positions. This means that a Promise<T>,
8273+
// where T is used only in callback parameter positions, will be co-variant (as opposed to bi-variant)
8274+
// with respect to T.
8275+
const callbacks = sourceSig && targetSig && !sourceSig.typePredicate && !targetSig.typePredicate &&
8276+
(getFalsyFlags(sourceType) & TypeFlags.Nullable) === (getFalsyFlags(targetType) & TypeFlags.Nullable);
8277+
const related = callbacks ?
8278+
compareSignaturesRelated(targetSig, sourceSig, /*checkAsCallback*/ true, /*ignoreReturnTypes*/ false, reportErrors, errorReporter, compareTypes) :
8279+
!checkAsCallback && compareTypes(sourceType, targetType, /*reportErrors*/ false) || compareTypes(targetType, sourceType, reportErrors);
82648280
if (!related) {
82658281
if (reportErrors) {
82668282
errorReporter(Diagnostics.Types_of_parameters_0_and_1_are_incompatible,
@@ -8292,7 +8308,11 @@ namespace ts {
82928308
}
82938309
}
82948310
else {
8295-
result &= compareTypes(sourceReturnType, targetReturnType, reportErrors);
8311+
// When relating callback signatures, we still need to relate return types bi-variantly as otherwise
8312+
// the containing type wouldn't be co-variant. For example, interface Foo<T> { add(cb: () => T): void }
8313+
// wouldn't be co-variant for T without this rule.
8314+
result &= checkAsCallback && compareTypes(targetReturnType, sourceReturnType, /*reportErrors*/ false) ||
8315+
compareTypes(sourceReturnType, targetReturnType, reportErrors);
82968316
}
82978317

82988318
}
@@ -9260,7 +9280,7 @@ namespace ts {
92609280
* See signatureAssignableTo, compareSignaturesIdentical
92619281
*/
92629282
function signatureRelatedTo(source: Signature, target: Signature, reportErrors: boolean): Ternary {
9263-
return compareSignaturesRelated(source, target, /*ignoreReturnTypes*/ false, reportErrors, reportError, isRelatedTo);
9283+
return compareSignaturesRelated(source, target, /*checkAsCallback*/ false, /*ignoreReturnTypes*/ false, reportErrors, reportError, isRelatedTo);
92649284
}
92659285

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

Diff for: src/compiler/parser.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ namespace ts {
5757
// The visitXXX functions could be written as local functions that close over the cbNode and cbNodeArray
5858
// callback parameters, but that causes a closure allocation for each invocation with noticeable effects
5959
// on performance.
60-
const visitNodes: (cb: (node: Node | Node[]) => T, nodes: Node[]) => T = cbNodeArray ? visitNodeArray : visitEachNode;
60+
const visitNodes: (cb: ((node: Node) => T) | ((node: Node[]) => T), nodes: Node[]) => T = cbNodeArray ? visitNodeArray : visitEachNode;
6161
const cbNodes = cbNodeArray || cbNode;
6262
switch (node.kind) {
6363
case SyntaxKind.QualifiedName:

Diff for: src/compiler/visitor.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -885,7 +885,7 @@ namespace ts {
885885
return initial;
886886
}
887887

888-
const reduceNodes: (nodes: NodeArray<Node>, f: (memo: T, node: Node | NodeArray<Node>) => T, initial: T) => T = cbNodeArray ? reduceNodeArray : reduceLeft;
888+
const reduceNodes: (nodes: NodeArray<Node>, f: ((memo: T, node: Node) => T) | ((memo: T, node: NodeArray<Node>) => T), initial: T) => T = cbNodeArray ? reduceNodeArray : reduceLeft;
889889
const cbNodes = cbNodeArray || cbNode;
890890
const kind = node.kind;
891891

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithCallSignatures3.ts(71,1): error TS2322: Type '(x: (arg: Base) => Derived, y: (arg2: Base) => Derived) => (r: Base) => Derived' is not assignable to type '<T extends Base, U extends Derived>(x: (arg: T) => U, y: (arg2: { foo: string; bing: number; }) => U) => (r: T) => U'.
2+
Types of parameters 'y' and 'y' are incompatible.
3+
Types of parameters 'arg2' and 'arg2' are incompatible.
4+
Type 'Base' is not assignable to type '{ foo: string; bing: number; }'.
5+
Property 'bing' is missing in type 'Base'.
6+
7+
8+
==== tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithCallSignatures3.ts (1 errors) ====
9+
// these are all permitted with the current rules, since we do not do contextual signature instantiation
10+
11+
class Base { foo: string; }
12+
class Derived extends Base { bar: string; }
13+
class Derived2 extends Derived { baz: string; }
14+
class OtherDerived extends Base { bing: string; }
15+
16+
var a: (x: number) => number[];
17+
var a2: (x: number) => string[];
18+
var a3: (x: number) => void;
19+
var a4: (x: string, y: number) => string;
20+
var a5: (x: (arg: string) => number) => string;
21+
var a6: (x: (arg: Base) => Derived) => Base;
22+
var a7: (x: (arg: Base) => Derived) => (r: Base) => Derived;
23+
var a8: (x: (arg: Base) => Derived, y: (arg2: Base) => Derived) => (r: Base) => Derived;
24+
var a9: (x: (arg: Base) => Derived, y: (arg2: Base) => Derived) => (r: Base) => Derived;
25+
var a10: (...x: Derived[]) => Derived;
26+
var a11: (x: { foo: string }, y: { foo: string; bar: string }) => Base;
27+
var a12: (x: Array<Base>, y: Array<Derived2>) => Array<Derived>;
28+
var a13: (x: Array<Base>, y: Array<Derived>) => Array<Derived>;
29+
var a14: (x: { a: string; b: number }) => Object;
30+
var a15: {
31+
(x: number): number[];
32+
(x: string): string[];
33+
}
34+
var a16: {
35+
<T extends Derived>(x: T): number[];
36+
<U extends Base>(x: U): number[];
37+
}
38+
var a17: {
39+
(x: (a: number) => number): number[];
40+
(x: (a: string) => string): string[];
41+
};
42+
var a18: {
43+
(x: {
44+
(a: number): number;
45+
(a: string): string;
46+
}): any[];
47+
(x: {
48+
(a: boolean): boolean;
49+
(a: Date): Date;
50+
}): any[];
51+
}
52+
53+
var b: <T>(x: T) => T[];
54+
a = b; // ok
55+
b = a; // ok
56+
var b2: <T>(x: T) => string[];
57+
a2 = b2; // ok
58+
b2 = a2; // ok
59+
var b3: <T>(x: T) => T;
60+
a3 = b3; // ok
61+
b3 = a3; // ok
62+
var b4: <T, U>(x: T, y: U) => T;
63+
a4 = b4; // ok
64+
b4 = a4; // ok
65+
var b5: <T, U>(x: (arg: T) => U) => T;
66+
a5 = b5; // ok
67+
b5 = a5; // ok
68+
var b6: <T extends Base, U extends Derived>(x: (arg: T) => U) => T;
69+
a6 = b6; // ok
70+
b6 = a6; // ok
71+
var b7: <T extends Base, U extends Derived>(x: (arg: T) => U) => (r: T) => U;
72+
a7 = b7; // ok
73+
b7 = a7; // ok
74+
var b8: <T extends Base, U extends Derived>(x: (arg: T) => U, y: (arg2: T) => U) => (r: T) => U;
75+
a8 = b8; // ok
76+
b8 = a8; // ok
77+
var b9: <T extends Base, U extends Derived>(x: (arg: T) => U, y: (arg2: { foo: string; bing: number }) => U) => (r: T) => U;
78+
a9 = b9; // ok
79+
b9 = a9; // ok
80+
~~
81+
!!! error TS2322: Type '(x: (arg: Base) => Derived, y: (arg2: Base) => Derived) => (r: Base) => Derived' is not assignable to type '<T extends Base, U extends Derived>(x: (arg: T) => U, y: (arg2: { foo: string; bing: number; }) => U) => (r: T) => U'.
82+
!!! error TS2322: Types of parameters 'y' and 'y' are incompatible.
83+
!!! error TS2322: Types of parameters 'arg2' and 'arg2' are incompatible.
84+
!!! error TS2322: Type 'Base' is not assignable to type '{ foo: string; bing: number; }'.
85+
!!! error TS2322: Property 'bing' is missing in type 'Base'.
86+
var b10: <T extends Derived>(...x: T[]) => T;
87+
a10 = b10; // ok
88+
b10 = a10; // ok
89+
var b11: <T extends Base>(x: T, y: T) => T;
90+
a11 = b11; // ok
91+
b11 = a11; // ok
92+
var b12: <T extends Array<Base>>(x: Array<Base>, y: T) => Array<Derived>;
93+
a12 = b12; // ok
94+
b12 = a12; // ok
95+
var b13: <T extends Array<Derived>>(x: Array<Base>, y: T) => T;
96+
a13 = b13; // ok
97+
b13 = a13; // ok
98+
var b14: <T>(x: { a: T; b: T }) => T;
99+
a14 = b14; // ok
100+
b14 = a14; // ok
101+
var b15: <T>(x: T) => T[];
102+
a15 = b15; // ok
103+
b15 = a15; // ok
104+
var b16: <T extends Base>(x: T) => number[];
105+
a16 = b16; // ok
106+
b16 = a16; // ok
107+
var b17: <T>(x: (a: T) => T) => T[]; // ok
108+
a17 = b17; // ok
109+
b17 = a17; // ok
110+
var b18: <T>(x: (a: T) => T) => T[];
111+
a18 = b18; // ok
112+
b18 = a18; // ok
113+

Diff for: tests/baselines/reference/assignmentCompatWithCallSignatures4.errors.txt

+16-20
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithCallSignatures4.ts(52,9): error TS2322: Type '<T extends Base, U extends Derived>(x: (arg: T) => U, y: (arg2: { foo: number; }) => U) => (r: T) => U' is not assignable to type '(x: (arg: Base) => Derived, y: (arg2: Base) => Derived) => (r: Base) => Derived'.
22
Types of parameters 'y' and 'y' are incompatible.
3-
Type '(arg2: Base) => Derived' is not assignable to type '(arg2: { foo: number; }) => any'.
4-
Types of parameters 'arg2' and 'arg2' are incompatible.
5-
Type '{ foo: number; }' is not assignable to type 'Base'.
6-
Types of property 'foo' are incompatible.
7-
Type 'number' is not assignable to type 'string'.
3+
Types of parameters 'arg2' and 'arg2' are incompatible.
4+
Type '{ foo: number; }' is not assignable to type 'Base'.
5+
Types of property 'foo' are incompatible.
6+
Type 'number' is not assignable to type 'string'.
87
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithCallSignatures4.ts(53,9): error TS2322: Type '(x: (arg: Base) => Derived, y: (arg2: Base) => Derived) => (r: Base) => Derived' is not assignable to type '<T extends Base, U extends Derived>(x: (arg: T) => U, y: (arg2: { foo: number; }) => U) => (r: T) => U'.
98
Types of parameters 'y' and 'y' are incompatible.
10-
Type '(arg2: { foo: number; }) => any' is not assignable to type '(arg2: Base) => Derived'.
11-
Types of parameters 'arg2' and 'arg2' are incompatible.
12-
Type 'Base' is not assignable to type '{ foo: number; }'.
13-
Types of property 'foo' are incompatible.
14-
Type 'string' is not assignable to type 'number'.
9+
Types of parameters 'arg2' and 'arg2' are incompatible.
10+
Type 'Base' is not assignable to type '{ foo: number; }'.
11+
Types of property 'foo' are incompatible.
12+
Type 'string' is not assignable to type 'number'.
1513

1614

1715
==== tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithCallSignatures4.ts (2 errors) ====
@@ -70,20 +68,18 @@ tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignme
7068
~~
7169
!!! error TS2322: Type '<T extends Base, U extends Derived>(x: (arg: T) => U, y: (arg2: { foo: number; }) => U) => (r: T) => U' is not assignable to type '(x: (arg: Base) => Derived, y: (arg2: Base) => Derived) => (r: Base) => Derived'.
7270
!!! error TS2322: Types of parameters 'y' and 'y' are incompatible.
73-
!!! error TS2322: Type '(arg2: Base) => Derived' is not assignable to type '(arg2: { foo: number; }) => any'.
74-
!!! error TS2322: Types of parameters 'arg2' and 'arg2' are incompatible.
75-
!!! error TS2322: Type '{ foo: number; }' is not assignable to type 'Base'.
76-
!!! error TS2322: Types of property 'foo' are incompatible.
77-
!!! error TS2322: Type 'number' is not assignable to type 'string'.
71+
!!! error TS2322: Types of parameters 'arg2' and 'arg2' are incompatible.
72+
!!! error TS2322: Type '{ foo: number; }' is not assignable to type 'Base'.
73+
!!! error TS2322: Types of property 'foo' are incompatible.
74+
!!! error TS2322: Type 'number' is not assignable to type 'string'.
7875
b8 = a8; // error, { foo: number } and Base are incompatible
7976
~~
8077
!!! error TS2322: Type '(x: (arg: Base) => Derived, y: (arg2: Base) => Derived) => (r: Base) => Derived' is not assignable to type '<T extends Base, U extends Derived>(x: (arg: T) => U, y: (arg2: { foo: number; }) => U) => (r: T) => U'.
8178
!!! error TS2322: Types of parameters 'y' and 'y' are incompatible.
82-
!!! error TS2322: Type '(arg2: { foo: number; }) => any' is not assignable to type '(arg2: Base) => Derived'.
83-
!!! error TS2322: Types of parameters 'arg2' and 'arg2' are incompatible.
84-
!!! error TS2322: Type 'Base' is not assignable to type '{ foo: number; }'.
85-
!!! error TS2322: Types of property 'foo' are incompatible.
86-
!!! error TS2322: Type 'string' is not assignable to type 'number'.
79+
!!! error TS2322: Types of parameters 'arg2' and 'arg2' are incompatible.
80+
!!! error TS2322: Type 'Base' is not assignable to type '{ foo: number; }'.
81+
!!! error TS2322: Types of property 'foo' are incompatible.
82+
!!! error TS2322: Type 'string' is not assignable to type 'number'.
8783

8884

8985
var b10: <T extends Derived>(...x: T[]) => T;

0 commit comments

Comments
 (0)