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

Experiment: Remove lower priority from contextual signature instantiation return type inference #58321

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/compiler/checker.ts
Expand Up @@ -33856,11 +33856,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const sourceSignature = mapper ? instantiateSignature(contextualSignature, mapper) : contextualSignature;
applyToParameterTypes(sourceSignature, signature, (source, target) => {
// Type parameters from outer context referenced by source type are fixed by instantiation of the source type
inferTypes(context.inferences, source, target);
inferTypes(context.inferences, source, target, /*priority*/ undefined, /*contravariant*/ strictFunctionTypes);
});
if (!inferenceContext) {
applyToReturnTypes(contextualSignature, signature, (source, target) => {
inferTypes(context.inferences, source, target, InferencePriority.ReturnType);
inferTypes(context.inferences, source, target);
});
}
return getSignatureInstantiation(signature, getInferredTypes(context), isInJSFile(contextualSignature.declaration));
Expand Down
Expand Up @@ -50,19 +50,14 @@ assignmentCompatWithCallSignatures3.ts(80,1): error TS2322: Type '(x: Base[], y:
assignmentCompatWithCallSignatures3.ts(83,1): error TS2322: Type '(x: Base[], y: Derived[]) => Derived[]' is not assignable to type '<T extends Derived[]>(x: Base[], y: T) => T'.
Type 'Derived[]' is not assignable to type 'T'.
'Derived[]' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'Derived[]'.
assignmentCompatWithCallSignatures3.ts(85,1): error TS2322: Type '<T>(x: { a: T; b: T; }) => T' is not assignable to type '(x: { a: string; b: number; }) => Object'.
Types of parameters 'x' and 'x' are incompatible.
Type '{ a: string; b: number; }' is not assignable to type '{ a: string; b: string; }'.
Types of property 'b' are incompatible.
Type 'number' is not assignable to type 'string'.
assignmentCompatWithCallSignatures3.ts(86,1): error TS2322: Type '(x: { a: string; b: number; }) => Object' is not assignable to type '<T>(x: { a: T; b: T; }) => T'.
Types of parameters 'x' and 'x' are incompatible.
Type '{ a: T; b: T; }' is not assignable to type '{ a: string; b: number; }'.
Types of property 'a' are incompatible.
Type 'T' is not assignable to type 'string'.


==== assignmentCompatWithCallSignatures3.ts (15 errors) ====
==== assignmentCompatWithCallSignatures3.ts (14 errors) ====
// these are all permitted with the current rules, since we do not do contextual signature instantiation

class Base { foo: string; }
Expand Down Expand Up @@ -218,12 +213,6 @@ assignmentCompatWithCallSignatures3.ts(86,1): error TS2322: Type '(x: { a: strin
!!! error TS2322: 'Derived[]' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'Derived[]'.
var b14: <T>(x: { a: T; b: T }) => T;
a14 = b14; // ok
~~~
!!! error TS2322: Type '<T>(x: { a: T; b: T; }) => T' is not assignable to type '(x: { a: string; b: number; }) => Object'.
!!! error TS2322: Types of parameters 'x' and 'x' are incompatible.
!!! error TS2322: Type '{ a: string; b: number; }' is not assignable to type '{ a: string; b: string; }'.
!!! error TS2322: Types of property 'b' are incompatible.
!!! error TS2322: Type 'number' is not assignable to type 'string'.
b14 = a14; // ok
~~~
!!! error TS2322: Type '(x: { a: string; b: number; }) => Object' is not assignable to type '<T>(x: { a: T; b: T; }) => T'.
Expand Down
Expand Up @@ -50,19 +50,14 @@ assignmentCompatWithConstructSignatures3.ts(80,1): error TS2322: Type 'new (x: B
assignmentCompatWithConstructSignatures3.ts(83,1): error TS2322: Type 'new (x: Base[], y: Derived[]) => Derived[]' is not assignable to type 'new <T extends Derived[]>(x: Base[], y: T) => T'.
Type 'Derived[]' is not assignable to type 'T'.
'Derived[]' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'Derived[]'.
assignmentCompatWithConstructSignatures3.ts(85,1): error TS2322: Type 'new <T>(x: { a: T; b: T; }) => T' is not assignable to type 'new (x: { a: string; b: number; }) => Object'.
Types of parameters 'x' and 'x' are incompatible.
Type '{ a: string; b: number; }' is not assignable to type '{ a: string; b: string; }'.
Types of property 'b' are incompatible.
Type 'number' is not assignable to type 'string'.
assignmentCompatWithConstructSignatures3.ts(86,1): error TS2322: Type 'new (x: { a: string; b: number; }) => Object' is not assignable to type 'new <T>(x: { a: T; b: T; }) => T'.
Types of parameters 'x' and 'x' are incompatible.
Type '{ a: T; b: T; }' is not assignable to type '{ a: string; b: number; }'.
Types of property 'a' are incompatible.
Type 'T' is not assignable to type 'string'.


