Dont allow generic narrowing when contextually typed by a binding pattern (#44081)

This commit is contained in:
Wesley Wigham 2021-05-13 16:51:52 -07:00 committed by GitHub
parent 493ec4cc26
commit f414d13679
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 247 additions and 4 deletions

View File

@ -24124,12 +24124,12 @@ namespace ts {
return !!(type.flags & TypeFlags.Instantiable || type.flags & TypeFlags.UnionOrIntersection && some((<UnionOrIntersectionType>type).types, containsGenericType));
}
function hasContextualTypeWithNoGenericTypes(node: Node) {
function hasNonBindingPatternContextualTypeWithNoGenericTypes(node: Node) {
// Computing the contextual type for a child of a JSX element involves resolving the type of the
// element's tag name, so we exclude that here to avoid circularities.
const contextualType = (isIdentifier(node) || isPropertyAccessExpression(node) || isElementAccessExpression(node)) &&
!((isJsxOpeningElement(node.parent) || isJsxSelfClosingElement(node.parent)) && node.parent.tagName === node) &&
getContextualType(node);
getContextualType(node, ContextFlags.SkipBindingPatterns);
return contextualType && !someType(contextualType, containsGenericType);
}
@ -24143,7 +24143,7 @@ namespace ts {
// 'string | undefined' to give control flow analysis the opportunity to narrow to type 'string'.
const substituteConstraints = !(checkMode && checkMode & CheckMode.Inferential) &&
someType(type, isGenericTypeWithUnionConstraint) &&
(isConstraintPosition(reference) || hasContextualTypeWithNoGenericTypes(reference));
(isConstraintPosition(reference) || hasNonBindingPatternContextualTypeWithNoGenericTypes(reference));
return substituteConstraints ? mapType(type, t => t.flags & TypeFlags.Instantiable ? getBaseConstraintOrType(t) : t) : type;
}

View File

@ -0,0 +1,67 @@
//// [genericObjectSpreadResultInSwitch.ts]
type Params = {
foo: string;
} & ({ tag: 'a'; type: number } | { tag: 'b'; type: string });
const getType = <P extends Params>(params: P) => {
const {
// Omit
foo,
...rest
} = params;
return rest;
};
declare const params: Params;
switch (params.tag) {
case 'a': {
// TS 4.2: number
// TS 4.3: string | number
const result = getType(params).type;
break;
}
case 'b': {
// TS 4.2: string
// TS 4.3: string | number
const result = getType(params).type;
break;
}
}
//// [genericObjectSpreadResultInSwitch.js]
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
var getType = function (params) {
var
// Omit
foo = params.foo, rest = __rest(params, ["foo"]);
return rest;
};
switch (params.tag) {
case 'a': {
// TS 4.2: number
// TS 4.3: string | number
var result = getType(params).type;
break;
}
case 'b': {
// TS 4.2: string
// TS 4.3: string | number
var result = getType(params).type;
break;
}
}

View File

@ -0,0 +1,70 @@
=== tests/cases/compiler/genericObjectSpreadResultInSwitch.ts ===
type Params = {
>Params : Symbol(Params, Decl(genericObjectSpreadResultInSwitch.ts, 0, 0))
foo: string;
>foo : Symbol(foo, Decl(genericObjectSpreadResultInSwitch.ts, 0, 15))
} & ({ tag: 'a'; type: number } | { tag: 'b'; type: string });
>tag : Symbol(tag, Decl(genericObjectSpreadResultInSwitch.ts, 2, 6))
>type : Symbol(type, Decl(genericObjectSpreadResultInSwitch.ts, 2, 16))
>tag : Symbol(tag, Decl(genericObjectSpreadResultInSwitch.ts, 2, 35))
>type : Symbol(type, Decl(genericObjectSpreadResultInSwitch.ts, 2, 45))
const getType = <P extends Params>(params: P) => {
>getType : Symbol(getType, Decl(genericObjectSpreadResultInSwitch.ts, 4, 5))
>P : Symbol(P, Decl(genericObjectSpreadResultInSwitch.ts, 4, 17))
>Params : Symbol(Params, Decl(genericObjectSpreadResultInSwitch.ts, 0, 0))
>params : Symbol(params, Decl(genericObjectSpreadResultInSwitch.ts, 4, 35))
>P : Symbol(P, Decl(genericObjectSpreadResultInSwitch.ts, 4, 17))
const {
// Omit
foo,
>foo : Symbol(foo, Decl(genericObjectSpreadResultInSwitch.ts, 5, 11))
...rest
>rest : Symbol(rest, Decl(genericObjectSpreadResultInSwitch.ts, 7, 12))
} = params;
>params : Symbol(params, Decl(genericObjectSpreadResultInSwitch.ts, 4, 35))
return rest;
>rest : Symbol(rest, Decl(genericObjectSpreadResultInSwitch.ts, 7, 12))
};
declare const params: Params;
>params : Symbol(params, Decl(genericObjectSpreadResultInSwitch.ts, 15, 13))
>Params : Symbol(Params, Decl(genericObjectSpreadResultInSwitch.ts, 0, 0))
switch (params.tag) {
>params.tag : Symbol(tag, Decl(genericObjectSpreadResultInSwitch.ts, 2, 6), Decl(genericObjectSpreadResultInSwitch.ts, 2, 35))
>params : Symbol(params, Decl(genericObjectSpreadResultInSwitch.ts, 15, 13))
>tag : Symbol(tag, Decl(genericObjectSpreadResultInSwitch.ts, 2, 6), Decl(genericObjectSpreadResultInSwitch.ts, 2, 35))
case 'a': {
// TS 4.2: number
// TS 4.3: string | number
const result = getType(params).type;
>result : Symbol(result, Decl(genericObjectSpreadResultInSwitch.ts, 21, 13))
>getType(params).type : Symbol(type, Decl(genericObjectSpreadResultInSwitch.ts, 2, 16))
>getType : Symbol(getType, Decl(genericObjectSpreadResultInSwitch.ts, 4, 5))
>params : Symbol(params, Decl(genericObjectSpreadResultInSwitch.ts, 15, 13))
>type : Symbol(type, Decl(genericObjectSpreadResultInSwitch.ts, 2, 16))
break;
}
case 'b': {
// TS 4.2: string
// TS 4.3: string | number
const result = getType(params).type;
>result : Symbol(result, Decl(genericObjectSpreadResultInSwitch.ts, 28, 13))
>getType(params).type : Symbol(type, Decl(genericObjectSpreadResultInSwitch.ts, 2, 45))
>getType : Symbol(getType, Decl(genericObjectSpreadResultInSwitch.ts, 4, 5))
>params : Symbol(params, Decl(genericObjectSpreadResultInSwitch.ts, 15, 13))
>type : Symbol(type, Decl(genericObjectSpreadResultInSwitch.ts, 2, 45))
break;
}
}

View File

@ -0,0 +1,73 @@
=== tests/cases/compiler/genericObjectSpreadResultInSwitch.ts ===
type Params = {
>Params : Params
foo: string;
>foo : string
} & ({ tag: 'a'; type: number } | { tag: 'b'; type: string });
>tag : "a"
>type : number
>tag : "b"
>type : string
const getType = <P extends Params>(params: P) => {
>getType : <P extends Params>(params: P) => Omit<P, "foo">
><P extends Params>(params: P) => { const { // Omit foo, ...rest } = params; return rest;} : <P extends Params>(params: P) => Omit<P, "foo">
>params : P
const {
// Omit
foo,
>foo : string
...rest
>rest : Omit<P, "foo">
} = params;
>params : P
return rest;
>rest : Omit<P, "foo">
};
declare const params: Params;
>params : Params
switch (params.tag) {
>params.tag : "a" | "b"
>params : Params
>tag : "a" | "b"
case 'a': {
>'a' : "a"
// TS 4.2: number
// TS 4.3: string | number
const result = getType(params).type;
>result : number
>getType(params).type : number
>getType(params) : Omit<{ foo: string; } & { tag: "a"; type: number; }, "foo">
>getType : <P extends Params>(params: P) => Omit<P, "foo">
>params : { foo: string; } & { tag: "a"; type: number; }
>type : number
break;
}
case 'b': {
>'b' : "b"
// TS 4.2: string
// TS 4.3: string | number
const result = getType(params).type;
>result : string
>getType(params).type : string
>getType(params) : Omit<{ foo: string; } & { tag: "b"; type: string; }, "foo">
>getType : <P extends Params>(params: P) => Omit<P, "foo">
>params : { foo: string; } & { tag: "b"; type: string; }
>type : string
break;
}
}

View File

@ -84,7 +84,7 @@ function f<T extends { b: string }>(p1: T, p2: T[]) {
var {...r5} = k; // Error, index
>r5 : any
>k : string | number | symbol
>k : keyof T
var {...r6} = mapped_generic; // Error, generic mapped object type
>r6 : { [P in keyof T]: T[P]; }

View File

@ -0,0 +1,33 @@
type Params = {
foo: string;
} & ({ tag: 'a'; type: number } | { tag: 'b'; type: string });
const getType = <P extends Params>(params: P) => {
const {
// Omit
foo,
...rest
} = params;
return rest;
};
declare const params: Params;
switch (params.tag) {
case 'a': {
// TS 4.2: number
// TS 4.3: string | number
const result = getType(params).type;
break;
}
case 'b': {
// TS 4.2: string
// TS 4.3: string | number
const result = getType(params).type;
break;
}
}