Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
493 changes: 470 additions & 23 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
10 changes: 6 additions & 4 deletions src/compiler/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 :
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
74 changes: 42 additions & 32 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down
95 changes: 48 additions & 47 deletions tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -7114,6 +7114,7 @@ declare namespace ts {
strictNullChecks?: boolean;
strictPropertyInitialization?: boolean;
strictBuiltinIteratorReturn?: boolean;
stableTypeOrdering?: boolean;
stripInternal?: boolean;
/** @deprecated */
suppressExcessPropertyErrors?: boolean;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"compilerOptions": {
"stableTypeOrdering": true
}
}
Original file line number Diff line number Diff line change
@@ -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))

Loading