diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index 649979e412084..5ab2120ebb94e 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -10369,7 +10369,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);
@@ -20726,6 +20729,16 @@ 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 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 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 06867a94c17cb..cb0550124e5e3 100644
--- a/src/compiler/types.ts
+++ b/src/compiler/types.ts
@@ -3667,6 +3667,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 {
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..8b0ab548fddf1
--- /dev/null
+++ b/tests/baselines/reference/badInferenceLowerPriorityThanGoodInference.types
@@ -0,0 +1,40 @@
+=== tests/cases/compiler/badInferenceLowerPriorityThanGoodInference.ts ===
+interface Foo {
+ a: A;
+>a : A
+
+ b: (x: A) => void;
+>b : (x: A) => void
+>x : A
+}
+
+declare function canYouInferThis(fn: () => Foo): A;
+>canYouInferThis : (fn: () => Foo) => A
+>fn : () => Foo
+
+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