Properly handle null and undefined in getCommonSupertype (#50021)

* Properly handle null and undefined in getCommonSupertype

* Add tests

* Add more tests
This commit is contained in:
Anders Hejlsberg
2022-07-24 08:09:14 -07:00
committed by GitHub
parent 165a1c4a40
commit 4026c6fd80
9 changed files with 711 additions and 24 deletions

View File

@@ -21198,39 +21198,35 @@ namespace ts {
function literalTypesWithSameBaseType(types: Type[]): boolean {
let commonBaseType: Type | undefined;
for (const t of types) {
const baseType = getBaseTypeOfLiteralType(t);
if (!commonBaseType) {
commonBaseType = baseType;
}
if (baseType === t || baseType !== commonBaseType) {
return false;
if (!(t.flags & TypeFlags.Never)) {
const baseType = getBaseTypeOfLiteralType(t);
commonBaseType ??= baseType;
if (baseType === t || baseType !== commonBaseType) {
return false;
}
}
}
return true;
}
// When the candidate types are all literal types with the same base type, return a union
// of those literal types. Otherwise, return the leftmost type for which no type to the
// right is a supertype.
function getSupertypeOrUnion(types: Type[]): Type {
if (types.length === 1) {
return types[0];
}
return literalTypesWithSameBaseType(types) ?
getUnionType(types) :
reduceLeft(types, (s, t) => isTypeSubtypeOf(s, t) ? t : s)!;
function getCombinedTypeFlags(types: Type[]): TypeFlags {
return reduceLeft(types, (flags, t) => flags | (t.flags & TypeFlags.Union ? getCombinedTypeFlags((t as UnionType).types) : t.flags), 0);
}
function getCommonSupertype(types: Type[]): Type {
if (!strictNullChecks) {
return getSupertypeOrUnion(types);
if (types.length === 1) {
return types[0];
}
const primaryTypes = filter(types, t => !(t.flags & TypeFlags.Nullable));
if (primaryTypes.length) {
const supertypeOrUnion = getSupertypeOrUnion(primaryTypes);
return primaryTypes === types ? supertypeOrUnion : getUnionType([supertypeOrUnion, ...filter(types, t => !!(t.flags & TypeFlags.Nullable))]);
}
return getUnionType(types, UnionReduction.Subtype);
// Remove nullable types from each of the candidates.
const primaryTypes = strictNullChecks ? sameMap(types, t => filterType(t, u => !(u.flags & TypeFlags.Nullable))) : types;
// When the candidate types are all literal types with the same base type, return a union
// of those literal types. Otherwise, return the leftmost type for which no type to the
// right is a supertype.
const superTypeOrUnion = literalTypesWithSameBaseType(primaryTypes) ?
getUnionType(primaryTypes) :
reduceLeft(primaryTypes, (s, t) => isTypeSubtypeOf(s, t) ? t : s)!;
// Add any nullable types that occurred in the candidates back to the result.
return primaryTypes === types ? superTypeOrUnion : getNullableType(superTypeOrUnion, getCombinedTypeFlags(types) & TypeFlags.Nullable);
}
// Return the leftmost type for which no type to the right is a subtype.

View File

@@ -0,0 +1,55 @@
//// [inferenceDoesNotAddUndefinedOrNull.ts]
interface NodeArray<T extends Node> extends ReadonlyArray<T> {}
interface Node {
forEachChild<T>(cbNode: (node: Node) => T | undefined, cbNodeArray?: (nodes: NodeArray<Node>) => T | undefined): T | undefined;
}
declare function toArray<T>(value: T | T[]): T[];
declare function toArray<T>(value: T | readonly T[]): readonly T[];
function flatMapChildren<T>(node: Node, cb: (child: Node) => readonly T[] | T | undefined): readonly T[] {
const result: T[] = [];
node.forEachChild(child => {
const value = cb(child);
if (value !== undefined) {
result.push(...toArray(value));
}
});
return result;
}
function flatMapChildren2<T>(node: Node, cb: (child: Node) => readonly T[] | T | null): readonly T[] {
const result: T[] = [];
node.forEachChild(child => {
const value = cb(child);
if (value !== null) {
result.push(...toArray(value));
}
});
return result;
}
//// [inferenceDoesNotAddUndefinedOrNull.js]
"use strict";
function flatMapChildren(node, cb) {
var result = [];
node.forEachChild(function (child) {
var value = cb(child);
if (value !== undefined) {
result.push.apply(result, toArray(value));
}
});
return result;
}
function flatMapChildren2(node, cb) {
var result = [];
node.forEachChild(function (child) {
var value = cb(child);
if (value !== null) {
result.push.apply(result, toArray(value));
}
});
return result;
}

View File

@@ -0,0 +1,127 @@
=== tests/cases/compiler/inferenceDoesNotAddUndefinedOrNull.ts ===
interface NodeArray<T extends Node> extends ReadonlyArray<T> {}
>NodeArray : Symbol(NodeArray, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 0, 0))
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 0, 20))
>Node : Symbol(Node, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(inferenceDoesNotAddUndefinedOrNull.ts, 0, 63))
>ReadonlyArray : Symbol(ReadonlyArray, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 0, 20))
interface Node {
>Node : Symbol(Node, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(inferenceDoesNotAddUndefinedOrNull.ts, 0, 63))
forEachChild<T>(cbNode: (node: Node) => T | undefined, cbNodeArray?: (nodes: NodeArray<Node>) => T | undefined): T | undefined;
>forEachChild : Symbol(Node.forEachChild, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 2, 16))
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 3, 17))
>cbNode : Symbol(cbNode, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 3, 20))
>node : Symbol(node, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 3, 29))
>Node : Symbol(Node, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(inferenceDoesNotAddUndefinedOrNull.ts, 0, 63))
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 3, 17))
>cbNodeArray : Symbol(cbNodeArray, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 3, 58))
>nodes : Symbol(nodes, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 3, 74))
>NodeArray : Symbol(NodeArray, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 0, 0))
>Node : Symbol(Node, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(inferenceDoesNotAddUndefinedOrNull.ts, 0, 63))
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 3, 17))
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 3, 17))
}
declare function toArray<T>(value: T | T[]): T[];
>toArray : Symbol(toArray, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 4, 1), Decl(inferenceDoesNotAddUndefinedOrNull.ts, 6, 49))
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 6, 25))
>value : Symbol(value, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 6, 28))
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 6, 25))
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 6, 25))
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 6, 25))
declare function toArray<T>(value: T | readonly T[]): readonly T[];
>toArray : Symbol(toArray, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 4, 1), Decl(inferenceDoesNotAddUndefinedOrNull.ts, 6, 49))
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 7, 25))
>value : Symbol(value, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 7, 28))
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 7, 25))
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 7, 25))
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 7, 25))
function flatMapChildren<T>(node: Node, cb: (child: Node) => readonly T[] | T | undefined): readonly T[] {
>flatMapChildren : Symbol(flatMapChildren, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 7, 67))
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 9, 25))
>node : Symbol(node, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 9, 28))
>Node : Symbol(Node, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(inferenceDoesNotAddUndefinedOrNull.ts, 0, 63))
>cb : Symbol(cb, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 9, 39))
>child : Symbol(child, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 9, 45))
>Node : Symbol(Node, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(inferenceDoesNotAddUndefinedOrNull.ts, 0, 63))
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 9, 25))
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 9, 25))
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 9, 25))
const result: T[] = [];
>result : Symbol(result, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 10, 9))
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 9, 25))
node.forEachChild(child => {
>node.forEachChild : Symbol(Node.forEachChild, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 2, 16))
>node : Symbol(node, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 9, 28))
>forEachChild : Symbol(Node.forEachChild, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 2, 16))
>child : Symbol(child, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 11, 22))
const value = cb(child);
>value : Symbol(value, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 12, 13))
>cb : Symbol(cb, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 9, 39))
>child : Symbol(child, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 11, 22))
if (value !== undefined) {
>value : Symbol(value, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 12, 13))
>undefined : Symbol(undefined)
result.push(...toArray(value));
>result.push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --))
>result : Symbol(result, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 10, 9))
>push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --))
>toArray : Symbol(toArray, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 4, 1), Decl(inferenceDoesNotAddUndefinedOrNull.ts, 6, 49))
>value : Symbol(value, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 12, 13))
}
});
return result;
>result : Symbol(result, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 10, 9))
}
function flatMapChildren2<T>(node: Node, cb: (child: Node) => readonly T[] | T | null): readonly T[] {
>flatMapChildren2 : Symbol(flatMapChildren2, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 18, 1))
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 20, 26))
>node : Symbol(node, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 20, 29))
>Node : Symbol(Node, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(inferenceDoesNotAddUndefinedOrNull.ts, 0, 63))
>cb : Symbol(cb, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 20, 40))
>child : Symbol(child, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 20, 46))
>Node : Symbol(Node, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(inferenceDoesNotAddUndefinedOrNull.ts, 0, 63))
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 20, 26))
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 20, 26))
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 20, 26))
const result: T[] = [];
>result : Symbol(result, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 21, 9))
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 20, 26))
node.forEachChild(child => {
>node.forEachChild : Symbol(Node.forEachChild, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 2, 16))
>node : Symbol(node, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 20, 29))
>forEachChild : Symbol(Node.forEachChild, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 2, 16))
>child : Symbol(child, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 22, 22))
const value = cb(child);
>value : Symbol(value, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 23, 13))
>cb : Symbol(cb, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 20, 40))
>child : Symbol(child, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 22, 22))
if (value !== null) {
>value : Symbol(value, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 23, 13))
result.push(...toArray(value));
>result.push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --))
>result : Symbol(result, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 21, 9))
>push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --))
>toArray : Symbol(toArray, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 4, 1), Decl(inferenceDoesNotAddUndefinedOrNull.ts, 6, 49))
>value : Symbol(value, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 23, 13))
}
});
return result;
>result : Symbol(result, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 21, 9))
}

