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