Skip to content

Commit b7e744a

Browse files
authored
Merge pull request #18976 from Microsoft/strictCallbackParameters
Strictly check callback parameters
2 parents afa4842 + 7fcf519 commit b7e744a

6 files changed

+369
-10
lines changed

src/compiler/checker.ts

+13-7
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,12 @@ namespace ts {
503503
Inferential = 2, // Inferential typing
504504
}
505505

506+
const enum CallbackCheck {
507+
None,
508+
Bivariant,
509+
Strict,
510+
}
511+
506512
const builtinGlobals = createSymbolTable();
507513
builtinGlobals.set(undefinedSymbol.escapedName, undefinedSymbol);
508514

@@ -8510,7 +8516,7 @@ namespace ts {
85108516
function isSignatureAssignableTo(source: Signature,
85118517
target: Signature,
85128518
ignoreReturnTypes: boolean): boolean {
8513-
return compareSignaturesRelated(source, target, /*checkAsCallback*/ false, ignoreReturnTypes, /*reportErrors*/ false,
8519+
return compareSignaturesRelated(source, target, CallbackCheck.None, ignoreReturnTypes, /*reportErrors*/ false,
85148520
/*errorReporter*/ undefined, compareTypesAssignable) !== Ternary.False;
85158521
}
85168522

@@ -8521,7 +8527,7 @@ namespace ts {
85218527
*/
85228528
function compareSignaturesRelated(source: Signature,
85238529
target: Signature,
8524-
checkAsCallback: boolean,
8530+
callbackCheck: CallbackCheck,
85258531
ignoreReturnTypes: boolean,
85268532
reportErrors: boolean,
85278533
errorReporter: ErrorReporter,
@@ -8540,7 +8546,7 @@ namespace ts {
85408546
}
85418547

85428548
const kind = target.declaration ? target.declaration.kind : SyntaxKind.Unknown;
8543-
const strictVariance = strictFunctionTypes && kind !== SyntaxKind.MethodDeclaration &&
8549+
const strictVariance = !callbackCheck && strictFunctionTypes && kind !== SyntaxKind.MethodDeclaration &&
85448550
kind !== SyntaxKind.MethodSignature && kind !== SyntaxKind.Constructor;
85458551
let result = Ternary.True;
85468552

@@ -8582,8 +8588,8 @@ namespace ts {
85828588
const callbacks = sourceSig && targetSig && !sourceSig.typePredicate && !targetSig.typePredicate &&
85838589
(getFalsyFlags(sourceType) & TypeFlags.Nullable) === (getFalsyFlags(targetType) & TypeFlags.Nullable);
85848590
const related = callbacks ?
8585-
compareSignaturesRelated(targetSig, sourceSig, /*checkAsCallback*/ true, /*ignoreReturnTypes*/ false, reportErrors, errorReporter, compareTypes) :
8586-
!checkAsCallback && !strictVariance && compareTypes(sourceType, targetType, /*reportErrors*/ false) || compareTypes(targetType, sourceType, reportErrors);
8591+
compareSignaturesRelated(targetSig, sourceSig, strictVariance ? CallbackCheck.Strict : CallbackCheck.Bivariant, /*ignoreReturnTypes*/ false, reportErrors, errorReporter, compareTypes) :
8592+
!callbackCheck && !strictVariance && compareTypes(sourceType, targetType, /*reportErrors*/ false) || compareTypes(targetType, sourceType, reportErrors);
85878593
if (!related) {
85888594
if (reportErrors) {
85898595
errorReporter(Diagnostics.Types_of_parameters_0_and_1_are_incompatible,
@@ -8618,7 +8624,7 @@ namespace ts {
86188624
// When relating callback signatures, we still need to relate return types bi-variantly as otherwise
86198625
// the containing type wouldn't be co-variant. For example, interface Foo<T> { add(cb: () => T): void }
86208626
// wouldn't be co-variant for T without this rule.
8621-
result &= checkAsCallback && compareTypes(targetReturnType, sourceReturnType, /*reportErrors*/ false) ||
8627+
result &= callbackCheck === CallbackCheck.Bivariant && compareTypes(targetReturnType, sourceReturnType, /*reportErrors*/ false) ||
86228628
compareTypes(sourceReturnType, targetReturnType, reportErrors);
86238629
}
86248630

@@ -9726,7 +9732,7 @@ namespace ts {
97269732
*/
97279733
function signatureRelatedTo(source: Signature, target: Signature, erase: boolean, reportErrors: boolean): Ternary {
97289734
return compareSignaturesRelated(erase ? getErasedSignature(source) : source, erase ? getErasedSignature(target) : target,
9729-
/*checkAsCallback*/ false, /*ignoreReturnTypes*/ false, reportErrors, reportError, isRelatedTo);
9735+
CallbackCheck.None, /*ignoreReturnTypes*/ false, reportErrors, reportError, isRelatedTo);
97309736
}
97319737

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

tests/baselines/reference/strictFunctionTypesErrors.errors.txt

+64-2
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,24 @@ tests/cases/compiler/strictFunctionTypesErrors.ts(126,1): error TS2322: Type 'Cr
8787
tests/cases/compiler/strictFunctionTypesErrors.ts(127,1): error TS2322: Type 'Crate<Animal>' is not assignable to type 'Crate<Dog>'.
8888
Types of property 'item' are incompatible.
8989
Type 'Animal' is not assignable to type 'Dog'.
90+
tests/cases/compiler/strictFunctionTypesErrors.ts(133,1): error TS2322: Type '(f: (x: Dog) => Dog) => void' is not assignable to type '(f: (x: Animal) => Animal) => void'.
91+
Types of parameters 'f' and 'f' are incompatible.
92+
Type 'Animal' is not assignable to type 'Dog'.
93+
tests/cases/compiler/strictFunctionTypesErrors.ts(134,1): error TS2322: Type '(f: (x: Animal) => Animal) => void' is not assignable to type '(f: (x: Dog) => Dog) => void'.
94+
Types of parameters 'f' and 'f' are incompatible.
95+
Types of parameters 'x' and 'x' are incompatible.
96+
Type 'Animal' is not assignable to type 'Dog'.
97+
tests/cases/compiler/strictFunctionTypesErrors.ts(147,5): error TS2322: Type '(cb: (x: Animal) => Animal) => void' is not assignable to type '(cb: (x: Dog) => Animal) => void'.
98+
Types of parameters 'cb' and 'cb' are incompatible.
99+
Types of parameters 'x' and 'x' are incompatible.
100+
Type 'Animal' is not assignable to type 'Dog'.
101+
tests/cases/compiler/strictFunctionTypesErrors.ts(155,5): error TS2322: Type '(cb: (x: Animal) => Animal) => void' is not assignable to type '(cb: (x: Dog) => Animal) => void'.
102+
Types of parameters 'cb' and 'cb' are incompatible.
103+
Types of parameters 'x' and 'x' are incompatible.
104+
Type 'Animal' is not assignable to type 'Dog'.
90105

91106

92-
==== tests/cases/compiler/strictFunctionTypesErrors.ts (31 errors) ====
107+
==== tests/cases/compiler/strictFunctionTypesErrors.ts (35 errors) ====
93108
export {}
94109

95110

@@ -337,4 +352,51 @@ tests/cases/compiler/strictFunctionTypesErrors.ts(127,1): error TS2322: Type 'Cr
337352
!!! error TS2322: Type 'Crate<Animal>' is not assignable to type 'Crate<Dog>'.
338353
!!! error TS2322: Types of property 'item' are incompatible.
339354
!!! error TS2322: Type 'Animal' is not assignable to type 'Dog'.
340-
355+
356+
// Verify that callback parameters are strictly checked
357+
358+
declare let fc1: (f: (x: Animal) => Animal) => void;
359+
declare let fc2: (f: (x: Dog) => Dog) => void;
360+
fc1 = fc2; // Error
361+
~~~
362+
!!! error TS2322: Type '(f: (x: Dog) => Dog) => void' is not assignable to type '(f: (x: Animal) => Animal) => void'.
363+
!!! error TS2322: Types of parameters 'f' and 'f' are incompatible.
364+
!!! error TS2322: Type 'Animal' is not assignable to type 'Dog'.
365+
fc2 = fc1; // Error
366+
~~~
367+
!!! error TS2322: Type '(f: (x: Animal) => Animal) => void' is not assignable to type '(f: (x: Dog) => Dog) => void'.
368+
!!! error TS2322: Types of parameters 'f' and 'f' are incompatible.
369+
!!! error TS2322: Types of parameters 'x' and 'x' are incompatible.
370+
!!! error TS2322: Type 'Animal' is not assignable to type 'Dog'.
371+
372+
// Verify that callback parameters aren't loosely checked when types
373+
// originate in method declarations
374+
375+
namespace n1 {
376+
class Foo {
377+
static f1(x: Animal): Animal { throw "wat"; }
378+
static f2(x: Dog): Animal { throw "wat"; };
379+
}
380+
declare let f1: (cb: typeof Foo.f1) => void;
381+
declare let f2: (cb: typeof Foo.f2) => void;
382+
f1 = f2;
383+
f2 = f1; // Error
384+
~~
385+
!!! error TS2322: Type '(cb: (x: Animal) => Animal) => void' is not assignable to type '(cb: (x: Dog) => Animal) => void'.
386+
!!! error TS2322: Types of parameters 'cb' and 'cb' are incompatible.
387+
!!! error TS2322: Types of parameters 'x' and 'x' are incompatible.
388+
!!! error TS2322: Type 'Animal' is not assignable to type 'Dog'.
389+
}
390+
391+
namespace n2 {
392+
type BivariantHack<Input, Output> = { foo(x: Input): Output }["foo"];
393+
declare let f1: (cb: BivariantHack<Animal, Animal>) => void;
394+
declare let f2: (cb: BivariantHack<Dog, Animal>) => void;
395+
f1 = f2;
396+
f2 = f1; // Error
397+
~~
398+
!!! error TS2322: Type '(cb: (x: Animal) => Animal) => void' is not assignable to type '(cb: (x: Dog) => Animal) => void'.
399+
!!! error TS2322: Types of parameters 'cb' and 'cb' are incompatible.
400+
!!! error TS2322: Types of parameters 'x' and 'x' are incompatible.
401+
!!! error TS2322: Type 'Animal' is not assignable to type 'Dog'.
402+
}

tests/baselines/reference/strictFunctionTypesErrors.js

+51-1
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,35 @@ declare let dogCrate: Crate<Dog>;
126126

127127
animalCrate = dogCrate; // Error
128128
dogCrate = animalCrate; // Error
129-
129+
130+
// Verify that callback parameters are strictly checked
131+
132+
declare let fc1: (f: (x: Animal) => Animal) => void;
133+
declare let fc2: (f: (x: Dog) => Dog) => void;
134+
fc1 = fc2; // Error
135+
fc2 = fc1; // Error
136+
137+
// Verify that callback parameters aren't loosely checked when types
138+
// originate in method declarations
139+
140+
namespace n1 {
141+
class Foo {
142+
static f1(x: Animal): Animal { throw "wat"; }
143+
static f2(x: Dog): Animal { throw "wat"; };
144+
}
145+
declare let f1: (cb: typeof Foo.f1) => void;
146+
declare let f2: (cb: typeof Foo.f2) => void;
147+
f1 = f2;
148+
f2 = f1; // Error
149+
}
150+
151+
namespace n2 {
152+
type BivariantHack<Input, Output> = { foo(x: Input): Output }["foo"];
153+
declare let f1: (cb: BivariantHack<Animal, Animal>) => void;
154+
declare let f2: (cb: BivariantHack<Dog, Animal>) => void;
155+
f1 = f2;
156+
f2 = f1; // Error
157+
}
130158

131159
//// [strictFunctionTypesErrors.js]
132160
"use strict";
@@ -186,3 +214,25 @@ dogComparer2 = animalComparer2; // Ok
186214
// Errors below should elaborate the reason for invariance
187215
animalCrate = dogCrate; // Error
188216
dogCrate = animalCrate; // Error
217+
fc1 = fc2; // Error
218+
fc2 = fc1; // Error
219+
// Verify that callback parameters aren't loosely checked when types
220+
// originate in method declarations
221+
var n1;
222+
(function (n1) {
223+
var Foo = /** @class */ (function () {
224+
function Foo() {
225+
}
226+
Foo.f1 = function (x) { throw "wat"; };
227+
Foo.f2 = function (x) { throw "wat"; };
228+
;
229+
return Foo;
230+
}());
231+
f1 = f2;
232+
f2 = f1; // Error
233+
})(n1 || (n1 = {}));
234+
var n2;
235+
(function (n2) {
236+
f1 = f2;
237+
f2 = f1; // Error
238+
})(n2 || (n2 = {}));

tests/baselines/reference/strictFunctionTypesErrors.symbols

+102
Original file line numberDiff line numberDiff line change
@@ -400,3 +400,105 @@ dogCrate = animalCrate; // Error
400400
>dogCrate : Symbol(dogCrate, Decl(strictFunctionTypesErrors.ts, 121, 11))
401401
>animalCrate : Symbol(animalCrate, Decl(strictFunctionTypesErrors.ts, 120, 11))
402402

403+
// Verify that callback parameters are strictly checked
404+
405+
declare let fc1: (f: (x: Animal) => Animal) => void;
406+
>fc1 : Symbol(fc1, Decl(strictFunctionTypesErrors.ts, 130, 11))
407+
>f : Symbol(f, Decl(strictFunctionTypesErrors.ts, 130, 18))
408+
>x : Symbol(x, Decl(strictFunctionTypesErrors.ts, 130, 22))
409+
>Animal : Symbol(Animal, Decl(strictFunctionTypesErrors.ts, 87, 8))
410+
>Animal : Symbol(Animal, Decl(strictFunctionTypesErrors.ts, 87, 8))
411+
412+
declare let fc2: (f: (x: Dog) => Dog) => void;
413+
>fc2 : Symbol(fc2, Decl(strictFunctionTypesErrors.ts, 131, 11))
414+
>f : Symbol(f, Decl(strictFunctionTypesErrors.ts, 131, 18))
415+
>x : Symbol(x, Decl(strictFunctionTypesErrors.ts, 131, 22))
416+
>Dog : Symbol(Dog, Decl(strictFunctionTypesErrors.ts, 89, 33))
417+
>Dog : Symbol(Dog, Decl(strictFunctionTypesErrors.ts, 89, 33))
418+
419+
fc1 = fc2; // Error
420+
>fc1 : Symbol(fc1, Decl(strictFunctionTypesErrors.ts, 130, 11))
421+
>fc2 : Symbol(fc2, Decl(strictFunctionTypesErrors.ts, 131, 11))
422+
423+
fc2 = fc1; // Error
424+
>fc2 : Symbol(fc2, Decl(strictFunctionTypesErrors.ts, 131, 11))
425+
>fc1 : Symbol(fc1, Decl(strictFunctionTypesErrors.ts, 130, 11))
426+
427+
// Verify that callback parameters aren't loosely checked when types
428+
// originate in method declarations
429+
430+
namespace n1 {
431+
>n1 : Symbol(n1, Decl(strictFunctionTypesErrors.ts, 133, 10))
432+
433+
class Foo {
434+
>Foo : Symbol(Foo, Decl(strictFunctionTypesErrors.ts, 138, 14))
435+
436+
static f1(x: Animal): Animal { throw "wat"; }
437+
>f1 : Symbol(Foo.f1, Decl(strictFunctionTypesErrors.ts, 139, 15))
438+
>x : Symbol(x, Decl(strictFunctionTypesErrors.ts, 140, 18))
439+
>Animal : Symbol(Animal, Decl(strictFunctionTypesErrors.ts, 87, 8))
440+
>Animal : Symbol(Animal, Decl(strictFunctionTypesErrors.ts, 87, 8))
441+
442+
static f2(x: Dog): Animal { throw "wat"; };
443+
>f2 : Symbol(Foo.f2, Decl(strictFunctionTypesErrors.ts, 140, 53))
444+
>x : Symbol(x, Decl(strictFunctionTypesErrors.ts, 141, 18))
445+
>Dog : Symbol(Dog, Decl(strictFunctionTypesErrors.ts, 89, 33))
446+
>Animal : Symbol(Animal, Decl(strictFunctionTypesErrors.ts, 87, 8))
447+
}
448+
declare let f1: (cb: typeof Foo.f1) => void;
449+
>f1 : Symbol(f1, Decl(strictFunctionTypesErrors.ts, 143, 15))
450+
>cb : Symbol(cb, Decl(strictFunctionTypesErrors.ts, 143, 21))
451+
>Foo.f1 : Symbol(Foo.f1, Decl(strictFunctionTypesErrors.ts, 139, 15))
452+
>Foo : Symbol(Foo, Decl(strictFunctionTypesErrors.ts, 138, 14))
453+
>f1 : Symbol(Foo.f1, Decl(strictFunctionTypesErrors.ts, 139, 15))
454+
455+
declare let f2: (cb: typeof Foo.f2) => void;
456+
>f2 : Symbol(f2, Decl(strictFunctionTypesErrors.ts, 144, 15))
457+
>cb : Symbol(cb, Decl(strictFunctionTypesErrors.ts, 144, 21))
458+
>Foo.f2 : Symbol(Foo.f2, Decl(strictFunctionTypesErrors.ts, 140, 53))
459+
>Foo : Symbol(Foo, Decl(strictFunctionTypesErrors.ts, 138, 14))
460+
>f2 : Symbol(Foo.f2, Decl(strictFunctionTypesErrors.ts, 140, 53))
461+
462+
f1 = f2;
463+
>f1 : Symbol(f1, Decl(strictFunctionTypesErrors.ts, 143, 15))
464+
>f2 : Symbol(f2, Decl(strictFunctionTypesErrors.ts, 144, 15))
465+
466+
f2 = f1; // Error
467+
>f2 : Symbol(f2, Decl(strictFunctionTypesErrors.ts, 144, 15))
468+
>f1 : Symbol(f1, Decl(strictFunctionTypesErrors.ts, 143, 15))
469+
}
470+
471+
namespace n2 {
472+
>n2 : Symbol(n2, Decl(strictFunctionTypesErrors.ts, 147, 1))
473+
474+
type BivariantHack<Input, Output> = { foo(x: Input): Output }["foo"];
475+
>BivariantHack : Symbol(BivariantHack, Decl(strictFunctionTypesErrors.ts, 149, 14))
476+
>Input : Symbol(Input, Decl(strictFunctionTypesErrors.ts, 150, 23))
477+
>Output : Symbol(Output, Decl(strictFunctionTypesErrors.ts, 150, 29))
478+
>foo : Symbol(foo, Decl(strictFunctionTypesErrors.ts, 150, 41))
479+
>x : Symbol(x, Decl(strictFunctionTypesErrors.ts, 150, 46))
480+
>Input : Symbol(Input, Decl(strictFunctionTypesErrors.ts, 150, 23))
481+
>Output : Symbol(Output, Decl(strictFunctionTypesErrors.ts, 150, 29))
482+
483+
declare let f1: (cb: BivariantHack<Animal, Animal>) => void;
484+
>f1 : Symbol(f1, Decl(strictFunctionTypesErrors.ts, 151, 15))
485+
>cb : Symbol(cb, Decl(strictFunctionTypesErrors.ts, 151, 21))
486+
>BivariantHack : Symbol(BivariantHack, Decl(strictFunctionTypesErrors.ts, 149, 14))
487+
>Animal : Symbol(Animal, Decl(strictFunctionTypesErrors.ts, 87, 8))
488+
>Animal : Symbol(Animal, Decl(strictFunctionTypesErrors.ts, 87, 8))
489+
490+
declare let f2: (cb: BivariantHack<Dog, Animal>) => void;
491+
>f2 : Symbol(f2, Decl(strictFunctionTypesErrors.ts, 152, 15))
492+
>cb : Symbol(cb, Decl(strictFunctionTypesErrors.ts, 152, 21))
493+
>BivariantHack : Symbol(BivariantHack, Decl(strictFunctionTypesErrors.ts, 149, 14))
494+
>Dog : Symbol(Dog, Decl(strictFunctionTypesErrors.ts, 89, 33))
495+
>Animal : Symbol(Animal, Decl(strictFunctionTypesErrors.ts, 87, 8))
496+
497+
f1 = f2;
498+
>f1 : Symbol(f1, Decl(strictFunctionTypesErrors.ts, 151, 15))
499+
>f2 : Symbol(f2, Decl(strictFunctionTypesErrors.ts, 152, 15))
500+
501+
f2 = f1; // Error
502+
>f2 : Symbol(f2, Decl(strictFunctionTypesErrors.ts, 152, 15))
503+
>f1 : Symbol(f1, Decl(strictFunctionTypesErrors.ts, 151, 15))
504+
}

0 commit comments

Comments
 (0)