View File

@@ -0,0 +1,109 @@
=== tests/cases/compiler/inferenceDoesNotAddUndefinedOrNull.ts ===
interface NodeArray<T extends Node> extends ReadonlyArray<T> {}
interface Node {
forEachChild<T>(cbNode: (node: Node) => T | undefined, cbNodeArray?: (nodes: NodeArray<Node>) => T | undefined): T | undefined;
>forEachChild : <T>(cbNode: (node: Node) => T | undefined, cbNodeArray?: ((nodes: NodeArray<Node>) => T | undefined) | undefined) => T | undefined
>cbNode : (node: Node) => T | undefined
>node : Node
>cbNodeArray : ((nodes: NodeArray<Node>) => T | undefined) | undefined
>nodes : NodeArray<Node>
}
declare function toArray<T>(value: T | T[]): T[];
>toArray : { <T>(value: T | T[]): T[]; <T>(value: T | readonly T[]): readonly T[]; }
>value : T | T[]
declare function toArray<T>(value: T | readonly T[]): readonly T[];
>toArray : { <T>(value: T | T[]): T[]; <T>(value: T | readonly T[]): readonly T[]; }
>value : T | readonly T[]
function flatMapChildren<T>(node: Node, cb: (child: Node) => readonly T[] | T | undefined): readonly T[] {
>flatMapChildren : <T>(node: Node, cb: (child: Node) => readonly T[] | T | undefined) => readonly T[]
>node : Node
>cb : (child: Node) => readonly T[] | T | undefined
>child : Node
const result: T[] = [];
>result : T[]
>[] : never[]
node.forEachChild(child => {
>node.forEachChild(child => { const value = cb(child); if (value !== undefined) { result.push(...toArray(value)); } }) : void | undefined
>node.forEachChild : <T>(cbNode: (node: Node) => T | undefined, cbNodeArray?: ((nodes: NodeArray<Node>) => T | undefined) | undefined) => T | undefined
>node : Node
>forEachChild : <T>(cbNode: (node: Node) => T | undefined, cbNodeArray?: ((nodes: NodeArray<Node>) => T | undefined) | undefined) => T | undefined
>child => { const value = cb(child); if (value !== undefined) { result.push(...toArray(value)); } } : (child: Node) => void
>child : Node
const value = cb(child);
>value : T | readonly T[] | undefined
>cb(child) : T | readonly T[] | undefined
>cb : (child: Node) => T | readonly T[] | undefined
>child : Node
if (value !== undefined) {
>value !== undefined : boolean
>value : T | readonly T[] | undefined
>undefined : undefined
result.push(...toArray(value));
>result.push(...toArray(value)) : number
>result.push : (...items: T[]) => number
>result : T[]
>push : (...items: T[]) => number
>...toArray(value) : T
>toArray(value) : readonly T[]
>toArray : { <T>(value: T | T[]): T[]; <T>(value: T | readonly T[]): readonly T[]; }
>value : readonly T[] | (T & ({} | null))
}
});
return result;
>result : T[]
}
function flatMapChildren2<T>(node: Node, cb: (child: Node) => readonly T[] | T | null): readonly T[] {
>flatMapChildren2 : <T>(node: Node, cb: (child: Node) => readonly T[] | T | null) => readonly T[]
>node : Node
>cb : (child: Node) => readonly T[] | T | null
>child : Node
>null : null
const result: T[] = [];
>result : T[]
>[] : never[]
node.forEachChild(child => {
>node.forEachChild(child => { const value = cb(child); if (value !== null) { result.push(...toArray(value)); } }) : void | undefined
>node.forEachChild : <T>(cbNode: (node: Node) => T | undefined, cbNodeArray?: ((nodes: NodeArray<Node>) => T | undefined) | undefined) => T | undefined
>node : Node
>forEachChild : <T>(cbNode: (node: Node) => T | undefined, cbNodeArray?: ((nodes: NodeArray<Node>) => T | undefined) | undefined) => T | undefined
>child => { const value = cb(child); if (value !== null) { result.push(...toArray(value)); } } : (child: Node) => void
>child : Node
const value = cb(child);
>value : T | readonly T[] | null
>cb(child) : T | readonly T[] | null
>cb : (child: Node) => T | readonly T[] | null
>child : Node
if (value !== null) {
>value !== null : boolean
>value : T | readonly T[] | null
>null : null
result.push(...toArray(value));
>result.push(...toArray(value)) : number
>result.push : (...items: T[]) => number
>result : T[]
>push : (...items: T[]) => number
>...toArray(value) : T
>toArray(value) : readonly T[]
>toArray : { <T>(value: T | T[]): T[]; <T>(value: T | readonly T[]): readonly T[]; }
>value : readonly T[] | (T & ({} | undefined))
}
});
return result;
>result : T[]
}

