Skip to content
Open
43 changes: 33 additions & 10 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1339,11 +1339,14 @@ export const enum CheckMode {
Inferential = 1 << 1, // Inferential typing
SkipContextSensitive = 1 << 2, // Skip context sensitive function expressions
SkipGenericFunctions = 1 << 3, // Skip single signature generic functions
IsForSignatureHelp = 1 << 4, // Call resolution for purposes of signature help
RestBindingElement = 1 << 5, // Checking a type that is going to be used to determine the type of a rest binding element
SkipReturnTypeFromBodyInference = 1 << 4, // Skip inferring from return types of context sensitive functions
// it's used to prevent inferring within return types of generic functions,
// as that could create overlapping inferences that would interfere with the logic `instantiateTypeWithSingleGenericCallSignature` that handles them better
IsForSignatureHelp = 1 << 5, // Call resolution for purposes of signature help
RestBindingElement = 1 << 6, // Checking a type that is going to be used to determine the type of a rest binding element
// e.g. in `const { a, ...rest } = foo`, when checking the type of `foo` to determine the type of `rest`,
// we need to preserve generic types instead of substituting them for constraints
TypeOnly = 1 << 6, // Called from getTypeOfExpression, diagnostics may be omitted
TypeOnly = 1 << 7, // Called from getTypeOfExpression, diagnostics may be omitted
}

/** @internal */
Expand Down Expand Up @@ -39550,7 +39553,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return getTypeOfSymbol(getSymbolOfDeclaration(node));
}