==== assignmentCompatWithConstructSignatures3.ts (15 errors) ====
==== assignmentCompatWithConstructSignatures3.ts (14 errors) ====
// checking assignment compatibility relations for function types. All of these are valid.

class Base { foo: string; }
Expand Down Expand Up @@ -218,12 +213,6 @@ assignmentCompatWithConstructSignatures3.ts(86,1): error TS2322: Type 'new (x: {
!!! error TS2322: 'Derived[]' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'Derived[]'.
var b14: new <T>(x: { a: T; b: T }) => T;
a14 = b14; // ok
~~~
!!! error TS2322: Type 'new <T>(x: { a: T; b: T; }) => T' is not assignable to type 'new (x: { a: string; b: number; }) => Object'.
!!! error TS2322: Types of parameters 'x' and 'x' are incompatible.
!!! error TS2322: Type '{ a: string; b: number; }' is not assignable to type '{ a: string; b: string; }'.
!!! error TS2322: Types of property 'b' are incompatible.
!!! error TS2322: Type 'number' is not assignable to type 'string'.
b14 = a14; // ok
~~~
!!! error TS2322: Type 'new (x: { a: string; b: number; }) => Object' is not assignable to type 'new <T>(x: { a: T; b: T; }) => T'.
Expand Down
@@ -0,0 +1,45 @@
callbackAssignabilityErrorMessage.ts(31,1): error TS2322: Type 'Source1' is not assignable to type 'Target'.
Types of property 'map' are incompatible.
Types of parameters 'callbackfn' and 'callbackfn' are incompatible.
Type 'T | undefined' is not assignable to type 'T'.
'T' could be instantiated with an arbitrary type which could be unrelated to 'T | undefined'.


==== callbackAssignabilityErrorMessage.ts (1 errors) ====
export interface Source1 {
map: <S>(callbackfn: (value: string) => S) => S[];
}

export interface Target {
map: <T>(callbackfn: (value: string) => T | undefined) => T[];
}

declare let s1: Source1;
declare let t: Target;

// During the following assignment, `Source1["map"]` gets
// instantiated with the contextual type `Target["map"]` before checking
// assignability. To do that, an inference is made to `S`. For `Source1`,
// the only candidate is `T | undefined` from the return type of `callbackfn`.
// Inference also runs on `map` return types `S[]` and `T[]`, but the result
// is discarded because it’s run with a lower inference priority. As a result,
// we end up seeing if the contextually instantiated source signature:
//
// (callbackfn: (value: string) => T | undefined) => (T | undefined)[]
//
// is assignable to the target signature:
//
// (callbackfn: (value: string) => T | undefined) => T[]
//
// and the return types cause the failed assignability. But as a human reader
// interpreting why `s1` is not assignable to `t`, I instead equate `S` and `T`
// and identify the “real” problem as the return type of `callbackfn` in `Target`
// (`T | undefined`) being a supertype of the one in `Source1` (`S` → `T`).

t = s1; // Bad: instantiates `S` with `T | undefined`, fails `map` return type assignability
~
!!! error TS2322: Type 'Source1' is not assignable to type 'Target'.
!!! error TS2322: Types of property 'map' are incompatible.
!!! error TS2322: Types of parameters 'callbackfn' and 'callbackfn' are incompatible.
!!! error TS2322: Type 'T | undefined' is not assignable to type 'T'.
!!! error TS2322: 'T' could be instantiated with an arbitrary type which could be unrelated to 'T | undefined'.
56 changes: 56 additions & 0 deletions tests/baselines/reference/callbackAssignabilityErrorMessage.js
@@ -0,0 +1,56 @@
//// [tests/cases/compiler/callbackAssignabilityErrorMessage.ts] ////