View File

@@ -0,0 +1,55 @@
//// [inferenceOfNullableObjectTypesWithCommonBase.ts]
function equal<T>(a: T, b: T) { }
let v = null!;
// Object types with common base types
type B = { foo: string }
type D = { foo: string; bar: number }
equal(v as B, v as undefined | D)
equal(v as undefined | D, v as B)
equal<undefined | B>(v as B, v as undefined | D)
equal<undefined | B>(v as undefined | D, v as B)
equal(v as B, v as undefined)
equal(v as undefined, v as B)
equal(v as B, v as D)
equal(v as D, v as B)
equal(v as B, v as B | undefined)
equal(v as B | undefined, v as B)
equal(v as 'a' | undefined, v as 'b');
equal(v as 'a', v as 'b' | undefined);
equal(v as 'a' | undefined, v as 'b' | null);
equal(v as 'a' | null, v as 'b' | undefined);
equal(v as string, v as string & { tag: 'foo' } | undefined);
equal(v as string & { tag: 'foo' } | undefined, v as string);
//// [inferenceOfNullableObjectTypesWithCommonBase.js]
"use strict";
function equal(a, b) { }
var v = null;
equal(v, v);
equal(v, v);
equal(v, v);
equal(v, v);
equal(v, v);
equal(v, v);
equal(v, v);
equal(v, v);
equal(v, v);
equal(v, v);
equal(v, v);
equal(v, v);
equal(v, v);
equal(v, v);
equal(v, v);
equal(v, v);

