Skip to content

Commit e36957a

Browse files
authored
Merge pull request #27028 from Microsoft/typedBindCallApply
Strict bind, call, and apply methods on functions
2 parents 8e1cce4 + b6e66c2 commit e36957a

File tree

42 files changed

+1281
-82
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1281
-82
lines changed

src/compiler/checker.ts

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ namespace ts {
7777
const allowSyntheticDefaultImports = getAllowSyntheticDefaultImports(compilerOptions);
7878
const strictNullChecks = getStrictOptionValue(compilerOptions, "strictNullChecks");
7979
const strictFunctionTypes = getStrictOptionValue(compilerOptions, "strictFunctionTypes");
80+
const strictBindCallApply = getStrictOptionValue(compilerOptions, "strictBindCallApply");
8081
const strictPropertyInitialization = getStrictOptionValue(compilerOptions, "strictPropertyInitialization");
8182
const noImplicitAny = getStrictOptionValue(compilerOptions, "noImplicitAny");
8283
const noImplicitThis = getStrictOptionValue(compilerOptions, "noImplicitThis");
@@ -476,6 +477,8 @@ namespace ts {
476477

477478
let globalObjectType: ObjectType;
478479
let globalFunctionType: ObjectType;
480+
let globalCallableFunctionType: ObjectType;
481+
let globalNewableFunctionType: ObjectType;
479482
let globalArrayType: GenericType;
480483
let globalReadonlyArrayType: GenericType;
481484
let globalStringType: ObjectType;
@@ -7391,8 +7394,12 @@ namespace ts {
73917394
if (symbol && symbolIsValue(symbol)) {
73927395
return symbol;
73937396
}
7394-
if (resolved === anyFunctionType || resolved.callSignatures.length || resolved.constructSignatures.length) {
7395-
const symbol = getPropertyOfObjectType(globalFunctionType, name);
7397+
const functionType = resolved === anyFunctionType ? globalFunctionType :
7398+
resolved.callSignatures.length ? globalCallableFunctionType :
7399+
resolved.constructSignatures.length ? globalNewableFunctionType :
7400+
undefined;
7401+
if (functionType) {
7402+
const symbol = getPropertyOfObjectType(functionType, name);
73967403
if (symbol) {
73977404
return symbol;
73987405
}
@@ -13250,10 +13257,8 @@ namespace ts {
1325013257
const targetCount = getParameterCount(target);
1325113258
const sourceRestType = getEffectiveRestType(source);
1325213259
const targetRestType = getEffectiveRestType(target);
13253-
const paramCount = targetRestType ? Math.min(targetCount - 1, sourceCount) :
13254-
sourceRestType ? targetCount :
13255-
Math.min(sourceCount, targetCount);
13256-
13260+
const targetNonRestCount = targetRestType ? targetCount - 1 : targetCount;
13261+
const paramCount = sourceRestType ? targetNonRestCount : Math.min(sourceCount, targetNonRestCount);
1325713262
const sourceThisType = getThisTypeOfSignature(source);
1325813263
if (sourceThisType) {
1325913264
const targetThisType = getThisTypeOfSignature(target);
@@ -13900,15 +13905,17 @@ namespace ts {
1390013905
if (!inferredType) {
1390113906
const signature = context.signature;
1390213907
if (signature) {
13903-
if (inference.contraCandidates && (!inference.candidates || inference.candidates.length === 1 && inference.candidates[0].flags & TypeFlags.Never)) {
13904-
// If we have contravariant inferences, but no covariant inferences or a single
13905-
// covariant inference of 'never', we find the best common subtype and treat that
13906-
// as a single covariant candidate.
13907-
inference.candidates = [getContravariantInference(inference)];
13908-
inference.contraCandidates = undefined;
13909-
}
13910-
if (inference.candidates) {
13911-
inferredType = getCovariantInference(inference, signature);
13908+
const inferredCovariantType = inference.candidates ? getCovariantInference(inference, signature) : undefined;
13909+
if (inference.contraCandidates) {
13910+
const inferredContravariantType = getContravariantInference(inference);
13911+
// If we have both co- and contra-variant inferences, we prefer the contra-variant inference
13912+
// unless the co-variant inference is a subtype and not 'never'.
13913+
inferredType = inferredCovariantType && !(inferredCovariantType.flags & TypeFlags.Never) &&
13914+
isTypeSubtypeOf(inferredCovariantType, inferredContravariantType) ?
13915+
inferredCovariantType : inferredContravariantType;
13916+
}
13917+
else if (inferredCovariantType) {
13918+
inferredType = inferredCovariantType;
1391213919
}
1391313920
else if (context.flags & InferenceFlags.NoDefault) {
1391413921
// We use silentNeverType as the wildcard that signals no inferences.
@@ -19695,7 +19702,10 @@ namespace ts {
1969519702
checkCandidate = candidate;
1969619703
}
1969719704
if (!checkApplicableSignature(node, args, checkCandidate, relation, excludeArgument, /*reportErrors*/ false)) {
19698-
candidateForArgumentError = checkCandidate;
19705+
// Give preference to error candidates that have no rest parameters (as they are more specific)
19706+
if (!candidateForArgumentError || getEffectiveRestType(candidateForArgumentError) || !getEffectiveRestType(checkCandidate)) {
19707+
candidateForArgumentError = checkCandidate;
19708+
}
1969919709
continue;
1970019710
}
1970119711
if (excludeArgument) {
@@ -19708,7 +19718,10 @@ namespace ts {
1970819718
checkCandidate = getSignatureInstantiation(candidate, typeArgumentTypes, isInJSFile(candidate.declaration));
1970919719
}
1971019720
if (!checkApplicableSignature(node, args, checkCandidate, relation, excludeArgument, /*reportErrors*/ false)) {
19711-
candidateForArgumentError = checkCandidate;
19721+
// Give preference to error candidates that have no rest parameters (as they are more specific)
19722+
if (!candidateForArgumentError || getEffectiveRestType(candidateForArgumentError) || !getEffectiveRestType(checkCandidate)) {
19723+
candidateForArgumentError = checkCandidate;
19724+
}
1971219725
continue;
1971319726
}
1971419727
}
@@ -28040,8 +28053,11 @@ namespace ts {
2804028053
function getAugmentedPropertiesOfType(type: Type): Symbol[] {
2804128054
type = getApparentType(type);
2804228055
const propsByName = createSymbolTable(getPropertiesOfType(type));
28043-
if (typeHasCallOrConstructSignatures(type)) {
28044-
forEach(getPropertiesOfType(globalFunctionType), p => {
28056+
const functionType = getSignaturesOfType(type, SignatureKind.Call).length ? globalCallableFunctionType :
28057+
getSignaturesOfType(type, SignatureKind.Construct).length ? globalNewableFunctionType :
28058+
undefined;
28059+
if (functionType) {
28060+
forEach(getPropertiesOfType(functionType), p => {
2804528061
if (!propsByName.has(p.escapedName)) {
2804628062
propsByName.set(p.escapedName, p);
2804728063
}
@@ -28821,6 +28837,8 @@ namespace ts {
2882128837
globalArrayType = getGlobalType("Array" as __String, /*arity*/ 1, /*reportErrors*/ true);
2882228838
globalObjectType = getGlobalType("Object" as __String, /*arity*/ 0, /*reportErrors*/ true);
2882328839
globalFunctionType = getGlobalType("Function" as __String, /*arity*/ 0, /*reportErrors*/ true);
28840+
globalCallableFunctionType = strictBindCallApply && getGlobalType("CallableFunction" as __String, /*arity*/ 0, /*reportErrors*/ true) || globalFunctionType;
28841+
globalNewableFunctionType = strictBindCallApply && getGlobalType("NewableFunction" as __String, /*arity*/ 0, /*reportErrors*/ true) || globalFunctionType;
2882428842
globalStringType = getGlobalType("String" as __String, /*arity*/ 0, /*reportErrors*/ true);
2882528843
globalNumberType = getGlobalType("Number" as __String, /*arity*/ 0, /*reportErrors*/ true);
2882628844
globalBooleanType = getGlobalType("Boolean" as __String, /*arity*/ 0, /*reportErrors*/ true);

src/compiler/commandLineParser.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,14 @@ namespace ts {
373373
category: Diagnostics.Strict_Type_Checking_Options,
374374
description: Diagnostics.Enable_strict_checking_of_function_types
375375
},
376+
{
377+
name: "strictBindCallApply",
378+
type: "boolean",
379+
strictFlag: true,
380+
showInSimplifiedHelpView: true,
381+
category: Diagnostics.Strict_Type_Checking_Options,
382+
description: Diagnostics.Enable_strict_bind_call_and_apply_methods_on_functions
383+
},
376384
{
377385
name: "strictPropertyInitialization",
378386
type: "boolean",

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3759,6 +3759,10 @@
37593759
"category": "Message",
37603760
"code": 6213
37613761
},
3762+
"Enable strict 'bind', 'call', and 'apply' methods on functions.": {
3763+
"category": "Message",
3764+
"code": 6214
3765+
},
37623766

37633767
"Projects to reference": {
37643768
"category": "Message",

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4427,6 +4427,7 @@ namespace ts {
44274427
sourceRoot?: string;
44284428
strict?: boolean;
44294429
strictFunctionTypes?: boolean; // Always combine with strict property
4430+
strictBindCallApply?: boolean; // Always combine with strict property
44304431
strictNullChecks?: boolean; // Always combine with strict property
44314432
strictPropertyInitialization?: boolean; // Always combine with strict property
44324433
stripInternal?: boolean;

src/compiler/utilities.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7085,7 +7085,7 @@ namespace ts {
70857085
return !!(compilerOptions.declaration || compilerOptions.composite);
70867086
}
70877087

7088-
export type StrictOptionName = "noImplicitAny" | "noImplicitThis" | "strictNullChecks" | "strictFunctionTypes" | "strictPropertyInitialization" | "alwaysStrict";
7088+
export type StrictOptionName = "noImplicitAny" | "noImplicitThis" | "strictNullChecks" | "strictFunctionTypes" | "strictBindCallApply" | "strictPropertyInitialization" | "alwaysStrict";
70897089

70907090
export function getStrictOptionValue(compilerOptions: CompilerOptions, flag: StrictOptionName): boolean {
70917091
return compilerOptions[flag] === undefined ? !!compilerOptions.strict : !!compilerOptions[flag];

src/harness/virtualFileSystemWithWatch.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ namespace ts.TestFSWithWatch {
44
content: `/// <reference no-default-lib="true"/>
55
interface Boolean {}
66
interface Function {}
7+
interface CallableFunction {}
8+
interface NewableFunction {}
79
interface IArguments {}
810
interface Number { toExponential: any; }
911
interface Object {}

src/lib/es5.d.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,66 @@ interface FunctionConstructor {
295295

296296
declare const Function: FunctionConstructor;
297297

298+
interface CallableFunction extends Function {
299+
/**
300+
* Calls the function with the specified object as the this value and the elements of specified array as the arguments.
301+
* @param thisArg The object to be used as the this object.
302+
* @param args An array of argument values to be passed to the function.
303+
*/
304+
apply<T, R>(this: (this: T) => R, thisArg: T): R;
305+
apply<T, A extends any[], R>(this: (this: T, ...args: A) => R, thisArg: T, args: A): R;
306+
307+
/**
308+
* Calls the function with the specified object as the this value and the specified rest arguments as the arguments.
309+
* @param thisArg The object to be used as the this object.
310+
* @param args Argument values to be passed to the function.
311+
*/
312+
call<T, A extends any[], R>(this: (this: T, ...args: A) => R, thisArg: T, ...args: A): R;
313+
314+
/**
315+
* For a given function, creates a bound function that has the same body as the original function.
316+
* The this object of the bound function is associated with the specified object, and has the specified initial parameters.
317+
* @param thisArg The object to be used as the this object.
318+
* @param args Arguments to bind to the parameters of the function.
319+
*/
320+
bind<T, A extends any[], R>(this: (this: T, ...args: A) => R, thisArg: T): (...args: A) => R;
321+
bind<T, A0, A extends any[], R>(this: (this: T, arg0: A0, ...args: A) => R, thisArg: T, arg0: A0): (...args: A) => R;
322+
bind<T, A0, A1, A extends any[], R>(this: (this: T, arg0: A0, arg1: A1, ...args: A) => R, thisArg: T, arg0: A0, arg1: A1): (...args: A) => R;
323+
bind<T, A0, A1, A2, A extends any[], R>(this: (this: T, arg0: A0, arg1: A1, arg2: A2, ...args: A) => R, thisArg: T, arg0: A0, arg1: A1, arg2: A2): (...args: A) => R;
324+
bind<T, A0, A1, A2, A3, A extends any[], R>(this: (this: T, arg0: A0, arg1: A1, arg2: A2, arg3: A3, ...args: A) => R, thisArg: T, arg0: A0, arg1: A1, arg2: A2, arg3: A3): (...args: A) => R;
325+
bind<T, AX, R>(this: (this: T, ...args: AX[]) => R, thisArg: T, ...args: AX[]): (...args: AX[]) => R;
326+
}
327+
328+
interface NewableFunction extends Function {
329+
/**
330+
* Calls the function with the specified object as the this value and the elements of specified array as the arguments.
331+
* @param thisArg The object to be used as the this object.
332+
* @param args An array of argument values to be passed to the function.
333+
*/
334+
apply<T>(this: new () => T, thisArg: T): void;
335+
apply<T, A extends any[]>(this: new (...args: A) => T, thisArg: T, args: A): void;
336+
337+
/**
338+
* Calls the function with the specified object as the this value and the specified rest arguments as the arguments.
339+
* @param thisArg The object to be used as the this object.
340+
* @param args Argument values to be passed to the function.
341+
*/
342+
call<T, A extends any[]>(this: new (...args: A) => T, thisArg: T, ...args: A): void;
343+
344+
/**
345+
* For a given function, creates a bound function that has the same body as the original function.
346+
* The this object of the bound function is associated with the specified object, and has the specified initial parameters.
347+
* @param thisArg The object to be used as the this object.
348+
* @param args Arguments to bind to the parameters of the function.
349+
*/
350+
bind<A extends any[], R>(this: new (...args: A) => R, thisArg: any): new (...args: A) => R;
351+
bind<A0, A extends any[], R>(this: new (arg0: A0, ...args: A) => R, thisArg: any, arg0: A0): new (...args: A) => R;
352+
bind<A0, A1, A extends any[], R>(this: new (arg0: A0, arg1: A1, ...args: A) => R, thisArg: any, arg0: A0, arg1: A1): new (...args: A) => R;
353+
bind<A0, A1, A2, A extends any[], R>(this: new (arg0: A0, arg1: A1, arg2: A2, ...args: A) => R, thisArg: any, arg0: A0, arg1: A1, arg2: A2): new (...args: A) => R;
354+
bind<A0, A1, A2, A3, A extends any[], R>(this: new (arg0: A0, arg1: A1, arg2: A2, arg3: A3, ...args: A) => R, thisArg: any, arg0: A0, arg1: A1, arg2: A2, arg3: A3): new (...args: A) => R;
355+
bind<AX, R>(this: new (...args: AX[]) => R, thisArg: any, ...args: AX[]): new (...args: AX[]) => R;
356+
}
357+
298358
interface IArguments {
299359
[index: number]: any;
300360
length: number;

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2494,6 +2494,7 @@ declare namespace ts {
24942494
sourceRoot?: string;
24952495
strict?: boolean;
24962496
strictFunctionTypes?: boolean;
2497+
strictBindCallApply?: boolean;
24972498
strictNullChecks?: boolean;
24982499
strictPropertyInitialization?: boolean;
24992500
stripInternal?: boolean;

tests/baselines/reference/api/typescript.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2494,6 +2494,7 @@ declare namespace ts {
24942494
sourceRoot?: string;
24952495
strict?: boolean;
24962496
strictFunctionTypes?: boolean;
2497+
strictBindCallApply?: boolean;
24972498
strictNullChecks?: boolean;
24982499
strictPropertyInitialization?: boolean;
24992500
stripInternal?: boolean;
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
error TS2318: Cannot find global type 'CallableFunction'.
2+
error TS2318: Cannot find global type 'NewableFunction'.
3+
4+
5+
!!! error TS2318: Cannot find global type 'CallableFunction'.
6+
!!! error TS2318: Cannot find global type 'NewableFunction'.
7+
==== tests/cases/compiler/booleanLiteralsContextuallyTypedFromUnion.tsx (0 errors) ====
8+
interface A { isIt: true; text: string; }
9+
interface B { isIt: false; value: number; }
10+
type C = A | B;
11+
const isIt = Math.random() > 0.5;
12+
const c: C = isIt ? { isIt, text: 'hey' } : { isIt, value: 123 };
13+
const cc: C = isIt ? { isIt: isIt, text: 'hey' } : { isIt: isIt, value: 123 };
14+
15+
type ComponentProps =
16+
| {
17+
optionalBool: true;
18+
mandatoryFn: () => void;
19+
}
20+
| {
21+
optionalBool: false;
22+
};
23+
24+
let Funk = (_props: ComponentProps) => <div>Hello</div>;
25+
26+
let Fail1 = () => <Funk mandatoryFn={() => { }} optionalBool={true} />
27+
let Fail2 = () => <Funk mandatoryFn={() => { }} optionalBool={true as true} />
28+
let True = true as true;
29+
let Fail3 = () => <Funk mandatoryFn={() => { }} optionalBool={True} />
30+
let attrs2 = { optionalBool: true as true, mandatoryFn: () => { } }
31+
let Success = () => <Funk {...attrs2} />

tests/baselines/reference/destructuringParameterDeclaration4.errors.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ tests/cases/conformance/es6/destructuring/destructuringParameterDeclaration4.ts(
4141
a1(...array2); // Error parameter type is (number|string)[]
4242
~~~~~~
4343
!!! error TS2552: Cannot find name 'array2'. Did you mean 'Array'?
44-
!!! related TS2728 /.ts/lib.es5.d.ts:1298:15: 'Array' is declared here.
44+
!!! related TS2728 /.ts/lib.es5.d.ts:1358:15: 'Array' is declared here.
4545
a5([1, 2, "string", false, true]); // Error, parameter type is [any, any, [[any]]]
4646
~~~~~~~~
4747
!!! error TS2322: Type 'string' is not assignable to type '[[any]]'.

tests/baselines/reference/externModule.errors.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,20 +69,20 @@ tests/cases/compiler/externModule.ts(37,3): error TS2552: Cannot find name 'XDat
6969
var d=new XDate();
7070
~~~~~
7171
!!! error TS2552: Cannot find name 'XDate'. Did you mean 'Date'?
72-
!!! related TS2728 /.ts/lib.es5.d.ts:837:15: 'Date' is declared here.
72+
!!! related TS2728 /.ts/lib.es5.d.ts:897:15: 'Date' is declared here.
7373
d.getDay();
7474
d=new XDate(1978,2);
7575
~~~~~
7676
!!! error TS2552: Cannot find name 'XDate'. Did you mean 'Date'?
77-
!!! related TS2728 /.ts/lib.es5.d.ts:837:15: 'Date' is declared here.
77+
!!! related TS2728 /.ts/lib.es5.d.ts:897:15: 'Date' is declared here.
7878
d.getXDate();
7979
var n=XDate.parse("3/2/2004");
8080
~~~~~
8181
!!! error TS2552: Cannot find name 'XDate'. Did you mean 'Date'?
82-
!!! related TS2728 /.ts/lib.es5.d.ts:837:15: 'Date' is declared here.
82+
!!! related TS2728 /.ts/lib.es5.d.ts:897:15: 'Date' is declared here.
8383
n=XDate.UTC(1964,2,1);
8484
~~~~~
8585
!!! error TS2552: Cannot find name 'XDate'. Did you mean 'Date'?
86-
!!! related TS2728 /.ts/lib.es5.d.ts:837:15: 'Date' is declared here.
86+
!!! related TS2728 /.ts/lib.es5.d.ts:897:15: 'Date' is declared here.
8787

8888

0 commit comments

Comments
 (0)