From 897de058b6b16d7372ea76c59648c8202489407c Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 25 Dec 2024 10:14:02 -1000 Subject: [PATCH 1/9] Deterministic ordering of types --- internal/ast/ast.go | 2 +- internal/ast/utilities.go | 12 +- internal/compiler/checker.go | 15 +- internal/compiler/mapper.go | 52 ++++- internal/compiler/printer.go | 32 ++- internal/compiler/types.go | 80 +++++--- internal/compiler/utilities.go | 349 +++++++++++++++++++++++++++++++-- 7 files changed, 457 insertions(+), 85 deletions(-) diff --git a/internal/ast/ast.go b/internal/ast/ast.go index 4e082d7bcd..e2affc20dd 100644 --- a/internal/ast/ast.go +++ b/internal/ast/ast.go @@ -5654,6 +5654,7 @@ type SourceFile struct { IsDeclarationFile bool IsBound bool ModuleReferencesProcessed bool + HasNoDefaultLib bool UsesUriStyleNodeCoreModules core.Tristate SymbolCount int ClassifiableNames core.Set[string] @@ -5661,7 +5662,6 @@ type SourceFile struct { ModuleAugmentations []*ModuleName // []ModuleName PatternAmbientModules []PatternAmbientModule AmbientModuleNames []string - HasNoDefaultLib bool jsdocCache map[*Node][]*Node Pragmas []Pragma ReferencedFiles []*FileReference diff --git a/internal/ast/utilities.go b/internal/ast/utilities.go index 6c36d54efc..63f89b1eba 100644 --- a/internal/ast/utilities.go +++ b/internal/ast/utilities.go @@ -821,15 +821,13 @@ func WalkUpParenthesizedTypes(node *TypeNode) *Node { // Walks up the parents of a node to find the containing SourceFile func GetSourceFileOfNode(node *Node) *SourceFile { - for { - if node == nil { - return nil - } - if node.Kind == KindSourceFile { - return node.AsSourceFile() - } + for node.Parent != nil { node = node.Parent } + if node.Kind == KindSourceFile { + return node.AsSourceFile() + } + return nil } // Walks up the parents of a node to find the ancestor that matches the callback diff --git a/internal/compiler/checker.go b/internal/compiler/checker.go index deef39d196..22c6845ec2 100644 --- a/internal/compiler/checker.go +++ b/internal/compiler/checker.go @@ -816,7 +816,7 @@ func NewChecker(program *Program) *Checker { c.emptyStringType = c.getStringLiteralType("") c.zeroType = c.getNumberLiteralType(0) c.zeroBigIntType = c.getBigIntLiteralType(PseudoBigInt{negative: false, base10Value: "0"}) - c.typeofType = c.getUnionType(core.Map(slices.Collect(maps.Keys(typeofNEFacts)), c.getStringLiteralType)) + c.typeofType = c.getUnionType(core.Map(slices.Sorted(maps.Keys(typeofNEFacts)), c.getStringLiteralType)) c.flowLoopCache = make(map[FlowLoopKey]*Type) c.flowNodeReachable = make(map[*ast.FlowNode]bool) c.flowNodePostSuper = make(map[*ast.FlowNode]bool) @@ -18214,14 +18214,7 @@ func (c *Checker) addTypeToUnion(typeSet []*Type, includes TypeFlags, t *Type) ( includes |= TypeFlagsIncludesNonWideningType } } else { - var index int - var ok bool - if len(typeSet) != 0 && t.id > typeSet[len(typeSet)-1].id { - index = len(typeSet) - } else { - index, ok = slices.BinarySearchFunc(typeSet, t, compareTypeIds) - } - if !ok { + if index, ok := slices.BinarySearchFunc(typeSet, t, compareTypes); !ok { typeSet = slices.Insert(typeSet, index, t) } } @@ -18963,12 +18956,12 @@ func (c *Checker) removeType(t *Type, targetType *Type) *Type { } func containsType(types []*Type, t *Type) bool { - _, ok := slices.BinarySearchFunc(types, t, compareTypeIds) + _, ok := slices.BinarySearchFunc(types, t, compareTypes) return ok } func insertType(types []*Type, t *Type) ([]*Type, bool) { - if i, ok := slices.BinarySearchFunc(types, t, compareTypeIds); !ok { + if i, ok := slices.BinarySearchFunc(types, t, compareTypes); !ok { return slices.Insert(types, i, t), true } return types, false diff --git a/internal/compiler/mapper.go b/internal/compiler/mapper.go index bdfe3c504e..771236394b 100644 --- a/internal/compiler/mapper.go +++ b/internal/compiler/mapper.go @@ -2,18 +2,31 @@ package compiler import "github.com/microsoft/typescript-go/internal/core" +// TypeMapperKind + +type TypeMapperKind int32 + +const ( + TypeMapperKindUnknown TypeMapperKind = iota + TypeMapperKindSimple + TypeMapperKindArray + TypeMapperKindMerged +) + // TypeMapper type TypeMapper struct { data TypeMapperData } -func (m *TypeMapper) Map(t *Type) *Type { return m.data.Map(t) } +func (m *TypeMapper) Map(t *Type) *Type { return m.data.Map(t) } +func (m *TypeMapper) Kind() TypeMapperKind { return m.data.Kind() } // TypeMapperData type TypeMapperData interface { Map(t *Type) *Type + Kind() TypeMapperKind } // Factory functions @@ -70,10 +83,19 @@ func (c *Checker) newBackreferenceMapper(context *InferenceContext, index int) * return newArrayToSingleTypeMapper(typeParameters, c.unknownType) } +// TypeMapperBase + +type TypeMapperBase struct { + TypeMapper +} + +func (m *TypeMapperBase) Map(t *Type) *Type { return t } +func (m *TypeMapperBase) Kind() TypeMapperKind { return TypeMapperKindUnknown } + // SimpleTypeMapper type SimpleTypeMapper struct { - TypeMapper + TypeMapperBase source *Type target *Type } @@ -93,10 +115,14 @@ func (m *SimpleTypeMapper) Map(t *Type) *Type { return t } +func (m *SimpleTypeMapper) Kind() TypeMapperKind { + return TypeMapperKindSimple +} + // ArrayTypeMapper type ArrayTypeMapper struct { - TypeMapper + TypeMapperBase sources []*Type targets []*Type } @@ -118,10 +144,14 @@ func (m *ArrayTypeMapper) Map(t *Type) *Type { return t } +func (m *ArrayTypeMapper) Kind() TypeMapperKind { + return TypeMapperKindArray +} + // ArrayToSingleTypeMapper type ArrayToSingleTypeMapper struct { - TypeMapper + TypeMapperBase sources []*Type target *Type } @@ -146,7 +176,7 @@ func (m *ArrayToSingleTypeMapper) Map(t *Type) *Type { // DeferredTypeMapper type DeferredTypeMapper struct { - TypeMapper + TypeMapperBase sources []*Type targets []func() *Type } @@ -171,7 +201,7 @@ func (m *DeferredTypeMapper) Map(t *Type) *Type { // FunctionTypeMapper type FunctionTypeMapper struct { - TypeMapper + TypeMapperBase fn func(*Type) *Type } @@ -189,7 +219,7 @@ func (m *FunctionTypeMapper) Map(t *Type) *Type { // MergedTypeMapper type MergedTypeMapper struct { - TypeMapper + TypeMapperBase m1 *TypeMapper m2 *TypeMapper } @@ -206,10 +236,14 @@ func (m *MergedTypeMapper) Map(t *Type) *Type { return m.m2.Map(m.m1.Map(t)) } +func (m *MergedTypeMapper) Kind() TypeMapperKind { + return TypeMapperKindMerged +} + // CompositeTypeMapper type CompositeTypeMapper struct { - TypeMapper + TypeMapperBase c *Checker m1 *TypeMapper m2 *TypeMapper @@ -235,7 +269,7 @@ func (m *CompositeTypeMapper) Map(t *Type) *Type { // InferenceTypeMapper type InferenceTypeMapper struct { - TypeMapper + TypeMapperBase c *Checker n *InferenceContext fixing bool diff --git a/internal/compiler/printer.go b/internal/compiler/printer.go index 402129449c..d158ab8e09 100644 --- a/internal/compiler/printer.go +++ b/internal/compiler/printer.go @@ -112,6 +112,7 @@ func (p *Printer) printType(t *Type) { } func (p *Printer) printTypeNoAlias(t *Type) { + p.depth++ switch { case t.flags&TypeFlagsIntrinsic != 0: p.print(t.AsIntrinsicType().intrinsicName) @@ -136,14 +137,13 @@ func (p *Printer) printTypeNoAlias(t *Type) { case t.flags&TypeFlagsStringMapping != 0: p.printStringMappingType(t) } + p.depth-- } func (p *Printer) printRecursive(t *Type, f func(*Printer, *Type)) { if !p.printing.Has(t) && p.depth < 10 { p.printing.Add(t) - p.depth++ f(p, t) - p.depth-- p.printing.Delete(t) } else { p.print("???") @@ -288,14 +288,28 @@ func (p *Printer) printTupleType(t *Type) { if info.flags&ElementFlagsVariable != 0 { p.print("...") } - if info.flags&ElementFlagsOptional != 0 { - p.printTypeEx(t, ast.TypePrecedencePostfix) - p.print("?") - } else if info.flags&ElementFlagsRest != 0 { - p.printTypeEx(t, ast.TypePrecedencePostfix) - p.print("[]") + if info.labeledDeclaration != nil { + p.print(info.labeledDeclaration.Name().Text()) + if info.flags&ElementFlagsOptional != 0 { + p.print("?") + } + p.print(": ") + if info.flags&ElementFlagsRest != 0 { + p.printTypeEx(t, ast.TypePrecedencePostfix) + p.print("[]") + } else { + p.printType(t) + } } else { - p.printType(t) + if info.flags&ElementFlagsOptional != 0 { + p.printTypeEx(t, ast.TypePrecedencePostfix) + p.print("?") + } else if info.flags&ElementFlagsRest != 0 { + p.printTypeEx(t, ast.TypePrecedencePostfix) + p.print("[]") + } else { + p.printType(t) + } } tail = true } diff --git a/internal/compiler/types.go b/internal/compiler/types.go index eceab24c1b..7830d43878 100644 --- a/internal/compiler/types.go +++ b/internal/compiler/types.go @@ -375,21 +375,21 @@ const ( TypeFlagsNone TypeFlags = 0 TypeFlagsAny TypeFlags = 1 << 0 TypeFlagsUnknown TypeFlags = 1 << 1 - TypeFlagsString TypeFlags = 1 << 2 - TypeFlagsNumber TypeFlags = 1 << 3 - TypeFlagsBoolean TypeFlags = 1 << 4 - TypeFlagsEnum TypeFlags = 1 << 5 // Numeric computed enum member value - TypeFlagsBigInt TypeFlags = 1 << 6 - TypeFlagsStringLiteral TypeFlags = 1 << 7 - TypeFlagsNumberLiteral TypeFlags = 1 << 8 - TypeFlagsBooleanLiteral TypeFlags = 1 << 9 - TypeFlagsEnumLiteral TypeFlags = 1 << 10 // Always combined with StringLiteral, NumberLiteral, or Union - TypeFlagsBigIntLiteral TypeFlags = 1 << 11 - TypeFlagsESSymbol TypeFlags = 1 << 12 // Type of symbol primitive introduced in ES6 - TypeFlagsUniqueESSymbol TypeFlags = 1 << 13 // unique symbol - TypeFlagsVoid TypeFlags = 1 << 14 - TypeFlagsUndefined TypeFlags = 1 << 15 - TypeFlagsNull TypeFlags = 1 << 16 + TypeFlagsUndefined TypeFlags = 1 << 2 + TypeFlagsNull TypeFlags = 1 << 3 + TypeFlagsVoid TypeFlags = 1 << 4 + TypeFlagsString TypeFlags = 1 << 5 + TypeFlagsNumber TypeFlags = 1 << 6 + TypeFlagsBigInt TypeFlags = 1 << 7 + TypeFlagsBoolean TypeFlags = 1 << 8 + TypeFlagsESSymbol TypeFlags = 1 << 9 // Type of symbol primitive introduced in ES6 + TypeFlagsStringLiteral TypeFlags = 1 << 10 + TypeFlagsNumberLiteral TypeFlags = 1 << 11 + TypeFlagsBigIntLiteral TypeFlags = 1 << 12 + TypeFlagsBooleanLiteral TypeFlags = 1 << 13 + TypeFlagsUniqueESSymbol TypeFlags = 1 << 14 // unique symbol + TypeFlagsEnumLiteral TypeFlags = 1 << 15 // Always combined with StringLiteral, NumberLiteral, or Union + TypeFlagsEnum TypeFlags = 1 << 16 // Numeric computed enum member value (must be right after EnumLiteral, see getSortOrderFlags) TypeFlagsNever TypeFlags = 1 << 17 // Never type TypeFlagsTypeParameter TypeFlags = 1 << 18 // Type parameter TypeFlagsObject TypeFlags = 1 << 19 // Object type @@ -488,7 +488,7 @@ const ( ObjectFlagsPropagatingFlags = ObjectFlagsContainsWideningType | ObjectFlagsContainsObjectOrArrayLiteral | ObjectFlagsNonInferrableType ObjectFlagsInstantiatedMapped = ObjectFlagsMapped | ObjectFlagsInstantiated // Object flags that uniquely identify the kind of ObjectType - ObjectFlagsObjectTypeKindMask = ObjectFlagsClassOrInterface | ObjectFlagsReference | ObjectFlagsTuple | ObjectFlagsAnonymous | ObjectFlagsMapped | ObjectFlagsReverseMapped | ObjectFlagsEvolvingArray + ObjectFlagsObjectTypeKindMask = ObjectFlagsClassOrInterface | ObjectFlagsReference | ObjectFlagsTuple | ObjectFlagsAnonymous | ObjectFlagsMapped | ObjectFlagsReverseMapped | ObjectFlagsEvolvingArray | ObjectFlagsInstantiationExpressionType | ObjectFlagsSingleSignatureType // Flags that require TypeFlags.Object ObjectFlagsContainsSpread = 1 << 22 // Object literal contains spread operation ObjectFlagsObjectRestType = 1 << 23 // Originates in object rest declaration @@ -719,17 +719,42 @@ func (t *StructuredType) ConstructSignatures() []*Signature { return slices.Clip(t.signatures[t.callSignatureCount:]) } -// ObjectType (base of all instantiable object types) -// Instances of ObjectType or derived types have the following ObjectFlags: -// ObjectType (ObjectFlagsAnonymous) -// TypeReference (ObjectFlagsReference) -// InterfaceType (ObjectFlagsReference | (ObjectFlagsClass|ObjectFlagsInterface)) -// TupleType (ObjectFlagsReference | ObjectFlagsTuple) -// SingleSignatureType (ObjectFlagsAnonymous|ObjectFlagsSingleSignatureType) -// InstantiationExpressionType (ObjectFlagsAnonymous|ObjectFlagsInstantiationExpressionType) -// MappedType (ObjectFlagsAnonymous|ObjectFlagsMapped) -// ReverseMapped (ObjectFlagsReverseMapped) -// EvolvingArray (ObjectFlagsEvolvingArray) +// Except for tuple type references and reverse mapped types, all object types have an associated symbol. +// Possible object type instances are listed in the following. + +// InterfaceType: +// ObjectFlagsClass: Originating non-generic class type +// ObjectFlagsClass|ObjectFlagsReference: Originating generic class type +// ObjectFlagsInterface: Originating non-generic interface type +// ObjectFlagsInterface|ObjectFlagsReference: Originating generic interface type + +// TupleType: +// ObjectFlagsReference|ObjectFlagsTuple: Originating generic tuple type (synthesized) + +// TypeReference +// ObjectFlagsReference: Instantiated generic class, interface, or tuple type + +// ObjectType: +// ObjectFlagsAnonymous: Originating anonymous object type +// ObjectFlagsAnonymous|ObjectFlagsInstantiated: Instantiated anonymous object type + +// MappedType: +// ObjectFlagsMapped: Originating mapped type +// ObjectFlagsMapped|ObjectFlagsInstantiated: Instantiated mapped type + +// InstantiationExpressionType: +// ObjectFlagsAnonymous|ObjectFlagsInstantiationExpression: Originating instantiation expression type +// ObjectFlagsAnonymous|ObjectFlagsInstantiated|ObjectFlagsInstantiationExpression: Instantiated instantiation expression type + +// SingleSignatureType: +// ObjectFlagsAnonymous|ObjectFlagsSingleSignatureType: Originating single signature type +// ObjectFlagsAnonymous|ObjectFlagsInstantiated|ObjectFlagsSingleSignatureType: Instantiated single signature type + +// ReverseMappedType: +// ObjectFlagsAnonymous|ObjectFlagsReverseMapped: Reverse mapped type + +// EvolvingArrayType: +// ObjectFlagsEvolvingArray: Evolving array type type ObjectType struct { StructuredType @@ -873,7 +898,6 @@ type UnionOrIntersectionType struct { propertyCache ast.SymbolTable propertyCacheWithoutFunctionPropertyAugment ast.SymbolTable resolvedProperties []*ast.Symbol - resolvedBaseConstraint *Type } func (t *UnionOrIntersectionType) AsUnionOrIntersectionType() *UnionOrIntersectionType { return t } diff --git a/internal/compiler/utilities.go b/internal/compiler/utilities.go index 7a95f323f5..4fb14c0a1c 100644 --- a/internal/compiler/utilities.go +++ b/internal/compiler/utilities.go @@ -1,6 +1,7 @@ package compiler import ( + "cmp" "maps" "math" "slices" @@ -1585,35 +1586,343 @@ func compareSymbols(s1, s2 *ast.Symbol) int { if s1 == s2 { return 0 } - if s1.ValueDeclaration != nil && s2.ValueDeclaration != nil { - // Symbols with the same unmerged parent are always in the same file - if s1.Parent != s2.Parent { - f1 := ast.GetSourceFileOfNode(s1.ValueDeclaration) - f2 := ast.GetSourceFileOfNode(s2.ValueDeclaration) - if f1 != f2 { - // Compare the full paths (no two files should have the same full path) - return strings.Compare(string(f1.Path()), string(f2.Path())) + if s1 == nil { + return 1 + } + if s2 == nil { + return -1 + } + if len(s1.Declarations) != 0 && len(s2.Declarations) != 0 { + if s1.Parent == s2.Parent && s1.Parent != nil { + // Symbols with the same unmerged parent are always in the same file + if c := s1.Declarations[0].Pos() - s2.Declarations[0].Pos(); c != 0 { + return c + } + } else { + if c := compareNodes(s1.Declarations[0], s2.Declarations[0]); c != 0 { + return c + } + } + } else if len(s1.Declarations) != 0 { + return -1 + } else if len(s2.Declarations) != 0 { + return 1 + } + if c := strings.Compare(s1.Name, s2.Name); c != 0 { + return c + } + // Fall back to symbol IDs. This is a last resort that should happen only when symbols have + // no declaration and duplicate names. + return int(ast.GetSymbolId(s1)) - int(ast.GetSymbolId(s2)) +} + +func compareNodes(n1, n2 *ast.Node) int { + if n1 == n2 { + return 0 + } + if n1 == nil { + return 1 + } + if n2 == nil { + return -1 + } + f1 := ast.GetSourceFileOfNode(n1) + f2 := ast.GetSourceFileOfNode(n2) + if f1 != f2 { + // Compare the full paths (no two files should have the same full path) + return strings.Compare(string(f1.Path()), string(f2.Path())) + } + // In the same file, compare source positions + return n1.Pos() - n2.Pos() +} + +func compareTypes(t1, t2 *Type) int { + if t1 == t2 { + return 0 + } + if t1 == nil { + return -1 + } + if t2 == nil { + return 1 + } + // First sort in order of increasing type flags values. + if c := getSortOrderFlags(t1) - getSortOrderFlags(t2); c != 0 { + return c + } + // We have identical type flags, now sort by data specific to the type. + switch { + case t1.flags&(TypeFlagsAny|TypeFlagsUnknown|TypeFlagsString|TypeFlagsNumber|TypeFlagsBoolean|TypeFlagsBigInt|TypeFlagsESSymbol|TypeFlagsVoid|TypeFlagsUndefined|TypeFlagsNull|TypeFlagsNever|TypeFlagsNonPrimitive) != 0: + // Only distinguished by type IDs + case t1.flags&TypeFlagsObject != 0: + // Order types by symbol. For ordering we prefer alias symbols over type symbols because alias symbols are + // more specific (two types with the same type symbol may have differing alias symbols, but not vice-versa). + if c := compareSymbols(getNamedObjectTypeSymbol(t1), getNamedObjectTypeSymbol(t2)); c != 0 { + return c + } + // When object types have the same symbol, order by kind. We order type references before other kinds. + if t1.objectFlags&ObjectFlagsReference != 0 && t2.objectFlags&ObjectFlagsReference != 0 { + r1 := t1.AsTypeReference() + r2 := t2.AsTypeReference() + if r1.target.objectFlags&ObjectFlagsTuple != 0 && r2.target.objectFlags&ObjectFlagsTuple != 0 { + // Tuple types have no associated symbol, instead we order by tuple element information. + if c := compareTupleTypes(r1.target.AsTupleType(), r2.target.AsTupleType()); c != 0 { + return c + } } - // In the same file, compare source positions + // Here we know we have references to instantiations of the same type because we have matching targets. + if r1.node == nil && r2.node == nil { + // Non-deferred type references with the same target are sorted by their type argument lists. + if c := compareTypeLists(t1.AsTypeReference().resolvedTypeArguments, t2.AsTypeReference().resolvedTypeArguments); c != 0 { + return c + } + } else { + // Deferred type references with the same target are ordered by the source location of the reference. + if c := compareNodes(r1.node, r2.node); 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). + if c := compareTypeMappers(t1.AsObjectType().mapper, t2.AsObjectType().mapper); c != 0 { + return c + } + } + } else if t1.objectFlags&ObjectFlagsReference != 0 { + return -1 + } else if t2.objectFlags&ObjectFlagsReference != 0 { + return 1 + } else { + // Order unnamed non-reference object types by their symbol (if any), then by their kind, and finally + // by their 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. + if c := compareSymbols(t1.symbol, t2.symbol); c != 0 { + return c + } + if c := int(t1.objectFlags&ObjectFlagsObjectTypeKindMask) - int(t2.objectFlags&ObjectFlagsObjectTypeKindMask); c != 0 { + return c + } + if c := compareTypeMappers(t1.AsObjectType().mapper, t2.AsObjectType().mapper); c != 0 { + return c + } + } + case t1.flags&TypeFlagsUnionOrIntersection != 0: + if c := compareAliases(t1.alias, t2.alias); c != 0 { + return c + } + if c := compareTypeLists(t1.Types(), t2.Types()); c != 0 { + return c } - r := s1.ValueDeclaration.Pos() - s2.ValueDeclaration.Pos() - if r != 0 { - return r + case t1.flags&(TypeFlagsEnumLiteral|TypeFlagsUniqueESSymbol) != 0: + if c := compareSymbols(t1.symbol, t2.symbol); c != 0 { + return c + } + case t1.flags&TypeFlagsStringLiteral != 0: + // We order string literal types by their values. + if c := strings.Compare(t1.AsLiteralType().value.(string), t2.AsLiteralType().value.(string)); c != 0 { + return c + } + case t1.flags&TypeFlagsNumberLiteral != 0: + // We order numeric literal types by their values. + if c := cmp.Compare(t1.AsLiteralType().value.(float64), t2.AsLiteralType().value.(float64)); c != 0 { + return c } + case t1.flags&TypeFlagsBooleanLiteral != 0: + b1 := t1.AsLiteralType().value.(bool) + b2 := t2.AsLiteralType().value.(bool) + if b1 != b2 { + if b1 { + return 1 + } + return -1 + } + case t1.flags&TypeFlagsTypeParameter != 0: + if c := compareSymbols(t1.symbol, t2.symbol); c != 0 { + return c + } + case t1.flags&TypeFlagsIndex != 0: + if c := compareTypes(t1.AsIndexType().target, t2.AsIndexType().target); c != 0 { + return c + } + if c := int(t1.AsIndexType().flags) - int(t2.AsIndexType().flags); c != 0 { + return c + } + case t1.flags&TypeFlagsIndexedAccess != 0: + if c := compareAliases(t1.alias, t2.alias); c != 0 { + return c + } + if c := compareTypes(t1.AsIndexedAccessType().objectType, t2.AsIndexedAccessType().objectType); c != 0 { + return c + } + if c := compareTypes(t1.AsIndexedAccessType().indexType, t2.AsIndexedAccessType().indexType); c != 0 { + return c + } + case t1.flags&TypeFlagsConditional != 0: + if c := compareAliases(t1.alias, t2.alias); c != 0 { + return c + } + if c := compareNodes(t1.AsConditionalType().root.node.AsNode(), t2.AsConditionalType().root.node.AsNode()); c != 0 { + return c + } + if c := compareTypeMappers(t1.AsConditionalType().mapper, t2.AsConditionalType().mapper); c != 0 { + return c + } + case t1.flags&TypeFlagsSubstitution != 0: + if c := compareTypes(t1.AsSubstitutionType().baseType, t2.AsSubstitutionType().baseType); c != 0 { + return c + } + if c := compareTypes(t1.AsSubstitutionType().constraint, t2.AsSubstitutionType().constraint); c != 0 { + return c + } + case t1.flags&TypeFlagsTemplateLiteral != 0: + if c := compareTexts(t1.AsTemplateLiteralType().texts, t2.AsTemplateLiteralType().texts); c != 0 { + return c + } + if c := compareTypeLists(t1.AsTemplateLiteralType().types, t2.AsTemplateLiteralType().types); c != 0 { + return c + } + case t1.flags&TypeFlagsStringMapping != 0: + if c := compareSymbols(t1.symbol, t2.symbol); c != 0 { + return c + } + if c := compareTypes(t1.AsStringMappingType().target, t2.AsStringMappingType().target); c != 0 { + return c + } + } + // Fall back to type IDs. This results in type creation order for built-in types. + return int(t1.id) - int(t2.id) +} + +func getSortOrderFlags(t *Type) int { + // We want enum literal and computed values to be ordered by their declarations, so we merge TypeFlagsEnum into + // TypeFlagsEnumLiteral and clear TypeFlagsEnum. + return int((t.flags&TypeFlagsEnum)>>1 | t.flags&^TypeFlagsEnum) +} + +func getNamedObjectTypeSymbol(t *Type) *ast.Symbol { + if t.alias != nil { + return t.alias.symbol } - // Symbols with value declarations sort before symbols without - if s1.ValueDeclaration != nil && s2.ValueDeclaration == nil { + if t.objectFlags&(ObjectFlagsClassOrInterface|ObjectFlagsReference) != 0 { + return t.symbol + } + return nil +} + +func compareTupleTypes(t1, t2 *TupleType) int { + if t1 == t2 { + return 0 + } + if t1.readonly != t2.readonly { + return core.IfElse(t1.readonly, 1, -1) + } + if len(t1.elementInfos) != len(t2.elementInfos) { + return len(t1.elementInfos) - len(t2.elementInfos) + } + for i := range t1.elementInfos { + if c := int(t1.elementInfos[i].flags) - int(t2.elementInfos[i].flags); c != 0 { + return c + } + } + for i := range t1.elementInfos { + if c := compareElementLabels(t1.elementInfos[i].labeledDeclaration, t2.elementInfos[i].labeledDeclaration); c != 0 { + return c + } + } + return 0 +} + +func compareElementLabels(n1, n2 *ast.Node) int { + if n1 == n2 { + return 0 + } + if n1 == nil { return -1 } - if s1.ValueDeclaration == nil && s2.ValueDeclaration != nil { + if n2 == nil { return 1 } - // Sort by name - r := strings.Compare(s1.Name, s2.Name) - if r != 0 { - return r + return strings.Compare(n1.Name().Text(), n2.Name().Text()) +} + +func compareAliases(a1, a2 *TypeAlias) int { + if a1 == a2 { + return 0 + } + if a1 == nil { + return 1 + } + if a2 == nil { + return -1 + } + if c := compareSymbols(a1.symbol, a2.symbol); c != 0 { + return c + } + return compareTypeLists(a1.typeArguments, a2.typeArguments) +} + +func compareTypeLists(s1, s2 []*Type) int { + if len(s1) != len(s2) { + return len(s1) - len(s2) + } + for i, t1 := range s1 { + if c := compareTypes(t1, s2[i]); c != 0 { + return c + } + } + return 0 +} + +func compareTexts(s1, s2 []string) int { + if len(s1) != len(s2) { + return len(s1) - len(s2) + } + for i, t1 := range s1 { + if c := strings.Compare(t1, s2[i]); c != 0 { + return c + } + } + return 0 +} + +func compareTypeMappers(m1, m2 *TypeMapper) int { + if m1 == m2 { + return 0 + } + if m1 == nil { + return 1 + } + if m2 == nil { + return -1 } - panic("Symbols must have unique names to be sorted") + kind1 := m1.Kind() + kind2 := m2.Kind() + if kind1 != kind2 { + return int(kind1) - int(kind2) + } + switch kind1 { + case TypeMapperKindSimple: + m1 := m1.data.(*SimpleTypeMapper) + m2 := m2.data.(*SimpleTypeMapper) + if c := compareTypes(m1.source, m2.source); c != 0 { + return c + } + return compareTypes(m1.target, m2.target) + case TypeMapperKindArray: + m1 := m1.data.(*ArrayTypeMapper) + m2 := m2.data.(*ArrayTypeMapper) + if c := compareTypeLists(m1.sources, m2.sources); c != 0 { + return c + } + return compareTypeLists(m1.targets, m2.targets) + case TypeMapperKindMerged: + m1 := m1.data.(*MergedTypeMapper) + m2 := m2.data.(*MergedTypeMapper) + if c := compareTypeMappers(m1.m1, m2.m1); c != 0 { + return c + } + return compareTypeMappers(m1.m2, m2.m2) + } + return 0 } func getClassLikeDeclarationOfSymbol(symbol *ast.Symbol) *ast.Node { From 9520c1cd97de54d0ebc43d12e47b3bdeea8763d6 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 26 Dec 2024 10:09:45 -1000 Subject: [PATCH 2/9] Remove unstable comparison --- internal/compiler/utilities.go | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/internal/compiler/utilities.go b/internal/compiler/utilities.go index 4fb14c0a1c..87615c2968 100644 --- a/internal/compiler/utilities.go +++ b/internal/compiler/utilities.go @@ -1593,15 +1593,8 @@ func compareSymbols(s1, s2 *ast.Symbol) int { return -1 } if len(s1.Declarations) != 0 && len(s2.Declarations) != 0 { - if s1.Parent == s2.Parent && s1.Parent != nil { - // Symbols with the same unmerged parent are always in the same file - if c := s1.Declarations[0].Pos() - s2.Declarations[0].Pos(); c != 0 { - return c - } - } else { - if c := compareNodes(s1.Declarations[0], s2.Declarations[0]); c != 0 { - return c - } + if c := compareNodes(s1.Declarations[0], s2.Declarations[0]); c != 0 { + return c } } else if len(s1.Declarations) != 0 { return -1 From e5a77070d099e38f7672860349bfb80edf9687fe Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 26 Dec 2024 10:10:11 -1000 Subject: [PATCH 3/9] Fix potential index out of range --- internal/compiler/printer.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/compiler/printer.go b/internal/compiler/printer.go index d158ab8e09..1f37bd53ee 100644 --- a/internal/compiler/printer.go +++ b/internal/compiler/printer.go @@ -280,11 +280,12 @@ func (p *Printer) printTupleType(t *Type) { tail := false p.print("[") elementInfos := t.TargetTupleType().elementInfos - for i, t := range p.c.getTypeArguments(t) { + typeArguments := p.c.getTypeArguments(t) + for i, info := range elementInfos { + t := typeArguments[i] if tail { p.print(", ") } - info := elementInfos[i] if info.flags&ElementFlagsVariable != 0 { p.print("...") } From f5c1201947caab79c9fcca1f4138f63496e6b740 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 27 Dec 2024 08:26:46 -1000 Subject: [PATCH 4/9] Include origin in union ordering --- internal/compiler/utilities.go | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/internal/compiler/utilities.go b/internal/compiler/utilities.go index 87615c2968..e4afaf3bd0 100644 --- a/internal/compiler/utilities.go +++ b/internal/compiler/utilities.go @@ -1698,7 +1698,28 @@ func compareTypes(t1, t2 *Type) int { return c } } - case t1.flags&TypeFlagsUnionOrIntersection != 0: + case t1.flags&TypeFlagsUnion != 0: + // Unions are ordered by their alias, then by their origin, and finally by their constituent type lists. + if c := compareAliases(t1.alias, t2.alias); c != 0 { + return c + } + o1 := t1.AsUnionType().origin + o2 := t2.AsUnionType().origin + if o1 == nil && o2 == nil { + if c := compareTypeLists(t1.Types(), t2.Types()); c != 0 { + return c + } + } else if o1 == nil { + return 1 + } else if o2 == nil { + return -1 + } else { + if c := compareTypes(o1, o2); c != 0 { + return c + } + } + case t1.flags&TypeFlagsIntersection != 0: + // Intersections are ordered by their alias and then by their constituent type lists. if c := compareAliases(t1.alias, t2.alias); c != 0 { return c } @@ -1706,16 +1727,17 @@ func compareTypes(t1, t2 *Type) int { return c } case t1.flags&(TypeFlagsEnumLiteral|TypeFlagsUniqueESSymbol) != 0: + // Enum members are ordered by their symbol (and thus their declaration order). if c := compareSymbols(t1.symbol, t2.symbol); c != 0 { return c } case t1.flags&TypeFlagsStringLiteral != 0: - // We order string literal types by their values. + // String literal types are ordered by their values. if c := strings.Compare(t1.AsLiteralType().value.(string), t2.AsLiteralType().value.(string)); c != 0 { return c } case t1.flags&TypeFlagsNumberLiteral != 0: - // We order numeric literal types by their values. + // Numeric literal types are ordered by their values. if c := cmp.Compare(t1.AsLiteralType().value.(float64), t2.AsLiteralType().value.(float64)); c != 0 { return c } From 8a8f8332414cc03512f9bbfcbdaa41a96d6f18d6 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 31 Dec 2024 07:40:35 -1000 Subject: [PATCH 5/9] Favor ordering types by name --- internal/compiler/utilities.go | 84 ++++++++++++++++------------------ 1 file changed, 39 insertions(+), 45 deletions(-) diff --git a/internal/compiler/utilities.go b/internal/compiler/utilities.go index e4afaf3bd0..7507926d38 100644 --- a/internal/compiler/utilities.go +++ b/internal/compiler/utilities.go @@ -1643,17 +1643,20 @@ func compareTypes(t1, t2 *Type) int { if c := getSortOrderFlags(t1) - getSortOrderFlags(t2); c != 0 { return c } - // We have identical type flags, now sort by data specific to the type. + // Order named types by name and, in the case of aliased types, by alias type arguments. + if c := compareTypeNames(t1, t2); c != 0 { + return c + } + // We have unnamed types or types with identical names. Now sort by data specific to the type. switch { case t1.flags&(TypeFlagsAny|TypeFlagsUnknown|TypeFlagsString|TypeFlagsNumber|TypeFlagsBoolean|TypeFlagsBigInt|TypeFlagsESSymbol|TypeFlagsVoid|TypeFlagsUndefined|TypeFlagsNull|TypeFlagsNever|TypeFlagsNonPrimitive) != 0: // Only distinguished by type IDs case t1.flags&TypeFlagsObject != 0: - // Order types by symbol. For ordering we prefer alias symbols over type symbols because alias symbols are - // more specific (two types with the same type symbol may have differing alias symbols, but not vice-versa). - if c := compareSymbols(getNamedObjectTypeSymbol(t1), getNamedObjectTypeSymbol(t2)); c != 0 { + // Order unnamed or identically named object types by symbol. + if c := compareSymbols(t1.symbol, t2.symbol); c != 0 { return c } - // When object types have the same symbol, order by kind. We order type references before other kinds. + // When object types have the same or no symbol, order by kind. We order type references before other kinds. if t1.objectFlags&ObjectFlagsReference != 0 && t2.objectFlags&ObjectFlagsReference != 0 { r1 := t1.AsTypeReference() r2 := t2.AsTypeReference() @@ -1685,12 +1688,9 @@ func compareTypes(t1, t2 *Type) int { } else if t2.objectFlags&ObjectFlagsReference != 0 { return 1 } else { - // Order unnamed non-reference object types by their symbol (if any), then by their kind, and finally - // by their 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. - if c := compareSymbols(t1.symbol, t2.symbol); c != 0 { - return c - } + // 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. if c := int(t1.objectFlags&ObjectFlagsObjectTypeKindMask) - int(t2.objectFlags&ObjectFlagsObjectTypeKindMask); c != 0 { return c } @@ -1699,10 +1699,7 @@ func compareTypes(t1, t2 *Type) int { } } case t1.flags&TypeFlagsUnion != 0: - // Unions are ordered by their alias, then by their origin, and finally by their constituent type lists. - if c := compareAliases(t1.alias, t2.alias); c != 0 { - return c - } + // Unions are ordered by origin and then constituent type lists. o1 := t1.AsUnionType().origin o2 := t2.AsUnionType().origin if o1 == nil && o2 == nil { @@ -1719,10 +1716,7 @@ func compareTypes(t1, t2 *Type) int { } } case t1.flags&TypeFlagsIntersection != 0: - // Intersections are ordered by their alias and then by their constituent type lists. - if c := compareAliases(t1.alias, t2.alias); c != 0 { - return c - } + // Intersections are ordered by their constituent type lists. if c := compareTypeLists(t1.Types(), t2.Types()); c != 0 { return c } @@ -1762,9 +1756,6 @@ func compareTypes(t1, t2 *Type) int { return c } case t1.flags&TypeFlagsIndexedAccess != 0: - if c := compareAliases(t1.alias, t2.alias); c != 0 { - return c - } if c := compareTypes(t1.AsIndexedAccessType().objectType, t2.AsIndexedAccessType().objectType); c != 0 { return c } @@ -1772,9 +1763,6 @@ func compareTypes(t1, t2 *Type) int { return c } case t1.flags&TypeFlagsConditional != 0: - if c := compareAliases(t1.alias, t2.alias); c != 0 { - return c - } if c := compareNodes(t1.AsConditionalType().root.node.AsNode(), t2.AsConditionalType().root.node.AsNode()); c != 0 { return c } @@ -1796,9 +1784,6 @@ func compareTypes(t1, t2 *Type) int { return c } case t1.flags&TypeFlagsStringMapping != 0: - if c := compareSymbols(t1.symbol, t2.symbol); c != 0 { - return c - } if c := compareTypes(t1.AsStringMappingType().target, t2.AsStringMappingType().target); c != 0 { return c } @@ -1813,10 +1798,35 @@ func getSortOrderFlags(t *Type) int { return int((t.flags&TypeFlagsEnum)>>1 | t.flags&^TypeFlagsEnum) } -func getNamedObjectTypeSymbol(t *Type) *ast.Symbol { +func compareTypeNames(t1, t2 *Type) int { + s1 := getTypeNameSymbol(t1) + s2 := getTypeNameSymbol(t2) + if s1 == s2 { + if t1.alias != nil { + return compareTypeLists(t1.alias.typeArguments, t2.alias.typeArguments) + } + return 0 + } + if s1 == nil { + return 1 + } + if s2 == nil { + return -1 + } + return strings.Compare(s1.Name, s2.Name) +} + +func getTypeNameSymbol(t *Type) *ast.Symbol { if t.alias != nil { return t.alias.symbol } + if t.flags&(TypeFlagsTypeParameter|TypeFlagsStringMapping) != 0 || t.objectFlags&(ObjectFlagsClassOrInterface|ObjectFlagsReference) != 0 { + return t.symbol + } + return nil +} + +func getObjectTypeName(t *Type) *ast.Symbol { if t.objectFlags&(ObjectFlagsClassOrInterface|ObjectFlagsReference) != 0 { return t.symbol } @@ -1859,22 +1869,6 @@ func compareElementLabels(n1, n2 *ast.Node) int { return strings.Compare(n1.Name().Text(), n2.Name().Text()) } -func compareAliases(a1, a2 *TypeAlias) int { - if a1 == a2 { - return 0 - } - if a1 == nil { - return 1 - } - if a2 == nil { - return -1 - } - if c := compareSymbols(a1.symbol, a2.symbol); c != 0 { - return c - } - return compareTypeLists(a1.typeArguments, a2.typeArguments) -} - func compareTypeLists(s1, s2 []*Type) int { if len(s1) != len(s2) { return len(s1) - len(s2) From 4ddd36887019430ad3b6b45e1aefe71022efd674 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 6 Jan 2025 18:08:41 -0800 Subject: [PATCH 6/9] Address CR feedback --- internal/compiler/utilities.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/internal/compiler/utilities.go b/internal/compiler/utilities.go index 7507926d38..aa3eb07b01 100644 --- a/internal/compiler/utilities.go +++ b/internal/compiler/utilities.go @@ -1650,7 +1650,7 @@ func compareTypes(t1, t2 *Type) int { // We have unnamed types or types with identical names. Now sort by data specific to the type. switch { case t1.flags&(TypeFlagsAny|TypeFlagsUnknown|TypeFlagsString|TypeFlagsNumber|TypeFlagsBoolean|TypeFlagsBigInt|TypeFlagsESSymbol|TypeFlagsVoid|TypeFlagsUndefined|TypeFlagsNull|TypeFlagsNever|TypeFlagsNonPrimitive) != 0: - // Only distinguished by type IDs + // Only distinguished by type IDs, handled below. case t1.flags&TypeFlagsObject != 0: // Order unnamed or identically named object types by symbol. if c := compareSymbols(t1.symbol, t2.symbol); c != 0 { @@ -1882,15 +1882,12 @@ func compareTypeLists(s1, s2 []*Type) int { } func compareTexts(s1, s2 []string) int { - if len(s1) != len(s2) { - return len(s1) - len(s2) - } - for i, t1 := range s1 { - if c := strings.Compare(t1, s2[i]); c != 0 { + for i := range min(len(s1), len(s2)) { + if c := strings.Compare(s1[i], s2[i]); c != 0 { return c } } - return 0 + return len(s1) - len(s2) } func compareTypeMappers(m1, m2 *TypeMapper) int { From 8adbe3b1c42f93ee56ba5aa301680b333d1014f0 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 7 Jan 2025 16:58:01 -0800 Subject: [PATCH 7/9] Use program file ordering instead of file path comparisons --- internal/compiler/checker.go | 15 ++++++++++++- internal/compiler/types.go | 1 + internal/compiler/utilities.go | 39 ++++++++++++++++++---------------- 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/internal/compiler/checker.go b/internal/compiler/checker.go index 330df2b793..7b2ea921d5 100644 --- a/internal/compiler/checker.go +++ b/internal/compiler/checker.go @@ -486,6 +486,8 @@ type Checker struct { host CompilerHost compilerOptions *core.CompilerOptions files []*ast.SourceFile + fileIndexMap map[*ast.SourceFile]int + compareSymbolsFunc func(*ast.Symbol, *ast.Symbol) int typeCount uint32 symbolCount uint32 totalInstantiationCount uint32 @@ -732,6 +734,8 @@ func NewChecker(program *Program) *Checker { c.host = program.host c.compilerOptions = program.compilerOptions c.files = program.files + c.fileIndexMap = createFileIndexMap(c.files) + c.compareSymbolsFunc = c.compareSymbols // Closure optimization c.languageVersion = c.compilerOptions.GetEmitScriptTarget() c.moduleKind = c.compilerOptions.GetEmitModuleKind() c.legacyDecorators = c.compilerOptions.ExperimentalDecorators == core.TSTrue @@ -903,6 +907,14 @@ func NewChecker(program *Program) *Checker { return c } +func createFileIndexMap(files []*ast.SourceFile) map[*ast.SourceFile]int { + result := make(map[*ast.SourceFile]int) + for i, file := range files { + result[file] = i + } + return result +} + func (c *Checker) reportUnreliableWorker(t *Type) *Type { if c.outofbandVarianceMarkerHandler != nil && (t == c.markerSuperType || t == c.markerSubType || t == c.markerOtherType) { c.outofbandVarianceMarkerHandler(true /*onlyUnreliable*/) @@ -15109,7 +15121,7 @@ func (c *Checker) getNamedMembers(members ast.SymbolTable) []*ast.Symbol { result = append(result, symbol) } } - sortSymbols(result) + c.sortSymbols(result) return result } @@ -17861,6 +17873,7 @@ func (c *Checker) newType(flags TypeFlags, objectFlags ObjectFlags, data TypeDat t.flags = flags t.objectFlags = objectFlags &^ (ObjectFlagsCouldContainTypeVariablesComputed | ObjectFlagsCouldContainTypeVariables | ObjectFlagsMembersResolved) t.id = TypeId(c.typeCount) + t.checker = c t.data = data return t } diff --git a/internal/compiler/types.go b/internal/compiler/types.go index da72ac77f9..c35e867c42 100644 --- a/internal/compiler/types.go +++ b/internal/compiler/types.go @@ -548,6 +548,7 @@ type Type struct { id TypeId symbol *ast.Symbol alias *TypeAlias + checker *Checker data TypeData // Type specific data } diff --git a/internal/compiler/utilities.go b/internal/compiler/utilities.go index e5475cfcfe..b38a1455f7 100644 --- a/internal/compiler/utilities.go +++ b/internal/compiler/utilities.go @@ -1579,11 +1579,11 @@ func createSymbolTable(symbols []*ast.Symbol) ast.SymbolTable { return result } -func sortSymbols(symbols []*ast.Symbol) { - slices.SortFunc(symbols, compareSymbols) +func (c *Checker) sortSymbols(symbols []*ast.Symbol) { + slices.SortFunc(symbols, c.compareSymbolsFunc) } -func compareSymbols(s1, s2 *ast.Symbol) int { +func (c *Checker) compareSymbols(s1, s2 *ast.Symbol) int { if s1 == s2 { return 0 } @@ -1594,23 +1594,23 @@ func compareSymbols(s1, s2 *ast.Symbol) int { return -1 } if len(s1.Declarations) != 0 && len(s2.Declarations) != 0 { - if c := compareNodes(s1.Declarations[0], s2.Declarations[0]); c != 0 { - return c + if r := c.compareNodes(s1.Declarations[0], s2.Declarations[0]); r != 0 { + return r } } else if len(s1.Declarations) != 0 { return -1 } else if len(s2.Declarations) != 0 { return 1 } - if c := strings.Compare(s1.Name, s2.Name); c != 0 { - return c + if r := strings.Compare(s1.Name, s2.Name); r != 0 { + return r } // Fall back to symbol IDs. This is a last resort that should happen only when symbols have // no declaration and duplicate names. return int(ast.GetSymbolId(s1)) - int(ast.GetSymbolId(s2)) } -func compareNodes(n1, n2 *ast.Node) int { +func (c *Checker) compareNodes(n1, n2 *ast.Node) int { if n1 == n2 { return 0 } @@ -1620,13 +1620,13 @@ func compareNodes(n1, n2 *ast.Node) int { if n2 == nil { return -1 } - f1 := ast.GetSourceFileOfNode(n1) - f2 := ast.GetSourceFileOfNode(n2) + f1 := c.fileIndexMap[ast.GetSourceFileOfNode(n1)] + f2 := c.fileIndexMap[ast.GetSourceFileOfNode(n2)] if f1 != f2 { - // Compare the full paths (no two files should have the same full path) - return strings.Compare(string(f1.Path()), string(f2.Path())) + // Order by index of file in the containing program + return f1 - f2 } - // In the same file, compare source positions + // In the same file, order by source position return n1.Pos() - n2.Pos() } @@ -1640,6 +1640,9 @@ func compareTypes(t1, t2 *Type) int { if t2 == nil { return 1 } + if t1.checker != t2.checker { + panic("Cannot compare types from different checkers") + } // First sort in order of increasing type flags values. if c := getSortOrderFlags(t1) - getSortOrderFlags(t2); c != 0 { return c @@ -1654,7 +1657,7 @@ func compareTypes(t1, t2 *Type) int { // Only distinguished by type IDs, handled below. case t1.flags&TypeFlagsObject != 0: // Order unnamed or identically named object types by symbol. - if c := compareSymbols(t1.symbol, t2.symbol); c != 0 { + if c := t1.checker.compareSymbols(t1.symbol, t2.symbol); c != 0 { return c } // When object types have the same or no symbol, order by kind. We order type references before other kinds. @@ -1675,7 +1678,7 @@ func compareTypes(t1, t2 *Type) int { } } else { // Deferred type references with the same target are ordered by the source location of the reference. - if c := compareNodes(r1.node, r2.node); c != 0 { + if c := t1.checker.compareNodes(r1.node, r2.node); c != 0 { return c } // Instantiations of the same deferred type reference are ordered by their associated type mappers @@ -1723,7 +1726,7 @@ func compareTypes(t1, t2 *Type) int { } case t1.flags&(TypeFlagsEnumLiteral|TypeFlagsUniqueESSymbol) != 0: // Enum members are ordered by their symbol (and thus their declaration order). - if c := compareSymbols(t1.symbol, t2.symbol); c != 0 { + if c := t1.checker.compareSymbols(t1.symbol, t2.symbol); c != 0 { return c } case t1.flags&TypeFlagsStringLiteral != 0: @@ -1746,7 +1749,7 @@ func compareTypes(t1, t2 *Type) int { return -1 } case t1.flags&TypeFlagsTypeParameter != 0: - if c := compareSymbols(t1.symbol, t2.symbol); c != 0 { + if c := t1.checker.compareSymbols(t1.symbol, t2.symbol); c != 0 { return c } case t1.flags&TypeFlagsIndex != 0: @@ -1764,7 +1767,7 @@ func compareTypes(t1, t2 *Type) int { return c } case t1.flags&TypeFlagsConditional != 0: - if c := compareNodes(t1.AsConditionalType().root.node.AsNode(), t2.AsConditionalType().root.node.AsNode()); c != 0 { + if c := t1.checker.compareNodes(t1.AsConditionalType().root.node.AsNode(), t2.AsConditionalType().root.node.AsNode()); c != 0 { return c } if c := compareTypeMappers(t1.AsConditionalType().mapper, t2.AsConditionalType().mapper); c != 0 { From 50dd980f7d6cad1c71872db17f5d426d7fdaef4b Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 7 Jan 2025 17:02:23 -0800 Subject: [PATCH 8/9] Use slices.Compare as suggested in CR feedback --- internal/compiler/utilities.go | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/internal/compiler/utilities.go b/internal/compiler/utilities.go index b38a1455f7..9804068e4b 100644 --- a/internal/compiler/utilities.go +++ b/internal/compiler/utilities.go @@ -1781,7 +1781,7 @@ func compareTypes(t1, t2 *Type) int { return c } case t1.flags&TypeFlagsTemplateLiteral != 0: - if c := compareTexts(t1.AsTemplateLiteralType().texts, t2.AsTemplateLiteralType().texts); c != 0 { + if c := slices.Compare(t1.AsTemplateLiteralType().texts, t2.AsTemplateLiteralType().texts); c != 0 { return c } if c := compareTypeLists(t1.AsTemplateLiteralType().types, t2.AsTemplateLiteralType().types); c != 0 { @@ -1885,15 +1885,6 @@ func compareTypeLists(s1, s2 []*Type) int { return 0 } -func compareTexts(s1, s2 []string) int { - for i := range min(len(s1), len(s2)) { - if c := strings.Compare(s1[i], s2[i]); c != 0 { - return c - } - } - return len(s1) - len(s2) -} - func compareTypeMappers(m1, m2 *TypeMapper) int { if m1 == m2 { return 0 From 826175b18d9338fc54e763f7f8d810da1834f86a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 8 Jan 2025 06:16:01 -0800 Subject: [PATCH 9/9] Address CR feedback --- internal/compiler/checker.go | 6 +++--- internal/compiler/utilities.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/compiler/checker.go b/internal/compiler/checker.go index 7b2ea921d5..3490a19259 100644 --- a/internal/compiler/checker.go +++ b/internal/compiler/checker.go @@ -487,7 +487,7 @@ type Checker struct { compilerOptions *core.CompilerOptions files []*ast.SourceFile fileIndexMap map[*ast.SourceFile]int - compareSymbolsFunc func(*ast.Symbol, *ast.Symbol) int + compareSymbols func(*ast.Symbol, *ast.Symbol) int typeCount uint32 symbolCount uint32 totalInstantiationCount uint32 @@ -735,7 +735,7 @@ func NewChecker(program *Program) *Checker { c.compilerOptions = program.compilerOptions c.files = program.files c.fileIndexMap = createFileIndexMap(c.files) - c.compareSymbolsFunc = c.compareSymbols // Closure optimization + c.compareSymbols = c.compareSymbolsWorker // Closure optimization c.languageVersion = c.compilerOptions.GetEmitScriptTarget() c.moduleKind = c.compilerOptions.GetEmitModuleKind() c.legacyDecorators = c.compilerOptions.ExperimentalDecorators == core.TSTrue @@ -908,7 +908,7 @@ func NewChecker(program *Program) *Checker { } func createFileIndexMap(files []*ast.SourceFile) map[*ast.SourceFile]int { - result := make(map[*ast.SourceFile]int) + result := make(map[*ast.SourceFile]int, len(files)) for i, file := range files { result[file] = i } diff --git a/internal/compiler/utilities.go b/internal/compiler/utilities.go index 9804068e4b..f6130aafbc 100644 --- a/internal/compiler/utilities.go +++ b/internal/compiler/utilities.go @@ -1580,10 +1580,10 @@ func createSymbolTable(symbols []*ast.Symbol) ast.SymbolTable { } func (c *Checker) sortSymbols(symbols []*ast.Symbol) { - slices.SortFunc(symbols, c.compareSymbolsFunc) + slices.SortFunc(symbols, c.compareSymbols) } -func (c *Checker) compareSymbols(s1, s2 *ast.Symbol) int { +func (c *Checker) compareSymbolsWorker(s1, s2 *ast.Symbol) int { if s1 == s2 { return 0 }