View File

@@ -0,0 +1,125 @@
=== tests/cases/compiler/inferenceOfNullableObjectTypesWithCommonBase.ts ===
function equal<T>(a: T, b: T) { }
>equal : Symbol(equal, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 0, 0))
>T : Symbol(T, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 0, 15))
>a : Symbol(a, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 0, 18))
>T : Symbol(T, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 0, 15))
>b : Symbol(b, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 0, 23))
>T : Symbol(T, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 0, 15))
let v = null!;
>v : Symbol(v, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 3))
// Object types with common base types
type B = { foo: string }
>B : Symbol(B, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 14))
>foo : Symbol(foo, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 6, 10))
type D = { foo: string; bar: number }
>D : Symbol(D, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 6, 24))
>foo : Symbol(foo, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 7, 10))
>bar : Symbol(bar, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 7, 23))
equal(v as B, v as undefined | D)
>equal : Symbol(equal, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 0, 0))
>v : Symbol(v, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 3))
>B : Symbol(B, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 14))
>v : Symbol(v, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 3))
>D : Symbol(D, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 6, 24))
equal(v as undefined | D, v as B)
>equal : Symbol(equal, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 0, 0))
>v : Symbol(v, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 3))
>D : Symbol(D, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 6, 24))
>v : Symbol(v, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 3))
>B : Symbol(B, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 14))
equal<undefined | B>(v as B, v as undefined | D)
>equal : Symbol(equal, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 0, 0))
>B : Symbol(B, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 14))
>v : Symbol(v, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 3))
>B : Symbol(B, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 14))
>v : Symbol(v, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 3))
>D : Symbol(D, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 6, 24))
equal<undefined | B>(v as undefined | D, v as B)
>equal : Symbol(equal, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 0, 0))
>B : Symbol(B, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 14))
>v : Symbol(v, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 3))
>D : Symbol(D, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 6, 24))
>v : Symbol(v, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 3))
>B : Symbol(B, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 14))
equal(v as B, v as undefined)
>equal : Symbol(equal, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 0, 0))
>v : Symbol(v, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 3))
>B : Symbol(B, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 14))
>v : Symbol(v, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 3))
equal(v as undefined, v as B)
>equal : Symbol(equal, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 0, 0))
>v : Symbol(v, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 3))
>v : Symbol(v, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 3))
>B : Symbol(B, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 14))
equal(v as B, v as D)
>equal : Symbol(equal, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 0, 0))
>v : Symbol(v, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 3))
>B : Symbol(B, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 14))
>v : Symbol(v, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 3))
>D : Symbol(D, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 6, 24))
equal(v as D, v as B)
>equal : Symbol(equal, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 0, 0))
>v : Symbol(v, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 3))
>D : Symbol(D, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 6, 24))
>v : Symbol(v, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 3))
>B : Symbol(B, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 14))
equal(v as B, v as B | undefined)
>equal : Symbol(equal, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 0, 0))
>v : Symbol(v, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 3))
>B : Symbol(B, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 14))
>v : Symbol(v, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 3))
>B : Symbol(B, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 14))
equal(v as B | undefined, v as B)
>equal : Symbol(equal, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 0, 0))
>v : Symbol(v, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 3))
>B : Symbol(B, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 14))
>v : Symbol(v, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 3))
>B : Symbol(B, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 14))
equal(v as 'a' | undefined, v as 'b');
>equal : Symbol(equal, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 0, 0))
>v : Symbol(v, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 3))
>v : Symbol(v, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 3))
equal(v as 'a', v as 'b' | undefined);
>equal : Symbol(equal, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 0, 0))
>v : Symbol(v, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 3))
>v : Symbol(v, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 3))
equal(v as 'a' | undefined, v as 'b' | null);
>equal : Symbol(equal, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 0, 0))
>v : Symbol(v, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 3))
>v : Symbol(v, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 3))
equal(v as 'a' | null, v as 'b' | undefined);
>equal : Symbol(equal, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 0, 0))
>v : Symbol(v, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 3))
>v : Symbol(v, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 3))
equal(v as string, v as string & { tag: 'foo' } | undefined);
>equal : Symbol(equal, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 0, 0))
>v : Symbol(v, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 3))
>v : Symbol(v, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 3))
>tag : Symbol(tag, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 30, 34))
equal(v as string & { tag: 'foo' } | undefined, v as string);
>equal : Symbol(equal, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 0, 0))
>v : Symbol(v, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 3))
>tag : Symbol(tag, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 31, 21))
>v : Symbol(v, Decl(inferenceOfNullableObjectTypesWithCommonBase.ts, 2, 3))

