From 83fdecf86a77f5ed5470e91029b0730dcdfaed83 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Thu, 27 Oct 2016 14:41:40 -0700 Subject: [PATCH 1/6] Skip overloads with too-short function parameters If the parameter of an overload is a function and the argument is also a function, skip the overload if the parameter has fewer arguments than the argument does. That overload cannot possibly apply, and should not participate in, for example, contextual typing. Example: ```ts interface I { (a: number): void; (b: string, c): void; } declare function f(i: I): void; f((x, y) => {}); ``` This code now skips the first overload instead of considering. This was a longstanding bug but was only uncovered now that more functions expressions are context sensitive. --- src/compiler/checker.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4aee170f195..7cf61a71317 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11890,6 +11890,11 @@ namespace ts { // If the effective argument type is 'undefined', there is no synthetic type // for the argument. In that case, we should check the argument. if (argType === undefined) { + // If the parameter and argument are both functions and the parameter has fewer arguments than the argument, + // then this signature is not applicable. Exit early. + if (!reportErrors && isAritySmaller(paramType, arg)) { + return false; + } argType = checkExpressionWithContextualType(arg, paramType, excludeArgument && excludeArgument[i] ? identityMapper : undefined); } @@ -11904,6 +11909,21 @@ namespace ts { return true; } + function isAritySmaller(sourceType: Type, target: Expression) { + if (isFunctionExpressionOrArrowFunction(target) && isFunctionType(sourceType)) { + let targetParameterCount = 0; + for (; targetParameterCount < target.parameters.length; targetParameterCount++) { + const param = target.parameters[targetParameterCount]; + if (param.initializer || param.questionToken || param.dotDotDotToken || isJSDocOptionalParameter(param)) { + break; + } + } + const sourceSignatures = getSignaturesOfType(sourceType, SignatureKind.Call); + const sourceLengths = sourceSignatures.map(sig => !sig.hasRestParameter ? sig.parameters.length : Number.MAX_VALUE); + return forEach(sourceLengths, len => len < targetParameterCount); + } + } + /** * Returns the this argument in calls like x.f(...) and x[f](...). Undefined otherwise. */ From cff586d4adaf4b776904f1cb645c8101034c7fbb Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Thu, 27 Oct 2016 14:44:54 -0700 Subject: [PATCH 2/6] Test skip overloads w/too-short function params 1. Update changed baseline. 2. Add a new test with baseline. --- .../contextualTypingOfTooShortOverloads.js | 59 ++++++++ ...ontextualTypingOfTooShortOverloads.symbols | 139 +++++++++++++++++ .../contextualTypingOfTooShortOverloads.types | 143 ++++++++++++++++++ ...AnnotatedFunctionInferenceError.errors.txt | 8 +- .../contextualTypingOfTooShortOverloads.ts | 49 ++++++ 5 files changed, 394 insertions(+), 4 deletions(-) create mode 100644 tests/baselines/reference/contextualTypingOfTooShortOverloads.js create mode 100644 tests/baselines/reference/contextualTypingOfTooShortOverloads.symbols create mode 100644 tests/baselines/reference/contextualTypingOfTooShortOverloads.types create mode 100644 tests/cases/compiler/contextualTypingOfTooShortOverloads.ts diff --git a/tests/baselines/reference/contextualTypingOfTooShortOverloads.js b/tests/baselines/reference/contextualTypingOfTooShortOverloads.js new file mode 100644 index 00000000000..55f1361f5a8 --- /dev/null +++ b/tests/baselines/reference/contextualTypingOfTooShortOverloads.js @@ -0,0 +1,59 @@ +//// [contextualTypingOfTooShortOverloads.ts] +// small repro from #11875 +var use: Overload; +use((req, res) => {}); + +interface Overload { + (handler1: (req1: string) => void): void; + (handler2: (req2: number, res2: number) => void): void; +} +// larger repro from #11875 +let app: MyApp; +app.use((err: any, req, res, next) => { return; }); + + +interface MyApp { + use: IRouterHandler & IRouterMatcher; +} + +interface IRouterHandler { + (...handlers: RequestHandler[]): T; + (...handlers: RequestHandlerParams[]): T; +} + +interface IRouterMatcher { + (path: PathParams, ...handlers: RequestHandler[]): T; + (path: PathParams, ...handlers: RequestHandlerParams[]): T; +} + +type PathParams = string | RegExp | (string | RegExp)[]; +type RequestHandlerParams = RequestHandler | ErrorRequestHandler | (RequestHandler | ErrorRequestHandler)[]; + +interface RequestHandler { + (req: Request, res: Response, next: NextFunction): any; +} + +interface ErrorRequestHandler { + (err: any, req: Request, res: Response, next: NextFunction): any; +} + +interface Request { + method: string; +} + +interface Response { + statusCode: number; +} + +interface NextFunction { + (err?: any): void; +} + + +//// [contextualTypingOfTooShortOverloads.js] +// small repro from #11875 +var use; +use(function (req, res) { }); +// larger repro from #11875 +var app; +app.use(function (err, req, res, next) { return; }); diff --git a/tests/baselines/reference/contextualTypingOfTooShortOverloads.symbols b/tests/baselines/reference/contextualTypingOfTooShortOverloads.symbols new file mode 100644 index 00000000000..8f196cf4825 --- /dev/null +++ b/tests/baselines/reference/contextualTypingOfTooShortOverloads.symbols @@ -0,0 +1,139 @@ +=== tests/cases/compiler/contextualTypingOfTooShortOverloads.ts === +// small repro from #11875 +var use: Overload; +>use : Symbol(use, Decl(contextualTypingOfTooShortOverloads.ts, 1, 3)) +>Overload : Symbol(Overload, Decl(contextualTypingOfTooShortOverloads.ts, 2, 22)) + +use((req, res) => {}); +>use : Symbol(use, Decl(contextualTypingOfTooShortOverloads.ts, 1, 3)) +>req : Symbol(req, Decl(contextualTypingOfTooShortOverloads.ts, 2, 5)) +>res : Symbol(res, Decl(contextualTypingOfTooShortOverloads.ts, 2, 9)) + +interface Overload { +>Overload : Symbol(Overload, Decl(contextualTypingOfTooShortOverloads.ts, 2, 22)) + + (handler1: (req1: string) => void): void; +>handler1 : Symbol(handler1, Decl(contextualTypingOfTooShortOverloads.ts, 5, 5)) +>req1 : Symbol(req1, Decl(contextualTypingOfTooShortOverloads.ts, 5, 16)) + + (handler2: (req2: number, res2: number) => void): void; +>handler2 : Symbol(handler2, Decl(contextualTypingOfTooShortOverloads.ts, 6, 5)) +>req2 : Symbol(req2, Decl(contextualTypingOfTooShortOverloads.ts, 6, 16)) +>res2 : Symbol(res2, Decl(contextualTypingOfTooShortOverloads.ts, 6, 29)) +} +// larger repro from #11875 +let app: MyApp; +>app : Symbol(app, Decl(contextualTypingOfTooShortOverloads.ts, 9, 3)) +>MyApp : Symbol(MyApp, Decl(contextualTypingOfTooShortOverloads.ts, 10, 51)) + +app.use((err: any, req, res, next) => { return; }); +>app.use : Symbol(MyApp.use, Decl(contextualTypingOfTooShortOverloads.ts, 13, 17)) +>app : Symbol(app, Decl(contextualTypingOfTooShortOverloads.ts, 9, 3)) +>use : Symbol(MyApp.use, Decl(contextualTypingOfTooShortOverloads.ts, 13, 17)) +>err : Symbol(err, Decl(contextualTypingOfTooShortOverloads.ts, 10, 9)) +>req : Symbol(req, Decl(contextualTypingOfTooShortOverloads.ts, 10, 18)) +>res : Symbol(res, Decl(contextualTypingOfTooShortOverloads.ts, 10, 23)) +>next : Symbol(next, Decl(contextualTypingOfTooShortOverloads.ts, 10, 28)) + + +interface MyApp { +>MyApp : Symbol(MyApp, Decl(contextualTypingOfTooShortOverloads.ts, 10, 51)) + + use: IRouterHandler & IRouterMatcher; +>use : Symbol(MyApp.use, Decl(contextualTypingOfTooShortOverloads.ts, 13, 17)) +>IRouterHandler : Symbol(IRouterHandler, Decl(contextualTypingOfTooShortOverloads.ts, 15, 1)) +>IRouterMatcher : Symbol(IRouterMatcher, Decl(contextualTypingOfTooShortOverloads.ts, 20, 1)) +} + +interface IRouterHandler { +>IRouterHandler : Symbol(IRouterHandler, Decl(contextualTypingOfTooShortOverloads.ts, 15, 1)) +>T : Symbol(T, Decl(contextualTypingOfTooShortOverloads.ts, 17, 25)) + + (...handlers: RequestHandler[]): T; +>handlers : Symbol(handlers, Decl(contextualTypingOfTooShortOverloads.ts, 18, 5)) +>RequestHandler : Symbol(RequestHandler, Decl(contextualTypingOfTooShortOverloads.ts, 28, 108)) +>T : Symbol(T, Decl(contextualTypingOfTooShortOverloads.ts, 17, 25)) + + (...handlers: RequestHandlerParams[]): T; +>handlers : Symbol(handlers, Decl(contextualTypingOfTooShortOverloads.ts, 19, 5)) +>RequestHandlerParams : Symbol(RequestHandlerParams, Decl(contextualTypingOfTooShortOverloads.ts, 27, 56)) +>T : Symbol(T, Decl(contextualTypingOfTooShortOverloads.ts, 17, 25)) +} + +interface IRouterMatcher { +>IRouterMatcher : Symbol(IRouterMatcher, Decl(contextualTypingOfTooShortOverloads.ts, 20, 1)) +>T : Symbol(T, Decl(contextualTypingOfTooShortOverloads.ts, 22, 25)) + + (path: PathParams, ...handlers: RequestHandler[]): T; +>path : Symbol(path, Decl(contextualTypingOfTooShortOverloads.ts, 23, 5)) +>PathParams : Symbol(PathParams, Decl(contextualTypingOfTooShortOverloads.ts, 25, 1)) +>handlers : Symbol(handlers, Decl(contextualTypingOfTooShortOverloads.ts, 23, 22)) +>RequestHandler : Symbol(RequestHandler, Decl(contextualTypingOfTooShortOverloads.ts, 28, 108)) +>T : Symbol(T, Decl(contextualTypingOfTooShortOverloads.ts, 22, 25)) + + (path: PathParams, ...handlers: RequestHandlerParams[]): T; +>path : Symbol(path, Decl(contextualTypingOfTooShortOverloads.ts, 24, 5)) +>PathParams : Symbol(PathParams, Decl(contextualTypingOfTooShortOverloads.ts, 25, 1)) +>handlers : Symbol(handlers, Decl(contextualTypingOfTooShortOverloads.ts, 24, 22)) +>RequestHandlerParams : Symbol(RequestHandlerParams, Decl(contextualTypingOfTooShortOverloads.ts, 27, 56)) +>T : Symbol(T, Decl(contextualTypingOfTooShortOverloads.ts, 22, 25)) +} + +type PathParams = string | RegExp | (string | RegExp)[]; +>PathParams : Symbol(PathParams, Decl(contextualTypingOfTooShortOverloads.ts, 25, 1)) +>RegExp : Symbol(RegExp, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) +>RegExp : Symbol(RegExp, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) + +type RequestHandlerParams = RequestHandler | ErrorRequestHandler | (RequestHandler | ErrorRequestHandler)[]; +>RequestHandlerParams : Symbol(RequestHandlerParams, Decl(contextualTypingOfTooShortOverloads.ts, 27, 56)) +>RequestHandler : Symbol(RequestHandler, Decl(contextualTypingOfTooShortOverloads.ts, 28, 108)) +>ErrorRequestHandler : Symbol(ErrorRequestHandler, Decl(contextualTypingOfTooShortOverloads.ts, 32, 1)) +>RequestHandler : Symbol(RequestHandler, Decl(contextualTypingOfTooShortOverloads.ts, 28, 108)) +>ErrorRequestHandler : Symbol(ErrorRequestHandler, Decl(contextualTypingOfTooShortOverloads.ts, 32, 1)) + +interface RequestHandler { +>RequestHandler : Symbol(RequestHandler, Decl(contextualTypingOfTooShortOverloads.ts, 28, 108)) + + (req: Request, res: Response, next: NextFunction): any; +>req : Symbol(req, Decl(contextualTypingOfTooShortOverloads.ts, 31, 5)) +>Request : Symbol(Request, Decl(contextualTypingOfTooShortOverloads.ts, 36, 1)) +>res : Symbol(res, Decl(contextualTypingOfTooShortOverloads.ts, 31, 18)) +>Response : Symbol(Response, Decl(contextualTypingOfTooShortOverloads.ts, 40, 1)) +>next : Symbol(next, Decl(contextualTypingOfTooShortOverloads.ts, 31, 33)) +>NextFunction : Symbol(NextFunction, Decl(contextualTypingOfTooShortOverloads.ts, 44, 1)) +} + +interface ErrorRequestHandler { +>ErrorRequestHandler : Symbol(ErrorRequestHandler, Decl(contextualTypingOfTooShortOverloads.ts, 32, 1)) + + (err: any, req: Request, res: Response, next: NextFunction): any; +>err : Symbol(err, Decl(contextualTypingOfTooShortOverloads.ts, 35, 5)) +>req : Symbol(req, Decl(contextualTypingOfTooShortOverloads.ts, 35, 14)) +>Request : Symbol(Request, Decl(contextualTypingOfTooShortOverloads.ts, 36, 1)) +>res : Symbol(res, Decl(contextualTypingOfTooShortOverloads.ts, 35, 28)) +>Response : Symbol(Response, Decl(contextualTypingOfTooShortOverloads.ts, 40, 1)) +>next : Symbol(next, Decl(contextualTypingOfTooShortOverloads.ts, 35, 43)) +>NextFunction : Symbol(NextFunction, Decl(contextualTypingOfTooShortOverloads.ts, 44, 1)) +} + +interface Request { +>Request : Symbol(Request, Decl(contextualTypingOfTooShortOverloads.ts, 36, 1)) + + method: string; +>method : Symbol(Request.method, Decl(contextualTypingOfTooShortOverloads.ts, 38, 19)) +} + +interface Response { +>Response : Symbol(Response, Decl(contextualTypingOfTooShortOverloads.ts, 40, 1)) + + statusCode: number; +>statusCode : Symbol(Response.statusCode, Decl(contextualTypingOfTooShortOverloads.ts, 42, 20)) +} + +interface NextFunction { +>NextFunction : Symbol(NextFunction, Decl(contextualTypingOfTooShortOverloads.ts, 44, 1)) + + (err?: any): void; +>err : Symbol(err, Decl(contextualTypingOfTooShortOverloads.ts, 47, 5)) +} + diff --git a/tests/baselines/reference/contextualTypingOfTooShortOverloads.types b/tests/baselines/reference/contextualTypingOfTooShortOverloads.types new file mode 100644 index 00000000000..9e306da3e61 --- /dev/null +++ b/tests/baselines/reference/contextualTypingOfTooShortOverloads.types @@ -0,0 +1,143 @@ +=== tests/cases/compiler/contextualTypingOfTooShortOverloads.ts === +// small repro from #11875 +var use: Overload; +>use : Overload +>Overload : Overload + +use((req, res) => {}); +>use((req, res) => {}) : void +>use : Overload +>(req, res) => {} : (req: number, res: number) => void +>req : number +>res : number + +interface Overload { +>Overload : Overload + + (handler1: (req1: string) => void): void; +>handler1 : (req1: string) => void +>req1 : string + + (handler2: (req2: number, res2: number) => void): void; +>handler2 : (req2: number, res2: number) => void +>req2 : number +>res2 : number +} +// larger repro from #11875 +let app: MyApp; +>app : MyApp +>MyApp : MyApp + +app.use((err: any, req, res, next) => { return; }); +>app.use((err: any, req, res, next) => { return; }) : MyApp +>app.use : IRouterHandler & IRouterMatcher +>app : MyApp +>use : IRouterHandler & IRouterMatcher +>(err: any, req, res, next) => { return; } : (err: any, req: any, res: any, next: any) => void +>err : any +>req : any +>res : any +>next : any + + +interface MyApp { +>MyApp : MyApp + + use: IRouterHandler & IRouterMatcher; +>use : IRouterHandler & IRouterMatcher +>IRouterHandler : IRouterHandler +>IRouterMatcher : IRouterMatcher +} + +interface IRouterHandler { +>IRouterHandler : IRouterHandler +>T : T + + (...handlers: RequestHandler[]): T; +>handlers : RequestHandler[] +>RequestHandler : RequestHandler +>T : T + + (...handlers: RequestHandlerParams[]): T; +>handlers : RequestHandlerParams[] +>RequestHandlerParams : RequestHandlerParams +>T : T +} + +interface IRouterMatcher { +>IRouterMatcher : IRouterMatcher +>T : T + + (path: PathParams, ...handlers: RequestHandler[]): T; +>path : PathParams +>PathParams : PathParams +>handlers : RequestHandler[] +>RequestHandler : RequestHandler +>T : T + + (path: PathParams, ...handlers: RequestHandlerParams[]): T; +>path : PathParams +>PathParams : PathParams +>handlers : RequestHandlerParams[] +>RequestHandlerParams : RequestHandlerParams +>T : T +} + +type PathParams = string | RegExp | (string | RegExp)[]; +>PathParams : PathParams +>RegExp : RegExp +>RegExp : RegExp + +type RequestHandlerParams = RequestHandler | ErrorRequestHandler | (RequestHandler | ErrorRequestHandler)[]; +>RequestHandlerParams : RequestHandlerParams +>RequestHandler : RequestHandler +>ErrorRequestHandler : ErrorRequestHandler +>RequestHandler : RequestHandler +>ErrorRequestHandler : ErrorRequestHandler + +interface RequestHandler { +>RequestHandler : RequestHandler + + (req: Request, res: Response, next: NextFunction): any; +>req : Request +>Request : Request +>res : Response +>Response : Response +>next : NextFunction +>NextFunction : NextFunction +} + +interface ErrorRequestHandler { +>ErrorRequestHandler : ErrorRequestHandler + + (err: any, req: Request, res: Response, next: NextFunction): any; +>err : any +>req : Request +>Request : Request +>res : Response +>Response : Response +>next : NextFunction +>NextFunction : NextFunction +} + +interface Request { +>Request : Request + + method: string; +>method : string +} + +interface Response { +>Response : Response + + statusCode: number; +>statusCode : number +} + +interface NextFunction { +>NextFunction : NextFunction + + (err?: any): void; +>err : any +} + diff --git a/tests/baselines/reference/partiallyAnnotatedFunctionInferenceError.errors.txt b/tests/baselines/reference/partiallyAnnotatedFunctionInferenceError.errors.txt index a9fc9b68cf8..d1e84494fa9 100644 --- a/tests/baselines/reference/partiallyAnnotatedFunctionInferenceError.errors.txt +++ b/tests/baselines/reference/partiallyAnnotatedFunctionInferenceError.errors.txt @@ -1,5 +1,5 @@ -tests/cases/conformance/types/contextualTypes/partiallyAnnotatedFunction/partiallyAnnotatedFunctionInferenceError.ts(12,11): error TS2345: Argument of type '(t1: D, t2: D, t3: any) => void' is not assignable to parameter of type '(t: D, t1: D) => void'. -tests/cases/conformance/types/contextualTypes/partiallyAnnotatedFunction/partiallyAnnotatedFunctionInferenceError.ts(13,11): error TS2345: Argument of type '(t1: D, t2: D, t3: any) => void' is not assignable to parameter of type '(t: D, t1: D) => void'. +tests/cases/conformance/types/contextualTypes/partiallyAnnotatedFunction/partiallyAnnotatedFunctionInferenceError.ts(12,11): error TS2345: Argument of type '(t1: D, t2: C, t3: any) => void' is not assignable to parameter of type '(t: C, t1: C) => void'. +tests/cases/conformance/types/contextualTypes/partiallyAnnotatedFunction/partiallyAnnotatedFunctionInferenceError.ts(13,11): error TS2345: Argument of type '(t1: C, t2: D, t3: any) => void' is not assignable to parameter of type '(t: C, t1: C) => void'. tests/cases/conformance/types/contextualTypes/partiallyAnnotatedFunction/partiallyAnnotatedFunctionInferenceError.ts(14,11): error TS2345: Argument of type '(t1: C, t2: C, t3: D) => void' is not assignable to parameter of type '(t: C, t1: C) => void'. @@ -17,10 +17,10 @@ tests/cases/conformance/types/contextualTypes/partiallyAnnotatedFunction/partial // more args testError((t1: D, t2, t3) => {}) ~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2345: Argument of type '(t1: D, t2: D, t3: any) => void' is not assignable to parameter of type '(t: D, t1: D) => void'. +!!! error TS2345: Argument of type '(t1: D, t2: C, t3: any) => void' is not assignable to parameter of type '(t: C, t1: C) => void'. testError((t1, t2: D, t3) => {}) ~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2345: Argument of type '(t1: D, t2: D, t3: any) => void' is not assignable to parameter of type '(t: D, t1: D) => void'. +!!! error TS2345: Argument of type '(t1: C, t2: D, t3: any) => void' is not assignable to parameter of type '(t: C, t1: C) => void'. testError((t1, t2, t3: D) => {}) ~~~~~~~~~~~~~~~~~~~~~ !!! error TS2345: Argument of type '(t1: C, t2: C, t3: D) => void' is not assignable to parameter of type '(t: C, t1: C) => void'. diff --git a/tests/cases/compiler/contextualTypingOfTooShortOverloads.ts b/tests/cases/compiler/contextualTypingOfTooShortOverloads.ts new file mode 100644 index 00000000000..6fa88a1e559 --- /dev/null +++ b/tests/cases/compiler/contextualTypingOfTooShortOverloads.ts @@ -0,0 +1,49 @@ +// small repro from #11875 +var use: Overload; +use((req, res) => {}); + +interface Overload { + (handler1: (req1: string) => void): void; + (handler2: (req2: number, res2: number) => void): void; +} +// larger repro from #11875 +let app: MyApp; +app.use((err: any, req, res, next) => { return; }); + + +interface MyApp { + use: IRouterHandler & IRouterMatcher; +} + +interface IRouterHandler { + (...handlers: RequestHandler[]): T; + (...handlers: RequestHandlerParams[]): T; +} + +interface IRouterMatcher { + (path: PathParams, ...handlers: RequestHandler[]): T; + (path: PathParams, ...handlers: RequestHandlerParams[]): T; +} + +type PathParams = string | RegExp | (string | RegExp)[]; +type RequestHandlerParams = RequestHandler | ErrorRequestHandler | (RequestHandler | ErrorRequestHandler)[]; + +interface RequestHandler { + (req: Request, res: Response, next: NextFunction): any; +} + +interface ErrorRequestHandler { + (err: any, req: Request, res: Response, next: NextFunction): any; +} + +interface Request { + method: string; +} + +interface Response { + statusCode: number; +} + +interface NextFunction { + (err?: any): void; +} From 5a336fedbaf6d87896c1b3bc928ceb5377c99fec Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Thu, 27 Oct 2016 15:38:29 -0700 Subject: [PATCH 3/6] Minor style improvements --- src/compiler/checker.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7cf61a71317..db1f3a5bd8f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11891,7 +11891,8 @@ namespace ts { // for the argument. In that case, we should check the argument. if (argType === undefined) { // If the parameter and argument are both functions and the parameter has fewer arguments than the argument, - // then this signature is not applicable. Exit early. + // then this signature is not applicable. Exit early to avoid fixing incorrect + // contextual types to the function expression parameters. if (!reportErrors && isAritySmaller(paramType, arg)) { return false; } @@ -11922,6 +11923,8 @@ namespace ts { const sourceLengths = sourceSignatures.map(sig => !sig.hasRestParameter ? sig.parameters.length : Number.MAX_VALUE); return forEach(sourceLengths, len => len < targetParameterCount); } + + return false; } /** From 3e71d50708759c0180e45657fe7714036e3f0415 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Thu, 27 Oct 2016 15:42:41 -0700 Subject: [PATCH 4/6] Ignore optionality when skipping overloads --- src/compiler/checker.ts | 9 +-------- ...genericCallWithOverloadedFunctionTypedArguments.types | 4 ++-- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index db1f3a5bd8f..812c55d0ea2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11912,16 +11912,9 @@ namespace ts { function isAritySmaller(sourceType: Type, target: Expression) { if (isFunctionExpressionOrArrowFunction(target) && isFunctionType(sourceType)) { - let targetParameterCount = 0; - for (; targetParameterCount < target.parameters.length; targetParameterCount++) { - const param = target.parameters[targetParameterCount]; - if (param.initializer || param.questionToken || param.dotDotDotToken || isJSDocOptionalParameter(param)) { - break; - } - } const sourceSignatures = getSignaturesOfType(sourceType, SignatureKind.Call); const sourceLengths = sourceSignatures.map(sig => !sig.hasRestParameter ? sig.parameters.length : Number.MAX_VALUE); - return forEach(sourceLengths, len => len < targetParameterCount); + return forEach(sourceLengths, len => len < target.parameters.length); } return false; diff --git a/tests/baselines/reference/genericCallWithOverloadedFunctionTypedArguments.types b/tests/baselines/reference/genericCallWithOverloadedFunctionTypedArguments.types index ab07e9c880a..c26370a7640 100644 --- a/tests/baselines/reference/genericCallWithOverloadedFunctionTypedArguments.types +++ b/tests/baselines/reference/genericCallWithOverloadedFunctionTypedArguments.types @@ -122,8 +122,8 @@ module GenericParameter { >'' : "" var r11 = foo6((x: T, y?: T) => ''); // any => string (+1 overload) ->r11 : { (x: any): string; (x: any, y?: any): string; } ->foo6((x: T, y?: T) => '') : { (x: any): string; (x: any, y?: any): string; } +>r11 : any +>foo6((x: T, y?: T) => '') : any >foo6 : (cb: { (x: T): string; (x: T, y?: T): string; }) => { (x: T): string; (x: T, y?: T): string; } >(x: T, y?: T) => '' : (x: T, y?: T) => string >T : T From 278d0a4665a5ac9a2a821f890fcb42a6e12202bf Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Thu, 27 Oct 2016 22:52:01 -0700 Subject: [PATCH 5/6] Do not use contextual signatures with too few parameters --- src/compiler/checker.ts | 40 +++++++++++-------- .../contextualTypingOfTooShortOverloads.types | 6 +-- ...WithOverloadedFunctionTypedArguments.types | 4 +- ...AnnotatedFunctionInferenceError.errors.txt | 12 +++--- 4 files changed, 35 insertions(+), 27 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 812c55d0ea2..b188fec8b4d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10343,6 +10343,10 @@ namespace ts { if (!type) { return undefined; } + // If the contextual signature has fewer parameters than the function expression, do not use it + if (isFunctionExpressionOrArrowFunction(node) && isAritySmaller(type, node)) { + return undefined; + } if (!(type.flags & TypeFlags.Union)) { return getNonGenericSignature(type); } @@ -10377,6 +10381,26 @@ namespace ts { return result; } + function isAritySmaller(sourceType: Type, target: FunctionExpression | ArrowFunction) { + if (isFunctionType(sourceType)) { + let targetParameterCount = 0; + for (; targetParameterCount < target.parameters.length; targetParameterCount++) { + const param = target.parameters[targetParameterCount]; + if (param.initializer || param.questionToken || param.dotDotDotToken || isJSDocOptionalParameter(param)) { + break; + } + } + if (target.parameters.length && parameterIsThisKeyword(target.parameters[0])) { + targetParameterCount--; + } + const sourceSignatures = getSignaturesOfType(sourceType, SignatureKind.Call); + const sourceLengths = sourceSignatures.map(sig => !sig.hasRestParameter ? sig.parameters.length : Number.MAX_VALUE); + return forEach(sourceLengths, len => len < targetParameterCount); + } + + return false; + } + /** * Detect if the mapper implies an inference context. Specifically, there are 4 possible values * for a mapper. Let's go through each one of them: @@ -11890,12 +11914,6 @@ namespace ts { // If the effective argument type is 'undefined', there is no synthetic type // for the argument. In that case, we should check the argument. if (argType === undefined) { - // If the parameter and argument are both functions and the parameter has fewer arguments than the argument, - // then this signature is not applicable. Exit early to avoid fixing incorrect - // contextual types to the function expression parameters. - if (!reportErrors && isAritySmaller(paramType, arg)) { - return false; - } argType = checkExpressionWithContextualType(arg, paramType, excludeArgument && excludeArgument[i] ? identityMapper : undefined); } @@ -11910,16 +11928,6 @@ namespace ts { return true; } - function isAritySmaller(sourceType: Type, target: Expression) { - if (isFunctionExpressionOrArrowFunction(target) && isFunctionType(sourceType)) { - const sourceSignatures = getSignaturesOfType(sourceType, SignatureKind.Call); - const sourceLengths = sourceSignatures.map(sig => !sig.hasRestParameter ? sig.parameters.length : Number.MAX_VALUE); - return forEach(sourceLengths, len => len < target.parameters.length); - } - - return false; - } - /** * Returns the this argument in calls like x.f(...) and x[f](...). Undefined otherwise. */ diff --git a/tests/baselines/reference/contextualTypingOfTooShortOverloads.types b/tests/baselines/reference/contextualTypingOfTooShortOverloads.types index 9e306da3e61..769bd55d4b5 100644 --- a/tests/baselines/reference/contextualTypingOfTooShortOverloads.types +++ b/tests/baselines/reference/contextualTypingOfTooShortOverloads.types @@ -7,9 +7,9 @@ var use: Overload; use((req, res) => {}); >use((req, res) => {}) : void >use : Overload ->(req, res) => {} : (req: number, res: number) => void ->req : number ->res : number +>(req, res) => {} : (req: any, res: any) => void +>req : any +>res : any interface Overload { >Overload : Overload diff --git a/tests/baselines/reference/genericCallWithOverloadedFunctionTypedArguments.types b/tests/baselines/reference/genericCallWithOverloadedFunctionTypedArguments.types index c26370a7640..ab07e9c880a 100644 --- a/tests/baselines/reference/genericCallWithOverloadedFunctionTypedArguments.types +++ b/tests/baselines/reference/genericCallWithOverloadedFunctionTypedArguments.types @@ -122,8 +122,8 @@ module GenericParameter { >'' : "" var r11 = foo6((x: T, y?: T) => ''); // any => string (+1 overload) ->r11 : any ->foo6((x: T, y?: T) => '') : any +>r11 : { (x: any): string; (x: any, y?: any): string; } +>foo6((x: T, y?: T) => '') : { (x: any): string; (x: any, y?: any): string; } >foo6 : (cb: { (x: T): string; (x: T, y?: T): string; }) => { (x: T): string; (x: T, y?: T): string; } >(x: T, y?: T) => '' : (x: T, y?: T) => string >T : T diff --git a/tests/baselines/reference/partiallyAnnotatedFunctionInferenceError.errors.txt b/tests/baselines/reference/partiallyAnnotatedFunctionInferenceError.errors.txt index d1e84494fa9..a6a45245929 100644 --- a/tests/baselines/reference/partiallyAnnotatedFunctionInferenceError.errors.txt +++ b/tests/baselines/reference/partiallyAnnotatedFunctionInferenceError.errors.txt @@ -1,6 +1,6 @@ -tests/cases/conformance/types/contextualTypes/partiallyAnnotatedFunction/partiallyAnnotatedFunctionInferenceError.ts(12,11): error TS2345: Argument of type '(t1: D, t2: C, t3: any) => void' is not assignable to parameter of type '(t: C, t1: C) => void'. -tests/cases/conformance/types/contextualTypes/partiallyAnnotatedFunction/partiallyAnnotatedFunctionInferenceError.ts(13,11): error TS2345: Argument of type '(t1: C, t2: D, t3: any) => void' is not assignable to parameter of type '(t: C, t1: C) => void'. -tests/cases/conformance/types/contextualTypes/partiallyAnnotatedFunction/partiallyAnnotatedFunctionInferenceError.ts(14,11): error TS2345: Argument of type '(t1: C, t2: C, t3: D) => void' is not assignable to parameter of type '(t: C, t1: C) => void'. +tests/cases/conformance/types/contextualTypes/partiallyAnnotatedFunction/partiallyAnnotatedFunctionInferenceError.ts(12,11): error TS2345: Argument of type '(t1: D, t2: any, t3: any) => void' is not assignable to parameter of type '(t: any, t1: any) => void'. +tests/cases/conformance/types/contextualTypes/partiallyAnnotatedFunction/partiallyAnnotatedFunctionInferenceError.ts(13,11): error TS2345: Argument of type '(t1: any, t2: D, t3: any) => void' is not assignable to parameter of type '(t: any, t1: any) => void'. +tests/cases/conformance/types/contextualTypes/partiallyAnnotatedFunction/partiallyAnnotatedFunctionInferenceError.ts(14,11): error TS2345: Argument of type '(t1: any, t2: any, t3: D) => void' is not assignable to parameter of type '(t: any, t1: any) => void'. ==== tests/cases/conformance/types/contextualTypes/partiallyAnnotatedFunction/partiallyAnnotatedFunctionInferenceError.ts (3 errors) ==== @@ -17,11 +17,11 @@ tests/cases/conformance/types/contextualTypes/partiallyAnnotatedFunction/partial // more args testError((t1: D, t2, t3) => {}) ~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2345: Argument of type '(t1: D, t2: C, t3: any) => void' is not assignable to parameter of type '(t: C, t1: C) => void'. +!!! error TS2345: Argument of type '(t1: D, t2: any, t3: any) => void' is not assignable to parameter of type '(t: any, t1: any) => void'. testError((t1, t2: D, t3) => {}) ~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2345: Argument of type '(t1: C, t2: D, t3: any) => void' is not assignable to parameter of type '(t: C, t1: C) => void'. +!!! error TS2345: Argument of type '(t1: any, t2: D, t3: any) => void' is not assignable to parameter of type '(t: any, t1: any) => void'. testError((t1, t2, t3: D) => {}) ~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2345: Argument of type '(t1: C, t2: C, t3: D) => void' is not assignable to parameter of type '(t: C, t1: C) => void'. +!!! error TS2345: Argument of type '(t1: any, t2: any, t3: D) => void' is not assignable to parameter of type '(t: any, t1: any) => void'. \ No newline at end of file From 179682610f723b5dfe5660e5162a44046294cf9f Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Fri, 28 Oct 2016 08:57:19 -0700 Subject: [PATCH 6/6] isAritySmaller runs later: getNonGenericSignature --- src/compiler/checker.ts | 48 +++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b188fec8b4d..474861589d2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10305,16 +10305,32 @@ namespace ts { // If the given type is an object or union type, if that type has a single signature, and if // that signature is non-generic, return the signature. Otherwise return undefined. - function getNonGenericSignature(type: Type): Signature { + function getNonGenericSignature(type: Type, node: FunctionExpression | ArrowFunction | MethodDeclaration): Signature { const signatures = getSignaturesOfStructuredType(type, SignatureKind.Call); if (signatures.length === 1) { const signature = signatures[0]; - if (!signature.typeParameters) { + if (!signature.typeParameters && !isAritySmaller(signature, node)) { return signature; } } } + /** If the contextual signature has fewer parameters than the function expression, do not use it */ + function isAritySmaller(signature: Signature, target: FunctionExpression | ArrowFunction | MethodDeclaration) { + let targetParameterCount = 0; + for (; targetParameterCount < target.parameters.length; targetParameterCount++) { + const param = target.parameters[targetParameterCount]; + if (param.initializer || param.questionToken || param.dotDotDotToken || isJSDocOptionalParameter(param)) { + break; + } + } + if (target.parameters.length && parameterIsThisKeyword(target.parameters[0])) { + targetParameterCount--; + } + const sourceLength = signature.hasRestParameter ? Number.MAX_VALUE : signature.parameters.length; + return sourceLength < targetParameterCount; + } + function isFunctionExpressionOrArrowFunction(node: Node): node is FunctionExpression | ArrowFunction { return node.kind === SyntaxKind.FunctionExpression || node.kind === SyntaxKind.ArrowFunction; } @@ -10343,17 +10359,13 @@ namespace ts { if (!type) { return undefined; } - // If the contextual signature has fewer parameters than the function expression, do not use it - if (isFunctionExpressionOrArrowFunction(node) && isAritySmaller(type, node)) { - return undefined; - } if (!(type.flags & TypeFlags.Union)) { - return getNonGenericSignature(type); + return getNonGenericSignature(type, node); } let signatureList: Signature[]; const types = (type).types; for (const current of types) { - const signature = getNonGenericSignature(current); + const signature = getNonGenericSignature(current, node); if (signature) { if (!signatureList) { // This signature will contribute to contextual union signature @@ -10381,26 +10393,6 @@ namespace ts { return result; } - function isAritySmaller(sourceType: Type, target: FunctionExpression | ArrowFunction) { - if (isFunctionType(sourceType)) { - let targetParameterCount = 0; - for (; targetParameterCount < target.parameters.length; targetParameterCount++) { - const param = target.parameters[targetParameterCount]; - if (param.initializer || param.questionToken || param.dotDotDotToken || isJSDocOptionalParameter(param)) { - break; - } - } - if (target.parameters.length && parameterIsThisKeyword(target.parameters[0])) { - targetParameterCount--; - } - const sourceSignatures = getSignaturesOfType(sourceType, SignatureKind.Call); - const sourceLengths = sourceSignatures.map(sig => !sig.hasRestParameter ? sig.parameters.length : Number.MAX_VALUE); - return forEach(sourceLengths, len => len < targetParameterCount); - } - - return false; - } - /** * Detect if the mapper implies an inference context. Specifically, there are 4 possible values * for a mapper. Let's go through each one of them: