From 2114f4ad05c0282fa311a4e3a0c78b6a55aa6b9c Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 25 Jul 2018 11:00:56 -0700 Subject: [PATCH 1/3] Still generate signatures in SkipContextSensitive mode just to match on return types --- src/compiler/checker.ts | 9 ++++ ...InferenceLowerPriorityThanGoodInference.js | 21 ++++++++ ...enceLowerPriorityThanGoodInference.symbols | 42 ++++++++++++++++ ...erenceLowerPriorityThanGoodInference.types | 49 +++++++++++++++++++ ...InferenceLowerPriorityThanGoodInference.ts | 13 +++++ 5 files changed, 134 insertions(+) create mode 100644 tests/baselines/reference/badInferenceLowerPriorityThanGoodInference.js create mode 100644 tests/baselines/reference/badInferenceLowerPriorityThanGoodInference.symbols create mode 100644 tests/baselines/reference/badInferenceLowerPriorityThanGoodInference.types create mode 100644 tests/cases/compiler/badInferenceLowerPriorityThanGoodInference.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b476b80e4a566..ebb05967f75dc 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10241,7 +10241,10 @@ namespace ts { return true; } } + return hasContextSensitiveReturnExpression(node); + } + function hasContextSensitiveReturnExpression(node: FunctionLikeDeclaration) { // TODO(anhans): A block should be context-sensitive if it has a context-sensitive return value. const body = node.body!; return body.kind === SyntaxKind.Block ? false : isContextSensitive(body); @@ -20543,6 +20546,12 @@ namespace ts { // The identityMapper object is used to indicate that function expressions are wildcards if (checkMode === CheckMode.SkipContextSensitive && isContextSensitive(node)) { + // Skip parameters, return signature with return type that retains noncontextual parts so inferences can still be drawn in an early stage + if (!getEffectiveReturnTypeNode(node) && hasContextSensitiveReturnExpression(node)) { + const returnType = getReturnTypeFromBody(node, checkMode); + const singleReturnSignature = createSignature(undefined, undefined, undefined, emptyArray, returnType, /*resolvedTypePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); + return createAnonymousType(node.symbol, emptySymbols, [singleReturnSignature], emptyArray, undefined, undefined); + } return anyFunctionType; } diff --git a/tests/baselines/reference/badInferenceLowerPriorityThanGoodInference.js b/tests/baselines/reference/badInferenceLowerPriorityThanGoodInference.js new file mode 100644 index 0000000000000..de98d754bb079 --- /dev/null +++ b/tests/baselines/reference/badInferenceLowerPriorityThanGoodInference.js @@ -0,0 +1,21 @@ +//// [badInferenceLowerPriorityThanGoodInference.ts] +interface Foo { + a: A; + b: (x: A) => void; +} + +declare function canYouInferThis(fn: () => Foo): A; + +const result = canYouInferThis(() => ({ + a: { BLAH: 33 }, + b: x => { } +})) + +result.BLAH; + +//// [badInferenceLowerPriorityThanGoodInference.js] +var result = canYouInferThis(function () { return ({ + a: { BLAH: 33 }, + b: function (x) { } +}); }); +result.BLAH; diff --git a/tests/baselines/reference/badInferenceLowerPriorityThanGoodInference.symbols b/tests/baselines/reference/badInferenceLowerPriorityThanGoodInference.symbols new file mode 100644 index 0000000000000..d03ac3b800a3f --- /dev/null +++ b/tests/baselines/reference/badInferenceLowerPriorityThanGoodInference.symbols @@ -0,0 +1,42 @@ +=== tests/cases/compiler/badInferenceLowerPriorityThanGoodInference.ts === +interface Foo { +>Foo : Symbol(Foo, Decl(badInferenceLowerPriorityThanGoodInference.ts, 0, 0)) +>A : Symbol(A, Decl(badInferenceLowerPriorityThanGoodInference.ts, 0, 14)) + + a: A; +>a : Symbol(Foo.a, Decl(badInferenceLowerPriorityThanGoodInference.ts, 0, 18)) +>A : Symbol(A, Decl(badInferenceLowerPriorityThanGoodInference.ts, 0, 14)) + + b: (x: A) => void; +>b : Symbol(Foo.b, Decl(badInferenceLowerPriorityThanGoodInference.ts, 1, 9)) +>x : Symbol(x, Decl(badInferenceLowerPriorityThanGoodInference.ts, 2, 8)) +>A : Symbol(A, Decl(badInferenceLowerPriorityThanGoodInference.ts, 0, 14)) +} + +declare function canYouInferThis(fn: () => Foo): A; +>canYouInferThis : Symbol(canYouInferThis, Decl(badInferenceLowerPriorityThanGoodInference.ts, 3, 1)) +>A : Symbol(A, Decl(badInferenceLowerPriorityThanGoodInference.ts, 5, 33)) +>fn : Symbol(fn, Decl(badInferenceLowerPriorityThanGoodInference.ts, 5, 36)) +>Foo : Symbol(Foo, Decl(badInferenceLowerPriorityThanGoodInference.ts, 0, 0)) +>A : Symbol(A, Decl(badInferenceLowerPriorityThanGoodInference.ts, 5, 33)) +>A : Symbol(A, Decl(badInferenceLowerPriorityThanGoodInference.ts, 5, 33)) + +const result = canYouInferThis(() => ({ +>result : Symbol(result, Decl(badInferenceLowerPriorityThanGoodInference.ts, 7, 5)) +>canYouInferThis : Symbol(canYouInferThis, Decl(badInferenceLowerPriorityThanGoodInference.ts, 3, 1)) + + a: { BLAH: 33 }, +>a : Symbol(a, Decl(badInferenceLowerPriorityThanGoodInference.ts, 7, 39)) +>BLAH : Symbol(BLAH, Decl(badInferenceLowerPriorityThanGoodInference.ts, 8, 8)) + + b: x => { } +>b : Symbol(b, Decl(badInferenceLowerPriorityThanGoodInference.ts, 8, 20)) +>x : Symbol(x, Decl(badInferenceLowerPriorityThanGoodInference.ts, 9, 6)) + +})) + +result.BLAH; +>result.BLAH : Symbol(BLAH, Decl(badInferenceLowerPriorityThanGoodInference.ts, 8, 8)) +>result : Symbol(result, Decl(badInferenceLowerPriorityThanGoodInference.ts, 7, 5)) +>BLAH : Symbol(BLAH, Decl(badInferenceLowerPriorityThanGoodInference.ts, 8, 8)) + diff --git a/tests/baselines/reference/badInferenceLowerPriorityThanGoodInference.types b/tests/baselines/reference/badInferenceLowerPriorityThanGoodInference.types new file mode 100644 index 0000000000000..8947448d8d338 --- /dev/null +++ b/tests/baselines/reference/badInferenceLowerPriorityThanGoodInference.types @@ -0,0 +1,49 @@ +=== tests/cases/compiler/badInferenceLowerPriorityThanGoodInference.ts === +interface Foo { +>Foo : Foo +>A : A + + a: A; +>a : A +>A : A + + b: (x: A) => void; +>b : (x: A) => void +>x : A +>A : A +} + +declare function canYouInferThis(fn: () => Foo): A; +>canYouInferThis : (fn: () => Foo) => A +>A : A +>fn : () => Foo +>Foo : Foo +>A : A +>A : A + +const result = canYouInferThis(() => ({ +>result : { BLAH: number; } +>canYouInferThis(() => ({ a: { BLAH: 33 }, b: x => { }})) : { BLAH: number; } +>canYouInferThis : (fn: () => Foo) => A +>() => ({ a: { BLAH: 33 }, b: x => { }}) : () => { a: { BLAH: number; }; b: (x: { BLAH: number; }) => void; } +>({ a: { BLAH: 33 }, b: x => { }}) : { a: { BLAH: number; }; b: (x: { BLAH: number; }) => void; } +>{ a: { BLAH: 33 }, b: x => { }} : { a: { BLAH: number; }; b: (x: { BLAH: number; }) => void; } + + a: { BLAH: 33 }, +>a : { BLAH: number; } +>{ BLAH: 33 } : { BLAH: number; } +>BLAH : number +>33 : 33 + + b: x => { } +>b : (x: { BLAH: number; }) => void +>x => { } : (x: { BLAH: number; }) => void +>x : { BLAH: number; } + +})) + +result.BLAH; +>result.BLAH : number +>result : { BLAH: number; } +>BLAH : number + diff --git a/tests/cases/compiler/badInferenceLowerPriorityThanGoodInference.ts b/tests/cases/compiler/badInferenceLowerPriorityThanGoodInference.ts new file mode 100644 index 0000000000000..59dfb3ff4473f --- /dev/null +++ b/tests/cases/compiler/badInferenceLowerPriorityThanGoodInference.ts @@ -0,0 +1,13 @@ +interface Foo { + a: A; + b: (x: A) => void; +} + +declare function canYouInferThis(fn: () => Foo): A; + +const result = canYouInferThis(() => ({ + a: { BLAH: 33 }, + b: x => { } +})) + +result.BLAH; \ No newline at end of file From d84df7710fe9e448721c5534c49cfa0d4c664953 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 1 Aug 2018 15:26:28 -0700 Subject: [PATCH 2/3] Add cache for context-free type of a signature node --- src/compiler/checker.ts | 6 +++++- src/compiler/types.ts | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ebb05967f75dc..4e1248f7c7b24 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -20548,9 +20548,13 @@ namespace ts { if (checkMode === CheckMode.SkipContextSensitive && isContextSensitive(node)) { // Skip parameters, return signature with return type that retains noncontextual parts so inferences can still be drawn in an early stage if (!getEffectiveReturnTypeNode(node) && hasContextSensitiveReturnExpression(node)) { + const links = getNodeLinks(node); + if (links.contextFreeType) { + return links.contextFreeType; + } const returnType = getReturnTypeFromBody(node, checkMode); const singleReturnSignature = createSignature(undefined, undefined, undefined, emptyArray, returnType, /*resolvedTypePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); - return createAnonymousType(node.symbol, emptySymbols, [singleReturnSignature], emptyArray, undefined, undefined); + return links.contextFreeType = createAnonymousType(node.symbol, emptySymbols, [singleReturnSignature], emptyArray, undefined, undefined); } return anyFunctionType; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 87664652b355b..eae7edb381b8c 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3644,6 +3644,7 @@ namespace ts { superCall?: SuperCall; // Cached first super-call found in the constructor. Used in checking whether super is called before this-accessing switchTypes?: Type[]; // Cached array of switch case expression types jsxNamespace?: Symbol | false; // Resolved jsx namespace symbol for this node + contextFreeType?: Type; // Cached context-free type used by the first pass of inference; used when a function's return is partially contextually sensitive } export const enum TypeFlags { From e061ea123df1d04e4026f5c4418474decf33419a Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 1 Aug 2018 16:59:17 -0700 Subject: [PATCH 3/3] Accept post-merge baseline --- .../badInferenceLowerPriorityThanGoodInference.types | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tests/baselines/reference/badInferenceLowerPriorityThanGoodInference.types b/tests/baselines/reference/badInferenceLowerPriorityThanGoodInference.types index 8947448d8d338..8b0ab548fddf1 100644 --- a/tests/baselines/reference/badInferenceLowerPriorityThanGoodInference.types +++ b/tests/baselines/reference/badInferenceLowerPriorityThanGoodInference.types @@ -1,25 +1,16 @@ === tests/cases/compiler/badInferenceLowerPriorityThanGoodInference.ts === interface Foo { ->Foo : Foo ->A : A - a: A; >a : A ->A : A b: (x: A) => void; >b : (x: A) => void >x : A ->A : A } declare function canYouInferThis(fn: () => Foo): A; >canYouInferThis : (fn: () => Foo) => A ->A : A >fn : () => Foo ->Foo : Foo ->A : A ->A : A const result = canYouInferThis(() => ({ >result : { BLAH: number; }