Skip to content

Commit

Permalink
Infer from annotated return type nodes before assigning contextual pa…
Browse files Browse the repository at this point in the history
…rameter types (#60964)
  • Loading branch information
Andarist authored Feb 20, 2025
1 parent 12c2323 commit 0d01692
Show file tree
Hide file tree
Showing 5 changed files with 289 additions and 3 deletions.
12 changes: 9 additions & 3 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37870,7 +37870,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return signature.parameters.length > 0 ? getTypeAtPosition(signature, 0) : fallbackType;
}

function inferFromAnnotatedParameters(signature: Signature, context: Signature, inferenceContext: InferenceContext) {
function inferFromAnnotatedParametersAndReturn(signature: Signature, context: Signature, inferenceContext: InferenceContext) {
const len = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0);
for (let i = 0; i < len; i++) {
const declaration = signature.parameters[i].valueDeclaration as ParameterDeclaration;
Expand All @@ -37881,6 +37881,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
inferTypes(inferenceContext.inferences, source, target);
}
}
const typeNode = signature.declaration && getEffectiveReturnTypeNode(signature.declaration);
if (typeNode) {
const source = getTypeFromTypeNode(typeNode);
const target = getReturnTypeOfSignature(context);
inferTypes(inferenceContext.inferences, source, target);
}
}

