From 8f2e00f6ffdb94c98225b3054d7fc91ff2ccfccb Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 25 Dec 2020 11:48:12 -1000 Subject: [PATCH] Create separate types for equivalent aliased unions --- src/compiler/checker.ts | 56 ++++++++++++++++++++++++++++++++--------- src/compiler/types.ts | 6 +++-- 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1c3bc622b0c..375362c0cff 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10622,7 +10622,7 @@ namespace ts { return type; } if (type.flags & TypeFlags.Union) { - return getUnionType(sameMap((type).types, getLowerBoundOfKeyType)); + return mapType(type, getLowerBoundOfKeyType); } if (type.flags & TypeFlags.Intersection) { return getIntersectionType(sameMap((type).types, getLowerBoundOfKeyType)); @@ -11443,6 +11443,13 @@ namespace ts { return reduced; } + function getUnaliasedUnionType(type: Type) { + if (type.flags & TypeFlags.Union && !(type.flags & TypeFlags.EnumLiteral) && (type).aliasSymbol) { + return (type).unaliasedType || ((type).unaliasedType = getUnionTypeFromSortedList((type).types, (type).objectFlags)); + } + return type; + } + function isNeverReducedProperty(prop: Symbol) { return isDiscriminantWithNeverType(prop) || isConflictingPrivateProperty(prop); } @@ -13126,7 +13133,7 @@ namespace ts { function addTypeToUnion(typeSet: Type[], includes: TypeFlags, type: Type) { const flags = type.flags; if (flags & TypeFlags.Union) { - return addTypesToUnion(typeSet, includes, (type).types); + return addTypesToUnion(typeSet, includes | (type.aliasSymbol ? TypeFlags.Union : 0), (type).types); } // We ignore 'never' types in unions if (!(flags & TypeFlags.Never)) { @@ -13242,6 +13249,21 @@ namespace ts { } } + function findSingleAliasedUnion(types: readonly Type[]) { + let aliasedUnion: UnionType | undefined; + for (const t of types) { + if (t.flags & TypeFlags.Union && (t).aliasSymbol) { + if (!aliasedUnion) { + aliasedUnion = t; + } + else if (t !== aliasedUnion) { + return undefined; + } + } + } + return aliasedUnion; + } + // We sort and deduplicate the constituent types based on object identity. If the subtypeReduction // flag is specified we also reduce the constituent type set to only include types that aren't subtypes // of other types. Subtype reduction is expensive for large union types and is possible only when union @@ -13258,6 +13280,12 @@ namespace ts { } const typeSet: Type[] = []; const includes = addTypesToUnion(typeSet, 0, types); + if (includes & TypeFlags.Union && !aliasSymbol) { + const aliasedUnion = findSingleAliasedUnion(types); + if (aliasedUnion && arraysEqual(typeSet, aliasedUnion.types)) { + return aliasedUnion; + } + } if (unionReduction !== UnionReduction.None) { if (includes & TypeFlags.AnyOrUnknown) { return includes & TypeFlags.Any ? includes & TypeFlags.IncludesWildcard ? wildcardType : anyType : unknownType; @@ -13328,7 +13356,7 @@ namespace ts { if (types.length === 1) { return types[0]; } - const id = getTypeListId(types); + const id = getTypeListId(types) + (aliasSymbol ? `@${getSymbolId(aliasSymbol)}` : ""); let type = unionTypes.get(id); if (!type) { type = createType(TypeFlags.Union); @@ -14850,7 +14878,7 @@ namespace ts { function getRegularTypeOfLiteralType(type: Type): Type { return type.flags & TypeFlags.FreshableLiteral ? (type).regularType : - type.flags & TypeFlags.Union ? ((type).regularType || ((type).regularType = getUnionType(sameMap((type).types, getRegularTypeOfLiteralType)) as UnionType)) : + type.flags & TypeFlags.Union ? ((type).regularType || ((type).regularType = mapType(type, getRegularTypeOfLiteralType) as UnionType)) : type; } @@ -16562,7 +16590,7 @@ namespace ts { while (true) { const t = isFreshLiteralType(type) ? (type).regularType : getObjectFlags(type) & ObjectFlags.Reference && (type).node ? createTypeReference((type).target, getTypeArguments(type)) : - type.flags & TypeFlags.UnionOrIntersection ? getReducedType(type) : + type.flags & TypeFlags.UnionOrIntersection ? getUnaliasedUnionType(getReducedType(type)) : type.flags & TypeFlags.Substitution ? writing ? (type).baseType : (type).substitute : type.flags & TypeFlags.Simplifiable ? getSimplifiedType(type, writing) : type; @@ -16960,7 +16988,7 @@ namespace ts { if (isPerformingExcessPropertyChecks) { if (hasExcessProperties(source, target, reportErrors)) { if (reportErrors) { - reportRelationError(headMessage, source, target); + reportRelationError(headMessage, source, originalTarget.aliasSymbol ? originalTarget : target); } return Ternary.False; } @@ -16972,14 +17000,16 @@ namespace ts { (getPropertiesOfType(source).length > 0 || typeHasCallOrConstructSignatures(source)); if (isPerformingCommonPropertyChecks && !hasCommonProperties(source, target, isComparingJsxAttributes)) { if (reportErrors) { + const sourceString = typeToString(originalSource.aliasSymbol ? originalSource : source); + const targetString = typeToString(originalTarget.aliasSymbol ? originalTarget : target); const calls = getSignaturesOfType(source, SignatureKind.Call); const constructs = getSignaturesOfType(source, SignatureKind.Construct); if (calls.length > 0 && isRelatedTo(getReturnTypeOfSignature(calls[0]), target, /*reportErrors*/ false) || constructs.length > 0 && isRelatedTo(getReturnTypeOfSignature(constructs[0]), target, /*reportErrors*/ false)) { - reportError(Diagnostics.Value_of_type_0_has_no_properties_in_common_with_type_1_Did_you_mean_to_call_it, typeToString(source), typeToString(target)); + reportError(Diagnostics.Value_of_type_0_has_no_properties_in_common_with_type_1_Did_you_mean_to_call_it, sourceString, targetString); } else { - reportError(Diagnostics.Type_0_has_no_properties_in_common_with_type_1, typeToString(source), typeToString(target)); + reportError(Diagnostics.Type_0_has_no_properties_in_common_with_type_1, sourceString, targetString); } } return Ternary.False; @@ -19238,7 +19268,7 @@ namespace ts { type.flags & TypeFlags.NumberLiteral ? numberType : type.flags & TypeFlags.BigIntLiteral ? bigintType : type.flags & TypeFlags.BooleanLiteral ? booleanType : - type.flags & TypeFlags.Union ? getUnionType(sameMap((type).types, getBaseTypeOfLiteralType)) : + type.flags & TypeFlags.Union ? mapType(type, getBaseTypeOfLiteralType) : type; } @@ -19248,13 +19278,13 @@ namespace ts { type.flags & TypeFlags.NumberLiteral && isFreshLiteralType(type) ? numberType : type.flags & TypeFlags.BigIntLiteral && isFreshLiteralType(type) ? bigintType : type.flags & TypeFlags.BooleanLiteral && isFreshLiteralType(type) ? booleanType : - type.flags & TypeFlags.Union ? getUnionType(sameMap((type).types, getWidenedLiteralType)) : + type.flags & TypeFlags.Union ? mapType(type, getWidenedLiteralType) : type; } function getWidenedUniqueESSymbolType(type: Type): Type { return type.flags & TypeFlags.UniqueESSymbol ? esSymbolType : - type.flags & TypeFlags.Union ? getUnionType(sameMap((type).types, getWidenedUniqueESSymbolType)) : + type.flags & TypeFlags.Union ? mapType(type, getWidenedUniqueESSymbolType) : type; } @@ -21474,8 +21504,10 @@ namespace ts { return mapper(type); } let mappedTypes: Type[] | undefined; + let changed = false; for (const t of (type).types) { const mapped = mapper(t); + changed ||= t !== mapped; if (mapped) { if (!mappedTypes) { mappedTypes = [mapped]; @@ -21485,7 +21517,7 @@ namespace ts { } } } - return mappedTypes && getUnionType(mappedTypes, noReductions ? UnionReduction.None : UnionReduction.Literal); + return changed ? mappedTypes && getUnionType(mappedTypes, noReductions ? UnionReduction.None : UnionReduction.Literal) : type; } function extractTypesOfKind(type: Type, kind: TypeFlags) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 81181164a79..ee9bd24cd07 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5292,9 +5292,11 @@ namespace ts { export interface UnionType extends UnionOrIntersectionType { /* @internal */ - resolvedReducedType: Type; + resolvedReducedType?: Type; /* @internal */ - regularType: UnionType; + regularType?: UnionType; + /* @internal */ + unaliasedType?: Type; } export interface IntersectionType extends UnionOrIntersectionType {