From a602e1fdb47c4e968e8894fc5d01493d91a7d9fe Mon Sep 17 00:00:00 2001 From: Asad Saeeduddin Date: Thu, 10 Aug 2017 01:16:47 -0400 Subject: [PATCH 01/20] Rudimentary attempt at parameter inference --- src/compiler/checker.ts | 36 ++++++++++++++++++++-- src/compiler/utilities.ts | 14 +++++++++ tests/cases/compiler/parameterInference.ts | 13 ++++++++ 3 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 tests/cases/compiler/parameterInference.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a8af11e780305..b9129f44c0a48 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4298,7 +4298,7 @@ namespace ts { type = getContextualThisParameterType(func); } else { - type = getContextuallyTypedParameterType(declaration); + type = getContextuallyTypedParameterType(declaration) } if (type) { return addOptionality(type, /*optional*/ declaration.questionToken && includeOptionality); @@ -4327,8 +4327,10 @@ namespace ts { return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ false, /*reportErrors*/ true); } + const inferredType = getParameterTypeFromBody(declaration) + // No type specified and nothing can be inferred - return undefined; + return inferredType || undefined; } function getWidenedTypeFromJSSpecialPropertyDeclarations(symbol: Symbol) { @@ -12798,6 +12800,20 @@ namespace ts { return undefined; } + function getParameterTypeFromBody(parameter: ParameterDeclaration): Type { + const func = parameter.parent + if (!func.body) { + return unknownType; + } + + let type: Type; + let types: Type[]; + types = checkAndAggregateParameterExpressionTypes(parameter) + type = types ? getWidenedType(getIntersectionType(types)) : undefined; + + return type; + } + // Return contextual type of parameter or undefined if no contextual type is available function getContextuallyTypedParameterType(parameter: ParameterDeclaration): Type { const func = parameter.parent; @@ -16729,6 +16745,22 @@ namespace ts { return true; } + function checkAndAggregateParameterExpressionTypes(parameter: ParameterDeclaration): Type[] { + const func = parameter.parent + const usageTypes: Type[] = [] + forEachInvocation(func.body, invocation => { + const usages = invocation.arguments + .map((arg, i) => ({ arg, symbol: getSymbolAtLocation(arg), i })) + .filter(({ symbol }) => symbol && symbol.valueDeclaration === parameter) + if (!usages.length) return + const sig = getSignatureFromDeclaration(getSymbolAtLocation(invocation.expression).valueDeclaration as FunctionLikeDeclaration) + const parameterTypes = sig.parameters.map(getTypeOfParameter) + const argumentTypes = usages.map(({ i }) => parameterTypes[i]) + usageTypes.splice(0, 0, ...argumentTypes); + }); + return usageTypes; + } + function checkAndAggregateReturnExpressionTypes(func: FunctionLikeDeclaration, checkMode: CheckMode): Type[] { const functionFlags = getFunctionFlags(func); const aggregatedTypes: Type[] = []; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 76d242e8b08d8..edfca2dbe11d3 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -757,6 +757,20 @@ namespace ts { return false; } + export function forEachInvocation(body: Block, visitor: (stmt: CallExpression) => T): T { + + return traverse(body); + + function traverse(node: Node): T { + switch (node.kind) { + case SyntaxKind.CallExpression: + return visitor(node) + default: + return forEachChild(node, traverse); + } + } + } + // Warning: This has the same semantics as the forEach family of functions, // in that traversal terminates in the event that 'visitor' supplies a truthy value. export function forEachReturnStatement(body: Block, visitor: (stmt: ReturnStatement) => T): T { diff --git a/tests/cases/compiler/parameterInference.ts b/tests/cases/compiler/parameterInference.ts new file mode 100644 index 0000000000000..abedcf8074aea --- /dev/null +++ b/tests/cases/compiler/parameterInference.ts @@ -0,0 +1,13 @@ +// CASE 1 +function foo(s) { + Math.sqrt(s) +} + +// CASE 2 +declare function swapNumberString(n: string): number; +declare function swapNumberString(n: number): string; + +// Should have identical signature to swapNumberString +function subs(s) { + return swapNumberString(s); +} From 99b536228203684a81a2f77cbe603a953ac91f60 Mon Sep 17 00:00:00 2001 From: Asad Saeeduddin Date: Thu, 10 Aug 2017 01:21:53 -0400 Subject: [PATCH 02/20] Add additional test case --- tests/cases/compiler/parameterInference.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/cases/compiler/parameterInference.ts b/tests/cases/compiler/parameterInference.ts index abedcf8074aea..1a984d7275145 100644 --- a/tests/cases/compiler/parameterInference.ts +++ b/tests/cases/compiler/parameterInference.ts @@ -11,3 +11,12 @@ declare function swapNumberString(n: number): string; function subs(s) { return swapNumberString(s); } + +// CASE 3 +function f(x: number){ + return x; +} + +function g(x){ return x}; + +function h(x){ return f(x); }; From adfd904d6bf15f81b99411f6e8b8847451c1696c Mon Sep 17 00:00:00 2001 From: Asad Saeeduddin Date: Thu, 10 Aug 2017 02:06:30 -0400 Subject: [PATCH 03/20] Don't infer anything when no usages found --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b9129f44c0a48..2c14bf6f8d255 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16758,7 +16758,7 @@ namespace ts { const argumentTypes = usages.map(({ i }) => parameterTypes[i]) usageTypes.splice(0, 0, ...argumentTypes); }); - return usageTypes; + return usageTypes.length ? usageTypes : undefined; } function checkAndAggregateReturnExpressionTypes(func: FunctionLikeDeclaration, checkMode: CheckMode): Type[] { From 4d8a8b37a5226314b9dfaebe9f00d1474c98fa16 Mon Sep 17 00:00:00 2001 From: Asad Saeeduddin Date: Thu, 10 Aug 2017 02:17:09 -0400 Subject: [PATCH 04/20] Don't perform inference for non-parameter declarations --- src/compiler/checker.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2c14bf6f8d255..799fb10c09cf0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4327,10 +4327,14 @@ namespace ts { return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ false, /*reportErrors*/ true); } - const inferredType = getParameterTypeFromBody(declaration) + // Important to do this *after* attempt has been made to resolve via initializer + if (declaration.kind === SyntaxKind.Parameter) { + const inferredType = getParameterTypeFromBody(declaration) + if (inferredType) return inferredType + } // No type specified and nothing can be inferred - return inferredType || undefined; + return undefined; } function getWidenedTypeFromJSSpecialPropertyDeclarations(symbol: Symbol) { From d14c5113bf3715c4e2b8f70515404e7890415833 Mon Sep 17 00:00:00 2001 From: Asad Saeeduddin Date: Thu, 10 Aug 2017 02:25:57 -0400 Subject: [PATCH 05/20] Bail if can't determine symbol for invoked function --- src/compiler/checker.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 799fb10c09cf0..94a48f3306ed0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16757,7 +16757,9 @@ namespace ts { .map((arg, i) => ({ arg, symbol: getSymbolAtLocation(arg), i })) .filter(({ symbol }) => symbol && symbol.valueDeclaration === parameter) if (!usages.length) return - const sig = getSignatureFromDeclaration(getSymbolAtLocation(invocation.expression).valueDeclaration as FunctionLikeDeclaration) + const funcSymbol = getSymbolAtLocation(invocation.expression) + if (!funcSymbol) return + const sig = getSignatureFromDeclaration(funcSymbol.valueDeclaration as FunctionLikeDeclaration) const parameterTypes = sig.parameters.map(getTypeOfParameter) const argumentTypes = usages.map(({ i }) => parameterTypes[i]) usageTypes.splice(0, 0, ...argumentTypes); From db39b4335904d531ce6a124757a707e1453247b1 Mon Sep 17 00:00:00 2001 From: Asad Saeeduddin Date: Thu, 10 Aug 2017 02:58:31 -0400 Subject: [PATCH 06/20] Bail when invocation symbol's value declaration isn't a function declaration --- src/compiler/checker.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 94a48f3306ed0..aba5528b1888c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16758,8 +16758,9 @@ namespace ts { .filter(({ symbol }) => symbol && symbol.valueDeclaration === parameter) if (!usages.length) return const funcSymbol = getSymbolAtLocation(invocation.expression) - if (!funcSymbol) return - const sig = getSignatureFromDeclaration(funcSymbol.valueDeclaration as FunctionLikeDeclaration) + if (!funcSymbol || !isFunctionDeclaration(funcSymbol.valueDeclaration)) + return + const sig = getSignatureFromDeclaration(funcSymbol.valueDeclaration) const parameterTypes = sig.parameters.map(getTypeOfParameter) const argumentTypes = usages.map(({ i }) => parameterTypes[i]) usageTypes.splice(0, 0, ...argumentTypes); From 6095dfd5970cc5fbe4c9618adda5fd6b3c94da96 Mon Sep 17 00:00:00 2001 From: Asad Saeeduddin Date: Thu, 10 Aug 2017 02:59:51 -0400 Subject: [PATCH 07/20] Bail on rest parameters --- src/compiler/checker.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index aba5528b1888c..b7f4f888fb1af 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12806,9 +12806,7 @@ namespace ts { function getParameterTypeFromBody(parameter: ParameterDeclaration): Type { const func = parameter.parent - if (!func.body) { - return unknownType; - } + if (!func.body || isRestParameter(parameter)) return let type: Type; let types: Type[]; From 5bc6895c43f8c5af9808493a2dfb13513694b322 Mon Sep 17 00:00:00 2001 From: Asad Saeeduddin Date: Thu, 10 Aug 2017 03:56:37 -0400 Subject: [PATCH 08/20] Formatting, remove unnecessary declarations --- src/compiler/checker.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b7f4f888fb1af..380850eff4779 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12808,12 +12808,8 @@ namespace ts { const func = parameter.parent if (!func.body || isRestParameter(parameter)) return - let type: Type; - let types: Type[]; - types = checkAndAggregateParameterExpressionTypes(parameter) - type = types ? getWidenedType(getIntersectionType(types)) : undefined; - - return type; + const types = checkAndAggregateParameterExpressionTypes(parameter) + return types ? getWidenedType(getIntersectionType(types)) : undefined } // Return contextual type of parameter or undefined if no contextual type is available @@ -16754,7 +16750,8 @@ namespace ts { const usages = invocation.arguments .map((arg, i) => ({ arg, symbol: getSymbolAtLocation(arg), i })) .filter(({ symbol }) => symbol && symbol.valueDeclaration === parameter) - if (!usages.length) return + if (!usages.length) + return const funcSymbol = getSymbolAtLocation(invocation.expression) if (!funcSymbol || !isFunctionDeclaration(funcSymbol.valueDeclaration)) return From 9bec8c16bc9deabbf8f488833c923c2c20896849 Mon Sep 17 00:00:00 2001 From: Asad Saeeduddin Date: Thu, 10 Aug 2017 03:57:20 -0400 Subject: [PATCH 09/20] Update testcases --- tests/cases/compiler/parameterInference.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/cases/compiler/parameterInference.ts b/tests/cases/compiler/parameterInference.ts index 1a984d7275145..03c2a51a19df6 100644 --- a/tests/cases/compiler/parameterInference.ts +++ b/tests/cases/compiler/parameterInference.ts @@ -2,21 +2,23 @@ function foo(s) { Math.sqrt(s) } +// => function foo(s: number): void // CASE 2 declare function swapNumberString(n: string): number; declare function swapNumberString(n: number): string; -// Should have identical signature to swapNumberString function subs(s) { return swapNumberString(s); } +// => function subs(s: string): number +// NOTE: Still broken, needs to deal with overloads. Should have been inferred as: +// => (s: string) => number & (s: number) => string // CASE 3 function f(x: number){ return x; } -function g(x){ return x}; - -function h(x){ return f(x); }; +function g(x){ return f(x); }; +// => function g(x: number): number From 9905af7317f8a62d758a315130dc7e7c30daa0f8 Mon Sep 17 00:00:00 2001 From: Asad Saeeduddin Date: Thu, 10 Aug 2017 04:26:57 -0400 Subject: [PATCH 10/20] Loosen restriction on declarations to 'function-like' --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 380850eff4779..39361a51bfbfb 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16753,7 +16753,7 @@ namespace ts { if (!usages.length) return const funcSymbol = getSymbolAtLocation(invocation.expression) - if (!funcSymbol || !isFunctionDeclaration(funcSymbol.valueDeclaration)) + if (!funcSymbol || !isFunctionLike(funcSymbol.valueDeclaration)) return const sig = getSignatureFromDeclaration(funcSymbol.valueDeclaration) const parameterTypes = sig.parameters.map(getTypeOfParameter) From a5d65429836b90057378ab605107fd341fdf99ef Mon Sep 17 00:00:00 2001 From: Asad Saeeduddin Date: Thu, 10 Aug 2017 04:28:58 -0400 Subject: [PATCH 11/20] Accept baselines for new test --- .../baselines/reference/parameterInference.js | 45 +++++++++++++++ .../reference/parameterInference.symbols | 52 ++++++++++++++++++ .../reference/parameterInference.types | 55 +++++++++++++++++++ 3 files changed, 152 insertions(+) create mode 100644 tests/baselines/reference/parameterInference.js create mode 100644 tests/baselines/reference/parameterInference.symbols create mode 100644 tests/baselines/reference/parameterInference.types diff --git a/tests/baselines/reference/parameterInference.js b/tests/baselines/reference/parameterInference.js new file mode 100644 index 0000000000000..d1e6dd7b03aaa --- /dev/null +++ b/tests/baselines/reference/parameterInference.js @@ -0,0 +1,45 @@ +//// [parameterInference.ts] +// CASE 1 +function foo(s) { + Math.sqrt(s) +} +// => function foo(s: number): void + +// CASE 2 +declare function swapNumberString(n: string): number; +declare function swapNumberString(n: number): string; + +function subs(s) { + return swapNumberString(s); +} +// => function subs(s: string): number +// NOTE: Still broken, needs to deal with overloads. Should have been inferred as: +// => (s: string) => number & (s: number) => string + +// CASE 3 +function f(x: number){ + return x; +} + +function g(x){ return f(x); }; +// => function g(x: number): number + + +//// [parameterInference.js] +// CASE 1 +function foo(s) { + Math.sqrt(s); +} +function subs(s) { + return swapNumberString(s); +} +// => function subs(s: string): number +// NOTE: Still broken, needs to deal with overloads. Should have been inferred as: +// => (s: string) => number & (s: number) => string +// CASE 3 +function f(x) { + return x; +} +function g(x) { return f(x); } +; +// => function g(x: number): number diff --git a/tests/baselines/reference/parameterInference.symbols b/tests/baselines/reference/parameterInference.symbols new file mode 100644 index 0000000000000..b72dde481a959 --- /dev/null +++ b/tests/baselines/reference/parameterInference.symbols @@ -0,0 +1,52 @@ +=== tests/cases/compiler/parameterInference.ts === +// CASE 1 +function foo(s) { +>foo : Symbol(foo, Decl(parameterInference.ts, 0, 0)) +>s : Symbol(s, Decl(parameterInference.ts, 1, 13)) + + Math.sqrt(s) +>Math.sqrt : Symbol(Math.sqrt, Decl(lib.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) +>sqrt : Symbol(Math.sqrt, Decl(lib.d.ts, --, --)) +>s : Symbol(s, Decl(parameterInference.ts, 1, 13)) +} +// => function foo(s: number): void + +// CASE 2 +declare function swapNumberString(n: string): number; +>swapNumberString : Symbol(swapNumberString, Decl(parameterInference.ts, 3, 1), Decl(parameterInference.ts, 7, 53)) +>n : Symbol(n, Decl(parameterInference.ts, 7, 34)) + +declare function swapNumberString(n: number): string; +>swapNumberString : Symbol(swapNumberString, Decl(parameterInference.ts, 3, 1), Decl(parameterInference.ts, 7, 53)) +>n : Symbol(n, Decl(parameterInference.ts, 8, 34)) + +function subs(s) { +>subs : Symbol(subs, Decl(parameterInference.ts, 8, 53)) +>s : Symbol(s, Decl(parameterInference.ts, 10, 14)) + + return swapNumberString(s); +>swapNumberString : Symbol(swapNumberString, Decl(parameterInference.ts, 3, 1), Decl(parameterInference.ts, 7, 53)) +>s : Symbol(s, Decl(parameterInference.ts, 10, 14)) +} +// => function subs(s: string): number +// NOTE: Still broken, needs to deal with overloads. Should have been inferred as: +// => (s: string) => number & (s: number) => string + +// CASE 3 +function f(x: number){ +>f : Symbol(f, Decl(parameterInference.ts, 12, 1)) +>x : Symbol(x, Decl(parameterInference.ts, 18, 11)) + + return x; +>x : Symbol(x, Decl(parameterInference.ts, 18, 11)) +} + +function g(x){ return f(x); }; +>g : Symbol(g, Decl(parameterInference.ts, 20, 1)) +>x : Symbol(x, Decl(parameterInference.ts, 22, 11)) +>f : Symbol(f, Decl(parameterInference.ts, 12, 1)) +>x : Symbol(x, Decl(parameterInference.ts, 22, 11)) + +// => function g(x: number): number + diff --git a/tests/baselines/reference/parameterInference.types b/tests/baselines/reference/parameterInference.types new file mode 100644 index 0000000000000..4cfbebd0fe4ff --- /dev/null +++ b/tests/baselines/reference/parameterInference.types @@ -0,0 +1,55 @@ +=== tests/cases/compiler/parameterInference.ts === +// CASE 1 +function foo(s) { +>foo : (s: number) => void +>s : number + + Math.sqrt(s) +>Math.sqrt(s) : number +>Math.sqrt : (x: number) => number +>Math : Math +>sqrt : (x: number) => number +>s : number +} +// => function foo(s: number): void + +// CASE 2 +declare function swapNumberString(n: string): number; +>swapNumberString : { (n: string): number; (n: number): string; } +>n : string + +declare function swapNumberString(n: number): string; +>swapNumberString : { (n: string): number; (n: number): string; } +>n : number + +function subs(s) { +>subs : (s: string) => number +>s : string + + return swapNumberString(s); +>swapNumberString(s) : number +>swapNumberString : { (n: string): number; (n: number): string; } +>s : string +} +// => function subs(s: string): number +// NOTE: Still broken, needs to deal with overloads. Should have been inferred as: +// => (s: string) => number & (s: number) => string + +// CASE 3 +function f(x: number){ +>f : (x: number) => number +>x : number + + return x; +>x : number +} + +function g(x){ return f(x); }; +>g : (x: number) => number +>x : number +>f(x) : number +>f : (x: number) => number +>x : number + +// => function g(x: number): number + From 767274764505207c286eef04b5aa6ee1026d5891 Mon Sep 17 00:00:00 2001 From: Asad Saeeduddin Date: Thu, 10 Aug 2017 04:29:31 -0400 Subject: [PATCH 12/20] Accept change in test due to improved inference --- .../reference/controlFlowPropertyDeclarations.types | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/baselines/reference/controlFlowPropertyDeclarations.types b/tests/baselines/reference/controlFlowPropertyDeclarations.types index 286124ca346ae..71b23141644b9 100644 --- a/tests/baselines/reference/controlFlowPropertyDeclarations.types +++ b/tests/baselines/reference/controlFlowPropertyDeclarations.types @@ -196,8 +196,8 @@ function hyphenToCamelCase(string) { * Determines if the specified string consists entirely of whitespace. */ function isEmpty(string) { ->isEmpty : (string: any) => boolean ->string : any +>isEmpty : (string: string) => boolean +>string : string return !/[^\s]/.test(string); >!/[^\s]/.test(string) : boolean @@ -205,7 +205,7 @@ function isEmpty(string) { >/[^\s]/.test : (string: string) => boolean >/[^\s]/ : RegExp >test : (string: string) => boolean ->string : any +>string : string } /** @@ -216,15 +216,15 @@ function isEmpty(string) { * @return {boolean} */ function isConvertiblePixelValue(value) { ->isConvertiblePixelValue : (value: any) => boolean ->value : any +>isConvertiblePixelValue : (value: string) => boolean +>value : string return /^\d+px$/.test(value); >/^\d+px$/.test(value) : boolean >/^\d+px$/.test : (string: string) => boolean >/^\d+px$/ : RegExp >test : (string: string) => boolean ->value : any +>value : string } export class HTMLtoJSX { From 351f6659b3c5fc7d6d30012370e9c5cc43f1383a Mon Sep 17 00:00:00 2001 From: Asad Saeeduddin Date: Thu, 10 Aug 2017 04:48:29 -0400 Subject: [PATCH 13/20] Bail when argument and parameter lists are misaligned The mechanism used to match up arguments and parameters here is very brittle, and can easily be broken by e.g. rest parameters. For purposes of the POC we deal with this by simply discarding such a usage site. --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 39361a51bfbfb..b77d9b9094583 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16757,7 +16757,7 @@ namespace ts { return const sig = getSignatureFromDeclaration(funcSymbol.valueDeclaration) const parameterTypes = sig.parameters.map(getTypeOfParameter) - const argumentTypes = usages.map(({ i }) => parameterTypes[i]) + const argumentTypes = usages.map(({ i }) => parameterTypes[i]).filter(t => !!t) usageTypes.splice(0, 0, ...argumentTypes); }); return usageTypes.length ? usageTypes : undefined; From ef5f7bc4c3ac44a7606628cc97d133cfe0914eca Mon Sep 17 00:00:00 2001 From: Asad Saeeduddin Date: Thu, 10 Aug 2017 04:53:49 -0400 Subject: [PATCH 14/20] Fix lint --- src/compiler/checker.ts | 34 ++++++++++++++++------------------ src/compiler/utilities.ts | 2 +- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b77d9b9094583..0e2a98469f77d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4298,7 +4298,7 @@ namespace ts { type = getContextualThisParameterType(func); } else { - type = getContextuallyTypedParameterType(declaration) + type = getContextuallyTypedParameterType(declaration); } if (type) { return addOptionality(type, /*optional*/ declaration.questionToken && includeOptionality); @@ -4329,8 +4329,8 @@ namespace ts { // Important to do this *after* attempt has been made to resolve via initializer if (declaration.kind === SyntaxKind.Parameter) { - const inferredType = getParameterTypeFromBody(declaration) - if (inferredType) return inferredType + const inferredType = getParameterTypeFromBody(declaration); + if (inferredType) return inferredType; } // No type specified and nothing can be inferred @@ -12805,11 +12805,11 @@ namespace ts { } function getParameterTypeFromBody(parameter: ParameterDeclaration): Type { - const func = parameter.parent - if (!func.body || isRestParameter(parameter)) return + const func = parameter.parent; + if (!func.body || isRestParameter(parameter)) return; - const types = checkAndAggregateParameterExpressionTypes(parameter) - return types ? getWidenedType(getIntersectionType(types)) : undefined + const types = checkAndAggregateParameterExpressionTypes(parameter); + return types ? getWidenedType(getIntersectionType(types)) : undefined; } // Return contextual type of parameter or undefined if no contextual type is available @@ -16744,20 +16744,18 @@ namespace ts { } function checkAndAggregateParameterExpressionTypes(parameter: ParameterDeclaration): Type[] { - const func = parameter.parent - const usageTypes: Type[] = [] + const func = parameter.parent; + const usageTypes: Type[] = []; forEachInvocation(func.body, invocation => { const usages = invocation.arguments .map((arg, i) => ({ arg, symbol: getSymbolAtLocation(arg), i })) - .filter(({ symbol }) => symbol && symbol.valueDeclaration === parameter) - if (!usages.length) - return - const funcSymbol = getSymbolAtLocation(invocation.expression) - if (!funcSymbol || !isFunctionLike(funcSymbol.valueDeclaration)) - return - const sig = getSignatureFromDeclaration(funcSymbol.valueDeclaration) - const parameterTypes = sig.parameters.map(getTypeOfParameter) - const argumentTypes = usages.map(({ i }) => parameterTypes[i]).filter(t => !!t) + .filter(({ symbol }) => symbol && symbol.valueDeclaration === parameter); + if (!usages.length) return; + const funcSymbol = getSymbolAtLocation(invocation.expression); + if (!funcSymbol || !isFunctionLike(funcSymbol.valueDeclaration)) return; + const sig = getSignatureFromDeclaration(funcSymbol.valueDeclaration); + const parameterTypes = sig.parameters.map(getTypeOfParameter); + const argumentTypes = usages.map(({ i }) => parameterTypes[i]).filter(t => !!t); usageTypes.splice(0, 0, ...argumentTypes); }); return usageTypes.length ? usageTypes : undefined; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index edfca2dbe11d3..50e44b777b1d3 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -764,7 +764,7 @@ namespace ts { function traverse(node: Node): T { switch (node.kind) { case SyntaxKind.CallExpression: - return visitor(node) + return visitor(node); default: return forEachChild(node, traverse); } From 15b6c0c664824ff1274b200af2a9c2d811dde4a3 Mon Sep 17 00:00:00 2001 From: Asad Saeeduddin Date: Thu, 10 Aug 2017 06:27:05 -0400 Subject: [PATCH 15/20] Recursively scan function arguments --- src/compiler/checker.ts | 14 +++++++++++++- tests/cases/compiler/parameterInference.ts | 7 +++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0e2a98469f77d..5a5e6deb91de4 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16746,7 +16746,18 @@ namespace ts { function checkAndAggregateParameterExpressionTypes(parameter: ParameterDeclaration): Type[] { const func = parameter.parent; const usageTypes: Type[] = []; - forEachInvocation(func.body, invocation => { + const invocations: CallExpression[] = []; + + function seekInvocations(f: FunctionBody) { + forEachInvocation(f, invocation => { + invocations.push(invocation); + invocation.arguments.filter(isFunctionExpressionOrArrowFunction).forEach(arg => seekInvocations(arg.body)); + }) + } + + seekInvocations(func.body) + + invocations.forEach(invocation => { const usages = invocation.arguments .map((arg, i) => ({ arg, symbol: getSymbolAtLocation(arg), i })) .filter(({ symbol }) => symbol && symbol.valueDeclaration === parameter); @@ -16758,6 +16769,7 @@ namespace ts { const argumentTypes = usages.map(({ i }) => parameterTypes[i]).filter(t => !!t); usageTypes.splice(0, 0, ...argumentTypes); }); + return usageTypes.length ? usageTypes : undefined; } diff --git a/tests/cases/compiler/parameterInference.ts b/tests/cases/compiler/parameterInference.ts index 03c2a51a19df6..1a558c9ae89c8 100644 --- a/tests/cases/compiler/parameterInference.ts +++ b/tests/cases/compiler/parameterInference.ts @@ -22,3 +22,10 @@ function f(x: number){ function g(x){ return f(x); }; // => function g(x: number): number +// CASE 4 +declare function f4(g: Function) +function g4(x) { + f4(() => { + Math.sqrt(x) + }) +} From d85a212d0d5913cc9d30bda7c5cc1b165759f06c Mon Sep 17 00:00:00 2001 From: Asad Saeeduddin Date: Thu, 10 Aug 2017 06:27:36 -0400 Subject: [PATCH 16/20] Rename --- tests/cases/compiler/parameterInference.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/cases/compiler/parameterInference.ts b/tests/cases/compiler/parameterInference.ts index 1a558c9ae89c8..006c57a69110b 100644 --- a/tests/cases/compiler/parameterInference.ts +++ b/tests/cases/compiler/parameterInference.ts @@ -16,12 +16,13 @@ function subs(s) { // => (s: string) => number & (s: number) => string // CASE 3 -function f(x: number){ +function f3(x: number){ return x; } -function g(x){ return f(x); }; -// => function g(x: number): number +function g3(x){ return f3(x); }; +// => function g3(x: number): number + // CASE 4 declare function f4(g: Function) function g4(x) { From cd788ee2b9c127ca669f1a4d30c057055ce33bd3 Mon Sep 17 00:00:00 2001 From: Asad Saeeduddin Date: Thu, 10 Aug 2017 06:27:51 -0400 Subject: [PATCH 17/20] Accept baselines --- .../baselines/reference/parameterInference.js | 24 ++++++++--- .../reference/parameterInference.symbols | 42 ++++++++++++++----- .../reference/parameterInference.types | 39 +++++++++++++---- 3 files changed, 82 insertions(+), 23 deletions(-) diff --git a/tests/baselines/reference/parameterInference.js b/tests/baselines/reference/parameterInference.js index d1e6dd7b03aaa..8859effa561fe 100644 --- a/tests/baselines/reference/parameterInference.js +++ b/tests/baselines/reference/parameterInference.js @@ -17,12 +17,20 @@ function subs(s) { // => (s: string) => number & (s: number) => string // CASE 3 -function f(x: number){ +function f3(x: number){ return x; } -function g(x){ return f(x); }; -// => function g(x: number): number +function g3(x){ return f3(x); }; +// => function g3(x: number): number + +// CASE 4 +declare function f4(g: Function) +function g4(x) { + f4(() => { + Math.sqrt(x) + }) +} //// [parameterInference.js] @@ -37,9 +45,13 @@ function subs(s) { // NOTE: Still broken, needs to deal with overloads. Should have been inferred as: // => (s: string) => number & (s: number) => string // CASE 3 -function f(x) { +function f3(x) { return x; } -function g(x) { return f(x); } +function g3(x) { return f3(x); } ; -// => function g(x: number): number +function g4(x) { + f4(function () { + Math.sqrt(x); + }); +} diff --git a/tests/baselines/reference/parameterInference.symbols b/tests/baselines/reference/parameterInference.symbols index b72dde481a959..2124c29a145da 100644 --- a/tests/baselines/reference/parameterInference.symbols +++ b/tests/baselines/reference/parameterInference.symbols @@ -34,19 +34,41 @@ function subs(s) { // => (s: string) => number & (s: number) => string // CASE 3 -function f(x: number){ ->f : Symbol(f, Decl(parameterInference.ts, 12, 1)) ->x : Symbol(x, Decl(parameterInference.ts, 18, 11)) +function f3(x: number){ +>f3 : Symbol(f3, Decl(parameterInference.ts, 12, 1)) +>x : Symbol(x, Decl(parameterInference.ts, 18, 12)) return x; ->x : Symbol(x, Decl(parameterInference.ts, 18, 11)) +>x : Symbol(x, Decl(parameterInference.ts, 18, 12)) } -function g(x){ return f(x); }; ->g : Symbol(g, Decl(parameterInference.ts, 20, 1)) ->x : Symbol(x, Decl(parameterInference.ts, 22, 11)) ->f : Symbol(f, Decl(parameterInference.ts, 12, 1)) ->x : Symbol(x, Decl(parameterInference.ts, 22, 11)) +function g3(x){ return f3(x); }; +>g3 : Symbol(g3, Decl(parameterInference.ts, 20, 1)) +>x : Symbol(x, Decl(parameterInference.ts, 22, 12)) +>f3 : Symbol(f3, Decl(parameterInference.ts, 12, 1)) +>x : Symbol(x, Decl(parameterInference.ts, 22, 12)) -// => function g(x: number): number +// => function g3(x: number): number + +// CASE 4 +declare function f4(g: Function) +>f4 : Symbol(f4, Decl(parameterInference.ts, 22, 32)) +>g : Symbol(g, Decl(parameterInference.ts, 26, 20)) +>Function : Symbol(Function, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) + +function g4(x) { +>g4 : Symbol(g4, Decl(parameterInference.ts, 26, 32)) +>x : Symbol(x, Decl(parameterInference.ts, 27, 12)) + + f4(() => { +>f4 : Symbol(f4, Decl(parameterInference.ts, 22, 32)) + + Math.sqrt(x) +>Math.sqrt : Symbol(Math.sqrt, Decl(lib.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) +>sqrt : Symbol(Math.sqrt, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(parameterInference.ts, 27, 12)) + + }) +} diff --git a/tests/baselines/reference/parameterInference.types b/tests/baselines/reference/parameterInference.types index 4cfbebd0fe4ff..aef600dc9db92 100644 --- a/tests/baselines/reference/parameterInference.types +++ b/tests/baselines/reference/parameterInference.types @@ -36,20 +36,45 @@ function subs(s) { // => (s: string) => number & (s: number) => string // CASE 3 -function f(x: number){ ->f : (x: number) => number +function f3(x: number){ +>f3 : (x: number) => number >x : number return x; >x : number } -function g(x){ return f(x); }; ->g : (x: number) => number +function g3(x){ return f3(x); }; +>g3 : (x: number) => number >x : number ->f(x) : number ->f : (x: number) => number +>f3(x) : number +>f3 : (x: number) => number >x : number -// => function g(x: number): number +// => function g3(x: number): number + +// CASE 4 +declare function f4(g: Function) +>f4 : (g: Function) => any +>g : Function +>Function : Function + +function g4(x) { +>g4 : (x: number) => void +>x : number + + f4(() => { +>f4(() => { Math.sqrt(x) }) : any +>f4 : (g: Function) => any +>() => { Math.sqrt(x) } : () => void + + Math.sqrt(x) +>Math.sqrt(x) : number +>Math.sqrt : (x: number) => number +>Math : Math +>sqrt : (x: number) => number +>x : number + + }) +} From f2853ae866f9ab04180639533907fcec69ca36c4 Mon Sep 17 00:00:00 2001 From: Asad Saeeduddin Date: Thu, 10 Aug 2017 07:08:17 -0400 Subject: [PATCH 18/20] Fix lint --- src/compiler/checker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5a5e6deb91de4..5befe3b1086f0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16752,10 +16752,10 @@ namespace ts { forEachInvocation(f, invocation => { invocations.push(invocation); invocation.arguments.filter(isFunctionExpressionOrArrowFunction).forEach(arg => seekInvocations(arg.body)); - }) + }); } - seekInvocations(func.body) + seekInvocations(func.body); invocations.forEach(invocation => { const usages = invocation.arguments From aff19fc5df58066e15fcee179204c3f3928315d2 Mon Sep 17 00:00:00 2001 From: Asad Saeeduddin Date: Fri, 11 Aug 2017 12:49:34 -0400 Subject: [PATCH 19/20] Add comments to describe test cases --- tests/cases/compiler/parameterInference.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/cases/compiler/parameterInference.ts b/tests/cases/compiler/parameterInference.ts index 006c57a69110b..972a99dbb130a 100644 --- a/tests/cases/compiler/parameterInference.ts +++ b/tests/cases/compiler/parameterInference.ts @@ -1,10 +1,10 @@ -// CASE 1 +// CASE 1 - Propagation of parameter types from invocations function foo(s) { Math.sqrt(s) } // => function foo(s: number): void -// CASE 2 +// CASE 2 - Inference from invocation of overloaded functions declare function swapNumberString(n: string): number; declare function swapNumberString(n: number): string; @@ -15,7 +15,7 @@ function subs(s) { // NOTE: Still broken, needs to deal with overloads. Should have been inferred as: // => (s: string) => number & (s: number) => string -// CASE 3 +// CASE 3 - Propagation of explicitly annotated parameter types function f3(x: number){ return x; } @@ -23,7 +23,7 @@ function f3(x: number){ function g3(x){ return f3(x); }; // => function g3(x: number): number -// CASE 4 +// CASE 4 - Inference from invocation sites within function expressions passed as arguments declare function f4(g: Function) function g4(x) { f4(() => { From 818b9c9ce154afcf8d43770bf2315783cdfc2037 Mon Sep 17 00:00:00 2001 From: Asad Saeeduddin Date: Mon, 14 Aug 2017 16:18:17 -0400 Subject: [PATCH 20/20] Expand testcases --- tests/cases/compiler/parameterInference.ts | 56 ++++++++++++++++++---- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/tests/cases/compiler/parameterInference.ts b/tests/cases/compiler/parameterInference.ts index 972a99dbb130a..b3f3ab21a2c68 100644 --- a/tests/cases/compiler/parameterInference.ts +++ b/tests/cases/compiler/parameterInference.ts @@ -1,32 +1,68 @@ // CASE 1 - Propagation of parameter types from invocations -function foo(s) { +function f1(s) { Math.sqrt(s) } -// => function foo(s: number): void +// => function f1(s: number): void // CASE 2 - Inference from invocation of overloaded functions declare function swapNumberString(n: string): number; declare function swapNumberString(n: number): string; -function subs(s) { +function f2(s) { return swapNumberString(s); } -// => function subs(s: string): number -// NOTE: Still broken, needs to deal with overloads. Should have been inferred as: +// => function f2(s: string): number +// TODO: Broken, needs to deal with overloads. Should have been inferred as: // => (s: string) => number & (s: number) => string // CASE 3 - Propagation of explicitly annotated parameter types -function f3(x: number){ - return x; -} +declare function f3(x: number) -function g3(x){ return f3(x); }; -// => function g3(x: number): number +function g3(x){ + return f3(x); +} +// => function g3(x: number): any // CASE 4 - Inference from invocation sites within function expressions passed as arguments declare function f4(g: Function) + function g4(x) { f4(() => { Math.sqrt(x) }) } +// => function g4(x: number): void + +// CASE 5 - Ensure termination for recursive invocations +function f5(s) { + f5(s) +} +// => function f5(s: any): void + +// CASE 6 - Inference as intersection from multiple usages +function f6(x) { + Math.sqrt(x) + "".indexOf(x) +} +// => function f6(x: string & number): void + +// CASE 7 - Inference as overloaded function from control-flow discriminated usages +declare function isNumber(x: T): x is T & number + +function f7(x) { + return isNumber(x) ? Math.sqrt(x) : "".indexOf(x) +} +// function f7(x: string & number & T): number +// TODO: Broken, generic type parameter leaks and x is inferred too conservatively (string & number) +// Should be: +// => (s: string) => number & (s: number) => number + +// CASE 8 - Inference by substitution of constraints for generic functions +declare function generic(t: T) + +function f8(x) { + generic(x) +} +// function f8(x: T): void +// TODO: Broken, type parameter leaks. Should be inferred as: +// function f8(x: {}): void