Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Merged
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
10 changes: 9 additions & 1 deletion 10 src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12849,9 +12849,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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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); });
Original file line number Diff line number Diff line change
@@ -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, --, --))

Original file line number Diff line number Diff line change
@@ -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>

34 changes: 34 additions & 0 deletions 34 tests/cases/compiler/callOfConditionalTypeWithConcreteBranches.ts
Original file line number Diff line number Diff line change
@@ -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;
Morty Proxy This is a proxified and sanitized view of the page, visit original site.