From 736bdc2fde9158f28d8e9961c920bcfa31f6d410 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Wed, 9 Aug 2017 10:26:50 -0700 Subject: [PATCH 1/3] A function should be context-sensitive if its return expression is --- src/compiler/checker.ts | 21 ++++++----- ...ntextualTypingFunctionReturningFunction.js | 19 ++++++++++ ...ualTypingFunctionReturningFunction.symbols | 31 ++++++++++++++++ ...xtualTypingFunctionReturningFunction.types | 36 +++++++++++++++++++ ...textualTypingFunctionReturningFunction2.js | 9 +++++ ...alTypingFunctionReturningFunction2.symbols | 15 ++++++++ ...tualTypingFunctionReturningFunction2.types | 18 ++++++++++ ...lTypingWithFixedTypeParameters1.errors.txt | 10 +++--- ...ontextualTypingWithFixedTypeParameters1.js | 4 +-- ...ntextualTypingFunctionReturningFunction.ts | 11 ++++++ ...textualTypingFunctionReturningFunction2.ts | 4 +++ ...ontextualTypingWithFixedTypeParameters1.ts | 2 +- 12 files changed, 163 insertions(+), 17 deletions(-) create mode 100644 tests/baselines/reference/contextualTypingFunctionReturningFunction.js create mode 100644 tests/baselines/reference/contextualTypingFunctionReturningFunction.symbols create mode 100644 tests/baselines/reference/contextualTypingFunctionReturningFunction.types create mode 100644 tests/baselines/reference/contextualTypingFunctionReturningFunction2.js create mode 100644 tests/baselines/reference/contextualTypingFunctionReturningFunction2.symbols create mode 100644 tests/baselines/reference/contextualTypingFunctionReturningFunction2.types create mode 100644 tests/cases/compiler/contextualTypingFunctionReturningFunction.ts create mode 100644 tests/cases/compiler/contextualTypingFunctionReturningFunction2.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1d1d16b7ee11e..8ef82258ba635 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8405,16 +8405,19 @@ namespace ts { if (forEach(node.parameters, p => !getEffectiveTypeAnnotationNode(p))) { return true; } - // For arrow functions we now know we're not context sensitive. - if (node.kind === SyntaxKind.ArrowFunction) { - return false; + if (node.kind !== SyntaxKind.ArrowFunction) { + // If the first parameter is not an explicit 'this' parameter, then the function has + // an implicit 'this' parameter which is subject to contextual typing. Otherwise we + // know that all parameters (including 'this') have type annotations and nothing is + // subject to contextual typing. + const parameter = firstOrUndefined(node.parameters); + if (!(parameter && parameterIsThisKeyword(parameter))) { + return true; + } } - // If the first parameter is not an explicit 'this' parameter, then the function has - // an implicit 'this' parameter which is subject to contextual typing. Otherwise we - // know that all parameters (including 'this') have type annotations and nothing is - // subject to contextual typing. - const parameter = firstOrUndefined(node.parameters); - return !(parameter && parameterIsThisKeyword(parameter)); + + // TODO(anhans): A block should be context-sensitive if it has a context-sentitive return value. + return node.body.kind === SyntaxKind.Block ? false : isContextSensitive(node.body); } function isContextSensitiveFunctionOrObjectLiteralMethod(func: Node): func is FunctionExpression | ArrowFunction | MethodDeclaration { diff --git a/tests/baselines/reference/contextualTypingFunctionReturningFunction.js b/tests/baselines/reference/contextualTypingFunctionReturningFunction.js new file mode 100644 index 0000000000000..7c84623578fa5 --- /dev/null +++ b/tests/baselines/reference/contextualTypingFunctionReturningFunction.js @@ -0,0 +1,19 @@ +//// [contextualTypingFunctionReturningFunction.ts] +interface I { + a(s: string): void; + b(): (n: number) => void; +} + +declare function f(i: I): void; + +f({ + a: s => {}, + b: () => n => {}, +}); + + +//// [contextualTypingFunctionReturningFunction.js] +f({ + a: function (s) { }, + b: function () { return function (n) { }; } +}); diff --git a/tests/baselines/reference/contextualTypingFunctionReturningFunction.symbols b/tests/baselines/reference/contextualTypingFunctionReturningFunction.symbols new file mode 100644 index 0000000000000..7fab00ceda4f2 --- /dev/null +++ b/tests/baselines/reference/contextualTypingFunctionReturningFunction.symbols @@ -0,0 +1,31 @@ +=== tests/cases/compiler/contextualTypingFunctionReturningFunction.ts === +interface I { +>I : Symbol(I, Decl(contextualTypingFunctionReturningFunction.ts, 0, 0)) + + a(s: string): void; +>a : Symbol(I.a, Decl(contextualTypingFunctionReturningFunction.ts, 0, 13)) +>s : Symbol(s, Decl(contextualTypingFunctionReturningFunction.ts, 1, 6)) + + b(): (n: number) => void; +>b : Symbol(I.b, Decl(contextualTypingFunctionReturningFunction.ts, 1, 23)) +>n : Symbol(n, Decl(contextualTypingFunctionReturningFunction.ts, 2, 10)) +} + +declare function f(i: I): void; +>f : Symbol(f, Decl(contextualTypingFunctionReturningFunction.ts, 3, 1)) +>i : Symbol(i, Decl(contextualTypingFunctionReturningFunction.ts, 5, 19)) +>I : Symbol(I, Decl(contextualTypingFunctionReturningFunction.ts, 0, 0)) + +f({ +>f : Symbol(f, Decl(contextualTypingFunctionReturningFunction.ts, 3, 1)) + + a: s => {}, +>a : Symbol(a, Decl(contextualTypingFunctionReturningFunction.ts, 7, 3)) +>s : Symbol(s, Decl(contextualTypingFunctionReturningFunction.ts, 8, 6)) + + b: () => n => {}, +>b : Symbol(b, Decl(contextualTypingFunctionReturningFunction.ts, 8, 15)) +>n : Symbol(n, Decl(contextualTypingFunctionReturningFunction.ts, 9, 12)) + +}); + diff --git a/tests/baselines/reference/contextualTypingFunctionReturningFunction.types b/tests/baselines/reference/contextualTypingFunctionReturningFunction.types new file mode 100644 index 0000000000000..5ab89419f4bb4 --- /dev/null +++ b/tests/baselines/reference/contextualTypingFunctionReturningFunction.types @@ -0,0 +1,36 @@ +=== tests/cases/compiler/contextualTypingFunctionReturningFunction.ts === +interface I { +>I : I + + a(s: string): void; +>a : (s: string) => void +>s : string + + b(): (n: number) => void; +>b : () => (n: number) => void +>n : number +} + +declare function f(i: I): void; +>f : (i: I) => void +>i : I +>I : I + +f({ +>f({ a: s => {}, b: () => n => {},}) : void +>f : (i: I) => void +>{ a: s => {}, b: () => n => {},} : { a: (s: string) => void; b: () => (n: number) => void; } + + a: s => {}, +>a : (s: string) => void +>s => {} : (s: string) => void +>s : string + + b: () => n => {}, +>b : () => (n: number) => void +>() => n => {} : () => (n: number) => void +>n => {} : (n: number) => void +>n : number + +}); + diff --git a/tests/baselines/reference/contextualTypingFunctionReturningFunction2.js b/tests/baselines/reference/contextualTypingFunctionReturningFunction2.js new file mode 100644 index 0000000000000..88308dbe2e60d --- /dev/null +++ b/tests/baselines/reference/contextualTypingFunctionReturningFunction2.js @@ -0,0 +1,9 @@ +//// [contextualTypingFunctionReturningFunction2.ts] +declare function f(n: number): void; +declare function f(cb: () => (n: number) => number): void; + +f(() => n => n); + + +//// [contextualTypingFunctionReturningFunction2.js] +f(function () { return function (n) { return n; }; }); diff --git a/tests/baselines/reference/contextualTypingFunctionReturningFunction2.symbols b/tests/baselines/reference/contextualTypingFunctionReturningFunction2.symbols new file mode 100644 index 0000000000000..9071bfc23d857 --- /dev/null +++ b/tests/baselines/reference/contextualTypingFunctionReturningFunction2.symbols @@ -0,0 +1,15 @@ +=== tests/cases/compiler/contextualTypingFunctionReturningFunction2.ts === +declare function f(n: number): void; +>f : Symbol(f, Decl(contextualTypingFunctionReturningFunction2.ts, 0, 0), Decl(contextualTypingFunctionReturningFunction2.ts, 0, 36)) +>n : Symbol(n, Decl(contextualTypingFunctionReturningFunction2.ts, 0, 19)) + +declare function f(cb: () => (n: number) => number): void; +>f : Symbol(f, Decl(contextualTypingFunctionReturningFunction2.ts, 0, 0), Decl(contextualTypingFunctionReturningFunction2.ts, 0, 36)) +>cb : Symbol(cb, Decl(contextualTypingFunctionReturningFunction2.ts, 1, 19)) +>n : Symbol(n, Decl(contextualTypingFunctionReturningFunction2.ts, 1, 30)) + +f(() => n => n); +>f : Symbol(f, Decl(contextualTypingFunctionReturningFunction2.ts, 0, 0), Decl(contextualTypingFunctionReturningFunction2.ts, 0, 36)) +>n : Symbol(n, Decl(contextualTypingFunctionReturningFunction2.ts, 3, 7)) +>n : Symbol(n, Decl(contextualTypingFunctionReturningFunction2.ts, 3, 7)) + diff --git a/tests/baselines/reference/contextualTypingFunctionReturningFunction2.types b/tests/baselines/reference/contextualTypingFunctionReturningFunction2.types new file mode 100644 index 0000000000000..14126a0e0cdd8 --- /dev/null +++ b/tests/baselines/reference/contextualTypingFunctionReturningFunction2.types @@ -0,0 +1,18 @@ +=== tests/cases/compiler/contextualTypingFunctionReturningFunction2.ts === +declare function f(n: number): void; +>f : { (n: number): void; (cb: () => (n: number) => number): void; } +>n : number + +declare function f(cb: () => (n: number) => number): void; +>f : { (n: number): void; (cb: () => (n: number) => number): void; } +>cb : () => (n: number) => number +>n : number + +f(() => n => n); +>f(() => n => n) : void +>f : { (n: number): void; (cb: () => (n: number) => number): void; } +>() => n => n : () => (n: number) => number +>n => n : (n: number) => number +>n : number +>n : number + diff --git a/tests/baselines/reference/contextualTypingWithFixedTypeParameters1.errors.txt b/tests/baselines/reference/contextualTypingWithFixedTypeParameters1.errors.txt index 95b3c66c264a9..0fe1e69d0799c 100644 --- a/tests/baselines/reference/contextualTypingWithFixedTypeParameters1.errors.txt +++ b/tests/baselines/reference/contextualTypingWithFixedTypeParameters1.errors.txt @@ -1,15 +1,15 @@ tests/cases/compiler/contextualTypingWithFixedTypeParameters1.ts(2,22): error TS2339: Property 'foo' does not exist on type 'string'. -tests/cases/compiler/contextualTypingWithFixedTypeParameters1.ts(3,32): error TS2339: Property 'foo' does not exist on type 'string'. -tests/cases/compiler/contextualTypingWithFixedTypeParameters1.ts(3,38): error TS2345: Argument of type '1' is not assignable to parameter of type 'string'. +tests/cases/compiler/contextualTypingWithFixedTypeParameters1.ts(3,32): error TS2339: Property 'foo' does not exist on type '""'. +tests/cases/compiler/contextualTypingWithFixedTypeParameters1.ts(3,38): error TS2345: Argument of type '1' is not assignable to parameter of type '""'. ==== tests/cases/compiler/contextualTypingWithFixedTypeParameters1.ts (3 errors) ==== var f10: (x: T, b: () => (a: T) => void, y: T) => T; - f10('', () => a => a.foo, ''); // a is string + f10('', () => a => a.foo, ''); // a is "" ~~~ !!! error TS2339: Property 'foo' does not exist on type 'string'. var r9 = f10('', () => (a => a.foo), 1); // error ~~~ -!!! error TS2339: Property 'foo' does not exist on type 'string'. +!!! error TS2339: Property 'foo' does not exist on type '""'. ~ -!!! error TS2345: Argument of type '1' is not assignable to parameter of type 'string'. \ No newline at end of file +!!! error TS2345: Argument of type '1' is not assignable to parameter of type '""'. \ No newline at end of file diff --git a/tests/baselines/reference/contextualTypingWithFixedTypeParameters1.js b/tests/baselines/reference/contextualTypingWithFixedTypeParameters1.js index 55f7580b3f880..b60a7c986abc9 100644 --- a/tests/baselines/reference/contextualTypingWithFixedTypeParameters1.js +++ b/tests/baselines/reference/contextualTypingWithFixedTypeParameters1.js @@ -1,9 +1,9 @@ //// [contextualTypingWithFixedTypeParameters1.ts] var f10: (x: T, b: () => (a: T) => void, y: T) => T; -f10('', () => a => a.foo, ''); // a is string +f10('', () => a => a.foo, ''); // a is "" var r9 = f10('', () => (a => a.foo), 1); // error //// [contextualTypingWithFixedTypeParameters1.js] var f10; -f10('', function () { return function (a) { return a.foo; }; }, ''); // a is string +f10('', function () { return function (a) { return a.foo; }; }, ''); // a is "" var r9 = f10('', function () { return (function (a) { return a.foo; }); }, 1); // error diff --git a/tests/cases/compiler/contextualTypingFunctionReturningFunction.ts b/tests/cases/compiler/contextualTypingFunctionReturningFunction.ts new file mode 100644 index 0000000000000..22c1d6f47796d --- /dev/null +++ b/tests/cases/compiler/contextualTypingFunctionReturningFunction.ts @@ -0,0 +1,11 @@ +interface I { + a(s: string): void; + b(): (n: number) => void; +} + +declare function f(i: I): void; + +f({ + a: s => {}, + b: () => n => {}, +}); diff --git a/tests/cases/compiler/contextualTypingFunctionReturningFunction2.ts b/tests/cases/compiler/contextualTypingFunctionReturningFunction2.ts new file mode 100644 index 0000000000000..9bcd34e4c0f0f --- /dev/null +++ b/tests/cases/compiler/contextualTypingFunctionReturningFunction2.ts @@ -0,0 +1,4 @@ +declare function f(n: number): void; +declare function f(cb: () => (n: number) => number): void; + +f(() => n => n); diff --git a/tests/cases/compiler/contextualTypingWithFixedTypeParameters1.ts b/tests/cases/compiler/contextualTypingWithFixedTypeParameters1.ts index 1ea001057a98f..451e67092bb7a 100644 --- a/tests/cases/compiler/contextualTypingWithFixedTypeParameters1.ts +++ b/tests/cases/compiler/contextualTypingWithFixedTypeParameters1.ts @@ -1,3 +1,3 @@ var f10: (x: T, b: () => (a: T) => void, y: T) => T; -f10('', () => a => a.foo, ''); // a is string +f10('', () => a => a.foo, ''); // a is "" var r9 = f10('', () => (a => a.foo), 1); // error \ No newline at end of file From e5b513f00e69cd5914fd754d5cc33e47e60c5b46 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Wed, 9 Aug 2017 14:21:25 -0700 Subject: [PATCH 2/3] Remove outdated comment --- 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 8ef82258ba635..ebd32bafcba99 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8407,9 +8407,7 @@ namespace ts { } if (node.kind !== SyntaxKind.ArrowFunction) { // If the first parameter is not an explicit 'this' parameter, then the function has - // an implicit 'this' parameter which is subject to contextual typing. Otherwise we - // know that all parameters (including 'this') have type annotations and nothing is - // subject to contextual typing. + // an implicit 'this' parameter which is subject to contextual typing. const parameter = firstOrUndefined(node.parameters); if (!(parameter && parameterIsThisKeyword(parameter))) { return true; From bed3f51a7d4209d6d4603fb90dda27c6bda781ab Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Wed, 9 Aug 2017 14:21:46 -0700 Subject: [PATCH 3/3] Fix typo --- 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 ebd32bafcba99..6d7395d9395fb 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8414,7 +8414,7 @@ namespace ts { } } - // TODO(anhans): A block should be context-sensitive if it has a context-sentitive return value. + // TODO(anhans): A block should be context-sensitive if it has a context-sensitive return value. return node.body.kind === SyntaxKind.Block ? false : isContextSensitive(node.body); }