function assignContextualParameterTypes(signature: Signature, context: Signature) {
Expand Down Expand Up @@ -38878,7 +38884,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const inferenceContext = getInferenceContext(node);
let instantiatedContextualSignature: Signature | undefined;
if (checkMode && checkMode & CheckMode.Inferential) {
inferFromAnnotatedParameters(signature, contextualSignature, inferenceContext!);
inferFromAnnotatedParametersAndReturn(signature, contextualSignature, inferenceContext!);
const restType = getEffectiveRestType(contextualSignature);
if (restType && restType.flags & TypeFlags.TypeParameter) {
instantiatedContextualSignature = instantiateSignature(contextualSignature, inferenceContext!.nonFixingMapper);
Expand All @@ -38896,7 +38902,7 @@ 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) {
inferFromAnnotatedParameters(signature, contextualSignature, inferenceContext!);
inferFromAnnotatedParametersAndReturn(signature, contextualSignature, inferenceContext!);
}
}
if (contextualSignature && !getReturnTypeFromAnnotation(node) && !signature.resolvedReturnType) {
Expand Down
30 changes: 30 additions & 0 deletions tests/baselines/reference/inferFromAnnotatedReturn1.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
inferFromAnnotatedReturn1.ts(4,36): error TS2322: Type 'string' is not assignable to type 'number'.


==== inferFromAnnotatedReturn1.ts (1 errors) ====
declare function test<T>(cb: (arg: T) => T): T;

const res1 = test((arg): number => 1); // ok
const res2 = test((arg): number => 'foo'); // error
~~~~~
!!! error TS2322: Type 'string' is not assignable to type 'number'.

export declare function linkedSignal<S, D>(options: {
source: () => S;
computation: (source: NoInfer<D>) => D;
}): D;

const signal = linkedSignal({
source: () => 3,
computation: (s): number => 3,
});

class Foo<T, R> {
constructor(readonly cb: (t: T, _: { x: number; other: NoInfer<R> }) => R) {}
}

const _1 = new Foo((name: string, { x }): { name: string; x: number } => ({
name,
x,
}));

88 changes: 88 additions & 0 deletions tests/baselines/reference/inferFromAnnotatedReturn1.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//// [tests/cases/compiler/inferFromAnnotatedReturn1.ts] ////

=== inferFromAnnotatedReturn1.ts ===
declare function test<T>(cb: (arg: T) => T): T;
>test : Symbol(test, Decl(inferFromAnnotatedReturn1.ts, 0, 0))
>T : Symbol(T, Decl(inferFromAnnotatedReturn1.ts, 0, 22))
>cb : Symbol(cb, Decl(inferFromAnnotatedReturn1.ts, 0, 25))
>arg : Symbol(arg, Decl(inferFromAnnotatedReturn1.ts, 0, 30))
>T : Symbol(T, Decl(inferFromAnnotatedReturn1.ts, 0, 22))
>T : Symbol(T, Decl(inferFromAnnotatedReturn1.ts, 0, 22))
>T : Symbol(T, Decl(inferFromAnnotatedReturn1.ts, 0, 22))

const res1 = test((arg): number => 1); // ok
>res1 : Symbol(res1, Decl(inferFromAnnotatedReturn1.ts, 2, 5))
>test : Symbol(test, Decl(inferFromAnnotatedReturn1.ts, 0, 0))
>arg : Symbol(arg, Decl(inferFromAnnotatedReturn1.ts, 2, 19))

const res2 = test((arg): number => 'foo'); // error
>res2 : Symbol(res2, Decl(inferFromAnnotatedReturn1.ts, 3, 5))
>test : Symbol(test, Decl(inferFromAnnotatedReturn1.ts, 0, 0))
>arg : Symbol(arg, Decl(inferFromAnnotatedReturn1.ts, 3, 19))

export declare function linkedSignal<S, D>(options: {
>linkedSignal : Symbol(linkedSignal, Decl(inferFromAnnotatedReturn1.ts, 3, 42))
>S : Symbol(S, Decl(inferFromAnnotatedReturn1.ts, 5, 37))
>D : Symbol(D, Decl(inferFromAnnotatedReturn1.ts, 5, 39))
>options : Symbol(options, Decl(inferFromAnnotatedReturn1.ts, 5, 43))

source: () => S;
>source : Symbol(source, Decl(inferFromAnnotatedReturn1.ts, 5, 53))
>S : Symbol(S, Decl(inferFromAnnotatedReturn1.ts, 5, 37))

computation: (source: NoInfer<D>) => D;
>computation : Symbol(computation, Decl(inferFromAnnotatedReturn1.ts, 6, 18))
>source : Symbol(source, Decl(inferFromAnnotatedReturn1.ts, 7, 16))
>NoInfer : Symbol(NoInfer, Decl(lib.es5.d.ts, --, --))
>D : Symbol(D, Decl(inferFromAnnotatedReturn1.ts, 5, 39))
>D : Symbol(D, Decl(inferFromAnnotatedReturn1.ts, 5, 39))

}): D;
>D : Symbol(D, Decl(inferFromAnnotatedReturn1.ts, 5, 39))

const signal = linkedSignal({
>signal : Symbol(signal, Decl(inferFromAnnotatedReturn1.ts, 10, 5))
>linkedSignal : Symbol(linkedSignal, Decl(inferFromAnnotatedReturn1.ts, 3, 42))

source: () => 3,
>source : Symbol(source, Decl(inferFromAnnotatedReturn1.ts, 10, 29))

computation: (s): number => 3,
>computation : Symbol(computation, Decl(inferFromAnnotatedReturn1.ts, 11, 18))
>s : Symbol(s, Decl(inferFromAnnotatedReturn1.ts, 12, 16))

});

class Foo<T, R> {
>Foo : Symbol(Foo, Decl(inferFromAnnotatedReturn1.ts, 13, 3))
>T : Symbol(T, Decl(inferFromAnnotatedReturn1.ts, 15, 10))
>R : Symbol(R, Decl(inferFromAnnotatedReturn1.ts, 15, 12))

constructor(readonly cb: (t: T, _: { x: number; other: NoInfer<R> }) => R) {}
>cb : Symbol(Foo.cb, Decl(inferFromAnnotatedReturn1.ts, 16, 14))
>t : Symbol(t, Decl(inferFromAnnotatedReturn1.ts, 16, 28))
>T : Symbol(T, Decl(inferFromAnnotatedReturn1.ts, 15, 10))
>_ : Symbol(_, Decl(inferFromAnnotatedReturn1.ts, 16, 33))
>x : Symbol(x, Decl(inferFromAnnotatedReturn1.ts, 16, 38))
>other : Symbol(other, Decl(inferFromAnnotatedReturn1.ts, 16, 49))
>NoInfer : Symbol(NoInfer, Decl(lib.es5.d.ts, --, --))
>R : Symbol(R, Decl(inferFromAnnotatedReturn1.ts, 15, 12))
>R : Symbol(R, Decl(inferFromAnnotatedReturn1.ts, 15, 12))
}

const _1 = new Foo((name: string, { x }): { name: string; x: number } => ({
>_1 : Symbol(_1, Decl(inferFromAnnotatedReturn1.ts, 19, 5))
>Foo : Symbol(Foo, Decl(inferFromAnnotatedReturn1.ts, 13, 3))
>name : Symbol(name, Decl(inferFromAnnotatedReturn1.ts, 19, 20))
>x : Symbol(x, Decl(inferFromAnnotatedReturn1.ts, 19, 35))
>name : Symbol(name, Decl(inferFromAnnotatedReturn1.ts, 19, 43))
>x : Symbol(x, Decl(inferFromAnnotatedReturn1.ts, 19, 57))

name,
>name : Symbol(name, Decl(inferFromAnnotatedReturn1.ts, 19, 75))

x,
>x : Symbol(x, Decl(inferFromAnnotatedReturn1.ts, 20, 7))

}));

136 changes: 136 additions & 0 deletions tests/baselines/reference/inferFromAnnotatedReturn1.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
//// [tests/cases/compiler/inferFromAnnotatedReturn1.ts] ////

=== inferFromAnnotatedReturn1.ts ===
declare function test<T>(cb: (arg: T) => T): T;
>test : <T>(cb: (arg: T) => T) => T
> : ^ ^^ ^^ ^^^^^
>cb : (arg: T) => T
> : ^ ^^ ^^^^^
>arg : T
> : ^

const res1 = test((arg): number => 1); // ok
>res1 : number
> : ^^^^^^
>test((arg): number => 1) : number
> : ^^^^^^
>test : <T>(cb: (arg: T) => T) => T
> : ^ ^^ ^^ ^^^^^
>(arg): number => 1 : (arg: number) => number
> : ^ ^^^^^^^^^^^^^
>arg : number
> : ^^^^^^
>1 : 1
> : ^

const res2 = test((arg): number => 'foo'); // error
>res2 : number
> : ^^^^^^
>test((arg): number => 'foo') : number
> : ^^^^^^
>test : <T>(cb: (arg: T) => T) => T
> : ^ ^^ ^^ ^^^^^
>(arg): number => 'foo' : (arg: number) => number
> : ^ ^^^^^^^^^^^^^
>arg : number
> : ^^^^^^
>'foo' : "foo"
> : ^^^^^

export declare function linkedSignal<S, D>(options: {
>linkedSignal : <S, D>(options: { source: () => S; computation: (source: NoInfer<D>) => D; }) => D
> : ^ ^^ ^^ ^^ ^^^^^
>options : { source: () => S; computation: (source: NoInfer<D>) => D; }
> : ^^^^^^^^^^ ^^^^^^^^^^^^^^^ ^^^

source: () => S;
>source : () => S
> : ^^^^^^

computation: (source: NoInfer<D>) => D;
>computation : (source: NoInfer<D>) => D
> : ^ ^^ ^^^^^
>source : NoInfer<D>
> : ^^^^^^^^^^

}): D;

const signal = linkedSignal({
>signal : number
> : ^^^^^^
>linkedSignal({ source: () => 3, computation: (s): number => 3,}) : number
> : ^^^^^^
>linkedSignal : <S, D>(options: { source: () => S; computation: (source: NoInfer<D>) => D; }) => D
> : ^ ^^ ^^ ^^ ^^^^^
>{ source: () => 3, computation: (s): number => 3,} : { source: () => number; computation: (s: number) => number; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^ ^^^

source: () => 3,
>source : () => number
> : ^^^^^^^^^^^^
>() => 3 : () => number
> : ^^^^^^^^^^^^
>3 : 3
> : ^

computation: (s): number => 3,
>computation : (s: number) => number
> : ^ ^^^^^^^^^^^^^
>(s): number => 3 : (s: number) => number
> : ^ ^^^^^^^^^^^^^
>s : number
> : ^^^^^^
>3 : 3
> : ^

});

class Foo<T, R> {
>Foo : Foo<T, R>
> : ^^^^^^^^^

constructor(readonly cb: (t: T, _: { x: number; other: NoInfer<R> }) => R) {}
>cb : (t: T, _: { x: number; other: NoInfer<R>; }) => R
> : ^ ^^ ^^ ^^ ^^^^^
>t : T
> : ^
>_ : { x: number; other: NoInfer<R>; }
> : ^^^^^ ^^^^^^^^^ ^^^
>x : number
> : ^^^^^^
>other : NoInfer<R>
> : ^^^^^^^^^^
}

const _1 = new Foo((name: string, { x }): { name: string; x: number } => ({
>_1 : Foo<string, { name: string; x: number; }>
> : ^^^^^^^^^^^^^^^^^^^^ ^^^^^ ^^^^
>new Foo((name: string, { x }): { name: string; x: number } => ({ name, x,})) : Foo<string, { name: string; x: number; }>
> : ^^^^^^^^^^^^^^^^^^^^ ^^^^^ ^^^^
>Foo : typeof Foo
> : ^^^^^^^^^^
>(name: string, { x }): { name: string; x: number } => ({ name, x,}) : (name: string, { x }: { x: number; other: NoInfer<{ name: string; x: number; }>; }) => { name: string; x: number; }
> : ^ ^^ ^^ ^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^ ^^^^^^^^^^^^
>name : string
> : ^^^^^^
>x : number
> : ^^^^^^
>name : string
> : ^^^^^^
>x : number
> : ^^^^^^
>({ name, x,}) : { name: string; x: number; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>{ name, x,} : { name: string; x: number; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

name,
>name : string
> : ^^^^^^

x,
>x : number
> : ^^^^^^

}));

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

declare function test<T>(cb: (arg: T) => T): T;

const res1 = test((arg): number => 1); // ok
const res2 = test((arg): number => 'foo'); // error

export declare function linkedSignal<S, D>(options: {
source: () => S;
computation: (source: NoInfer<D>) => D;
}): D;

const signal = linkedSignal({
source: () => 3,
computation: (s): number => 3,
});

class Foo<T, R> {
constructor(readonly cb: (t: T, _: { x: number; other: NoInfer<R> }) => R) {}
}

const _1 = new Foo((name: string, { x }): { name: string; x: number } => ({
name,
x,
}));

0 comments on commit 0d01692

Please sign in to comment.