View File

@@ -0,0 +1,154 @@
=== tests/cases/compiler/inferenceOfNullableObjectTypesWithCommonBase.ts ===
function equal<T>(a: T, b: T) { }
>equal : <T>(a: T, b: T) => void
>a : T
>b : T
let v = null!;
>v : never
>null! : never
>null : null
// Object types with common base types
type B = { foo: string }
>B : { foo: string; }
>foo : string
type D = { foo: string; bar: number }
>D : { foo: string; bar: number; }
>foo : string
>bar : number
equal(v as B, v as undefined | D)
>equal(v as B, v as undefined | D) : void
>equal : <T>(a: T, b: T) => void
>v as B : B
>v : never
>v as undefined | D : D | undefined
>v : never
equal(v as undefined | D, v as B)
>equal(v as undefined | D, v as B) : void
>equal : <T>(a: T, b: T) => void
>v as undefined | D : D | undefined
>v : never
>v as B : B
>v : never
equal<undefined | B>(v as B, v as undefined | D)
>equal<undefined | B>(v as B, v as undefined | D) : void
>equal : <T>(a: T, b: T) => void
>v as B : B
>v : never
>v as undefined | D : D | undefined
>v : never
equal<undefined | B>(v as undefined | D, v as B)
>equal<undefined | B>(v as undefined | D, v as B) : void
>equal : <T>(a: T, b: T) => void
>v as undefined | D : D | undefined
>v : never
>v as B : B
>v : never
equal(v as B, v as undefined)
>equal(v as B, v as undefined) : void
>equal : <T>(a: T, b: T) => void
>v as B : B
>v : never
>v as undefined : undefined
>v : never
equal(v as undefined, v as B)
>equal(v as undefined, v as B) : void
>equal : <T>(a: T, b: T) => void
>v as undefined : undefined
>v : never
>v as B : B
>v : never
equal(v as B, v as D)
>equal(v as B, v as D) : void
>equal : <T>(a: T, b: T) => void
>v as B : B
>v : never
>v as D : D
>v : never
equal(v as D, v as B)
>equal(v as D, v as B) : void
>equal : <T>(a: T, b: T) => void
>v as D : D
>v : never
>v as B : B
>v : never
equal(v as B, v as B | undefined)
>equal(v as B, v as B | undefined) : void
>equal : <T>(a: T, b: T) => void
>v as B : B
>v : never
>v as B | undefined : B | undefined
>v : never
equal(v as B | undefined, v as B)
>equal(v as B | undefined, v as B) : void
>equal : <T>(a: T, b: T) => void
>v as B | undefined : B | undefined
>v : never
>v as B : B
>v : never
equal(v as 'a' | undefined, v as 'b');
>equal(v as 'a' | undefined, v as 'b') : void
>equal : <T>(a: T, b: T) => void
>v as 'a' | undefined : "a" | undefined
>v : never
>v as 'b' : "b"
>v : never
equal(v as 'a', v as 'b' | undefined);
>equal(v as 'a', v as 'b' | undefined) : void
>equal : <T>(a: T, b: T) => void
>v as 'a' : "a"
>v : never
>v as 'b' | undefined : "b" | undefined
>v : never
equal(v as 'a' | undefined, v as 'b' | null);
>equal(v as 'a' | undefined, v as 'b' | null) : void
>equal : <T>(a: T, b: T) => void
>v as 'a' | undefined : "a" | undefined
>v : never
>v as 'b' | null : "b" | null
>v : never
>null : null
equal(v as 'a' | null, v as 'b' | undefined);
>equal(v as 'a' | null, v as 'b' | undefined) : void
>equal : <T>(a: T, b: T) => void
>v as 'a' | null : "a" | null
>v : never
>null : null
>v as 'b' | undefined : "b" | undefined
>v : never
equal(v as string, v as string & { tag: 'foo' } | undefined);
>equal(v as string, v as string & { tag: 'foo' } | undefined) : void
>equal : <T>(a: T, b: T) => void
>v as string : string
>v : never
>v as string & { tag: 'foo' } | undefined : (string & { tag: 'foo'; }) | undefined
>v : never
>tag : "foo"
equal(v as string & { tag: 'foo' } | undefined, v as string);
>equal(v as string & { tag: 'foo' } | undefined, v as string) : void
>equal : <T>(a: T, b: T) => void
>v as string & { tag: 'foo' } | undefined : (string & { tag: 'foo'; }) | undefined
>v : never
>tag : "foo"
>v as string : string
>v : never

