diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 348d6051831a2..02b295a984d34 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -82,6 +82,7 @@ import { classOrConstructorParameterIsDecorated, ClassStaticBlockDeclaration, clear, + compareComparableValues, compareDiagnostics, comparePaths, compareValues, @@ -428,6 +429,7 @@ import { Identifier, identifierToKeywordKind, IdentifierTypePredicate, + identity, idText, IfStatement, ImportAttribute, @@ -1538,6 +1540,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { var useUnknownInCatchVariables = getStrictOptionValue(compilerOptions, "useUnknownInCatchVariables"); var exactOptionalPropertyTypes = compilerOptions.exactOptionalPropertyTypes; var noUncheckedSideEffectImports = compilerOptions.noUncheckedSideEffectImports !== false; + var stableTypeOrdering = !!compilerOptions.stableTypeOrdering; + + var fileIndexMap = stableTypeOrdering ? new Map(host.getSourceFiles().map((file, i) => [file, i])) : undefined; var checkBinaryExpression = createCheckBinaryExpression(); var emitResolver = createResolver(); @@ -5554,7 +5559,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function createTypeofType() { - return getUnionType(arrayFrom(typeofNEFacts.keys(), getStringLiteralType)); + return getUnionType(map(stableTypeOrdering ? [...typeofNEFacts.keys()].sort() : arrayFrom(typeofNEFacts.keys()), getStringLiteralType)); } function createTypeParameter(symbol?: Symbol) { @@ -5573,22 +5578,67 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { (name as string).charCodeAt(2) !== CharacterCodes.hash; } - function getNamedMembers(members: SymbolTable): Symbol[] { - let result: Symbol[] | undefined; + function getNamedMembers(members: SymbolTable, container: Symbol | undefined): Symbol[] { + if (!stableTypeOrdering) { + let result: Symbol[] | undefined; + members.forEach((symbol, id) => { + if (isNamedMember(symbol, id)) { + (result ??= []).push(symbol); + } + }); + return result ?? emptyArray; + } + + if (members.size === 0) { + return emptyArray; + } + + // For classes and interfaces, we store explicitly declared members ahead of inherited members. This ensures we process + // explicitly declared members first in type relations, which is beneficial because explicitly declared members are more + // likely to contain discriminating differences. See for example https://github.com/microsoft/typescript-go/issues/1968. + let contained: Symbol[] | undefined; + if (container && container.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { + members.forEach((symbol, id) => { + if (isNamedMember(symbol, id) && isDeclarationContainedBy(symbol, container)) { + contained = append(contained, symbol); + } + }); + } + + let nonContained: Symbol[] | undefined; members.forEach((symbol, id) => { - if (isNamedMember(symbol, id)) { - (result || (result = [])).push(symbol); + if (isNamedMember(symbol, id) && (!container || !(container.flags & (SymbolFlags.Class | SymbolFlags.Interface)) || !isDeclarationContainedBy(symbol, container))) { + nonContained = append(nonContained, symbol); } }); - return result || emptyArray; + + contained?.sort(compareSymbols); + nonContained?.sort(compareSymbols); + return concatenate(contained, nonContained) ?? emptyArray; + + function isDeclarationContainedBy(symbol: Symbol, container: Symbol): boolean { + const declaration = symbol.valueDeclaration; + if (declaration && container.declarations) { + for (const d of container.declarations) { + if (containedBy(declaration, d)) { + return true; + } + } + } + return false; + + function containedBy(a: Node, b: Node): boolean { + return b.pos <= a.pos && b.end >= a.end; + } + } } function isNamedMember(member: Symbol, escapedName: __String) { return !isReservedMemberName(escapedName) && symbolIsValue(member); } - function getNamedOrIndexSignatureMembers(members: SymbolTable): Symbol[] { - const result = getNamedMembers(members); + function getNamedOrIndexSignatureMembers(members: SymbolTable, symbol: Symbol): Symbol[] { + const result = getNamedMembers(members, symbol); const index = getIndexSymbolFromSymbolTable(members); return index ? concatenate(result, [index]) : result; } @@ -5601,7 +5651,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { resolved.constructSignatures = constructSignatures; resolved.indexInfos = indexInfos; // This can loop back to getPropertyOfType() which would crash if `callSignatures` & `constructSignatures` are not initialized. - if (members !== emptySymbols) resolved.properties = getNamedMembers(members); + if (members !== emptySymbols) resolved.properties = getNamedMembers(members, type.symbol); return resolved; } @@ -8935,7 +8985,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function getPropertyNameNodeForSymbol(symbol: Symbol, context: NodeBuilderContext) { const hashPrivateName = getClonedHashPrivateName(symbol); if (hashPrivateName) { - return hashPrivateName; + const shouldEmitErroneousFieldName = !!context.tracker.reportPrivateInBaseOfClassExpression && + context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral; + if (!shouldEmitErroneousFieldName) { + return hashPrivateName; + } + else { + let rawName = unescapeLeadingUnderscores(symbol.escapedName); + // symbol IDs are unstable - replace #nnn# with #private# + rawName = rawName.replace(/__#\d+@#/g, "__#private@#"); + return createPropertyNameNodeForIdentifierOrLiteral(rawName, getEmitScriptTarget(compilerOptions), /*singleQuote*/ false, /*stringNamed*/ true, !!(symbol.flags & SymbolFlags.Method)); + } } const stringNamed = !!length(symbol.declarations) && every(symbol.declarations, isStringNamed); const singleQuote = !!length(symbol.declarations) && every(symbol.declarations, isSingleQuotedStringNamed); @@ -13704,7 +13764,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (!(type as InterfaceTypeWithDeclaredMembers).declaredProperties) { const symbol = type.symbol; const members = getMembersOfSymbol(symbol); - (type as InterfaceTypeWithDeclaredMembers).declaredProperties = getNamedMembers(members); + (type as InterfaceTypeWithDeclaredMembers).declaredProperties = getNamedMembers(members, symbol); // Start with signatures at empty array in case of recursive types (type as InterfaceTypeWithDeclaredMembers).declaredCallSignatures = emptyArray; (type as InterfaceTypeWithDeclaredMembers).declaredConstructSignatures = emptyArray; @@ -14580,7 +14640,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const classType = getDeclaredTypeOfClassOrInterface(symbol); const baseConstructorType = getBaseConstructorTypeOfClass(classType); if (baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.TypeVariable)) { - members = createSymbolTable(getNamedOrIndexSignatureMembers(members)); + members = createSymbolTable(getNamedOrIndexSignatureMembers(members, symbol)); addInheritedMembers(members, getPropertiesOfType(baseConstructorType)); } else if (baseConstructorType === anyType) { @@ -15043,7 +15103,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { break; } } - type.resolvedProperties = getNamedMembers(members); + type.resolvedProperties = getNamedMembers(members, type.symbol); } return type.resolvedProperties; } @@ -18016,11 +18076,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function containsType(types: readonly Type[], type: Type): boolean { - return binarySearch(types, type, getTypeId, compareValues) >= 0; + return stableTypeOrdering ? binarySearch(types, type, identity, compareTypes) >= 0 : binarySearch(types, type, getTypeId, compareValues) >= 0; } function insertType(types: Type[], type: Type): boolean { - const index = binarySearch(types, type, getTypeId, compareValues); + const index = stableTypeOrdering ? binarySearch(types, type, identity, compareTypes) : binarySearch(types, type, getTypeId, compareValues); if (index < 0) { types.splice(~index, 0, type); return true; @@ -18042,7 +18102,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } else { const len = typeSet.length; - const index = len && type.id > typeSet[len - 1].id ? ~len : binarySearch(typeSet, type, getTypeId, compareValues); + const index = stableTypeOrdering ? binarySearch(typeSet, type, identity, compareTypes) : (len && type.id > typeSet[len - 1].id ? ~len : binarySearch(typeSet, type, getTypeId, compareValues)); if (index < 0) { typeSet.splice(~index, 0, type); } @@ -35207,7 +35267,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // So the table *contains* `x` but `x` isn't actually in scope. // However, resolveNameHelper will continue and call this callback again, so we'll eventually get a correct suggestion. if (symbol) return symbol; - let candidates: Symbol[]; + let candidates = arrayFrom(symbols.values()); if (symbols === globals) { const primitives = mapDefined( ["string", "number", "boolean", "object", "bigint", "symbol"], @@ -35215,11 +35275,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { ? createSymbol(SymbolFlags.TypeAlias, s as __String) as Symbol : undefined, ); - candidates = primitives.concat(arrayFrom(symbols.values())); - } - else { - candidates = arrayFrom(symbols.values()); + candidates = concatenate(primitives, candidates); } + sortSymbolsIfTSGoCompat(candidates); return getSpellingSuggestionForName(unescapeLeadingUnderscores(name), candidates, meaning); } function getSuggestedSymbolForNonexistentSymbol(location: Node | undefined, outerName: __String, meaning: SymbolFlags): Symbol | undefined { @@ -35229,7 +35287,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function getSuggestedSymbolForNonexistentModule(name: Identifier, targetModule: Symbol): Symbol | undefined { - return targetModule.exports && getSpellingSuggestionForName(idText(name), getExportsOfModuleAsArray(targetModule), SymbolFlags.ModuleMember); + return targetModule.exports && getSpellingSuggestionForName(idText(name), sortSymbolsIfTSGoCompat(getExportsOfModuleAsArray(targetModule)), SymbolFlags.ModuleMember); } function getSuggestionForNonexistentIndexSignature(objectType: Type, expr: ElementAccessExpression, keyedType: Type): string | undefined { @@ -50478,7 +50536,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } }); } - return getNamedMembers(propsByName); + return getNamedMembers(propsByName, /*container*/ undefined); } function typeHasCallOrConstructSignatures(type: Type): boolean { @@ -53729,6 +53787,395 @@ 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 sortSymbolsIfTSGoCompat(array: Symbol[]): Symbol[]; + function sortSymbolsIfTSGoCompat(array: Symbol[] | undefined): Symbol[] | undefined; + function sortSymbolsIfTSGoCompat(array: Symbol[] | undefined): Symbol[] | undefined { + if (stableTypeOrdering && array) { + return array.sort(compareSymbols); // eslint-disable-line local/no-array-mutating-method-expressions + } + return array; + } + + 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 s1 = getSourceFileOfNode(n1); + const s2 = getSourceFileOfNode(n2); + if (s1 !== s2) { + const f1 = fileIndexMap!.get(s1)!; + const f2 = fileIndexMap!.get(s2)!; + // 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).indexFlags - (t2 as IndexType).indexFlags; + 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/commandLineParser.ts b/src/compiler/commandLineParser.ts index 8a62a51e6f75f..bc8e7c01ceff3 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -961,6 +961,15 @@ const commandOptionsWithoutBuild: CommandLineOption[] = [ description: Diagnostics.Built_in_iterators_are_instantiated_with_a_TReturn_type_of_undefined_instead_of_any, defaultValueDescription: Diagnostics.false_unless_strict_is_set, }, + { + name: "stableTypeOrdering", + type: "boolean", + affectsSemanticDiagnostics: true, + affectsBuildInfo: true, + category: Diagnostics.Type_Checking, + description: Diagnostics.Ensure_types_are_ordered_stably_and_deterministically_across_compilations, + defaultValueDescription: false, + }, { name: "noImplicitThis", type: "boolean", diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 47f6ce95df557..2085548836e8d 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/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 1a69dba02a6c1..3aa4556459f13 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -6589,6 +6589,10 @@ "category": "Message", "code": 6808 }, + "Ensure types are ordered stably and deterministically across compilations.": { + "category": "Message", + "code": 6809 + }, "one of:": { "category": "Message", diff --git a/src/compiler/types.ts b/src/compiler/types.ts index df5d03e7ef964..9523952e63941 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -6320,42 +6320,49 @@ export interface SerializedTypeEntry { trackedSymbols: readonly TrackedSymbol[] | undefined; } +// Note that for types of different kinds, the numeric values of TypeFlags determine the order +// computed by the CompareTypes function and therefore the order of constituent types in union types. +// Since union type processing often bails out early when a result is known, it is important to order +// TypeFlags in increasing order of potential type complexity. In particular, indexed access and +// conditional types should sort last as those types are potentially recursive and possibly infinite. + // dprint-ignore 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, - Never = 1 << 17, // Never type - TypeParameter = 1 << 18, // Type parameter - Object = 1 << 19, // Object type - Union = 1 << 20, // Union (T | U) - Intersection = 1 << 21, // Intersection (T & U) - Index = 1 << 22, // keyof T - IndexedAccess = 1 << 23, // T[K] - Conditional = 1 << 24, // T extends U ? X : Y - Substitution = 1 << 25, // Type parameter substitution - NonPrimitive = 1 << 26, // intrinsic object type - TemplateLiteral = 1 << 27, // Template literal type - StringMapping = 1 << 28, // Uppercase/Lowercase type + 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, + BigIntLiteral = 1 << 12, + BooleanLiteral = 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 (must be right after EnumLiteral, see getSortOrderFlags) + NonPrimitive = 1 << 17, // intrinsic object type + Never = 1 << 18, // Never type + TypeParameter = 1 << 19, // Type parameter + Object = 1 << 20, // Object type + Index = 1 << 21, // keyof T + TemplateLiteral = 1 << 22, // Template literal type + StringMapping = 1 << 23, // Uppercase/Lowercase type + Substitution = 1 << 24, // Type parameter substitution + IndexedAccess = 1 << 25, // T[K] + Conditional = 1 << 26, // T extends U ? X : Y + Union = 1 << 27, // Union (T | U) + Intersection = 1 << 28, // Intersection (T & U) /** @internal */ Reserved1 = 1 << 29, // Used by union/intersection type construction /** @internal */ Reserved2 = 1 << 30, // Used by union/intersection type construction - + /** @internal */ + Reserved3 = 1 << 31, /** @internal */ AnyOrUnknown = Any | Unknown, /** @internal */ @@ -6539,14 +6546,16 @@ 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 + + // 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 @@ -7532,6 +7541,7 @@ export interface CompilerOptions { strictNullChecks?: boolean; // Always combine with strict property strictPropertyInitialization?: boolean; // Always combine with strict property strictBuiltinIteratorReturn?: boolean; // Always combine with strict property + stableTypeOrdering?: boolean; stripInternal?: boolean; /** @deprecated */ suppressExcessPropertyErrors?: boolean; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 96243aa142da3..6cff8766fe2eb 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -6593,53 +6593,53 @@ declare namespace ts { enum TypeFlags { Any = 1, Unknown = 2, - String = 4, - Number = 8, - Boolean = 16, - Enum = 32, - BigInt = 64, - StringLiteral = 128, - NumberLiteral = 256, - BooleanLiteral = 512, - EnumLiteral = 1024, - BigIntLiteral = 2048, - ESSymbol = 4096, - UniqueESSymbol = 8192, - Void = 16384, - Undefined = 32768, - Null = 65536, - Never = 131072, - TypeParameter = 262144, - Object = 524288, - Union = 1048576, - Intersection = 2097152, - Index = 4194304, - IndexedAccess = 8388608, - Conditional = 16777216, - Substitution = 33554432, - NonPrimitive = 67108864, - TemplateLiteral = 134217728, - StringMapping = 268435456, - Literal = 2944, - Unit = 109472, - Freshable = 2976, - StringOrNumberLiteral = 384, - PossiblyFalsy = 117724, - StringLike = 402653316, - NumberLike = 296, - BigIntLike = 2112, - BooleanLike = 528, - EnumLike = 1056, - ESSymbolLike = 12288, - VoidLike = 49152, - UnionOrIntersection = 3145728, - StructuredType = 3670016, - TypeVariable = 8650752, - InstantiableNonPrimitive = 58982400, - InstantiablePrimitive = 406847488, - Instantiable = 465829888, - StructuredOrInstantiable = 469499904, - Narrowable = 536624127, + Undefined = 4, + Null = 8, + Void = 16, + String = 32, + Number = 64, + BigInt = 128, + Boolean = 256, + ESSymbol = 512, + StringLiteral = 1024, + NumberLiteral = 2048, + BigIntLiteral = 4096, + BooleanLiteral = 8192, + UniqueESSymbol = 16384, + EnumLiteral = 32768, + Enum = 65536, + NonPrimitive = 131072, + Never = 262144, + TypeParameter = 524288, + Object = 1048576, + Index = 2097152, + TemplateLiteral = 4194304, + StringMapping = 8388608, + Substitution = 16777216, + IndexedAccess = 33554432, + Conditional = 67108864, + Union = 134217728, + Intersection = 268435456, + Literal = 15360, + Unit = 97292, + Freshable = 80896, + StringOrNumberLiteral = 3072, + PossiblyFalsy = 15868, + StringLike = 12583968, + NumberLike = 67648, + BigIntLike = 4224, + BooleanLike = 8448, + EnumLike = 98304, + ESSymbolLike = 16896, + VoidLike = 20, + UnionOrIntersection = 402653184, + StructuredType = 403701760, + TypeVariable = 34078720, + InstantiableNonPrimitive = 117964800, + InstantiablePrimitive = 14680064, + Instantiable = 132644864, + StructuredOrInstantiable = 536346624, + Narrowable = 536575971, } type DestructuringPattern = BindingPattern | ObjectLiteralExpression | ArrayLiteralExpression; interface Type { @@ -7114,6 +7114,7 @@ declare namespace ts { strictNullChecks?: boolean; strictPropertyInitialization?: boolean; strictBuiltinIteratorReturn?: boolean; + stableTypeOrdering?: boolean; stripInternal?: boolean; /** @deprecated */ suppressExcessPropertyErrors?: boolean; diff --git a/tests/baselines/reference/config/showConfig/Shows tsconfig for single option/stableTypeOrdering/tsconfig.json b/tests/baselines/reference/config/showConfig/Shows tsconfig for single option/stableTypeOrdering/tsconfig.json new file mode 100644 index 0000000000000..f5d608c9c3da0 --- /dev/null +++ b/tests/baselines/reference/config/showConfig/Shows tsconfig for single option/stableTypeOrdering/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "stableTypeOrdering": true + } +} diff --git a/tests/baselines/reference/stableTypeOrdering(stabletypeordering=false).symbols b/tests/baselines/reference/stableTypeOrdering(stabletypeordering=false).symbols new file mode 100644 index 0000000000000..e2d17d9dc5918 --- /dev/null +++ b/tests/baselines/reference/stableTypeOrdering(stabletypeordering=false).symbols @@ -0,0 +1,75 @@ +//// [tests/cases/compiler/stableTypeOrdering.ts] //// + +=== stableTypeOrdering.ts === +class Message { value: string = ""; } +>Message : Symbol(Message, Decl(stableTypeOrdering.ts, 0, 0)) +>value : Symbol(Message.value, Decl(stableTypeOrdering.ts, 0, 15)) + +function takeMessageOrArray(message: Message | Message[]) { return message; } +>takeMessageOrArray : Symbol(takeMessageOrArray, Decl(stableTypeOrdering.ts, 0, 37)) +>message : Symbol(message, Decl(stableTypeOrdering.ts, 2, 28)) +>Message : Symbol(Message, Decl(stableTypeOrdering.ts, 0, 0)) +>Message : Symbol(Message, Decl(stableTypeOrdering.ts, 0, 0)) +>message : Symbol(message, Decl(stableTypeOrdering.ts, 2, 28)) + +const result1 = takeMessageOrArray(null!); +>result1 : Symbol(result1, Decl(stableTypeOrdering.ts, 3, 5)) +>takeMessageOrArray : Symbol(takeMessageOrArray, Decl(stableTypeOrdering.ts, 0, 37)) + +function checkType(x: string | number | boolean) { +>checkType : Symbol(checkType, Decl(stableTypeOrdering.ts, 3, 42)) +>x : Symbol(x, Decl(stableTypeOrdering.ts, 5, 19)) + + const t = typeof x; +>t : Symbol(t, Decl(stableTypeOrdering.ts, 6, 9)) +>x : Symbol(x, Decl(stableTypeOrdering.ts, 5, 19)) + + return t; +>t : Symbol(t, Decl(stableTypeOrdering.ts, 6, 9)) +} + +function mixedUnion(x: string | number[] | { a: number }) { +>mixedUnion : Symbol(mixedUnion, Decl(stableTypeOrdering.ts, 8, 1)) +>x : Symbol(x, Decl(stableTypeOrdering.ts, 10, 20)) +>a : Symbol(a, Decl(stableTypeOrdering.ts, 10, 44)) + + return x; +>x : Symbol(x, Decl(stableTypeOrdering.ts, 10, 20)) +} + +enum Color { Red, Green, Blue } +>Color : Symbol(Color, Decl(stableTypeOrdering.ts, 12, 1)) +>Red : Symbol(Color.Red, Decl(stableTypeOrdering.ts, 14, 12)) +>Green : Symbol(Color.Green, Decl(stableTypeOrdering.ts, 14, 17)) +>Blue : Symbol(Color.Blue, Decl(stableTypeOrdering.ts, 14, 24)) + +function enumUnion(x: Color | string | string[]) { +>enumUnion : Symbol(enumUnion, Decl(stableTypeOrdering.ts, 14, 31)) +>x : Symbol(x, Decl(stableTypeOrdering.ts, 15, 19)) +>Color : Symbol(Color, Decl(stableTypeOrdering.ts, 12, 1)) + + return x; +>x : Symbol(x, Decl(stableTypeOrdering.ts, 15, 19)) +} + +type Named = { name: string }; +>Named : Symbol(Named, Decl(stableTypeOrdering.ts, 17, 1)) +>name : Symbol(name, Decl(stableTypeOrdering.ts, 19, 14)) + +type Aged = { age: number }; +>Aged : Symbol(Aged, Decl(stableTypeOrdering.ts, 19, 30)) +>age : Symbol(age, Decl(stableTypeOrdering.ts, 20, 13)) + +type Person = Named & Aged; +>Person : Symbol(Person, Decl(stableTypeOrdering.ts, 20, 28)) +>Named : Symbol(Named, Decl(stableTypeOrdering.ts, 17, 1)) +>Aged : Symbol(Aged, Decl(stableTypeOrdering.ts, 19, 30)) + +declare const person: Person | null; +>person : Symbol(person, Decl(stableTypeOrdering.ts, 22, 13)) +>Person : Symbol(Person, Decl(stableTypeOrdering.ts, 20, 28)) + +const p = person; +>p : Symbol(p, Decl(stableTypeOrdering.ts, 23, 5)) +>person : Symbol(person, Decl(stableTypeOrdering.ts, 22, 13)) + diff --git a/tests/baselines/reference/stableTypeOrdering(stabletypeordering=false).types b/tests/baselines/reference/stableTypeOrdering(stabletypeordering=false).types new file mode 100644 index 0000000000000..74a991cf3982b --- /dev/null +++ b/tests/baselines/reference/stableTypeOrdering(stabletypeordering=false).types @@ -0,0 +1,108 @@ +//// [tests/cases/compiler/stableTypeOrdering.ts] //// + +=== stableTypeOrdering.ts === +class Message { value: string = ""; } +>Message : Message +> : ^^^^^^^ +>value : string +> : ^^^^^^ +>"" : "" +> : ^^ + +function takeMessageOrArray(message: Message | Message[]) { return message; } +>takeMessageOrArray : (message: Message | Message[]) => Message | Message[] +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ +>message : Message | Message[] +> : ^^^^^^^^^^^^^^^^^^^ +>message : Message | Message[] +> : ^^^^^^^^^^^^^^^^^^^ + +const result1 = takeMessageOrArray(null!); +>result1 : Message | Message[] +> : ^^^^^^^^^^^^^^^^^^^ +>takeMessageOrArray(null!) : Message | Message[] +> : ^^^^^^^^^^^^^^^^^^^ +>takeMessageOrArray : (message: Message | Message[]) => Message | Message[] +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ +>null! : never +> : ^^^^^ + +function checkType(x: string | number | boolean) { +>checkType : (x: string | number | boolean) => "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>x : string | number | boolean +> : ^^^^^^^^^^^^^^^^^^^^^^^^^ + + const t = typeof x; +>t : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>x : string | number | boolean +> : ^^^^^^^^^^^^^^^^^^^^^^^^^ + + return t; +>t : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +} + +function mixedUnion(x: string | number[] | { a: number }) { +>mixedUnion : (x: string | number[] | { a: number; }) => string | { a: number; } | number[] +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^ +>x : string | { a: number; } | number[] +> : ^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^ +>a : number +> : ^^^^^^ + + return x; +>x : string | { a: number; } | number[] +> : ^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^ +} + +enum Color { Red, Green, Blue } +>Color : Color +> : ^^^^^ +>Red : Color.Red +> : ^^^^^^^^^ +>Green : Color.Green +> : ^^^^^^^^^^^ +>Blue : Color.Blue +> : ^^^^^^^^^^ + +function enumUnion(x: Color | string | string[]) { +>enumUnion : (x: Color | string | string[]) => string | Color | string[] +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>x : string | Color | string[] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^ + + return x; +>x : string | Color | string[] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^ +} + +type Named = { name: string }; +>Named : Named +> : ^^^^^ +>name : string +> : ^^^^^^ + +type Aged = { age: number }; +>Aged : Aged +> : ^^^^ +>age : number +> : ^^^^^^ + +type Person = Named & Aged; +>Person : Person +> : ^^^^^^ + +declare const person: Person | null; +>person : Person | null +> : ^^^^^^^^^^^^^ + +const p = person; +>p : Person | null +> : ^^^^^^^^^^^^^ +>person : Person | null +> : ^^^^^^^^^^^^^ + diff --git a/tests/baselines/reference/stableTypeOrdering(stabletypeordering=true).symbols b/tests/baselines/reference/stableTypeOrdering(stabletypeordering=true).symbols new file mode 100644 index 0000000000000..e2d17d9dc5918 --- /dev/null +++ b/tests/baselines/reference/stableTypeOrdering(stabletypeordering=true).symbols @@ -0,0 +1,75 @@ +//// [tests/cases/compiler/stableTypeOrdering.ts] //// + +=== stableTypeOrdering.ts === +class Message { value: string = ""; } +>Message : Symbol(Message, Decl(stableTypeOrdering.ts, 0, 0)) +>value : Symbol(Message.value, Decl(stableTypeOrdering.ts, 0, 15)) + +function takeMessageOrArray(message: Message | Message[]) { return message; } +>takeMessageOrArray : Symbol(takeMessageOrArray, Decl(stableTypeOrdering.ts, 0, 37)) +>message : Symbol(message, Decl(stableTypeOrdering.ts, 2, 28)) +>Message : Symbol(Message, Decl(stableTypeOrdering.ts, 0, 0)) +>Message : Symbol(Message, Decl(stableTypeOrdering.ts, 0, 0)) +>message : Symbol(message, Decl(stableTypeOrdering.ts, 2, 28)) + +const result1 = takeMessageOrArray(null!); +>result1 : Symbol(result1, Decl(stableTypeOrdering.ts, 3, 5)) +>takeMessageOrArray : Symbol(takeMessageOrArray, Decl(stableTypeOrdering.ts, 0, 37)) + +function checkType(x: string | number | boolean) { +>checkType : Symbol(checkType, Decl(stableTypeOrdering.ts, 3, 42)) +>x : Symbol(x, Decl(stableTypeOrdering.ts, 5, 19)) + + const t = typeof x; +>t : Symbol(t, Decl(stableTypeOrdering.ts, 6, 9)) +>x : Symbol(x, Decl(stableTypeOrdering.ts, 5, 19)) + + return t; +>t : Symbol(t, Decl(stableTypeOrdering.ts, 6, 9)) +} + +function mixedUnion(x: string | number[] | { a: number }) { +>mixedUnion : Symbol(mixedUnion, Decl(stableTypeOrdering.ts, 8, 1)) +>x : Symbol(x, Decl(stableTypeOrdering.ts, 10, 20)) +>a : Symbol(a, Decl(stableTypeOrdering.ts, 10, 44)) + + return x; +>x : Symbol(x, Decl(stableTypeOrdering.ts, 10, 20)) +} + +enum Color { Red, Green, Blue } +>Color : Symbol(Color, Decl(stableTypeOrdering.ts, 12, 1)) +>Red : Symbol(Color.Red, Decl(stableTypeOrdering.ts, 14, 12)) +>Green : Symbol(Color.Green, Decl(stableTypeOrdering.ts, 14, 17)) +>Blue : Symbol(Color.Blue, Decl(stableTypeOrdering.ts, 14, 24)) + +function enumUnion(x: Color | string | string[]) { +>enumUnion : Symbol(enumUnion, Decl(stableTypeOrdering.ts, 14, 31)) +>x : Symbol(x, Decl(stableTypeOrdering.ts, 15, 19)) +>Color : Symbol(Color, Decl(stableTypeOrdering.ts, 12, 1)) + + return x; +>x : Symbol(x, Decl(stableTypeOrdering.ts, 15, 19)) +} + +type Named = { name: string }; +>Named : Symbol(Named, Decl(stableTypeOrdering.ts, 17, 1)) +>name : Symbol(name, Decl(stableTypeOrdering.ts, 19, 14)) + +type Aged = { age: number }; +>Aged : Symbol(Aged, Decl(stableTypeOrdering.ts, 19, 30)) +>age : Symbol(age, Decl(stableTypeOrdering.ts, 20, 13)) + +type Person = Named & Aged; +>Person : Symbol(Person, Decl(stableTypeOrdering.ts, 20, 28)) +>Named : Symbol(Named, Decl(stableTypeOrdering.ts, 17, 1)) +>Aged : Symbol(Aged, Decl(stableTypeOrdering.ts, 19, 30)) + +declare const person: Person | null; +>person : Symbol(person, Decl(stableTypeOrdering.ts, 22, 13)) +>Person : Symbol(Person, Decl(stableTypeOrdering.ts, 20, 28)) + +const p = person; +>p : Symbol(p, Decl(stableTypeOrdering.ts, 23, 5)) +>person : Symbol(person, Decl(stableTypeOrdering.ts, 22, 13)) + diff --git a/tests/baselines/reference/stableTypeOrdering(stabletypeordering=true).types b/tests/baselines/reference/stableTypeOrdering(stabletypeordering=true).types new file mode 100644 index 0000000000000..99c6a4a95b10a --- /dev/null +++ b/tests/baselines/reference/stableTypeOrdering(stabletypeordering=true).types @@ -0,0 +1,108 @@ +//// [tests/cases/compiler/stableTypeOrdering.ts] //// + +=== stableTypeOrdering.ts === +class Message { value: string = ""; } +>Message : Message +> : ^^^^^^^ +>value : string +> : ^^^^^^ +>"" : "" +> : ^^ + +function takeMessageOrArray(message: Message | Message[]) { return message; } +>takeMessageOrArray : (message: Message | Message[]) => Message[] | Message +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ +>message : Message[] | Message +> : ^^^^^^^^^^^^^^^^^^^ +>message : Message[] | Message +> : ^^^^^^^^^^^^^^^^^^^ + +const result1 = takeMessageOrArray(null!); +>result1 : Message[] | Message +> : ^^^^^^^^^^^^^^^^^^^ +>takeMessageOrArray(null!) : Message[] | Message +> : ^^^^^^^^^^^^^^^^^^^ +>takeMessageOrArray : (message: Message | Message[]) => Message[] | Message +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^ +>null! : never +> : ^^^^^ + +function checkType(x: string | number | boolean) { +>checkType : (x: string | number | boolean) => "bigint" | "boolean" | "function" | "number" | "object" | "string" | "symbol" | "undefined" +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>x : string | number | boolean +> : ^^^^^^^^^^^^^^^^^^^^^^^^^ + + const t = typeof x; +>t : "bigint" | "boolean" | "function" | "number" | "object" | "string" | "symbol" | "undefined" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>typeof x : "bigint" | "boolean" | "function" | "number" | "object" | "string" | "symbol" | "undefined" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>x : string | number | boolean +> : ^^^^^^^^^^^^^^^^^^^^^^^^^ + + return t; +>t : "bigint" | "boolean" | "function" | "number" | "object" | "string" | "symbol" | "undefined" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +} + +function mixedUnion(x: string | number[] | { a: number }) { +>mixedUnion : (x: string | number[] | { a: number; }) => string | number[] | { a: number; } +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ +>x : string | number[] | { a: number; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ +>a : number +> : ^^^^^^ + + return x; +>x : string | number[] | { a: number; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ +} + +enum Color { Red, Green, Blue } +>Color : Color +> : ^^^^^ +>Red : Color.Red +> : ^^^^^^^^^ +>Green : Color.Green +> : ^^^^^^^^^^^ +>Blue : Color.Blue +> : ^^^^^^^^^^ + +function enumUnion(x: Color | string | string[]) { +>enumUnion : (x: Color | string | string[]) => string | string[] | Color +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>x : string | string[] | Color +> : ^^^^^^^^^^^^^^^^^^^^^^^^^ + + return x; +>x : string | string[] | Color +> : ^^^^^^^^^^^^^^^^^^^^^^^^^ +} + +type Named = { name: string }; +>Named : Named +> : ^^^^^ +>name : string +> : ^^^^^^ + +type Aged = { age: number }; +>Aged : Aged +> : ^^^^ +>age : number +> : ^^^^^^ + +type Person = Named & Aged; +>Person : Person +> : ^^^^^^ + +declare const person: Person | null; +>person : Person | null +> : ^^^^^^^^^^^^^ + +const p = person; +>p : Person | null +> : ^^^^^^^^^^^^^ +>person : Person | null +> : ^^^^^^^^^^^^^ + diff --git a/tests/baselines/reference/tsc/commandLine/help-all.js b/tests/baselines/reference/tsc/commandLine/help-all.js index a88ff54eef08e..83a39463f7643 100644 --- a/tests/baselines/reference/tsc/commandLine/help-all.js +++ b/tests/baselines/reference/tsc/commandLine/help-all.js @@ -271,6 +271,11 @@ Raise an error when a function parameter isn't read. type: boolean default: false +--stableTypeOrdering +Ensure types are ordered stably and deterministically across compilations. +type: boolean +default: false + --strict Enable all strict type-checking options. type: boolean diff --git a/tests/cases/compiler/stableTypeOrdering.ts b/tests/cases/compiler/stableTypeOrdering.ts new file mode 100644 index 0000000000000..c9ef90e2c12ef --- /dev/null +++ b/tests/cases/compiler/stableTypeOrdering.ts @@ -0,0 +1,28 @@ +// @stableTypeOrdering: true,false +// @noEmit: true +// @strict: true + +class Message { value: string = ""; } + +function takeMessageOrArray(message: Message | Message[]) { return message; } +const result1 = takeMessageOrArray(null!); + +function checkType(x: string | number | boolean) { + const t = typeof x; + return t; +} + +function mixedUnion(x: string | number[] | { a: number }) { + return x; +} + +enum Color { Red, Green, Blue } +function enumUnion(x: Color | string | string[]) { + return x; +} + +type Named = { name: string }; +type Aged = { age: number }; +type Person = Named & Aged; +declare const person: Person | null; +const p = person;