//// [callbackAssignabilityErrorMessage.ts]
export interface Source1 {
map: <S>(callbackfn: (value: string) => S) => S[];
}

export interface Target {
map: <T>(callbackfn: (value: string) => T | undefined) => T[];
}

declare let s1: Source1;
declare let t: Target;

// During the following assignment, `Source1["map"]` gets
// instantiated with the contextual type `Target["map"]` before checking
// assignability. To do that, an inference is made to `S`. For `Source1`,
// the only candidate is `T | undefined` from the return type of `callbackfn`.
// Inference also runs on `map` return types `S[]` and `T[]`, but the result
// is discarded because it’s run with a lower inference priority. As a result,
// we end up seeing if the contextually instantiated source signature:
//
// (callbackfn: (value: string) => T | undefined) => (T | undefined)[]
//
// is assignable to the target signature:
//
// (callbackfn: (value: string) => T | undefined) => T[]
//
// and the return types cause the failed assignability. But as a human reader
// interpreting why `s1` is not assignable to `t`, I instead equate `S` and `T`
// and identify the “real” problem as the return type of `callbackfn` in `Target`
// (`T | undefined`) being a supertype of the one in `Source1` (`S` → `T`).

t = s1; // Bad: instantiates `S` with `T | undefined`, fails `map` return type assignability

//// [callbackAssignabilityErrorMessage.js]
// During the following assignment, `Source1["map"]` gets
// instantiated with the contextual type `Target["map"]` before checking
// assignability. To do that, an inference is made to `S`. For `Source1`,
// the only candidate is `T | undefined` from the return type of `callbackfn`.
// Inference also runs on `map` return types `S[]` and `T[]`, but the result
// is discarded because it’s run with a lower inference priority. As a result,
// we end up seeing if the contextually instantiated source signature:
//
// (callbackfn: (value: string) => T | undefined) => (T | undefined)[]
//
// is assignable to the target signature:
//
// (callbackfn: (value: string) => T | undefined) => T[]
//
// and the return types cause the failed assignability. But as a human reader
// interpreting why `s1` is not assignable to `t`, I instead equate `S` and `T`
// and identify the “real” problem as the return type of `callbackfn` in `Target`
// (`T | undefined`) being a supertype of the one in `Source1` (`S` → `T`).
t = s1; // Bad: instantiates `S` with `T | undefined`, fails `map` return type assignability
export {};
@@ -0,0 +1,58 @@
//// [tests/cases/compiler/callbackAssignabilityErrorMessage.ts] ////

=== callbackAssignabilityErrorMessage.ts ===
export interface Source1 {
>Source1 : Symbol(Source1, Decl(callbackAssignabilityErrorMessage.ts, 0, 0))

map: <S>(callbackfn: (value: string) => S) => S[];
>map : Symbol(Source1.map, Decl(callbackAssignabilityErrorMessage.ts, 0, 26))
>S : Symbol(S, Decl(callbackAssignabilityErrorMessage.ts, 1, 8))
>callbackfn : Symbol(callbackfn, Decl(callbackAssignabilityErrorMessage.ts, 1, 11))
>value : Symbol(value, Decl(callbackAssignabilityErrorMessage.ts, 1, 24))
>S : Symbol(S, Decl(callbackAssignabilityErrorMessage.ts, 1, 8))
>S : Symbol(S, Decl(callbackAssignabilityErrorMessage.ts, 1, 8))
}

export interface Target {
>Target : Symbol(Target, Decl(callbackAssignabilityErrorMessage.ts, 2, 1))

map: <T>(callbackfn: (value: string) => T | undefined) => T[];
>map : Symbol(Target.map, Decl(callbackAssignabilityErrorMessage.ts, 4, 25))
>T : Symbol(T, Decl(callbackAssignabilityErrorMessage.ts, 5, 8))
>callbackfn : Symbol(callbackfn, Decl(callbackAssignabilityErrorMessage.ts, 5, 11))
>value : Symbol(value, Decl(callbackAssignabilityErrorMessage.ts, 5, 24))
>T : Symbol(T, Decl(callbackAssignabilityErrorMessage.ts, 5, 8))
>T : Symbol(T, Decl(callbackAssignabilityErrorMessage.ts, 5, 8))
}

