Sort unions without using type IDs

This commit is contained in:
Jake Bailey
2025-03-24 21:12:31 -07:00
parent a2239d6eef
commit 29d1a90c70
3 changed files with 413 additions and 29 deletions
+386 -6
View File
@@ -81,9 +81,9 @@ import {
classOrConstructorParameterIsDecorated,
ClassStaticBlockDeclaration,
clear,
compareComparableValues,
compareDiagnostics,
comparePaths,
compareValues,
Comparison,
CompilerOptions,
ComputedPropertyName,
@@ -417,6 +417,7 @@ import {
Identifier,
identifierToKeywordKind,
IdentifierTypePredicate,
identity,
idText,
IfStatement,
ImportAttribute,
@@ -1482,6 +1483,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
var scanner: Scanner | undefined;
var fileIndexMap = new Map(host.getSourceFiles().map((file, i) => [file, i]));
var Symbol = objectAllocator.getSymbolConstructor();
var Type = objectAllocator.getTypeConstructor();
var Signature = objectAllocator.getSignatureConstructor();
@@ -5391,7 +5394,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
function createTypeofType() {
return getUnionType(arrayFrom(typeofNEFacts.keys(), getStringLiteralType));
return getUnionType(map([...typeofNEFacts.keys()].sort(), getStringLiteralType));
}
function createTypeParameter(symbol?: Symbol) {
@@ -17649,11 +17652,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
function containsType(types: readonly Type[], type: Type): boolean {
return binarySearch(types, type, getTypeId, compareValues) >= 0;
return binarySearch(types, type, identity, compareTypes) >= 0;
}
function insertType(types: Type[], type: Type): boolean {
const index = binarySearch(types, type, getTypeId, compareValues);
const index = binarySearch(types, type, identity, compareTypes);
if (index < 0) {
types.splice(~index, 0, type);
return true;
@@ -17674,8 +17677,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (!(getObjectFlags(type) & ObjectFlags.ContainsWideningType)) includes |= TypeFlags.IncludesNonWideningType;
}
else {
const len = typeSet.length;
const index = len && type.id > typeSet[len - 1].id ? ~len : binarySearch(typeSet, type, getTypeId, compareValues);
const index = binarySearch(typeSet, type, identity, compareTypes);
if (index < 0) {
typeSet.splice(~index, 0, type);
}
@@ -52849,6 +52851,384 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
Debug.assert(specifier && nodeIsSynthesized(specifier) && specifier.text === "tslib", `Expected sourceFile.imports[0] to be the synthesized tslib import`);
return specifier;
}
function compareSymbols(s1: Symbol | undefined, s2: Symbol | undefined): number {
if (s1 === s2) return 0;
if (s1 === undefined) return 1;
if (s2 === undefined) return -1;
if (length(s1.declarations) !== 0 && length(s2.declarations) !== 0) {
const r = compareNodes(s1.declarations![0], s2.declarations![0]);
if (r !== 0) return r;
}
else if (length(s1.declarations) !== 0) {
return -1;
}
else if (length(s2.declarations) !== 0) {
return 1;
}
const r = compareComparableValues(s1.escapedName as string, s2.escapedName as string);
if (r !== 0) return r;
return getSymbolId(s1) - getSymbolId(s2);
}
function compareNodes(n1: Node | undefined, n2: Node | undefined): number {
if (n1 === n2) return 0;
if (n1 === undefined) return 1;
if (n2 === undefined) return -1;
const f1 = fileIndexMap.get(getSourceFileOfNode(n1))!;
const f2 = fileIndexMap.get(getSourceFileOfNode(n2))!;
if (f1 !== f2) {
// Order by index of file in the containing program
return f1 - f2;
}
// In the same file, order by source position
return n1.pos - n2.pos;
}
function compareTypes(t1: Type | undefined, t2: Type | undefined): number {
if (t1 === t2) return 0;
if (t1 === undefined) return 1;
if (t2 === undefined) return -1;
// First sort in order of increasing type flags values.
let c = getSortOrderFlags(t1) - getSortOrderFlags(t2);
if (c !== 0) return c;
// Order named types by name and, in the case of aliased types, by alias type arguments.
c = compareTypeNames(t1, t2);
if (c !== 0) return c;
// We have unnamed types or types with identical names. Now sort by data specific to the type.
if (t1.flags & (TypeFlags.Any | TypeFlags.Unknown | TypeFlags.String | TypeFlags.Number | TypeFlags.Boolean | TypeFlags.BigInt | TypeFlags.ESSymbol | TypeFlags.Void | TypeFlags.Undefined | TypeFlags.Null | TypeFlags.Never | TypeFlags.NonPrimitive)) {
// Only distinguished by type IDs, handled below.
}
else if (t1.flags & TypeFlags.Object) {
// Order unnamed or identically named object types by symbol.
const c = compareSymbols(t1.symbol, t2.symbol);
if (c !== 0) return c;
// When object types have the same or no symbol, order by kind. We order type references before other kinds.
if (getObjectFlags(t1) & ObjectFlags.Reference && getObjectFlags(t2) & ObjectFlags.Reference) {
const r1 = t1 as TypeReference;
const r2 = t2 as TypeReference;
if (getObjectFlags(r1.target) & ObjectFlags.Tuple && getObjectFlags(r2.target) & ObjectFlags.Tuple) {
// Tuple types have no associated symbol, instead we order by tuple element information.
const c = compareTupleTypes(r1.target as TupleType, r2.target as TupleType);
if (c !== 0) {
return c;
}
}
// Here we know we have references to instantiations of the same type because we have matching targets.
if (r1.node === undefined && r2.node === undefined) {
// Non-deferred type references with the same target are sorted by their type argument lists.
const c = compareTypeLists((t1 as TypeReference).resolvedTypeArguments, (t2 as TypeReference).resolvedTypeArguments);
if (c !== 0) {
return c;
}
}
else {
// Deferred type references with the same target are ordered by the source location of the reference.
let c = compareNodes(r1.node, r2.node);
if (c !== 0) {
return c;
}
// Instantiations of the same deferred type reference are ordered by their associated type mappers
// (which reflect the mapping of in-scope type parameters to type arguments).
c = compareTypeMappers((t1 as AnonymousType).mapper, (t2 as AnonymousType).mapper);
if (c !== 0) {
return c;
}
}
}
else if (getObjectFlags(t1) & ObjectFlags.Reference) {
return -1;
}
else if (getObjectFlags(t2) & ObjectFlags.Reference) {
return 1;
}
else {
// Order unnamed non-reference object types by kind associated type mappers. Reverse mapped types have
// neither symbols nor mappers so they're ultimately ordered by unstable type IDs, but given their rarity
// this should be fine.
let c = getObjectFlags(t1) & ObjectFlags.ObjectTypeKindMask - getObjectFlags(t2) & ObjectFlags.ObjectTypeKindMask;
if (c !== 0) {
return c;
}
c = compareTypeMappers((t1 as AnonymousType).mapper, (t2 as AnonymousType).mapper);
if (c !== 0) {
return c;
}
}
}
else if (t1.flags & TypeFlags.Union) {
// Unions are ordered by origin and then constituent type lists.
const o1 = (t1 as UnionType).origin;
const o2 = (t2 as UnionType).origin;
if (o1 === undefined && o2 === undefined) {
const c = compareTypeLists((t1 as UnionType).types, (t2 as UnionType).types);
if (c !== 0) {
return c;
}
}
else if (o1 === undefined) {
return 1;
}
else if (o2 === undefined) {
return -1;
}
else {
const c = compareTypes(o1, o2);
if (c !== 0) {
return c;
}
}
}
else if (t1.flags & TypeFlags.Intersection) {
// Intersections are ordered by their constituent type lists.
const c = compareTypeLists((t1 as IntersectionType).types, (t2 as IntersectionType).types);
if (c !== 0) {
return c;
}
}
else if (t1.flags & (TypeFlags.Enum | TypeFlags.EnumLiteral | TypeFlags.UniqueESSymbol)) {
// Enum members are ordered by their symbol (and thus their declaration order).
const c = compareSymbols(t1.symbol, t2.symbol);
if (c !== 0) {
return c;
}
}
else if (t1.flags & TypeFlags.StringLiteral) {
// String literal types are ordered by their values.
const c = compareComparableValues((t1 as LiteralType).value as string, (t2 as LiteralType).value as string);
if (c !== 0) {
return c;
}
}
else if (t1.flags & TypeFlags.NumberLiteral) {
// Numeric literal types are ordered by their values.
const c = compareComparableValues((t1 as LiteralType).value as number, (t2 as LiteralType).value as number);
if (c !== 0) {
return c;
}
}
else if (t1.flags & TypeFlags.BooleanLiteral) {
const b1 = (t1 as IntrinsicType).intrinsicName === "true";
const b2 = (t2 as IntrinsicType).intrinsicName === "true";
if (b1 !== b2) {
if (b1) {
return 1;
}
return -1;
}
}
else if (t1.flags & TypeFlags.TypeParameter) {
const c = compareSymbols(t1.symbol, t2.symbol);
if (c !== 0) {
return c;
}
}
else if (t1.flags & TypeFlags.Index) {
let c = compareTypes((t1 as IndexType).type, (t2 as IndexType).type);
if (c !== 0) {
return c;
}
c = (t1 as IndexType).flags - (t2 as IndexType).flags;
if (c !== 0) {
return c;
}
}
else if (t1.flags & TypeFlags.IndexedAccess) {
let c = compareTypes((t1 as IndexedAccessType).objectType, (t2 as IndexedAccessType).objectType);
if (c !== 0) {
return c;
}
c = compareTypes((t1 as IndexedAccessType).indexType, (t2 as IndexedAccessType).indexType);
if (c !== 0) {
return c;
}
}
else if (t1.flags & TypeFlags.Conditional) {
let c = compareNodes((t1 as ConditionalType).root.node, (t2 as ConditionalType).root.node);
if (c !== 0) {
return c;
}
c = compareTypeMappers((t1 as ConditionalType).mapper, (t2 as ConditionalType).mapper);
if (c !== 0) {
return c;
}
}
else if (t1.flags & TypeFlags.Substitution) {
let c = compareTypes((t1 as SubstitutionType).baseType, (t2 as SubstitutionType).baseType);
if (c !== 0) {
return c;
}
c = compareTypes((t1 as SubstitutionType).constraint, (t2 as SubstitutionType).constraint);
if (c !== 0) {
return c;
}
}
else if (t1.flags & TypeFlags.TemplateLiteral) {
let c = slicesCompareString((t1 as TemplateLiteralType).texts, (t2 as TemplateLiteralType).texts);
if (c !== 0) {
return c;
}
c = compareTypeLists((t1 as TemplateLiteralType).types, (t2 as TemplateLiteralType).types);
if (c !== 0) {
return c;
}
}
else if (t1.flags & TypeFlags.StringMapping) {
const c = compareTypes((t1 as StringMappingType).type, (t2 as StringMappingType).type);
if (c !== 0) {
return c;
}
}
// Fall back to type IDs. This results in type creation order for built-in types.
return t1.id - t2.id;
function slicesCompareString(s1: readonly string[], s2: readonly string[]): number {
for (let i = 0; i < s1.length; i++) {
if (i > s2.length) {
return 1;
}
const v1 = s1[i];
const v2 = s2[i];
const c = compareComparableValues(v1, v2);
if (c !== 0) return c;
}
if (s1.length < s2.length) {
return -1;
}
return 0;
}
}
function getSortOrderFlags(t: Type): number {
// Return TypeFlagsEnum for all enum-like unit types (they'll be sorted by their symbols)
if (t.flags & (TypeFlags.EnumLiteral | TypeFlags.Enum) && !(t.flags & TypeFlags.Union)) {
return TypeFlags.Enum;
}
return t.flags;
}
function compareTypeNames(t1: Type, t2: Type): number {
const s1 = getTypeNameSymbol(t1);
const s2 = getTypeNameSymbol(t2);
if (s1 === s2) {
if (t1.aliasTypeArguments !== undefined) {
return compareTypeLists(t1.aliasTypeArguments, t2.aliasTypeArguments);
}
return 0;
}
if (s1 === undefined) {
return 1;
}
if (s2 === undefined) {
return -1;
}
return compareComparableValues(s1.escapedName as string, s2.escapedName as string);
}
function getTypeNameSymbol(t: Type): Symbol | undefined {
if (t.aliasSymbol !== undefined) {
return t.aliasSymbol;
}
if (t.flags & (TypeFlags.TypeParameter | TypeFlags.StringMapping) || getObjectFlags(t) & (ObjectFlags.ClassOrInterface | ObjectFlags.Reference)) {
return t.symbol;
}
return undefined;
}
function compareTupleTypes(t1: TupleType, t2: TupleType): number {
if (t1 === t2) {
return 0;
}
if (t1.readonly === t2.readonly) {
return t1.readonly ? 1 : -1;
}
if (t1.elementFlags.length !== t2.elementFlags.length) {
return t1.elementFlags.length - t2.elementFlags.length;
}
for (let i = 0; i < t1.elementFlags.length; i++) {
const c = t1.elementFlags[i] - t2.elementFlags[i];
if (c !== 0) {
return c;
}
}
for (let i = 0; i < (t1.labeledElementDeclarations?.length ?? 0); i++) {
const c = compareElementLabels(t1.labeledElementDeclarations![i], t2.labeledElementDeclarations![i]);
if (c !== 0) {
return c;
}
}
return 0;
}
function compareElementLabels(n1: NamedTupleMember | ParameterDeclaration | undefined, n2: NamedTupleMember | ParameterDeclaration | undefined): number {
if (n1 === n2) {
return 0;
}
if (n1 === undefined) {
return -1;
}
if (n2 === undefined) {
return 1;
}
return compareComparableValues((n1.name as Identifier).escapedText as string, (n2.name as Identifier).escapedText as string);
}
function compareTypeLists(s1: readonly Type[] | undefined, s2: readonly Type[] | undefined): number {
if (length(s1) !== length(s2)) {
return length(s1) - length(s2);
}
for (let i = 0; i < length(s1); i++) {
const c = compareTypes(s1![i], s2?.[i]);
if (c !== 0) return c;
}
return 0;
}
function compareTypeMappers(m1: TypeMapper | undefined, m2: TypeMapper | undefined): number {
if (m1 === m2) {
return 0;
}
if (m1 === undefined) {
return 1;
}
if (m2 === undefined) {
return -1;
}
const kind1 = m1.kind;
const kind2 = m2.kind;
if (kind1 !== kind2) {
return kind1 - kind2;
}
switch (kind1) {
case TypeMapKind.Simple: {
const c = compareTypes(m1.source, (m2 as typeof m1).source);
if (c !== 0) {
return c;
}
return compareTypes(m1.target, (m2 as typeof m1).target);
}
case TypeMapKind.Array: {
const c = compareTypeLists(m1.sources, (m2 as typeof m1).sources);
if (c !== 0) {
return c;
}
return compareTypeLists(m1.targets, (m2 as typeof m1).targets);
}
case TypeMapKind.Merged: {
const c = compareTypeMappers(m1.mapper1, (m2 as typeof m1).mapper1);
if (c !== 0) {
return c;
}
return compareTypeMappers(m1.mapper2, (m2 as typeof m1).mapper2);
}
}
return 0;
}
}
function isNotAccessor(declaration: Declaration): boolean {
+6 -4
View File
@@ -1209,7 +1209,7 @@ export function binarySearchKey<T, U>(array: readonly T[], key: U, keySelector:
while (low <= high) {
const middle = low + ((high - low) >> 1);
const midKey = keySelector(array[middle], middle);
switch (keyComparer(midKey, key)) {
switch (Math.sign(keyComparer(midKey, key))) {
case Comparison.LessThan:
low = middle + 1;
break;
@@ -1967,9 +1967,11 @@ export function equateStringsCaseSensitive(a: string, b: string): boolean {
return equateValues(a, b);
}
function compareComparableValues(a: string | undefined, b: string | undefined): Comparison;
function compareComparableValues(a: number | undefined, b: number | undefined): Comparison;
function compareComparableValues(a: string | number | undefined, b: string | number | undefined) {
/** @internal */
export function compareComparableValues(a: string | undefined, b: string | undefined): Comparison;
/** @internal */
export function compareComparableValues(a: number | undefined, b: number | undefined): Comparison;
export function compareComparableValues(a: string | number | undefined, b: string | number | undefined) {
return a === b ? Comparison.EqualTo :
a === undefined ? Comparison.LessThan :
b === undefined ? Comparison.GreaterThan :
+21 -19
View File
@@ -6257,21 +6257,21 @@ export interface SerializedTypeEntry {
export const enum TypeFlags {
Any = 1 << 0,
Unknown = 1 << 1,
String = 1 << 2,
Number = 1 << 3,
Boolean = 1 << 4,
Enum = 1 << 5, // Numeric computed enum member value
BigInt = 1 << 6,
StringLiteral = 1 << 7,
NumberLiteral = 1 << 8,
BooleanLiteral = 1 << 9,
EnumLiteral = 1 << 10, // Always combined with StringLiteral, NumberLiteral, or Union
BigIntLiteral = 1 << 11,
ESSymbol = 1 << 12, // Type of symbol primitive introduced in ES6
UniqueESSymbol = 1 << 13, // unique symbol
Void = 1 << 14,
Undefined = 1 << 15,
Null = 1 << 16,
Undefined = 1 << 2,
Null = 1 << 3,
Void = 1 << 4,
String = 1 << 5,
Number = 1 << 6,
BigInt = 1 << 7,
Boolean = 1 << 8,
ESSymbol = 1 << 9, // Type of symbol primitive introduced in ES6
StringLiteral = 1 << 10,
NumberLiteral = 1 << 11,
BooleanLiteral = 1 << 12,
BigIntLiteral = 1 << 13,
UniqueESSymbol = 1 << 14, // unique symbol
EnumLiteral = 1 << 15, // Always combined with StringLiteral, NumberLiteral, or Union
Enum = 1 << 16, // Numeric computed enum member value
Never = 1 << 17, // Never type
TypeParameter = 1 << 18, // Type parameter
Object = 1 << 19, // Object type
@@ -6472,15 +6472,17 @@ export const enum ObjectFlags {
PropagatingFlags = ContainsWideningType | ContainsObjectOrArrayLiteral | NonInferrableType,
/** @internal */
InstantiatedMapped = Mapped | Instantiated,
// Object flags that uniquely identify the kind of ObjectType
/** @internal */
ObjectTypeKindMask = ClassOrInterface | Reference | Tuple | Anonymous | Mapped | ReverseMapped | EvolvingArray,
// Flags that require TypeFlags.Object
ContainsSpread = 1 << 21, // Object literal contains spread operation
ObjectRestType = 1 << 22, // Originates in object rest declaration
InstantiationExpressionType = 1 << 23, // Originates in instantiation expression
SingleSignatureType = 1 << 27, // A single signature type extracted from a potentially broader type
// Object flags that uniquely identify the kind of ObjectType
/** @internal */
ObjectTypeKindMask = ClassOrInterface | Reference | Tuple | Anonymous | Mapped | ReverseMapped | EvolvingArray | InstantiationExpressionType | SingleSignatureType,
/** @internal */
IsClassInstanceClone = 1 << 24, // Type is a clone of a class instance type
// Flags that require TypeFlags.Object and ObjectFlags.Reference