Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit cef42c9

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

21 files changed

+727-154Lines changed: 727 additions & 154 deletions
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎src/compiler/checker.ts‎

Copy file name to clipboardExpand all lines: src/compiler/checker.ts
+26-6Lines changed: 26 additions & 6 deletions
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 {
Collapse file

‎src/compiler/parser.ts‎

Copy file name to clipboardExpand all lines: src/compiler/parser.ts
+1-1Lines changed: 1 addition & 1 deletion
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:
Collapse file

‎src/compiler/visitor.ts‎

Copy file name to clipboardExpand all lines: src/compiler/visitor.ts
+1-1Lines changed: 1 addition & 1 deletion
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

Collapse file
+113Lines changed: 113 additions & 0 deletions
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+
Collapse file

‎tests/baselines/reference/assignmentCompatWithCallSignatures4.errors.txt‎

Copy file name to clipboardExpand all lines: tests/baselines/reference/assignmentCompatWithCallSignatures4.errors.txt
+16-20Lines changed: 16 additions & 20 deletions
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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.