mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-06 20:14:01 -06:00
Do not covariantly mix in constraints from contravarrying positions (#43439)
* Do not covariantly mix in constraints from contravarrying positions * Exclude keyof from constraint variance tracking * Extra test case * Always subtitute on type parameter types
This commit is contained in:
parent
2f36065588
commit
f03e59ecc9
@ -12854,9 +12854,17 @@ namespace ts {
|
||||
|
||||
function getConditionalFlowTypeOfType(type: Type, node: Node) {
|
||||
let constraints: Type[] | undefined;
|
||||
let covariant = true;
|
||||
while (node && !isStatement(node) && node.kind !== SyntaxKind.JSDocComment) {
|
||||
const parent = node.parent;
|
||||
if (parent.kind === SyntaxKind.ConditionalType && node === (<ConditionalTypeNode>parent).trueType) {
|
||||
// only consider variance flipped by parameter locations - `keyof` types would usually be considered variance inverting, but
|
||||
// often get used in indexed accesses where they behave sortof invariantly, but our checking is lax
|
||||
if (parent.kind === SyntaxKind.Parameter) {
|
||||
covariant = !covariant;
|
||||
}
|
||||
// Always substitute on type parameters, regardless of variance, since even
|
||||
// in contravarrying positions, they may be reliant on subtuted constraints to be valid
|
||||
if ((covariant || type.flags & TypeFlags.TypeParameter) && parent.kind === SyntaxKind.ConditionalType && node === (<ConditionalTypeNode>parent).trueType) {
|
||||
const constraint = getImpliedConstraint(type, (<ConditionalTypeNode>parent).checkType, (<ConditionalTypeNode>parent).extendsType);
|
||||
if (constraint) {
|
||||
constraints = append(constraints, constraint);
|
||||
|
||||
@ -0,0 +1,53 @@
|
||||
//// [callOfConditionalTypeWithConcreteBranches.ts]
|
||||
type Q<T> = number extends T ? (n: number) => void : never;
|
||||
function fn<T>(arg: Q<T>) {
|
||||
// Expected: OK
|
||||
// Actual: Cannot convert 10 to number & T
|
||||
arg(10);
|
||||
}
|
||||
// Legal invocations are not problematic
|
||||
fn<string | number>(m => m.toFixed());
|
||||
fn<number>(m => m.toFixed());
|
||||
|
||||
// Ensure the following real-world example that relies on substitution still works
|
||||
type ExtractParameters<T> = "parameters" extends keyof T
|
||||
// The above allows "parameters" to index `T` since all later
|
||||
// instances are actually implicitly `"parameters" & keyof T`
|
||||
? {
|
||||
[K in keyof T["parameters"]]: T["parameters"][K];
|
||||
}[keyof T["parameters"]]
|
||||
: {};
|
||||
|
||||
// Original example, but with inverted variance
|
||||
type Q2<T> = number extends T ? (cb: (n: number) => void) => void : never;
|
||||
function fn2<T>(arg: Q2<T>) {
|
||||
function useT(_arg: T): void {}
|
||||
// Expected: OK
|
||||
arg(arg => useT(arg));
|
||||
}
|
||||
// Legal invocations are not problematic
|
||||
fn2<string | number>(m => m(42));
|
||||
fn2<number>(m => m(42));
|
||||
|
||||
// webidl-conversions example where substituion must occur, despite contravariance of the position
|
||||
// due to the invariant usage in `Parameters`
|
||||
|
||||
type X<V> = V extends (...args: any[]) => any ? (...args: Parameters<V>) => void : Function;
|
||||
|
||||
//// [callOfConditionalTypeWithConcreteBranches.js]
|
||||
function fn(arg) {
|
||||
// Expected: OK
|
||||
// Actual: Cannot convert 10 to number & T
|
||||
arg(10);
|
||||
}
|
||||
// Legal invocations are not problematic
|
||||
fn(function (m) { return m.toFixed(); });
|
||||
fn(function (m) { return m.toFixed(); });
|
||||
function fn2(arg) {
|
||||
function useT(_arg) { }
|
||||
// Expected: OK
|
||||
arg(function (arg) { return useT(arg); });
|
||||
}
|
||||
// Legal invocations are not problematic
|
||||
fn2(function (m) { return m(42); });
|
||||
fn2(function (m) { return m(42); });
|
||||
@ -0,0 +1,105 @@
|
||||
=== tests/cases/compiler/callOfConditionalTypeWithConcreteBranches.ts ===
|
||||
type Q<T> = number extends T ? (n: number) => void : never;
|
||||
>Q : Symbol(Q, Decl(callOfConditionalTypeWithConcreteBranches.ts, 0, 0))
|
||||
>T : Symbol(T, Decl(callOfConditionalTypeWithConcreteBranches.ts, 0, 7))
|
||||
>T : Symbol(T, Decl(callOfConditionalTypeWithConcreteBranches.ts, 0, 7))
|
||||
>n : Symbol(n, Decl(callOfConditionalTypeWithConcreteBranches.ts, 0, 32))
|
||||
|
||||
function fn<T>(arg: Q<T>) {
|
||||
>fn : Symbol(fn, Decl(callOfConditionalTypeWithConcreteBranches.ts, 0, 59))
|
||||
>T : Symbol(T, Decl(callOfConditionalTypeWithConcreteBranches.ts, 1, 12))
|
||||
>arg : Symbol(arg, Decl(callOfConditionalTypeWithConcreteBranches.ts, 1, 15))
|
||||
>Q : Symbol(Q, Decl(callOfConditionalTypeWithConcreteBranches.ts, 0, 0))
|
||||
>T : Symbol(T, Decl(callOfConditionalTypeWithConcreteBranches.ts, 1, 12))
|
||||
|
||||
// Expected: OK
|
||||
// Actual: Cannot convert 10 to number & T
|
||||
arg(10);
|
||||
>arg : Symbol(arg, Decl(callOfConditionalTypeWithConcreteBranches.ts, 1, 15))
|
||||
}
|
||||
// Legal invocations are not problematic
|
||||
fn<string | number>(m => m.toFixed());
|
||||
>fn : Symbol(fn, Decl(callOfConditionalTypeWithConcreteBranches.ts, 0, 59))
|
||||
>m : Symbol(m, Decl(callOfConditionalTypeWithConcreteBranches.ts, 7, 20))
|
||||
>m.toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --))
|
||||
>m : Symbol(m, Decl(callOfConditionalTypeWithConcreteBranches.ts, 7, 20))
|
||||
>toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --))
|
||||
|
||||
fn<number>(m => m.toFixed());
|
||||
>fn : Symbol(fn, Decl(callOfConditionalTypeWithConcreteBranches.ts, 0, 59))
|
||||
>m : Symbol(m, Decl(callOfConditionalTypeWithConcreteBranches.ts, 8, 11))
|
||||
>m.toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --))
|
||||
>m : Symbol(m, Decl(callOfConditionalTypeWithConcreteBranches.ts, 8, 11))
|
||||
>toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --))
|
||||
|
||||
// Ensure the following real-world example that relies on substitution still works
|
||||
type ExtractParameters<T> = "parameters" extends keyof T
|
||||
>ExtractParameters : Symbol(ExtractParameters, Decl(callOfConditionalTypeWithConcreteBranches.ts, 8, 29))
|
||||
>T : Symbol(T, Decl(callOfConditionalTypeWithConcreteBranches.ts, 11, 23))
|
||||
>T : Symbol(T, Decl(callOfConditionalTypeWithConcreteBranches.ts, 11, 23))
|
||||
|
||||
// The above allows "parameters" to index `T` since all later
|
||||
// instances are actually implicitly `"parameters" & keyof T`
|
||||
? {
|
||||
[K in keyof T["parameters"]]: T["parameters"][K];
|
||||
>K : Symbol(K, Decl(callOfConditionalTypeWithConcreteBranches.ts, 15, 9))
|
||||
>T : Symbol(T, Decl(callOfConditionalTypeWithConcreteBranches.ts, 11, 23))
|
||||
>T : Symbol(T, Decl(callOfConditionalTypeWithConcreteBranches.ts, 11, 23))
|
||||
>K : Symbol(K, Decl(callOfConditionalTypeWithConcreteBranches.ts, 15, 9))
|
||||
|
||||
}[keyof T["parameters"]]
|
||||
>T : Symbol(T, Decl(callOfConditionalTypeWithConcreteBranches.ts, 11, 23))
|
||||
|
||||
: {};
|
||||
|
||||
// Original example, but with inverted variance
|
||||
type Q2<T> = number extends T ? (cb: (n: number) => void) => void : never;
|
||||
>Q2 : Symbol(Q2, Decl(callOfConditionalTypeWithConcreteBranches.ts, 17, 7))
|
||||
>T : Symbol(T, Decl(callOfConditionalTypeWithConcreteBranches.ts, 20, 8))
|
||||
>T : Symbol(T, Decl(callOfConditionalTypeWithConcreteBranches.ts, 20, 8))
|
||||
>cb : Symbol(cb, Decl(callOfConditionalTypeWithConcreteBranches.ts, 20, 33))
|
||||
>n : Symbol(n, Decl(callOfConditionalTypeWithConcreteBranches.ts, 20, 38))
|
||||
|
||||
function fn2<T>(arg: Q2<T>) {
|
||||
>fn2 : Symbol(fn2, Decl(callOfConditionalTypeWithConcreteBranches.ts, 20, 74))
|
||||
>T : Symbol(T, Decl(callOfConditionalTypeWithConcreteBranches.ts, 21, 13))
|
||||
>arg : Symbol(arg, Decl(callOfConditionalTypeWithConcreteBranches.ts, 21, 16))
|
||||
>Q2 : Symbol(Q2, Decl(callOfConditionalTypeWithConcreteBranches.ts, 17, 7))
|
||||
>T : Symbol(T, Decl(callOfConditionalTypeWithConcreteBranches.ts, 21, 13))
|
||||
|
||||
function useT(_arg: T): void {}
|
||||
>useT : Symbol(useT, Decl(callOfConditionalTypeWithConcreteBranches.ts, 21, 29))
|
||||
>_arg : Symbol(_arg, Decl(callOfConditionalTypeWithConcreteBranches.ts, 22, 16))
|
||||
>T : Symbol(T, Decl(callOfConditionalTypeWithConcreteBranches.ts, 21, 13))
|
||||
|
||||
// Expected: OK
|
||||
arg(arg => useT(arg));
|
||||
>arg : Symbol(arg, Decl(callOfConditionalTypeWithConcreteBranches.ts, 21, 16))
|
||||
>arg : Symbol(arg, Decl(callOfConditionalTypeWithConcreteBranches.ts, 24, 6))
|
||||
>useT : Symbol(useT, Decl(callOfConditionalTypeWithConcreteBranches.ts, 21, 29))
|
||||
>arg : Symbol(arg, Decl(callOfConditionalTypeWithConcreteBranches.ts, 24, 6))
|
||||
}
|
||||
// Legal invocations are not problematic
|
||||
fn2<string | number>(m => m(42));
|
||||
>fn2 : Symbol(fn2, Decl(callOfConditionalTypeWithConcreteBranches.ts, 20, 74))
|
||||
>m : Symbol(m, Decl(callOfConditionalTypeWithConcreteBranches.ts, 27, 21))
|
||||
>m : Symbol(m, Decl(callOfConditionalTypeWithConcreteBranches.ts, 27, 21))
|
||||
|
||||
fn2<number>(m => m(42));
|
||||
>fn2 : Symbol(fn2, Decl(callOfConditionalTypeWithConcreteBranches.ts, 20, 74))
|
||||
>m : Symbol(m, Decl(callOfConditionalTypeWithConcreteBranches.ts, 28, 12))
|
||||
>m : Symbol(m, Decl(callOfConditionalTypeWithConcreteBranches.ts, 28, 12))
|
||||
|
||||
// webidl-conversions example where substituion must occur, despite contravariance of the position
|
||||
// due to the invariant usage in `Parameters`
|
||||
|
||||
type X<V> = V extends (...args: any[]) => any ? (...args: Parameters<V>) => void : Function;
|
||||
>X : Symbol(X, Decl(callOfConditionalTypeWithConcreteBranches.ts, 28, 24))
|
||||
>V : Symbol(V, Decl(callOfConditionalTypeWithConcreteBranches.ts, 33, 7))
|
||||
>V : Symbol(V, Decl(callOfConditionalTypeWithConcreteBranches.ts, 33, 7))
|
||||
>args : Symbol(args, Decl(callOfConditionalTypeWithConcreteBranches.ts, 33, 23))
|
||||
>args : Symbol(args, Decl(callOfConditionalTypeWithConcreteBranches.ts, 33, 49))
|
||||
>Parameters : Symbol(Parameters, Decl(lib.es5.d.ts, --, --))
|
||||
>V : Symbol(V, Decl(callOfConditionalTypeWithConcreteBranches.ts, 33, 7))
|
||||
>Function : Symbol(Function, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
|
||||
|
||||
@ -0,0 +1,99 @@
|
||||
=== tests/cases/compiler/callOfConditionalTypeWithConcreteBranches.ts ===
|
||||
type Q<T> = number extends T ? (n: number) => void : never;
|
||||
>Q : Q<T>
|
||||
>n : number
|
||||
|
||||
function fn<T>(arg: Q<T>) {
|
||||
>fn : <T>(arg: Q<T>) => void
|
||||
>arg : Q<T>
|
||||
|
||||
// Expected: OK
|
||||
// Actual: Cannot convert 10 to number & T
|
||||
arg(10);
|
||||
>arg(10) : void
|
||||
>arg : Q<T>
|
||||
>10 : 10
|
||||
}
|
||||
// Legal invocations are not problematic
|
||||
fn<string | number>(m => m.toFixed());
|
||||
>fn<string | number>(m => m.toFixed()) : void
|
||||
>fn : <T>(arg: Q<T>) => void
|
||||
>m => m.toFixed() : (m: number) => string
|
||||
>m : number
|
||||
>m.toFixed() : string
|
||||
>m.toFixed : (fractionDigits?: number) => string
|
||||
>m : number
|
||||
>toFixed : (fractionDigits?: number) => string
|
||||
|
||||
fn<number>(m => m.toFixed());
|
||||
>fn<number>(m => m.toFixed()) : void
|
||||
>fn : <T>(arg: Q<T>) => void
|
||||
>m => m.toFixed() : (m: number) => string
|
||||
>m : number
|
||||
>m.toFixed() : string
|
||||
>m.toFixed : (fractionDigits?: number) => string
|
||||
>m : number
|
||||
>toFixed : (fractionDigits?: number) => string
|
||||
|
||||
// Ensure the following real-world example that relies on substitution still works
|
||||
type ExtractParameters<T> = "parameters" extends keyof T
|
||||
>ExtractParameters : ExtractParameters<T>
|
||||
|
||||
// The above allows "parameters" to index `T` since all later
|
||||
// instances are actually implicitly `"parameters" & keyof T`
|
||||
? {
|
||||
[K in keyof T["parameters"]]: T["parameters"][K];
|
||||
}[keyof T["parameters"]]
|
||||
: {};
|
||||
|
||||
// Original example, but with inverted variance
|
||||
type Q2<T> = number extends T ? (cb: (n: number) => void) => void : never;
|
||||
>Q2 : Q2<T>
|
||||
>cb : (n: number) => void
|
||||
>n : number
|
||||
|
||||
function fn2<T>(arg: Q2<T>) {
|
||||
>fn2 : <T>(arg: Q2<T>) => void
|
||||
>arg : Q2<T>
|
||||
|
||||
function useT(_arg: T): void {}
|
||||
>useT : (_arg: T) => void
|
||||
>_arg : T
|
||||
|
||||
// Expected: OK
|
||||
arg(arg => useT(arg));
|
||||
>arg(arg => useT(arg)) : void
|
||||
>arg : Q2<T>
|
||||
>arg => useT(arg) : (arg: T & number) => void
|
||||
>arg : T & number
|
||||
>useT(arg) : void
|
||||
>useT : (_arg: T) => void
|
||||
>arg : T & number
|
||||
}
|
||||
// Legal invocations are not problematic
|
||||
fn2<string | number>(m => m(42));
|
||||
>fn2<string | number>(m => m(42)) : void
|
||||
>fn2 : <T>(arg: Q2<T>) => void
|
||||
>m => m(42) : (m: (n: number) => void) => void
|
||||
>m : (n: number) => void
|
||||
>m(42) : void
|
||||
>m : (n: number) => void
|
||||
>42 : 42
|
||||
|
||||
fn2<number>(m => m(42));
|
||||
>fn2<number>(m => m(42)) : void
|
||||
>fn2 : <T>(arg: Q2<T>) => void
|
||||
>m => m(42) : (m: (n: number) => void) => void
|
||||
>m : (n: number) => void
|
||||
>m(42) : void
|
||||
>m : (n: number) => void
|
||||
>42 : 42
|
||||
|
||||
// webidl-conversions example where substituion must occur, despite contravariance of the position
|
||||
// due to the invariant usage in `Parameters`
|
||||
|
||||
type X<V> = V extends (...args: any[]) => any ? (...args: Parameters<V>) => void : Function;
|
||||
>X : X<V>
|
||||
>args : any[]
|
||||
>args : Parameters<V>
|
||||
|
||||
@ -0,0 +1,34 @@
|
||||
type Q<T> = number extends T ? (n: number) => void : never;
|
||||
function fn<T>(arg: Q<T>) {
|
||||
// Expected: OK
|
||||
// Actual: Cannot convert 10 to number & T
|
||||
arg(10);
|
||||
}
|
||||
// Legal invocations are not problematic
|
||||
fn<string | number>(m => m.toFixed());
|
||||
fn<number>(m => m.toFixed());
|
||||
|
||||
// Ensure the following real-world example that relies on substitution still works
|
||||
type ExtractParameters<T> = "parameters" extends keyof T
|
||||
// The above allows "parameters" to index `T` since all later
|
||||
// instances are actually implicitly `"parameters" & keyof T`
|
||||
? {
|
||||
[K in keyof T["parameters"]]: T["parameters"][K];
|
||||
}[keyof T["parameters"]]
|
||||
: {};
|
||||
|
||||
// Original example, but with inverted variance
|
||||
type Q2<T> = number extends T ? (cb: (n: number) => void) => void : never;
|
||||
function fn2<T>(arg: Q2<T>) {
|
||||
function useT(_arg: T): void {}
|
||||
// Expected: OK
|
||||
arg(arg => useT(arg));
|
||||
}
|
||||
// Legal invocations are not problematic
|
||||
fn2<string | number>(m => m(42));
|
||||
fn2<number>(m => m(42));
|
||||
|
||||
// webidl-conversions example where substituion must occur, despite contravariance of the position
|
||||
// due to the invariant usage in `Parameters`
|
||||
|
||||
type X<V> = V extends (...args: any[]) => any ? (...args: Parameters<V>) => void : Function;
|
||||
Loading…
x
Reference in New Issue
Block a user