Skip to content

Commit cb1833b

Browse files
committed
Do not covariantly mix in constraints from contravarrying positions
1 parent 294a5a7 commit cb1833b

6 files changed

+105
-15
lines changed

src/compiler/checker.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12849,9 +12849,13 @@ namespace ts {
1284912849

1285012850
function getConditionalFlowTypeOfType(type: Type, node: Node) {
1285112851
let constraints: Type[] | undefined;
12852+
let covariant = true;
1285212853
while (node && !isStatement(node) && node.kind !== SyntaxKind.JSDocComment) {
1285312854
const parent = node.parent;
12854-
if (parent.kind === SyntaxKind.ConditionalType && node === (<ConditionalTypeNode>parent).trueType) {
12855+
if (parent.kind === SyntaxKind.Parameter || parent.kind === SyntaxKind.TypeOperator && (parent as TypeOperatorNode).operator === SyntaxKind.KeyOfKeyword) {
12856+
covariant = !covariant;
12857+
}
12858+
if (covariant && parent.kind === SyntaxKind.ConditionalType && node === (<ConditionalTypeNode>parent).trueType) {
1285512859
const constraint = getImpliedConstraint(type, (<ConditionalTypeNode>parent).checkType, (<ConditionalTypeNode>parent).extendsType);
1285612860
if (constraint) {
1285712861
constraints = append(constraints, constraint);
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//// [callOfConditionalTypeWithConcreteBranches.ts]
2+
type Q<T> = number extends T ? (n: number) => void : never;
3+
function fn<T>(arg: Q<T>) {
4+
// Expected: OK
5+
// Actual: Cannot convert 10 to number & T
6+
arg(10);
7+
}
8+
// Legal invocations are not problematic
9+
fn<string | number>(m => m.toFixed());
10+
fn<number>(m => m.toFixed());
11+
12+
//// [callOfConditionalTypeWithConcreteBranches.js]
13+
function fn(arg) {
14+
// Expected: OK
15+
// Actual: Cannot convert 10 to number & T
16+
arg(10);
17+
}
18+
// Legal invocations are not problematic
19+
fn(function (m) { return m.toFixed(); });
20+
fn(function (m) { return m.toFixed(); });
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
=== tests/cases/compiler/callOfConditionalTypeWithConcreteBranches.ts ===
2+
type Q<T> = number extends T ? (n: number) => void : never;
3+
>Q : Symbol(Q, Decl(callOfConditionalTypeWithConcreteBranches.ts, 0, 0))
4+
>T : Symbol(T, Decl(callOfConditionalTypeWithConcreteBranches.ts, 0, 7))
5+
>T : Symbol(T, Decl(callOfConditionalTypeWithConcreteBranches.ts, 0, 7))
6+
>n : Symbol(n, Decl(callOfConditionalTypeWithConcreteBranches.ts, 0, 32))
7+
8+
function fn<T>(arg: Q<T>) {
9+
>fn : Symbol(fn, Decl(callOfConditionalTypeWithConcreteBranches.ts, 0, 59))
10+
>T : Symbol(T, Decl(callOfConditionalTypeWithConcreteBranches.ts, 1, 12))
11+
>arg : Symbol(arg, Decl(callOfConditionalTypeWithConcreteBranches.ts, 1, 15))
12+
>Q : Symbol(Q, Decl(callOfConditionalTypeWithConcreteBranches.ts, 0, 0))
13+
>T : Symbol(T, Decl(callOfConditionalTypeWithConcreteBranches.ts, 1, 12))
14+
15+
// Expected: OK
16+
// Actual: Cannot convert 10 to number & T
17+
arg(10);
18+
>arg : Symbol(arg, Decl(callOfConditionalTypeWithConcreteBranches.ts, 1, 15))
19+
}
20+
// Legal invocations are not problematic
21+
fn<string | number>(m => m.toFixed());
22+
>fn : Symbol(fn, Decl(callOfConditionalTypeWithConcreteBranches.ts, 0, 59))
23+
>m : Symbol(m, Decl(callOfConditionalTypeWithConcreteBranches.ts, 7, 20))
24+
>m.toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --))
25+
>m : Symbol(m, Decl(callOfConditionalTypeWithConcreteBranches.ts, 7, 20))
26+
>toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --))
27+
28+
fn<number>(m => m.toFixed());
29+
>fn : Symbol(fn, Decl(callOfConditionalTypeWithConcreteBranches.ts, 0, 59))
30+
>m : Symbol(m, Decl(callOfConditionalTypeWithConcreteBranches.ts, 8, 11))
31+
>m.toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --))
32+
>m : Symbol(m, Decl(callOfConditionalTypeWithConcreteBranches.ts, 8, 11))
33+
>toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --))
34+
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
=== tests/cases/compiler/callOfConditionalTypeWithConcreteBranches.ts ===
2+
type Q<T> = number extends T ? (n: number) => void : never;
3+
>Q : Q<T>
4+
>n : number
5+
6+
function fn<T>(arg: Q<T>) {
7+
>fn : <T>(arg: Q<T>) => void
8+
>arg : Q<T>
9+
10+
// Expected: OK
11+
// Actual: Cannot convert 10 to number & T
12+
arg(10);
13+
>arg(10) : void
14+
>arg : Q<T>
15+
>10 : 10
16+
}
17+
// Legal invocations are not problematic
18+
fn<string | number>(m => m.toFixed());
19+
>fn<string | number>(m => m.toFixed()) : void
20+
>fn : <T>(arg: Q<T>) => void
21+
>m => m.toFixed() : (m: number) => string
22+
>m : number
23+
>m.toFixed() : string
24+
>m.toFixed : (fractionDigits?: number) => string
25+
>m : number
26+
>toFixed : (fractionDigits?: number) => string
27+
28+
fn<number>(m => m.toFixed());
29+
>fn<number>(m => m.toFixed()) : void
30+
>fn : <T>(arg: Q<T>) => void
31+
>m => m.toFixed() : (m: number) => string
32+
>m : number
33+
>m.toFixed() : string
34+
>m.toFixed : (fractionDigits?: number) => string
35+
>m : number
36+
>toFixed : (fractionDigits?: number) => string
37+

