Narrowing type parameter intersects w/narrowed types

This makes sure that a union type that includes a type parameter is
still usable as the actual type that the type guard narrows to.
This commit is contained in:
Nathan Shively-Sanders
2016-08-02 16:10:20 -07:00
parent 013744bf85
commit 4189b4d718
5 changed files with 156 additions and 7 deletions

View File

@@ -7900,13 +7900,17 @@ namespace ts {
return TypeFacts.All;
}
function getTypeWithFacts(type: Type, include: TypeFacts) {
function getTypeWithFacts(type: Type, include: TypeFacts, intersectForTypeParameters = false) {
if (!(type.flags & TypeFlags.Union)) {
return getTypeFacts(type) & include ? type : neverType;
}
let firstType: Type;
let hasTypeParameter = false;
let types: Type[];
for (const t of (type as UnionType).types) {
if (t.flags & TypeFlags.TypeParameter) {
hasTypeParameter = true;
}
if (getTypeFacts(t) & include) {
if (!firstType) {
firstType = t;
@@ -7919,7 +7923,19 @@ namespace ts {
}
}
}
return firstType ? types ? getUnionType(types) : firstType : neverType;
const narrowed = types ? getUnionType(types) :
firstType ? firstType : neverType;
// if there is a type parameter in the narrowed type,
// add an intersection with the members of the narrowed type so that the shape of the type is correct
if (type.flags & TypeFlags.Union &&
narrowed.flags & TypeFlags.Union &&
hasTypeParameter &&
intersectForTypeParameters) {
return getIntersectionType(types.concat([narrowed]));
}
else {
return narrowed;
}
}
function getTypeWithDefault(type: Type, defaultExpression: Expression) {
@@ -8183,7 +8199,7 @@ namespace ts {
let type = getTypeAtFlowNode(flow.antecedent);
if (type !== neverType) {
// If we have an antecedent type (meaning we're reachable in some way), we first
// attempt to narrow the antecedent type. If that produces the nothing type, then
// attempt to narrow the antecedent type. If that produces the never type, then
// we take the type guard as an indication that control could reach here in a
// manner not understood by the control flow analyzer (e.g. a function argument
// has an invalid type, or a nested function has possibly made an assignment to a
@@ -8292,10 +8308,10 @@ namespace ts {
function narrowTypeByTruthiness(type: Type, expr: Expression, assumeTrue: boolean): Type {
if (isMatchingReference(reference, expr)) {
return getTypeWithFacts(type, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy);
return getTypeWithFacts(type, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy, assumeTrue);
}
if (isMatchingPropertyAccess(expr)) {
return narrowTypeByDiscriminant(type, <PropertyAccessExpression>expr, t => getTypeWithFacts(t, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy));
return narrowTypeByDiscriminant(type, <PropertyAccessExpression>expr, t => getTypeWithFacts(t, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy, assumeTrue));
}
return type;
}
@@ -8353,7 +8369,7 @@ namespace ts {
value.kind === SyntaxKind.NullKeyword ?
assumeTrue ? TypeFacts.EQNull : TypeFacts.NENull :
assumeTrue ? TypeFacts.EQUndefined : TypeFacts.NEUndefined;
return getTypeWithFacts(type, facts);
return getTypeWithFacts(type, facts, assumeTrue);
}
if (type.flags & TypeFlags.NotUnionOrUnit) {
return type;
@@ -8391,7 +8407,7 @@ namespace ts {
const facts = assumeTrue ?
getProperty(typeofEQFacts, literal.text) || TypeFacts.TypeofEQHostObject :
getProperty(typeofNEFacts, literal.text) || TypeFacts.TypeofNEHostObject;
return getTypeWithFacts(type, facts);
return getTypeWithFacts(type, facts, assumeTrue);
}
function narrowTypeBySwitchOnDiscriminant(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) {

View File

@@ -35,6 +35,22 @@ function b() {
}
x; // string
}
function c<T>(data: string | T): T {
if (typeof data === 'string') {
return JSON.parse(data);
}
else {
return data;
}
}
function d<T extends string>(data: string | T): never {
if (typeof data === 'string') {
throw new Error('will always happen');
}
else {
return data;
}
}
//// [controlFlowIfStatement.js]
@@ -72,3 +88,19 @@ function b() {
}
x; // string
}
function c(data) {
if (typeof data === 'string') {
return JSON.parse(data);
}
else {
return data;
}
}
function d(data) {
if (typeof data === 'string') {
throw new Error('will always happen');
}
else {
return data;
}
}

View File

@@ -71,4 +71,42 @@ function b() {
x; // string
>x : Symbol(x, Decl(controlFlowIfStatement.ts, 26, 7))
}
function c<T>(data: string | T): T {
>c : Symbol(c, Decl(controlFlowIfStatement.ts, 35, 1))
>T : Symbol(T, Decl(controlFlowIfStatement.ts, 36, 11))
>data : Symbol(data, Decl(controlFlowIfStatement.ts, 36, 14))
>T : Symbol(T, Decl(controlFlowIfStatement.ts, 36, 11))
>T : Symbol(T, Decl(controlFlowIfStatement.ts, 36, 11))
if (typeof data === 'string') {
>data : Symbol(data, Decl(controlFlowIfStatement.ts, 36, 14))
return JSON.parse(data);
>JSON.parse : Symbol(JSON.parse, Decl(lib.d.ts, --, --))
>JSON : Symbol(JSON, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
>parse : Symbol(JSON.parse, Decl(lib.d.ts, --, --))
>data : Symbol(data, Decl(controlFlowIfStatement.ts, 36, 14))
}
else {
return data;
>data : Symbol(data, Decl(controlFlowIfStatement.ts, 36, 14))
}
}
function d<T extends string>(data: string | T): never {
>d : Symbol(d, Decl(controlFlowIfStatement.ts, 43, 1))
>T : Symbol(T, Decl(controlFlowIfStatement.ts, 44, 11))
>data : Symbol(data, Decl(controlFlowIfStatement.ts, 44, 29))
>T : Symbol(T, Decl(controlFlowIfStatement.ts, 44, 11))
if (typeof data === 'string') {
>data : Symbol(data, Decl(controlFlowIfStatement.ts, 44, 29))
throw new Error('will always happen');
>Error : Symbol(Error, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
}
else {
return data;
>data : Symbol(data, Decl(controlFlowIfStatement.ts, 44, 29))
}
}

View File

@@ -90,4 +90,51 @@ function b() {
x; // string
>x : string
}
function c<T>(data: string | T): T {
>c : <T>(data: string | T) => T
>T : T
>data : string | T
>T : T
>T : T
if (typeof data === 'string') {
>typeof data === 'string' : boolean
>typeof data : string
>data : string | T
>'string' : "string"
return JSON.parse(data);
>JSON.parse(data) : any
>JSON.parse : (text: string, reviver?: (key: any, value: any) => any) => any
>JSON : JSON
>parse : (text: string, reviver?: (key: any, value: any) => any) => any
>data : string & T & (string | T)
}
else {
return data;
>data : T
}
}
function d<T extends string>(data: string | T): never {
>d : <T extends string>(data: string | T) => never
>T : T
>data : string | T
>T : T
if (typeof data === 'string') {
>typeof data === 'string' : boolean
>typeof data : string
>data : string | T
>'string' : "string"
throw new Error('will always happen');
>new Error('will always happen') : Error
>Error : ErrorConstructor
>'will always happen' : string
}
else {
return data;
>data : never
}
}

View File

@@ -34,3 +34,19 @@ function b() {
}
x; // string
}
function c<T>(data: string | T): T {
if (typeof data === 'string') {
return JSON.parse(data);
}
else {
return data;
}
}
function d<T extends string>(data: string | T): never {
if (typeof data === 'string') {
throw new Error('will always happen');
}
else {
return data;
}
}