declare let s1: Source1;
>s1 : Symbol(s1, Decl(callbackAssignabilityErrorMessage.ts, 8, 11))
>Source1 : Symbol(Source1, Decl(callbackAssignabilityErrorMessage.ts, 0, 0))

declare let t: Target;
>t : Symbol(t, Decl(callbackAssignabilityErrorMessage.ts, 9, 11))
>Target : Symbol(Target, Decl(callbackAssignabilityErrorMessage.ts, 2, 1))

// During the following assignment, `Source1["map"]` gets
// instantiated with the contextual type `Target["map"]` before checking
// assignability. To do that, an inference is made to `S`. For `Source1`,
// the only candidate is `T | undefined` from the return type of `callbackfn`.
// Inference also runs on `map` return types `S[]` and `T[]`, but the result
// is discarded because it’s run with a lower inference priority. As a result,
// we end up seeing if the contextually instantiated source signature:
//
// (callbackfn: (value: string) => T | undefined) => (T | undefined)[]
//
// is assignable to the target signature:
//
// (callbackfn: (value: string) => T | undefined) => T[]
//
// and the return types cause the failed assignability. But as a human reader
// interpreting why `s1` is not assignable to `t`, I instead equate `S` and `T`
// and identify the “real” problem as the return type of `callbackfn` in `Target`
// (`T | undefined`) being a supertype of the one in `Source1` (`S` → `T`).

t = s1; // Bad: instantiates `S` with `T | undefined`, fails `map` return type assignability
>t : Symbol(t, Decl(callbackAssignabilityErrorMessage.ts, 9, 11))
>s1 : Symbol(s1, Decl(callbackAssignabilityErrorMessage.ts, 8, 11))

58 changes: 58 additions & 0 deletions tests/baselines/reference/callbackAssignabilityErrorMessage.types
@@ -0,0 +1,58 @@
//// [tests/cases/compiler/callbackAssignabilityErrorMessage.ts] ////

=== callbackAssignabilityErrorMessage.ts ===
export interface Source1 {
map: <S>(callbackfn: (value: string) => S) => S[];
>map : <S>(callbackfn: (value: string) => S) => S[]
> : ^ ^^ ^^ ^^^^^
>callbackfn : (value: string) => S
> : ^ ^^ ^^^^^
>value : string
> : ^^^^^^
}

export interface Target {
map: <T>(callbackfn: (value: string) => T | undefined) => T[];
>map : <T>(callbackfn: (value: string) => T | undefined) => T[]
> : ^ ^^ ^^ ^^^^^
>callbackfn : (value: string) => T | undefined
> : ^ ^^ ^^^^^
>value : string
> : ^^^^^^
}

declare let s1: Source1;
>s1 : Source1
> : ^^^^^^^

declare let t: Target;
>t : Target
> : ^^^^^^

// During the following assignment, `Source1["map"]` gets
// instantiated with the contextual type `Target["map"]` before checking
// assignability. To do that, an inference is made to `S`. For `Source1`,
// the only candidate is `T | undefined` from the return type of `callbackfn`.
// Inference also runs on `map` return types `S[]` and `T[]`, but the result
// is discarded because it’s run with a lower inference priority. As a result,
// we end up seeing if the contextually instantiated source signature:
//
// (callbackfn: (value: string) => T | undefined) => (T | undefined)[]
//
// is assignable to the target signature:
//
// (callbackfn: (value: string) => T | undefined) => T[]
//
// and the return types cause the failed assignability. But as a human reader
// interpreting why `s1` is not assignable to `t`, I instead equate `S` and `T`
// and identify the “real” problem as the return type of `callbackfn` in `Target`
// (`T | undefined`) being a supertype of the one in `Source1` (`S` → `T`).

t = s1; // Bad: instantiates `S` with `T | undefined`, fails `map` return type assignability
>t = s1 : Source1
> : ^^^^^^^
>t : Target
> : ^^^^^^
>s1 : Source1
> : ^^^^^^^

