Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Contextual typing for return expressions of functions with contextual signatures based on instantiated types #61185

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31971,6 +31971,34 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}

function isReturnExpressionLiteralContext(node: Node) {
const ancestor = findAncestor(node, n => {
if (isCallOrNewExpression(n)) {
// prevent inference candidates of outer inference context to provide contextual type information for the expressions within the inner context
// that could turn fresh literal candidates in the inner context into regular types for union-like literals (such as booleans and enums)
// and that would create mismatches between inferred types for outer and inner contexts which is especially problematic when invariant type parameters are involved
//
// the call below should be ok but with the inner one receiving `boolean` as contextual type it would infer `true` for its type parameter
// and that would create outer signature applicability error with outer `Box<boolean>` and inner `Box<true>`
//
// interface Box<T> { v: (arg: T) => T; }
// declare function invariantBox<T>(v: T): Box<T>
// declare function fn<T>(arg: Box<T>, get: () => Box<T>): void;
// fn(invariantBox(true), () => invariantBox(true));
Comment on lines +31977 to +31987
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this problem is quite similar to #48363 , #59754 and #61196

So it feels like some consistent approach could be used to fix those and to remove this branch. But that problem is out of the scope of this PR so I'm not attempting to deal with it. Thanks to this branch the behavior should be no worse than the existing one.

return "quit";
}
const parent = n.parent;
if (isStatement(parent)) {
return parent.kind === SyntaxKind.ReturnStatement || "quit";
}
if (parent.kind === SyntaxKind.ArrowFunction) {
return (parent as ArrowFunction).body === n || "quit";
}
return false;
});
return !!(ancestor && isExpression(ancestor) && getContextualTypeForReturnExpression(ancestor, /*contextFlags*/ undefined));
}