View File

@@ -0,0 +1,32 @@
// @strict: true
interface NodeArray<T extends Node> extends ReadonlyArray<T> {}
interface Node {
forEachChild<T>(cbNode: (node: Node) => T | undefined, cbNodeArray?: (nodes: NodeArray<Node>) => T | undefined): T | undefined;
}
declare function toArray<T>(value: T | T[]): T[];
declare function toArray<T>(value: T | readonly T[]): readonly T[];
function flatMapChildren<T>(node: Node, cb: (child: Node) => readonly T[] | T | undefined): readonly T[] {
const result: T[] = [];
node.forEachChild(child => {
const value = cb(child);
if (value !== undefined) {
result.push(...toArray(value));
}
});
return result;
}
function flatMapChildren2<T>(node: Node, cb: (child: Node) => readonly T[] | T | null): readonly T[] {
const result: T[] = [];
node.forEachChild(child => {
const value = cb(child);
if (value !== null) {
result.push(...toArray(value));
}
});
return result;
}

View File

@@ -0,0 +1,34 @@
// @strict: true
function equal<T>(a: T, b: T) { }
let v = null!;
// Object types with common base types
type B = { foo: string }
type D = { foo: string; bar: number }
equal(v as B, v as undefined | D)
equal(v as undefined | D, v as B)
equal<undefined | B>(v as B, v as undefined | D)
equal<undefined | B>(v as undefined | D, v as B)
equal(v as B, v as undefined)
equal(v as undefined, v as B)
equal(v as B, v as D)
equal(v as D, v as B)
equal(v as B, v as B | undefined)
equal(v as B | undefined, v as B)
equal(v as 'a' | undefined, v as 'b');
equal(v as 'a', v as 'b' | undefined);
equal(v as 'a' | undefined, v as 'b' | null);
equal(v as 'a' | null, v as 'b' | undefined);
equal(v as string, v as string & { tag: 'foo' } | undefined);
equal(v as string & { tag: 'foo' } | undefined, v as string);