function contextuallyCheckFunctionExpressionOrObjectLiteralMethod(node: FunctionExpression | ArrowFunction | MethodDeclaration, checkMode?: CheckMode) {
function contextuallyCheckFunctionExpressionOrObjectLiteralMethod(node: FunctionExpression | ArrowFunction | MethodDeclaration, checkMode = CheckMode.Normal) {
const links = getNodeLinks(node);
// Check if function expression is contextually typed and assign parameter types if so.
if (!(links.flags & NodeCheckFlags.ContextChecked)) {
Expand All @@ -39564,11 +39567,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (!signature) {
return;
}
if (isContextSensitive(node)) {
const isNodeContextSensitive = isContextSensitive(node);
if (isNodeContextSensitive) {
if (contextualSignature) {
const inferenceContext = getInferenceContext(node);
let instantiatedContextualSignature: Signature | undefined;
if (checkMode && checkMode & CheckMode.Inferential) {
if (checkMode & CheckMode.Inferential) {
inferFromAnnotatedParametersAndReturn(signature, contextualSignature, inferenceContext!);
const restType = getEffectiveRestType(contextualSignature);
if (restType && restType.flags & TypeFlags.TypeParameter) {
Expand All @@ -39586,15 +39590,34 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
else if (contextualSignature && !node.typeParameters && contextualSignature.parameters.length > node.parameters.length) {
const inferenceContext = getInferenceContext(node);
if (checkMode && checkMode & CheckMode.Inferential) {
if (checkMode & CheckMode.Inferential) {
inferFromAnnotatedParametersAndReturn(signature, contextualSignature, inferenceContext!);
}
}
if (contextualSignature && !getReturnTypeFromAnnotation(node) && !signature.resolvedReturnType) {
const returnType = getReturnTypeFromBody(node, checkMode);
if (!signature.resolvedReturnType) {
signature.resolvedReturnType = returnType;
let contextualReturnType: Type;
let returnType: Type;

if (node.typeParameters) {
checkMode |= CheckMode.SkipReturnTypeFromBodyInference;
}

if (
isNodeContextSensitive && ((checkMode & (CheckMode.Inferential | CheckMode.SkipReturnTypeFromBodyInference)) === CheckMode.Inferential) &&
couldContainTypeVariables(contextualReturnType = getReturnTypeOfSignature(contextualSignature))
) {
const inferenceContext = getInferenceContext(node);
const isReturnContextSensitive = !!node.body && (node.body.kind === SyntaxKind.Block ? forEachReturnStatement(node.body as Block, statement => !!statement.expression && isContextSensitive(statement.expression)) : isContextSensitive(node.body));
returnType = getReturnTypeFromBody(node, checkMode | (isReturnContextSensitive ? CheckMode.SkipContextSensitive : 0));
inferTypes(inferenceContext!.inferences, returnType, contextualReturnType);
if (isReturnContextSensitive) {
returnType = getReturnTypeFromBody(node, checkMode);
}
}
else {
returnType = getReturnTypeFromBody(node, checkMode);
}
signature.resolvedReturnType ??= returnType;
}
checkSignatureDeclaration(node);
}
Expand Down
295 changes: 295 additions & 0 deletions tests/baselines/reference/InferFromReturnsInContextSensitive1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,295 @@
//// [tests/cases/conformance/types/typeRelationships/typeInference/InferFromReturnsInContextSensitive1.ts] ////

//// [InferFromReturnsInContextSensitive1.ts]
// https://github.com/microsoft/TypeScript/issues/60720

type Options<TContext> = {
onStart?: () => TContext;
onEnd?: (context: TContext) => void;
};

function create<TContext>(builder: (arg: boolean) => Options<TContext>) {
return builder(true);
}

create((arg) => ({
onStart: () => ({ time: new Date() }),
onEnd: (context) => {},
}));

create((arg) => ({
onEnd: (context) => {},
onStart: () => ({ time: new Date() }),
}));

// https://github.com/microsoft/TypeScript/issues/57021

type Schema = Record<string, unknown>;

type StepFunction<TSchema extends Schema = Schema> = (anything: unknown) => {
readonly schema: TSchema;
readonly toAnswers?: (keys: keyof TSchema) => unknown;
};

function step<TSchema extends Schema = Schema>(
stepVal: StepFunction<TSchema>,
): StepFunction<TSchema> {
return stepVal;
}

const stepResult1 = step((_something) => ({
schema: {
attribute: "anything",
},
toAnswers: (keys) => {
type Test = string extends typeof keys ? never : "true";
const test: Test = "true"; // ok
return { test };
},
}));

const stepResult2 = step((_something) => ({
toAnswers: (keys) => {
type Test = string extends typeof keys ? never : "true";
const test: Test = "true"; // ok
return { test };
},
schema: {
attribute: "anything",
},
}));

type Fn1<T, T2> = (anything: unknown) => {
stuff: T;
consume: (arg: T) => (anything: unknown) => {
stuff2: T2;
consume2: (arg: T2) => void;
};
};

declare function test1<T, T2>(fn: Fn1<T, T2>): [T, T2];

const res1 = test1((_something) => ({
stuff: "foo",
consume: (arg) => {
return (_something) => ({
stuff2: 42,
consume2: (arg2) => {},
});
},
}));

const res2 = test1((_something) => ({
consume: (arg) => {
return (_something) => ({
consume2: (arg2) => {},
stuff2: 42,
});
},
stuff: "foo",
}));

const res3 = test1((_something) => ({
stuff: "foo",
consume: () => {
return (_something) => ({
stuff2: 42,
consume2: (arg2) => {},
});
},
}));

const res4 = test1((_something) => ({
consume: () => {
return (_something) => ({
consume2: (arg2) => {},
stuff2: 42,
});
},
stuff: "foo",
}));

const res5 = test1((_something) => ({
stuff: "foo",
consume: () => {
return () => ({
stuff2: 42,
consume2: (arg2) => {},
});
},
}));

const res6 = test1((_something) => ({
consume: () => {
return () => ({
consume2: (arg2) => {},
stuff2: 42,
});
},
stuff: "foo",
}));

const res7 = test1((_something) => ({
stuff: "foo",
consume: () => {
return () => ({
stuff2: 42,
consume2: () => {},
});
},
}));

const res8 = test1((_something) => ({
consume: () => {
return () => ({
consume2: () => {},
stuff2: 42,
});
},
stuff: "foo",
}));


//// [InferFromReturnsInContextSensitive1.js]
"use strict";
// https://github.com/microsoft/TypeScript/issues/60720
function create(builder) {
return builder(true);
}
create(function (arg) { return ({
onStart: function () { return ({ time: new Date() }); },
onEnd: function (context) { },
}); });
create(function (arg) { return ({
onEnd: function (context) { },
onStart: function () { return ({ time: new Date() }); },
}); });
function step(stepVal) {
return stepVal;
}
var stepResult1 = step(function (_something) { return ({
schema: {
attribute: "anything",
},
toAnswers: function (keys) {
var test = "true"; // ok
return { test: test };
},
}); });
var stepResult2 = step(function (_something) { return ({
toAnswers: function (keys) {
var test = "true"; // ok
return { test: test };
},
schema: {
attribute: "anything",
},
}); });
var res1 = test1(function (_something) { return ({
stuff: "foo",
consume: function (arg) {
return function (_something) { return ({
stuff2: 42,
consume2: function (arg2) { },
}); };
},
}); });
var res2 = test1(function (_something) { return ({
consume: function (arg) {
return function (_something) { return ({
consume2: function (arg2) { },
stuff2: 42,
}); };
},
stuff: "foo",
}); });
var res3 = test1(function (_something) { return ({
stuff: "foo",
consume: function () {
return function (_something) { return ({
stuff2: 42,
consume2: function (arg2) { },
}); };
},
}); });
var res4 = test1(function (_something) { return ({
consume: function () {
return function (_something) { return ({
consume2: function (arg2) { },
stuff2: 42,
}); };
},
stuff: "foo",
}); });
var res5 = test1(function (_something) { return ({
stuff: "foo",
consume: function () {
return function () { return ({
stuff2: 42,
consume2: function (arg2) { },
}); };
},
}); });
var res6 = test1(function (_something) { return ({
consume: function () {
return function () { return ({
consume2: function (arg2) { },
stuff2: 42,
}); };
},
stuff: "foo",
}); });
var res7 = test1(function (_something) { return ({
stuff: "foo",
consume: function () {
return function () { return ({
stuff2: 42,
consume2: function () { },
}); };
},
}); });
var res8 = test1(function (_something) { return ({
consume: function () {
return function () { return ({
consume2: function () { },
stuff2: 42,
}); };
},
stuff: "foo",
}); });


//// [InferFromReturnsInContextSensitive1.d.ts]
type Options<TContext> = {
onStart?: () => TContext;
onEnd?: (context: TContext) => void;
};
declare function create<TContext>(builder: (arg: boolean) => Options<TContext>): Options<TContext>;
type Schema = Record<string, unknown>;
type StepFunction<TSchema extends Schema = Schema> = (anything: unknown) => {
readonly schema: TSchema;
readonly toAnswers?: (keys: keyof TSchema) => unknown;
};
declare function step<TSchema extends Schema = Schema>(stepVal: StepFunction<TSchema>): StepFunction<TSchema>;
declare const stepResult1: StepFunction<{
attribute: string;
}>;
declare const stepResult2: StepFunction<{
attribute: string;
}>;
type Fn1<T, T2> = (anything: unknown) => {
stuff: T;
consume: (arg: T) => (anything: unknown) => {
stuff2: T2;
consume2: (arg: T2) => void;
};
};
declare function test1<T, T2>(fn: Fn1<T, T2>): [T, T2];
declare const res1: [string, number];
declare const res2: [string, number];
declare const res3: [string, number];
declare const res4: [string, number];
declare const res5: [string, number];
declare const res6: [string, number];
declare const res7: [string, number];
declare const res8: [string, number];
Loading