Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
23 changes: 20 additions & 3 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26852,14 +26852,31 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const constraint = getConstraintOfTypeParameter(inference.typeParameter);
if (constraint) {
const instantiatedConstraint = instantiateType(constraint, context.nonFixingMapper);
if (!inferredType || !context.compareTypes(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) {
// If the fallback type satisfies the constraint, we pick it. Otherwise, we pick the constraint.
inference.inferredType = fallbackType && context.compareTypes(fallbackType, getTypeWithThisArgument(instantiatedConstraint, fallbackType)) ? fallbackType : instantiatedConstraint;
if (!inferredType) {
inference.inferredType = instantiatedConstraint;
}
else {
if (inference.priority! & InferencePriority.ReturnType) {
inference.inferredType = filterContextualInferredType(inferredType, instantiatedConstraint) ?? (fallbackType && filterContextualInferredType(fallbackType, instantiatedConstraint)) ?? instantiatedConstraint;
Copy link
Member

Choose a reason for hiding this comment

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

We only need to bother with this filtering if the constraint check for the whole union fails, right? (Since if it passes, no filtering will occur, as every union member must also pass.) This can probably be inside the comparison branch below?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That’s right but it felt cleaner to have this in a separate branch. Shouldnt filterType already optimize for array/type allocations?

}
else if (!context.compareTypes(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) {
// If the fallback type satisfies the constraint, we pick it. Otherwise, we pick the constraint.
inference.inferredType = fallbackType && context.compareTypes(fallbackType, getTypeWithThisArgument(instantiatedConstraint, fallbackType)) ? fallbackType : instantiatedConstraint;
}
}
}
}

return inference.inferredType;

function filterContextualInferredType(inferredType: Type, constraint: Type) {
if (inferredType.flags & TypeFlags.Never) {
return inferredType;
}
const constraintWithThisArgument = getTypeWithThisArgument(constraint, inferredType);
const applicableByConstraint = filterType(inferredType, t => !!context.compareTypes(t, constraintWithThisArgument));
return !(applicableByConstraint.flags & TypeFlags.Never) ? inferredType : undefined;
}
}

function getDefaultTypeArgumentType(isInJavaScriptFile: boolean): Type {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//// [tests/cases/compiler/inferenceContextualReturnTypeUnion1.ts] ////

=== inferenceContextualReturnTypeUnion1.ts ===
// https://github.com/microsoft/TypeScript/issues/50719

declare function useCallback1<T extends Function>(fn: T): T;
>useCallback1 : Symbol(useCallback1, Decl(inferenceContextualReturnTypeUnion1.ts, 0, 0))
>T : Symbol(T, Decl(inferenceContextualReturnTypeUnion1.ts, 2, 30))
>Function : Symbol(Function, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>fn : Symbol(fn, Decl(inferenceContextualReturnTypeUnion1.ts, 2, 50))
>T : Symbol(T, Decl(inferenceContextualReturnTypeUnion1.ts, 2, 30))
>T : Symbol(T, Decl(inferenceContextualReturnTypeUnion1.ts, 2, 30))

declare function ex2(callback?: (x: number) => void): void;
>ex2 : Symbol(ex2, Decl(inferenceContextualReturnTypeUnion1.ts, 2, 60))
>callback : Symbol(callback, Decl(inferenceContextualReturnTypeUnion1.ts, 4, 21))
>x : Symbol(x, Decl(inferenceContextualReturnTypeUnion1.ts, 4, 33))

ex2(useCallback1((x) => {}));
>ex2 : Symbol(ex2, Decl(inferenceContextualReturnTypeUnion1.ts, 2, 60))
>useCallback1 : Symbol(useCallback1, Decl(inferenceContextualReturnTypeUnion1.ts, 0, 0))
>x : Symbol(x, Decl(inferenceContextualReturnTypeUnion1.ts, 5, 18))

declare function ex3(callback: ((x: number) => void) | 5): void;
>ex3 : Symbol(ex3, Decl(inferenceContextualReturnTypeUnion1.ts, 5, 29))
>callback : Symbol(callback, Decl(inferenceContextualReturnTypeUnion1.ts, 7, 21))
>x : Symbol(x, Decl(inferenceContextualReturnTypeUnion1.ts, 7, 33))

ex3(useCallback1((x) => {}));
>ex3 : Symbol(ex3, Decl(inferenceContextualReturnTypeUnion1.ts, 5, 29))
>useCallback1 : Symbol(useCallback1, Decl(inferenceContextualReturnTypeUnion1.ts, 0, 0))
>x : Symbol(x, Decl(inferenceContextualReturnTypeUnion1.ts, 8, 18))

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

declare function useCallback2<T extends (...args: any[]) => any>(
>useCallback2 : Symbol(useCallback2, Decl(inferenceContextualReturnTypeUnion1.ts, 8, 29))
>T : Symbol(T, Decl(inferenceContextualReturnTypeUnion1.ts, 12, 30))
>args : Symbol(args, Decl(inferenceContextualReturnTypeUnion1.ts, 12, 41))

callback: T,
>callback : Symbol(callback, Decl(inferenceContextualReturnTypeUnion1.ts, 12, 65))
>T : Symbol(T, Decl(inferenceContextualReturnTypeUnion1.ts, 12, 30))

): T;
>T : Symbol(T, Decl(inferenceContextualReturnTypeUnion1.ts, 12, 30))

const test: ((x: string) => void) | undefined = useCallback2((x) => {});
>test : Symbol(test, Decl(inferenceContextualReturnTypeUnion1.ts, 15, 5))
>x : Symbol(x, Decl(inferenceContextualReturnTypeUnion1.ts, 15, 14))
>useCallback2 : Symbol(useCallback2, Decl(inferenceContextualReturnTypeUnion1.ts, 8, 29))
>x : Symbol(x, Decl(inferenceContextualReturnTypeUnion1.ts, 15, 62))

Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//// [tests/cases/compiler/inferenceContextualReturnTypeUnion1.ts] ////

=== inferenceContextualReturnTypeUnion1.ts ===
// https://github.com/microsoft/TypeScript/issues/50719

declare function useCallback1<T extends Function>(fn: T): T;
>useCallback1 : <T extends Function>(fn: T) => T
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
>fn : T
> : ^

declare function ex2(callback?: (x: number) => void): void;
>ex2 : (callback?: (x: number) => void) => void
> : ^ ^^^ ^^^^^
>callback : ((x: number) => void) | undefined
> : ^^ ^^ ^^^^^ ^^^^^^^^^^^^^
>x : number
> : ^^^^^^

ex2(useCallback1((x) => {}));
>ex2(useCallback1((x) => {})) : void
> : ^^^^
>ex2 : (callback?: (x: number) => void) => void
> : ^ ^^^ ^^^^^
>useCallback1((x) => {}) : (x: number) => void
> : ^ ^^^^^^^^^^^^^^^^^
>useCallback1 : <T extends Function>(fn: T) => T
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
>(x) => {} : (x: number) => void
> : ^ ^^^^^^^^^^^^^^^^^
>x : number
> : ^^^^^^

declare function ex3(callback: ((x: number) => void) | 5): void;
>ex3 : (callback: ((x: number) => void) | 5) => void
> : ^ ^^ ^^^^^
>callback : ((x: number) => void) | 5
> : ^^ ^^ ^^^^^ ^^^^^
>x : number
> : ^^^^^^

ex3(useCallback1((x) => {}));
>ex3(useCallback1((x) => {})) : void
> : ^^^^
>ex3 : (callback: ((x: number) => void) | 5) => void
> : ^ ^^ ^^^^^
>useCallback1((x) => {}) : (x: number) => void
> : ^ ^^^^^^^^^^^^^^^^^
>useCallback1 : <T extends Function>(fn: T) => T
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
>(x) => {} : (x: number) => void
> : ^ ^^^^^^^^^^^^^^^^^
>x : number
> : ^^^^^^

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

declare function useCallback2<T extends (...args: any[]) => any>(
>useCallback2 : <T extends (...args: any[]) => any>(callback: T) => T
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
>args : any[]
> : ^^^^^

callback: T,
>callback : T
> : ^

): T;
const test: ((x: string) => void) | undefined = useCallback2((x) => {});
>test : ((x: string) => void) | undefined
> : ^^ ^^ ^^^^^ ^^^^^^^^^^^^^
>x : string
> : ^^^^^^
>useCallback2((x) => {}) : (x: string) => void
> : ^ ^^^^^^^^^^^^^^^^^
>useCallback2 : <T extends (...args: any[]) => any>(callback: T) => T
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
>(x) => {} : (x: string) => void
> : ^ ^^^^^^^^^^^^^^^^^
>x : string
> : ^^^^^^

19 changes: 19 additions & 0 deletions tests/cases/compiler/inferenceContextualReturnTypeUnion1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// @strict: true
// @noEmit: true

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

declare function useCallback1<T extends Function>(fn: T): T;

declare function ex2(callback?: (x: number) => void): void;
ex2(useCallback1((x) => {}));

declare function ex3(callback: ((x: number) => void) | 5): void;
ex3(useCallback1((x) => {}));

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

declare function useCallback2<T extends (...args: any[]) => any>(
callback: T,
): T;
const test: ((x: string) => void) | undefined = useCallback2((x) => {});