tests/baselines/reference/conditionalTypes2.errors.txt

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,6 @@ tests/cases/conformance/types/conditional/conditionalTypes2.ts(24,5): error TS23
1313
Type 'keyof B' is not assignable to type 'keyof A'.
1414
Type 'string | number | symbol' is not assignable to type 'keyof A'.
1515
Type 'string' is not assignable to type 'keyof A'.
16-
Type 'string' is not assignable to type 'number | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
17-
Type 'keyof B' is not assignable to type 'number | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
18-
Type 'string | number | symbol' is not assignable to type 'number | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
19-
Type 'string' is not assignable to type 'number | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
20-
Type 'keyof B' is not assignable to type 'number'.
21-
Type 'string | number | symbol' is not assignable to type 'number'.
22-
Type 'string' is not assignable to type 'number'.
2316
tests/cases/conformance/types/conditional/conditionalTypes2.ts(25,5): error TS2322: Type 'Invariant<A>' is not assignable to type 'Invariant<B>'.
2417
Types of property 'foo' are incompatible.
2518
Type 'A extends string ? keyof A : A' is not assignable to type 'B extends string ? keyof B : B'.
@@ -81,13 +74,6 @@ tests/cases/conformance/types/conditional/conditionalTypes2.ts(75,12): error TS2
8174
!!! error TS2322: Type 'keyof B' is not assignable to type 'keyof A'.
8275
!!! error TS2322: Type 'string | number | symbol' is not assignable to type 'keyof A'.
8376
!!! error TS2322: Type 'string' is not assignable to type 'keyof A'.
84-
!!! error TS2322: Type 'string' is not assignable to type 'number | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
85-
!!! error TS2322: Type 'keyof B' is not assignable to type 'number | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
86-
!!! error TS2322: Type 'string | number | symbol' is not assignable to type 'number | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
87-
!!! error TS2322: Type 'string' is not assignable to type 'number | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf"'.
88-
!!! error TS2322: Type 'keyof B' is not assignable to type 'number'.
89-
!!! error TS2322: Type 'string | number | symbol' is not assignable to type 'number'.
90-
!!! error TS2322: Type 'string' is not assignable to type 'number'.
9177
b = a; // Error
9278
~
9379
!!! error TS2322: Type 'Invariant<A>' is not assignable to type 'Invariant<B>'.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
type Q<T> = number extends T ? (n: number) => void : never;
2+
function fn<T>(arg: Q<T>) {
3+
// Expected: OK
4+
// Actual: Cannot convert 10 to number & T
5+
arg(10);
6+
}
7+
// Legal invocations are not problematic
8+
fn<string | number>(m => m.toFixed());
9+
fn<number>(m => m.toFixed());

0 commit comments

Comments
 (0)