@@ -1,9 +1,7 @@
mappedTypeInferenceFromApparentType.ts(10,1): error TS2322: Type 'foo' is not assignable to type 'bar'.
Types of parameters 'target' and 'source' are incompatible.
Type '{ [K in keyof U]: Obj[K]; }' is not assignable to type '{ [K in keyof U]: U[K]; }'.
Type 'Obj[K]' is not assignable to type 'U[K]'.
Type 'Obj' is not assignable to type 'U'.
'U' could be instantiated with an arbitrary type which could be unrelated to 'Obj'.
Type '{ [K in keyof U]: Obj[K]; }' is not assignable to type '{ [x: string]: number; }'.
Index signature for type 'string' is missing in type 'number[]'.


==== mappedTypeInferenceFromApparentType.ts (1 errors) ====
Expand All @@ -20,7 +18,5 @@ mappedTypeInferenceFromApparentType.ts(10,1): error TS2322: Type 'foo' is not as
~
!!! error TS2322: Type 'foo' is not assignable to type 'bar'.
!!! error TS2322: Types of parameters 'target' and 'source' are incompatible.
!!! error TS2322: Type '{ [K in keyof U]: Obj[K]; }' is not assignable to type '{ [K in keyof U]: U[K]; }'.
!!! error TS2322: Type 'Obj[K]' is not assignable to type 'U[K]'.
!!! error TS2322: Type 'Obj' is not assignable to type 'U'.
!!! error TS2322: 'U' could be instantiated with an arbitrary type which could be unrelated to 'Obj'.
!!! error TS2322: Type '{ [K in keyof U]: Obj[K]; }' is not assignable to type '{ [x: string]: number; }'.
Copy link
Member

Choose a reason for hiding this comment

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

I wrote this test to exercise a very specific inference code path in the PR that introduced it, so it's not grounded in a real world scenario, but I don't think the new error is worse? Maybe the connection between the error and the actual code is more surprising now just because we end up inferring T = { [x: string]: number } instead of T = U.

Maybe that's a separate problem with signature comparison: it's not immediately clear to users that we've done inference in order to compare two signatures, and it's not immediately clear what the result of that inference was.

!!! error TS2322: Index signature for type 'string' is missing in type 'number[]'.
6 changes: 4 additions & 2 deletions tests/baselines/reference/subtypingWithCallSignatures2.types
Expand Up @@ -1178,8 +1178,10 @@ var r14arg2 = (x: { a: string; b: number }) => <Object>null;
> : ^^^^^^

var r14 = foo14(r14arg1); // any
>r14 : any
>foo14(r14arg1) : any
>r14 : (x: { a: string; b: number; }) => Object
> : ^ ^^ ^^^^^^^^^^^
>foo14(r14arg1) : (x: { a: string; b: number; }) => Object
> : ^ ^^ ^^^^^^^^^^^
>foo14 : { (a: (x: { a: string; b: number; }) => Object): (x: { a: string; b: number; }) => Object; (a: any): any; }
> : ^^^ ^^ ^^^^ ^^ ^^^^^^^^^^^^^^ ^^ ^^^^^^^^^
>r14arg1 : <T>(x: { a: T; b: T; }) => T
Expand Down
Expand Up @@ -1038,8 +1038,10 @@ var r14arg2: new (x: { a: string; b: number }) => Object;
> : ^^^^^^

var r14 = foo14(r14arg1); // any
>r14 : any
>foo14(r14arg1) : any
>r14 : new (x: { a: string; b: number; }) => Object
> : ^^^^^ ^^ ^^^^^^^^^^^
>foo14(r14arg1) : new (x: { a: string; b: number; }) => Object
> : ^^^^^ ^^ ^^^^^^^^^^^
>foo14 : { (a: new (x: { a: string; b: number; }) => Object): new (x: { a: string; b: number; }) => Object; (a: any): any; }
> : ^^^ ^^ ^^^^^^^^ ^^ ^^^^^^^^^^^^^^ ^^ ^^^^^^^^^
>r14arg1 : new <T>(x: { a: T; b: T; }) => T
Expand Down