// If the given contextual type contains instantiable types and if a mapper representing
// return type inferences is available, instantiate those types using that mapper.
function instantiateContextualType(contextualType: Type | undefined, node: Node, contextFlags: ContextFlags | undefined): Type | undefined {
Expand All @@ -31979,7 +32007,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// If no inferences have been made, and none of the type parameters for which we are inferring
// specify default types, nothing is gained from instantiating as type parameters would just be
// replaced with their constraints similar to the apparent type.
if (inferenceContext && contextFlags! & ContextFlags.Signature && some(inferenceContext.inferences, hasInferenceCandidatesOrDefault)) {
if (inferenceContext && (contextFlags! & ContextFlags.Signature || isReturnExpressionLiteralContext(node)) && some(inferenceContext.inferences, hasInferenceCandidatesOrDefault)) {
// For contextual signatures we incorporate all inferences made so far, e.g. from return
// types as well as arguments to the left in a function call.
return instantiateInstantiableTypes(contextualType, inferenceContext.nonFixingMapper);
Expand Down
333 changes: 333 additions & 0 deletions tests/baselines/reference/instantiateContextualTypes2.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,333 @@
//// [tests/cases/compiler/instantiateContextualTypes2.ts] ////

=== instantiateContextualTypes2.ts ===
type ContextStates =
>ContextStates : Symbol(ContextStates, Decl(instantiateContextualTypes2.ts, 0, 0))

| {
status: "loading";
>status : Symbol(status, Decl(instantiateContextualTypes2.ts, 1, 5))

data: null;
>data : Symbol(data, Decl(instantiateContextualTypes2.ts, 2, 24))
}
| {
status: "success";
>status : Symbol(status, Decl(instantiateContextualTypes2.ts, 5, 5))

data: string;
>data : Symbol(data, Decl(instantiateContextualTypes2.ts, 6, 24))

};

declare function createStore<TContext>(
>createStore : Symbol(createStore, Decl(instantiateContextualTypes2.ts, 8, 6))
>TContext : Symbol(TContext, Decl(instantiateContextualTypes2.ts, 10, 29))

context: TContext,
>context : Symbol(context, Decl(instantiateContextualTypes2.ts, 10, 39))
>TContext : Symbol(TContext, Decl(instantiateContextualTypes2.ts, 10, 29))

config: {
>config : Symbol(config, Decl(instantiateContextualTypes2.ts, 11, 20))

on: Record<string, (ctx: TContext) => TContext>;
>on : Symbol(on, Decl(instantiateContextualTypes2.ts, 12, 11))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
>ctx : Symbol(ctx, Decl(instantiateContextualTypes2.ts, 13, 24))
>TContext : Symbol(TContext, Decl(instantiateContextualTypes2.ts, 10, 29))
>TContext : Symbol(TContext, Decl(instantiateContextualTypes2.ts, 10, 29))

},
): void;

const store1 = createStore(
>store1 : Symbol(store1, Decl(instantiateContextualTypes2.ts, 17, 5))
>createStore : Symbol(createStore, Decl(instantiateContextualTypes2.ts, 8, 6))
{
status: "loading",
>status : Symbol(status, Decl(instantiateContextualTypes2.ts, 18, 3))

data: null,
>data : Symbol(data, Decl(instantiateContextualTypes2.ts, 19, 22))

} as ContextStates,
>ContextStates : Symbol(ContextStates, Decl(instantiateContextualTypes2.ts, 0, 0))
{
on: {
>on : Symbol(on, Decl(instantiateContextualTypes2.ts, 22, 3))

fetch: (ctx) => ({
>fetch : Symbol(fetch, Decl(instantiateContextualTypes2.ts, 23, 9))
>ctx : Symbol(ctx, Decl(instantiateContextualTypes2.ts, 24, 14))

status: "success",
>status : Symbol(status, Decl(instantiateContextualTypes2.ts, 24, 24))

data: "hello",
>data : Symbol(data, Decl(instantiateContextualTypes2.ts, 25, 26))

}),
},
},
);

const store2 = createStore(
>store2 : Symbol(store2, Decl(instantiateContextualTypes2.ts, 32, 5))
>createStore : Symbol(createStore, Decl(instantiateContextualTypes2.ts, 8, 6))
{
status: "loading",
>status : Symbol(status, Decl(instantiateContextualTypes2.ts, 33, 3))

data: null,
>data : Symbol(data, Decl(instantiateContextualTypes2.ts, 34, 22))

} as ContextStates,
>ContextStates : Symbol(ContextStates, Decl(instantiateContextualTypes2.ts, 0, 0))
{
on: {
>on : Symbol(on, Decl(instantiateContextualTypes2.ts, 37, 3))

fetch: () => ({
>fetch : Symbol(fetch, Decl(instantiateContextualTypes2.ts, 38, 9))

status: "success",
>status : Symbol(status, Decl(instantiateContextualTypes2.ts, 39, 21))

data: "hello",
>data : Symbol(data, Decl(instantiateContextualTypes2.ts, 40, 26))

}),
},
},
);

const store3 = createStore(
>store3 : Symbol(store3, Decl(instantiateContextualTypes2.ts, 47, 5))
>createStore : Symbol(createStore, Decl(instantiateContextualTypes2.ts, 8, 6))
{
status: "loading",
>status : Symbol(status, Decl(instantiateContextualTypes2.ts, 48, 3))

data: null,
>data : Symbol(data, Decl(instantiateContextualTypes2.ts, 49, 22))

} as ContextStates,
>ContextStates : Symbol(ContextStates, Decl(instantiateContextualTypes2.ts, 0, 0))
{
on: {
>on : Symbol(on, Decl(instantiateContextualTypes2.ts, 52, 3))

fetch: (ctx) => {
>fetch : Symbol(fetch, Decl(instantiateContextualTypes2.ts, 53, 9))
>ctx : Symbol(ctx, Decl(instantiateContextualTypes2.ts, 54, 14))

return {
status: "success",
>status : Symbol(status, Decl(instantiateContextualTypes2.ts, 55, 16))

data: "hello",
>data : Symbol(data, Decl(instantiateContextualTypes2.ts, 56, 28))

};
},
},
},
);

const store4 = createStore(
>store4 : Symbol(store4, Decl(instantiateContextualTypes2.ts, 64, 5))
>createStore : Symbol(createStore, Decl(instantiateContextualTypes2.ts, 8, 6))
{
status: "loading",
>status : Symbol(status, Decl(instantiateContextualTypes2.ts, 65, 3))

data: null,
>data : Symbol(data, Decl(instantiateContextualTypes2.ts, 66, 22))

} as ContextStates,
>ContextStates : Symbol(ContextStates, Decl(instantiateContextualTypes2.ts, 0, 0))
{
on: {
>on : Symbol(on, Decl(instantiateContextualTypes2.ts, 69, 3))

fetch: () => {
>fetch : Symbol(fetch, Decl(instantiateContextualTypes2.ts, 70, 9))

return {
status: "success",
>status : Symbol(status, Decl(instantiateContextualTypes2.ts, 72, 16))

data: "hello",
>data : Symbol(data, Decl(instantiateContextualTypes2.ts, 73, 28))

};
},
},
},
);

declare function createStore2<TContext>(
>createStore2 : Symbol(createStore2, Decl(instantiateContextualTypes2.ts, 79, 2))
>TContext : Symbol(TContext, Decl(instantiateContextualTypes2.ts, 81, 30))

context: TContext,
>context : Symbol(context, Decl(instantiateContextualTypes2.ts, 81, 40))
>TContext : Symbol(TContext, Decl(instantiateContextualTypes2.ts, 81, 30))

config: {
>config : Symbol(config, Decl(instantiateContextualTypes2.ts, 82, 20))

on: Record<string, (ctx: TContext) => { context: TContext }>;
>on : Symbol(on, Decl(instantiateContextualTypes2.ts, 83, 11))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
>ctx : Symbol(ctx, Decl(instantiateContextualTypes2.ts, 84, 24))
>TContext : Symbol(TContext, Decl(instantiateContextualTypes2.ts, 81, 30))
>context : Symbol(context, Decl(instantiateContextualTypes2.ts, 84, 43))
>TContext : Symbol(TContext, Decl(instantiateContextualTypes2.ts, 81, 30))

},
): void;

const store5 = createStore2(
>store5 : Symbol(store5, Decl(instantiateContextualTypes2.ts, 88, 5))
>createStore2 : Symbol(createStore2, Decl(instantiateContextualTypes2.ts, 79, 2))
{
status: "loading",
>status : Symbol(status, Decl(instantiateContextualTypes2.ts, 89, 3))

data: null,
>data : Symbol(data, Decl(instantiateContextualTypes2.ts, 90, 22))

} as ContextStates,
>ContextStates : Symbol(ContextStates, Decl(instantiateContextualTypes2.ts, 0, 0))
{
on: {
>on : Symbol(on, Decl(instantiateContextualTypes2.ts, 93, 3))

fetch: (ctx) => ({
>fetch : Symbol(fetch, Decl(instantiateContextualTypes2.ts, 94, 9))
>ctx : Symbol(ctx, Decl(instantiateContextualTypes2.ts, 95, 14))

context: {
>context : Symbol(context, Decl(instantiateContextualTypes2.ts, 95, 24))

status: "success",
>status : Symbol(status, Decl(instantiateContextualTypes2.ts, 96, 18))

data: "hello",
>data : Symbol(data, Decl(instantiateContextualTypes2.ts, 97, 28))

},
}),
},
},
);

const store6 = createStore2(
>store6 : Symbol(store6, Decl(instantiateContextualTypes2.ts, 105, 5))
>createStore2 : Symbol(createStore2, Decl(instantiateContextualTypes2.ts, 79, 2))
{
status: "loading",
>status : Symbol(status, Decl(instantiateContextualTypes2.ts, 106, 3))

data: null,
>data : Symbol(data, Decl(instantiateContextualTypes2.ts, 107, 22))

} as ContextStates,
>ContextStates : Symbol(ContextStates, Decl(instantiateContextualTypes2.ts, 0, 0))
{
on: {
>on : Symbol(on, Decl(instantiateContextualTypes2.ts, 110, 3))

fetch: () => ({
>fetch : Symbol(fetch, Decl(instantiateContextualTypes2.ts, 111, 9))

context: {
>context : Symbol(context, Decl(instantiateContextualTypes2.ts, 112, 21))

status: "success",
>status : Symbol(status, Decl(instantiateContextualTypes2.ts, 113, 18))

data: "hello",
>data : Symbol(data, Decl(instantiateContextualTypes2.ts, 114, 28))

},
}),
},
},
);

const store7 = createStore2(
>store7 : Symbol(store7, Decl(instantiateContextualTypes2.ts, 122, 5))
>createStore2 : Symbol(createStore2, Decl(instantiateContextualTypes2.ts, 79, 2))
{
status: "loading",
>status : Symbol(status, Decl(instantiateContextualTypes2.ts, 123, 3))

data: null,
>data : Symbol(data, Decl(instantiateContextualTypes2.ts, 124, 22))

} as ContextStates,
>ContextStates : Symbol(ContextStates, Decl(instantiateContextualTypes2.ts, 0, 0))
{
on: {
>on : Symbol(on, Decl(instantiateContextualTypes2.ts, 127, 3))

fetch: (ctx) => {
>fetch : Symbol(fetch, Decl(instantiateContextualTypes2.ts, 128, 9))
>ctx : Symbol(ctx, Decl(instantiateContextualTypes2.ts, 129, 14))

return {
context: {
>context : Symbol(context, Decl(instantiateContextualTypes2.ts, 130, 16))

status: "success",
>status : Symbol(status, Decl(instantiateContextualTypes2.ts, 131, 20))

data: "hello",
>data : Symbol(data, Decl(instantiateContextualTypes2.ts, 132, 30))

},
};
},
},
},
);

const store8 = createStore2(
>store8 : Symbol(store8, Decl(instantiateContextualTypes2.ts, 141, 5))
>createStore2 : Symbol(createStore2, Decl(instantiateContextualTypes2.ts, 79, 2))
{
status: "loading",
>status : Symbol(status, Decl(instantiateContextualTypes2.ts, 142, 3))

data: null,
>data : Symbol(data, Decl(instantiateContextualTypes2.ts, 143, 22))

} as ContextStates,
>ContextStates : Symbol(ContextStates, Decl(instantiateContextualTypes2.ts, 0, 0))
{
on: {
>on : Symbol(on, Decl(instantiateContextualTypes2.ts, 146, 3))

fetch: () => {
>fetch : Symbol(fetch, Decl(instantiateContextualTypes2.ts, 147, 9))

return {
context: {
>context : Symbol(context, Decl(instantiateContextualTypes2.ts, 149, 16))

status: "success",
>status : Symbol(status, Decl(instantiateContextualTypes2.ts, 150, 20))

data: "hello",
>data : Symbol(data, Decl(instantiateContextualTypes2.ts, 151, 30))

},
};
},
},
},
);

Loading