diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index dbe53a978c6..ff4aafaf8e7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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 { diff --git a/src/compiler/core.ts b/src/compiler/core.ts index ef83fefd533..89c468344fc 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1209,7 +1209,7 @@ export function binarySearchKey(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 : diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 975d488c589..167b7588a0b 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -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