Skip to content

Commit 0cc5c7b

Browse files
committed
Dont allow generic narrowing when contextually typed by a binding pattern
1 parent 4990bd9 commit 0cc5c7b

6 files changed

+247
-4
lines changed

src/compiler/checker.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -24124,12 +24124,12 @@ namespace ts {
2412424124
return !!(type.flags & TypeFlags.Instantiable || type.flags & TypeFlags.UnionOrIntersection && some((<UnionOrIntersectionType>type).types, containsGenericType));
2412524125
}
2412624126

24127-
function hasContextualTypeWithNoGenericTypes(node: Node) {
24127+
function hasNonBindingPatternContextualTypeWithNoGenericTypes(node: Node) {
2412824128
// Computing the contextual type for a child of a JSX element involves resolving the type of the
2412924129
// element's tag name, so we exclude that here to avoid circularities.
2413024130
const contextualType = (isIdentifier(node) || isPropertyAccessExpression(node) || isElementAccessExpression(node)) &&
2413124131
!((isJsxOpeningElement(node.parent) || isJsxSelfClosingElement(node.parent)) && node.parent.tagName === node) &&
24132-
getContextualType(node);
24132+
getContextualType(node, ContextFlags.SkipBindingPatterns);
2413324133
return contextualType && !someType(contextualType, containsGenericType);
2413424134
}
2413524135

@@ -24143,7 +24143,7 @@ namespace ts {
2414324143
// 'string | undefined' to give control flow analysis the opportunity to narrow to type 'string'.
2414424144
const substituteConstraints = !(checkMode && checkMode & CheckMode.Inferential) &&
2414524145
someType(type, isGenericTypeWithUnionConstraint) &&
24146-
(isConstraintPosition(reference) || hasContextualTypeWithNoGenericTypes(reference));
24146+
(isConstraintPosition(reference) || hasNonBindingPatternContextualTypeWithNoGenericTypes(reference));
2414724147
return substituteConstraints ? mapType(type, t => t.flags & TypeFlags.Instantiable ? getBaseConstraintOrType(t) : t) : type;
2414824148
}
2414924149

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
//// [genericObjectSpreadResultInSwitch.ts]
2+
type Params = {
3+
foo: string;
4+
} & ({ tag: 'a'; type: number } | { tag: 'b'; type: string });
5+
6+
const getType = <P extends Params>(params: P) => {
7+
const {
8+
// Omit
9+
foo,
10+
11+
...rest
12+
} = params;
13+
14+
return rest;
15+
};
16+
17+
declare const params: Params;
18+
19+
switch (params.tag) {
20+
case 'a': {
21+
// TS 4.2: number
22+
// TS 4.3: string | number
23+
const result = getType(params).type;
24+
25+
break;
26+
}
27+
case 'b': {
28+
// TS 4.2: string
29+
// TS 4.3: string | number
30+
const result = getType(params).type;
31+
32+
break;
33+
}
34+
}
35+
36+
//// [genericObjectSpreadResultInSwitch.js]
37+
var __rest = (this && this.__rest) || function (s, e) {
38+
var t = {};
39+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
40+
t[p] = s[p];
41+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
42+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
43+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
44+
t[p[i]] = s[p[i]];
45+
}
46+
return t;
47+
};
48+
var getType = function (params) {
49+
var
50+
// Omit
51+
foo = params.foo, rest = __rest(params, ["foo"]);
52+
return rest;
53+
};
54+
switch (params.tag) {
55+
case 'a': {
56+
// TS 4.2: number
57+
// TS 4.3: string | number
58+
var result = getType(params).type;
59+
break;
60+
}
61+
case 'b': {
62+
// TS 4.2: string
63+
// TS 4.3: string | number
64+
var result = getType(params).type;
65+
break;
66+
}
67+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
=== tests/cases/compiler/genericObjectSpreadResultInSwitch.ts ===
2+
type Params = {
3+
>Params : Symbol(Params, Decl(genericObjectSpreadResultInSwitch.ts, 0, 0))
4+
5+
foo: string;
6+
>foo : Symbol(foo, Decl(genericObjectSpreadResultInSwitch.ts, 0, 15))
7+
8+
} & ({ tag: 'a'; type: number } | { tag: 'b'; type: string });
9+
>tag : Symbol(tag, Decl(genericObjectSpreadResultInSwitch.ts, 2, 6))
10+
>type : Symbol(type, Decl(genericObjectSpreadResultInSwitch.ts, 2, 16))
11+
>tag : Symbol(tag, Decl(genericObjectSpreadResultInSwitch.ts, 2, 35))
12+
>type : Symbol(type, Decl(genericObjectSpreadResultInSwitch.ts, 2, 45))
13+
14+
const getType = <P extends Params>(params: P) => {
15+
>getType : Symbol(getType, Decl(genericObjectSpreadResultInSwitch.ts, 4, 5))
16+
>P : Symbol(P, Decl(genericObjectSpreadResultInSwitch.ts, 4, 17))
17+
>Params : Symbol(Params, Decl(genericObjectSpreadResultInSwitch.ts, 0, 0))
18+
>params : Symbol(params, Decl(genericObjectSpreadResultInSwitch.ts, 4, 35))
19+
>P : Symbol(P, Decl(genericObjectSpreadResultInSwitch.ts, 4, 17))
20+
21+
const {
22+
// Omit
23+
foo,
24+
>foo : Symbol(foo, Decl(genericObjectSpreadResultInSwitch.ts, 5, 11))
25+
26+
...rest
27+
>rest : Symbol(rest, Decl(genericObjectSpreadResultInSwitch.ts, 7, 12))
28+
29+
} = params;
30+
>params : Symbol(params, Decl(genericObjectSpreadResultInSwitch.ts, 4, 35))
31+
32+
return rest;
33+
>rest : Symbol(rest, Decl(genericObjectSpreadResultInSwitch.ts, 7, 12))
34+
35+
};
36+
37+
declare const params: Params;
38+
>params : Symbol(params, Decl(genericObjectSpreadResultInSwitch.ts, 15, 13))
39+
>Params : Symbol(Params, Decl(genericObjectSpreadResultInSwitch.ts, 0, 0))
40+
41+
switch (params.tag) {
42+
>params.tag : Symbol(tag, Decl(genericObjectSpreadResultInSwitch.ts, 2, 6), Decl(genericObjectSpreadResultInSwitch.ts, 2, 35))
43+
>params : Symbol(params, Decl(genericObjectSpreadResultInSwitch.ts, 15, 13))
44+
>tag : Symbol(tag, Decl(genericObjectSpreadResultInSwitch.ts, 2, 6), Decl(genericObjectSpreadResultInSwitch.ts, 2, 35))
45+
46+
case 'a': {
47+
// TS 4.2: number
48+
// TS 4.3: string | number
49+
const result = getType(params).type;
50+
>result : Symbol(result, Decl(genericObjectSpreadResultInSwitch.ts, 21, 13))
51+
>getType(params).type : Symbol(type, Decl(genericObjectSpreadResultInSwitch.ts, 2, 16))
52+
>getType : Symbol(getType, Decl(genericObjectSpreadResultInSwitch.ts, 4, 5))
53+
>params : Symbol(params, Decl(genericObjectSpreadResultInSwitch.ts, 15, 13))
54+
>type : Symbol(type, Decl(genericObjectSpreadResultInSwitch.ts, 2, 16))
55+
56+
break;
57+
}
58+
case 'b': {
59+
// TS 4.2: string
60+
// TS 4.3: string | number
61+
const result = getType(params).type;
62+
>result : Symbol(result, Decl(genericObjectSpreadResultInSwitch.ts, 28, 13))
63+
>getType(params).type : Symbol(type, Decl(genericObjectSpreadResultInSwitch.ts, 2, 45))
64+
>getType : Symbol(getType, Decl(genericObjectSpreadResultInSwitch.ts, 4, 5))
65+
>params : Symbol(params, Decl(genericObjectSpreadResultInSwitch.ts, 15, 13))
66+
>type : Symbol(type, Decl(genericObjectSpreadResultInSwitch.ts, 2, 45))
67+
68+
break;
69+
}
70+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
=== tests/cases/compiler/genericObjectSpreadResultInSwitch.ts ===
2+
type Params = {
3+
>Params : Params
4+
5+
foo: string;
6+
>foo : string
7+
8+
} & ({ tag: 'a'; type: number } | { tag: 'b'; type: string });
9+
>tag : "a"
10+
>type : number
11+
>tag : "b"
12+
>type : string
13+
14+
const getType = <P extends Params>(params: P) => {
15+
>getType : <P extends Params>(params: P) => Omit<P, "foo">
16+
><P extends Params>(params: P) => { const { // Omit foo, ...rest } = params; return rest;} : <P extends Params>(params: P) => Omit<P, "foo">
17+
>params : P
18+
19+
const {
20+
// Omit
21+
foo,
22+
>foo : string
23+
24+
...rest
25+
>rest : Omit<P, "foo">
26+
27+
} = params;
28+
>params : P
29+
30+
return rest;
31+
>rest : Omit<P, "foo">
32+
33+
};
34+
35+
declare const params: Params;
36+
>params : Params
37+
38+
switch (params.tag) {
39+
>params.tag : "a" | "b"
40+
>params : Params
41+
>tag : "a" | "b"
42+
43+
case 'a': {
44+
>'a' : "a"
45+
46+
// TS 4.2: number
47+
// TS 4.3: string | number
48+
const result = getType(params).type;
49+
>result : number
50+
>getType(params).type : number
51+
>getType(params) : Omit<{ foo: string; } & { tag: "a"; type: number; }, "foo">
52+
>getType : <P extends Params>(params: P) => Omit<P, "foo">
53+
>params : { foo: string; } & { tag: "a"; type: number; }
54+
>type : number
55+
56+
break;
57+
}
58+
case 'b': {
59+
>'b' : "b"
60+
61+
// TS 4.2: string
62+
// TS 4.3: string | number
63+
const result = getType(params).type;
64+
>result : string
65+
>getType(params).type : string
66+
>getType(params) : Omit<{ foo: string; } & { tag: "b"; type: string; }, "foo">
67+
>getType : <P extends Params>(params: P) => Omit<P, "foo">
68+
>params : { foo: string; } & { tag: "b"; type: string; }
69+
>type : string
70+
71+
break;
72+
}
73+
}

tests/baselines/reference/restInvalidArgumentType.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ function f<T extends { b: string }>(p1: T, p2: T[]) {
8484

8585
var {...r5} = k; // Error, index
8686
>r5 : any
87-
>k : string | number | symbol
87+
>k : keyof T
8888

8989
var {...r6} = mapped_generic; // Error, generic mapped object type
9090
>r6 : { [P in keyof T]: T[P]; }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
type Params = {
2+
foo: string;
3+
} & ({ tag: 'a'; type: number } | { tag: 'b'; type: string });
4+
5+
const getType = <P extends Params>(params: P) => {
6+
const {
7+
// Omit
8+
foo,
9+
10+
...rest
11+
} = params;
12+
13+
return rest;
14+
};
15+
16+
declare const params: Params;
17+
18+
switch (params.tag) {
19+
case 'a': {
20+
// TS 4.2: number
21+
// TS 4.3: string | number
22+
const result = getType(params).type;
23+
24+
break;
25+
}
26+
case 'b': {
27+
// TS 4.2: string
28+
// TS 4.3: string | number
29+
const result = getType(params).type;
30+
31+
break;
32+
}
33+
}

0 commit comments

Comments
 (0)