Skip to content

Commit 0e52555

Browse files
ahejlsbergmhegazy
authored andcommitted
Restoring union type subtype reduction
1 parent 9866172 commit 0e52555

File tree

1 file changed

+44
-94
lines changed

1 file changed

+44
-94
lines changed

src/compiler/checker.ts

Lines changed: 44 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -3119,7 +3119,7 @@ namespace ts {
31193119
}
31203120

31213121
function resolveTupleTypeMembers(type: TupleType) {
3122-
let arrayType = resolveStructuredTypeMembers(createArrayType(getUnionType(type.elementTypes, /*noDeduplication*/ true)));
3122+
let arrayType = resolveStructuredTypeMembers(createArrayType(getUnionType(type.elementTypes, /*noSubtypeReduction*/ true)));
31233123
let members = createTupleTypeMemberSymbols(type.elementTypes);
31243124
addInheritedMembers(members, arrayType.properties);
31253125
setObjectTypeMembers(type, members, arrayType.callSignatures, arrayType.constructSignatures, arrayType.stringIndexType, arrayType.numberIndexType);
@@ -3451,29 +3451,6 @@ namespace ts {
34513451
return undefined;
34523452
}
34533453

3454-
// Check if a property with the given name is known anywhere in the given type. In an object
3455-
// type, a property is considered known if the object type is empty, if it has any index
3456-
// signatures, or if the property is actually declared in the type. In a union or intersection
3457-
// type, a property is considered known if it is known in any constituent type.
3458-
function isKnownProperty(type: Type, name: string): boolean {
3459-
if (type.flags & TypeFlags.ObjectType && type !== globalObjectType) {
3460-
const resolved = resolveStructuredTypeMembers(type);
3461-
return !!(resolved.properties.length === 0 ||
3462-
resolved.stringIndexType ||
3463-
resolved.numberIndexType ||
3464-
getPropertyOfType(type, name));
3465-
}
3466-
if (type.flags & TypeFlags.UnionOrIntersection) {
3467-
for (let t of (<UnionOrIntersectionType>type).types) {
3468-
if (isKnownProperty(t, name)) {
3469-
return true;
3470-
}
3471-
}
3472-
return false;
3473-
}
3474-
return true;
3475-
}
3476-
34773454
function getSignaturesOfStructuredType(type: Type, kind: SignatureKind): Signature[] {
34783455
if (type.flags & TypeFlags.StructuredType) {
34793456
let resolved = resolveStructuredTypeMembers(<ObjectType>type);
@@ -4103,73 +4080,20 @@ namespace ts {
41034080
}
41044081
}
41054082

4106-
function isObjectLiteralTypeDuplicateOf(source: ObjectType, target: ObjectType): boolean {
4107-
let sourceProperties = getPropertiesOfObjectType(source);
4108-
let targetProperties = getPropertiesOfObjectType(target);
4109-
if (sourceProperties.length !== targetProperties.length) {
4110-
return false;
4111-
}
4112-
for (let sourceProp of sourceProperties) {
4113-
let targetProp = getPropertyOfObjectType(target, sourceProp.name);
4114-
if (!targetProp ||
4115-
getDeclarationFlagsFromSymbol(targetProp) & (NodeFlags.Private | NodeFlags.Protected) ||
4116-
!isTypeDuplicateOf(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp))) {
4117-
return false;
4118-
}
4119-
}
4120-
return true;
4121-
}
4122-
4123-
function isTupleTypeDuplicateOf(source: TupleType, target: TupleType): boolean {
4124-
let sourceTypes = source.elementTypes;
4125-
let targetTypes = target.elementTypes;
4126-
if (sourceTypes.length !== targetTypes.length) {
4127-
return false;
4128-
}
4129-
for (var i = 0; i < sourceTypes.length; i++) {
4130-
if (!isTypeDuplicateOf(sourceTypes[i], targetTypes[i])) {
4131-
return false;
4132-
}
4133-
}
4134-
return true;
4135-
}
4136-
4137-
// Returns true if the source type is a duplicate of the target type. A source type is a duplicate of
4138-
// a target type if the the two are identical, with the exception that the source type may have null or
4139-
// undefined in places where the target type doesn't. This is by design an asymmetric relationship.
4140-
function isTypeDuplicateOf(source: Type, target: Type): boolean {
4141-
if (source === target) {
4142-
return true;
4143-
}
4144-
if (source.flags & TypeFlags.Undefined || source.flags & TypeFlags.Null && !(target.flags & TypeFlags.Undefined)) {
4145-
return true;
4146-
}
4147-
if (source.flags & TypeFlags.ObjectLiteral && target.flags & TypeFlags.ObjectType) {
4148-
return isObjectLiteralTypeDuplicateOf(<ObjectType>source, <ObjectType>target);
4149-
}
4150-
if (isArrayType(source) && isArrayType(target)) {
4151-
return isTypeDuplicateOf((<TypeReference>source).typeArguments[0], (<TypeReference>target).typeArguments[0]);
4152-
}
4153-
if (isTupleType(source) && isTupleType(target)) {
4154-
return isTupleTypeDuplicateOf(<TupleType>source, <TupleType>target);
4155-
}
4156-
return isTypeIdenticalTo(source, target);
4157-
}
4158-
4159-
function isTypeDuplicateOfSomeType(candidate: Type, types: Type[]): boolean {
4160-
for (let type of types) {
4161-
if (candidate !== type && isTypeDuplicateOf(candidate, type)) {
4083+
function isSubtypeOfAny(candidate: Type, types: Type[]): boolean {
4084+
for (var i = 0, len = types.length; i < len; i++) {
4085+
if (candidate !== types[i] && isTypeSubtypeOf(candidate, types[i])) {
41624086
return true;
41634087
}
41644088
}
41654089
return false;
41664090
}
41674091

4168-
function removeDuplicateTypes(types: Type[]) {
4169-
let i = types.length;
4092+
function removeSubtypes(types: Type[]) {
4093+
var i = types.length;
41704094
while (i > 0) {
41714095
i--;
4172-
if (isTypeDuplicateOfSomeType(types[i], types)) {
4096+
if (isSubtypeOfAny(types[i], types)) {
41734097
types.splice(i, 1);
41744098
}
41754099
}
@@ -4194,12 +4118,14 @@ namespace ts {
41944118
}
41954119
}
41964120

4197-
// We always deduplicate the constituent type set based on object identity, but we'll also deduplicate
4198-
// based on the structure of the types unless the noDeduplication flag is true, which is the case when
4199-
// creating a union type from a type node and when instantiating a union type. In both of those cases,
4200-
// structural deduplication has to be deferred to properly support recursive union types. For example,
4201-
// a type of the form "type Item = string | (() => Item)" cannot be deduplicated during its declaration.
4202-
function getUnionType(types: Type[], noDeduplication?: boolean): Type {
4121+
// We reduce the constituent type set to only include types that aren't subtypes of other types, unless
4122+
// the noSubtypeReduction flag is specified, in which case we perform a simple deduplication based on
4123+
// object identity. Subtype reduction is possible only when union types are known not to circularly
4124+
// reference themselves (as is the case with union types created by expression constructs such as array
4125+
// literals and the || and ?: operators). Named types can circularly reference themselves and therefore
4126+
// cannot be deduplicated during their declaration. For example, "type Item = string | (() => Item" is
4127+
// a named type that circularly references itself.
4128+
function getUnionType(types: Type[], noSubtypeReduction?: boolean): Type {
42034129
if (types.length === 0) {
42044130
return emptyObjectType;
42054131
}
@@ -4208,12 +4134,12 @@ namespace ts {
42084134
if (containsTypeAny(typeSet)) {
42094135
return anyType;
42104136
}
4211-
if (noDeduplication) {
4137+
if (noSubtypeReduction) {
42124138
removeAllButLast(typeSet, undefinedType);
42134139
removeAllButLast(typeSet, nullType);
42144140
}
42154141
else {
4216-
removeDuplicateTypes(typeSet);
4142+
removeSubtypes(typeSet);
42174143
}
42184144
if (typeSet.length === 1) {
42194145
return typeSet[0];
@@ -4230,7 +4156,7 @@ namespace ts {
42304156
function getTypeFromUnionTypeNode(node: UnionTypeNode): Type {
42314157
let links = getNodeLinks(node);
42324158
if (!links.resolvedType) {
4233-
links.resolvedType = getUnionType(map(node.types, getTypeFromTypeNode), /*noDeduplication*/ true);
4159+
links.resolvedType = getUnionType(map(node.types, getTypeFromTypeNode), /*noSubtypeReduction*/ true);
42344160
}
42354161
return links.resolvedType;
42364162
}
@@ -4526,7 +4452,7 @@ namespace ts {
45264452
return createTupleType(instantiateList((<TupleType>type).elementTypes, mapper, instantiateType));
45274453
}
45284454
if (type.flags & TypeFlags.Union) {
4529-
return getUnionType(instantiateList((<UnionType>type).types, mapper, instantiateType), /*noDeduplication*/ true);
4455+
return getUnionType(instantiateList((<UnionType>type).types, mapper, instantiateType), /*noSubtypeReduction*/ true);
45304456
}
45314457
if (type.flags & TypeFlags.Intersection) {
45324458
return getIntersectionType(instantiateList((<IntersectionType>type).types, mapper, instantiateType));
@@ -4813,6 +4739,30 @@ namespace ts {
48134739
return Ternary.False;
48144740
}
48154741

4742+
// Check if a property with the given name is known anywhere in the given type. In an object type, a property
4743+
// is considered known if the object type is empty and the check is for assignability, if the object type has
4744+
// index signatures, or if the property is actually declared in the object type. In a union or intersection
4745+
// type, a property is considered known if it is known in any constituent type.
4746+
function isKnownProperty(type: Type, name: string): boolean {
4747+
if (type.flags & TypeFlags.ObjectType) {
4748+
const resolved = resolveStructuredTypeMembers(type);
4749+
if (relation === assignableRelation && (type === globalObjectType || resolved.properties.length === 0) ||
4750+
resolved.stringIndexType || resolved.numberIndexType || getPropertyOfType(type, name)) {
4751+
return true;
4752+
}
4753+
return false;
4754+
}
4755+
if (type.flags & TypeFlags.UnionOrIntersection) {
4756+
for (let t of (<UnionOrIntersectionType>type).types) {
4757+
if (isKnownProperty(t, name)) {
4758+
return true;
4759+
}
4760+
}
4761+
return false;
4762+
}
4763+
return true;
4764+
}
4765+
48164766
function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean {
48174767
for (let prop of getPropertiesOfObjectType(source)) {
48184768
if (!isKnownProperty(target, prop.name)) {
@@ -5594,7 +5544,7 @@ namespace ts {
55945544
return getWidenedTypeOfObjectLiteral(type);
55955545
}
55965546
if (type.flags & TypeFlags.Union) {
5597-
return getUnionType(map((<UnionType>type).types, getWidenedType));
5547+
return getUnionType(map((<UnionType>type).types, getWidenedType), /*noSubtypeReduction*/ true);
55985548
}
55995549
if (isArrayType(type)) {
56005550
return createArrayType(getWidenedType((<TypeReference>type).typeArguments[0]));

0 commit comments

Comments
 (0)