From 8c5257f791f8d72b1072272f1fb0840de7b03ca9 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Tue, 3 Nov 2026 08:33:17 -0800 Subject: [PATCH 1/9] Make checker results independent of check and query order The checker caches nearly everything it computes, and many results depend on what happened to be cached when they were computed: which file was checked first, which types an editor asked for before requesting diagnostics, whether a file was emitted before it was checked. The check order tests and fuzzer show hundreds of such differences on main. As long as they exist, checkers cannot share results, a cached result cannot be dropped and recomputed later, and concurrent checking needs separate checkers with fixed file assignments to stay deterministic. The goal is a checker that behaves like a pure function with a cache: the answer to any query is the answer of computing it from scratch. The cached state of a checker moves to a memo store, leaving the Checker with only the state of the computation in progress. Every cached result is a memo: a cell, a keyed map, or a check-once set. Reading one records it as a dependency of the computation in progress. Computing a result can produce more than the result: diagnostics, reference marks for unused checks, deferred nodes, required emit helpers. When a query computed a result first, those effects were either reported for code checking never visits or lost when checking reused the result. A query now records them on the result instead, along with the results it reused, and checking replays them, exactly once, when it reuses the result, as if it had computed it. Effects produced outside any memoized computation belong only to the query and are dropped. Circularities are where most of the order dependence lives. A computation that re-enters one in progress sees a provisional state, so whatever is cached until the outermost computation of the cycle finishes depends on where the cycle was entered. Those results are now discarded when it finishes, and recomputed when next requested. The computations in progress when the cycle was detected keep their fallbacks, as each reaches the cycle when computed from scratch too. Results that define identity, such as declared types and instantiations, are kept, so no second, distinct result can exist. Main detects many re-entries silently, through markers and probes rather than the resolution stack; those now go through the resolution stack too, so their cycles are seen. Where a cycle's result depends on which member was entered, the cycle is resolved from a canonical entry, the member declared first, as when checking in program order: cycles through the types of symbols, mutually dependent member tables and module tables, and type parameter defaults. Variances of circular types are measured with TypeScript's rule from the canonical type of each strongly connected component, so that they no longer depend on which type was measured first. Instantiation depth is measured from the start of each memoized computation, and a reused result adds none, so whether a result reaches the depth limit does not depend on what was cached. The count limit, which only stops runaway instantiations, still counts per statement or expression, and starts again for each outermost computation; its error is reported once, where it is reached. Smaller fixes needed for the same reason are part of this change: errors for accessor pairs are reported by each accessor without a flag, type alias type parameters are read through the declared type, name lookups that cannot match a late-bound member do not resolve late-bound members, symbols derived from the same declaration are ordered without ids, awaited types that refer back to themselves fail as a cycle, and relation complexity overflows are reported in every comparison that needs them. --- packages/typescript/enums/objectFlags.d.ts | 1 - packages/typescript/enums/objectFlags.js | 1 - tsc/internal/api/enum_values_generated.go | 1 - tsc/internal/ast/symbol.go | 5 + tsc/internal/ast/utilities.go | 10 + tsc/internal/checker/checker.go | 4868 ++++++++++++----- tsc/internal/checker/checker_test.go | 508 ++ tsc/internal/checker/emitresolver.go | 8 +- tsc/internal/checker/flow.go | 183 +- tsc/internal/checker/grammarchecks.go | 18 +- tsc/internal/checker/inference.go | 57 +- tsc/internal/checker/jsx.go | 172 +- tsc/internal/checker/links.go | 14 +- tsc/internal/checker/memo.go | 2190 ++++++++ tsc/internal/checker/nodebuilder_hover.go | 2 +- tsc/internal/checker/nodebuilderimpl.go | 32 +- tsc/internal/checker/printer.go | 6 +- tsc/internal/checker/relater.go | 837 ++- tsc/internal/checker/resolutions.go | 321 ++ tsc/internal/checker/services.go | 17 +- tsc/internal/checker/symbolaccessibility.go | 112 +- tsc/internal/checker/tracer.go | 13 +- tsc/internal/checker/types.go | 235 +- tsc/internal/checker/types_test.go | 127 +- tsc/internal/checker/utilities.go | 19 +- tsc/internal/execute/tsctests/tsc_test.go | 6 +- tsc/internal/testrunner/compiler_runner.go | 13 - ...entifier(checkorder=typesfirst).errors.txt | 5 +- ...waitedCycle(checkorder=default).errors.txt | 5 +- ...waitedCycle(checkorder=reverse).errors.txt | 5 +- ...tedCycle(checkorder=typesfirst).errors.txt | 5 +- ...rConstraint(checkorder=default).errors.txt | 1 - ...rConstraint(checkorder=reverse).errors.txt | 1 - ...nstraint(checkorder=typesfirst).errors.txt | 1 - ...larDefaults(checkorder=default).errors.txt | 10 +- ...CircularDefaults(checkorder=default).types | 4 +- ...arDefaults(checkorder=isolated).errors.txt | 15 + ...ircularDefaults(checkorder=isolated).types | 4 +- ...Defaults(checkorder=typesfirst).errors.txt | 10 +- ...cularDefaults(checkorder=typesfirst).types | 4 +- ...BeforeCheck(checkorder=default).errors.txt | 6 +- ...eforeCheck(checkorder=isolated).errors.txt | 4 +- ...eckOrderSpreadCycle(checkorder=default).js | 15 +- ...OrderSpreadCycle(checkorder=default).types | 50 +- ...ckOrderSpreadCycle(checkorder=isolated).js | 16 +- ...rderSpreadCycle(checkorder=isolated).types | 50 +- ...eckOrderSpreadCycle(checkorder=reverse).js | 15 +- ...OrderSpreadCycle(checkorder=reverse).types | 50 +- ...OrderSpreadCycle(checkorder=typesfirst).js | 15 +- ...erSpreadCycle(checkorder=typesfirst).types | 50 +- ...ationErrors(checkorder=default).errors.txt | 13 +- ...ationErrors(checkorder=reverse).errors.txt | 13 +- ...onErrors(checkorder=typesfirst).errors.txt | 18 +- ...orAnnotations(checkorder=typesfirst).types | 8 +- ...larIndexedPropertyInstantiation.errors.txt | 5 +- ...TypeNoCrash(checkorder=default).errors.txt | 5 +- ...eNoCrash(checkorder=typesfirst).errors.txt | 5 +- ...ReturnTypeSubtypeReductionOrder.errors.txt | 12 +- ...cularReturnTypeSubtypeReductionOrder.types | 28 +- ...hCallbackArgument(strict=false).errors.txt | 38 + ...hroughCallbackArgument(strict=false).types | 24 +- ...ghCallbackArgument(strict=true).errors.txt | 30 +- ...ThroughCallbackArgument(strict=true).types | 24 +- ...verseMapped(checkorder=default).errors.txt | 40 + ...kReverseMapped(checkorder=default).symbols | 8 - ...unkReverseMapped(checkorder=default).types | 54 +- ...seMapped(checkorder=typesfirst).errors.txt | 40 + ...verseMapped(checkorder=typesfirst).symbols | 8 - ...ReverseMapped(checkorder=typesfirst).types | 54 +- ...lassVarianceResolveCircularity2.errors.txt | 24 - .../classVarianceResolveCircularity2.types | 2 +- ...ernQueryOrder(checkorder=typesfirst).types | 10 +- .../contextualCheckInDiscardedCycle.symbols | 150 + .../contextualCheckInDiscardedCycle.types | 117 + ...textualCheckReentryAfterParameters.symbols | 181 + ...ontextualCheckReentryAfterParameters.types | 190 + .../contextualReturnTypeInSpreadArgument.js | 36 + ...ntextualReturnTypeInSpreadArgument.symbols | 59 + ...contextualReturnTypeInSpreadArgument.types | 63 + ...nalModuleContainerIndexDeclarationOrder.js | 2 + ...ModuleContainerIndexDeclarationOrder.types | 4 +- ...leSpread(checkorder=typesfirst).errors.txt | 10 +- ...ctRecursiveMappedTypeConstraint.errors.txt | 2 - ...andoIndexedAssignmentDiagnostic.errors.txt | 25 + .../jsExpandoIndexedAssignmentDiagnostic.js | 20 + ...ExpandoIndexedAssignmentDiagnostic.symbols | 33 + ...jsExpandoIndexedAssignmentDiagnostic.types | 42 + ...aComment(checkorder=typesfirst).errors.txt | 6 +- ...dsRecord(checkorder=typesfirst).errors.txt | 12 - ...ExtendsRecord(checkorder=typesfirst).types | 2 +- ...enericExtendingClassDoubleLayer.errors.txt | 5 +- .../lateBoundMutualMemberTables.errors.txt | 26 + .../compiler/lateBoundMutualMemberTables.js | 33 + .../lateBoundMutualMemberTables.symbols | 71 + .../lateBoundMutualMemberTables.types | 69 + ...tantiations(checkorder=default).errors.txt | 5 +- ...tiations(checkorder=typesfirst).errors.txt | 10 +- ...embersResolutionOrderExportStar.errors.txt | 6 +- ...mespaceMembersResolutionOrderExportStar.js | 28 +- ...ceMembersResolutionOrderExportStar.symbols | 4 +- ...paceMembersResolutionOrderExportStar.types | 120 +- ...ationEmitExportEqualsQualifiedNameOrder.js | 2 + ...onEmitExportEqualsQualifiedNameOrder.types | 4 +- ...onEmitExternalModuleContainerIndexOrder.js | 2 + ...mitExternalModuleContainerIndexOrder.types | 4 +- ...mitLateBoundIndexSignatureSiblingsOrder.js | 2 + ...LateBoundIndexSignatureSiblingsOrder.types | 4 +- ...oFlakyDeclarationEmitRenamedExportOrder.js | 2 + ...akyDeclarationEmitRenamedExportOrder.types | 4 +- ...akyDeclarationEmitSharedCheckerOrder.types | 6 +- ...akyDeclarationEmitUmdGlobalAliasesOrder.js | 2 + ...DeclarationEmitUmdGlobalAliasesOrder.types | 4 +- ...larationEmitVariableMatchesInScopeOrder.js | 2 + ...ationEmitVariableMatchesInScopeOrder.types | 4 +- ...nosticsExportStarCollisionOrder.errors.txt | 4 +- ...yDiagnosticsExportStarCollisionOrder.types | 2 +- ...rtStarDeclarationCollisionOrder.errors.txt | 4 +- ...sExportStarDeclarationCollisionOrder.types | 2 +- ...NamespaceMembersResolutionOrder.errors.txt | 6 +- ...gnosticsNamespaceMembersResolutionOrder.js | 28 +- ...icsNamespaceMembersResolutionOrder.symbols | 4 +- ...sticsNamespaceMembersResolutionOrder.types | 120 +- ...sResolutionOrderReexportedTypes.errors.txt | 6 +- ...ceMembersResolutionOrderReexportedTypes.js | 28 +- ...bersResolutionOrderReexportedTypes.symbols | 4 +- ...embersResolutionOrderReexportedTypes.types | 132 +- ...agnosticsNonExportedMemberOrder.errors.txt | 4 +- ...akyDiagnosticsNonExportedMemberOrder.types | 2 +- ...qualsWithOtherExportsCheckOrder.errors.txt | 5 +- ...rtEqualsWithOtherExportsCheckOrder.symbols | 2 + ...portEqualsWithOtherExportsCheckOrder.types | 4 +- ...ExportEqualsWithOtherExportsImportOrder.js | 2 + ...ortEqualsWithOtherExportsImportOrder.types | 4 +- ...lexityError(checkorder=default).errors.txt | 19 +- ...ityError(checkorder=typesfirst).errors.txt | 19 +- .../relationDeeplyNestedCheckOrder.errors.txt | 32 - ...ssiveLength(checkorder=default).errors.txt | 35 +- ...veLength(checkorder=typesfirst).errors.txt | 31 +- ...nstraint(checkorder=typesfirst).errors.txt | 1 - ...ameterWithInvalidConstraintType.errors.txt | 1 - ...aration1(checkorder=typesfirst).errors.txt | 5 +- .../varianceCircularConsistency.errors.txt | 24 +- ...rianceCycleDiscardedDiagnostics.errors.txt | 6 +- .../varianceCycleDiscardedDiagnostics.symbols | 2 - .../varianceCycleDiscardedDiagnostics.types | 6 +- ...aration1(checkorder=typesfirst).errors.txt | 5 +- ...nDeclaration1(checkorder=typesfirst).types | 2 +- ...ortTag13(checkorder=typesfirst).errors.txt | 5 +- ...hadowing(checkorder=typesfirst).errors.txt | 2 +- .../recursiveMappedTypes.errors.txt | 14 +- .../templateLiteralTypes1.errors.txt | 13 +- ...stics-produced-during-semantic-checking.js | 70 +- ...ive-mapped-type-after-comment-only-edit.js | 118 +- ...nly-mapped-type-after-comment-only-edit.js | 118 +- .../contextualCheckInDiscardedCycle.ts | 39 + .../contextualCheckReentryAfterParameters.ts | 41 + .../contextualReturnTypeInSpreadArgument.ts | 18 + .../jsExpandoIndexedAssignmentDiagnostic.ts | 13 + .../compiler/lateBoundMutualMemberTables.ts | 20 + 159 files changed, 9930 insertions(+), 3045 deletions(-) create mode 100644 tsc/internal/checker/memo.go create mode 100644 tsc/internal/checker/resolutions.go create mode 100644 tsc/testdata/baselines/reference/compiler/checkOrderCircularDefaults(checkorder=isolated).errors.txt create mode 100644 tsc/testdata/baselines/reference/compiler/circularReturnTypeThroughCallbackArgument(strict=false).errors.txt create mode 100644 tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=default).errors.txt create mode 100644 tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=typesfirst).errors.txt delete mode 100644 tsc/testdata/baselines/reference/compiler/classVarianceResolveCircularity2.errors.txt create mode 100644 tsc/testdata/baselines/reference/compiler/contextualCheckInDiscardedCycle.symbols create mode 100644 tsc/testdata/baselines/reference/compiler/contextualCheckInDiscardedCycle.types create mode 100644 tsc/testdata/baselines/reference/compiler/contextualCheckReentryAfterParameters.symbols create mode 100644 tsc/testdata/baselines/reference/compiler/contextualCheckReentryAfterParameters.types create mode 100644 tsc/testdata/baselines/reference/compiler/contextualReturnTypeInSpreadArgument.js create mode 100644 tsc/testdata/baselines/reference/compiler/contextualReturnTypeInSpreadArgument.symbols create mode 100644 tsc/testdata/baselines/reference/compiler/contextualReturnTypeInSpreadArgument.types create mode 100644 tsc/testdata/baselines/reference/compiler/jsExpandoIndexedAssignmentDiagnostic.errors.txt create mode 100644 tsc/testdata/baselines/reference/compiler/jsExpandoIndexedAssignmentDiagnostic.js create mode 100644 tsc/testdata/baselines/reference/compiler/jsExpandoIndexedAssignmentDiagnostic.symbols create mode 100644 tsc/testdata/baselines/reference/compiler/jsExpandoIndexedAssignmentDiagnostic.types delete mode 100644 tsc/testdata/baselines/reference/compiler/jsxIntrinsicElementsExtendsRecord(checkorder=typesfirst).errors.txt create mode 100644 tsc/testdata/baselines/reference/compiler/lateBoundMutualMemberTables.errors.txt create mode 100644 tsc/testdata/baselines/reference/compiler/lateBoundMutualMemberTables.js create mode 100644 tsc/testdata/baselines/reference/compiler/lateBoundMutualMemberTables.symbols create mode 100644 tsc/testdata/baselines/reference/compiler/lateBoundMutualMemberTables.types delete mode 100644 tsc/testdata/baselines/reference/compiler/relationDeeplyNestedCheckOrder.errors.txt create mode 100644 tsc/testdata/tests/cases/compiler/contextualCheckInDiscardedCycle.ts create mode 100644 tsc/testdata/tests/cases/compiler/contextualCheckReentryAfterParameters.ts create mode 100644 tsc/testdata/tests/cases/compiler/contextualReturnTypeInSpreadArgument.ts create mode 100644 tsc/testdata/tests/cases/compiler/jsExpandoIndexedAssignmentDiagnostic.ts create mode 100644 tsc/testdata/tests/cases/compiler/lateBoundMutualMemberTables.ts diff --git a/packages/typescript/enums/objectFlags.d.ts b/packages/typescript/enums/objectFlags.d.ts index fd17f37c6fc0b..1b0a911b1a531 100644 --- a/packages/typescript/enums/objectFlags.d.ts +++ b/packages/typescript/enums/objectFlags.d.ts @@ -34,7 +34,6 @@ export declare enum ObjectFlags { ContainsSpread = 1 << 22, ObjectRestType = 1 << 23, IsClassInstanceClone = 1 << 26, - IdenticalBaseTypeCalculated = 1 << 27, IdenticalBaseTypeExists = 1 << 28, FromTypeNode = 1 << 29, IsGenericTypeComputed = 1 << 22, diff --git a/packages/typescript/enums/objectFlags.js b/packages/typescript/enums/objectFlags.js index 013bee411f001..d59fc5f642370 100644 --- a/packages/typescript/enums/objectFlags.js +++ b/packages/typescript/enums/objectFlags.js @@ -34,7 +34,6 @@ export var ObjectFlags; ObjectFlags[ObjectFlags["ContainsSpread"] = 4194304] = "ContainsSpread"; ObjectFlags[ObjectFlags["ObjectRestType"] = 8388608] = "ObjectRestType"; ObjectFlags[ObjectFlags["IsClassInstanceClone"] = 67108864] = "IsClassInstanceClone"; - ObjectFlags[ObjectFlags["IdenticalBaseTypeCalculated"] = 134217728] = "IdenticalBaseTypeCalculated"; ObjectFlags[ObjectFlags["IdenticalBaseTypeExists"] = 268435456] = "IdenticalBaseTypeExists"; ObjectFlags[ObjectFlags["FromTypeNode"] = 536870912] = "FromTypeNode"; ObjectFlags[ObjectFlags["IsGenericTypeComputed"] = 4194304] = "IsGenericTypeComputed"; diff --git a/tsc/internal/api/enum_values_generated.go b/tsc/internal/api/enum_values_generated.go index 1697a479539a5..b223dab0403fb 100644 --- a/tsc/internal/api/enum_values_generated.go +++ b/tsc/internal/api/enum_values_generated.go @@ -242,7 +242,6 @@ func main() { "ContainsSpread": toInt32(checker.ObjectFlagsContainsSpread), "ObjectRestType": toInt32(checker.ObjectFlagsObjectRestType), "IsClassInstanceClone": toInt32(checker.ObjectFlagsIsClassInstanceClone), - "IdenticalBaseTypeCalculated": toInt32(checker.ObjectFlagsIdenticalBaseTypeCalculated), "IdenticalBaseTypeExists": toInt32(checker.ObjectFlagsIdenticalBaseTypeExists), "FromTypeNode": toInt32(checker.ObjectFlagsFromTypeNode), "IsGenericTypeComputed": toInt32(checker.ObjectFlagsIsGenericTypeComputed), diff --git a/tsc/internal/ast/symbol.go b/tsc/internal/ast/symbol.go index 7c8e60e921d55..28862ff494d3d 100644 --- a/tsc/internal/ast/symbol.go +++ b/tsc/internal/ast/symbol.go @@ -223,6 +223,11 @@ func (m *SymbolMap) SymbolAt(i int) *Symbol { return symbol } +// At returns the name and symbol of the i-th entry, in insertion order. +func (m *SymbolMap) At(i int) (string, *Symbol) { + return m.KeyAt(i), m.SymbolAt(i) +} + func (m *SymbolMap) Find(key string) int { if m == nil { return -1 diff --git a/tsc/internal/ast/utilities.go b/tsc/internal/ast/utilities.go index f178903372dc2..d029159e971d3 100644 --- a/tsc/internal/ast/utilities.go +++ b/tsc/internal/ast/utilities.go @@ -33,6 +33,16 @@ func GetNodeId(node *Node) NodeId { return NodeId(id) } +// PeekNodeId returns the id of node, or 0 if it has none yet. +func PeekNodeId(node *Node) NodeId { + return NodeId(node.id.Load()) +} + +// PeekSymbolId returns the id of symbol, or 0 if it has none yet. +func PeekSymbolId(symbol *Symbol) SymbolId { + return SymbolId(symbol.id.Load()) +} + func GetSymbolId(symbol *Symbol) SymbolId { id := symbol.id.Load() if id == 0 { diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index ec824a2f51036..c199490f34389 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -1,6 +1,7 @@ package checker import ( + "cmp" "context" "encoding/binary" "fmt" @@ -66,12 +67,33 @@ const ( TypeSystemPropertyNameInitializerIsUndefined TypeSystemPropertyNameAliasTarget TypeSystemPropertyNameComputedPropertyName + TypeSystemPropertyNameContextChecked + TypeSystemPropertyNameVariances + TypeSystemPropertyNameStructuredMembers + TypeSystemPropertyNameCurrentNode + TypeSystemPropertyNameSimplifiedForReading + TypeSystemPropertyNameSimplifiedForWriting + TypeSystemPropertyNameDefaultType + TypeSystemPropertyNameAwaitedType + TypeSystemPropertyNameExplicitType + TypeSystemPropertyNameBindingElementParent + TypeSystemPropertyNameCallResolution + TypeSystemPropertyNameAssertionOperand + TypeSystemPropertyNamePatternInitializer ) type TypeResolution struct { - target TypeSystemEntity + target TypeSystemEntity + // canonical is the member of a cycle headed by this resolution that is + // resolved first; see observeCircularHead. + canonical TypeSystemEntity propertyName TypeSystemPropertyName result bool + // restartHidden records that a cycle entered below this resolution of a + // module table passes through it; see resolveModuleTableMembers. + restartHidden bool + // participant records that the resolution is in cycleParticipants. + participant bool } // ContextualInfo @@ -243,14 +265,6 @@ type PropertiesTypesKey struct { includeOrigin bool } -// NonExistentPropertyKey - -type NonExistentPropertyKey struct { - propNode *ast.Node - containingType *Type - isUncheckedJS bool -} - // FlowLoopKey type FlowLoopKey struct { @@ -258,6 +272,10 @@ type FlowLoopKey struct { refKey CacheHashKey } +type flowLoopCacheEntry struct { + t *Type +} + type FlowLoopInfo struct { key FlowLoopKey types []*Type @@ -552,6 +570,15 @@ type WideningContext struct { type VarianceStackEntry struct { symbol *ast.Symbol typeParameters []*Type + resolution int // Index in Checker.resolutions of the resolution of the measurement + prevStackIndex int32 // varianceStackIndex of symbol before the measurement + // The lowest index in the variance stack of a measurement in progress + // that this one or one it reached needed, and the smallest type in the + // circularity so far, as in Tarjan's algorithm for strongly connected + // components. + low int + min *ast.Symbol + minTypeParameters []*Type } type ResolvingObjectType struct { @@ -594,256 +621,100 @@ type Host interface { var nextCheckerID atomic.Uint32 +// Checker is a checker: the per-computation state (resolution stacks, scratch +// buffers, allocation arenas, and closures bound to this checker) layered +// over a memoStore that holds all memoized, shareable state. Multiple +// checkers may share one store; see Clone. type Checker struct { - id uint32 - program Program - compilerOptions *core.CompilerOptions - files []*ast.SourceFile - fileIndexMap map[*ast.SourceFile]int - compareSymbols func(*ast.Symbol, *ast.Symbol) int - compareSymbolChains func([]*ast.Symbol, []*ast.Symbol) int - TypeCount uint32 - SymbolCount uint32 - SignatureCount uint32 - TotalInstantiationCount uint32 - instantiationCount uint32 - memberLayoutCount uint32 - instantiationStack []*Type - conditionalConstraintDepth uint32 - inlineLevel int - serializationLevel int - currentNode *ast.Node - varianceTypeParameter *Type - languageVersion core.ScriptTarget - moduleKind core.ModuleKind - moduleResolutionKind core.ModuleResolutionKind - isInferencePartiallyBlocked bool - legacyDecorators bool - emitStandardClassFields bool - strictNullChecks bool - strictFunctionTypes bool - strictBindCallApply bool - strictPropertyInitialization bool - strictBuiltinIteratorReturn bool - noImplicitAny bool - noImplicitThis bool - useUnknownInCatchVariables bool - exactOptionalPropertyTypes bool - canCollectSymbolAliasAccessibilityData bool - wasCanceled bool - arrayVariances []VarianceFlags - globals ast.SymbolTable - evaluate evaluator.Evaluator - stringLiteralTypes map[string]*Type - numberLiteralTypes map[jsnum.Number]*Type - nanType *Type - bigintLiteralTypes map[jsnum.PseudoBigInt]*Type - enumLiteralTypes map[EnumLiteralKey]*Type - enumNaNLiteralTypes map[*ast.Symbol]*Type - indexedAccessTypes map[CacheHashKey]*Type - templateLiteralTypes map[CacheHashKey]*Type - stringMappingTypes map[StringMappingKey]*Type - uniqueESSymbolTypes map[*ast.Symbol]*Type - thisExpandoKinds map[*ast.Symbol]thisAssignmentDeclarationKind - thisExpandoLocations map[*ast.Symbol]*ast.Node - subtypeReductionCache map[CacheHashKey][]*Type - cachedTypes map[CachedTypeKey]*Type - cachedSignatures map[CachedSignatureKey]*Signature - undefinedProperties map[*ast.Symbol]*ast.Symbol - narrowedTypes map[NarrowedTypeKey]*Type - assignmentReducedTypes map[AssignmentReducedKey]*Type - discriminatedContextualTypes map[DiscriminatedContextualTypeKey]*Type - instantiationExpressionTypes map[InstantiationExpressionKey]*Type - substitutionTypes map[SubstitutionTypeKey]*Type - reverseMappedCache map[ReverseMappedTypeKey]*Type - reverseHomomorphicMappedCache map[ReverseMappedTypeKey]*Type - iterationTypesCache map[IterationTypesKey]IterationTypes - memberLayoutCache map[CacheHashKey]*MemberLayout - markerTypes collections.Set[*Type] - resolvingExplicitTypeOfSymbol collections.Set[*ast.Symbol] - resolvingAssertionOperands collections.Set[*ast.Node] - constructorOrFunctionChecked collections.Set[*ast.Symbol] - undefinedSymbol *ast.Symbol - argumentsSymbol *ast.Symbol - requireSymbol *ast.Symbol - unknownSymbol *ast.Symbol - unresolvedSymbols map[string]*ast.Symbol - errorTypes map[CacheHashKey]*Type - moduleSymbols map[*ast.Node]*ast.Symbol - globalThisSymbol *ast.Symbol - symbolTableAliasCache map[symbolTableID][]*ast.Symbol - classExpressionNameTables map[ast.NodeId]ast.SymbolTable - resolveName func(location *ast.Node, name string, meaning ast.SymbolFlags, nameNotFoundMessage *diagnostics.Message, isUse bool, excludeGlobals bool) *ast.Symbol - resolveNameForSymbolSuggestion func(location *ast.Node, name string, meaning ast.SymbolFlags, nameNotFoundMessage *diagnostics.Message, isUse bool, excludeGlobals bool) *ast.Symbol - tupleTypes map[CacheHashKey]*Type - unionTypes map[CacheHashKey]*Type - objectLiteralTypes map[CacheHashKey]*Type - spreadTypes map[CacheHashKey]*Type - objectContentTypes map[CacheHashKey]*Type - spreadSymbols map[spreadSymbolKey]*ast.Symbol - symbolsWithType map[symbolWithTypeKey]*ast.Symbol - paddedProperties map[symbolWithTypeKey]*ast.Symbol - bindingPatternTypes map[CacheHashKey]*Type - unionOfUnionTypes map[UnionOfUnionKey]*Type - intersectionTypes map[CacheHashKey]*Type - intersectionContentTypes map[CacheHashKey]*Type - propertiesTypes map[PropertiesTypesKey]*Type - diagnostics ast.DiagnosticsCollection - suggestionDiagnostics ast.DiagnosticsCollection - symbolArena core.Arena[ast.Symbol] - symbolWithDataArena core.Arena[ast.SymbolWithData] - symbolExtraArena ast.SymbolExtraArena - signatureArena core.Arena[Signature] - signatureExtraArena core.Arena[signatureExtra] - indexInfoArena core.Arena[IndexInfo] - literalTypeArena core.Arena[LiteralType] - objectTypeArena core.Arena[ObjectType] - typeReferenceArena core.Arena[TypeReference] - interfaceTypeArena core.Arena[InterfaceType] - mappedTypeArena core.Arena[MappedType] - unionTypeArena core.Arena[UnionType] - intersectionTypeArena core.Arena[IntersectionType] - typeParameterArena core.Arena[TypeParameter] - indexedAccessTypeArena core.Arena[IndexedAccessType] - conditionalTypeArena core.Arena[ConditionalType] - typeListArena core.Arena[*Type] - outerTypeParameterListArena core.Arena[[]*Type] - valueSymbolExtraArena core.Arena[valueSymbolExtra] - structuredTypeExtraArena core.Arena[structuredTypeExtra] - lazyStateArena core.Arena[LazyState] - memberLayoutArena core.Arena[MemberLayout] - layoutMembers ast.SymbolTable - layoutBaseIndices []int32 - freeTypeSets *temporaryTypeSet - freePropertyCounts *temporaryPropertyCounts - mergedSymbols symbolLinkStore[*ast.Symbol] - mergedExportsChecked collections.Set[*ast.Symbol] - factory ast.NodeFactory - nodeLinks core.LinkStore[*ast.Node, NodeLinks] - signatureLinks nodeLinkStore[SignatureLinks] - symbolNodeLinks nodeLinkStore[SymbolNodeLinks] - typeNodeLinks nodeLinkStore[TypeNodeLinks] - enumMemberLinks core.LinkStore[*ast.Node, EnumMemberLinks] - arrayLiteralLinks core.LinkStore[*ast.Node, ArrayLiteralLinks] - switchStatementLinks core.LinkStore[*ast.Node, SwitchStatementLinks] - jsxElementLinks core.LinkStore[*ast.Node, JsxElementLinks] - computedNameLinks core.LinkStore[*ast.Node, ComputedNameNodeLinks] - symbolReferenceLinks core.LinkStore[*ast.Symbol, SymbolReferenceLinks] - valueSymbolLinks symbolLinkStore[ValueSymbolLinks] - mappedSymbolLinks core.LinkStore[*ast.Symbol, MappedSymbolLinks] - deferredSymbolLinks core.LinkStore[*ast.Symbol, DeferredSymbolLinks] - aliasSymbolLinks core.LinkStore[*ast.Symbol, AliasSymbolLinks] - moduleSymbolLinks core.LinkStore[*ast.Symbol, ModuleSymbolLinks] - lateBoundLinks core.LinkStore[*ast.Symbol, LateBoundLinks] - exportTypeLinks core.LinkStore[*ast.Symbol, ExportTypeLinks] - membersAndExportsLinks core.LinkStore[*ast.Symbol, MembersAndExportsLinks] - typeAliasLinks core.LinkStore[*ast.Symbol, TypeAliasLinks] - declaredTypeLinks core.LinkStore[*ast.Symbol, DeclaredTypeLinks] - spreadLinks core.LinkStore[*ast.Symbol, SpreadLinks] - varianceLinks core.LinkStore[*ast.Symbol, VarianceLinks] - ReverseMappedSymbolLinks core.LinkStore[*ast.Symbol, ReverseMappedSymbolLinks] - markedAssignmentSymbolLinks core.LinkStore[*ast.Symbol, MarkedAssignmentSymbolLinks] - symbolContainerLinks core.LinkStore[*ast.Symbol, ContainingSymbolLinks] - externalModuleContainers *externalModuleContainerIndex - sourceFileLinks core.LinkStore[*ast.SourceFile, SourceFileLinks] - regExpScanner *scanner.Scanner - patternForType map[*Type]*ast.Node - contextFreeTypes map[*ast.Node]*Type - anyType *Type - autoType *Type - wildcardType *Type - blockedStringType *Type - errorType *Type - unresolvedType *Type - nonInferrableAnyType *Type - intrinsicMarkerType *Type - unknownType *Type - undefinedType *Type - undefinedWideningType *Type - missingType *Type - undefinedOrMissingType *Type - optionalType *Type - nullType *Type - nullWideningType *Type - stringType *Type - numberType *Type - bigintType *Type - regularFalseType *Type - falseType *Type - regularTrueType *Type - trueType *Type - booleanType *Type - esSymbolType *Type - voidType *Type - neverType *Type - silentNeverType *Type - implicitNeverType *Type - unreachableNeverType *Type - nonPrimitiveType *Type - stringOrNumberType *Type - stringNumberSymbolType *Type - numberOrBigIntType *Type - templateConstraintType *Type - numericStringType *Type - uniqueLiteralType *Type - uniqueLiteralMapper *TypeMapper - reliabilityFlags RelationComparisonResult - reportUnreliableMapper *TypeMapper - reportUnmeasurableMapper *TypeMapper - restrictiveMapper *TypeMapper - permissiveMapper *TypeMapper - emptyObjectType *Type - emptyJsxObjectType *Type - emptyFreshJsxObjectType *Type - emptyTypeLiteralType *Type - unknownEmptyObjectType *Type - unknownUnionType *Type - emptyGenericType *Type - anyFunctionType *Type - noConstraintType *Type - circularConstraintType *Type - resolvingDefaultType *Type - markerSuperType *Type - markerSubType *Type - markerOtherType *Type - markerSuperTypeForCheck *Type - markerSubTypeForCheck *Type - noTypePredicate *TypePredicate - anySignature *Signature - unknownSignature *Signature - resolvingSignature *Signature - silentNeverSignature *Signature - cachedArgumentsReferenced map[*ast.Node]bool - enumNumberIndexInfo *IndexInfo - anyBaseTypeIndexInfo *IndexInfo - patternAmbientModules []*ast.PatternAmbientModule - patternAmbientModuleAugmentations ast.SymbolTable - patternAmbientModuleAugmentationTargets ast.SymbolTable - moduleImportAttributesTypes map[*ast.Symbol]*Type - globalObjectType *Type - globalFunctionType *Type - globalCallableFunctionType *Type - globalNewableFunctionType *Type - globalArrayType *Type - globalReadonlyArrayType *Type - globalStringType *Type - globalNumberType *Type - globalBooleanType *Type - globalRegExpType *Type - globalThisType *Type - anyArrayType *Type - autoArrayType *Type - anyReadonlyArrayType *Type - deferredGlobalImportMetaExpressionType *Type - contextualBindingPatterns []*ast.Node - emptyStringType *Type - zeroType *Type - zeroBigIntType *Type - typeofType *Type - typeResolutions []TypeResolution - resolutionStart int + *memoStore + compareSymbols func(*ast.Symbol, *ast.Symbol) int + compareSymbolChains func([]*ast.Symbol, []*ast.Symbol) int + globalIDs map[globalKey]globalID + awaitedTypeErrors uint32 // Errors computing awaited types have reported or would report; see getAwaitedTypeNoAliasEx + resolutionSerials uint32 + resolutions []resolution + instantiationStack []*Type + conditionalConstraintDepth depthLimit + inlineLevel depthLimit + serializationLevel depthLimit + currentNode *ast.Node + currentNodeResolution int // 1-based index in resolutions of the resolution of currentNode, or 0; see enterCurrentNode + querySave savedCurrentNode // Saved by the query in progress made outside of checking; see startEffects + inQuery bool + varianceTypeParameter *Type + // The parent last looked up in parentLateBindableKinds and its kinds, as + // members of one parent are often looked up together. + lastLateBindableParent *ast.Symbol + lastLateBindableKinds uint8 + isInferencePartiallyBlocked bool + wasCanceled bool + buildingExternalModuleContainers bool + evaluate evaluator.Evaluator + resolveName func(location *ast.Node, name string, meaning ast.SymbolFlags, nameNotFoundMessage *diagnostics.Message, isUse bool, excludeGlobals bool) *ast.Symbol + resolveNameForSymbolSuggestion func(location *ast.Node, name string, meaning ast.SymbolFlags, nameNotFoundMessage *diagnostics.Message, isUse bool, excludeGlobals bool) *ast.Symbol + symbolArena core.Arena[ast.Symbol] + symbolWithDataArena core.Arena[ast.SymbolWithData] + symbolExtraArena ast.SymbolExtraArena + signatureArena core.Arena[Signature] + signatureExtraArena core.Arena[signatureExtra] + indexInfoArena core.Arena[IndexInfo] + literalTypeArena core.Arena[LiteralType] + objectTypeArena core.Arena[ObjectType] + typeReferenceArena core.Arena[TypeReference] + interfaceTypeArena core.Arena[InterfaceType] + mappedTypeArena core.Arena[MappedType] + unionTypeArena core.Arena[UnionType] + intersectionTypeArena core.Arena[IntersectionType] + typeParameterArena core.Arena[TypeParameter] + indexedAccessTypeArena core.Arena[IndexedAccessType] + conditionalTypeArena core.Arena[ConditionalType] + typeListArena core.Arena[*Type] + outerTypeParameterListArena core.Arena[[]*Type] + valueSymbolExtraArena core.Arena[valueSymbolExtra] + structuredTypeExtraArena core.Arena[structuredTypeExtra] + lazyStateArena core.Arena[LazyState] + memberLayoutArena core.Arena[MemberLayout] + layoutMembers ast.SymbolTable + layoutBaseIndices []int32 + freeTypeSets *temporaryTypeSet + freePropertyCounts *temporaryPropertyCounts + factory ast.NodeFactory + regExpScanner *scanner.Scanner + uniqueLiteralMapper *TypeMapper + reliabilityFlags RelationComparisonResult + reportUnreliableMapper *TypeMapper + reportUnmeasurableMapper *TypeMapper + restrictiveMapper *TypeMapper + permissiveMapper *TypeMapper + contextualBindingPatterns []*ast.Node + resolving resolvingTable // Index in resolutions of the outermost computation of each key in progress + typeResolutionTop int // 1-based index in resolutions of the innermost resolution, or 0 + // Like typeResolutionTop, but skipping the type resolutions of current + // nodes, which are many and which nothing searches for. + searchableTop int + resolutionStart int // Index in resolutions below which resolutions are not searched for cycles + visibleStart int // Index in resolutions below which nothing in progress is visible; see yieldCycle + cycleYields []cycleYield // Canonical recomputations in progress; see yieldCycle + circularityStart int // 1-based index in resolutions of the outermost cycle in progress, or 0; see memo.go + currentNodeErrors []*currentNodeError // Errors reported at the current node while collecting; see startCurrentNodeErrors + currentNodeErrorCollectors int // Number of computations collecting currentNodeErrors + currentNodeErrorsCyclic bool // Whether a cycle was observed while collecting currentNodeErrors; see endCurrentNodeErrors + discardLog []discardEntry // Restores what was cached while anything may be discarded; see "Discard log" in memo.go + discardLogTrims int // Number of times trimDiscardLog cleared discardLog + circularityMark int // Length of discard log when the cycle in progress was detected + innerCycles []innerCycle // Cycles detected inside the one in progress; see extendCircularity + flowDepthFailures map[FlowLoopKey]int // Largest depth budget known to be too small for a flow walk from a node; see getTypeAtFlowNode + cycleParticipants []TypeResolution // Computations that observed a cycle in progress + effectsMarkDepth int // 1 + resolution index of the innermost effects mark that may be discarded, or 0 varianceStack []VarianceStackEntry - callResolutionStack []*ast.Node + varianceStackIndex map[*ast.Symbol]int32 // 1 + the index in varianceStack of the measurement of a symbol in progress, if any + varianceCycleRoot int // 1 + index in varianceStack of the outermost measurement re-entered in progress, or 0; see getRecursiveVariances + incompleteVariances int // 1 + resolution index of the outermost measurement whose incomplete variances were observed, or 0; see getRecursiveVariances + measuringVariances bool // The comparison about to start measures variances; see isMarkerTypeAssignableTo + flushBuf []*memoState + resumeBuf []int + deferredAttach []*memoState // See attachState + flushStates *[]*memoState // Collects memo states given effects to replay, if non-nil; see withoutChecking apparentArgumentCount *int lastGetCombinedNodeFlagsNode *ast.Node lastGetCombinedNodeFlagsResult ast.NodeFlags @@ -851,32 +722,24 @@ type Checker struct { lastGetCombinedModifierFlagsResult ast.ModifierFlags freeinferenceState *InferenceState freeFlowState *FlowState - flowLoopCache map[FlowLoopKey]*Type flowLoopStack []FlowLoopInfo + signaturesResolving []signatureResolving // Calls whose resolution is in progress sharedFlows []SharedFlow flowLoopFrameCount uint64 antecedentTypes []*Type flowInvocationCount int - flowTypeCache map[*ast.Node]*Type + flowTypeCache map[*ast.Node]*Type // Types of expressions within the innermost loop analysis lastFlowNode *ast.FlowNode lastFlowNodeReachable bool - flowNodeReachable map[*ast.FlowNode]bool - flowNodePostSuper map[*ast.FlowNode]bool renamedBindingElementsInTypes []*ast.Node contextualInfos []ContextualInfo inferenceContextInfos []InferenceContextInfo - awaitedTypeStack []*Type reverseMappedSourceStack []*Type reverseMappedTargetStack []*Type resolvingObjectTypeStack []ResolvingObjectType reverseExpandingFlags ExpandingFlags + reverseMappedStart int32 freeRelater *Relater - subtypeRelation *Relation - strictSubtypeRelation *Relation - assignableRelation *Relation - comparableRelation *Relation - identityRelation *Relation - enumRelation map[EnumRelationKey]RelationComparisonResult getGlobalESSymbolType func() *Type getGlobalBigIntType func() *Type getGlobalImportMetaType func() *Type @@ -937,38 +800,274 @@ type Checker struct { isStringIndexSignatureOnlyType func(*Type) bool markNodeAssignments func(*ast.Node) bool compareTypesAssignable TypeComparer - emitResolverLinks EmitResolverLinks - _jsxNamespace string - _jsxFactoryEntity *ast.Node skipDirectInferenceNodes collections.Set[*ast.Node] ctx context.Context - packagesMap map[string]bool activeMappers []*TypeMapper - activeTypeMappersCaches []clearableMap[*Type] - ambientModulesOnce sync.Once - ambientModules []*ast.Symbol + activeTypeMappersCaches []clearableMap[activeMapperCacheEntry] withinUnreachableCode bool - reportedUnreachableNodes collections.Set[*ast.Node] - nonExistentProperties collections.Set[NonExistentPropertyKey] - deferredDiagnosticCallbacks []func() typeToStringNodebuilder *NodeBuilder - - mu sync.Mutex - tracer *Tracer // Optional tracer for trace events and type recording (for --generateTrace) + tracer *Tracer // Optional tracer for trace events and type recording (for --generateTrace) + checking bool // Whether side effects such as diagnostics are applied; see memo.go + observingMemos bool // See syncObserving + instantiationCount uint32 // Instantiations since the start of the current statement, expression or outermost computation; see "Instantiation limits" + memoTop int // 1 + index in resolutions of the innermost computation recording effects, or 0; see memo.go +} + +// memoStore holds the memoized state of a checker: interned types, links, +// relation caches, and collected diagnostics. Every checker sharing a store +// observes the same results. +type memoStore struct { + root *Checker // The checker that created this store; owns type back-pointers. + id uint32 + program Program + compilerOptions *core.CompilerOptions + files []*ast.SourceFile + fileIndexMap map[*ast.SourceFile]int + TypeCount uint32 + memberLayoutCount uint32 + SymbolCount uint32 + SignatureCount uint32 + TotalInstantiationCount uint32 + languageVersion core.ScriptTarget + moduleKind core.ModuleKind + moduleResolutionKind core.ModuleResolutionKind + legacyDecorators bool + emitStandardClassFields bool + strictNullChecks bool + strictFunctionTypes bool + strictBindCallApply bool + strictPropertyInitialization bool + strictBuiltinIteratorReturn bool + noImplicitAny bool + noImplicitThis bool + useUnknownInCatchVariables bool + exactOptionalPropertyTypes bool + canCollectSymbolAliasAccessibilityData bool + arrayVariances []VarianceFlags + globals ast.SymbolTable + globalTypes []memo[resolvedGlobal[*Type]] // Indexed by globalID + globalSymbols []memo[resolvedGlobal[*ast.Symbol]] // Indexed by globalID + stringLiteralTypes internMap[string, *Type] + numberLiteralTypes internMap[jsnum.Number, *Type] + nanType *Type + bigintLiteralTypes internMap[jsnum.PseudoBigInt, *Type] + enumLiteralTypes internMap[EnumLiteralKey, *Type] + enumNaNLiteralTypes internMap[*ast.Symbol, *Type] + indexedAccessTypes internMap[CacheHashKey, *Type] + templateLiteralTypes internMap[CacheHashKey, *Type] + stringMappingTypes internMap[StringMappingKey, *Type] + uniqueESSymbolTypes internMap[*ast.Symbol, *Type] + thisExpandos internMap[*ast.Symbol, thisExpando] + subtypeReductionCache memoMap[CacheHashKey, []*Type] + cachedTypes memoMap[CachedTypeKey, *Type] + cachedSignatures memoMap[CachedSignatureKey, *Signature] + undefinedProperties memoMap[*ast.Symbol, *ast.Symbol] + narrowedTypes memoMap[NarrowedTypeKey, *Type] + assignmentReducedTypes memoMap[AssignmentReducedKey, *Type] + discriminatedContextualTypes memoMap[DiscriminatedContextualTypeKey, *Type] + instantiationExpressionTypes internMap[InstantiationExpressionKey, *instantiationExpressionEntry] + substitutionTypes internMap[SubstitutionTypeKey, *Type] + reverseMappedCache memoMap[ReverseMappedTypeKey, *Type] + reverseHomomorphicMappedCache memoMap[ReverseMappedTypeKey, *Type] + iterationTypesCache memoMap[IterationTypesKey, IterationTypes] + memberLayoutCache memoMap[CacheHashKey, *MemberLayout] + constructorOrFunctionChecked memoSet[*ast.Symbol] + typeParametersChecked memoSet[*ast.Symbol] + indexSignaturesChecked memoSet[*ast.Symbol] + interfaceChecked memoSet[*ast.Symbol] + enumChecked memoSet[*ast.Symbol] + moduleExportsChecked memoSet[*ast.Symbol] + assignmentsMarked memoSet[*ast.Node] // Functions and source files whose assignments have been marked + undefinedSymbol *ast.Symbol + argumentsSymbol *ast.Symbol + requireSymbol *ast.Symbol + unknownSymbol *ast.Symbol + unresolvedSymbols internMap[string, *ast.Symbol] + errorTypes internMap[CacheHashKey, *Type] + globalThisSymbol *ast.Symbol + symbolTableAliasCache internMap[symbolTableID, []*ast.Symbol] + classExpressionNameTables internMap[ast.NodeId, ast.SymbolTable] + tupleTypes internMap[CacheHashKey, *Type] + unionTypes internMap[CacheHashKey, *Type] + unionOfUnionTypes memoMap[UnionOfUnionKey, *Type] + intersectionTypes memoMap[CacheHashKey, *Type] + objectLiteralTypes internMap[*ast.Node, []objectLiteralType] + spreadTypes memoMap[CacheHashKey, *Type] + objectContentTypes internMap[CacheHashKey, *Type] + spreadSymbols memoMap[spreadSymbolKey, *ast.Symbol] + symbolsWithType internMap[symbolWithTypeKey, *ast.Symbol] + paddedProperties internMap[symbolWithTypeKey, *ast.Symbol] + bindingPatternTypes internMap[CacheHashKey, *Type] + intersectionContentTypes internMap[CacheHashKey, *Type] + propertiesTypes memoMap[PropertiesTypesKey, *Type] + diagnostics ast.DiagnosticsCollection + suggestionDiagnostics ast.DiagnosticsCollection + mergedSymbols symbolLinkStore[*ast.Symbol] + lateMergedSymbols memoMap[*ast.Symbol, *ast.Symbol] + // getLateBindableKinds of the merged symbol of a parent of members, with + // lateBindableKnown set once known; see getLateMergedSymbol. + parentLateBindableKinds symbolLinkStore[uint8] + // mergesFrozen is set once global merging is done; mergedSymbols is + // immutable from then on. + mergesFrozen bool + mergedExportsChecked memoSet[*ast.Symbol] + nodeLinks core.LinkStore[*ast.Node, NodeLinks] + signatureLinks nodeLinkStore[SignatureLinks] + symbolNodeLinks nodeLinkStore[SymbolNodeLinks] + typeNodeLinks nodeLinkStore[TypeNodeLinks] + enumMemberLinks core.LinkStore[*ast.Node, EnumMemberLinks] + arrayLiteralLinks core.LinkStore[*ast.Node, ArrayLiteralLinks] + switchStatementLinks core.LinkStore[*ast.Node, SwitchStatementLinks] + jsxElementLinks core.LinkStore[*ast.Node, JsxElementLinks] + computedNameLinks core.LinkStore[*ast.Node, ComputedNameNodeLinks] + symbolReferenceLinks core.LinkStore[*ast.Symbol, SymbolReferenceLinks] + valueSymbolLinks symbolLinkStore[ValueSymbolLinks] + mappedSymbolLinks core.LinkStore[*ast.Symbol, MappedSymbolLinks] + deferredSymbolLinks core.LinkStore[*ast.Symbol, DeferredSymbolLinks] + aliasSymbolLinks core.LinkStore[*ast.Symbol, AliasSymbolLinks] + moduleSymbolLinks core.LinkStore[*ast.Symbol, ModuleSymbolLinks] + lateBoundLinks core.LinkStore[*ast.Symbol, LateBoundLinks] + exportTypeLinks core.LinkStore[*ast.Symbol, ExportTypeLinks] + membersAndExportsLinks core.LinkStore[*ast.Symbol, MembersAndExportsLinks] + typeAliasLinks core.LinkStore[*ast.Symbol, TypeAliasLinks] + declaredTypeLinks core.LinkStore[*ast.Symbol, DeclaredTypeLinks] + spreadLinks core.LinkStore[*ast.Symbol, SpreadLinks] + varianceLinks symbolLinkStore[VarianceLinks] + ReverseMappedSymbolLinks core.LinkStore[*ast.Symbol, ReverseMappedSymbolLinks] + markedAssignmentSymbolLinks core.LinkStore[*ast.Symbol, MarkedAssignmentSymbolLinks] + importedContainingModules memoMap[symbolFileKey, []*ast.Symbol] // Modules imported by a file that export a symbol + extendedContainers memoMap[*ast.Symbol, []*ast.Symbol] // Containers (other than the parent) which a symbol is aliased in + accessibleChains memoMap[accessibleChainCacheKey, []*ast.Symbol] // Accessible symbol chains by location + externalModuleContainers memo[*externalModuleContainerIndex] + sourceFileLinks core.LinkStore[*ast.SourceFile, SourceFileLinks] + patternForType internMap[*Type, *ast.Node] + contextFreeTypes memoMap[*ast.Node, *Type] + anyType *Type + autoType *Type + wildcardType *Type + blockedStringType *Type + errorType *Type + unresolvedType *Type + nonInferrableAnyType *Type + intrinsicMarkerType *Type + unknownType *Type + undefinedType *Type + undefinedWideningType *Type + missingType *Type + undefinedOrMissingType *Type + optionalType *Type + nullType *Type + nullWideningType *Type + stringType *Type + numberType *Type + bigintType *Type + regularFalseType *Type + falseType *Type + regularTrueType *Type + trueType *Type + booleanType *Type + esSymbolType *Type + voidType *Type + neverType *Type + silentNeverType *Type + implicitNeverType *Type + unreachableNeverType *Type + nonPrimitiveType *Type + stringOrNumberType *Type + stringNumberSymbolType *Type + numberOrBigIntType *Type + templateConstraintType *Type + numericStringType *Type + uniqueLiteralType *Type + emptyObjectType *Type + emptyJsxObjectType *Type + emptyFreshJsxObjectType *Type + emptyTypeLiteralType *Type + unknownEmptyObjectType *Type + unknownUnionType *Type + emptyGenericType *Type + anyFunctionType *Type + noConstraintType *Type + circularConstraintType *Type + resolvingDefaultType *Type + markerSuperType *Type + markerSubType *Type + markerOtherType *Type + markerSuperTypeForCheck *Type + markerSubTypeForCheck *Type + noTypePredicate *TypePredicate + anySignature *Signature + unknownSignature *Signature + resolvingSignature *Signature + silentNeverSignature *Signature + cachedArgumentsReferenced memoMap[*ast.Node, bool] + enumNumberIndexInfo *IndexInfo + anyBaseTypeIndexInfo *IndexInfo + patternAmbientModules []*ast.PatternAmbientModule + patternAmbientModuleAugmentations ast.SymbolTable + patternAmbientModuleAugmentationTargets ast.SymbolTable + moduleImportAttributesTypes memoMap[*ast.Symbol, *Type] + globalObjectType *Type + globalFunctionType *Type + globalCallableFunctionType *Type + globalNewableFunctionType *Type + globalArrayType *Type + globalReadonlyArrayType *Type + globalStringType *Type + globalNumberType *Type + globalBooleanType *Type + globalRegExpType *Type + globalThisType *Type + anyArrayType *Type + autoArrayType *Type + anyReadonlyArrayType *Type + deferredGlobalImportMetaExpressionType memo[*Type] + emptyStringType *Type + zeroType *Type + zeroBigIntType *Type + typeofType *Type + flowExpressionTypes memoMap[*ast.Node, *Type] + flowLoopCache internMap[FlowLoopKey, *flowLoopCacheEntry] + flowNodeReachable memoMap[*ast.FlowNode, bool] + flowNodePostSuper memoMap[*ast.FlowNode, bool] + subtypeRelation *Relation + strictSubtypeRelation *Relation + assignableRelation *Relation + comparableRelation *Relation + identityRelation *Relation + enumRelation memoMap[EnumRelationKey, RelationComparisonResult] + emitResolverLinks EmitResolverLinks + _jsxNamespace string + _jsxFactoryEntity *ast.Node + packagesMap map[string]bool + ambientModulesOnce sync.Once + ambientModules []*ast.Symbol + reportedUnreachableNodes memoSet[*ast.Node] + unreachableRuns memoMap[*ast.Node, map[*ast.Node]unreachableRun] + checkedRegularExpressions memoSet[*ast.Node] + nonExistentPropertyNodes collections.Set[*ast.Node] // Property names reported as nonexistent + deferredDiagnosticCallbacks []func(c *Checker) + memoStates map[memoKey]*memoState // Memo state of results computed by queries; see memo.go + stateFilter stateKeyFilter // Memo keys that may have a memo state; see memoState + errorFilter errorKeyFilter // Memo keys that may have reports; see observeMemo + memoPendingCount int // Number of memoStates with unreplayed effects + memoErrorStates int // Number of memoStates with reports; see setReported + + mu sync.Mutex } func NewChecker(program Program, tracer *Tracer) (*Checker, *sync.Mutex) { program.BindSourceFiles() - c := &Checker{} + c := &Checker{memoStore: &memoStore{}, checking: true} + c.initResolutions() + c.root = c c.id = nextCheckerID.Add(1) c.tracer = tracer c.program = program c.compilerOptions = program.Options() c.files = program.SourceFiles() c.fileIndexMap = createFileIndexMap(c.files) - c.compareSymbols = c.compareSymbolsWorker // Closure optimization - c.compareSymbolChains = c.compareSymbolChainsWorker // Closure optimization c.languageVersion = c.compilerOptions.GetEmitScriptTarget() c.moduleKind = c.compilerOptions.GetEmitModuleKind() c.moduleResolutionKind = c.compilerOptions.GetModuleResolutionKind() @@ -986,57 +1085,14 @@ func NewChecker(program Program, tracer *Tracer) (*Checker, *sync.Mutex) { c.canCollectSymbolAliasAccessibilityData = c.compilerOptions.VerbatimModuleSyntax.IsFalseOrUnknown() c.arrayVariances = []VarianceFlags{VarianceFlagsCovariant} c.globals = ast.NewSymbolTableWithCapacity(countGlobalSymbols(c.files)) - c.evaluate = evaluator.NewEvaluator(c.evaluateEntity, ast.OEKParentheses) - c.stringLiteralTypes = make(map[string]*Type) - c.numberLiteralTypes = make(map[jsnum.Number]*Type) - c.bigintLiteralTypes = make(map[jsnum.PseudoBigInt]*Type) - c.enumLiteralTypes = make(map[EnumLiteralKey]*Type) - c.enumNaNLiteralTypes = make(map[*ast.Symbol]*Type) - c.indexedAccessTypes = make(map[CacheHashKey]*Type) - c.templateLiteralTypes = make(map[CacheHashKey]*Type) - c.stringMappingTypes = make(map[StringMappingKey]*Type) - c.uniqueESSymbolTypes = make(map[*ast.Symbol]*Type) - c.thisExpandoKinds = make(map[*ast.Symbol]thisAssignmentDeclarationKind) - c.thisExpandoLocations = make(map[*ast.Symbol]*ast.Node) - c.subtypeReductionCache = make(map[CacheHashKey][]*Type) - c.cachedTypes = make(map[CachedTypeKey]*Type) - c.cachedSignatures = make(map[CachedSignatureKey]*Signature) - c.undefinedProperties = make(map[*ast.Symbol]*ast.Symbol) - c.narrowedTypes = make(map[NarrowedTypeKey]*Type) - c.assignmentReducedTypes = make(map[AssignmentReducedKey]*Type) - c.discriminatedContextualTypes = make(map[DiscriminatedContextualTypeKey]*Type) - c.instantiationExpressionTypes = make(map[InstantiationExpressionKey]*Type) - c.substitutionTypes = make(map[SubstitutionTypeKey]*Type) - c.reverseMappedCache = make(map[ReverseMappedTypeKey]*Type) - c.reverseHomomorphicMappedCache = make(map[ReverseMappedTypeKey]*Type) - c.iterationTypesCache = make(map[IterationTypesKey]IterationTypes) c.undefinedSymbol = c.newSymbol(ast.SymbolFlagsProperty, "undefined") c.argumentsSymbol = c.newSymbol(ast.SymbolFlagsProperty, "arguments") c.requireSymbol = c.newSymbol(ast.SymbolFlagsProperty, "require") c.unknownSymbol = c.newSymbol(ast.SymbolFlagsProperty, "unknown") - c.unresolvedSymbols = make(map[string]*ast.Symbol) - c.errorTypes = make(map[CacheHashKey]*Type) - c.moduleSymbols = make(map[*ast.Node]*ast.Symbol) c.globalThisSymbol = c.newSymbolEx(ast.SymbolFlagsModule, "globalThis", ast.CheckFlagsReadonly) c.globalThisSymbol.SetExports(c.globals, &c.symbolExtraArena) c.globals.Set(c.globalThisSymbol.Name(), c.globalThisSymbol) - c.resolveName = c.createNameResolver().Resolve - c.resolveNameForSymbolSuggestion = c.createNameResolverForSuggestion().Resolve - c.tupleTypes = make(map[CacheHashKey]*Type) - c.unionTypes = make(map[CacheHashKey]*Type) - c.objectLiteralTypes = make(map[CacheHashKey]*Type) - c.spreadTypes = make(map[CacheHashKey]*Type) - c.objectContentTypes = make(map[CacheHashKey]*Type) - c.spreadSymbols = make(map[spreadSymbolKey]*ast.Symbol) - c.symbolsWithType = make(map[symbolWithTypeKey]*ast.Symbol) - c.paddedProperties = make(map[symbolWithTypeKey]*ast.Symbol) - c.bindingPatternTypes = make(map[CacheHashKey]*Type) - c.unionOfUnionTypes = make(map[UnionOfUnionKey]*Type) - c.intersectionTypes = make(map[CacheHashKey]*Type) - c.intersectionContentTypes = make(map[CacheHashKey]*Type) - c.propertiesTypes = make(map[PropertiesTypesKey]*Type) - c.patternForType = make(map[*Type]*ast.Node) - c.contextFreeTypes = make(map[*ast.Node]*Type) + c.initCheckerState() c.anyType = c.newIntrinsicType(TypeFlagsAny, "any") c.autoType = c.newIntrinsicTypeEx(TypeFlagsAny, "any", ObjectFlagsNonInferrableType) c.wildcardType = c.newIntrinsicType(TypeFlagsAny, "any") @@ -1078,11 +1134,6 @@ func NewChecker(program Program, tracer *Tracer) (*Checker, *sync.Mutex) { c.numericStringType = c.getTemplateLiteralType([]string{"", ""}, []*Type{c.numberType}) // The `${number}` type c.templateConstraintType = c.getUnionType([]*Type{c.stringType, c.numberType, c.booleanType, c.bigintType, c.nullType, c.undefinedType}) c.uniqueLiteralType = c.newIntrinsicType(TypeFlagsNever, "never") // Special `never` flagged by union reduction to behave as a literal - c.uniqueLiteralMapper = newFunctionTypeMapper(c.getUniqueLiteralTypeForTypeParameter) - c.reportUnreliableMapper = newFunctionTypeMapper(c.reportUnreliableWorker) - c.reportUnmeasurableMapper = newFunctionTypeMapper(c.reportUnmeasurableWorker) - c.restrictiveMapper = newFunctionTypeMapper(c.restrictiveMapperWorker) - c.permissiveMapper = newFunctionTypeMapper(c.permissiveMapperWorker) c.emptyObjectType = c.newAnonymousType(nil /*symbol*/, nil, nil, nil, nil) c.emptyJsxObjectType = c.newAnonymousType(nil /*symbol*/, nil, nil, nil, nil) c.emptyFreshJsxObjectType = c.newAnonymousType(nil /*symbol*/, nil, nil, nil, nil) @@ -1090,7 +1141,6 @@ func NewChecker(program Program, tracer *Tracer) (*Checker, *sync.Mutex) { c.unknownEmptyObjectType = c.newAnonymousType(nil /*symbol*/, nil, nil, nil, nil) c.unknownUnionType = c.createUnknownUnionType() c.emptyGenericType = c.newAnonymousType(nil /*symbol*/, nil, nil, nil, nil) - c.emptyGenericType.AsObjectType().instantiations = make(map[CacheHashKey]*Type) c.anyFunctionType = c.newAnonymousType(nil /*symbol*/, nil, nil, nil, nil) c.anyFunctionType.objectFlags |= ObjectFlagsNonInferrableType c.noConstraintType = c.newAnonymousType(nil /*symbol*/, nil, nil, nil, nil) @@ -1098,33 +1148,50 @@ func NewChecker(program Program, tracer *Tracer) (*Checker, *sync.Mutex) { c.resolvingDefaultType = c.newAnonymousType(nil /*symbol*/, nil, nil, nil, nil) c.markerSuperType = c.newTypeParameter(nil) c.markerSubType = c.newTypeParameter(nil) - c.markerSubType.AsTypeParameter().constraint = c.markerSuperType + c.markerSubType.AsTypeParameter().constraint.intern(c.markerSuperType) c.markerOtherType = c.newTypeParameter(nil) c.markerSuperTypeForCheck = c.newTypeParameter(nil) c.markerSubTypeForCheck = c.newTypeParameter(nil) - c.markerSubTypeForCheck.AsTypeParameter().constraint = c.markerSuperTypeForCheck + c.markerSubTypeForCheck.AsTypeParameter().constraint.intern(c.markerSuperTypeForCheck) c.noTypePredicate = &TypePredicate{kind: TypePredicateKindIdentifier, parameterIndex: 0, parameterName: "<>", t: c.anyType} c.anySignature = c.newSignature(SignatureFlagsNone, nil, nil, nil, nil, c.anyType, nil, 0) c.unknownSignature = c.newSignature(SignatureFlagsNone, nil, nil, nil, nil, c.errorType, nil, 0) c.resolvingSignature = c.newSignature(SignatureFlagsNone, nil, nil, nil, nil, c.anyType, nil, 0) c.silentNeverSignature = c.newSignature(SignatureFlagsNone, nil, nil, nil, nil, c.silentNeverType, nil, 0) - c.cachedArgumentsReferenced = make(map[*ast.Node]bool) c.enumNumberIndexInfo = &IndexInfo{keyType: c.numberType, valueType: c.stringType, isReadonly: true} c.anyBaseTypeIndexInfo = &IndexInfo{keyType: c.stringType, valueType: c.anyType, isReadonly: false} c.emptyStringType = c.getStringLiteralType("") c.zeroType = c.getNumberLiteralType(0) c.zeroBigIntType = c.getBigIntLiteralType(jsnum.PseudoBigInt{}) 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) c.subtypeRelation = &Relation{} c.strictSubtypeRelation = &Relation{} c.assignableRelation = &Relation{} c.comparableRelation = &Relation{} c.identityRelation = &Relation{} - c.enumRelation = make(map[EnumRelationKey]RelationComparisonResult) - c.moduleImportAttributesTypes = make(map[*ast.Symbol]*Type) + for _, r := range []*Relation{c.subtypeRelation, c.strictSubtypeRelation, c.assignableRelation, c.comparableRelation, c.identityRelation} { + r.results.setKeyBits(relationKeyBits) + } + c.initializeChecker() + c.mergesFrozen = true + c.checking = false + c.syncObserving() + return c, &c.mu +} + +// initCheckerState initializes the per-checker state of c, including closures +// that must be bound to this checker rather than the store. +func (c *Checker) initCheckerState() { + c.compareSymbols = c.compareSymbolsWorker // Closure optimization + c.compareSymbolChains = c.compareSymbolChainsWorker // Closure optimization + c.evaluate = evaluator.NewEvaluator(c.evaluateEntity, ast.OEKParentheses) + c.resolveName = c.createNameResolver().Resolve + c.resolveNameForSymbolSuggestion = c.createNameResolverForSuggestion().Resolve + c.uniqueLiteralMapper = newFunctionTypeMapper(c.getUniqueLiteralTypeForTypeParameter) + c.reportUnreliableMapper = newFunctionTypeMapper(c.reportUnreliableWorker) + c.reportUnmeasurableMapper = newFunctionTypeMapper(c.reportUnmeasurableWorker) + c.restrictiveMapper = newFunctionTypeMapper(c.restrictiveMapperWorker) + c.permissiveMapper = newFunctionTypeMapper(c.permissiveMapperWorker) c.getGlobalESSymbolType = c.getGlobalTypeResolver("Symbol", 0 /*arity*/, false /*reportErrors*/) c.getGlobalBigIntType = c.getGlobalTypeResolver("BigInt", 0 /*arity*/, false /*reportErrors*/) c.getGlobalImportMetaType = c.getGlobalTypeResolver("ImportMeta", 0 /*arity*/, true /*reportErrors*/) @@ -1178,8 +1245,25 @@ func NewChecker(program Program, tracer *Tracer) (*Checker, *sync.Mutex) { c.getGlobalClassFieldDecoratorContextType = c.getGlobalTypeResolver("ClassFieldDecoratorContext", 2 /*arity*/, true /*reportErrors*/) c.initializeClosures() c.initializeIterationResolvers() - c.initializeChecker() - return c, &c.mu + // Memos are identified by their addresses, so the cells are allocated + // once all resolvers have their IDs, which every checker sharing the + // store assigns alike. + if c.globalTypes == nil { + c.globalTypes = make([]memo[resolvedGlobal[*Type]], len(c.globalIDs)) + c.globalSymbols = make([]memo[resolvedGlobal[*ast.Symbol]], len(c.globalIDs)) + } + debug.Assert(len(c.globalTypes) == len(c.globalIDs), "checkers sharing a store assigned different global IDs") +} + +// Clone returns a new checker sharing c's store. The clone observes and +// contributes to the same memoized state, but has its own resolution stacks. +// Checkers sharing a store must not be used concurrently; acquire the store's +// lock (returned by NewChecker) before use. +func (c *Checker) Clone() *Checker { + clone := &Checker{memoStore: c.memoStore, tracer: c.tracer} + clone.initResolutions() + clone.initCheckerState() + return clone } func createFileIndexMap(files []*ast.SourceFile) map[*ast.SourceFile]int { @@ -1214,39 +1298,102 @@ func (c *Checker) reportUnmeasurableWorker(t *Type) *Type { return t } +// globalKey identifies the resolution of a global by name. Resolutions are +// cached in the store, so all checkers share them. +type globalKey struct { + kind globalKind + name string + arity int + reportErrors bool +} + +// globalID is the small integer standing for a globalKey in the store, so +// that resolving a global hashes an integer rather than a name. Resolvers are +// all created as a checker is initialized, in the same order, so every +// checker sharing a store assigns the same IDs. +type globalID uint32 + +func (c *Checker) getGlobalID(key globalKey) globalID { + if id, ok := c.globalIDs[key]; ok { + return id + } + if c.globalIDs == nil { + c.globalIDs = make(map[globalKey]globalID) + } + id := globalID(len(c.globalIDs)) + c.globalIDs[key] = id + return id +} + +type globalKind uint8 + +const ( + globalKindType globalKind = iota + globalKindTypeAlias + globalKindValueSymbol + globalKindTypeSymbol +) + // Resolve to the global class or interface by the given name and arity, or emptyObjectType/emptyGenericType otherwise func (c *Checker) getGlobalTypeResolver(name string, arity int, reportErrors bool) func() *Type { - return core.Memoize(func() *Type { - return c.getGlobalType(name, arity, reportErrors) - }) + key := c.getGlobalID(globalKey{globalKindType, name, arity, reportErrors}) + return func() *Type { + m := &c.globalTypes[key] + if r := m.get(c); r.resolved { + return r.value + } + return c.memoCompute(m, func() resolvedGlobal[*Type] { + return resolvedGlobal[*Type]{c.getGlobalType(name, arity, reportErrors), true} + }).value + } +} + +// resolvedGlobal is the result of resolving a global, which may be nil. +type resolvedGlobal[T any] struct { + value T + resolved bool } // Resolve to the global type alias symbol by the given name and arity, or nil otherwise func (c *Checker) getGlobalTypeAliasResolver(name string, arity int, reportErrors bool) func() *ast.Symbol { - return core.Memoize(func() *ast.Symbol { + return c.getGlobalSymbolResolver(globalKey{globalKindTypeAlias, name, arity, reportErrors}, func() *ast.Symbol { return c.getGlobalTypeAliasSymbol(name, arity, reportErrors) }) } // Resolve to the global value symbol by the given name, or nil otherwise func (c *Checker) getGlobalValueSymbolResolver(name string, reportErrors bool) func() *ast.Symbol { - return core.Memoize(func() *ast.Symbol { + return c.getGlobalSymbolResolver(globalKey{globalKindValueSymbol, name, 0, reportErrors}, func() *ast.Symbol { return c.getGlobalSymbol(name, ast.SymbolFlagsValue, core.IfElse(reportErrors, diagnostics.Cannot_find_global_value_0, nil)) }) } func (c *Checker) getGlobalTypeSymbolResolver(name string, reportErrors bool) func() *ast.Symbol { - return core.Memoize(func() *ast.Symbol { + return c.getGlobalSymbolResolver(globalKey{globalKindTypeSymbol, name, 0, reportErrors}, func() *ast.Symbol { return c.getGlobalSymbol(name, ast.SymbolFlagsType, core.IfElse(reportErrors, diagnostics.Cannot_find_global_type_0, nil)) }) } +func (c *Checker) getGlobalSymbolResolver(k globalKey, resolve func() *ast.Symbol) func() *ast.Symbol { + key := c.getGlobalID(k) + return func() *ast.Symbol { + m := &c.globalSymbols[key] + if r := m.get(c); r.resolved { + return r.value + } + return c.memoCompute(m, func() resolvedGlobal[*ast.Symbol] { + return resolvedGlobal[*ast.Symbol]{resolve(), true} + }).value + } +} + func (c *Checker) getGlobalTypesResolver(names []string, arity int, reportErrors bool) func() []*Type { - return core.Memoize(func() []*Type { - return core.Map(names, func(name string) *Type { - return c.getGlobalType(name, arity, reportErrors) - }) + resolvers := core.Map(names, func(name string) func() *Type { + return c.getGlobalTypeResolver(name, arity, reportErrors) }) + return func() []*Type { + return core.Map(resolvers, func(resolve func() *Type) *Type { return resolve() }) + } } func (c *Checker) getGlobalTypeAliasSymbol(name string, arity int, reportErrors bool) *ast.Symbol { @@ -1256,7 +1403,7 @@ func (c *Checker) getGlobalTypeAliasSymbol(name string, arity int, reportErrors } // Resolve the declared type of the symbol. This resolves type parameters for the type alias so that we can check arity. c.getDeclaredTypeOfSymbol(symbol) - if len(c.typeAliasLinks.Get(symbol).typeParameters) != arity { + if len(c.getTypeParametersOfTypeAlias(symbol)) != arity { if reportErrors { decl := core.Find(symbol.Declarations(), ast.IsTypeAliasDeclaration) c.error(decl, diagnostics.Global_type_0_must_have_1_type_parameter_s, ast.SymbolName(symbol), arity) @@ -1270,6 +1417,12 @@ func (c *Checker) GetTypeAliasTypeParameters(symbol *ast.Symbol) []*Type { if symbol.Flags()&ast.SymbolFlagsTypeAlias == 0 { panic("Attempted to fetch type alias parameters for non-type-alias symbol") } + return c.getTypeParametersOfTypeAlias(symbol) +} + +// getTypeParametersOfTypeAlias returns the type parameters of a type alias, +// which are resolved along with its declared type. +func (c *Checker) getTypeParametersOfTypeAlias(symbol *ast.Symbol) []*Type { c.getDeclaredTypeOfSymbol(symbol) return c.typeAliasLinks.Get(symbol).typeParameters } @@ -1409,10 +1562,10 @@ func (c *Checker) initializeChecker() { } } c.addUndefinedToGlobalsOrErrorOnRedeclaration() - c.valueSymbolLinks.Get(c.undefinedSymbol).resolvedType = c.undefinedWideningType - c.valueSymbolLinks.Get(c.argumentsSymbol).resolvedType = c.getGlobalType("IArguments", 0 /*arity*/, true /*reportErrors*/) - c.valueSymbolLinks.Get(c.unknownSymbol).resolvedType = c.errorType - c.valueSymbolLinks.Get(c.globalThisSymbol).resolvedType = c.newObjectType(ObjectFlagsAnonymous, c.globalThisSymbol) + c.valueSymbolLinks.Get(c.undefinedSymbol).resolvedType.set(c, c.undefinedWideningType) + c.valueSymbolLinks.Get(c.argumentsSymbol).resolvedType.set(c, c.getGlobalType("IArguments", 0 /*arity*/, true /*reportErrors*/)) + c.valueSymbolLinks.Get(c.unknownSymbol).resolvedType.set(c, c.errorType) + c.valueSymbolLinks.Get(c.globalThisSymbol).resolvedType.set(c, c.newObjectType(ObjectFlagsAnonymous, c.globalThisSymbol)) // Initialize special types c.globalArrayType = c.getGlobalType("Array", 1 /*arity*/, true /*reportErrors*/) c.globalObjectType = c.getGlobalType("Object", 0 /*arity*/, true /*reportErrors*/) @@ -1595,6 +1748,10 @@ func (c *Checker) createNameResolverForSuggestion() *binder.NameResolver { } func (c *Checker) symbolReferenced(symbol *ast.Symbol, meaning ast.SymbolFlags) { + if !c.checking { + c.recordEffect(func(c *Checker) { c.symbolReferenced(symbol, meaning) }) + return + } c.symbolReferenceLinks.Get(symbol).referenceKinds |= meaning } @@ -1665,7 +1822,7 @@ func (c *Checker) onFailedToResolveSymbol(errorLocation *ast.Node, name string, // resolution that failed would make the explanation, and any circularity // errors it causes, depend on what else happened to be in progress. Defer // it until no resolution is in progress, so it only observes results. - c.addDeferredDiagnostic(func() { + c.addDeferredDiagnostic(func(c *Checker) { c.reportFailedToResolveSymbol(errorLocation, name, meaning, nameNotFoundMessage) }) } @@ -2264,14 +2421,16 @@ func (c *Checker) getTypeOnlyAliasDeclarationEx(symbol *ast.Symbol, meaning ast. func (c *Checker) getImmediateAliasedSymbol(symbol *ast.Symbol) *ast.Symbol { debug.Assert(symbol.Flags()&ast.SymbolFlagsAlias != 0, "Should only get Alias here.") links := c.aliasSymbolLinks.Get(symbol) - if links.immediateTarget == nil { + if links.immediateTarget.get(c) == nil { node := c.getDeclarationOfAliasSymbol(symbol) if node == nil { panic("Unexpected nil in getImmediateAliasedSymbol") } - links.immediateTarget = c.getTargetOfAliasDeclaration(node, true /*dontRecursivelyResolve*/) + links.immediateTarget.begin(c) + links.immediateTarget.set(c, c.getTargetOfAliasDeclaration(node, true /*dontRecursivelyResolve*/)) + c.endMemo() } - return links.immediateTarget + return links.immediateTarget.get(c) } func (c *Checker) addTypeOnlyDeclarationRelatedInfo(diagnostic *ast.Diagnostic, typeOnlyDeclaration *ast.Node, name string) *ast.Diagnostic { @@ -2284,7 +2443,21 @@ func (c *Checker) addTypeOnlyDeclarationRelatedInfo(diagnostic *ast.Diagnostic, func (c *Checker) getSymbol(symbols ast.SymbolTable, name string, meaning ast.SymbolFlags) *ast.Symbol { if meaning&ast.SymbolFlagsAll != 0 { - symbol := c.getMergedSymbol(symbols.Get(name)) + symbol := symbols.Get(name) + if symbol == nil { + return nil + } + // Late binding can only merge member declarations into a symbol, so + // skip it when those could not make the symbol match. Resolving the + // late-bound members here would otherwise be circular when looking up + // a name inside one of their computed property names. + if merged := c.mergedSymbolOf(symbol); merged != nil { + symbol = merged + } + if symbol.Flags()&(meaning|ast.SymbolFlagsAlias) == 0 && meaning&(ast.SymbolFlagsClassMember|ast.SymbolFlagsAssignment) == 0 { + return nil + } + symbol = c.getMergedSymbol(symbol) if symbol != nil { if symbol.Flags()&meaning != 0 { return symbol @@ -2303,6 +2476,10 @@ func (c *Checker) getSymbol(symbols ast.SymbolTable, name string, meaning ast.Sy } func (c *Checker) checkSourceFile(ctx context.Context, sourceFile *ast.SourceFile, checkUnused bool) { + c.withChecking(func() { c.checkSourceFileWorker(ctx, sourceFile, checkUnused) }) +} + +func (c *Checker) checkSourceFileWorker(ctx context.Context, sourceFile *ast.SourceFile, checkUnused bool) { c.ctx = ctx links := c.sourceFileLinks.Get(sourceFile) if !links.typeChecked { @@ -2321,8 +2498,8 @@ func (c *Checker) checkSourceFile(ctx context.Context, sourceFile *ast.SourceFil if !sourceFile.IsDeclarationFile && !c.isCanceled() { c.checkUnusedRenamedBindingElements() } + c.checkExternalHelperRequests(sourceFile) c.produceDeferredDiagnostics() - c.reportedUnreachableNodes.Clear() links.typeChecked = true } if checkUnused && !links.unusedChecked { @@ -2349,12 +2526,16 @@ func (c *Checker) checkSourceElements(nodes []*ast.Node) { func (c *Checker) checkSourceElement(node *ast.Node) bool { if node != nil { - saveCurrentNode := c.currentNode + saveCurrentNode, shared := c.enterSharedCurrentNode(node) + if !shared { + saveCurrentNode = c.enterCurrentNode(node) + } saveWithinUnreachableCode := c.withinUnreachableCode - c.currentNode = node c.instantiationCount = 0 c.checkSourceElementWorker(node) - c.currentNode = saveCurrentNode + if !c.exitSharedCurrentNode(saveCurrentNode) { + c.exitCurrentNode(saveCurrentNode) + } c.withinUnreachableCode = saveWithinUnreachableCode } return false @@ -2499,64 +2680,64 @@ func (c *Checker) checkSourceElementWorker(node *ast.Node) { } func (c *Checker) checkSourceElementUnreachable(node *ast.Node) bool { - if !ast.IsPotentiallyExecutableNode(node) { - return false - } - - if c.reportedUnreachableNodes.Has(node) { - return true - } - - if !c.isSourceElementUnreachable(node) { + if !ast.IsPotentiallyExecutableNode(node) || !c.isSourceElementUnreachable(node) { return false } - - c.reportedUnreachableNodes.Add(node) - - sourceFile := ast.GetSourceFileOfNode(node) - + // Report the whole run of unreachable statements containing node once, at + // the same range whichever statement of the run is checked first. startNode := node endNode := node - - parent := node.Parent - if parent.CanHaveStatements() { - statements := parent.Statements() - if offset := slices.Index(statements, node); offset >= 0 { - // Scan backwards to find the first unreachable unreported node; - // this may happen when producing region diagnostics where not all nodes - // will have been visited. - // TODO: enable this code once we support region diagnostics again. - first := offset - // for i := offset - 1; i >= 0; i-- { - // prevNode := statements[i] - // if !ast.IsPotentiallyExecutableNode(prevNode) || c.reportedUnreachableNodes.Has(prevNode) || !c.isSourceElementUnreachable(prevNode) { - // break - // } - // firstUnreachableIndex = i - // c.reportedUnreachableNodes.Add(prevNode) - // } - - last := offset - for i := offset + 1; i < len(statements); i++ { - nextNode := statements[i] - if !ast.IsPotentiallyExecutableNode(nextNode) || !c.isSourceElementUnreachable(nextNode) { - break - } - last = i - c.reportedUnreachableNodes.Add(nextNode) - } - - startNode = statements[first] - endNode = statements[last] + if parent := node.Parent; parent.CanHaveStatements() { + if run, ok := c.getUnreachableRuns(parent)[node]; ok { + startNode = run.start + endNode = run.end } } + c.reportedUnreachableNodes.once(c, startNode, func() { + sourceFile := ast.GetSourceFileOfNode(node) + start := scanner.GetTokenPosOfNode(startNode, sourceFile, false /*includeJSDoc*/) + diagnostic := ast.NewDiagnostic(sourceFile, core.NewTextRange(start, endNode.End()), diagnostics.Unreachable_code_detected) + c.addErrorOrSuggestion(c.compilerOptions.AllowUnreachableCode == core.TSFalse, diagnostic) + }) + return true +} - start := scanner.GetTokenPosOfNode(startNode, sourceFile, false /*includeJSDoc*/) - - diagnostic := ast.NewDiagnostic(sourceFile, core.NewTextRange(start, endNode.End()), diagnostics.Unreachable_code_detected) - c.addErrorOrSuggestion(c.compilerOptions.AllowUnreachableCode == core.TSFalse, diagnostic) +type unreachableRun struct { + start *ast.Node + end *ast.Node +} - return true +// getUnreachableRuns returns the run of unreachable statements containing +// each unreachable statement of container. The runs are found together, so +// checking every statement of a long run does not rescan it each time. +func (c *Checker) getUnreachableRuns(container *ast.Node) map[*ast.Node]unreachableRun { + if runs, ok := c.unreachableRuns.get(c, container); ok { + return runs + } + return c.unreachableRuns.compute(c, container, func() map[*ast.Node]unreachableRun { + var runs map[*ast.Node]unreachableRun + isUnreachable := func(n *ast.Node) bool { + return ast.IsPotentiallyExecutableNode(n) && c.isSourceElementUnreachable(n) + } + statements := container.Statements() + for i := 0; i < len(statements); i++ { + if !isUnreachable(statements[i]) { + continue + } + first := i + for i+1 < len(statements) && isUnreachable(statements[i+1]) { + i++ + } + run := unreachableRun{statements[first], statements[i]} + if runs == nil { + runs = make(map[*ast.Node]unreachableRun) + } + for _, statement := range statements[first : i+1] { + runs[statement] = run + } + } + return runs + }) } func (c *Checker) isSourceElementUnreachable(node *ast.Node) bool { @@ -2591,6 +2772,10 @@ func (c *Checker) isSourceElementUnreachable(node *ast.Node) bool { // determining the type of foo would cause foo to be given type any because of the recursive reference. // Delaying the type check of the body ensures foo has been assigned a type. func (c *Checker) checkNodeDeferred(node *ast.Node) { + if !c.checking { + c.recordEffect(func(c *Checker) { c.checkNodeDeferred(node) }) + return + } enclosingFile := ast.GetSourceFileOfNode(node) links := c.sourceFileLinks.Get(enclosingFile) if !links.typeChecked { @@ -2613,8 +2798,10 @@ func (c *Checker) checkDeferredNode(node *ast.Node) { if tr := c.tracer; tr != nil { defer tr.Push(tracing.PhaseCheck, "checkDeferredNode", map[string]any{"kind": node.Kind, "pos": node.Pos(), "end": node.End(), "path": ast.GetSourceFileOfNode(node).FileName()}, false)() } - saveCurrentNode := c.currentNode - c.currentNode = node + saveCurrentNode, shared := c.enterSharedCurrentNode(node) + if !shared { + saveCurrentNode = c.enterCurrentNode(node) + } c.instantiationCount = 0 switch node.Kind { case ast.KindCallExpression, ast.KindNewExpression, ast.KindTaggedTemplateExpression, ast.KindDecorator, ast.KindJsxOpeningElement: @@ -2644,7 +2831,9 @@ func (c *Checker) checkDeferredNode(node *ast.Node) { case ast.KindObjectLiteralExpression, ast.KindJsxAttributes: c.checkContextualDeprecations(node) } - c.currentNode = saveCurrentNode + if !c.exitSharedCurrentNode(saveCurrentNode) { + c.exitCurrentNode(saveCurrentNode) + } } func (c *Checker) checkJSDocComments(node *ast.Node) { @@ -3073,17 +3262,16 @@ func (c *Checker) checkAccessorDeclaration(node *ast.Node) { symbol := c.getSymbolOfDeclaration(node) getter := ast.GetDeclarationOfKind(symbol, ast.KindGetAccessor) setter := ast.GetDeclarationOfKind(symbol, ast.KindSetAccessor) - if getter != nil && setter != nil && c.nodeLinks.Get(getter).flags&NodeCheckFlagsTypeChecked == 0 { - c.nodeLinks.Get(getter).flags |= NodeCheckFlagsTypeChecked + // Each accessor of the pair reports the errors on its own name, so that + // they do not depend on which of the two is checked first. + if getter != nil && setter != nil && (node == getter || node == setter) { getterFlags := getter.ModifierFlags() setterFlags := setter.ModifierFlags() if (getterFlags & ast.ModifierFlagsAbstract) != (setterFlags & ast.ModifierFlagsAbstract) { - c.error(getter.Name(), diagnostics.Accessors_must_both_be_abstract_or_non_abstract) - c.error(setter.Name(), diagnostics.Accessors_must_both_be_abstract_or_non_abstract) + c.error(node.Name(), diagnostics.Accessors_must_both_be_abstract_or_non_abstract) } if ((getterFlags&ast.ModifierFlagsProtected != 0) && setterFlags&(ast.ModifierFlagsProtected|ast.ModifierFlagsPrivate) == 0) || ((getterFlags&ast.ModifierFlagsPrivate != 0) && setterFlags&ast.ModifierFlagsPrivate == 0) { - c.error(getter.Name(), diagnostics.A_get_accessor_must_be_at_least_as_accessible_as_the_setter) - c.error(setter.Name(), diagnostics.A_get_accessor_must_be_at_least_as_accessible_as_the_setter) + c.error(node.Name(), diagnostics.A_get_accessor_must_be_at_least_as_accessible_as_the_setter) } } } @@ -3246,12 +3434,12 @@ func (c *Checker) checkIfTypePredicateVariableIsDeclaredInBindingPattern(pattern } func (c *Checker) checkTypeQuery(node *ast.Node) { - c.getTypeFromTypeQueryNode(node) + memoTypeNode(c, node, (*Checker).getTypeFromTypeQueryNode) } func (c *Checker) checkTypeLiteral(node *ast.Node) { c.checkSourceElements(node.Members()) - t := c.getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node) + t := memoTypeNode(c, node, (*Checker).getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode) c.checkIndexConstraints(t, t.symbol, false /*isStaticIndex*/) c.checkTypeForDuplicateIndexSignatures(node) c.checkObjectTypeForDuplicateDeclarations(node, false /*checkPrivateNames*/) @@ -3391,7 +3579,7 @@ func (c *Checker) checkUnionOrIntersectionType(node *ast.Node) { } func (c *Checker) checkThisType(node *ast.Node) { - c.getTypeFromThisTypeNode(node) + memoTypeNode(c, node, (*Checker).getTypeFromThisTypeNode) } func (c *Checker) checkTypeOperator(node *ast.Node) { @@ -3413,9 +3601,7 @@ func (c *Checker) checkInferType(node *ast.Node) { c.checkSourceElement(typeParameterDeclarationNode) symbol := c.getSymbolOfDeclaration(typeParameterDeclarationNode) if len(symbol.Declarations()) > 1 { - links := c.declaredTypeLinks.Get(symbol) - if !links.typeParametersChecked { - links.typeParametersChecked = true + c.typeParametersChecked.once(c, symbol, func() { typeParameter := c.getDeclaredTypeOfTypeParameter(symbol) declarations := getDeclarationsOfKind(symbol, ast.KindTypeParameter) if !c.areTypeParametersIdentical(declarations, []*Type{typeParameter}, func(decl *ast.Node) []*ast.Node { return []*ast.Node{decl} }) { @@ -3425,7 +3611,7 @@ func (c *Checker) checkInferType(node *ast.Node) { c.error(declaration.Name(), diagnostics.All_declarations_of_0_must_have_identical_constraints, name) } } - } + }) } c.registerForUnusedIdentifiersCheck(node) } @@ -3476,7 +3662,7 @@ func (c *Checker) checkNamedTupleMember(node *ast.Node) { func (c *Checker) checkIndexedAccessType(node *ast.Node) { node.ForEachChild(c.checkSourceElement) - c.checkIndexedAccessIndexType(c.getTypeFromIndexedAccessTypeNode(node), node) + c.checkIndexedAccessIndexType(memoTypeNode(c, node, (*Checker).getTypeFromIndexedAccessTypeNode), node) } func (c *Checker) checkMappedType(node *ast.Node) { @@ -3488,7 +3674,7 @@ func (c *Checker) checkMappedType(node *ast.Node) { if mappedTypeNode.Type == nil { c.reportImplicitAny(node, c.anyType, WideningKindNormal) } - t := c.getTypeFromMappedTypeNode(node) + t := memoTypeNode(c, node, (*Checker).getTypeFromMappedTypeNode) nameType := c.getNameTypeFromMappedType(t) if nameType != nil { c.checkTypeAssignableTo(nameType, c.stringNumberSymbolType, mappedTypeNode.NameType, nil) @@ -3557,10 +3743,7 @@ func (c *Checker) checkFunctionOrMethodDeclaration(node *ast.Node) { } func (c *Checker) checkFunctionOrConstructorSymbol(symbol *ast.Symbol) { - // Only check the symbol once - if c.constructorOrFunctionChecked.AddIfAbsent(symbol) { - c.checkFunctionOrConstructorSymbolWorker(symbol) - } + c.constructorOrFunctionChecked.once(c, symbol, func() { c.checkFunctionOrConstructorSymbolWorker(symbol) }) } func (c *Checker) checkFunctionOrConstructorSymbolWorker(symbol *ast.Symbol) { @@ -4527,9 +4710,7 @@ func (c *Checker) checkTypeParameterListsIdentical(symbol *ast.Symbol) { if len(symbol.Declarations()) == 1 { return } - links := c.declaredTypeLinks.Get(symbol) - if !links.typeParametersChecked { - links.typeParametersChecked = true + c.typeParametersChecked.once(c, symbol, func() { declarations := c.getClassOrInterfaceDeclarationsOfSymbol(symbol) if len(declarations) <= 1 { return @@ -4542,7 +4723,7 @@ func (c *Checker) checkTypeParameterListsIdentical(symbol *ast.Symbol) { c.error(declaration.Name(), diagnostics.All_declarations_of_0_must_have_identical_type_parameters, name) } } - } + }) } func (c *Checker) getClassOrInterfaceDeclarationsOfSymbol(symbol *ast.Symbol) []*ast.Node { @@ -4628,8 +4809,8 @@ func (c *Checker) getTypeWithoutSignatures(t *Type) *Type { if len(resolved.getSignatures()) != 0 { result := c.newObjectType(ObjectFlagsAnonymous, t.symbol) result.objectFlags |= ObjectFlagsMembersResolved - result.AsObjectType().members = resolved.getMembers() - result.AsObjectType().properties = resolved.getProperties() + result.AsObjectType().members = resolved.getMembers(c) + result.AsObjectType().properties = resolved.getProperties(c) return result } case t.flags&TypeFlagsIntersection != 0: @@ -5039,10 +5220,9 @@ func (c *Checker) checkIndexConstraintForIndexSignature(t *Type, checkInfo *Inde func (c *Checker) checkClassOrInterfaceForDuplicateIndexSignatures(node *ast.Node) { // Only check the type once - if links := c.declaredTypeLinks.Get(c.getSymbolOfDeclaration(node)); !links.indexSignaturesChecked { - links.indexSignaturesChecked = true + c.indexSignaturesChecked.once(c, c.getSymbolOfDeclaration(node), func() { c.checkTypeForDuplicateIndexSignatures(node) - } + }) } func (c *Checker) checkTypeForDuplicateIndexSignatures(node *ast.Node) { @@ -5146,8 +5326,7 @@ func (c *Checker) checkInterfaceDeclaration(node *ast.Node) { c.checkTypeParameterListsIdentical(symbol) // Check once per checker, but report on the first interface declaration, // independently of which declaration is checked first. - if links := c.declaredTypeLinks.Get(symbol); !links.interfaceChecked { - links.interfaceChecked = true + c.interfaceChecked.once(c, symbol, func() { firstInterfaceDeclaration := ast.GetDeclarationOfKind(symbol, ast.KindInterfaceDeclaration) t := c.getDeclaredTypeOfSymbol(symbol) typeWithThis := c.getTypeWithThisArgument(t, nil, false) @@ -5158,7 +5337,8 @@ func (c *Checker) checkInterfaceDeclaration(node *ast.Node) { } c.checkIndexConstraints(t, symbol /*isStaticIndex*/, false) } - } + }) + c.checkObjectTypeForDuplicateDeclarations(node, false /*checkPrivateNames*/) for _, heritageElement := range ast.GetExtendsHeritageClauseElements(node) { if ast.IsExpressionWithTypeArguments(heritageElement) { @@ -5233,8 +5413,7 @@ func (c *Checker) checkEnumDeclaration(node *ast.Node) { // // Only perform this check once per symbol enumSymbol := c.getSymbolOfDeclaration(node) - if links := c.declaredTypeLinks.Get(enumSymbol); !links.enumChecked { - links.enumChecked = true + c.enumChecked.once(c, enumSymbol, func() { if len(enumSymbol.Declarations()) > 1 { firstEnumDeclaration := ast.GetDeclarationOfKind(enumSymbol, ast.KindEnumDeclaration) enumIsConst := ast.IsEnumConst(firstEnumDeclaration) @@ -5264,7 +5443,7 @@ func (c *Checker) checkEnumDeclaration(node *ast.Node) { } } } - } + }) } func (c *Checker) checkEnumMember(node *ast.Node) { @@ -5397,9 +5576,10 @@ func (c *Checker) getTypeOfModuleDeclarationImportAttributes(attributes *ast.Typ } func (c *Checker) getTypeOfModuleImportAttributes(symbol *ast.Symbol) *Type { - if t, ok := c.moduleImportAttributesTypes[symbol]; ok { + if t, ok := c.moduleImportAttributesTypes.get(c, symbol); ok { return t } + c.moduleImportAttributesTypes.begin(c, symbol) var result *Type moduleDecl := core.Find(symbol.Declarations(), ast.IsModuleWithStringLiteralName) if moduleDecl == nil { @@ -5407,7 +5587,8 @@ func (c *Checker) getTypeOfModuleImportAttributes(symbol *ast.Symbol) *Type { } else { result = c.getTypeOfModuleDeclarationImportAttributes(moduleDecl.AsModuleDeclaration().Attributes) } - c.moduleImportAttributesTypes[symbol] = result + c.moduleImportAttributesTypes.set(c, symbol, result) + c.endMemo() return result } @@ -5640,19 +5821,19 @@ func (c *Checker) getTypeFromImportAttributes(node *ast.Node) *Type { func (c *Checker) checkImportAttributesExpression(node *ast.Node) *Type { links := c.typeNodeLinks.Get(node) - if links.resolvedType == nil { + if links.resolvedType.get(c) == nil { symbol := c.newSymbol(ast.SymbolFlagsObjectLiteral, ast.InternalSymbolNameImportAttributes) members := ast.NewSymbolTable() for _, attribute := range node.AsImportAttributes().Attributes.Nodes { member := c.newSymbol(ast.SymbolFlagsProperty, attribute.Name().Text()) - c.valueSymbolLinks.Get(member).resolvedType = c.getRegularTypeOfLiteralType(c.checkExpressionCached(attribute.AsImportAttribute().Value)) + c.valueSymbolLinks.Get(member).resolvedType.setIdentity(c, c.getRegularTypeOfLiteralType(c.checkExpressionCached(attribute.AsImportAttribute().Value))) members.Set(member.Name(), member) } t := c.newAnonymousType(symbol, members, nil, nil, nil) t.objectFlags |= ObjectFlagsObjectLiteral | ObjectFlagsNonInferrableType - links.resolvedType = t + links.resolvedType.set(c, t) } - return links.resolvedType + return links.resolvedType.get(c) } func (c *Checker) getImportAttributesTypeForModuleSpecifier(moduleSpecifier *ast.Node) *Type { @@ -5919,8 +6100,7 @@ func getVerbatimModuleSyntaxErrorMessage(node *ast.Node) *diagnostics.Message { func (c *Checker) checkExternalModuleExports(node *ast.Node) { moduleSymbol := c.getSymbolOfDeclaration(node) - links := c.moduleSymbolLinks.Get(moduleSymbol) - if !links.exportsChecked { + c.moduleExportsChecked.once(c, moduleSymbol, func() { exportEqualsSymbol := moduleSymbol.Exports().Get(ast.InternalSymbolNameExportEquals) // An export assignment is in error if (a) the module exports value members or (b) if the module exports type or // namespace members and the exported entity also exports type or namespace members. @@ -5958,8 +6138,7 @@ func (c *Checker) checkExternalModuleExports(node *ast.Node) { } } } - links.exportsChecked = true - } + }) } func (c *Checker) hasExportedMembersOfKind(moduleSymbol *ast.Symbol, kind ast.SymbolFlags) bool { @@ -6047,6 +6226,10 @@ func (c *Checker) checkVariableLikeDeclaration(node *ast.Node) { // ^^^^^^ // variable renaming in function type notation is confusing, // so we forbid it even if noUnusedLocals is not enabled + if !c.checking { + c.recordEffect(func(c *Checker) { c.renamedBindingElementsInTypes = append(c.renamedBindingElementsInTypes, node) }) + return + } c.renamedBindingElementsInTypes = append(c.renamedBindingElementsInTypes, node) // Still resolve the type like the end of this function does for every other declaration, // so implicit any errors are reported by checking instead of by whatever asks for the type later @@ -6312,11 +6495,11 @@ func (c *Checker) checkDecorator(node *ast.Node) { } // if we fail to get a signature and return type here, we will have already reported a grammar error in `checkDecorators`. decoratorSignature := c.getDecoratorCallSignature(node) - if decoratorSignature == nil || decoratorSignature.resolvedReturnType == nil { + if decoratorSignature == nil || decoratorSignature.resolvedReturnType.get(c) == nil { return } var headMessage *diagnostics.Message - expectedReturnType := decoratorSignature.resolvedReturnType + expectedReturnType := decoratorSignature.resolvedReturnType.get(c) switch node.Parent.Kind { case ast.KindClassDeclaration, ast.KindClassExpression: headMessage = diagnostics.Decorator_function_return_type_0_is_not_assignable_to_type_1 @@ -6511,20 +6694,27 @@ func (c *Checker) getIterationTypesOfIterable(t *Type, use IterationUse, errorNo if IsTypeAny(t) { return IterationTypes{c.anyType, c.anyType, c.anyType} } + if errorNode != nil { + // Errors belong to errorNode, so compute them here rather than + // cache them with the types every other use of t shares. + return c.getIterationTypesOfIterableWorker(t, use, errorNode) + } key := IterationTypesKey{typeId: t.id, use: use & IterationUseCacheFlags} - // If we are reporting errors and encounter a cached `noIterationTypes`, we should ignore the cached value and continue as if nothing was cached. - // In addition, we should not cache any new results for this call. - noCache := false - if cached, ok := c.iterationTypesCache[key]; ok { - if errorNode == nil || cached.hasTypes() { - return cached - } - noCache = true + if c.memoActive() { + c.iterationTypesCache.begin(c, key) + result := c.getIterationTypesOfIterableCached(t, use, key) + c.endMemo() + return result } - result := c.getIterationTypesOfIterableWorker(t, use, errorNode) - if !noCache { - c.iterationTypesCache[key] = result + return c.getIterationTypesOfIterableCached(t, use, key) +} + +func (c *Checker) getIterationTypesOfIterableCached(t *Type, use IterationUse, key IterationTypesKey) IterationTypes { + if cached, ok := c.iterationTypesCache.get(c, key); ok { + return cached } + result := c.getIterationTypesOfIterableWorker(t, use, nil /*errorNode*/) + c.iterationTypesCache.set(c, key, result) return result } @@ -6535,7 +6725,7 @@ func (c *Checker) getIterationTypesOfIterableWorker(t *Type, use IterationUse, e iterationTypes := c.getIterationTypesOfIterableWorker(constituent, use, nil) if !iterationTypes.hasTypes() { if errorNode != nil { - c.addDeferredDiagnostic(func() { + c.addDeferredDiagnostic(func(c *Checker) { c.reportTypeNotIterableError(errorNode, t, use&IterationUseAllowsAsyncIterablesFlag != 0) }) } @@ -6588,7 +6778,7 @@ func (c *Checker) getIterationTypesOfIterableWorker(t *Type, use IterationUse, e if errorNode != nil { // We defer the diagnostic because TypeToString may attempt to resolve symbols that are already being // resolved, possibly causing circularities. - c.addDeferredDiagnostic(func() { + c.addDeferredDiagnostic(func(c *Checker) { diagnostic := c.reportTypeNotIterableError(errorNode, t, use&IterationUseAllowsAsyncIterablesFlag != 0) for _, d := range diags { diagnostic.AddRelatedInfo(d) @@ -7165,9 +7355,13 @@ func (c *Checker) checkExportsOnMergedDeclarations(node *ast.Node) { return } } - if len(symbol.Declarations()) < 2 || !c.mergedExportsChecked.AddIfAbsent(symbol) { + if len(symbol.Declarations()) < 2 { return } + c.mergedExportsChecked.once(c, symbol, func() { c.checkExportsOnMergedDeclarationsWorker(symbol) }) +} + +func (c *Checker) checkExportsOnMergedDeclarationsWorker(symbol *ast.Symbol) { exportedDeclarationSpaces := DeclarationSpacesNone nonExportedDeclarationSpaces := DeclarationSpacesNone defaultExportedDeclarationSpaces := DeclarationSpacesNone @@ -7283,6 +7477,10 @@ func (c *Checker) checkTypeParametersNotReferenced(root *ast.Node, typeParameter } func (c *Checker) registerForUnusedIdentifiersCheck(node *ast.Node) { + if !c.checking { + c.recordEffect(func(c *Checker) { c.registerForUnusedIdentifiersCheck(node) }) + return + } sourceFile := ast.GetSourceFileOfNode(node) links := c.sourceFileLinks.Get(sourceFile) links.identifierCheckNodes = append(links.identifierCheckNodes, node) @@ -7585,18 +7783,44 @@ func (c *Checker) getTypeOfExpression(node *ast.Node) *Type { if quickType != nil { return quickType } - // If a type has been cached for the node, return it. + if len(c.flowLoopStack) == 0 { + // Outside loop analysis, the type is independent of the flow state in + // progress, so it is cached in the store. + if t, ok := c.flowExpressionTypes.get(c, node); ok { + return t + } + c.flowExpressionTypes.begin(c, node) + startInvocationCount := c.flowInvocationCount + t := c.checkExpressionEx(node, CheckModeTypeOnly) + // If control flow analysis was required to determine the type, it is worth caching. + if c.flowInvocationCount != startInvocationCount { + c.flowExpressionTypes.set(c, node, t) + c.endMemo() + } else { + c.endMemoUncached() + } + return t + } + // Within loop analysis, the type may depend on the transient types of the + // loop, so it is only cached until the analysis of the loop is restarted. + key := memoKeyOf(node, memoFlowExpressionType) if cachedType := c.flowTypeCache[node]; cachedType != nil { + if c.memoActive() { + c.observeMemo(key) + } return cachedType } + c.beginMemo(key) startInvocationCount := c.flowInvocationCount t := c.checkExpressionEx(node, CheckModeTypeOnly) - // If control flow analysis was required to determine the type, it is worth caching. if c.flowInvocationCount != startInvocationCount { if c.flowTypeCache == nil { c.flowTypeCache = make(map[*ast.Node]*Type) } c.flowTypeCache[node] = t + c.endMemo() + } else { + c.endMemoUncached() } return t } @@ -7764,7 +7988,24 @@ func (c *Checker) checkExpressionCachedEx(node *ast.Node, checkMode CheckMode) * return c.checkExpressionEx(node, checkMode) } links := c.typeNodeLinks.Get(node) - if links.resolvedType == nil { + if t := links.resolvedType.get(c); t != nil { + return t + } + if c.memoActive() { + links.resolvedType.begin(c) + result := c.checkExpressionCachedExUncached(node, checkMode) + c.endMemo() + return result + } + return c.checkExpressionCachedExUncached(node, checkMode) +} + +func (c *Checker) checkExpressionCachedExUncached(node *ast.Node, checkMode CheckMode) *Type { + if checkMode != CheckModeNormal { + return c.checkExpressionEx(node, checkMode) + } + links := c.typeNodeLinks.Get(node) + if links.resolvedType.get(c) == nil { // When computing a type that we're going to cache, we need to ignore any ongoing control flow // analysis because variables may have transient types in indeterminable states. Moving flowLoopStart // to the top of the stack ensures all transient types are computed from a known point. @@ -7772,11 +8013,11 @@ func (c *Checker) checkExpressionCachedEx(node *ast.Node, checkMode CheckMode) * saveFlowTypeCache := c.flowTypeCache c.flowLoopStack = nil c.flowTypeCache = nil - links.resolvedType = c.checkExpressionEx(node, checkMode) + links.resolvedType.set(c, c.checkExpressionEx(node, checkMode)) c.flowTypeCache = saveFlowTypeCache c.flowLoopStack = saveFlowLoopStack } - return links.resolvedType + return links.resolvedType.get(c) } // Returns the type of an expression. Unlike checkExpression, this function is simply concerned @@ -7785,12 +8026,14 @@ func (c *Checker) checkExpressionCachedEx(node *ast.Node, checkMode CheckMode) * // and requesting the contextual type might cause a circularity or other bad behaviour. // It sets the contextual type of the node to any before calling getTypeOfExpression. func (c *Checker) getContextFreeTypeOfExpression(node *ast.Node) *Type { - if cached := c.contextFreeTypes[node]; cached != nil { + if cached, _ := c.contextFreeTypes.get(c, node); cached != nil { return cached } + c.contextFreeTypes.begin(c, node) c.pushContextualType(node, c.anyType, false /*isCache*/) t := c.checkExpressionEx(node, CheckModeSkipContextSensitive) - c.contextFreeTypes[node] = t + c.contextFreeTypes.set(c, node, t) + c.endMemo() c.popContextualType() return t } @@ -7803,15 +8046,19 @@ func (c *Checker) checkExpressionEx(node *ast.Node, checkMode CheckMode) *Type { if tr := c.tracer; tr != nil { defer tr.Push(tracing.PhaseCheck, "checkExpression", map[string]any{"kind": node.Kind, "pos": node.Pos(), "end": node.End(), "path": ast.GetSourceFileOfNode(node).FileName()}, false)() } - saveCurrentNode := c.currentNode - c.currentNode = node + saveCurrentNode, shared := c.enterSharedCurrentNode(node) + if !shared { + saveCurrentNode = c.enterCurrentNode(node) + } c.instantiationCount = 0 uninstantiatedType := c.checkExpressionWorker(node, checkMode) t := c.instantiateTypeWithSingleGenericCallSignature(node, uninstantiatedType, checkMode) if isConstEnumObjectType(t) { c.checkConstEnumAccess(node, t) } - c.currentNode = saveCurrentNode + if !c.exitSharedCurrentNode(saveCurrentNode) { + c.exitCurrentNode(saveCurrentNode) + } return t } @@ -8090,10 +8337,10 @@ func (c *Checker) checkPrivateIdentifierExpression(node *ast.Node) *Type { func (c *Checker) getSymbolForPrivateIdentifierExpression(node *ast.Node) *ast.Symbol { links := c.symbolNodeLinks.Get(node) - if links.resolvedSymbol == nil { - links.resolvedSymbol = c.lookupSymbolForPrivateIdentifierDeclaration(node.Text(), node) + if links.resolvedSymbol.get(c) == nil { + links.resolvedSymbol.set(c, c.lookupSymbolForPrivateIdentifierDeclaration(node.Text(), node)) } - return links.resolvedSymbol + return links.resolvedSymbol.get(c) } func (c *Checker) checkSuperExpression(node *ast.Node) *Type { @@ -8255,11 +8502,9 @@ func (c *Checker) isTemplateLiteralContextualType(t *Type) bool { } func (c *Checker) checkRegularExpressionLiteral(node *ast.Node) *Type { - nodeLinks := c.nodeLinks.Get(node) - if nodeLinks.flags&NodeCheckFlagsTypeChecked == 0 { - nodeLinks.flags |= NodeCheckFlagsTypeChecked + c.checkedRegularExpressions.once(c, node, func() { c.checkGrammarRegularExpressionLiteral(node.AsRegularExpressionLiteral()) - } + }) return c.globalRegExpType } @@ -8272,7 +8517,7 @@ func (c *Checker) checkArrayLiteral(node *ast.Node, checkMode CheckMode) *Type { inConstContext := c.isConstContext(node) contextualType := c.getApparentTypeOfContextualType(node, ContextFlagsNone) inTupleContext := isSpreadIntoCallOrNew(node) || contextualType != nil && someType(contextualType, func(t *Type) bool { - return c.isTupleLikeType(t) || c.isGenericMappedType(t) && t.AsMappedType().nameType == nil && c.getHomomorphicTypeVariable(core.OrElse(t.AsMappedType().target, t)) != nil + return c.isTupleLikeType(t) || c.isGenericMappedType(t) && c.getNameTypeFromMappedType(t) == nil && c.getHomomorphicTypeVariable(core.OrElse(t.AsMappedType().target, t)) != nil }) hasOmittedExpression := false for i, e := range elements { @@ -8350,12 +8595,12 @@ func (c *Checker) createArrayLiteralType(t *Type) *Type { return t } key := CachedTypeKey{kind: CachedTypeKindArrayLiteralType, typeId: t.id} - if cached, ok := c.cachedTypes[key]; ok { + if cached, ok := c.cachedTypes.get(c, key); ok { return cached } literalType := c.cloneTypeReference(t) literalType.objectFlags |= ObjectFlagsArrayLiteral | ObjectFlagsContainsObjectOrArrayLiteral - c.cachedTypes[key] = literalType + c.cachedTypes.intern(key, literalType) return literalType } @@ -8706,20 +8951,34 @@ func (c *Checker) getResolvedSignature(node *ast.Node, candidatesOutArray *[]*Si // Resolution is located at the call however it was requested, such as // from contextually typing an argument, so that errors it reports at the // current node do not depend on which was checked first. - saveCurrentNode := c.currentNode - c.currentNode = node - result := c.resolveSignatureOfCall(node, candidatesOutArray, checkMode) - c.currentNode = saveCurrentNode + saveCurrentNode, shared := c.enterSharedCurrentNode(node) + if !shared { + saveCurrentNode = c.enterCurrentNode(node) + } + var result *Signature + if c.memoActive() { + c.signatureLinks.Get(node).resolvedSignature.begin(c) + result = c.getResolvedSignatureUncached(node, candidatesOutArray, checkMode) + c.endMemo() + } else { + result = c.getResolvedSignatureUncached(node, candidatesOutArray, checkMode) + } + if !c.exitSharedCurrentNode(saveCurrentNode) { + c.exitCurrentNode(saveCurrentNode) + } return result } -func (c *Checker) resolveSignatureOfCall(node *ast.Node, candidatesOutArray *[]*Signature, checkMode CheckMode) *Signature { +func (c *Checker) getResolvedSignatureUncached(node *ast.Node, candidatesOutArray *[]*Signature, checkMode CheckMode) *Signature { links := c.signatureLinks.Get(node) // If getResolvedSignature has already been called, we will have cached the resolvedSignature. // However, it is possible that either candidatesOutArray was not passed in the first time, // or that a different candidatesOutArray was passed in. Therefore, we need to redo the work // to correctly fill the candidatesOutArray. - cached := links.resolvedSignature + cached := links.resolvedSignature.get(c) + if cached == c.resolvingSignature && c.isSignatureResolutionHidden(links) { + cached = nil + } if cached != nil && cached != c.resolvingSignature && candidatesOutArray == nil { return cached } @@ -8732,7 +8991,10 @@ func (c *Checker) resolveSignatureOfCall(node *ast.Node, candidatesOutArray *[]* if (ast.IsCallExpression(node) || ast.IsNewExpression(node)) && isContextuallyTypedByEnclosingCall(node) { c.getContextualType(node, ContextFlagsNone) } - cached = links.resolvedSignature + cached = links.resolvedSignature.get(c) + if cached == c.resolvingSignature && c.isSignatureResolutionHidden(links) { + cached = nil + } if cached != nil && cached != c.resolvingSignature && candidatesOutArray == nil { return cached } @@ -8745,10 +9007,12 @@ func (c *Checker) resolveSignatureOfCall(node *ast.Node, candidatesOutArray *[]* // through resolution of the containing function call. By resetting the resolution stack we'll // retry the symbol type resolution with the resolvingSignature marker in place to suppress // the contextual type circularity. - c.resolutionStart = len(c.typeResolutions) + c.resolutionStart = len(c.resolutions) } - links.resolvedSignature = c.resolvingSignature + links.resolvedSignature.swap(c.resolvingSignature) + c.signaturesResolving = append(c.signaturesResolving, signatureResolving{links, len(c.resolutions)}) result := c.resolveSignature(node, candidatesOutArray, checkMode) + c.signaturesResolving = c.signaturesResolving[:len(c.signaturesResolving)-1] c.resolutionStart = saveResolutionStart // When CheckMode.SkipGenericFunctions is set we use resolvingSignature to indicate that call // resolution should be deferred. @@ -8758,21 +9022,51 @@ func (c *Checker) resolveSignatureOfCall(node *ast.Node, candidatesOutArray *[]* // since resolving such signature leads to resolving the potential outer signature, its arguments and thus the very same signature // it's possible that this inner resolution sets the resolvedSignature first. // In such a case we ignore the local result and reuse the correct one that was cached. - if links.resolvedSignature != c.resolvingSignature { - result = links.resolvedSignature + if links.resolvedSignature.get(c) != c.resolvingSignature { + result = links.resolvedSignature.get(c) } // If signature resolution originated in control flow type analysis (for example to compute the // assigned type in a flow assignment) we don't cache the result as it may be based on temporary // types from the control flow analysis. if len(c.flowLoopStack) == 0 { - links.resolvedSignature = result + c.setResolvedSignature(links, result) } else { - links.resolvedSignature = cached + links.resolvedSignature.swap(cached) } } return result } +// setResolvedSignature caches the signature a call resolves to, in place of +// the marker of its resolution, which is only restored while the resolution +// is still in progress. +func (c *Checker) setResolvedSignature(links *SignatureLinks, signature *Signature) { + setAfterMarker(c, &links.resolvedSignature, signature, links, isStaleSignatureMarker) +} + +func isStaleSignatureMarker(c *Checker, links *SignatureLinks, s *Signature) bool { + return s == c.resolvingSignature && !slices.ContainsFunc(c.signaturesResolving, func(r signatureResolving) bool { return r.links == links }) +} + +type signatureResolving struct { + links *SignatureLinks + start int // Index in resolutions on entry +} + +// isSignatureResolutionHidden reports whether the resolution of the call +// with the given links is in progress below what is visible; see yieldCycle. +func (c *Checker) isSignatureResolutionHidden(links *SignatureLinks) bool { + if c.visibleStart == 0 { + return false + } + for _, r := range slices.Backward(c.signaturesResolving) { + if r.links == links { + return r.start < c.visibleStart + } + } + return false +} + func (c *Checker) resolveSignature(node *ast.Node, candidatesOutArray *[]*Signature, checkMode CheckMode) *Signature { switch node.Kind { case ast.KindCallExpression: @@ -9254,15 +9548,18 @@ func (c *Checker) resolveCall(node *ast.Node, signatures []*Signature, candidate // is just important for choosing the best signature. So in the case where there is only one // signature, the subtype pass is useless. So skipping it is an optimization. var result *Signature - s.recursiveResolution = slices.Contains(c.callResolutionStack, s.node) - c.callResolutionStack = append(c.callResolutionStack, s.node) + if i := c.resolvingAnywhereIndex(s.node, TypeSystemPropertyNameCallResolution); i >= 0 && !c.yieldCycle(i) { + c.markTypeResolutionCircular(i) + s.recursiveResolution = true + } + c.beginTypeResolution(s.node, TypeSystemPropertyNameCallResolution) if len(s.candidates) > 1 { result = c.chooseOverload(&s, c.subtypeRelation) } if result == nil { result = c.chooseOverload(&s, c.assignableRelation) } - c.callResolutionStack = c.callResolutionStack[:len(c.callResolutionStack)-1] + c.popTypeResolution() if result != nil { return result } @@ -9274,7 +9571,7 @@ func (c *Checker) resolveCall(node *ast.Node, signatures []*Signature, candidate // don't hit this issue because they only observe this result after it's had a chance to // be cached, but the error reporting code below executes before getResolvedSignature sets // resolvedSignature. - c.signatureLinks.Get(node).resolvedSignature = result + c.signatureLinks.Get(node).resolvedSignature.set(c, result) // No signatures were applicable. Now report errors based on the last applicable signature with // no arguments excluded from assignability checks. // If candidate is undefined, it means that no candidates had a suitable arity. In that case, @@ -9350,12 +9647,14 @@ func (c *Checker) getOptionalCallSignature(signature *Signature, callChainFlags return signature } key := CachedSignatureKey{sig: signature, key: core.IfElse(callChainFlags == SignatureFlagsIsInnerCallChain, SignatureKeyInner, SignatureKeyOuter)} - if cached := c.cachedSignatures[key]; cached != nil { + if cached, _ := c.cachedSignatures.get(c, key); cached != nil { return cached } + c.cachedSignatures.begin(c, key) result := c.cloneSignature(signature) result.flags |= callChainFlags - c.cachedSignatures[key] = result + c.cachedSignatures.set(c, key, result) + c.endMemo() return result } @@ -10468,14 +10767,16 @@ func (c *Checker) checkFunctionExpressionOrObjectLiteralMethod(node *ast.Node, c // Return plain anyFunctionType if there is no possibility we'll make inferences from the return type contextualSignature := c.getContextualSignature(node) if contextualSignature != nil && c.couldContainTypeVariables(c.getReturnTypeOfSignature(contextualSignature)) { - if cached, ok := c.contextFreeTypes[node]; ok { + if cached, ok := c.contextFreeTypes.get(c, node); ok { return cached } + c.contextFreeTypes.begin(c, node) returnType := c.getReturnTypeFromBody(node, checkMode) returnOnlySignature := c.newSignature(SignatureFlagsIsNonInferrable, nil, nil /*typeParameters*/, nil /*thisParameter*/, nil, returnType, nil /*resolvedTypePredicate*/, 0) returnOnlyType := c.newAnonymousType(node.Symbol(), nil, []*Signature{returnOnlySignature}, nil, nil) returnOnlyType.objectFlags |= ObjectFlagsNonInferrableType - c.contextFreeTypes[node] = returnOnlyType + c.contextFreeTypes.set(c, node, returnOnlyType) + c.endMemo() return returnOnlyType } } @@ -10497,19 +10798,33 @@ func (c *Checker) checkFunctionExpressionOrObjectLiteralMethod(node *ast.Node, c func (c *Checker) contextuallyCheckFunctionExpressionOrObjectLiteralMethod(node *ast.Node, checkMode CheckMode) { c.assignContextualParameterTypesOfFunction(node, checkMode) + c.beginMemo(memoKeyOf(node, memoContextChecked)) + c.contextuallyCheckFunctionExpressionOrObjectLiteralMethodUncached(node, checkMode) + c.endMemo() +} + +func (c *Checker) contextuallyCheckFunctionExpressionOrObjectLiteralMethodUncached(node *ast.Node, checkMode CheckMode) { links := c.nodeLinks.Get(node) if links.flags&NodeCheckFlagsContextChecked == 0 || links.flags&NodeCheckFlagsSignatureContextChecked != 0 { return } - links.flags |= NodeCheckFlagsSignatureContextChecked + setFlagMemoBits(c, memoKeyOf(node, memoContextChecked), &links.flags, NodeCheckFlagsSignatureContextChecked, NodeCheckFlagsSignatureContextChecked) signature := c.getSignatureFromDeclarationUncontextual(node) contextualSignature := c.getContextualSignature(node) - if contextualSignature != nil && c.getReturnTypeFromAnnotation(node) == nil && signature.resolvedReturnType == nil { + // The return type is inferred now, while the contextual types of the + // function's enclosing expressions are in place; a later request may + // come from where they are not. + if contextualSignature != nil && checkMode&^CheckModeTypeOnly == CheckModeNormal && c.getReturnTypeFromAnnotation(node) == nil && !c.isResolvingReturnTypeOfSignature(signature) { + c.getReturnTypeOfSignature(signature) + } else if contextualSignature != nil && c.getReturnTypeFromAnnotation(node) == nil && signature.resolvedReturnType.get(c) == nil && !c.isResolvingReturnTypeOfSignature(signature) && c.pushTypeResolution(signature, TypeSystemPropertyNameResolvedReturnType) { // resolvedReturnType is cached indefinitely, so the return type here has to be computed without CheckModeSkipContextSensitive; // otherwise anyFunctionType could leak as part of the computed (and cached) return type. returnType := c.getReturnTypeFromBody(node, checkMode&^CheckModeSkipContextSensitive) - if signature.resolvedReturnType == nil { - signature.resolvedReturnType = returnType + if !c.popTypeResolution() { + returnType = c.circularReturnType(signature) + } + if signature.resolvedReturnType.get(c) == nil { + signature.resolvedReturnType.set(c, returnType) } } c.checkSignatureDeclaration(node) @@ -10521,6 +10836,12 @@ func (c *Checker) contextuallyCheckFunctionExpressionOrObjectLiteralMethod(node // signature of the function is not complete until then, so // getSignatureFromDeclaration requests this before returning it. func (c *Checker) assignContextualParameterTypesOfFunction(node *ast.Node, checkMode CheckMode) { + c.beginMemo(memoKeyOf(node, memoContextualParameters)) + c.assignContextualParameterTypesOfFunctionUncached(node, checkMode) + c.endMemo() +} + +func (c *Checker) assignContextualParameterTypesOfFunctionUncached(node *ast.Node, checkMode CheckMode) { links := c.nodeLinks.Get(node) if links.flags&NodeCheckFlagsContextChecked == 0 { contextualSignature := c.getContextualSignature(node) @@ -10528,8 +10849,9 @@ func (c *Checker) assignContextualParameterTypesOfFunction(node *ast.Node, check // contextual type may recursively get back to here during overload resolution of the call. If so, we will have // already assigned contextual types. if links.flags&NodeCheckFlagsContextChecked == 0 { - links.flags |= NodeCheckFlagsContextChecked + setFlagMemoBits(c, memoKeyOf(node, memoContextualParameters), &links.flags, NodeCheckFlagsContextChecked, NodeCheckFlagsContextChecked) signature := c.getSignatureFromDeclarationUncontextual(node) + c.pushTypeResolution(node, TypeSystemPropertyNameContextChecked) if c.isContextSensitive(node) { if contextualSignature != nil { inferenceContext := c.getInferenceContext(node) @@ -10559,8 +10881,11 @@ func (c *Checker) assignContextualParameterTypesOfFunction(node *ast.Node, check c.inferFromAnnotatedParametersAndReturn(signature, contextualSignature, inferenceContext) } } + c.popTypeResolution() + return } } + c.observeResolutionInProgress(node, TypeSystemPropertyNameContextChecked) } func (c *Checker) checkFunctionExpressionOrObjectLiteralMethodDeferred(node *ast.Node) { @@ -10715,6 +11040,9 @@ func (c *Checker) assignContextualParameterTypes(sig *Signature, context *Signat // This signature has already has a contextual inference performed and cached on it return } + if c.logResults() { + c.logFlagMemoDiscard(func() { sig.setTypeParameters(c, nil) }) + } sig.setTypeParameters(c, context.TypeParameters()) } if context.ThisParameter() != nil { @@ -10766,7 +11094,7 @@ func (c *Checker) assignNonContextualParameterTypes(signature *Signature) { func (c *Checker) assignParameterType(parameter *ast.Symbol, contextualType *Type) { links := c.valueSymbolLinks.Get(parameter) - if links.resolvedType != nil { + if links.resolvedType.get(c) != nil { return } declaration := parameter.ValueDeclaration() @@ -10778,13 +11106,13 @@ func (c *Checker) assignParameterType(parameter *ast.Symbol, contextualType *Typ t = c.getTypeOfSymbol(parameter) } } - links.resolvedType = c.addOptionalityEx(t, false, declaration != nil && declaration.Initializer() == nil && isOptionalDeclaration(declaration)) + links.resolvedType.set(c, c.addOptionalityEx(t, false, declaration != nil && declaration.Initializer() == nil && isOptionalDeclaration(declaration))) if declaration != nil && !ast.IsIdentifier(declaration.Name()) { // if inference didn't come up with anything but unknown, fall back to the binding pattern if present. - if links.resolvedType == c.unknownType { - links.resolvedType = c.getTypeFromBindingPattern(declaration.Name(), false, false) + if links.resolvedType.get(c) == c.unknownType { + links.resolvedType.set(c, c.getTypeFromBindingPattern(declaration.Name(), false, false)) } - c.assignBindingElementTypes(declaration.Name(), links.resolvedType) + c.assignBindingElementTypes(declaration.Name(), links.resolvedType.get(c)) } } @@ -10796,7 +11124,7 @@ func (c *Checker) assignBindingElementTypes(pattern *ast.Node, parentType *Type) if name != nil { t := c.getBindingElementTypeFromParentType(element, parentType, false /*noTupleBoundsCheck*/) if ast.IsIdentifier(name) { - c.valueSymbolLinks.Get(c.getSymbolOfDeclaration(element)).resolvedType = t + c.valueSymbolLinks.Get(c.getSymbolOfDeclaration(element)).resolvedType.set(c, t) } else { c.assignBindingElementTypes(name, t) } @@ -10902,7 +11230,7 @@ func (c *Checker) setNodeLinksForPrivateIdentifierScope(node *ast.Node) { func (c *Checker) recordPotentialCollisionWithWeakMapSetInGeneratedCode(node *ast.Node, name *ast.Node) { if c.languageVersion <= core.ScriptTargetES2021 && (c.needCollisionCheckForIdentifier(node, name, "WeakMap") || c.needCollisionCheckForIdentifier(node, name, "WeakSet")) { - c.addDeferredDiagnostic(func() { + c.addDeferredDiagnostic(func(c *Checker) { c.checkWeakMapSetCollision(node) }) } @@ -10936,7 +11264,7 @@ func (c *Checker) checkCollisionWithGlobalPromiseInGeneratedCode(node *ast.Node, func (c *Checker) recordPotentialCollisionWithReflectInGeneratedCode(node *ast.Node, name *ast.Node) { if name != nil && c.languageVersion <= core.ScriptTargetES2021 && c.needCollisionCheckForIdentifier(node, name, "Reflect") { - c.addDeferredDiagnostic(func() { + c.addDeferredDiagnostic(func(c *Checker) { c.checkReflectCollision(node) }) } @@ -11026,8 +11354,24 @@ func (c *Checker) getInstantiationExpressionType(exprType *Type, node *ast.Node) return exprType } key := InstantiationExpressionKey{nodeId: ast.GetNodeId(node), typeId: exprType.id} - if cached := c.instantiationExpressionTypes[key]; cached != nil { - return cached + entry, _ := c.instantiationExpressionTypes.get(key) + if entry == nil { + entry = &instantiationExpressionEntry{} + c.instantiationExpressionTypes.intern(key, entry) + } + c.beginMemo(memoKeyOf(entry, memoInstantiationExpressionType)) + result := c.getInstantiationExpressionTypeCached(exprType, node, typeArguments, entry) + c.endMemo() + return result +} + +type instantiationExpressionEntry struct { + t *Type +} + +func (c *Checker) getInstantiationExpressionTypeCached(exprType *Type, node *ast.Node, typeArguments *ast.NodeList, entry *instantiationExpressionEntry) *Type { + if entry.t != nil { + return entry.t } hasSomeApplicableSignature := false var nonApplicableType *Type @@ -11060,7 +11404,7 @@ func (c *Checker) getInstantiationExpressionType(exprType *Type, node *ast.Node) debug.Assert(t.symbol != nil, "Instantiation expression source type must have a symbol") symbol.SetDeclarations(t.symbol.Declarations()) result := c.newObjectType(ObjectFlagsAnonymous|ObjectFlagsInstantiationExpressionType, symbol) - c.setStructuredTypeMembers(result, resolved.getMembers(), callSignatures, constructSignatures, resolved.getIndexInfos()) + c.setStructuredTypeMembers(result, resolved.getMembers(c), callSignatures, constructSignatures, resolved.getIndexInfos()) result.AsInstantiationExpressionType().node = node return result } @@ -11089,7 +11433,7 @@ func (c *Checker) getInstantiationExpressionType(exprType *Type, node *ast.Node) return result } result := getInstantiatedType(exprType) - c.instantiationExpressionTypes[key] = result + c.setFlagMemoValue(memoKeyOf(entry, memoInstantiationExpressionType), &entry.t, result) var errorType *Type if hasSomeApplicableSignature { errorType = nonApplicableType @@ -11585,6 +11929,13 @@ func (c *Checker) removeOptionalityFromDeclaredType(declaredType *Type, declarat } func (c *Checker) parameterInitializerContainsUndefined(declaration *ast.Node) bool { + c.beginMemo(memoKeyOf(declaration, memoInitializerIsUndefined)) + result := c.parameterInitializerContainsUndefinedUncached(declaration) + c.endMemo() + return result +} + +func (c *Checker) parameterInitializerContainsUndefinedUncached(declaration *ast.Node) bool { links := c.nodeLinks.Get(declaration) if links.flags&NodeCheckFlagsInitializerIsUndefinedComputed == 0 { if !c.pushTypeResolution(declaration, TypeSystemPropertyNameInitializerIsUndefined) { @@ -11597,7 +11948,7 @@ func (c *Checker) parameterInitializerContainsUndefined(declaration *ast.Node) b return true } if links.flags&NodeCheckFlagsInitializerIsUndefinedComputed == 0 { - links.flags |= NodeCheckFlagsInitializerIsUndefinedComputed | core.IfElse(containsUndefined, NodeCheckFlagsInitializerIsUndefined, 0) + setFlagMemoBits(c, memoKeyOf(declaration, memoInitializerIsUndefined), &links.flags, NodeCheckFlagsInitializerIsUndefinedComputed|NodeCheckFlagsInitializerIsUndefined, NodeCheckFlagsInitializerIsUndefinedComputed|core.IfElse(containsUndefined, NodeCheckFlagsInitializerIsUndefined, 0)) } } return links.flags&NodeCheckFlagsInitializerIsUndefined != 0 @@ -11712,7 +12063,7 @@ func (c *Checker) checkPropertyAccessExpressionOrQualifiedName(node *ast.Node, l return c.anyType } if right.Text() != "" && !c.checkAndReportErrorForExtendingInterface(node) { - c.addDeferredDiagnostic(func() { + c.addDeferredDiagnostic(func(c *Checker) { // must be deferred because reporting this error can cause us to materialize the containing type completely (to print it), leading to erroneous circularity errors c.reportNonexistentProperty(right, core.IfElse(isThisTypeParameter(leftType), apparentType, leftType), isUncheckedJS) }) @@ -11739,7 +12090,7 @@ func (c *Checker) checkPropertyAccessExpressionOrQualifiedName(node *ast.Node, l } c.checkPropertyNotUsedBeforeDeclaration(prop, node, right) c.markPropertyAsReferenced(prop, node, c.isSelfTypeAccess(left, parentSymbol)) - c.symbolNodeLinks.Get(node).resolvedSymbol = prop + setIfChanged(c, &c.symbolNodeLinks.Get(node).resolvedSymbol, prop) c.checkPropertyAccessibility(node, left.Kind == ast.KindSuperKeyword, ast.IsWriteAccess(node), apparentType, prop) if c.isAssignmentToReadonlyEntity(node, prop, assignmentKind) { c.error(right, diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, right.Text()) @@ -11897,16 +12248,16 @@ func (c *Checker) checkPrivateIdentifierPropertyAccess(leftType *Type, right *as } func (c *Checker) reportNonexistentProperty(propNode *ast.Node, containingType *Type, isUncheckedJS bool) { - key := NonExistentPropertyKey{propNode, containingType, isUncheckedJS} - if c.nonExistentProperties.Has(key) { + if !c.checking { + c.recordEffect(func(c *Checker) { c.reportNonexistentProperty(propNode, containingType, isUncheckedJS) }) return } - c.nonExistentProperties.Add(key) - links := c.nodeLinks.Get(propNode) - if links.flags&NodeCheckFlagsTypeChecked != 0 { - return // error already made/in progress + // Only the first report for a name is made, even if it is reported again + // on another containing type. + if c.nonExistentPropertyNodes.Has(propNode) { + return } - links.flags |= NodeCheckFlagsTypeChecked + c.nonExistentPropertyNodes.Add(propNode) if ast.IsJSDocNameReferenceContext(propNode) { return } @@ -12670,7 +13021,7 @@ func (c *Checker) checkAssertion(node *ast.Node, checkMode CheckMode) *Type { var exprType *Type if isConst { exprType = c.checkExpressionEx(node.Expression(), checkMode) - } else if c.resolvingAssertionOperands.AddIfAbsent(node) { + } else if !c.isResolvingAnywhere(node, TypeSystemPropertyNameAssertionOperand) { // Only the deferred check uses the operand type, and it uses the // cached one, so compute that here rather than checking twice. The // operand is contextually typed by the asserted type alone; hiding @@ -12680,10 +13031,11 @@ func (c *Checker) checkAssertion(node *ast.Node, checkMode CheckMode) *Type { // assertion is re-entered when the operand's flow type depends on // a call containing it within a loop; the enclosing computation // then provides the cached type. + c.beginTypeResolution(node, TypeSystemPropertyNameAssertionOperand) c.pushInferenceContext(node.Expression(), nil) c.checkExpressionCached(node.Expression()) c.popInferenceContext() - c.resolvingAssertionOperands.Delete(node) + c.popTypeResolution() } // Always check the type node so its identifiers are resolved. resolveName knows not // to resolve (or report an error for) the `const` in a `const` assertion, so this is @@ -13153,7 +13505,7 @@ func (c *Checker) checkAssignmentOperator(left *ast.Node, operator ast.Kind, rig if ast.IsAssignmentOperator(operator) { // We ignore assignments of undefined to CommonJS exports when there are multiple assignment declarations if ast.IsDeclarationNode(left.Parent) && ast.GetAssignmentDeclarationKind(left.Parent) == ast.JSDeclarationKindExportsProperty { - if symbol := c.symbolNodeLinks.Get(left).resolvedSymbol; symbol != nil && len(symbol.Declarations()) > 1 && rightType.flags&TypeFlagsUndefined != 0 { + if symbol := c.symbolNodeLinks.Get(left).resolvedSymbol.get(c); symbol != nil && len(symbol.Declarations()) > 1 && rightType.flags&TypeFlagsUndefined != 0 { return } } @@ -13481,7 +13833,7 @@ func (c *Checker) checkInExpression(left *ast.Expression, right *ast.Expression, } // Unlike in 'checkPrivateIdentifierExpression' we now have access to the RHS type // which provides us with the opportunity to emit more detailed errors - if c.symbolNodeLinks.Get(left).resolvedSymbol == nil && ast.GetContainingClass(left) != nil { + if c.symbolNodeLinks.Get(left).resolvedSymbol.get(c) == nil && ast.GetContainingClass(left) != nil { isUncheckedJS := c.isUncheckedJSSuggestion(left, rightType.symbol, true /*excludeClasses*/) c.reportNonexistentProperty(left, rightType, isUncheckedJS) } @@ -13562,7 +13914,7 @@ func (c *Checker) checkObjectLiteral(node *ast.Node, checkMode CheckMode) *Type contextualType := c.getApparentTypeOfContextualType(node, ContextFlagsNone) var contextualTypeHasPattern bool if contextualType != nil { - if pattern := c.patternForType[contextualType]; pattern != nil && (ast.IsObjectBindingPattern(pattern) || ast.IsObjectLiteralExpression(pattern)) { + if pattern, _ := c.patternForType.get(contextualType); pattern != nil && (ast.IsObjectBindingPattern(pattern) || ast.IsObjectLiteralExpression(pattern)) { contextualTypeHasPattern = true } } @@ -13604,16 +13956,18 @@ func (c *Checker) checkObjectLiteral(node *ast.Node, checkMode CheckMode) *Type if patternWithComputedProperties { flags |= ObjectFlagsObjectLiteralPatternWithComputedProperties } - key := c.getObjectLiteralTypeKey(node, flags, propertiesTable, indexInfos) - if existing := c.objectLiteralTypes[key]; existing != nil { - return existing + existingTypes, _ := c.objectLiteralTypes.get(node) + for _, existing := range existingTypes { + if c.isSameObjectLiteralType(existing, flags, propertiesTable, indexInfos) { + return existing.t + } } result := c.newAnonymousType(node.Symbol(), propertiesTable, nil, nil, indexInfos) result.objectFlags |= flags if inDestructuringPattern { - c.patternForType[result] = node + c.patternForType.intern(result, node) } - c.objectLiteralTypes[key] = result + c.objectLiteralTypes.intern(node, append(existingTypes, objectLiteralType{flags, propertiesTable, indexInfos, result})) return result } for _, memberDecl := range node.Properties() { @@ -13664,7 +14018,7 @@ func (c *Checker) checkObjectLiteral(node *ast.Node, checkMode CheckMode) *Type prop.SetDeclarations(member.Declarations()) prop.SetParent(member.Parent()) prop.SetValueDeclaration(member.ValueDeclaration()) - links.resolvedType = t + links.resolvedType.setIdentity(c, t) links.target = member member = prop if allPropertiesTable != nil { @@ -13863,7 +14217,7 @@ func (c *Checker) getSpreadType(left *Type, right *Type, symbol *ast.Symbol, obj b.writeByte(0) } key := b.hash() - if existing := c.spreadTypes[key]; existing != nil { + if existing, _ := c.spreadTypes.get(c, key); existing != nil { return existing } members := ast.NewSymbolTable() @@ -13891,7 +14245,7 @@ func (c *Checker) getSpreadType(left *Type, right *Type, symbol *ast.Symbol, obj rightType := c.getTypeOfSymbol(rightProp) if rightProp.Flags()&ast.SymbolFlagsOptional != 0 { key := spreadSymbolKey{left: leftProp, right: rightProp} - if existing := c.spreadSymbols[key]; existing != nil { + if existing, _ := c.spreadSymbols.get(c, key); existing != nil { members.Set(leftProp.Name(), existing) continue } @@ -13907,15 +14261,15 @@ func (c *Checker) getSpreadType(left *Type, right *Type, symbol *ast.Symbol, obj leftTypeWithoutUndefined := c.removeMissingOrUndefinedType(leftType) rightTypeWithoutUndefined := c.removeMissingOrUndefinedType(rightType) if leftTypeWithoutUndefined == rightTypeWithoutUndefined { - links.resolvedType = leftType + links.resolvedType.setIdentity(c, leftType) } else { - links.resolvedType = c.getUnionTypeEx([]*Type{leftType, rightTypeWithoutUndefined}, UnionReductionSubtype, nil, nil) + links.resolvedType.setIdentity(c, c.getUnionTypeEx([]*Type{leftType, rightTypeWithoutUndefined}, UnionReductionSubtype, nil, nil)) } c.spreadLinks.Get(result).leftSpread = leftProp c.spreadLinks.Get(result).rightSpread = rightProp result.SetDeclarations(declarations) links.setNameType(c, c.valueSymbolLinks.Get(leftProp).getNameType()) - c.spreadSymbols[key] = result + c.spreadSymbols.set(c, key, result) members.Set(leftProp.Name(), result) } } else { @@ -13929,14 +14283,14 @@ func (c *Checker) getSpreadType(left *Type, right *Type, symbol *ast.Symbol, obj // Different inputs can produce the same members, e.g. when the right side // overrides every property of the left side. contentKey := c.getObjectTypeContentKey(symbol, flags, members, spreadIndexInfos) - if existing := c.objectContentTypes[contentKey]; existing != nil { - c.spreadTypes[key] = existing + if existing, _ := c.objectContentTypes.get(contentKey); existing != nil { + c.spreadTypes.set(c, key, existing) return existing } spread := c.newAnonymousType(symbol, members, nil, nil, spreadIndexInfos) spread.objectFlags |= flags - c.objectContentTypes[contentKey] = spread - c.spreadTypes[key] = spread + c.objectContentTypes.intern(contentKey, spread) + c.spreadTypes.set(c, key, spread) return spread } @@ -14007,9 +14361,9 @@ func (c *Checker) tryMergeUnionOfObjectTypeAndEmptyObject(t *Type, readonly bool result := c.newSymbolEx(flags, prop.Name(), prop.CheckFlags()&ast.CheckFlagsLate|core.IfElse(readonly, ast.CheckFlagsReadonly, 0)) links := c.valueSymbolLinks.Get(result) if isSetonlyAccessor { - links.resolvedType = c.undefinedType + links.resolvedType.setIdentity(c, c.undefinedType) } else { - links.resolvedType = c.addOptionalityEx(c.getTypeOfSymbol(prop), true /*isProperty*/, true /*isOptional*/) + links.resolvedType.setIdentity(c, c.addOptionalityEx(c.getTypeOfSymbol(prop), true /*isProperty*/, true /*isOptional*/)) } result.SetDeclarations(prop.Declarations()) links.setNameType(c, c.valueSymbolLinks.Get(prop).getNameType()) @@ -14034,21 +14388,21 @@ func (c *Checker) getSpreadSymbol(prop *ast.Symbol, readonly bool) *ast.Symbol { return prop } key := spreadSymbolKey{right: prop, readonly: readonly} - if existing := c.spreadSymbols[key]; existing != nil { + if existing, _ := c.spreadSymbols.get(c, key); existing != nil { return existing } flags := ast.SymbolFlagsProperty | (prop.Flags() & ast.SymbolFlagsOptional) result := c.newSymbolEx(flags, prop.Name(), prop.CheckFlags()&ast.CheckFlagsLate|core.IfElse(readonly, ast.CheckFlagsReadonly, 0)) links := c.valueSymbolLinks.Get(result) if isSetonlyAccessor { - links.resolvedType = c.undefinedType + links.resolvedType.setIdentity(c, c.undefinedType) } else { - links.resolvedType = c.getTypeOfSymbol(prop) + links.resolvedType.setIdentity(c, c.getTypeOfSymbol(prop)) } result.SetDeclarations(prop.Declarations()) links.setNameType(c, c.valueSymbolLinks.Get(prop).getNameType()) c.mappedSymbolLinks.Get(result).syntheticOrigin = prop - c.spreadSymbols[key] = result + c.spreadSymbols.set(c, key, result) return result } @@ -14276,16 +14630,15 @@ func (c *Checker) getNarrowedTypeOfSymbol(symbol *ast.Symbol, location *ast.Node } parent := declaration.Parent.Parent if ast.IsVariableDeclaration(rootDeclaration) && c.getCombinedNodeFlagsCached(rootDeclaration)&ast.NodeFlagsConstant != 0 || ast.IsParameterDeclaration(rootDeclaration) { - links := c.nodeLinks.Get(parent) - if links.flags&NodeCheckFlagsInCheckIdentifier == 0 { - links.flags |= NodeCheckFlagsInCheckIdentifier + if !c.isResolvingAnywhere(parent, TypeSystemPropertyNameBindingElementParent) { + c.beginTypeResolution(parent, TypeSystemPropertyNameBindingElementParent) parentType := c.getTypeForBindingElementParent(parent, CheckModeNormal) var parentTypeConstraint *Type if parentType != nil { parentTypeConstraint = c.mapType(parentType, c.getBaseConstraintOrType) } // Guard parent-type resolution only; flow analysis should allow re-entrant narrowing - links.flags &^= NodeCheckFlagsInCheckIdentifier + c.popTypeResolution() if parentTypeConstraint != nil && parentTypeConstraint.flags&TypeFlagsUnion != 0 && !(ast.IsParameterDeclaration(rootDeclaration) && c.isSomeSymbolAssigned(rootDeclaration)) { pattern := declaration.Parent narrowedType := c.getFlowTypeOfReferenceEx(pattern, parentTypeConstraint, parentTypeConstraint, nil /*flowContainer*/, getFlowNodeOfNode(location)) @@ -14406,23 +14759,37 @@ func (c *Checker) checkExpressionForMutableLocation(node *ast.Node, checkMode Ch func (c *Checker) getResolvedSymbol(node *ast.Node) *ast.Symbol { links := c.symbolNodeLinks.Get(node) - if links.resolvedSymbol == nil { + if symbol := links.resolvedSymbol.get(c); symbol != nil { + return symbol + } + if c.memoActive() { + links.resolvedSymbol.begin(c) + result := c.getResolvedSymbolUncached(node) + c.endMemo() + return result + } + return c.getResolvedSymbolUncached(node) +} + +func (c *Checker) getResolvedSymbolUncached(node *ast.Node) *ast.Symbol { + links := c.symbolNodeLinks.Get(node) + if links.resolvedSymbol.get(c) == nil { var symbol *ast.Symbol if !ast.NodeIsMissing(node) { symbol = c.resolveName(node, node.Text(), ast.SymbolFlagsValue|ast.SymbolFlagsExportValue, c.getCannotFindNameDiagnosticForName(node), !ast.IsWriteOnlyAccess(node), false /*excludeGlobals*/) } - links.resolvedSymbol = core.OrElse(symbol, c.unknownSymbol) + links.resolvedSymbol.set(c, core.OrElse(symbol, c.unknownSymbol)) } - return links.resolvedSymbol + return links.resolvedSymbol.get(c) } func (c *Checker) getResolvedSymbolOrNil(node *ast.Node) *ast.Symbol { - return c.symbolNodeLinks.Get(node).resolvedSymbol + return c.symbolNodeLinks.Get(node).resolvedSymbol.get(c) } func (c *Checker) getReferencedValueOrAliasSymbol(reference *ast.Node) *ast.Symbol { - resolvedSymbol := c.symbolNodeLinks.Get(reference).resolvedSymbol + resolvedSymbol := c.symbolNodeLinks.Get(reference).resolvedSymbol.get(c) if resolvedSymbol != nil && resolvedSymbol != c.unknownSymbol { return resolvedSymbol } @@ -14485,24 +14852,34 @@ func (c *Checker) getDiagnostics(ctx context.Context, sourceFile *ast.SourceFile func (c *Checker) GetGlobalDiagnostics() []*ast.Diagnostic { c.checkNotCanceled() - c.produceDeferredDiagnostics() + c.withChecking(c.produceDeferredDiagnostics) return c.diagnostics.GetGlobalDiagnostics() } -func (c *Checker) addDeferredDiagnostic(callback func()) { +// Deferred callbacks receive the checker that produces them, since the one +// that queued them may be a checker answering queries sharing this store. +func (c *Checker) addDeferredDiagnostic(callback func(c *Checker)) { + if !c.checking { + c.recordEffect(func(c *Checker) { c.addDeferredDiagnostic(callback) }) + return + } c.deferredDiagnosticCallbacks = append(c.deferredDiagnosticCallbacks, callback) } func (c *Checker) produceDeferredDiagnostics() { for _, cb := range c.deferredDiagnosticCallbacks { - cb() + cb(c) } c.deferredDiagnosticCallbacks = nil } func (c *Checker) addDiagnostic(diagnostic *ast.Diagnostic) *ast.Diagnostic { // Discard diagnostics created while at the maximum number of recursive TypeToString invocations. - if c.serializationLevel < maxSerializationLevel { + if !c.depthLimitReached(&c.serializationLevel, maxSerializationLevel) { + if !c.checking { + c.recordEffect(func(c *Checker) { c.diagnostics.Add(diagnostic) }) + return diagnostic + } return c.diagnostics.Add(diagnostic) } return diagnostic @@ -14510,7 +14887,11 @@ func (c *Checker) addDiagnostic(diagnostic *ast.Diagnostic) *ast.Diagnostic { func (c *Checker) addSuggestionDiagnostic(diagnostic *ast.Diagnostic) *ast.Diagnostic { // Discard diagnostics created while at the maximum number of recursive TypeToString invocations. - if c.serializationLevel < maxSerializationLevel { + if !c.depthLimitReached(&c.serializationLevel, maxSerializationLevel) { + if !c.checking { + c.recordEffect(func(c *Checker) { c.suggestionDiagnostics.Add(diagnostic) }) + return diagnostic + } return c.suggestionDiagnostics.Add(diagnostic) } return diagnostic @@ -14520,6 +14901,50 @@ func (c *Checker) error(location *ast.Node, message *diagnostics.Message, args . return c.addDiagnostic(NewDiagnosticForNode(location, message, args...)) } +// errorAtCurrentNode reports an error at the node currently being checked. +// When a result is computed outside of checking, there is no such node; the +// location is then resolved when checking first observes the result, which +// is where the error would have been reported had checking computed it. +func (c *Checker) errorAtCurrentNode(message *diagnostics.Message, args ...any) { + c.reportAtCurrentNode(func(c *Checker, node *ast.Node) { c.error(node, message, args...) }) +} + +// reportAtCurrentNode reports an error located at whichever node's check +// reached it, so that every node reaching it reports it, as it would if it +// were checked first. Results cached from now until the current node is left +// were computed for that node, and are discarded so that other nodes +// compute them again. +func (c *Checker) reportAtCurrentNode(report func(c *Checker, node *ast.Node)) { + c.reportNode(¤tNodeError{report}) +} + +func (c *Checker) reportNode(r *currentNodeError) { + c.markCurrentNodeCircular() + if c.currentNodeErrorCollectors != 0 { + c.currentNodeErrors = append(c.currentNodeErrors, r) + } + c.reportAtCurrentNodeOnce(r.report) +} + +// reportAtCurrentNodeOnce is reportAtCurrentNode for an error whose result +// is kept, so that only the node reaching it first reports it. +func (c *Checker) reportAtCurrentNodeOnce(report func(c *Checker, node *ast.Node)) { + if !c.checking { + // A node the computation started at is where the computation was + // requested from, so it is located wherever the result is reused. + node := c.currentNode + if c.atMemoStartNode() { + node = nil + } + if c.memoTop != 0 { + c.resolutions[c.memoTop-1].reportsAtCurrentNode = true + } + c.recordEffect(func(c *Checker) { report(c, core.OrElse(node, c.currentNode)) }) + return + } + report(c, c.currentNode) +} + func (c *Checker) errorSkippedOnNoEmit(location *ast.Node, message *diagnostics.Message, args ...any) *ast.Diagnostic { diagnostic := c.error(location, message, args...) diagnostic.SetSkippedOnNoEmit() @@ -14608,13 +15033,13 @@ func (c *Checker) newSymbolEx(flags ast.SymbolFlags, name string, checkFlags ast func (c *Checker) newParameter(name string, t *Type) *ast.Symbol { symbol := c.newSymbol(ast.SymbolFlagsFunctionScopedVariable, name) - c.valueSymbolLinks.Get(symbol).resolvedType = t + c.valueSymbolLinks.Get(symbol).resolvedType.setIdentity(c, t) return symbol } func (c *Checker) newProperty(name string, t *Type) *ast.Symbol { symbol := c.newSymbol(ast.SymbolFlagsProperty, name) - c.valueSymbolLinks.Get(symbol).resolvedType = t + c.valueSymbolLinks.Get(symbol).resolvedType.setIdentity(c, t) return symbol } @@ -14625,8 +15050,18 @@ func (c *Checker) combineSymbolTables(first ast.SymbolTable, second ast.SymbolTa if second.Len() == 0 { return first } - combined := ast.NewSymbolTable() - c.mergeSymbolTable(combined, first, false, nil) + // Late binding has merged into early symbols already; the merges are + // looked up without resolving the table, whose resolution is this one. + combined := ast.NewSymbolTableWithCapacity(first.Len() + second.Len()) + for id, symbol := range first.All() { + if merged := c.mergedSymbolOf(symbol); merged != nil { + symbol = merged + } + if merged, _ := c.lateMergedSymbols.get(c, symbol); merged != nil { + symbol = merged + } + combined.Set(id, symbol) + } c.mergeSymbolTable(combined, second, false, nil) return combined } @@ -14872,23 +15307,123 @@ func (c *Checker) cloneSymbol(symbol *ast.Symbol) *ast.Symbol { } func (c *Checker) getMergedSymbol(symbol *ast.Symbol) *ast.Symbol { - if symbol != nil { - if merged := c.mergedSymbols.TryGet(symbol); merged != nil && *merged != nil { - return *merged - } + if symbol == nil { + return nil + } + if merged := c.mergedSymbolOf(symbol); merged != nil { + symbol = merged + } + if symbol.Flags()&(ast.SymbolFlagsClassMember|ast.SymbolFlagsSignature) != 0 { + return c.getLateMergedSymbol(symbol) } return symbol } -func (c *Checker) getParentOfSymbol(symbol *ast.Symbol) *ast.Symbol { - if symbol.Parent() != nil { - return c.getMergedSymbol(c.getLateBoundSymbol(symbol.Parent())) +// getLateMergedSymbol returns the symbol a member is merged into when its +// parent's late-bound members are resolved, resolving them if they could +// merge with the member. The answer is thus the same whether or not anything +// has resolved the parent's members yet. +func (c *Checker) getLateMergedSymbol(symbol *ast.Symbol) *ast.Symbol { + if !c.mergesFrozen || symbol.Parent() == nil { + return symbol } - return nil -} - -func (c *Checker) recordMergedSymbol(target *ast.Symbol, source *ast.Symbol) { - *c.mergedSymbols.Get(source) = target + late := symbol.CheckFlags()&ast.CheckFlagsLate != 0 + var lateBindable uint8 + if !late { + // Most members cannot merge, which depends only on syntax and on + // the merges done before checking, so it is looked up cheaply. + if symbol.Parent() != c.lastLateBindableParent { + known := c.parentLateBindableKinds.Get(symbol.Parent()) + if *known == 0 { + *known = c.getLateBindableKinds(c.getMergedSymbol(symbol.Parent())) | lateBindableKnown + } + c.lastLateBindableParent = symbol.Parent() + c.lastLateBindableKinds = *known &^ lateBindableKnown + } + lateBindable = c.lastLateBindableKinds + if lateBindable == 0 { + return symbol + } + } + parent := c.getMergedSymbol(symbol.Parent()) + var kind MembersOrExportsResolutionKind + switch symbol { + case parent.Members().Get(symbol.Name()): + kind = MembersOrExportsResolutionKindResolvedMembers + case parent.Exports().Get(symbol.Name()): + kind = MembersOrExportsResolutionKindResolvedExports + default: + if !late { + return symbol + } + // Late-bound symbols are created by resolving their parent's members. + } + if !late { + if lateBindable&(1<= 0 { + return c.yieldMembers(key, symbol, resolutionKind, floor) + } + c.beginMemo(key) + result := c.getResolvedMembersOrExportsOfSymbolUncached(symbol, resolutionKind) + c.endMemo() + return result +} + +// findEarlierMembersInCycle handles re-entering the member table of symbol, +// identified by key, while it is resolved. Re-entry observes the part of the +// table published so far, so when the tables of several symbols are resolved +// in a cycle, the result depends on which table was entered first. If the +// table of a symbol declared before this one is resolved since this table +// was entered, it returns the index in resolutions of the first declared, +// the table the cycle canonically enters at; otherwise -1. +func (c *Checker) findEarlierMembersInCycle(key memoKey, symbol *ast.Symbol) int { + i, ok := c.resolving.get(key) + if !ok || int(i) < c.visibleStart || c.isYieldingTo(key, TypeSystemPropertyNameStructuredMembers) { + return -1 + } + for j := int(i) + 1; j < len(c.resolutions); j++ { + f := &c.resolutions[j] + if field := f.key.field(); f.key != 0 && (field == memoSymbolMembers || field == memoSymbolExports) { + if c.compareSymbols(f.key.symbol(), symbol) < 0 { + return int(i) + 1 + } + } + } + return -1 +} + +// yieldMembers computes the member table of symbol again, as if the cycle +// in progress had been entered at the table of a symbol declared before it: +// with the computation of the table in progress, which starts at floor, +// hidden. Re-entry observes the incomplete table, so what is computed since +// it started is in a cycle. The table in progress is the one cached once it +// is complete, so whatever computing it again caches is discarded, keeping +// only its result and effects for the caller. +func (c *Checker) yieldMembers(key memoKey, symbol *ast.Symbol, resolutionKind MembersOrExportsResolutionKind, floor int) ast.SymbolTable { + c.markCircular(floor - 1) + mark := c.markDiscard() + links := c.membersAndExportsLinks.Get(symbol) + saved := links[resolutionKind] + links[resolutionKind] = nil + c.beginCycleYield(key, TypeSystemPropertyNameStructuredMembers, -1, floor) + c.beginMemo(key) + result := c.resolveMembersOrExportsOfSymbol(symbol, resolutionKind, links) + c.endMemoUncached() + c.endCycleYield() + links[resolutionKind] = saved + c.discardLogSince(mark.discardLog) + c.releaseDiscardMark(mark) + return result +} + +func (c *Checker) getResolvedMembersOrExportsOfSymbolUncached(symbol *ast.Symbol, resolutionKind MembersOrExportsResolutionKind) ast.SymbolTable { links := c.membersAndExportsLinks.Get(symbol) if links[resolutionKind] == nil { - isStatic := resolutionKind == MembersOrExportsResolutionKindResolvedExports - earlySymbols := symbol.Exports() - switch { - case !isStatic: - earlySymbols = symbol.Members() - case symbol.Flags()&ast.SymbolFlagsModule != 0: - earlySymbols, _ = c.getExportsOfModuleWorker(symbol) - } - links[resolutionKind] = earlySymbols - // fill in any as-yet-unresolved late-bound members. - var lateSymbols ast.SymbolTable - for _, decl := range symbol.Declarations() { - for _, member := range getMembersOfDeclaration(decl) { - if isStatic == ast.HasStaticModifier(member) { - switch { - case c.hasLateBindableName(member): - if lateSymbols == nil { - lateSymbols = ast.NewSymbolTable() - } - c.lateBindMember(symbol, earlySymbols, lateSymbols, member) - case c.hasLateBindableIndexSignature(member): - if lateSymbols == nil { - lateSymbols = ast.NewSymbolTable() - } - c.lateBindIndexSignature(symbol, earlySymbols, lateSymbols, member.AsNode() /* as LateBoundDeclaration | LateBoundBinaryExpressionDeclaration */) + c.resolveMembersOrExportsOfSymbol(symbol, resolutionKind, links) + if c.logResults() { + c.discardLog = append(c.discardLog, discardEntry{nil, func() { links[resolutionKind] = nil }, false}) + } + } + return links[resolutionKind] +} + +func (c *Checker) resolveMembersOrExportsOfSymbol(symbol *ast.Symbol, resolutionKind MembersOrExportsResolutionKind, links *MembersAndExportsLinks) ast.SymbolTable { + isStatic := resolutionKind == MembersOrExportsResolutionKindResolvedExports + earlySymbols := symbol.Exports() + switch { + case !isStatic: + earlySymbols = symbol.Members() + case symbol.Flags()&ast.SymbolFlagsModule != 0: + earlySymbols, _ = c.getExportsOfModuleWorker(symbol) + } + links[resolutionKind] = earlySymbols + // fill in any as-yet-unresolved late-bound members. + var lateSymbols ast.SymbolTable + for _, decl := range symbol.Declarations() { + for _, member := range getMembersOfDeclaration(decl) { + if isStatic == ast.HasStaticModifier(member) { + switch { + case c.hasLateBindableName(member): + if lateSymbols == nil { + lateSymbols = ast.NewSymbolTable() + } + c.lateBindMember(symbol, earlySymbols, lateSymbols, member) + case c.hasLateBindableIndexSignature(member): + if lateSymbols == nil { + lateSymbols = ast.NewSymbolTable() } + c.lateBindIndexSignature(symbol, earlySymbols, lateSymbols, member.AsNode() /* as LateBoundDeclaration | LateBoundBinaryExpressionDeclaration */) } } } - if isStatic { - if assignmentSymbol := symbol.Exports().Get(ast.InternalSymbolNameAssignmentDeclaration); assignmentSymbol != nil { - for _, member := range assignmentSymbol.Declarations() { - if c.hasLateBindableName(member) { - if lateSymbols == nil { - lateSymbols = ast.NewSymbolTable() - } - c.lateBindMember(symbol, earlySymbols, lateSymbols, member) + } + if isStatic { + if assignmentSymbol := symbol.Exports().Get(ast.InternalSymbolNameAssignmentDeclaration); assignmentSymbol != nil { + for _, member := range assignmentSymbol.Declarations() { + if c.hasLateBindableName(member) { + if lateSymbols == nil { + lateSymbols = ast.NewSymbolTable() } + c.lateBindMember(symbol, earlySymbols, lateSymbols, member) } } } - links[resolutionKind] = c.combineSymbolTables(earlySymbols, lateSymbols) } + links[resolutionKind] = c.combineSymbolTables(earlySymbols, lateSymbols) return links[resolutionKind] } @@ -16670,10 +17280,10 @@ func (c *Checker) getResolvedMembersOrExportsOfSymbol(symbol *ast.Symbol, resolu func (c *Checker) lateBindMember(parent *ast.Symbol, earlySymbols ast.SymbolTable, lateSymbols ast.SymbolTable, decl *ast.Node) *ast.Symbol { debug.Assert(decl.Symbol() != nil, "The member is expected to have a symbol.") links := c.symbolNodeLinks.Get(decl) - if links.resolvedSymbol == nil { + if links.resolvedSymbol.get(c) == nil { // In the event we attempt to resolve the late-bound name of this member recursively, // fall back to the early-bound name of this member. - links.resolvedSymbol = decl.Symbol() + links.resolvedSymbol.set(c, decl.Symbol()) var declName *ast.Node if ast.IsBinaryExpression(decl) { declName = decl.AsBinaryExpression().Left @@ -16724,10 +17334,10 @@ func (c *Checker) lateBindMember(parent *ast.Symbol, earlySymbols ast.SymbolTabl if lateSymbol.Parent() == nil { lateSymbol.SetParent(parent) } - links.resolvedSymbol = lateSymbol + links.resolvedSymbol.set(c, lateSymbol) } } - return links.resolvedSymbol + return links.resolvedSymbol.get(c) } func (c *Checker) lateBindIndexSignature(parent *ast.Symbol, earlySymbols ast.SymbolTable, lateSymbols ast.SymbolTable, decl *ast.Node) { @@ -16795,10 +17405,22 @@ func (c *Checker) getMembersOfSymbol(symbol *ast.Symbol) ast.SymbolTable { func (c *Checker) getExportsOfModule(moduleSymbol *ast.Symbol) ast.SymbolTable { links := c.moduleSymbolLinks.Get(moduleSymbol) + key := memoKeyOf(links, memoModuleExports) + if links.resolvedExports != nil && c.readsWithoutMemo(key) { + return links.resolvedExports + } + c.beginMemo(key) + result := c.getExportsOfModuleCached(moduleSymbol, links) + c.endMemo() + return result +} + +func (c *Checker) getExportsOfModuleCached(moduleSymbol *ast.Symbol, links *ModuleSymbolLinks) ast.SymbolTable { if links.resolvedExports == nil { exports, typeOnlyExportStarMap := c.getExportsOfModuleWorker(moduleSymbol) - links.resolvedExports = exports - links.typeOnlyExportStarMap = typeOnlyExportStarMap + key := memoKeyOf(links, memoModuleExports) + c.setFlagMemoValue(key, &links.typeOnlyExportStarMap, typeOnlyExportStarMap) + c.setFlagMemoValue(key, &links.resolvedExports, exports) } return links.resolvedExports } @@ -16930,11 +17552,25 @@ func (c *Checker) ResolveAlias(symbol *ast.Symbol) (*ast.Symbol, bool) { // meaning. Pure aliases are eagerly resolved and any type-only markers are back-propagated to the original // symbol. The function panics if the argument is not a symbol with an alias meaning. func (c *Checker) resolveAlias(symbol *ast.Symbol) *ast.Symbol { + links := c.aliasSymbolLinks.Get(symbol) + if target := links.aliasTarget.get(c); target != nil { + return target + } + if c.memoActive() { + links.aliasTarget.begin(c) + result := c.resolveAliasUncached(symbol) + c.endMemo() + return result + } + return c.resolveAliasUncached(symbol) +} + +func (c *Checker) resolveAliasUncached(symbol *ast.Symbol) *ast.Symbol { if symbol.Flags()&ast.SymbolFlagsAlias == 0 { panic("Should only get alias here") } links := c.aliasSymbolLinks.Get(symbol) - if links.aliasTarget == nil { + if links.aliasTarget.get(c) == nil { if !c.pushTypeResolution(symbol, TypeSystemPropertyNameAliasTarget) { return c.unknownSymbol } @@ -16947,13 +17583,13 @@ func (c *Checker) resolveAlias(symbol *ast.Symbol) *ast.Symbol { // When the target is a pure alias, we transitively resolve and propagate any typeOnlyDeclaration target = c.resolveIndirectionAlias(symbol, target) } - links.aliasTarget = core.OrElse(target, c.unknownSymbol) + links.aliasTarget.set(c, core.OrElse(target, c.unknownSymbol)) if !c.popTypeResolution() { c.error(node, diagnostics.Circular_definition_of_import_alias_0, c.symbolToString(symbol)) - links.aliasTarget = c.unknownSymbol + links.aliasTarget.set(c, c.unknownSymbol) } } - return links.aliasTarget + return links.aliasTarget.get(c) } func (c *Checker) resolveIndirectionAlias(source *ast.Symbol, target *ast.Symbol) *ast.Symbol { @@ -16968,7 +17604,7 @@ func (c *Checker) resolveIndirectionAlias(source *ast.Symbol, target *ast.Symbol func (c *Checker) tryResolveAlias(symbol *ast.Symbol) *ast.Symbol { links := c.aliasSymbolLinks.Get(symbol) - if links.aliasTarget != nil || c.findResolutionCycleStartIndex(symbol, TypeSystemPropertyNameAliasTarget) < 0 { + if links.aliasTarget.get(c) != nil || c.findResolutionCycleStartIndex(symbol, TypeSystemPropertyNameAliasTarget) < 0 { return c.resolveAlias(symbol) } return nil @@ -17066,20 +17702,20 @@ func (c *Checker) getDeclarationOfAliasSymbol(symbol *ast.Symbol) *ast.Node { func (c *Checker) getTypeOfSymbolWithDeferredType(symbol *ast.Symbol) *Type { links := c.valueSymbolLinks.Get(symbol) - if links.resolvedType == nil { + if links.resolvedType.get(c) == nil { deferred := c.deferredSymbolLinks.Get(symbol) if deferred.parent.flags&TypeFlagsUnion != 0 { - links.resolvedType = c.getUnionType(deferred.constituents) + links.resolvedType.set(c, c.getUnionType(deferred.constituents)) } else { - links.resolvedType = c.getIntersectionType(deferred.constituents) + links.resolvedType.set(c, c.getIntersectionType(deferred.constituents)) } } - return links.resolvedType + return links.resolvedType.get(c) } func (c *Checker) getWriteTypeOfSymbolWithDeferredType(symbol *ast.Symbol) *Type { links := c.valueSymbolLinks.Get(symbol) - if links.getWriteType() == nil { + if links.getWriteType(c) == nil { deferred := c.deferredSymbolLinks.Get(symbol) if len(deferred.writeConstituents) != 0 { if deferred.parent.flags&TypeFlagsUnion != 0 { @@ -17091,19 +17727,26 @@ func (c *Checker) getWriteTypeOfSymbolWithDeferredType(symbol *ast.Symbol) *Type links.setWriteType(c, c.getTypeOfSymbolWithDeferredType(symbol)) } } - return links.getWriteType() + return links.getWriteType(c) } // Distinct write types come only from set accessors, but synthetic union and intersection // properties deriving from set accessors will either pre-compute or defer the union or // intersection of the writeTypes of their constituents. func (c *Checker) getWriteTypeOfSymbol(symbol *ast.Symbol) *Type { + c.beginMemo(memoKeyOf(symbol, memoWriteType)) + result := c.getWriteTypeOfSymbolUncached(symbol) + c.endMemo() + return result +} + +func (c *Checker) getWriteTypeOfSymbolUncached(symbol *ast.Symbol) *Type { if symbol.CheckFlags()&ast.CheckFlagsSyntheticProperty != 0 { if symbol.CheckFlags()&ast.CheckFlagsDeferredType != 0 { return c.getWriteTypeOfSymbolWithDeferredType(symbol) } links := c.valueSymbolLinks.Get(symbol) - return core.OrElse(links.getWriteType(), links.resolvedType) + return core.OrElse(links.getWriteType(c), links.resolvedType.get(c)) } if symbol.Flags()&ast.SymbolFlagsProperty != 0 { return c.removeMissingType(c.getTypeOfSymbol(symbol), symbol.Flags()&ast.SymbolFlagsOptional != 0) @@ -17161,7 +17804,7 @@ func (c *Checker) GetTypeOfSymbolAtLocation(symbol *ast.Symbol, location *ast.No // getSymbolOfNameOrPropertyAccessExpression), so an access that resolved to // none is matched against the index signature it would use. func (c *Checker) resolvedSymbolAtLocation(location *ast.Node, symbol *ast.Symbol) *ast.Symbol { - resolved := c.symbolNodeLinks.Get(location).resolvedSymbol + resolved := c.symbolNodeLinks.Get(location).resolvedSymbol.get(c) if resolved == nil && symbol.CheckFlags()&ast.CheckFlagsIndexSymbol != 0 && ast.IsPropertyAccessExpression(location) && !ast.IsPrivateIdentifier(location.Name()) { resolved = c.getApplicableIndexSymbol(c.checkExpressionCached(location.Expression()), c.getLiteralTypeFromPropertyName(location.Name())) } @@ -17169,6 +17812,20 @@ func (c *Checker) resolvedSymbolAtLocation(location *ast.Node, symbol *ast.Symbo } func (c *Checker) getTypeOfSymbol(symbol *ast.Symbol) *Type { + links := c.valueSymbolLinks.Get(symbol) + if t := links.resolvedType.get(c); t != nil { + return t + } + if c.memoActive() { + links.resolvedType.begin(c) + result := c.getTypeOfSymbolUncached(symbol) + c.endMemo() + return result + } + return c.getTypeOfSymbolUncached(symbol) +} + +func (c *Checker) getTypeOfSymbolUncached(symbol *ast.Symbol) *Type { if symbol.CheckFlags()&ast.CheckFlagsDeferredType != 0 { return c.getTypeOfSymbolWithDeferredType(symbol) } @@ -17205,23 +17862,23 @@ func (c *Checker) getNonMissingTypeOfSymbol(symbol *ast.Symbol) *Type { func (c *Checker) getTypeOfInstantiatedSymbol(symbol *ast.Symbol) *Type { links := c.valueSymbolLinks.Get(symbol) - if links.resolvedType == nil { - links.resolvedType = c.instantiateType(c.getTypeOfSymbol(links.target), links.mapper) + if links.resolvedType.get(c) == nil { + links.resolvedType.set(c, c.instantiateType(c.getTypeOfSymbol(links.target), links.mapper)) } - return links.resolvedType + return links.resolvedType.get(c) } func (c *Checker) getWriteTypeOfInstantiatedSymbol(symbol *ast.Symbol) *Type { links := c.valueSymbolLinks.Get(symbol) - if links.getWriteType() == nil { + if links.getWriteType(c) == nil { links.setWriteType(c, c.instantiateType(c.getWriteTypeOfSymbol(links.target), links.mapper)) } - return links.getWriteType() + return links.getWriteType(c) } func (c *Checker) getTypeOfVariableOrParameterOrProperty(symbol *ast.Symbol) *Type { links := c.valueSymbolLinks.Get(symbol) - if links.resolvedType == nil { + if links.resolvedType.get(c) == nil { t := c.getTypeOfVariableOrParameterOrPropertyWorker(symbol) if t == nil { panic("Unexpected nil type") @@ -17231,15 +17888,15 @@ func (c *Checker) getTypeOfVariableOrParameterOrProperty(symbol *ast.Symbol) *Ty // to preserve this type. In fact, we need to _prefer_ that type, but it won't // be assigned until contextual typing is complete, so we need to defer in // cases where contextual typing may take place. - if assigned := links.resolvedType; assigned != nil { + if assigned := links.resolvedType.get(c); assigned != nil { return assigned } if !c.isParameterOfContextSensitiveSignature(symbol) { - links.resolvedType = t + links.resolvedType.set(c, t) } return t } - return links.resolvedType + return links.resolvedType.get(c) } func (c *Checker) isParameterOfContextSensitiveSignature(symbol *ast.Symbol) bool { @@ -17274,17 +17931,35 @@ func (c *Checker) getTypeOfVariableOrParameterOrPropertyWorker(symbol *ast.Symbo } return c.getWidenedType(c.getWidenedLiteralType(c.checkExpression(statements[0].Expression()))) } + if ast.IsBindingElement(declaration) && c.strictNullChecks { + // The type of a binding element depends on the type of the + // initializer its pattern destructures (see + // getBindingElementTypeFromParentType), which may in turn depend on + // the element. Resolving the initializer first, as for any element of + // the pattern, makes the cycle resolve the same way whichever element + // is requested first. + if initializer := declaration.Parent.Parent.Initializer(); initializer != nil && !c.isResolutionInProgress(initializer, TypeSystemPropertyNamePatternInitializer) { + c.beginTypeResolution(initializer, TypeSystemPropertyNamePatternInitializer) + c.getTypeOfInitializer(initializer) + c.popTypeResolution() + } + } // Handle variable, parameter or property if !c.pushTypeResolution(symbol, TypeSystemPropertyNameType) { return c.reportCircularityError(symbol) } + var result *Type if symbol.Flags()&ast.SymbolFlagsModuleExports != 0 { if symbol.Name() == "exports" { - return c.getTypeOfSymbol(c.resolveExternalModuleSymbol(symbol.ValueDeclaration().Symbol(), false /*dontResolveAlias*/)) + result = c.getTypeOfSymbol(c.resolveExternalModuleSymbol(symbol.ValueDeclaration().Symbol(), false /*dontResolveAlias*/)) + } else { + result = c.newAnonymousType(symbol, symbol.Members(), nil, nil, nil) + } + if !c.popTypeResolution() { + return c.reportCircularityError(symbol) } - return c.newAnonymousType(symbol, symbol.Members(), nil, nil, nil) + return result } - var result *Type switch declaration.Kind { case ast.KindParameter, ast.KindPropertyDeclaration, ast.KindPropertySignature, ast.KindVariableDeclaration, ast.KindBindingElement: @@ -17523,11 +18198,11 @@ func (c *Checker) padObjectLiteralType(t *Type, pattern *ast.Node) *Type { name := c.getPropertyNameFromBindingElement(e) propType := c.getTypeFromBindingElement(e, false /*includePatternInType*/, true /*reportErrors*/) key := symbolWithTypeKey{name: name, t: propType} - symbol := c.paddedProperties[key] + symbol, _ := c.paddedProperties.get(key) if symbol == nil { symbol = c.newSymbol(ast.SymbolFlagsProperty|ast.SymbolFlagsOptional, name) - c.valueSymbolLinks.Get(symbol).resolvedType = propType - c.paddedProperties[key] = symbol + c.valueSymbolLinks.Get(symbol).resolvedType.setIdentity(c, propType) + c.paddedProperties.intern(key, symbol) } members.Set(symbol.Name(), symbol) } @@ -17595,8 +18270,8 @@ func (c *Checker) getWidenedLiteralTypeForInitializer(declaration *ast.Node, t * func (c *Checker) getTypeOfFuncClassEnumModule(symbol *ast.Symbol) *Type { links := c.valueSymbolLinks.Get(symbol) - if links.resolvedType == nil { - links.resolvedType = c.getTypeOfFuncClassEnumModuleWorker(symbol) + if links.resolvedType.get(c) == nil { + links.resolvedType.set(c, c.getTypeOfFuncClassEnumModuleWorker(symbol)) } if declaration := symbol.ValueDeclaration(); declaration != nil && c.isContextSensitiveFunctionOrObjectLiteralMethod(declaration) { // Complete the signature before anything resolves the members of the @@ -17604,7 +18279,7 @@ func (c *Checker) getTypeOfFuncClassEnumModule(symbol *ast.Symbol) *Type { // members of the type. c.assignContextualParameterTypesOfFunction(declaration, CheckModeNormal) } - return links.resolvedType + return links.resolvedType.get(c) } func (c *Checker) getTypeOfFuncClassEnumModuleWorker(symbol *ast.Symbol) *Type { @@ -17653,14 +18328,24 @@ func (c *Checker) getBaseTypeVariableOfClass(symbol *ast.Symbol) *Type { * * an object type with at least one construct signature. */ func (c *Checker) getBaseConstructorTypeOfClass(t *Type) *Type { + if c.memoActive() { + t.AsInterfaceType().resolvedBaseConstructorType.begin(c) + result := c.getBaseConstructorTypeOfClassUncached(t) + c.endMemo() + return result + } + return c.getBaseConstructorTypeOfClassUncached(t) +} + +func (c *Checker) getBaseConstructorTypeOfClassUncached(t *Type) *Type { data := t.AsInterfaceType() - if data.resolvedBaseConstructorType != nil { - return data.resolvedBaseConstructorType + if data.resolvedBaseConstructorType.get(c) != nil { + return data.resolvedBaseConstructorType.get(c) } baseTypeNode := getBaseTypeNodeOfClass(t) if baseTypeNode == nil { - data.resolvedBaseConstructorType = c.undefinedType - return data.resolvedBaseConstructorType + data.resolvedBaseConstructorType.set(c, c.undefinedType) + return data.resolvedBaseConstructorType.get(c) } if !c.pushTypeResolution(t, TypeSystemPropertyNameResolvedBaseConstructorType) { return c.errorType @@ -17673,10 +18358,10 @@ func (c *Checker) getBaseConstructorTypeOfClass(t *Type) *Type { } if !c.popTypeResolution() { c.error(t.symbol.ValueDeclaration(), diagnostics.X_0_is_referenced_directly_or_indirectly_in_its_own_base_expression, c.symbolToString(t.symbol)) - if data.resolvedBaseConstructorType == nil { - data.resolvedBaseConstructorType = c.errorType + if data.resolvedBaseConstructorType.get(c) == nil { + data.resolvedBaseConstructorType.set(c, c.errorType) } - return data.resolvedBaseConstructorType + return data.resolvedBaseConstructorType.get(c) } if baseConstructorType.flags&TypeFlagsAny == 0 && baseConstructorType != c.nullWideningType && !c.isConstructorType(baseConstructorType) { err := c.error(baseTypeNode.Expression(), diagnostics.Type_0_is_not_a_constructor_function_type, c.TypeToString(baseConstructorType)) @@ -17693,15 +18378,15 @@ func (c *Checker) getBaseConstructorTypeOfClass(t *Type) *Type { err.AddRelatedInfo(createDiagnosticForNode(baseConstructorType.symbol.Declarations()[0], diagnostics.Did_you_mean_for_0_to_be_constrained_to_type_new_args_Colon_any_1, c.symbolToString(baseConstructorType.symbol), c.TypeToString(ctorReturn))) } } - if data.resolvedBaseConstructorType == nil { - data.resolvedBaseConstructorType = c.errorType + if data.resolvedBaseConstructorType.get(c) == nil { + data.resolvedBaseConstructorType.set(c, c.errorType) } - return data.resolvedBaseConstructorType + return data.resolvedBaseConstructorType.get(c) } - if data.resolvedBaseConstructorType == nil { - data.resolvedBaseConstructorType = baseConstructorType + if data.resolvedBaseConstructorType.get(c) == nil { + data.resolvedBaseConstructorType.set(c, baseConstructorType) } - return data.resolvedBaseConstructorType + return data.resolvedBaseConstructorType.get(c) } func (c *Checker) isFunctionType(t *Type) bool { @@ -17755,6 +18440,17 @@ func (c *Checker) getConstraintOfType(t *Type) *Type { } func (c *Checker) getConstraintOfTypeParameter(typeParameter *Type) *Type { + key := memoKeyOf(typeParameter, memoTypeParameterConstraint) + if typeParameter.flags&TypeFlagsTypeParameter != 0 && typeParameter.AsTypeParameter().resolvedBaseConstraint.peek() != nil && typeParameter.AsTypeParameter().constraint.peek() != nil && c.readsWithoutMemo(key) { + return c.getConstraintOfTypeParameterUncached(typeParameter) + } + c.beginMemo(key) + result := c.getConstraintOfTypeParameterUncached(typeParameter) + c.endMemo() + return result +} + +func (c *Checker) getConstraintOfTypeParameterUncached(typeParameter *Type) *Type { if c.hasNonCircularBaseConstraint(typeParameter) { return c.getConstraintFromTypeParameter(typeParameter) } @@ -17772,7 +18468,7 @@ func (c *Checker) getConstraintFromTypeParameter(t *Type) *Type { } tp := t.AsTypeParameter() - if tp.constraint == nil { + if tp.constraint.get(c) == nil { var constraint *Type if tp.target != nil { constraint = c.instantiateType(c.getConstraintOfTypeParameter(tp.target), tp.mapper) @@ -17796,10 +18492,10 @@ func (c *Checker) getConstraintFromTypeParameter(t *Type) *Type { if constraint == nil { constraint = c.noConstraintType } - tp.constraint = constraint + tp.constraint.set(c, constraint) } - if tp.constraint != c.noConstraintType { - return tp.constraint + if tp.constraint.get(c) != c.noConstraintType { + return tp.constraint.get(c) } return nil } @@ -17896,7 +18592,7 @@ func (c *Checker) getTypeParametersForTypeReferenceOrImport(node *ast.Node) []*T func (c *Checker) getTypeParametersForTypeAndSymbol(t *Type, symbol *ast.Symbol) []*Type { if !c.isErrorType(t) { if symbol.Flags()&ast.SymbolFlagsTypeAlias != 0 { - if typeParameters := c.typeAliasLinks.Get(symbol).typeParameters; len(typeParameters) != 0 { + if typeParameters := c.getTypeParametersOfTypeAlias(symbol); len(typeParameters) != 0 { return typeParameters } } @@ -17960,7 +18656,7 @@ func (c *Checker) getConstraintFromConditionalType(t *Type) *Type { func (c *Checker) getDefaultConstraintOfConditionalType(t *Type) *Type { d := t.AsConditionalType() - if d.resolvedDefaultConstraint == nil { + return c.memoCompute(&d.resolvedDefaultConstraint, func() *Type { // An `any` branch of a conditional type would normally be viral - specifically, without special handling here, // a conditional type with a single branch of type `any` would be assignable to anything, since it's constraint would simplify to // just `any`. This result is _usually_ unwanted - so instead here we elide an `any` branch from the constraint type, @@ -17969,19 +18665,18 @@ func (c *Checker) getDefaultConstraintOfConditionalType(t *Type) *Type { falseConstraint := c.getFalseTypeFromConditionalType(t) switch { case IsTypeAny(trueConstraint): - d.resolvedDefaultConstraint = falseConstraint + return falseConstraint case IsTypeAny(falseConstraint): - d.resolvedDefaultConstraint = trueConstraint + return trueConstraint default: - d.resolvedDefaultConstraint = c.getUnionType([]*Type{trueConstraint, falseConstraint}) + return c.getUnionType([]*Type{trueConstraint, falseConstraint}) } - } - return d.resolvedDefaultConstraint + }) } func (c *Checker) getConstraintOfDistributiveConditionalType(t *Type) *Type { d := t.AsConditionalType() - if d.resolvedConstraintOfDistributive == nil { + result := c.memoCompute(&d.resolvedConstraintOfDistributive, func() *Type { // Check if we have a conditional type of the form 'T extends U ? X : Y', where T is a constrained // type parameter. If so, create an instantiation of the conditional type where T is replaced // with its constraint. We do this because if the constraint is a union type it will be distributed @@ -17993,7 +18688,7 @@ func (c *Checker) getConstraintOfDistributiveConditionalType(t *Type) *Type { // Please note: the distributive constraint is a kludge for emulating what a negated type could to do filter // a union - once negated types exist and are applied to the conditional false branch, this "constraint" // likely doesn't need to exist. - if d.root.isDistributive && c.cachedTypes[CachedTypeKey{kind: CachedTypeKindRestrictiveInstantiation, typeId: t.id}] != t { + if d.root.isDistributive && !c.isRestrictiveInstantiation(t) { constraint := c.getSimplifiedType(d.checkType, false /*writing*/) if constraint == d.checkType { constraint = c.getConstraintOfType(constraint) @@ -18001,25 +18696,24 @@ func (c *Checker) getConstraintOfDistributiveConditionalType(t *Type) *Type { if constraint != nil && constraint != d.checkType { instantiated := c.getConditionalTypeInstantiation(t, prependTypeMapping(d.root.checkType, constraint, d.mapper), true /*forConstraint*/, nil) if instantiated.flags&TypeFlagsNever == 0 { - d.resolvedConstraintOfDistributive = instantiated return instantiated } } } - d.resolvedConstraintOfDistributive = c.noConstraintType - } - if d.resolvedConstraintOfDistributive != c.noConstraintType { - return d.resolvedConstraintOfDistributive + return c.noConstraintType + }) + if result != c.noConstraintType { + return result } return nil } func (c *Checker) getDeclaredTypeOfClassOrInterface(symbol *ast.Symbol) *Type { links := c.declaredTypeLinks.Get(symbol) - if links.declaredType == nil { + if links.declaredType.get(c) == nil { kind := core.IfElse(symbol.Flags()&ast.SymbolFlagsClass != 0, ObjectFlagsClass, ObjectFlagsInterface) t := c.newObjectType(kind, symbol) - links.declaredType = t + links.declaredType.setIdentity(c, t) outerTypeParameters := c.getOuterTypeParametersOfClassOrInterface(symbol) typeParameters := c.appendLocalTypeParametersOfClassOrInterfaceOrTypeAlias(outerTypeParameters, symbol) // A class or interface is generic if it has type parameters or a "this" type. We always give classes a "this" type @@ -18032,16 +18726,15 @@ func (c *Checker) getDeclaredTypeOfClassOrInterface(symbol *ast.Symbol) *Type { d := t.AsInterfaceType() d.thisType = c.newTypeParameter(symbol) d.thisType.AsTypeParameter().isThisType = true - d.thisType.AsTypeParameter().constraint = t + d.thisType.AsTypeParameter().constraint.intern(t) d.allTypeParameters = append(typeParameters, d.thisType) d.outerTypeParameterCount = len(outerTypeParameters) - d.resolvedTypeArguments = d.TypeParameters() - d.instantiations = make(map[CacheHashKey]*Type) - d.instantiations[getTypeListKey(d.resolvedTypeArguments)] = t + d.resolvedTypeArguments.intern(d.TypeParameters()) + d.instantiations.intern(getTypeListKey(d.TypeParameters()), t) d.target = t } } - return links.declaredType + return links.declaredType.get(c) } /** @@ -18169,15 +18862,15 @@ func (b *keyBuilder) writeAlias(alias *TypeAlias) { } } -func (b *keyBuilder) writeGenericTypeReferences(source *Type, target *Type, ignoreConstraints bool) bool { +func (b *keyBuilder) writeGenericTypeReferences(c *Checker, source *Type, target *Type, ignoreConstraints bool) bool { var constrained bool typeParameters := make([]*Type, 0, 8) var writeTypeReference func(*Type, int) writeTypeReference = func(ref *Type, depth int) { b.writeType(ref.Target()) - for _, t := range ref.AsTypeReference().resolvedTypeArguments { + for _, t := range ref.AsTypeReference().resolvedTypeArguments.peek() { if t.flags&TypeFlagsTypeParameter != 0 { - if ignoreConstraints || t.checker.getConstraintOfTypeParameter(t) == nil { + if ignoreConstraints || c.getConstraintOfTypeParameter(t) == nil { index := slices.Index(typeParameters, t) if index < 0 { index = len(typeParameters) @@ -18188,7 +18881,7 @@ func (b *keyBuilder) writeGenericTypeReferences(source *Type, target *Type, igno continue } constrained = true - } else if depth < 4 && isTypeReferenceWithGenericArguments(t) { + } else if depth < 4 && c.isTypeReferenceWithGenericArguments(t) { b.writeByte('<') writeTypeReference(t, depth+1) b.writeByte('>') @@ -18214,52 +18907,49 @@ func (b *keyBuilder) writeNode(node *ast.Node) { } } -// getObjectLiteralTypeKey identifies the content of an object literal type, so -// that checking the same object literal again with the same results yields the -// same type. -func (c *Checker) getObjectLiteralTypeKey(node *ast.Node, flags ObjectFlags, properties ast.SymbolTable, indexInfos []*IndexInfo) CacheHashKey { - var b keyBuilder - b.writeNode(node) - b.writeUint32(uint32(flags)) - names := slices.Sorted(properties.Keys()) - b.writeInt(len(names)) - for _, name := range names { - prop := properties.Get(name) - b.writeString(name) - b.writeByte(0) - b.writeUint32(uint32(prop.Flags())) - b.writeUint32(uint32(prop.CheckFlags())) - // Members created by this object literal have their types set at +// An objectLiteralType is a type created for an object literal, with the +// content it was created from; see isSameObjectLiteralType. +type objectLiteralType struct { + flags ObjectFlags + properties ast.SymbolTable + indexInfos []*IndexInfo + t *Type +} + +// isSameObjectLiteralType reports whether an object literal type created +// before has the given content, so that checking the same object literal +// again with the same results yields the same type. +func (c *Checker) isSameObjectLiteralType(e objectLiteralType, flags ObjectFlags, properties ast.SymbolTable, indexInfos []*IndexInfo) bool { + if e.flags != flags || e.properties.Len() != properties.Len() || len(e.indexInfos) != len(indexInfos) { + return false + } + for i, info := range indexInfos { + other := e.indexInfos[i] + if info.keyType != other.keyType || info.valueType != other.valueType || info.isReadonly != other.isReadonly || !slices.Equal(info.components, other.components) { + return false + } + } + for i := range properties.Len() { + name, prop := properties.At(i) + otherName, other := e.properties.At(i) + if name != otherName || prop.Flags() != other.Flags() || prop.CheckFlags() != other.CheckFlags() { + return false + } + // Members created by an object literal have their types set at // creation; other members (accessors) are the declared symbols. links := c.valueSymbolLinks.TryGet(prop) - if links == nil || links.target == nil { - b.writeByte(2) - b.writeSymbol(prop) + otherLinks := c.valueSymbolLinks.TryGet(other) + if links == nil || links.target == nil || otherLinks == nil || otherLinks.target == nil { + if prop != other { + return false + } continue } - if nameType := links.getNameType(); nameType != nil { - b.writeByte(1) - b.writeType(nameType) - } else { - b.writeByte(0) - } - b.writeType(links.resolvedType) - } - b.writeInt(len(indexInfos)) - for _, info := range indexInfos { - b.writeType(info.keyType) - b.writeType(info.valueType) - if info.isReadonly { - b.writeByte(1) - } else { - b.writeByte(0) - } - b.writeInt(len(info.components)) - for _, n := range info.components { - b.writeNode(n) + if links.getNameType() != otherLinks.getNameType() || links.resolvedType.peek() != otherLinks.resolvedType.peek() { + return false } } - return b.hash() + return true } func getTypeListKey(types []*Type) CacheHashKey { @@ -18379,15 +19069,22 @@ func getConditionalTypeKey(typeArguments []*Type, alias *TypeAlias, forConstrain return b.hash() } -func getRelationKey(source *Type, target *Type, intersectionState IntersectionState, isIdentity bool, ignoreConstraints bool) (CacheHashKey, bool) { +func (c *Checker) getRelationKey(source *Type, target *Type, intersectionState IntersectionState, isIdentity bool, ignoreConstraints bool) (CacheHashKey, bool) { if isIdentity && source.id > target.id { source, target = target, source } var b keyBuilder var constrained bool - if isTypeReferenceWithGenericArguments(source) && isTypeReferenceWithGenericArguments(target) { - b.writeByte('g') - constrained = b.writeGenericTypeReferences(source, target, ignoreConstraints) + if c.isTypeReferenceWithGenericArguments(source) && c.isTypeReferenceWithGenericArguments(target) { + // Marker types are compared structurally while their variances are being + // measured, and other instantiations with the same key by those variances, + // which may be guesses until the measurement finishes. + if c.isMarkerType(source) || c.isMarkerType(target) { + b.writeByte('m') + } else { + b.writeByte('g') + } + constrained = b.writeGenericTypeReferences(c, source, target, ignoreConstraints) } else { b.writeByte('s') b.writeType(source) @@ -18406,9 +19103,9 @@ func getNodeListKey(nodes []*ast.Node) CacheHashKey { return b.hash() } -func isTypeReferenceWithGenericArguments(t *Type) bool { - return isNonDeferredTypeReference(t) && core.Some(t.checker.getTypeArguments(t), func(t *Type) bool { - return t.flags&TypeFlagsTypeParameter != 0 || isTypeReferenceWithGenericArguments(t) +func (c *Checker) isTypeReferenceWithGenericArguments(t *Type) bool { + return isNonDeferredTypeReference(t) && core.Some(c.getTypeArguments(t), func(t *Type) bool { + return t.flags&TypeFlagsTypeParameter != 0 || c.isTypeReferenceWithGenericArguments(t) }) } @@ -18709,7 +19406,7 @@ func (c *Checker) getTypeFromObjectBindingPattern(pattern *ast.Node, includePatt text := getPropertyNameFromType(exprType) flags := ast.SymbolFlagsProperty | core.IfElse(e.Initializer() != nil, ast.SymbolFlagsOptional, 0) symbol := c.newSymbol(flags, text) - c.valueSymbolLinks.Get(symbol).resolvedType = c.getTypeFromBindingElement(e, includePatternInType, reportErrors) + c.valueSymbolLinks.Get(symbol).resolvedType.setIdentity(c, c.getTypeFromBindingElement(e, includePatternInType, reportErrors)) members.Set(symbol.Name(), symbol) } var indexInfos []*IndexInfo @@ -18727,24 +19424,23 @@ func (c *Checker) getTypeFromObjectBindingPattern(pattern *ast.Node, includePatt } b.writeUint32(uint32(objectFlags)) b.writeInt(len(indexInfos)) - names := slices.Sorted(members.Keys()) - for _, name := range names { + for name, member := range members.All() { b.writeString(name) b.writeByte(0) - b.writeUint32(uint32(members.Get(name).Flags())) - b.writeType(c.valueSymbolLinks.Get(members.Get(name)).resolvedType) + b.writeUint32(uint32(member.Flags())) + b.writeType(c.valueSymbolLinks.Get(member).resolvedType.peek()) } key := b.hash() - if existing := c.bindingPatternTypes[key]; existing != nil { + if existing, _ := c.bindingPatternTypes.get(key); existing != nil { return existing } result := c.newAnonymousType(nil, members, nil, nil, indexInfos) result.objectFlags |= objectFlags if includePatternInType { - c.patternForType[result] = pattern + c.patternForType.intern(result, pattern) result.objectFlags |= ObjectFlagsContainsObjectOrArrayLiteral } - c.bindingPatternTypes[key] = result + c.bindingPatternTypes.intern(key, result) return result } @@ -18792,13 +19488,13 @@ func (c *Checker) getTypeFromArrayBindingPattern(pattern *ast.Node, includePatte b.writeNode(pattern) b.writeType(result) key := b.hash() - if existing := c.bindingPatternTypes[key]; existing != nil { + if existing, _ := c.bindingPatternTypes.get(key); existing != nil { return existing } result = c.cloneTypeReference(result) - c.patternForType[result] = pattern + c.patternForType.intern(result, pattern) result.objectFlags |= ObjectFlagsContainsObjectOrArrayLiteral - c.bindingPatternTypes[key] = result + c.bindingPatternTypes.intern(key, result) } return result } @@ -18991,16 +19687,17 @@ func (c *Checker) getTypeFromPropertyDescriptor(node *ast.Node) *Type { // A property is considered a constructor declared property when all declaration sites are this.xxx assignments, // when no declaration sites have JSDoc type annotations, and when at least one declaration site is in the body of // a class constructor. +type thisExpando struct { + kind thisAssignmentDeclarationKind + location *ast.Node +} + func (c *Checker) isConstructorDeclaredThisProperty(symbol *ast.Symbol) (thisAssignmentDeclarationKind, *ast.Node) { if symbol.ValueDeclaration() == nil || !ast.IsBinaryExpression(symbol.ValueDeclaration()) { return thisAssignmentDeclarationNone, nil } - if kind, ok := c.thisExpandoKinds[symbol]; ok { - location, ok2 := c.thisExpandoLocations[symbol] - if !ok2 { - panic("location should be cached whenever this expando symbol is cached") - } - return kind, location + if cached, ok := c.thisExpandos.get(symbol); ok { + return cached.kind, cached.location } allThis := true var typeAnnotation *ast.Node @@ -19031,8 +19728,7 @@ func (c *Checker) isConstructorDeclaredThisProperty(symbol *ast.Symbol) (thisAss kind = core.IfElse(location == nil, thisAssignmentDeclarationMethod, thisAssignmentDeclarationConstructor) } } - c.thisExpandoKinds[symbol] = kind - c.thisExpandoLocations[symbol] = location + c.thisExpandos.intern(symbol, thisExpando{kind, location}) return kind, location } @@ -19166,9 +19862,12 @@ func (c *Checker) getWidenedType(t *Type) *Type { func (c *Checker) getWidenedTypeWithContext(t *Type, context *WideningContext) *Type { if t.objectFlags&ObjectFlagsRequiresWidening != 0 { if context == nil { - if cached := c.cachedTypes[CachedTypeKey{kind: CachedTypeKindWidened, typeId: t.id}]; cached != nil { + key := CachedTypeKey{kind: CachedTypeKindWidened, typeId: t.id} + if cached, _ := c.cachedTypes.get(c, key); cached != nil { return cached } + c.cachedTypes.begin(c, key) + defer c.endMemo() } var result *Type switch { @@ -19197,7 +19896,7 @@ func (c *Checker) getWidenedTypeWithContext(t *Type, context *WideningContext) * result = c.createTypeReference(t.Target(), core.SameMap(c.getTypeArguments(t), c.getWidenedType)) } if result != nil && context == nil { - c.cachedTypes[CachedTypeKey{kind: CachedTypeKindWidened, typeId: t.id}] = result + c.cachedTypes.set(c, CachedTypeKey{kind: CachedTypeKindWidened, typeId: t.id}, result) } return core.OrElse(result, t) } @@ -19303,26 +20002,35 @@ func (c *Checker) getSiblingsOfContext(context *WideningContext) []*Type { } func (c *Checker) getUndefinedProperty(prop *ast.Symbol) *ast.Symbol { - if cached := c.undefinedProperties[prop]; cached != nil { + if cached, _ := c.undefinedProperties.get(c, prop); cached != nil { return cached } + c.undefinedProperties.begin(c, prop) result := c.createSymbolWithType(prop, c.undefinedOrMissingType) result.SetFlags(result.Flags() | ast.SymbolFlagsOptional) - c.undefinedProperties[prop] = result + c.undefinedProperties.set(c, prop, result) + c.endMemo() return result } func (c *Checker) getTypeOfEnumMember(symbol *ast.Symbol) *Type { links := c.valueSymbolLinks.Get(symbol) - if links.resolvedType == nil { - links.resolvedType = c.getDeclaredTypeOfEnumMember(symbol) + if links.resolvedType.get(c) == nil { + links.resolvedType.set(c, c.getDeclaredTypeOfEnumMember(symbol)) } - return links.resolvedType + return links.resolvedType.get(c) } func (c *Checker) getTypeOfAccessors(symbol *ast.Symbol) *Type { + c.beginMemo(memoKeyOf(symbol, memoAccessorType)) + result := c.getTypeOfAccessorsUncached(symbol) + c.endMemo() + return result +} + +func (c *Checker) getTypeOfAccessorsUncached(symbol *ast.Symbol) *Type { links := c.valueSymbolLinks.Get(symbol) - if links.resolvedType == nil { + if links.resolvedType.get(c) == nil { if !c.pushTypeResolution(symbol, TypeSystemPropertyNameType) { return c.errorType } @@ -19368,16 +20076,16 @@ func (c *Checker) getTypeOfAccessors(symbol *ast.Symbol) *Type { } t = c.anyType } - if links.resolvedType == nil { - links.resolvedType = t + if links.resolvedType.get(c) == nil { + links.resolvedType.set(c, t) } } - return links.resolvedType + return links.resolvedType.get(c) } func (c *Checker) getWriteTypeOfAccessors(symbol *ast.Symbol) *Type { links := c.valueSymbolLinks.Get(symbol) - if links.getWriteType() == nil { + if links.getWriteType(c) == nil { if !c.pushTypeResolution(symbol, TypeSystemPropertyNameWriteType) { return c.errorType } @@ -19396,7 +20104,7 @@ func (c *Checker) getWriteTypeOfAccessors(symbol *ast.Symbol) *Type { writeType = c.anyType } // Absent an explicit setter type annotation we use the read type of the accessor. - if links.getWriteType() == nil { + if links.getWriteType(c) == nil { if writeType != nil { links.setWriteType(c, writeType) } else { @@ -19404,12 +20112,12 @@ func (c *Checker) getWriteTypeOfAccessors(symbol *ast.Symbol) *Type { } } } - return links.getWriteType() + return links.getWriteType(c) } func (c *Checker) getTypeOfAlias(symbol *ast.Symbol) *Type { links := c.valueSymbolLinks.Get(symbol) - if links.resolvedType == nil { + if links.resolvedType.get(c) == nil { if !c.pushTypeResolution(symbol, TypeSystemPropertyNameType) { return c.errorType } @@ -19420,22 +20128,22 @@ func (c *Checker) getTypeOfAlias(symbol *ast.Symbol) *Type { // type symbol, call getDeclaredTypeOfSymbol. // This check is important because without it, a call to getTypeOfSymbol could end // up recursively calling getTypeOfAlias, causing a stack overflow. - if links.resolvedType == nil { + if links.resolvedType.get(c) == nil { if c.getSymbolFlags(targetSymbol)&ast.SymbolFlagsValue != 0 { - links.resolvedType = c.getTypeOfSymbol(targetSymbol) + links.resolvedType.set(c, c.getTypeOfSymbol(targetSymbol)) } else { - links.resolvedType = c.errorType + links.resolvedType.set(c, c.errorType) } } if !c.popTypeResolution() { c.reportCircularityError(core.OrElse(exportSymbol, symbol)) - if links.resolvedType == nil { - links.resolvedType = c.errorType + if links.resolvedType.get(c) == nil { + links.resolvedType.set(c, c.errorType) } - return links.resolvedType + return links.resolvedType.get(c) } } - return links.resolvedType + return links.resolvedType.get(c) } func (c *Checker) addOptionality(t *Type) *Type { @@ -19520,12 +20228,13 @@ func (c *Checker) getEffectivePropertyNameForPropertyNameNode(node *ast.Property case ast.IsComputedPropertyName(node): // This is cached so `getTypeOfExpression` isn't constantly reinvoked for every property name lookup links := c.computedNameLinks.Get(node) - if links != nil && !links.hasName.IsUnknown() { - return links.name, links.hasName.IsTrue() + if result := links.name.get(c); !result.exists.IsUnknown() { + return result.name, result.exists.IsTrue() } + links.name.begin(c) name, exists := c.tryGetNameFromType(c.getTypeOfExpression(node.Expression())) - links.name = name - links.hasName = core.BoolToTristate(exists) + links.name.set(c, computedName{name: name, exists: core.BoolToTristate(exists)}) + c.endMemo() return name, exists } return "", false @@ -19570,32 +20279,529 @@ func (c *Checker) getCombinedModifierFlagsCached(node *ast.Node) ast.ModifierFla func (c *Checker) pushTypeResolution(target TypeSystemEntity, propertyName TypeSystemPropertyName) bool { resolutionCycleStartIndex := c.findResolutionCycleStartIndex(target, propertyName) if resolutionCycleStartIndex >= 0 { - // A cycle was found - for i := resolutionCycleStartIndex; i < len(c.typeResolutions); i++ { - c.typeResolutions[i].result = false + if !c.yieldCycleToMembers(resolutionCycleStartIndex) { + return false + } + } + c.beginTypeResolution(target, propertyName) + return true +} + +// cycleYield is a canonical recomputation in progress; see yieldCycle. +type cycleYield struct { + end int // Index in resolutions of the resolution whose pop ends it, or -1 + target TypeSystemEntity + propertyName TypeSystemPropertyName + savedResolutionStart int + savedVisibleStart int + savedDepths [3]depthLimit + shadowed []shadowedResolving +} + +type shadowedResolving struct { + key memoKey + index int32 +} + +// yieldCycle handles re-entering the resolution in progress at start, a +// cycle that would fail the resolution re-entered. Which resolution is +// re-entered depends on which was entered first, so a cycle through the +// types of symbols declared before the one re-entered (if it is one) +// fails at the first of those instead, as if that symbol had been resolved +// first: its type is computed again from scratch, with nothing in progress +// visible, and the resolution re-entered is then computed again likewise, +// by the caller, which pushes it. The computations in progress then continue +// from those results. Termination: a resolution is never computed again +// while it is already being computed again. +func (c *Checker) yieldCycle(start int) bool { + r := &c.resolutions[start].typeResolution + var first *ast.Symbol + if r.propertyName == TypeSystemPropertyNameType { + first = r.target.(*ast.Symbol) + } + entered := first + for i := start + 1; i < len(c.resolutions); i++ { + if f := &c.resolutions[i]; f.isTypeResolution && f.typeResolution.propertyName == TypeSystemPropertyNameType { + if s := f.typeResolution.target.(*ast.Symbol); first == nil || c.compareSymbols(s, first) < 0 { + first = s + } + } + } + if first == nil || first == entered || c.isYieldingTo(first, TypeSystemPropertyNameType) || c.isYieldingTo(r.target, r.propertyName) { + return false + } + c.beginCycleYield(first, TypeSystemPropertyNameType, -1, len(c.resolutions)) + c.getTypeOfSymbol(first) + c.endCycleYield() + c.beginCycleYield(r.target, r.propertyName, len(c.resolutions), len(c.resolutions)) + return true +} + +// yieldCycleToMembers handles re-entering the type resolution in progress +// at start, a cycle that would fail it, when the cycle passes through a +// member table. Entering the cycle at the member table instead would not +// fail, as the table is available while it is resolved, so the resolution +// re-entered is computed again as if the table had been entered first: by +// the caller, with everything entered before the table hidden. A module +// table is instead resolved again itself, as if entered first (see +// resolveModuleTableMembers), so everything above it fails until then, to +// be discarded. Otherwise it yields as yieldCycle does. It reports whether +// the resolution re-entered may be pushed. +func (c *Checker) yieldCycleToMembers(start int) bool { + if m := c.findMembersInCycle(start); m >= 0 { + r := &c.resolutions[start].typeResolution + if c.isYieldingTo(r.target, r.propertyName) { + c.markResolutionCycle(start) + return false + } + if t := &c.resolutions[m].typeResolution; t.propertyName == TypeSystemPropertyNameStructuredMembers && c.isModuleTableType(t.target.(*Type)) { + t.restartHidden = true + c.markResolutionCycle(m) + return false + } + c.beginCycleYield(r.target, r.propertyName, len(c.resolutions), m) + return true + } + if !c.yieldCycle(start) { + c.markResolutionCycle(start) + return false + } + return true +} + +// findMembersInCycle returns the index in resolutions of the outermost +// resolution of a member table in progress above start, or -1. Member +// tables are available while they are resolved, so a cycle entered at its +// member table never fails. +func (c *Checker) findMembersInCycle(start int) int { + for i := start + 1; i < len(c.resolutions); i++ { + f := &c.resolutions[i] + if f.isTypeResolution && f.typeResolution.propertyName == TypeSystemPropertyNameStructuredMembers { + return i + } + if field := f.key.field(); f.key != 0 && (field == memoSymbolMembers || field == memoSymbolExports) { + return i + } + } + return -1 +} + +func (c *Checker) isYieldingTo(target TypeSystemEntity, propertyName TypeSystemPropertyName) bool { + return slices.ContainsFunc(c.cycleYields, func(y cycleYield) bool { return y.target == target && y.propertyName == propertyName }) +} + +// beginCycleYield hides the resolutions in progress below floor until +// endCycleYield, which popping the resolution at end calls if it is not -1. +func (c *Checker) beginCycleYield(target TypeSystemEntity, propertyName TypeSystemPropertyName, end int, floor int) { + n := floor + c.cycleYields = append(c.cycleYields, cycleYield{ + end: end, + target: target, + propertyName: propertyName, + savedResolutionStart: c.resolutionStart, + savedVisibleStart: c.visibleStart, + savedDepths: [3]depthLimit{c.serializationLevel, c.inlineLevel, c.conditionalConstraintDepth}, + }) + c.resolutionStart = n + c.visibleStart = n + c.serializationLevel, c.inlineLevel, c.conditionalConstraintDepth = depthLimit{}, depthLimit{}, depthLimit{} +} + +func (c *Checker) endCycleYield() { + y := &c.cycleYields[len(c.cycleYields)-1] + c.resolutionStart = y.savedResolutionStart + c.visibleStart = y.savedVisibleStart + c.serializationLevel, c.inlineLevel, c.conditionalConstraintDepth = y.savedDepths[0], y.savedDepths[1], y.savedDepths[2] + for _, sh := range y.shadowed { + c.resolving.set(sh.key, sh.index) + } + c.cycleYields[len(c.cycleYields)-1] = cycleYield{} + c.cycleYields = c.cycleYields[:len(c.cycleYields)-1] +} + +// markResolutionCycle records that the type resolution entries from start to +// the top of the stack form a cycle, so each of them fails. The failed +// results are kept, except that of a resolution yieldCycleToMembers computes +// again while it is in progress below: the computation below sets its own. +func (c *Checker) markResolutionCycle(start int) { + for i := start; i < len(c.resolutions); i++ { + if r := &c.resolutions[i].typeResolution; c.resolutions[i].isTypeResolution && r.result { + if !c.isRecomputedByYield(i) { + c.cycleParticipants = append(c.cycleParticipants, *r) + } + r.result = false + } + } + c.markTypeResolutionCircular(start) +} + +func (c *Checker) isRecomputedByYield(i int) bool { + for j := range c.cycleYields { + if c.cycleYields[j].end == i { + return true + } + } + return false +} + +// observeResolutionInProgress is for computations that are skipped rather +// than failed when re-entered. Skipping still observes the incomplete +// computation, so everything on the stack since it started is in a cycle. +func (c *Checker) observeResolutionInProgress(target TypeSystemEntity, propertyName TypeSystemPropertyName) { + if start := c.findResolutionCycleStartIndex(target, propertyName); start >= 0 { + c.markResolutionCycle(start) + } +} + +// isResolutionInProgress reports whether a resolution is on the stack, for +// computations that deliberately take another path to avoid a circularity. +// The answer depends on the incomplete resolution, so results cached while it +// is in progress must not outlive it, but nothing fails. +func (c *Checker) isResolutionInProgress(target TypeSystemEntity, propertyName TypeSystemPropertyName) bool { + start := c.findResolutionCycleStartIndex(target, propertyName) + if start < 0 { + return false + } + c.markTypeResolutionCircular(start) + return true +} + +// isResolvingAnywhere reports whether the property of target is being +// resolved. A computation finding it so fails, as computing it from scratch +// would, so the results cached since it started are discarded when it +// finishes, like those of any other failure, and every caller computes them +// again and reports the failure. Unlike isResolutionInProgress, it searches +// the whole stack, for computations that would not otherwise stop. +func (c *Checker) isResolvingAnywhere(target TypeSystemEntity, propertyName TypeSystemPropertyName) bool { + if i := c.resolvingAnywhereIndex(target, propertyName); i >= 0 { + c.markTypeResolutionCircular(i) + return true + } + return false +} + +// resolvingAnywhereIndex returns the index in resolutions of the visible +// resolution of the property of target in progress, or -1. +func (c *Checker) resolvingAnywhereIndex(target TypeSystemEntity, propertyName TypeSystemPropertyName) int { + for i := c.searchableTop - 1; i >= c.visibleStart; i = int(c.resolutions[i].prevSearchable) - 1 { + if r := &c.resolutions[i].typeResolution; r.target == target && r.propertyName == propertyName { + return i + } + } + return -1 +} + +// observeCircularHead is for computations where re-entering one in progress +// fails only that computation, while the others complete from its failed +// result. Which member fails then depends on which was entered first, so the +// cycle records its canonical member, the first in program order, and the +// head resolves that member first when it finishes; see popCircularHead. +// Everything else cached during the cycle is discarded like for +// isResolutionInProgress. It returns the index in resolutions of the head, or +// -1 if target is not in progress. +func (c *Checker) observeCircularHead(target TypeSystemEntity, propertyName TypeSystemPropertyName, compare func(a, b TypeSystemEntity) int) int { + start := c.findResolutionCycleStartIndex(target, propertyName) + if start < 0 { + return -1 + } + c.markTypeResolutionCircular(start) + head := &c.resolutions[start].typeResolution + canonical := core.OrElse(head.canonical, head.target) + for i := start + 1; i < len(c.resolutions); i++ { + if r := &c.resolutions[i].typeResolution; c.resolutions[i].isTypeResolution && r.propertyName == propertyName && compare(r.target, canonical) < 0 { + canonical = r.target } + } + head.canonical = canonical + return start +} + +// addCycleParticipants keeps the results of the type resolutions from start +// to the top of the stack when the cycle in progress ends, as they would be +// kept had the resolution at start been entered first. +func (c *Checker) addCycleParticipants(start int) { + for i := start; i < len(c.resolutions); i++ { + if r := &c.resolutions[i].typeResolution; c.resolutions[i].isTypeResolution && r.result && !r.participant { + r.participant = true + c.cycleParticipants = append(c.cycleParticipants, *r) + } + } +} + +// popCircularHead pops a resolution that may head a cycle observed by +// observeCircularHead. It returns the cycle's canonical member when that is +// not the head itself, in which case everything cached during the cycle has +// been discarded and the caller must resolve the canonical member before +// resolving the head again. +func (c *Checker) popCircularHead() TypeSystemEntity { + r := c.topTypeResolution() + canonical := r.canonical + if canonical == r.target { + canonical = nil + } + c.popTypeResolution() + return canonical +} + +// enterSharedCurrentNode is enterCurrentNode for a node that shares the +// resolution of the enclosing node, small enough to inline for the common +// case. It reports false, changing nothing, if the node needs its own. +func (c *Checker) enterSharedCurrentNode(node *ast.Node) (savedCurrentNode, bool) { + save := savedCurrentNode{node: c.currentNode, resolution: c.currentNodeResolution} + if c.currentNodeResolution == 0 || c.currentNodeResolution != len(c.resolutions) || c.circularityStart != 0 || len(c.innerCycles) != 0 { + return save, false + } + c.currentNode = node + return save, true +} + +// exitSharedCurrentNode is exitCurrentNode for a node entered without a +// resolution of its own, when that ends no cycle. It reports false, changing +// nothing, otherwise. +func (c *Checker) exitSharedCurrentNode(save savedCurrentNode) bool { + if c.currentNodeResolution != save.resolution || c.circularityStart >= save.resolution || len(c.innerCycles) != 0 { + return false + } + c.currentNode = save.node + return true +} + +// enterCurrentNode makes node the node being checked, until the matching +// exitCurrentNode. Its type resolution delimits the results that errors +// located at node (see reportAtCurrentNode) depend on. +func (c *Checker) enterCurrentNode(node *ast.Node) savedCurrentNode { + save := savedCurrentNode{node: c.currentNode, resolution: c.currentNodeResolution} + if c.circularityStart != 0 && c.circularityStart == c.currentNodeResolution && c.typeResolutionTop == c.currentNodeResolution { + // The results discarded when the current node is left depend on errors + // located at it, so a node within it must not reuse them either. + c.endCircularity() + } + c.currentNode = node + if c.currentNodeResolution != 0 && c.currentNodeResolution == len(c.resolutions) && c.circularityStart == 0 && len(c.innerCycles) == 0 { + // Nothing separates the node from the one enclosing it, so the + // resolution of that one stands in for it: a cycle through the node + // starts at that resolution, and nothing else can until the node is + // left, where exitCurrentNode ends it as popping the node's own + // resolution would. + return save + } + // beginTypeResolution, without the call, for a resolution that is not + // searchable. + f := c.pushResolution() + parent := &c.resolutions[len(c.resolutions)-2] + f.depthStart = parent.depthStart + f.serial = parent.serial + f.prevMemo = int32(c.memoTop) + f.isTypeResolution = true + f.prevTypeResolution = int32(c.typeResolutionTop) + f.typeResolution.target = node + f.typeResolution.propertyName = TypeSystemPropertyNameCurrentNode + f.typeResolution.result = true + c.typeResolutionTop = len(c.resolutions) + c.currentNodeResolution = c.typeResolutionTop + return save +} + +func (c *Checker) exitCurrentNode(save savedCurrentNode) { + if c.currentNodeResolution == save.resolution { + // The node had no resolution of its own; see enterCurrentNode. + if c.circularityStart >= save.resolution { + c.endCircularity() + } else if len(c.innerCycles) != 0 { + c.endInnerCycles(save.resolution - 1) + } + c.currentNode = save.node + return + } + // popTypeResolution, without the call. + n := len(c.resolutions) - 1 + debug.Assert(c.currentNodeResolution == n+1 && c.typeResolutionTop == n+1, "resolutions left on the stack") + f := &c.resolutions[n] + c.typeResolutionTop = int(f.prevTypeResolution) + *f = resolution{} + c.resolutions = c.resolutions[:n] + if c.circularityStart > n { + c.endCircularity() + } else if len(c.innerCycles) != 0 { + c.endInnerCycles(n) + } + if len(c.cycleYields) != 0 && c.cycleYields[len(c.cycleYields)-1].end == n { + c.endCycleYield() + } + c.currentNode = save.node + c.currentNodeResolution = save.resolution +} + +// enterQuery starts a query made outside of checking, unless one is already +// in progress. Results that depend on errors located at the current node are +// discarded when the query finishes (see reportAtCurrentNode), so a query +// that reads results back from caches after computing them must do so before +// then. +func (c *Checker) enterQuery() (savedCurrentNode, bool) { + if c.memoTop != 0 || c.currentNodeResolution != 0 { + return savedCurrentNode{}, false + } + return c.enterCurrentNode(c.currentNode), true +} + +func (c *Checker) exitQuery(save savedCurrentNode, ok bool) { + if ok { + c.exitCurrentNode(save) + } +} + +type savedCurrentNode struct { + node *ast.Node + resolution int +} + +// markCurrentNodeCircular records that results cached until the current node +// is left depend on that node. +func (c *Checker) markCurrentNodeCircular() { + if c.currentNodeResolution != 0 { + c.extendCircularity(c.currentNodeResolution) + } +} + +// markTypeResolutionCircular records that the computations from start to the +// top of the stack observed the incomplete resolution at start without +// failing, so results cached while it is in progress are discarded when it +// finishes. +func (c *Checker) markTypeResolutionCircular(start int) { + if c.checking && c.memoTop != 0 { + c.recordFromCheck() + } + if !c.checking { + c.memoCycle(int(c.resolutions[start].prevMemo)) + } + c.markCircular(start) +} + +// observedOnlyOutermostMeasurement reports whether the cycle in progress is +// the one headed by the outermost measurement of variances, which the +// resolution at index n is inside, and no other. +func (c *Checker) observedOnlyOutermostMeasurement(n int) bool { + if len(c.varianceStack) == 0 || len(c.innerCycles) != 0 { + return false + } + head := c.varianceStack[0].resolution + return n > head && c.circularityStart == head+1 +} + +// markCircular records that the computations from the resolution at start to +// the top of the stack observed the incomplete computation at start, so +// results cached while it is in progress are discarded when it finishes. +func (c *Checker) markCircular(start int) { + c.currentNodeErrorsCyclic = true + c.extendCircularity(start + 1) +} + +// depthLimit counts the nesting of a recursion that is cut short at a fixed +// depth. How deep a computation may go then depends on how deep it was +// entered, so a computation cut short keeps its result only if it started +// counting. +type depthLimit struct { + depth int32 + start int32 // Index in resolutions of the computation that started counting +} + +// depthLimitReached reports whether l is at limit, in which case the +// computations since the one that started counting are marked circular. +func (c *Checker) depthLimitReached(l *depthLimit, limit int32) bool { + if l.depth < limit { return false } - c.typeResolutions = append(c.typeResolutions, TypeResolution{target: target, propertyName: propertyName, result: true}) + c.markCircular(int(l.start)) return true } +func (c *Checker) enterDepthLimit(l *depthLimit) { + if l.depth == 0 { + l.start = int32(len(c.resolutions) - 1) + } + l.depth++ +} + +func (l *depthLimit) exit() { + l.depth-- +} + /** * Pop an entry from the type resolution stack and return its associated result value. The result value will * be true if no circularities were detected, or false if a circularity was found. */ func (c *Checker) popTypeResolution() bool { - lastIndex := len(c.typeResolutions) - 1 - result := c.typeResolutions[lastIndex].result - c.typeResolutions[lastIndex] = TypeResolution{} // Clear the last entry to avoid memory leaks - c.typeResolutions = c.typeResolutions[:lastIndex] + n := len(c.resolutions) - 1 + debug.Assert(c.typeResolutionTop == n+1, "resolutions left above a type resolution") + f := &c.resolutions[n] + result := f.typeResolution.result + if r := &f.typeResolution; result && !r.participant && c.observedOnlyOutermostMeasurement(n) { + // The outermost measurement of variances hides what is below it and + // is measured from the canonical entry of its circularity, so what + // it computes is the same whatever led to it. This resolution only + // observed its incomplete variances, so its result is what it + // computes first from scratch too, which reaches the measurement and + // computes it again inside, as main keeps the first result set. + r.participant = true + c.cycleParticipants = append(c.cycleParticipants, *r) + } + c.typeResolutionTop = int(f.prevTypeResolution) + if f.typeResolution.propertyName != TypeSystemPropertyNameCurrentNode { + c.searchableTop = int(f.prevSearchable) + } + c.resolutions[n] = resolution{} + c.resolutions = c.resolutions[:n] + if c.circularityStart > n { + c.endCircularity() + } else if len(c.innerCycles) != 0 { + c.endInnerCycles(n) + } + if len(c.cycleYields) != 0 && c.cycleYields[len(c.cycleYields)-1].end == n { + c.endCycleYield() + } return result } +// beginTypeResolution starts the resolution of propertyName for target, which +// re-entering detects as a cycle. Its resolution shares the work of the +// resolution below it. +func (c *Checker) beginTypeResolution(target TypeSystemEntity, propertyName TypeSystemPropertyName) { + f := c.pushResolution() + parent := &c.resolutions[len(c.resolutions)-2] + f.depthStart = parent.depthStart + f.serial = parent.serial + f.prevMemo = int32(c.memoTop) + f.isTypeResolution = true + f.prevTypeResolution = int32(c.typeResolutionTop) + f.typeResolution.target = target + f.typeResolution.propertyName = propertyName + f.typeResolution.result = true + c.typeResolutionTop = len(c.resolutions) + if propertyName != TypeSystemPropertyNameCurrentNode { + f.prevSearchable = int32(c.searchableTop) + c.searchableTop = len(c.resolutions) + } +} + +// topTypeResolution returns the innermost resolution, which must be the +// innermost resolution. +func (c *Checker) topTypeResolution() *TypeResolution { + debug.Assert(c.typeResolutionTop == len(c.resolutions), "resolutions left above a type resolution") + return &c.resolutions[len(c.resolutions)-1].typeResolution +} + func (c *Checker) findResolutionCycleStartIndex(target TypeSystemEntity, propertyName TypeSystemPropertyName) int { - for i := len(c.typeResolutions) - 1; i >= c.resolutionStart; i-- { - resolution := &c.typeResolutions[i] + for i := c.searchableTop - 1; i >= c.resolutionStart; i = int(c.resolutions[i].prevSearchable) - 1 { + resolution := &c.resolutions[i].typeResolution if c.typeResolutionHasProperty(resolution) { + // The result was set before its computation finished, which + // hides a cycle below it. Only then does the search depend on + // the result. + if c.memoActive() && c.findResolutionBelow(i, target, propertyName) { + if p := c.typeResolutionMemo(resolution); p != nil { + c.observeCell(p) + } + } return -1 } if resolution.target == target && resolution.propertyName == propertyName { @@ -19605,30 +20811,56 @@ func (c *Checker) findResolutionCycleStartIndex(target TypeSystemEntity, propert return -1 } +// findResolutionBelow reports whether a type resolution of target below the +// one at index i is in progress. +func (c *Checker) findResolutionBelow(i int, target TypeSystemEntity, propertyName TypeSystemPropertyName) bool { + for i = int(c.resolutions[i].prevSearchable) - 1; i >= c.resolutionStart; i = int(c.resolutions[i].prevSearchable) - 1 { + r := &c.resolutions[i].typeResolution + if r.target == target && r.propertyName == propertyName { + return true + } + } + return false +} + +// typeResolutionHasProperty reports whether a resolution on the stack already +// has a result. Its computation is in progress, so the result is read without +// observing it; see findResolutionCycleStartIndex. func (c *Checker) typeResolutionHasProperty(r *TypeResolution) bool { switch r.propertyName { case TypeSystemPropertyNameType: - return c.valueSymbolLinks.Get(r.target.(*ast.Symbol)).resolvedType != nil + return c.valueSymbolLinks.Get(r.target.(*ast.Symbol)).resolvedType.peek() != nil case TypeSystemPropertyNameDeclaredType: - return c.typeAliasLinks.Get(r.target.(*ast.Symbol)).declaredType != nil + return c.typeAliasLinks.Get(r.target.(*ast.Symbol)).declaredType.peek() != nil case TypeSystemPropertyNameResolvedTypeArguments: - return r.target.(*Type).AsTypeReference().resolvedTypeArguments != nil + return r.target.(*Type).AsTypeReference().resolvedTypeArguments.peek() != nil case TypeSystemPropertyNameResolvedBaseTypes: return r.target.(*Type).AsInterfaceType().baseTypesResolved case TypeSystemPropertyNameResolvedBaseConstructorType: - return r.target.(*Type).AsInterfaceType().resolvedBaseConstructorType != nil + return r.target.(*Type).AsInterfaceType().resolvedBaseConstructorType.peek() != nil case TypeSystemPropertyNameResolvedReturnType: - return r.target.(*Signature).resolvedReturnType != nil + return r.target.(*Signature).resolvedReturnType.peek() != nil case TypeSystemPropertyNameResolvedBaseConstraint: - return r.target.(*Type).AsConstrainedType().resolvedBaseConstraint != nil + return r.target.(*Type).AsConstrainedType().resolvedBaseConstraint.peek() != nil case TypeSystemPropertyNameInitializerIsUndefined: return c.nodeLinks.Get(r.target.(*ast.Node)).flags&NodeCheckFlagsInitializerIsUndefinedComputed != 0 case TypeSystemPropertyNameWriteType: - return c.valueSymbolLinks.Get(r.target.(*ast.Symbol)).getWriteType() != nil + extra := c.valueSymbolLinks.Get(r.target.(*ast.Symbol)).extra + return extra != nil && extra.writeType.peek() != nil case TypeSystemPropertyNameAliasTarget: - return c.aliasSymbolLinks.Get(r.target.(*ast.Symbol)).aliasTarget != nil + return c.aliasSymbolLinks.Get(r.target.(*ast.Symbol)).aliasTarget.peek() != nil + case TypeSystemPropertyNameContextChecked, TypeSystemPropertyNameStructuredMembers, TypeSystemPropertyNameCurrentNode, + TypeSystemPropertyNameSimplifiedForReading, TypeSystemPropertyNameSimplifiedForWriting, TypeSystemPropertyNameDefaultType, + TypeSystemPropertyNameAwaitedType, TypeSystemPropertyNameExplicitType, + TypeSystemPropertyNameBindingElementParent, TypeSystemPropertyNameCallResolution, + TypeSystemPropertyNameAssertionOperand, TypeSystemPropertyNamePatternInitializer: + // Marked as checked or resolved on entry, so it is only on the stack + // while in progress. + return false case TypeSystemPropertyNameComputedPropertyName: - return c.typeNodeLinks.Get(r.target.(*ast.Node)).resolvedType != nil + return c.typeNodeLinks.Get(r.target.(*ast.Node)).resolvedType.peek() != nil + case TypeSystemPropertyNameVariances: + return c.varianceLinks.Get(r.target.(*ast.Symbol)).variances.peek() != nil } panic("Unhandled case in typeResolutionHasProperty") } @@ -19667,7 +20899,7 @@ func (c *Checker) getPropertiesOfType(t *Type) []*ast.Symbol { func (c *Checker) getPropertiesOfObjectType(t *Type) []*ast.Symbol { if t.flags&TypeFlagsObject != 0 { - return c.resolveStructuredTypeMembers(t).getProperties() + return c.resolveStructuredTypeMembers(t).getProperties(c) } return nil } @@ -19698,28 +20930,31 @@ func (c *Checker) getLazyStateOfType(t *Type) *LazyState { func (c *Checker) getPropertiesOfUnionOrIntersectionType(t *Type) []*ast.Symbol { d := t.AsUnionOrIntersectionType() - if d.resolvedProperties == nil { - var checked collections.Set[string] - props := []*ast.Symbol{} - for _, current := range d.types { - for _, prop := range c.getPropertiesOfType(current) { - if !checked.Has(prop.Name()) { - checked.Add(prop.Name()) - combinedProp := c.getPropertyOfUnionOrIntersectionType(t, prop.Name(), t.flags&TypeFlagsIntersection != 0 /*skipObjectFunctionPropertyAugment*/) - if combinedProp != nil { - props = append(props, combinedProp) - } + if props := d.resolvedProperties.get(c); props != nil { + return props + } + d.resolvedProperties.begin(c) + var checked collections.Set[string] + props := []*ast.Symbol{} + for _, current := range d.types { + for _, prop := range c.getPropertiesOfType(current) { + if !checked.Has(prop.Name()) { + checked.Add(prop.Name()) + combinedProp := c.getPropertyOfUnionOrIntersectionType(t, prop.Name(), t.flags&TypeFlagsIntersection != 0 /*skipObjectFunctionPropertyAugment*/) + if combinedProp != nil { + props = append(props, combinedProp) } } - // The properties of a union type are those that are present in all constituent types, so - // we only need to check the properties of the first type without index signature - if t.flags&TypeFlagsUnion != 0 && len(c.getIndexInfosOfType(current)) == 0 { - break - } } - d.resolvedProperties = props + // The properties of a union type are those that are present in all constituent types, so + // we only need to check the properties of the first type without index signature + if t.flags&TypeFlagsUnion != 0 && len(c.getIndexInfosOfType(current)) == 0 { + break + } } - return d.resolvedProperties + d.resolvedProperties.set(c, props) + c.endMemo() + return props } func (c *Checker) getPropertyOfType(t *Type, name string) *ast.Symbol { @@ -19744,7 +20979,7 @@ func (c *Checker) getPropertyOfTypeEx(t *Type, name string, skipObjectFunctionPr } } resolved := c.resolveStructuredTypeMembers(t) - symbol := resolved.getMember(name) + symbol := resolved.getMember(c, name) if symbol != nil { if !includeTypeOnlyMembers && t.symbol != nil && t.symbol.Flags()&ast.SymbolFlagsValueModule != 0 && c.moduleSymbolLinks.Get(t.symbol).typeOnlyExportStarMap[name] != nil { // If this is the type of a module, `resolved.members.get(name)` might have effectively skipped over @@ -19794,9 +21029,15 @@ func (c *Checker) getPropertyOfTypeEx(t *Type, name string, skipObjectFunctionPr // means that base classes of a class or interface can access members declared in the class or interface // during its resolution without causing a circularity error. Members resolved by this function are // eventually propagated into the resolved members of the class or interface. +// +// The answer depends on the resolution in progress, so it is observed as a +// cycle through it. func (c *Checker) getDeclaredPropertyOfResolvingType(t *Type, name string) *ast.Symbol { for i := len(c.resolvingObjectTypeStack) - 1; i >= 0; i-- { if c.resolvingObjectTypeStack[i].t == t { + if !c.isResolutionInProgress(t, TypeSystemPropertyNameStructuredMembers) { + return nil + } resolved := c.resolvingObjectTypeStack[i].resolvedMembers.Get(name) if resolved == nil { if symbol := c.resolveDeclaredMembers(getTargetType(t)).declaredMembers.Get(name); symbol != nil && c.isNamedMember(symbol, name) { @@ -19938,30 +21179,260 @@ func (c *Checker) isApplicableIndexType(source *Type, target *Type) bool { } func (c *Checker) resolveStructuredTypeMembers(t *Type) *StructuredType { - if t.objectFlags&ObjectFlagsMembersResolved == 0 { - switch { - case t.flags&TypeFlagsObject != 0: - switch { - case t.objectFlags&(ObjectFlagsClassOrInterface|ObjectFlagsReference) != 0: - c.resolveObjectTypeMembers(t) - case t.objectFlags&ObjectFlagsReverseMapped != 0: - c.resolveReverseMappedTypeMembers(t) - case t.objectFlags&ObjectFlagsAnonymous != 0: - c.resolveAnonymousTypeMembers(t) - case t.objectFlags&ObjectFlagsMapped != 0: - c.resolveMappedTypeMembers(t) - default: - panic("Unhandled case in resolveStructuredTypeMembers") + if t.objectFlags&ObjectFlagsMembersResolved != 0 && !c.observingMemos { + return t.asResolvedStructuredType() + } + return c.resolveStructuredTypeMembersSlow(t) +} + +// resolveStructuredTypeMembersSlow is the part of resolveStructuredTypeMembers +// past reading resolved members, out of line so that the rest inlines. +func (c *Checker) resolveStructuredTypeMembersSlow(t *Type) *StructuredType { + key := memoKeyOf(t, memoStructuredMembers) + if t.objectFlags&ObjectFlagsMembersResolved != 0 { + // Resolved members are only read. + c.observeMemo(key) + return t.AsStructuredType() + } + published := false + enclosing, inProgress := c.resolvingAnyIndex(key) + if !inProgress { + enclosing = -1 + } else { + // Resolving members may observe the type itself, whose members are + // then incomplete. + if c.isModuleTableType(t) { + if start := c.observeCircularHead(t, TypeSystemPropertyNameStructuredMembers, c.compareModuleTables); start >= 0 { + // When the table is canonical, the computations that read + // its partial members keep their results, as they would + // had the table been entered first. + c.addCycleParticipants(start) } - case t.flags&TypeFlagsUnion != 0: - c.resolveUnionTypeMembers(t) - case t.flags&TypeFlagsIntersection != 0: - c.resolveIntersectionTypeMembers(t) + } else { + c.isResolutionInProgress(t, TypeSystemPropertyNameStructuredMembers) + } + if j, ok := c.resolving.get(key); ok && int(j) < c.visibleStart { + // The members are being resolved below the cycle yield in + // progress, which then sees whatever part of them that + // resolution has published, rather than compute them from + // scratch, so the yield fails. + c.markResolutionCycle(c.visibleStart) + } + published = c.resolutions[enclosing].membersPublished + } + c.beginMemo(key) + if !published { + c.resolveStructuredTypeMembersUncached(t, enclosing) + } + c.endMemo() + return t.AsStructuredType() +} + +// resolveStructuredTypeMembersUncached resolves the members of t in the memo +// just begun for them. Many kinds of types make members available before +// they are complete, through publishStructuredTypeMembers, so the resolution +// is on the stack, but never fails. The members are resolved only once the +// outermost resolution of them completes. A nested one, which re-entering +// the members before any are available starts, makes its result available +// to the rest of the enclosing resolution at index enclosing, which may be +// hidden by a cycle yield. +func (c *Checker) resolveStructuredTypeMembersUncached(t *Type, enclosing int) { + outermost := enclosing < 0 + if outermost && c.isModuleTableType(t) { + c.resolveModuleTableMembers(t) + return + } + c.beginTypeResolution(t, TypeSystemPropertyNameStructuredMembers) + defer func() { + c.popTypeResolution() + if outermost { + t.objectFlags |= ObjectFlagsMembersResolved + } else { + c.resolutions[enclosing].membersPublished = true + } + if c.logResults() { + c.logMembersDiscard(t) + } + }() + switch { + case t.flags&TypeFlagsObject != 0: + switch { + case t.objectFlags&(ObjectFlagsClassOrInterface|ObjectFlagsReference) != 0: + c.resolveObjectTypeMembers(t) + case t.objectFlags&ObjectFlagsReverseMapped != 0: + c.resolveReverseMappedTypeMembers(t) + case t.objectFlags&ObjectFlagsAnonymous != 0: + c.resolveAnonymousTypeMembers(t) + case t.objectFlags&ObjectFlagsMapped != 0: + c.resolveMappedTypeMembers(t) default: panic("Unhandled case in resolveStructuredTypeMembers") } + case t.flags&TypeFlagsUnion != 0: + c.resolveUnionTypeMembers(t) + case t.flags&TypeFlagsIntersection != 0: + c.resolveIntersectionTypeMembers(t) + default: + panic("Unhandled case in resolveStructuredTypeMembers") } - return t.AsStructuredType() +} + +// markMembersPublished records that the members of t, whose resolution is +// in progress, are available to computations re-entering it. +func (c *Checker) markMembersPublished(t *Type) { + i, ok := c.resolvingAnyIndex(memoKeyOf(t, memoStructuredMembers)) + debug.Assert(ok, "members published outside their resolution") + c.resolutions[i].membersPublished = true +} + +// publishStructuredTypeMembers sets the members of t while they are being +// resolved, making them available to computations re-entering the resolution +// before it completes. +func (c *Checker) publishStructuredTypeMembers(t *Type, members ast.SymbolTable, callSignatures []*Signature, constructSignatures []*Signature, indexInfos []*IndexInfo) { + // Listing the properties may re-enter the resolution, which then sees + // the members. + c.markMembersPublished(t) + c.setStructuredTypeData(t, members, callSignatures, constructSignatures, indexInfos) +} + +// isModuleTableType reports whether t is the type of a module object, whose +// members are the exports of the module. +func (c *Checker) isModuleTableType(t *Type) bool { + if t.objectFlags&ObjectFlagsAnonymous == 0 || t.symbol == nil || t.AsObjectType().target != nil { + return false + } + flags := c.getMergedSymbol(t.symbol).Flags() + return flags&ast.SymbolFlagsValueModule != 0 && flags&(ast.SymbolFlagsFunction|ast.SymbolFlagsClass|ast.SymbolFlagsEnum|ast.SymbolFlagsTypeLiteral) == 0 +} + +// compareModuleTables orders the resolutions of module object types in a +// cycle by the declarations of their modules, before any other resolutions. +func (c *Checker) compareModuleTables(a, b TypeSystemEntity) int { + ta, tb := a.(*Type), b.(*Type) + ma, mb := c.isModuleTableType(ta), c.isModuleTableType(tb) + switch { + case ma && mb: + return c.compareSymbols(ta.symbol, tb.symbol) + case ma: + return -1 + case mb: + return 1 + } + return 0 +} + +// resolveModuleTableMembers resolves the members of a module object type. +// Members are available while they are resolved, so module tables that +// read each other's members see the partial members of whichever was +// entered first. The table of the module first in program order is always +// resolved first, discarding whatever was computed starting from another, +// and a table that a cycle entered elsewhere passes through is resolved +// again as if the cycle had been entered at the table. +func (c *Checker) resolveModuleTableMembers(t *Type) { + if c.checking { + // Results computed starting from another table are discarded along + // with their effects, so resolve while recording effects. + c.withoutChecking(func() { + c.resolveModuleTableMembers(t) + }) + return + } + mark := c.markDiscard() + hidden := false + for { + c.beginTypeResolution(t, TypeSystemPropertyNameStructuredMembers) + c.resolveAnonymousTypeMembers(t) + r := c.topTypeResolution() + restart := r.restartHidden && !hidden + canonical := r.canonical + if canonical == t { + canonical = nil + } + if canonical != nil || restart { + // Discard while the cycle is still in progress, so that the + // results of its participants are discarded too. + c.discardSince(mark) + } + c.popTypeResolution() + if canonical == nil && !restart { + break + } + c.clearStructuredMembers(t) + key := memoKeyOf(t, memoStructuredMembers) + i, _ := c.resolving.get(key) + c.resolutions[i].membersPublished = false + if canonical != nil { + c.resolveStructuredTypeMembers(canonical.(*Type)) + continue + } + // A cycle entered below the table passed through it, so the table + // is resolved again as if it had been entered first: with the + // computations in progress below it hidden. + hidden = true + c.beginCycleYield(t, TypeSystemPropertyNameStructuredMembers, -1, int(i)) + } + if hidden { + c.endCycleYield() + } + t.objectFlags |= ObjectFlagsMembersResolved + c.releaseDiscardMark(mark) + if c.logResults() { + c.logMembersDiscard(t) + } +} + +func (c *Checker) clearStructuredMembers(t *Type) { + t.objectFlags &^= ObjectFlagsMembersResolved + data := t.AsStructuredType() + data.members = nil + data.properties = nil + if data.extra != nil { + data.extra.signatures = nil + data.extra.callSignatureCount = 0 + data.extra.indexInfos = nil + } +} + +// logMembersDiscard discards the members of t when the cycle in progress +// ends, like any other result cached during the cycle, so that they are +// resolved again from scratch when next requested. +// +//go:noinline +func (c *Checker) logMembersDiscard(t *Type) { + c.discardLog = append(c.discardLog, discardEntry{nil, func() { + if _, ok := c.resolvingAnyIndex(memoKeyOf(t, memoStructuredMembers)); ok { + // An enclosing resolution of the same members sets them again. + return + } + t.objectFlags &^= ObjectFlagsMembersResolved + data := t.AsStructuredType() + data.members = nil + data.properties = nil + if data.extra != nil { + data.extra.signatures = nil + data.extra.callSignatureCount = 0 + data.extra.indexInfos = nil + } + if t.objectFlags&(ObjectFlagsClassOrInterface|ObjectFlagsReference) != 0 { + t.AsTypeReference().lazy = nil + } + }, false}) +} + +// logPropertySlotDiscard discards the property in slot index of t, filled on +// first use during a cycle, when the cycle ends. The property may be one of a +// base type whose members the cycle discards. +// +//go:noinline +func (c *Checker) logPropertySlotDiscard(t *StructuredType, lazy *LazyState, index int) { + properties := t.properties + c.discardLog = append(c.discardLog, discardEntry{nil, func() { + properties[index] = nil + lazy.propertiesResolved = false + if t.AsType().AsTypeReference().lazy == lazy { + t.members = nil + } + }, false}) } func (c *Checker) resolveObjectTypeMembers(t *Type) { @@ -20015,7 +21486,7 @@ func (c *Checker) resolveObjectTypeMembers(t *Type) { return findIndexInfo(indexInfos, info.keyType) == nil })) } - c.setStructuredTypeMembers(t, nil, callSignatures, constructSignatures, indexInfos) + c.setStructuredTypeData(t, nil, callSignatures, constructSignatures, indexInfos) lazyState := c.lazyStateArena.New() lazyState.layout = layout lazyState.mapper = mapper @@ -20046,15 +21517,12 @@ func (c *Checker) getMemberLayout(t *Type, baseTypes []*Type) *MemberLayout { } source := c.getTargetType(t) key := c.getMemberLayoutKey(source, baseTypes) - layout := c.memberLayoutCache[key] - if layout == nil { - layout = c.createMemberLayout(source, baseTypes) - if c.memberLayoutCache == nil { - c.memberLayoutCache = make(map[CacheHashKey]*MemberLayout) - } - c.memberLayoutCache[key] = layout + if layout, ok := c.memberLayoutCache.get(c, key); ok { + return layout } - return layout + return c.memberLayoutCache.compute(c, key, func() *MemberLayout { + return c.createMemberLayout(source, baseTypes) + }) } func (c *Checker) getMemberLayoutKey(t *Type, baseTypes []*Type) CacheHashKey { @@ -20133,17 +21601,36 @@ func findIndexInfo(indexInfos []*IndexInfo, keyType *Type) *IndexInfo { } func (c *Checker) getBaseTypes(t *Type) []*Type { + if t.objectFlags&(ObjectFlagsClassOrInterface|ObjectFlagsTuple) == 0 { + return nil + } + if data := t.AsInterfaceType(); data.baseTypesResolved { + // Observing the result is what re-entering its computation amounts + // to, without the resolution. Walks over base types such as + // hasBaseType revisit shared bases many times. + if c.memoActive() { + c.observeMemo(memoKeyOf(t, memoBaseTypes)) + } + return data.resolvedBaseTypes.get(c) + } + c.beginMemo(memoKeyOf(t, memoBaseTypes)) + result := c.getBaseTypesUncached(t) + c.endMemo() + return result +} + +func (c *Checker) getBaseTypesUncached(t *Type) []*Type { if t.objectFlags&(ObjectFlagsClassOrInterface|ObjectFlagsTuple) == 0 { return nil } data := t.AsInterfaceType() if !data.baseTypesResolved { if !c.pushTypeResolution(t, TypeSystemPropertyNameResolvedBaseTypes) { - return data.resolvedBaseTypes + return data.resolvedBaseTypes.get(c) } switch { case t.objectFlags&ObjectFlagsTuple != 0: - data.resolvedBaseTypes = []*Type{c.getTupleBaseType(t)} + data.resolvedBaseTypes.set(c, []*Type{c.getTupleBaseType(t)}) case t.symbol.Flags()&(ast.SymbolFlagsClass|ast.SymbolFlagsInterface) != 0: if t.symbol.Flags()&ast.SymbolFlagsClass != 0 { c.resolveBaseTypesOfClass(t) @@ -20167,8 +21654,11 @@ func (c *Checker) getBaseTypes(t *Type) []*Type { // See https://github.com/microsoft/TypeScript/issues/16861 for an example. t.objectFlags &^= ObjectFlagsMembersResolved data.baseTypesResolved = true + if c.logResults() { + c.discardLog = append(c.discardLog, discardEntry{&data.resolvedBaseTypes.memoHeader, func() { data.baseTypesResolved = false }, false}) + } } - return data.resolvedBaseTypes + return data.resolvedBaseTypes.get(c) } func (c *Checker) getTupleBaseType(t *Type) *Type { @@ -20229,7 +21719,7 @@ func (c *Checker) resolveBaseTypesOfClass(t *Type) { c.error(t.symbol.ValueDeclaration(), diagnostics.Type_0_recursively_references_itself_as_a_base_type, c.TypeToString(t)) return } - t.AsInterfaceType().resolvedBaseTypes = []*Type{reducedBaseType} + t.AsInterfaceType().resolvedBaseTypes.set(c, []*Type{reducedBaseType}) } func getBaseTypeNodeOfClass(t *Type) *ast.Node { @@ -20268,7 +21758,7 @@ func (c *Checker) getSignatureInstantiation(sig *Signature, typeArguments []*Typ newReturnType := c.getOrCreateTypeFromSignature(newReturnSignature) newReturnType.AsObjectType().mapper = instantiatedSignature.mapper newInstantiatedSignature := c.cloneSignature(instantiatedSignature) - newInstantiatedSignature.resolvedReturnType = newReturnType + newInstantiatedSignature.resolvedReturnType.intern(newReturnType) return newInstantiatedSignature } } @@ -20285,10 +21775,12 @@ func (c *Checker) cloneSignature(sig *Signature) *Signature { func (c *Checker) getSignatureInstantiationWithoutFillingInTypeArguments(sig *Signature, typeArguments []*Type) *Signature { key := CachedSignatureKey{sig: sig, key: getTypeListKey(typeArguments)} - instantiation := c.cachedSignatures[key] + instantiation, _ := c.cachedSignatures.get(c, key) if instantiation == nil { + c.cachedSignatures.begin(c, key) instantiation = c.createSignatureInstantiation(sig, typeArguments) - c.cachedSignatures[key] = instantiation + c.cachedSignatures.set(c, key, instantiation) + c.endMemo() } return instantiation } @@ -20366,10 +21858,12 @@ func (c *Checker) getErasedSignature(signature *Signature) *Signature { return signature } key := CachedSignatureKey{sig: signature, key: SignatureKeyErased} - erased := c.cachedSignatures[key] + erased, _ := c.cachedSignatures.get(c, key) if erased == nil { + c.cachedSignatures.begin(c, key) erased = c.instantiateSignatureEx(signature, newArrayToSingleTypeMapper(signature.TypeParameters(), c.anyType), true /*eraseTypeParameters*/) - c.cachedSignatures[key] = erased + c.cachedSignatures.set(c, key, erased) + c.endMemo() } return erased } @@ -20379,10 +21873,12 @@ func (c *Checker) getCanonicalSignature(signature *Signature) *Signature { return signature } key := CachedSignatureKey{sig: signature, key: SignatureKeyCanonical} - canonical := c.cachedSignatures[key] + canonical, _ := c.cachedSignatures.get(c, key) if canonical == nil { + c.cachedSignatures.begin(c, key) canonical = c.createCanonicalSignature(signature) - c.cachedSignatures[key] = canonical + c.cachedSignatures.set(c, key, canonical) + c.endMemo() } return canonical } @@ -20409,9 +21905,10 @@ func (c *Checker) getBaseSignature(signature *Signature) *Signature { return signature } key := CachedSignatureKey{sig: signature, key: SignatureKeyBase} - if cached := c.cachedSignatures[key]; cached != nil { + if cached, _ := c.cachedSignatures.get(c, key); cached != nil { return cached } + c.cachedSignatures.begin(c, key) baseConstraintMapper := newTypeMapper(typeParameters, core.Map(typeParameters, func(tp *Type) *Type { return core.OrElse(c.getConstraintOfTypeParameter(tp), c.unknownType) })) @@ -20426,7 +21923,8 @@ func (c *Checker) getBaseSignature(signature *Signature) *Signature { // and then apply a type eraser to remove any remaining circularly dependent type parameters baseConstraints = c.instantiateTypes(baseConstraints, newArrayToSingleTypeMapper(typeParameters, c.anyType)) result := c.instantiateSignatureEx(signature, newTypeMapper(typeParameters, baseConstraints), true /*eraseTypeParameters*/) - c.cachedSignatures[key] = result + c.cachedSignatures.set(c, key, result) + c.endMemo() return result } @@ -20472,7 +21970,7 @@ func (c *Checker) resolveBaseTypesOfInterface(t *Type) { if !c.isErrorType(baseType) { if c.isValidBaseType(baseType) { if t != baseType && !c.hasBaseType(baseType, t) { - data.resolvedBaseTypes = append(data.resolvedBaseTypes, baseType) + data.resolvedBaseTypes.set(c, append(data.resolvedBaseTypes.get(c), baseType)) } else { c.reportCircularBaseType(declaration, t) } @@ -20586,16 +22084,32 @@ func (c *Checker) addInheritedMembers(symbols ast.SymbolTable, baseSymbols []*as } func (c *Checker) resolveDeclaredMembers(t *Type) *InterfaceType { + d := t.AsInterfaceType() + key := memoKeyOf(t, memoDeclaredMembers) + if d.declaredMembersResolved { + c.observeFlagMemo(key) + return d + } + c.beginMemo(key) + c.resolveDeclaredMembersUncached(t) + c.endMemo() + return d +} + +func (c *Checker) resolveDeclaredMembersUncached(t *Type) { d := t.AsInterfaceType() if !d.declaredMembersResolved { members := c.getMembersOfSymbol(t.symbol) d.declaredMembersResolved = true + if c.logResults() { + c.logFlagMemoDiscard(func() { d.declaredMembersResolved = false }) + } + c.setFlagMemo(memoKeyOf(t, memoDeclaredMembers)) d.declaredMembers = members d.declaredCallSignatures = c.getSignaturesOfSymbol(d.declaredMembers.Get(ast.InternalSymbolNameCall)) d.declaredConstructSignatures = c.getSignaturesOfSymbol(d.declaredMembers.Get(ast.InternalSymbolNameNew)) d.declaredIndexInfos = c.getIndexInfosOfSymbol(t.symbol) } - return d } func (c *Checker) getIndexInfosOfSymbol(symbol *ast.Symbol) []*IndexInfo { @@ -20822,8 +22336,8 @@ func (c *Checker) getSignatureFromDeclaration(declaration *ast.Node) *Signature // of context-sensitive functions. func (c *Checker) getSignatureFromDeclarationUncontextual(declaration *ast.Node) *Signature { links := c.signatureLinks.Get(declaration) - if links.resolvedSignature != nil { - return links.resolvedSignature + if links.resolvedSignature.get(c) != nil { + return links.resolvedSignature.get(c) } var parameters []*ast.Symbol var flags SignatureFlags @@ -20892,14 +22406,14 @@ func (c *Checker) getSignatureFromDeclarationUncontextual(declaration *ast.Node) if ast.IsConstructorTypeNode(declaration) && ast.HasSyntacticModifier(declaration, ast.ModifierFlagsAbstract) || ast.IsConstructorDeclaration(declaration) && ast.HasSyntacticModifier(declaration.Parent, ast.ModifierFlagsAbstract) { flags |= SignatureFlagsAbstract } - if links.resolvedSignature != nil { + if existing := links.resolvedSignature.get(c); existing != nil { // Computing the signature reached the enclosing call, which // contextually typed the function with a signature of its own, as // when the call is checked first. Keep that one. - return links.resolvedSignature + return existing } - links.resolvedSignature = c.newSignature(flags, declaration, typeParameters, thisParameter, parameters, nil /*resolvedReturnType*/, nil /*resolvedTypePredicate*/, minArgumentCount) - return links.resolvedSignature + links.resolvedSignature.set(c, c.newSignature(flags, declaration, typeParameters, thisParameter, parameters, nil /*resolvedReturnType*/, nil /*resolvedTypePredicate*/, minArgumentCount)) + return links.resolvedSignature.get(c) } func (c *Checker) getTypeParametersFromDeclaration(declaration *ast.Node) []*Type { @@ -20992,8 +22506,21 @@ func isLateBindableAST(node *ast.Node) bool { } func (c *Checker) getReturnTypeOfSignature(sig *Signature) *Type { - if sig.resolvedReturnType != nil { - return sig.resolvedReturnType + if t := sig.resolvedReturnType.get(c); t != nil { + return t + } + if c.memoActive() { + sig.resolvedReturnType.begin(c) + result := c.getReturnTypeOfSignatureUncached(sig) + c.endMemo() + return result + } + return c.getReturnTypeOfSignatureUncached(sig) +} + +func (c *Checker) getReturnTypeOfSignatureUncached(sig *Signature) *Type { + if sig.resolvedReturnType.get(c) != nil { + return sig.resolvedReturnType.get(c) } if !c.pushTypeResolution(sig, TypeSystemPropertyNameResolvedReturnType) { return c.errorType @@ -21020,25 +22547,31 @@ func (c *Checker) getReturnTypeOfSignature(sig *Signature) *Type { t = c.getOptionalType(t, false /*isProperty*/) } if !c.popTypeResolution() { - if sig.declaration != nil { - typeNode := sig.declaration.Type() - if typeNode != nil { - c.error(typeNode, diagnostics.Return_type_annotation_circularly_references_itself) - } else if c.noImplicitAny { - name := ast.GetNameOfDeclaration(sig.declaration) - if name != nil { - c.error(name, diagnostics.X_0_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions, scanner.DeclarationNameToString(name)) - } else { - c.error(sig.declaration, diagnostics.Function_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions) - } + t = c.circularReturnType(sig) + } + if sig.resolvedReturnType.get(c) == nil { + sig.resolvedReturnType.set(c, t) + } + return sig.resolvedReturnType.get(c) +} + +// circularReturnType reports a return type that depends on itself and returns +// the type it resolves to instead. +func (c *Checker) circularReturnType(sig *Signature) *Type { + if sig.declaration != nil { + typeNode := sig.declaration.Type() + if typeNode != nil { + c.error(typeNode, diagnostics.Return_type_annotation_circularly_references_itself) + } else if c.noImplicitAny { + name := ast.GetNameOfDeclaration(sig.declaration) + if name != nil { + c.error(name, diagnostics.X_0_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions, scanner.DeclarationNameToString(name)) + } else { + c.error(sig.declaration, diagnostics.Function_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions) } } - t = c.anyType - } - if sig.resolvedReturnType == nil { - sig.resolvedReturnType = t } - return sig.resolvedReturnType + return c.anyType } func (c *Checker) getNonCircularReturnTypeOfSignature(sig *Signature) *Type { @@ -21643,22 +23176,22 @@ func (c *Checker) instantiateIndexInfo(info *IndexInfo, m *TypeMapper) *IndexInf func (c *Checker) resolveAnonymousTypeMembers(t *Type) { d := t.AsObjectType() if d.target != nil { - c.setStructuredTypeMembers(t, nil, nil, nil, nil) + c.publishStructuredTypeMembers(t, nil, nil, nil, nil) members := c.createInstantiatedSymbolTable(c.getPropertiesOfObjectType(d.target), d.mapper) callSignatures := c.instantiateSignatures(c.getSignaturesOfType(d.target, SignatureKindCall), d.mapper) constructSignatures := c.instantiateSignatures(c.getSignaturesOfType(d.target, SignatureKindConstruct), d.mapper) indexInfos := c.instantiateIndexInfos(c.getIndexInfosOfType(d.target), d.mapper) - c.setStructuredTypeMembers(t, members, callSignatures, constructSignatures, indexInfos) + c.publishStructuredTypeMembers(t, members, callSignatures, constructSignatures, indexInfos) return } symbol := c.getMergedSymbol(t.symbol) if symbol.Flags()&ast.SymbolFlagsTypeLiteral != 0 { - c.setStructuredTypeMembers(t, nil, nil, nil, nil) + c.publishStructuredTypeMembers(t, nil, nil, nil, nil) members := c.getMembersOfSymbol(symbol) callSignatures := c.getSignaturesOfSymbol(members.Get(ast.InternalSymbolNameCall)) constructSignatures := c.getSignaturesOfSymbol(members.Get(ast.InternalSymbolNameNew)) indexInfos := c.getIndexInfosOfSymbol(symbol) - c.setStructuredTypeMembers(t, members, callSignatures, constructSignatures, indexInfos) + c.publishStructuredTypeMembers(t, members, callSignatures, constructSignatures, indexInfos) return } // Combinations of function, class, enum and module @@ -21683,7 +23216,7 @@ func (c *Checker) resolveAnonymousTypeMembers(t *Type) { baseConstructorIndexInfo = c.anyBaseTypeIndexInfo } } - c.setStructuredTypeMembers(t, members, nil, nil, nil) + c.publishStructuredTypeMembers(t, members, nil, nil, nil) var indexInfos []*IndexInfo if indexSymbol := members.Get(ast.InternalSymbolNameIndex); indexSymbol != nil { indexInfos = c.getIndexInfosOfIndexSymbol(indexSymbol, slices.Collect(members.Values())) @@ -21691,7 +23224,7 @@ func (c *Checker) resolveAnonymousTypeMembers(t *Type) { if baseConstructorIndexInfo != nil { indexInfos = append(indexInfos, baseConstructorIndexInfo) } - if symbol.Flags()&ast.SymbolFlagsEnum != 0 && (c.getDeclaredTypeOfSymbol(symbol).flags&TypeFlagsEnum != 0 || core.Some(d.getProperties(), func(prop *ast.Symbol) bool { + if symbol.Flags()&ast.SymbolFlagsEnum != 0 && (c.getDeclaredTypeOfSymbol(symbol).flags&TypeFlagsEnum != 0 || core.Some(d.getProperties(c), func(prop *ast.Symbol) bool { return c.getTypeOfSymbol(prop).flags&TypeFlagsNumberLike != 0 })) { indexInfos = append(indexInfos, c.enumNumberIndexInfo) @@ -21747,12 +23280,12 @@ func (c *Checker) instantiateSymbol(symbol *ast.Symbol, m *TypeMapper) *ast.Symb } // If the type of the symbol is already resolved, and if that type could not possibly // be affected by instantiation, simply return the symbol itself. - if links.resolvedType != nil && !c.couldContainTypeVariables(links.resolvedType) { + if links.resolvedType.get(c) != nil && !c.couldContainTypeVariables(links.resolvedType.get(c)) { if symbol.Flags()&ast.SymbolFlagsSetAccessor == 0 { return symbol } // If we're a setter, check writeType. - if links.getWriteType() != nil && !c.couldContainTypeVariables(links.getWriteType()) { + if links.getWriteType(c) != nil && !c.couldContainTypeVariables(links.getWriteType(c)) { return symbol } } @@ -21862,7 +23395,7 @@ func (c *Checker) getDefaultConstructSignatures(classType *Type) []*Signature { sig = c.cloneSignature(baseSig) } sig.setTypeParameters(c, classType.AsInterfaceType().LocalTypeParameters()) - sig.resolvedReturnType = classType + sig.resolvedReturnType.intern(classType) if isAbstract { sig.flags |= SignatureFlagsAbstract } else { @@ -21878,7 +23411,7 @@ func (c *Checker) resolveMappedTypeMembers(t *Type) { members := ast.NewSymbolTable() var indexInfos []*IndexInfo // Resolve upfront such that recursive references see an empty object type. - c.setStructuredTypeMembers(t, nil, nil, nil, nil) + c.publishStructuredTypeMembers(t, nil, nil, nil, nil) // In { [P in K]: T }, we refer to P as the type parameter type, K as the constraint type, // and T as the template type. typeParameter := c.getTypeParameterFromMappedType(t) @@ -21961,15 +23494,15 @@ func (c *Checker) resolveMappedTypeMembers(t *Type) { } else { forEachType(c.getLowerBoundOfKeyType(constraintType), addMemberForKeyType) } - c.setStructuredTypeMembers(t, members, nil, nil, indexInfos) + c.publishStructuredTypeMembers(t, members, nil, nil, indexInfos) } func (c *Checker) getTypeOfMappedSymbol(symbol *ast.Symbol) *Type { links := c.valueSymbolLinks.Get(symbol) - if links.resolvedType == nil { + if links.resolvedType.get(c) == nil { mappedType := links.getContainingType() if !c.pushTypeResolution(symbol, TypeSystemPropertyNameType) { - mappedType.AsMappedType().containsError = true + mappedType.AsMappedType().containsError.setIdentity(c, true) return c.errorType } templateType := c.getTemplateTypeFromMappedType(core.OrElse(mappedType.AsMappedType().target, mappedType)) @@ -21985,17 +23518,17 @@ func (c *Checker) getTypeOfMappedSymbol(symbol *ast.Symbol) *Type { propType = c.removeMissingOrUndefinedType(propType) } if c.popTypeResolution() { - if links.resolvedType == nil { - links.resolvedType = propType + if links.resolvedType.get(c) == nil { + links.resolvedType.set(c, propType) } } else { - if links.resolvedType == nil { - links.resolvedType = c.errorType + if links.resolvedType.get(c) == nil { + links.resolvedType.set(c, c.errorType) } - c.error(c.currentNode, diagnostics.Type_of_property_0_circularly_references_itself_in_mapped_type_1, c.symbolToString(symbol), c.TypeToString(mappedType)) + c.errorAtCurrentNode(diagnostics.Type_of_property_0_circularly_references_itself_in_mapped_type_1, c.symbolToString(symbol), c.TypeToString(mappedType)) } } - return links.resolvedType + return links.resolvedType.get(c) } // Return the lower bound of the key type in a mapped type. Intuitively, the lower @@ -22048,7 +23581,7 @@ func (c *Checker) resolveUnionTypeMembers(t *Type) { return c.getSignaturesOfType(t, SignatureKindConstruct) })) indexInfos := c.getUnionIndexInfos(t.Types()) - c.setStructuredTypeMembers(t, nil, callSignatures, constructSignatures, indexInfos) + c.setStructuredTypeData(t, nil, callSignatures, constructSignatures, indexInfos) } func (c *Checker) getArrayMemberCallSignatures(t *Type) []*Signature { @@ -22250,18 +23783,18 @@ func (c *Checker) combineUnionOrIntersectionParameters(left *Signature, right *S core.IfElse(isRestParam, ast.CheckFlagsRestParameter, core.IfElse(isOptional, ast.CheckFlagsOptionalParameter, 0))) links := c.valueSymbolLinks.Get(paramSymbol) if isRestParam { - links.resolvedType = c.createArrayType(combinedParamType) + links.resolvedType.setIdentity(c, c.createArrayType(combinedParamType)) } else { - links.resolvedType = combinedParamType + links.resolvedType.setIdentity(c, combinedParamType) } params[i] = paramSymbol } if needsExtraRestElement { restParamSymbol := c.newSymbolEx(ast.SymbolFlagsFunctionScopedVariable, "args", ast.CheckFlagsRestParameter) links := c.valueSymbolLinks.Get(restParamSymbol) - links.resolvedType = c.createArrayType(c.getTypeAtPosition(shorter, longestCount)) + links.resolvedType.setIdentity(c, c.createArrayType(c.getTypeAtPosition(shorter, longestCount))) if shorter == right { - links.resolvedType = c.instantiateType(links.resolvedType, mapper) + links.resolvedType.setIdentity(c, c.instantiateType(links.resolvedType.get(c), mapper)) } params[longestCount] = restParamSymbol } @@ -22301,7 +23834,7 @@ func (c *Checker) resolveIntersectionTypeMembers(t *Type) { if len(signatures) != 0 && mixinCount > 0 { signatures = core.Map(signatures, func(s *Signature) *Signature { clone := c.cloneSignature(s) - clone.resolvedReturnType = c.includeMixinType(c.getReturnTypeOfSignature(s), types, mixinFlags, i) + clone.resolvedReturnType.intern(c.includeMixinType(c.getReturnTypeOfSignature(s), types, mixinFlags, i)) return clone }) } @@ -22312,7 +23845,7 @@ func (c *Checker) resolveIntersectionTypeMembers(t *Type) { indexInfos = c.appendIndexInfo(indexInfos, info, false /*union*/) } } - c.setStructuredTypeMembers(t, nil, callSignatures, constructSignatures, indexInfos) + c.setStructuredTypeData(t, nil, callSignatures, constructSignatures, indexInfos) } func (c *Checker) appendSignatures(signatures []*Signature, newSignatures []*Signature) []*Signature { @@ -22386,7 +23919,7 @@ func (c *Checker) includeMixinType(t *Type, types []*Type, mixinFlags []bool, in func (c *Checker) getPropertyOfObjectType(t *Type, name string) *ast.Symbol { if t.flags&TypeFlagsObject != 0 { resolved := c.resolveStructuredTypeMembers(t) - symbol := resolved.getMember(name) + symbol := resolved.getMember(c, name) if symbol != nil && c.symbolIsValue(symbol) { return symbol } @@ -22409,29 +23942,28 @@ func (c *Checker) getPropertyOfUnionOrIntersectionType(t *Type, name string, ski // these partial properties when identifying discriminant properties, but otherwise they are filtered out // and do not appear to be present in the union type. func (c *Checker) getUnionOrIntersectionProperty(t *Type, name string, skipObjectFunctionPropertyAugment bool) *ast.Symbol { - data := t.AsUnionOrIntersectionType() + d := t.AsUnionOrIntersectionType() + cache := &d.propertyCache if skipObjectFunctionPropertyAugment { - if prop := data.propertyCacheWithoutFunctionPropertyAugment.Get(name); prop != nil { - return prop - } - } else { - if prop := data.propertyCache.Get(name); prop != nil { - return prop - } + cache = &d.propertyCacheWithoutFunctionPropertyAugment + } + if prop, _ := cache.get(c, name); prop != nil { + return prop + } + if !skipObjectFunctionPropertyAugment { // Complete non-augmented properties are also valid for augmented // lookup. Share the entry instead of retaining it in both tables. - if prop := data.propertyCacheWithoutFunctionPropertyAugment.Get(name); prop != nil && prop.CheckFlags()&ast.CheckFlagsPartial == 0 { + if prop, _ := d.propertyCacheWithoutFunctionPropertyAugment.get(c, name); prop != nil && prop.CheckFlags()&ast.CheckFlagsPartial == 0 { return prop } } + // The property is created once, so its identity is kept. + cache.begin(c, name) prop := c.createUnionOrIntersectionProperty(t, name, skipObjectFunctionPropertyAugment) if prop != nil { - if skipObjectFunctionPropertyAugment { - ast.GetSymbolTable(&data.propertyCacheWithoutFunctionPropertyAugment).Set(name, prop) - } else { - ast.GetSymbolTable(&data.propertyCache).Set(name, prop) - } + cache.setIdentity(c, name, prop) } + c.endMemo() return prop } @@ -22576,7 +24108,7 @@ func (c *Checker) createUnionOrIntersectionProperty(containingType *Type, name s var singlePropMapper *TypeMapper if singleProp.Flags()&ast.SymbolFlagsTransient != 0 { links := c.valueSymbolLinks.Get(singleProp) - singlePropType = links.resolvedType + singlePropType = links.resolvedType.get(c) singlePropMapper = links.mapper } clone := c.createSymbolWithType(singleProp, singlePropType) @@ -22586,7 +24118,7 @@ func (c *Checker) createUnionOrIntersectionProperty(containingType *Type, name s links := c.valueSymbolLinks.Get(clone) links.setContainingType(c, containingType) links.mapper = singlePropMapper - links.setWriteType(c, c.getWriteTypeOfSymbol(singleProp)) + links.setNewWriteType(c, c.getWriteTypeOfSymbol(singleProp)) return clone } if propSet.Size() == 0 { @@ -22652,15 +24184,15 @@ func (c *Checker) createUnionOrIntersectionProperty(containingType *Type, name s return result } if isUnion { - links.resolvedType = c.getUnionType(propTypes) + links.resolvedType.setIdentity(c, c.getUnionType(propTypes)) } else { - links.resolvedType = c.getIntersectionType(propTypes) + links.resolvedType.setIdentity(c, c.getIntersectionType(propTypes)) } if writeTypes != nil { if isUnion { - links.setWriteType(c, c.getUnionType(writeTypes)) + links.setNewWriteType(c, c.getUnionType(writeTypes)) } else { - links.setWriteType(c, c.getIntersectionType(writeTypes)) + links.setNewWriteType(c, c.getIntersectionType(writeTypes)) } } return result @@ -22717,11 +24249,11 @@ type symbolWithTypeKey struct { // returning the same copy for the same property and type. func (c *Checker) getSymbolWithType(source *ast.Symbol, t *Type) *ast.Symbol { key := symbolWithTypeKey{source: source, t: t} - if existing := c.symbolsWithType[key]; existing != nil { + if existing, _ := c.symbolsWithType.get(key); existing != nil { return existing } result := c.createSymbolWithType(source, t) - c.symbolsWithType[key] = result + c.symbolsWithType.intern(key, result) return result } @@ -22729,11 +24261,11 @@ func (c *Checker) getSymbolWithType(source *ast.Symbol, t *Type) *ast.Symbol { // creates one with the given creation. func (c *Checker) internObjectType(symbol *ast.Symbol, flags ObjectFlags, members ast.SymbolTable, indexInfos []*IndexInfo, create func() *Type) *Type { key := c.getObjectTypeContentKey(symbol, flags&^(ObjectFlagsCouldContainTypeVariablesComputed|ObjectFlagsCouldContainTypeVariables|ObjectFlagsMembersResolved), members, indexInfos) - if existing := c.objectContentTypes[key]; existing != nil { + if existing, _ := c.objectContentTypes.get(key); existing != nil { return existing } result := create() - c.objectContentTypes[key] = result + c.objectContentTypes.intern(key, result) return result } @@ -22743,7 +24275,7 @@ func (c *Checker) createSymbolWithType(source *ast.Symbol, t *Type) *ast.Symbol symbol.SetParent(source.Parent()) symbol.SetValueDeclaration(source.ValueDeclaration()) links := c.valueSymbolLinks.Get(symbol) - links.resolvedType = t + links.resolvedType.setIdentity(c, t) links.target = source links.setNameType(c, c.valueSymbolLinks.Get(source).getNameType()) return symbol @@ -22800,10 +24332,7 @@ func (c *Checker) getApparentType(t *Type) *Type { func (c *Checker) getApparentTypeOfMappedType(t *Type) *Type { m := t.AsMappedType() - if m.resolvedApparentType == nil { - m.resolvedApparentType = c.getResolvedApparentTypeOfMappedType(t) - } - return m.resolvedApparentType + return c.memoCompute(&m.resolvedApparentType, func() *Type { return c.getResolvedApparentTypeOfMappedType(t) }) } func (c *Checker) getResolvedApparentTypeOfMappedType(t *Type) *Type { @@ -22833,16 +24362,17 @@ func (c *Checker) getResolvedApparentTypeOfMappedType(t *Type) *Type { func (c *Checker) getApparentTypeOfIntersectionType(t *Type, thisArgument *Type) *Type { if t == thisArgument { d := t.AsIntersectionType() - if d.resolvedApparentType == nil { - d.resolvedApparentType = c.getTypeWithThisArgument(t, thisArgument, true /*needApparentType*/) - } - return d.resolvedApparentType + return c.memoCompute(&d.resolvedApparentType, func() *Type { + return c.getTypeWithThisArgument(t, thisArgument, true /*needApparentType*/) + }) } key := CachedTypeKey{kind: CachedTypeKindApparentType, typeId: thisArgument.id} - result := c.cachedTypes[key] + result, _ := c.cachedTypes.get(c, key) if result == nil { + c.cachedTypes.begin(c, key) result = c.getTypeWithThisArgument(t, thisArgument, true /*needApparentType*/) - c.cachedTypes[key] = result + c.cachedTypes.set(c, key, result) + c.endMemo() } return result } @@ -22857,27 +24387,29 @@ func (c *Checker) getReducedType(t *Type) *Type { switch { case t.flags&TypeFlagsUnion != 0: if t.objectFlags&ObjectFlagsContainsIntersections != 0 { - if reducedType := t.AsUnionType().resolvedReducedType; reducedType != nil { - return reducedType - } - reducedType := c.getReducedUnionType(t) - t.AsUnionType().resolvedReducedType = reducedType - return reducedType + return c.memoCompute(&t.AsUnionType().resolvedReducedType, func() *Type { return c.getReducedUnionType(t) }) } case t.flags&TypeFlagsIntersection != 0: - if t.objectFlags&ObjectFlagsIsNeverIntersectionComputed == 0 { - t.objectFlags |= ObjectFlagsIsNeverIntersectionComputed - if !c.isMappingOfSameObjectType(t.Types()) && c.somePropertyReducesToNever(t) { - t.objectFlags |= ObjectFlagsIsNeverIntersection - } - } - if t.objectFlags&ObjectFlagsIsNeverIntersection != 0 { + if c.isNeverIntersection(t) { return c.neverType } } return t } +// isNeverIntersection reports whether the intersection t reduces to never. +func (c *Checker) isNeverIntersection(t *Type) bool { + if c.objectFlagsKnown(t, ObjectFlagsIsNeverIntersectionComputed) { + return t.objectFlags&ObjectFlagsIsNeverIntersection != 0 + } + return c.objectFlagsCell(t, memoNeverIntersection, ObjectFlagsIsNeverIntersectionComputed, ObjectFlagsIsNeverIntersection, true /*inProgress*/, func() ObjectFlags { + if !c.isMappingOfSameObjectType(t.Types()) && c.somePropertyReducesToNever(t) { + return ObjectFlagsIsNeverIntersection + } + return 0 + }) != 0 +} + func (c *Checker) isMappingOfSameObjectType(types []*Type) bool { if len(types) != 0 && types[0].objectFlags&ObjectFlagsMapped != 0 { if firstType := c.getModifiersTypeFromMappedType(types[0]); firstType.flags&TypeFlagsObject != 0 { @@ -22926,7 +24458,7 @@ func (c *Checker) getReducedUnionType(unionType *Type) *Type { } reduced := c.getUnionType(reducedTypes) if reduced.flags&TypeFlagsUnion != 0 { - reduced.AsUnionType().resolvedReducedType = reduced + reduced.AsUnionType().resolvedReducedType.set(c, reduced) } return reduced } @@ -22944,7 +24476,7 @@ func (c *Checker) getReducedApparentType(t *Type) *Type { } func (c *Checker) elaborateNeverIntersection(chain *ast.Diagnostic, node *ast.Node, t *Type) *ast.Diagnostic { - if t.flags&TypeFlagsIntersection != 0 && t.objectFlags&ObjectFlagsIsNeverIntersection != 0 { + if t.flags&TypeFlagsIntersection != 0 && c.isNeverIntersection(t) { neverProp := core.Find(c.getPropertiesOfUnionOrIntersectionType(t), c.isDiscriminantWithNeverType) if neverProp != nil { return NewDiagnosticChainForNode(chain, node, diagnostics.The_intersection_0_was_reduced_to_never_because_property_1_has_conflicting_types_in_some_constituents, c.typeToStringEx(t, nil, TypeFormatFlagsNoTypeReduction, nil), c.symbolToString(neverProp)) @@ -22970,42 +24502,56 @@ func isConflictingPrivateProperty(prop *ast.Symbol) bool { func (c *Checker) getTypeArguments(t *Type) []*Type { d := t.AsTypeReference() - if d.resolvedTypeArguments == nil { - n := d.target.AsInterfaceType() - if !c.pushTypeResolution(t, TypeSystemPropertyNameResolvedTypeArguments) { - return slices.Repeat([]*Type{c.errorType}, len(n.TypeParameters())) + if typeArguments := d.resolvedTypeArguments.get(c); typeArguments != nil { + return typeArguments + } + d.resolvedTypeArguments.begin(c) + result := c.getTypeArgumentsUncached(t) + c.endMemo() + return result +} + +func (c *Checker) getTypeArgumentsUncached(t *Type) []*Type { + d := t.AsTypeReference() + n := d.target.AsInterfaceType() + if !c.pushTypeResolution(t, TypeSystemPropertyNameResolvedTypeArguments) { + return slices.Repeat([]*Type{c.errorType}, len(n.TypeParameters())) + } + var typeArguments []*Type + node := t.AsTypeReference().node + if node != nil { + switch node.Kind { + case ast.KindTypeReference: + typeArguments = append(n.OuterTypeParameters(), c.getEffectiveTypeArguments(node, n.LocalTypeParameters())...) + case ast.KindArrayType: + typeArguments = []*Type{c.getTypeFromTypeNode(node.AsArrayTypeNode().ElementType)} + case ast.KindTupleType: + typeArguments = core.Map(node.Elements(), c.getTypeFromTypeNode) + default: + panic("Unhandled case in getTypeArguments") } - var typeArguments []*Type - node := t.AsTypeReference().node - if node != nil { - switch node.Kind { - case ast.KindTypeReference: - typeArguments = append(n.OuterTypeParameters(), c.getEffectiveTypeArguments(node, n.LocalTypeParameters())...) - case ast.KindArrayType: - typeArguments = []*Type{c.getTypeFromTypeNode(node.AsArrayTypeNode().ElementType)} - case ast.KindTupleType: - typeArguments = core.Map(node.Elements(), c.getTypeFromTypeNode) - default: - panic("Unhandled case in getTypeArguments") - } + } + if c.popTypeResolution() { + if d.resolvedTypeArguments.peek() == nil { + d.resolvedTypeArguments.set(c, c.instantiateTypes(typeArguments, d.mapper)) } - if c.popTypeResolution() { - if d.resolvedTypeArguments == nil { - d.resolvedTypeArguments = c.instantiateTypes(typeArguments, d.mapper) - } + } else { + if d.resolvedTypeArguments.peek() == nil { + d.resolvedTypeArguments.set(c, slices.Repeat([]*Type{c.errorType}, len(n.TypeParameters()))) + } + message := diagnostics.Tuple_type_arguments_circularly_reference_themselves + var args []any + if d.target.symbol != nil { + message = diagnostics.Type_arguments_for_0_circularly_reference_themselves + args = []any{c.symbolToString(d.target.symbol)} + } + if node != nil { + c.error(node, message, args...) } else { - if d.resolvedTypeArguments == nil { - d.resolvedTypeArguments = slices.Repeat([]*Type{c.errorType}, len(n.TypeParameters())) - } - errorNode := core.IfElse(node != nil, node, c.currentNode) - if d.target.symbol != nil { - c.error(errorNode, diagnostics.Type_arguments_for_0_circularly_reference_themselves, c.symbolToString(d.target.symbol)) - } else { - c.error(errorNode, diagnostics.Tuple_type_arguments_circularly_reference_themselves) - } + c.errorAtCurrentNode(message, args...) } } - return d.resolvedTypeArguments + return d.resolvedTypeArguments.peek() } func (c *Checker) getEffectiveTypeArguments(node *ast.Node, typeParameters []*Type) []*Type { @@ -23072,6 +24618,17 @@ func (c *Checker) getDefaultTypeArgumentType(isInJavaScriptFile bool) *Type { // this gets the instantiated default type of its target. If the type parameter has no default type or // the default is circular, `undefined` is returned. func (c *Checker) getDefaultFromTypeParameter(t *Type) *Type { + key := memoKeyOf(t, memoTypeParameterDefault) + if t.flags&TypeFlagsTypeParameter != 0 && t.AsTypeParameter().resolvedDefaultType.peek() != nil && c.readsWithoutMemo(key) { + return c.getDefaultFromTypeParameterUncached(t) + } + c.beginMemo(key) + result := c.getDefaultFromTypeParameterUncached(t) + c.endMemo() + return result +} + +func (c *Checker) getDefaultFromTypeParameterUncached(t *Type) *Type { if t.flags&TypeFlagsTypeParameter == 0 { return nil } @@ -23084,39 +24641,78 @@ func (c *Checker) getDefaultFromTypeParameter(t *Type) *Type { func (c *Checker) getResolvedTypeParameterDefault(t *Type) *Type { d := t.AsTypeParameter() - if d.resolvedDefaultType == nil { + if d.resolvedDefaultType.get(c) == nil { if d.target != nil { targetDefault := c.getResolvedTypeParameterDefault(d.target) if targetDefault != nil { - d.resolvedDefaultType = c.instantiateType(targetDefault, d.mapper) + d.resolvedDefaultType.set(c, c.instantiateType(targetDefault, d.mapper)) } else { - d.resolvedDefaultType = c.noConstraintType + d.resolvedDefaultType.set(c, c.noConstraintType) } } else { - // To block recursion, set the initial value to the resolvingDefaultType. - d.resolvedDefaultType = c.resolvingDefaultType - defaultType := c.noConstraintType - if t.symbol != nil { - defaultDeclaration := core.FirstNonNil(t.symbol.Declarations(), func(decl *ast.Node) *ast.Node { - if ast.IsTypeParameterDeclaration(decl) { - return decl.AsTypeParameterDeclaration().DefaultType - } - return nil - }) - if defaultDeclaration != nil { - defaultType = c.getTypeFromTypeNode(defaultDeclaration) + c.resolveTypeParameterDefault(t) + } + } else if d.resolvedDefaultType.get(c) == c.resolvingDefaultType { + // If we are called recursively for this type parameter, mark the default as circular. + c.observeCircularHead(t, TypeSystemPropertyNameDefaultType, c.compareTypeParameterDeclarations) + d.resolvedDefaultType.swap(c.circularConstraintType) + return c.circularConstraintType + } + return d.resolvedDefaultType.get(c) +} + +// resolveTypeParameterDefault resolves the default of a declared type +// parameter. When defaults form a cycle, the first default resolved is the +// one found to be circular, so the default first in program order is always +// resolved first, discarding whatever was computed starting from another. +func (c *Checker) resolveTypeParameterDefault(t *Type) { + d := t.AsTypeParameter() + if c.checking { + // Results computed starting from another default are discarded + // along with their effects, so resolve while recording effects. + c.withoutChecking(func() { + c.resolveTypeParameterDefault(t) + }) + return + } + d.resolvedDefaultType.begin(c) + mark := c.markDiscard() + for { + // To block recursion, set the initial value to the resolvingDefaultType. + // Markers of a resolution in progress are never observed once it + // finishes, so they are not results to discard. + d.resolvedDefaultType.swap(c.resolvingDefaultType) + c.pushTypeResolution(t, TypeSystemPropertyNameDefaultType) + defaultType := c.noConstraintType + if t.symbol != nil { + defaultDeclaration := core.FirstNonNil(t.symbol.Declarations(), func(decl *ast.Node) *ast.Node { + if ast.IsTypeParameterDeclaration(decl) { + return decl.AsTypeParameterDeclaration().DefaultType } - } - if d.resolvedDefaultType == c.resolvingDefaultType { - // If we have not been called recursively, set the correct default type. - d.resolvedDefaultType = defaultType + return nil + }) + if defaultDeclaration != nil { + defaultType = c.getTypeFromTypeNode(defaultDeclaration) } } - } else if d.resolvedDefaultType == c.resolvingDefaultType { - // If we are called recursively for this type parameter, mark the default as circular. - d.resolvedDefaultType = c.circularConstraintType + if d.resolvedDefaultType.swap(nil) != c.resolvingDefaultType { + // We were called recursively, so the default is circular. + defaultType = c.circularConstraintType + } + canonical := c.popCircularHead() + if canonical == nil { + d.resolvedDefaultType.set(c, defaultType) + break + } + c.discardSince(mark) + c.getResolvedTypeParameterDefault(canonical.(*Type)) } - return d.resolvedDefaultType + c.releaseDiscardMark(mark) + c.endMemo() +} + +func (c *Checker) compareTypeParameterDeclarations(a, b TypeSystemEntity) int { + return c.compareSymbols(a.(*Type).symbol, b.(*Type).symbol) } func (c *Checker) getDefaultOrUnknownFromTypeParameter(t *Type) *Type { @@ -23162,21 +24758,33 @@ func (c *Checker) instantiateTypeWithAlias(t *Type, m *TypeMapper, alias *TypeAl if t == nil || m == nil || !(c.couldContainTypeVariables(t) || (t.alias != nil && len(t.alias.typeArguments) > 0 && core.Some(t.alias.typeArguments, c.couldContainTypeVariables))) { return t } - if len(c.instantiationStack) == 100 || c.instantiationCount >= 5_000_000 { - // We have reached 100 recursive type instantiations, or 5M type instantiations caused by the same statement - // or expression. There is a very high likelihood we're dealing with a combination of infinite generic types + // See "Instantiation limits" in memo.go. + depthLimitReached := c.instantiationDepthLimitReached() + if depthLimitReached || c.instantiationCount >= instantiationCountLimit { + // We have reached 100 recursive type instantiations in one computation, or 5M type instantiations caused by + // the same statement or expression. There is a very high likelihood we're dealing with a combination of infinite generic types // that perpetually generate new type identities, so we stop the recursion here by yielding the error type. if tr := c.tracer; tr != nil { tr.Instant(tracing.PhaseCheckTypes, "instantiateType_DepthLimit", map[string]any{"typeId": t.id, "instantiationDepth": len(c.instantiationStack), "instantiationCount": c.instantiationCount}) } circularTypeNames := c.getCircularTypeNames() + var message *diagnostics.Message + var args []any switch { case len(circularTypeNames) == 1: - c.error(c.currentNode, diagnostics.Instantiations_of_type_0_appear_infinitely_circular, circularTypeNames[0]) + message, args = diagnostics.Instantiations_of_type_0_appear_infinitely_circular, []any{circularTypeNames[0]} case len(circularTypeNames) > 1: - c.error(c.currentNode, diagnostics.Instantiations_of_the_following_types_appear_infinitely_circular_Colon_0, quotedAndCommaSeparated(circularTypeNames)) + message, args = diagnostics.Instantiations_of_the_following_types_appear_infinitely_circular_Colon_0, []any{quotedAndCommaSeparated(circularTypeNames)} default: - c.error(c.currentNode, diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite) + message = diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite + } + report := func(c *Checker, node *ast.Node) { c.error(node, message, args...) } + if depthLimitReached { + c.reportAtCurrentNode(report) + } else { + // The count depends on how much was already cached, so computing + // the result again elsewhere would not reliably reach it either. + c.reportAtCurrentNodeOnce(report) } return c.errorType } @@ -23187,8 +24795,11 @@ func (c *Checker) instantiateTypeWithAlias(t *Type, m *TypeMapper, alias *TypeAl c.pushActiveMapper(m) } else { key = getActiveMapperCacheKey(t, alias) - if cachedType, ok := c.activeTypeMappersCaches[index].get(key); ok { - return cachedType + // An instantiation cached in another resolution was computed against that + // resolution's depth limit, so it is only reused within the same + // resolution. + if cached, ok := c.activeTypeMappersCaches[index].get(key); ok && cached.serial == c.currentResolution().serial { + return cached.result } } c.TotalInstantiationCount++ @@ -23198,13 +24809,18 @@ func (c *Checker) instantiateTypeWithAlias(t *Type, m *TypeMapper, alias *TypeAl if index == -1 { c.popActiveMapper() } else { - c.activeTypeMappersCaches[index].set(key, result) + c.activeTypeMappersCaches[index].set(key, activeMapperCacheEntry{result, c.currentResolution().serial}) } c.instantiationStack[len(c.instantiationStack)-1] = nil c.instantiationStack = c.instantiationStack[:len(c.instantiationStack)-1] return result } +type activeMapperCacheEntry struct { + result *Type + serial uint32 +} + // Without an alias the key is just the type ID, which avoids hashing. Hashed keys have // a nonzero high half with overwhelming probability, so the two kinds do not collide. func getActiveMapperCacheKey(t *Type, alias *TypeAlias) CacheHashKey { @@ -23220,7 +24836,14 @@ func getActiveMapperCacheKey(t *Type, alias *TypeAlias) CacheHashKey { func (c *Checker) getCircularTypeNames() []string { typeCounts := make(map[*Type]int) var circularTypeNames []string - for _, t := range c.instantiationStack { + // A computation reaching the depth limit names only its own + // instantiations, which do not depend on where it was first computed; + // see "Instantiation limits". + stack := c.instantiationStack + if len(stack) != instantiationTotalDepthLimit { + stack = stack[c.currentResolution().depthStart:] + } + for _, t := range stack { typeCounts[t] = typeCounts[t] + 1 if typeCounts[t] == 3 { symbol := t.symbol @@ -23245,7 +24868,7 @@ func (c *Checker) pushActiveMapper(mapper *TypeMapper) { // The cap may contain an empty cache from popActiveMapper; reuse it. c.activeTypeMappersCaches = c.activeTypeMappersCaches[:lastIndex+1] } else { - c.activeTypeMappersCaches = append(c.activeTypeMappersCaches, clearableMap[*Type]{}) + c.activeTypeMappersCaches = append(c.activeTypeMappersCaches, clearableMap[activeMapperCacheEntry]{}) } } @@ -23276,16 +24899,21 @@ func (c *Checker) couldContainTypeVariablesWorker(t *Type) bool { if t.flags&TypeFlagsStructuredOrInstantiable == 0 { return false } - objectFlags := t.objectFlags - if objectFlags&ObjectFlagsCouldContainTypeVariablesComputed != 0 { - return objectFlags&ObjectFlagsCouldContainTypeVariables != 0 + if c.objectFlagsKnown(t, ObjectFlagsCouldContainTypeVariablesComputed) { + return t.objectFlags&ObjectFlagsCouldContainTypeVariables != 0 } + return c.objectFlagsCell(t, memoCouldContainTypeVariables, ObjectFlagsCouldContainTypeVariablesComputed, ObjectFlagsCouldContainTypeVariables, false /*inProgress*/, func() ObjectFlags { + return core.IfElse(c.computeCouldContainTypeVariables(t), ObjectFlagsCouldContainTypeVariables, 0) + }) != 0 +} + +func (c *Checker) computeCouldContainTypeVariables(t *Type) bool { + objectFlags := t.objectFlags result := t.flags&TypeFlagsInstantiable != 0 || t.flags&TypeFlagsObject != 0 && !c.isNonGenericTopLevelType(t) && (objectFlags&ObjectFlagsReference != 0 && (t.AsTypeReference().node != nil || core.Some(c.getTypeArguments(t), c.couldContainTypeVariables)) || objectFlags&ObjectFlagsAnonymous != 0 && t.symbol != nil && t.symbol.Flags()&(ast.SymbolFlagsFunction|ast.SymbolFlagsMethod|ast.SymbolFlagsClass|ast.SymbolFlagsTypeLiteral|ast.SymbolFlagsObjectLiteral) != 0 && t.symbol.Declarations() != nil || objectFlags&(ObjectFlagsMapped|ObjectFlagsReverseMapped|ObjectFlagsObjectRestType|ObjectFlagsInstantiationExpressionType) != 0) || t.flags&TypeFlagsUnionOrIntersection != 0 && t.flags&TypeFlagsEnumLiteral == 0 && !c.isNonGenericTopLevelType(t) && core.Some(t.Types(), c.couldContainTypeVariables) - t.objectFlags |= ObjectFlagsCouldContainTypeVariablesComputed | core.IfElse(result, ObjectFlagsCouldContainTypeVariables, 0) return result } @@ -23317,7 +24945,7 @@ func (c *Checker) instantiateTypeWorker(t *Type, m *TypeMapper, alias *TypeAlias objectFlags := t.objectFlags if objectFlags&(ObjectFlagsReference|ObjectFlagsAnonymous|ObjectFlagsMapped) != 0 { if objectFlags&ObjectFlagsReference != 0 && t.AsTypeReference().node == nil { - resolvedTypeArguments := t.AsTypeReference().resolvedTypeArguments + resolvedTypeArguments := t.AsTypeReference().resolvedTypeArguments.peek() newTypeArguments := c.instantiateTypes(resolvedTypeArguments, m) if core.Same(newTypeArguments, resolvedTypeArguments) { return t @@ -23407,14 +25035,15 @@ func (c *Checker) getObjectTypeInstantiation(t *Type, m *TypeMapper, alias *Type links := c.typeNodeLinks.Get(declaration) switch { case t.objectFlags&ObjectFlagsReference != 0: // Deferred type reference - target = links.resolvedType + target = links.resolvedType.get(c) case t.objectFlags&ObjectFlagsInstantiated != 0: target = t.Target() default: target = t } - typeParameters = links.getOuterTypeParameters() + typeParameters = links.getOuterTypeParameters(c) if typeParameters == nil { + links.outerTypeParameters.begin(c) // The first time an anonymous type is instantiated we compute and store a list of the type // parameters that are in scope (and therefore potentially referenced). For type literals that // aren't the right hand side of a generic type alias declaration we optimize by reducing the @@ -23437,6 +25066,7 @@ func (c *Checker) getObjectTypeInstantiation(t *Type, m *TypeMapper, alias *Type typeParameters = []*Type{} } links.setOuterTypeParameters(c, typeParameters) + c.endMemo() } if len(typeParameters) == 0 { return t @@ -23470,25 +25100,29 @@ func (c *Checker) getObjectTypeInstantiation(t *Type, m *TypeMapper, alias *Type } data := target.AsObjectType() key := getTypeInstantiationKey(typeArguments, newAlias, t.objectFlags&ObjectFlagsSingleSignatureType != 0) - if data.instantiations == nil { - data.instantiations = make(map[CacheHashKey]*Type) - data.instantiations[getTypeInstantiationKey(typeParameters, target.alias, false)] = target + if data.instantiations.isEmpty() { + data.instantiations.intern(getTypeInstantiationKey(typeParameters, target.alias, false), target) } - result := data.instantiations[key] + result, _ := data.instantiations.get(c, key) if result == nil { + reports := c.startCurrentNodeErrors() + data.instantiations.begin(c, key) result = instantiate(newTypeMapper(typeParameters, typeArguments)) - data.instantiations[key] = result + data.instantiations.setReported(c, key, result, c.endCurrentNodeErrors(reports)) + c.endMemo() if result.flags&TypeFlagsObjectFlagsType != 0 && result.objectFlags&ObjectFlagsCouldContainTypeVariablesComputed == 0 { // if `result` is one of the object types we tried to make (it may not be, due to how `instantiateMappedType` works), we can carry forward the type variable containment check from the input type arguments resultCouldContainObjectFlags := core.Some(typeArguments, c.couldContainTypeVariables) if result.objectFlags&ObjectFlagsCouldContainTypeVariablesComputed == 0 { + key := memoKeyOf(result, memoCouldContainTypeVariables) + mask := ObjectFlagsCouldContainTypeVariablesComputed | ObjectFlagsCouldContainTypeVariables if result.objectFlags&(ObjectFlagsMapped|ObjectFlagsAnonymous|ObjectFlagsReference) != 0 { - result.objectFlags |= ObjectFlagsCouldContainTypeVariablesComputed | core.IfElse(resultCouldContainObjectFlags, ObjectFlagsCouldContainTypeVariables, 0) - } else { + setFlagMemoBits(c, key, &result.objectFlags, mask, ObjectFlagsCouldContainTypeVariablesComputed|core.IfElse(resultCouldContainObjectFlags, ObjectFlagsCouldContainTypeVariables, 0)) + } else if !resultCouldContainObjectFlags { // If none of the type arguments for the outer type parameters contain type variables, it follows // that the instantiated type doesn't reference type variables. // Intrinsics have `CouldContainTypeVariablesComputed` pre-set, so this should only cover unions and intersections resulting from `instantiateMappedType` - result.objectFlags |= core.IfElse(!resultCouldContainObjectFlags, ObjectFlagsCouldContainTypeVariablesComputed, 0) + setFlagMemoBits(c, key, &result.objectFlags, mask, ObjectFlagsCouldContainTypeVariablesComputed) } } } @@ -23559,7 +25193,7 @@ func (c *Checker) instantiateAnonymousType(t *Type, m *TypeMapper, alias *TypeAl // C.f. instantiateSignature origTypeParameter := c.getTypeParameterFromMappedType(t) freshTypeParameter := c.cloneTypeParameter(origTypeParameter) - result.AsMappedType().typeParameter = freshTypeParameter + result.AsMappedType().typeParameter.intern(freshTypeParameter) m = c.combineTypeMappers(newSimpleTypeMapper(origTypeParameter, freshTypeParameter), m) freshTypeParameter.AsTypeParameter().mapper = m case t.objectFlags&ObjectFlagsInstantiationExpressionType != 0: @@ -23586,8 +25220,10 @@ func (c *Checker) getConditionalTypeInstantiation(t *Type, mapper *TypeMapper, f // instantiation cache key from the type IDs of the type arguments. typeArguments := core.Map(root.outerTypeParameters, mapper.Map) key := getConditionalTypeKey(typeArguments, alias, forConstraint) - result := root.instantiations[key] + result, _ := root.instantiations.get(c, key) if result == nil { + reports := c.startCurrentNodeErrors() + root.instantiations.begin(c, key) newMapper := newTypeMapper(root.outerTypeParameters, typeArguments) checkType := root.checkType var distributionType *Type @@ -23604,7 +25240,8 @@ func (c *Checker) getConditionalTypeInstantiation(t *Type, mapper *TypeMapper, f } else { result = c.getConditionalType(root, newMapper, forConstraint, alias) } - root.instantiations[key] = result + root.instantiations.setReported(c, key, result, c.endCurrentNodeErrors(reports)) + c.endMemo() } return result } @@ -23648,7 +25285,7 @@ func (c *Checker) instantiateMappedType(t *Type, m *TypeMapper, alias *TypeAlias return s } if d.declaration.NameType == nil { - if c.isArrayType(s) || s.flags&TypeFlagsAny != 0 && c.findResolutionCycleStartIndex(typeVariable, TypeSystemPropertyNameResolvedBaseConstraint) < 0 && c.hasArrayOrTypeTypeConstraint(typeVariable) { + if c.isArrayType(s) || s.flags&TypeFlagsAny != 0 && !c.isResolutionInProgress(typeVariable, TypeSystemPropertyNameResolvedBaseConstraint) && c.hasArrayOrTypeTypeConstraint(typeVariable) { return c.instantiateMappedArrayType(s, t, prependTypeMapping(typeVariable, s, m)) } if isTupleType(s) { @@ -23765,18 +25402,16 @@ func getModifiedReadonlyState(state bool, modifiers MappedTypeModifiers) bool { func (c *Checker) getTypeParameterFromMappedType(t *Type) *Type { m := t.AsMappedType() - if m.typeParameter == nil { - m.typeParameter = c.getDeclaredTypeOfTypeParameter(c.getSymbolOfDeclaration(m.declaration.TypeParameter)) - } - return m.typeParameter + return c.memoCompute(&m.typeParameter, func() *Type { + return c.getDeclaredTypeOfTypeParameter(c.getSymbolOfDeclaration(m.declaration.TypeParameter)) + }) } func (c *Checker) getConstraintTypeFromMappedType(t *Type) *Type { m := t.AsMappedType() - if m.constraintType == nil { - m.constraintType = core.OrElse(c.getConstraintOfTypeParameter(c.getTypeParameterFromMappedType(t)), c.errorType) - } - return m.constraintType + return c.memoCompute(&m.constraintType, func() *Type { + return core.OrElse(c.getConstraintOfTypeParameter(c.getTypeParameterFromMappedType(t)), c.errorType) + }) } func (c *Checker) getNameTypeFromMappedType(t *Type) *Type { @@ -23784,22 +25419,19 @@ func (c *Checker) getNameTypeFromMappedType(t *Type) *Type { if m.declaration.NameType == nil { return nil } - if m.nameType == nil { - m.nameType = c.instantiateType(c.getTypeFromTypeNode(m.declaration.NameType), m.mapper) - } - return m.nameType + return c.memoCompute(&m.nameType, func() *Type { + return c.instantiateType(c.getTypeFromTypeNode(m.declaration.NameType), m.mapper) + }) } func (c *Checker) getTemplateTypeFromMappedType(t *Type) *Type { m := t.AsMappedType() - if m.templateType == nil { + return c.memoCompute(&m.templateType, func() *Type { if m.declaration.Type != nil { - m.templateType = c.instantiateType(c.addOptionalityEx(c.getTypeFromTypeNode(m.declaration.Type) /*isProperty*/, true, getMappedTypeModifiers(t)&MappedTypeModifiersIncludeOptional != 0), m.mapper) - } else { - m.templateType = c.errorType + return c.instantiateType(c.addOptionalityEx(c.getTypeFromTypeNode(m.declaration.Type) /*isProperty*/, true, getMappedTypeModifiers(t)&MappedTypeModifiersIncludeOptional != 0), m.mapper) } - } - return m.templateType + return c.errorType + }) } func (c *Checker) isMappedTypeWithKeyofConstraintDeclaration(t *Type) bool { @@ -23901,26 +25533,70 @@ func (c *Checker) tryGetTypeFromTypeNode(node *ast.Node) *Type { } func (c *Checker) getTypeFromTypeNode(node *ast.Node) *Type { - return c.getConditionalFlowTypeOfType(c.getTypeFromTypeNodeWorker(node), node) + if t := c.getKeywordTypeOfTypeNode(node); t != nil && !isConditionalFlowTypeLocation(node) { + // A keyword type is the same everywhere getConditionalFlowTypeOfType + // leaves it unchanged, and computing it has no effects. + return t + } + key := memoKeyOf(node, memoTypeNodeType) + if t := c.peekTypeFromTypeNode(node); t != nil && c.readsWithoutMemo(key) { + return t + } + c.beginMemo(key) + result := c.getTypeFromTypeNodeUncached(node) + c.endMemo() + return result } -func (c *Checker) getTypeFromTypeNodeWorker(node *ast.Node) *Type { +// memoTypeNode calls a getTypeFromXXXNode worker that caches its result in the +// links of the type node. +func memoTypeNode(c *Checker, node *ast.Node, worker func(*Checker, *ast.Node) *Type) *Type { + if c.memoActive() { + c.typeNodeLinks.Get(node).resolvedType.begin(c) + result := worker(c, node) + c.endMemo() + return result + } + return worker(c, node) +} + +// peekTypeFromTypeNode returns the type of node if reading it only reads a +// cached result, or nil: for a kind whose type is cached in the node's links, +// at a location where getConditionalFlowTypeOfType leaves it unchanged. +func (c *Checker) peekTypeFromTypeNode(node *ast.Node) *Type { + switch node.Kind { + case ast.KindTypeReference, ast.KindExpressionWithTypeArguments, ast.KindUnionType, ast.KindIntersectionType, + ast.KindConditionalType, ast.KindIndexedAccessType, ast.KindArrayType, ast.KindTupleType, ast.KindThisType, + ast.KindTypeOperator, ast.KindLiteralType: + links := c.typeNodeLinks.TryGet(node) + if links == nil || links.resolvedType.peek() == nil || isConditionalFlowTypeLocation(node) { + return nil + } + return links.resolvedType.peek() + } + return nil +} + +// isConditionalFlowTypeLocation reports whether getConditionalFlowTypeOfType +// may change the type of node: whether it is within the true branch of a +// conditional type, or the template of a mapped type without an as clause. +func isConditionalFlowTypeLocation(node *ast.Node) bool { + for n := node; n != nil && !ast.IsStatement(n) && n.Kind != ast.KindJSDoc; n = n.Parent { + parent := n.Parent + if ast.IsConditionalTypeNode(parent) && n == parent.AsConditionalTypeNode().TrueType || + ast.IsMappedTypeNode(parent) && parent.AsMappedTypeNode().NameType == nil && n == parent.Type() { + return true + } + } + return false +} + +// getKeywordTypeOfTypeNode returns the type of a keyword type node, or nil if +// node is not one. +func (c *Checker) getKeywordTypeOfTypeNode(node *ast.Node) *Type { switch node.Kind { case ast.KindAnyKeyword, ast.KindJSDocAllType: return c.anyType - case ast.KindJSDocNonNullableType: - return c.getTypeFromTypeNode(node.Type()) - case ast.KindJSDocNullableType: - t := c.getTypeFromTypeNode(node.Type()) - if c.strictNullChecks { - return c.getNullableType(t, TypeFlagsNull) - } else { - return t - } - case ast.KindJSDocVariadicType: - return c.createArrayType(c.getTypeFromTypeNode(node.AsJSDocVariadicType().Type)) - case ast.KindJSDocOptionalType: - return c.addOptionality(c.getTypeFromTypeNode(node.Type())) case ast.KindUnknownKeyword: return c.unknownType case ast.KindStringKeyword: @@ -23945,6 +25621,32 @@ func (c *Checker) getTypeFromTypeNodeWorker(node *ast.Node) *Type { return c.nonPrimitiveType case ast.KindIntrinsicKeyword: return c.intrinsicMarkerType + } + return nil +} + +func (c *Checker) getTypeFromTypeNodeUncached(node *ast.Node) *Type { + return c.getConditionalFlowTypeOfType(c.getTypeFromTypeNodeWorker(node), node) +} + +func (c *Checker) getTypeFromTypeNodeWorker(node *ast.Node) *Type { + if t := c.getKeywordTypeOfTypeNode(node); t != nil { + return t + } + switch node.Kind { + case ast.KindJSDocNonNullableType: + return c.getTypeFromTypeNode(node.Type()) + case ast.KindJSDocNullableType: + t := c.getTypeFromTypeNode(node.Type()) + if c.strictNullChecks { + return c.getNullableType(t, TypeFlagsNull) + } else { + return t + } + case ast.KindJSDocVariadicType: + return c.createArrayType(c.getTypeFromTypeNode(node.AsJSDocVariadicType().Type)) + case ast.KindJSDocOptionalType: + return c.addOptionality(c.getTypeFromTypeNode(node.Type())) case ast.KindThisType, ast.KindThisKeyword: return c.getTypeFromThisTypeNode(node) case ast.KindLiteralType: @@ -23995,10 +25697,10 @@ func (c *Checker) getTypeFromTypeNodeWorker(node *ast.Node) *Type { func (c *Checker) getTypeFromThisTypeNode(node *ast.Node) *Type { links := c.typeNodeLinks.Get(node) - if links.resolvedType == nil { - links.resolvedType = c.getThisType(node) + if links.resolvedType.get(c) == nil { + links.resolvedType.set(c, c.getThisType(node)) } - return links.resolvedType + return links.resolvedType.get(c) } func (c *Checker) getThisType(node *ast.Node) *Type { @@ -24020,66 +25722,66 @@ func (c *Checker) getTypeFromLiteralTypeNode(node *ast.Node) *Type { return c.nullType } links := c.typeNodeLinks.Get(node) - if links.resolvedType == nil { - links.resolvedType = c.getRegularTypeOfLiteralType(c.checkExpression(node.AsLiteralTypeNode().Literal)) + if links.resolvedType.get(c) == nil { + links.resolvedType.set(c, c.getRegularTypeOfLiteralType(c.checkExpression(node.AsLiteralTypeNode().Literal))) } - return links.resolvedType + return links.resolvedType.get(c) } func (c *Checker) getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node *ast.Node) *Type { links := c.typeNodeLinks.Get(node) - if links.resolvedType == nil { + if links.resolvedType.get(c) == nil { // Deferred resolution of members is handled by resolveObjectTypeMembers alias := c.getAliasForTypeNode(node) if sym := node.Symbol(); sym == nil || c.getMembersOfSymbol(sym).Len() == 0 && alias == nil { - links.resolvedType = c.emptyTypeLiteralType + links.resolvedType.set(c, c.emptyTypeLiteralType) } else { t := c.newObjectType(ObjectFlagsAnonymous, node.Symbol()) t.alias = alias - links.resolvedType = t + links.resolvedType.set(c, t) } } - return links.resolvedType + return links.resolvedType.get(c) } func (c *Checker) getTypeFromIndexedAccessTypeNode(node *ast.Node) *Type { links := c.typeNodeLinks.Get(node) - if links.resolvedType == nil { + if links.resolvedType.get(c) == nil { objectType := c.getTypeFromTypeNode(node.AsIndexedAccessTypeNode().ObjectType) indexType := c.getTypeFromTypeNode(node.AsIndexedAccessTypeNode().IndexType) potentialAlias := c.getAliasForTypeNode(node) - links.resolvedType = c.getIndexedAccessTypeEx(objectType, indexType, AccessFlagsNone, node, potentialAlias) + links.resolvedType.set(c, c.getIndexedAccessTypeEx(objectType, indexType, AccessFlagsNone, node, potentialAlias)) } - return links.resolvedType + return links.resolvedType.get(c) } func (c *Checker) getTypeFromTypeOperatorNode(node *ast.Node) *Type { links := c.typeNodeLinks.Get(node) - if links.resolvedType == nil { + if links.resolvedType.get(c) == nil { argType := node.Type() switch node.AsTypeOperatorNode().Operator { case ast.KindKeyOfKeyword: - links.resolvedType = c.getIndexType(c.getTypeFromTypeNode(argType)) + links.resolvedType.set(c, c.getIndexType(c.getTypeFromTypeNode(argType))) case ast.KindUniqueKeyword: if argType.Kind == ast.KindSymbolKeyword { - links.resolvedType = c.getESSymbolLikeTypeForNode(ast.WalkUpParenthesizedTypes(node.Parent)) + links.resolvedType.set(c, c.getESSymbolLikeTypeForNode(ast.WalkUpParenthesizedTypes(node.Parent))) } else { - links.resolvedType = c.errorType + links.resolvedType.set(c, c.errorType) } case ast.KindReadonlyKeyword: - links.resolvedType = c.getTypeFromTypeNode(argType) + links.resolvedType.set(c, c.getTypeFromTypeNode(argType)) default: panic("Unhandled case in getTypeFromTypeOperatorNode") } } - return links.resolvedType + return links.resolvedType.get(c) } func (c *Checker) getESSymbolLikeTypeForNode(node *ast.Node) *Type { if isValidESSymbolDeclaration(node) { symbol := c.getSymbolOfNode(node) if symbol != nil { - uniqueType := c.uniqueESSymbolTypes[symbol] + uniqueType, _ := c.uniqueESSymbolTypes.get(symbol) if uniqueType == nil { var b strings.Builder b.WriteString(ast.InternalSymbolNamePrefix) @@ -24088,7 +25790,7 @@ func (c *Checker) getESSymbolLikeTypeForNode(node *ast.Node) *Type { b.WriteByte('@') b.WriteString(strconv.FormatUint(uint64(ast.GetSymbolId(symbol)), 10)) uniqueType = c.newUniqueESSymbolType(symbol, b.String()) - c.uniqueESSymbolTypes[symbol] = uniqueType + c.uniqueESSymbolTypes.intern(symbol, uniqueType) } return uniqueType } @@ -24098,19 +25800,25 @@ func (c *Checker) getESSymbolLikeTypeForNode(node *ast.Node) *Type { func (c *Checker) getTypeFromTypeReference(node *ast.Node) *Type { links := c.typeNodeLinks.Get(node) - if links.resolvedType == nil { + if links.resolvedType.get(c) == nil { // Cache both the resolved symbol and the resolved type. The resolved symbol is needed when we check the // type reference in checkTypeReferenceNode. // handle LS queries on the `const` in `x as const` by resolving to the type of `x` if isConstTypeReference(node) && ast.IsAssertionExpression(node.Parent) { - links.resolvedType = c.checkExpressionCached(node.Parent.Expression()) + links.resolvedType.set(c, c.checkExpressionCached(node.Parent.Expression())) } else if t := c.getIntendedTypeFromJSDocTypeReference(node); t != nil { - links.resolvedType = t + links.resolvedType.set(c, t) } else { - links.resolvedType = c.getDistributedTypeParameter(node, c.getTypeReferenceType(node, c.getSymbolFromTypeReference(node))) + t := c.getDistributedTypeParameter(node, c.getTypeReferenceType(node, c.getSymbolFromTypeReference(node))) + if t.objectFlags&ObjectFlagsReference != 0 && t.AsTypeReference().node == node { + // A deferred reference is identified by the node, so it outlives a cycle. + links.resolvedType.setIdentity(c, t) + } else { + links.resolvedType.set(c, t) + } } } - return links.resolvedType + return links.resolvedType.get(c) } func (c *Checker) getDistributedTypeParameter(node *ast.Node, t *Type) *Type { @@ -24132,15 +25840,17 @@ func (c *Checker) getDistributedTypeFromTypeParameter(t *Type) *Type { tp := t.AsTypeParameter() if tp.distributedType == nil { tp.distributedType = c.newTypeParameter(t.symbol) - tp.distributedType.AsTypeParameter().isDistributed = true - tp.distributedType.AsTypeParameter().constraint = t + distributed := tp.distributedType.AsTypeParameter() + distributed.isDistributed = true + distributed.constraint.intern(t) + distributed.distributedType = t } return tp.distributedType } func getNonDistributedTypeParameter(t *Type) *Type { if t.flags&TypeFlagsTypeParameter != 0 && t.AsTypeParameter().isDistributed { - return t.AsTypeParameter().constraint + return t.AsTypeParameter().distributedType } return t } @@ -24207,13 +25917,23 @@ func (c *Checker) getIntendedTypeFromJSDocTypeReference(node *ast.Node) *Type { } func (c *Checker) getSymbolFromTypeReference(node *ast.Node) *ast.Symbol { + if c.memoActive() { + c.symbolNodeLinks.Get(node).resolvedSymbol.begin(c) + result := c.getSymbolFromTypeReferenceCached(node) + c.endMemo() + return result + } + return c.getSymbolFromTypeReferenceCached(node) +} + +func (c *Checker) getSymbolFromTypeReferenceCached(node *ast.Node) *ast.Symbol { links := c.symbolNodeLinks.Get(node) - if links.resolvedSymbol == nil { + if links.resolvedSymbol.get(c) == nil { // The `const` in a `const` assertion resolves to nothing; resolveName knows not to // report an error for it, so no special-casing is needed here. - links.resolvedSymbol = c.resolveTypeReferenceName(node, ast.SymbolFlagsType, false /*ignoreErrors*/) + links.resolvedSymbol.set(c, c.resolveTypeReferenceName(node, ast.SymbolFlagsType, false /*ignoreErrors*/)) } - return links.resolvedSymbol + return links.resolvedSymbol.get(c) } func (c *Checker) resolveTypeReferenceName(typeReference *ast.Node, meaning ast.SymbolFlags, ignoreErrors bool) *ast.Symbol { @@ -24256,12 +25976,12 @@ func (c *Checker) getUnresolvedSymbolForEntityName(name *ast.Node) *ast.Symbol { } else { path = text } - result := c.unresolvedSymbols[path] + result, _ := c.unresolvedSymbols.get(path) if result == nil { result = c.newSymbolEx(ast.SymbolFlagsTypeAlias, text, ast.CheckFlagsUnresolved) - c.unresolvedSymbols[path] = result + c.unresolvedSymbols.intern(path, result) result.SetParent(parentSymbol) - c.typeAliasLinks.Get(result).declaredType = c.unresolvedType + c.typeAliasLinks.Get(result).declaredType.intern(c.unresolvedType) } return result } @@ -24509,10 +26229,11 @@ func (n *TupleNormalizer) normalize(c *Checker, elementTypes []*Type, elementInf } else if isTupleType(t) { spreadTypes := c.getElementTypes(t) if len(spreadTypes)+len(n.types) >= 10_000 { - message := core.IfElse(c.currentNode != nil && ast.IsPartOfTypeNode(c.currentNode), - diagnostics.Type_produces_a_tuple_type_that_is_too_large_to_represent, - diagnostics.Expression_produces_a_tuple_type_that_is_too_large_to_represent) - c.error(c.currentNode, message) + c.reportAtCurrentNode(func(c *Checker, node *ast.Node) { + c.error(node, core.IfElse(node != nil && ast.IsPartOfTypeNode(node), + diagnostics.Type_produces_a_tuple_type_that_is_too_large_to_represent, + diagnostics.Expression_produces_a_tuple_type_that_is_too_large_to_represent)) + }) return false } // Spread variadic elements with tuple types into the resulting tuple. @@ -24714,16 +26435,16 @@ func (c *Checker) getTypeFromTypeAliasReference(node *ast.Node, symbol *ast.Symb if symbol.CheckFlags()&ast.CheckFlagsUnresolved != 0 { alias := &TypeAlias{symbol: symbol, typeArguments: core.Map(typeArguments, c.getTypeFromTypeNode)} key := getAliasKey(alias) - errorType := c.errorTypes[key] + errorType, _ := c.errorTypes.get(key) if errorType == nil { errorType = c.newIntrinsicType(TypeFlagsAny, "error") errorType.alias = alias - c.errorTypes[key] = errorType + c.errorTypes.intern(key, errorType) } return errorType } t := c.getDeclaredTypeOfSymbol(symbol) - typeParameters := c.typeAliasLinks.Get(symbol).typeParameters + typeParameters := c.getTypeParametersOfTypeAlias(symbol) if len(typeParameters) != 0 { numTypeArguments := len(typeArguments) minTypeArgumentCount := c.getMinTypeArgumentCount(typeParameters) @@ -24785,13 +26506,13 @@ func (c *Checker) getTypeAliasInstantiation(symbol *ast.Symbol, typeArguments [] links := c.typeAliasLinks.Get(symbol) typeParameters := links.typeParameters key := getTypeAliasInstantiationKey(typeArguments, alias) - instantiation := links.instantiations[key] - if instantiation == nil { - mapper := newTypeMapper(typeParameters, c.fillMissingTypeArguments(typeArguments, typeParameters, c.getMinTypeArgumentCount(typeParameters), ast.IsInJSFile(symbol.ValueDeclaration()))) - instantiation = c.instantiateTypeWithAlias(t, mapper, alias) - links.instantiations[key] = instantiation + if instantiation, _ := links.instantiations.get(c, key); instantiation != nil { + return instantiation } - return instantiation + return links.instantiations.compute(c, key, func() *Type { + mapper := newTypeMapper(typeParameters, c.fillMissingTypeArguments(typeArguments, typeParameters, c.getMinTypeArgumentCount(typeParameters), ast.IsInJSFile(symbol.ValueDeclaration()))) + return c.instantiateTypeWithAlias(t, mapper, alias) + }) } func isLocalTypeAlias(symbol *ast.Symbol) bool { @@ -24800,6 +26521,17 @@ func isLocalTypeAlias(symbol *ast.Symbol) bool { } func (c *Checker) getDeclaredTypeOfSymbol(symbol *ast.Symbol) *Type { + key := memoKeyOf(symbol, memoDeclaredType) + if t := c.peekDeclaredTypeOfSymbol(symbol); t != nil && c.readsWithoutMemo(key) { + return t + } + c.beginMemo(key) + result := c.getDeclaredTypeOfSymbolUncached(symbol) + c.endMemo() + return result +} + +func (c *Checker) getDeclaredTypeOfSymbolUncached(symbol *ast.Symbol) *Type { result := c.tryGetDeclaredTypeOfSymbol(symbol) if result == nil { result = c.errorType @@ -24807,6 +26539,22 @@ func (c *Checker) getDeclaredTypeOfSymbol(symbol *ast.Symbol) *Type { return result } +// peekDeclaredTypeOfSymbol returns the declared type of symbol if it is +// resolved, or nil. +func (c *Checker) peekDeclaredTypeOfSymbol(symbol *ast.Symbol) *Type { + switch { + case symbol.Flags()&ast.SymbolFlagsTypeAlias != 0 && symbol.Flags()&(ast.SymbolFlagsClass|ast.SymbolFlagsInterface|ast.SymbolFlagsTypeParameter) == 0: + if links := c.typeAliasLinks.TryGet(symbol); links != nil && c.memoErrorStates == 0 { + return links.declaredType.peek() + } + case symbol.Flags()&(ast.SymbolFlagsClass|ast.SymbolFlagsInterface|ast.SymbolFlagsTypeParameter|ast.SymbolFlagsEnum|ast.SymbolFlagsEnumMember|ast.SymbolFlagsAlias) != 0: + if links := c.declaredTypeLinks.TryGet(symbol); links != nil { + return links.declaredType.peek() + } + } + return nil +} + func (c *Checker) tryGetDeclaredTypeOfSymbol(symbol *ast.Symbol) *Type { switch { case symbol.Flags()&(ast.SymbolFlagsClass|ast.SymbolFlagsInterface) != 0: @@ -24969,20 +26717,21 @@ func (c *Checker) appendTypeParameters(typeParameters []*Type, declarations []*a func (c *Checker) getDeclaredTypeOfTypeParameter(symbol *ast.Symbol) *Type { links := c.declaredTypeLinks.Get(symbol) - if links.declaredType == nil { - links.declaredType = c.newTypeParameter(symbol) + if links.declaredType.get(c) == nil { + links.declaredType.setIdentity(c, c.newTypeParameter(symbol)) } - return links.declaredType + return links.declaredType.get(c) } func (c *Checker) getDeclaredTypeOfTypeAlias(symbol *ast.Symbol) *Type { links := c.typeAliasLinks.Get(symbol) - if links.declaredType == nil { + if links.declaredType.get(c) == nil { // Note that we use the links object as the target here because the symbol object is used as the unique // identity for resolution of the 'type' property in SymbolLinks. if !c.pushTypeResolution(symbol, TypeSystemPropertyNameDeclaredType) { return c.errorType } + reports := c.startCurrentNodeErrors() declaration := core.Find(symbol.Declarations(), ast.IsTypeOrJSTypeAliasDeclaration) typeNode := declaration.Type() t := c.getTypeFromTypeNode(typeNode) @@ -24992,8 +26741,7 @@ func (c *Checker) getDeclaredTypeOfTypeAlias(symbol *ast.Symbol) *Type { // Initialize the instantiation cache for generic type aliases. The declared type corresponds to // an instantiation of the type alias with the type parameters supplied as type arguments. links.typeParameters = typeParameters - links.instantiations = make(map[CacheHashKey]*Type) - links.instantiations[getTypeListKey(typeParameters)] = t + links.instantiations.intern(getTypeListKey(typeParameters), t) } if t == c.intrinsicMarkerType && symbol.Name() == "BuiltinIteratorReturn" { t = c.getBuiltinIteratorReturnType() @@ -25006,16 +26754,18 @@ func (c *Checker) getDeclaredTypeOfTypeAlias(symbol *ast.Symbol) *Type { c.error(errorNode, diagnostics.Type_alias_0_circularly_references_itself, c.symbolToString(symbol)) t = c.errorType } - if links.declaredType == nil { - links.declaredType = t + nodeReports := c.endCurrentNodeErrors(reports) + if links.declaredType.peek() == nil { + links.declaredType.setReported(c, t, nodeReports) } + return links.declaredType.peek() } - return links.declaredType + return links.declaredType.get(c) } func (c *Checker) getDeclaredTypeOfEnum(symbol *ast.Symbol) *Type { links := c.declaredTypeLinks.Get(symbol) - if !(links.declaredType != nil) { + if !(links.declaredType.get(c) != nil) { var memberTypeList []*Type for _, declaration := range symbol.Declarations() { if declaration.Kind == ast.KindEnumDeclaration { @@ -25029,7 +26779,7 @@ func (c *Checker) getDeclaredTypeOfEnum(symbol *ast.Symbol) *Type { } else { memberType = c.createComputedEnumType(memberSymbol) } - c.declaredTypeLinks.Get(memberSymbol).declaredType = c.getFreshTypeOfLiteralType(memberType) + c.declaredTypeLinks.Get(memberSymbol).declaredType.setIdentity(c, c.getFreshTypeOfLiteralType(memberType)) memberTypeList = append(memberTypeList, memberType) } } @@ -25045,9 +26795,9 @@ func (c *Checker) getDeclaredTypeOfEnum(symbol *ast.Symbol) *Type { enumType.flags |= TypeFlagsEnumLiteral enumType.symbol = symbol } - links.declaredType = enumType + links.declaredType.setIdentity(c, enumType) } - return links.declaredType + return links.declaredType.get(c) } func (c *Checker) getEnumMemberValue(node *ast.Node) evaluator.Result { @@ -25067,19 +26817,25 @@ func (c *Checker) createComputedEnumType(symbol *ast.Symbol) *Type { func (c *Checker) getDeclaredTypeOfEnumMember(symbol *ast.Symbol) *Type { links := c.declaredTypeLinks.Get(symbol) - if !(links.declaredType != nil) { + if !(links.declaredType.get(c) != nil) { enumType := c.getDeclaredTypeOfEnum(c.getParentOfSymbol(symbol)) - if links.declaredType == nil { - links.declaredType = enumType + if links.declaredType.get(c) == nil { + links.declaredType.setIdentity(c, enumType) } } - return links.declaredType + return links.declaredType.get(c) } func (c *Checker) computeEnumMemberValues(node *ast.Node) { + c.beginMemo(memoKeyOf(node, memoEnumMemberValue)) + c.computeEnumMemberValuesUncached(node) + c.endMemo() +} + +func (c *Checker) computeEnumMemberValuesUncached(node *ast.Node) { nodeLinks := c.nodeLinks.Get(node) - if !(nodeLinks.flags&NodeCheckFlagsEnumValuesComputed != 0) { - nodeLinks.flags |= NodeCheckFlagsEnumValuesComputed + if nodeLinks.flags&NodeCheckFlagsEnumValuesComputed == 0 { + setFlagMemoBits(c, memoKeyOf(node, memoEnumMemberValue), &nodeLinks.flags, NodeCheckFlagsEnumValuesComputed, NodeCheckFlagsEnumValuesComputed) autoValue := new(jsnum.Number) var previous *ast.Node for _, member := range node.Members() { @@ -25232,36 +26988,37 @@ func (c *Checker) evaluateEnumMember(expr *ast.Node, symbol *ast.Symbol, locatio func (c *Checker) getDeclaredTypeOfAlias(symbol *ast.Symbol) *Type { links := c.declaredTypeLinks.Get(symbol) - if links.declaredType == nil { - links.declaredType = c.getDeclaredTypeOfSymbol(c.resolveAlias(symbol)) + if links.declaredType.get(c) == nil { + links.declaredType.set(c, c.getDeclaredTypeOfSymbol(c.resolveAlias(symbol))) } - return links.declaredType + return links.declaredType.get(c) } func (c *Checker) getTypeFromTypeQueryNode(node *ast.Node) *Type { links := c.typeNodeLinks.Get(node) - if links.resolvedType == nil { + if links.resolvedType.get(c) == nil { // TypeScript 1.0 spec (April 2014): 3.6.3 // The expression is processed as an identifier expression (section 4.3) // or property access expression(section 4.10), // the widened type(section 3.9) of which becomes the result. t := c.checkExpressionWithTypeArguments(node) - links.resolvedType = c.getRegularTypeOfLiteralType(c.getWidenedType(t)) + links.resolvedType.set(c, c.getRegularTypeOfLiteralType(c.getWidenedType(t))) } - return links.resolvedType + return links.resolvedType.get(c) } func (c *Checker) getTypeFromArrayOrTupleTypeNode(node *ast.Node) *Type { links := c.typeNodeLinks.Get(node) - if links.resolvedType == nil { + if links.resolvedType.get(c) == nil { target := c.getArrayOrTupleTargetType(node) if target == c.emptyGenericType { - links.resolvedType = c.emptyObjectType + links.resolvedType.set(c, c.emptyObjectType) } else if !(node.Kind == ast.KindTupleType && core.Some(node.Elements(), c.isVariadicTupleElement)) && c.isDeferredTypeReferenceNode(node, false) { if node.Kind == ast.KindTupleType && len(node.Elements()) == 0 { - links.resolvedType = target + links.resolvedType.set(c, target) } else { - links.resolvedType = c.createDeferredTypeReference(target, node, nil /*mapper*/, nil /*alias*/) + // The deferred reference is identified by the node, so it outlives a cycle. + links.resolvedType.setIdentity(c, c.createDeferredTypeReference(target, node, nil /*mapper*/, nil /*alias*/)) } } else { var elementTypes []*Type @@ -25271,13 +27028,13 @@ func (c *Checker) getTypeFromArrayOrTupleTypeNode(node *ast.Node) *Type { elementTypes = core.Map(node.Elements(), c.getTypeFromTypeNode) } if target.objectFlags&ObjectFlagsTuple != 0 { - links.resolvedType = c.createNormalizedTupleTypeEx(target, elementTypes, ObjectFlagsFromTypeNode) + links.resolvedType.set(c, c.createNormalizedTupleTypeEx(target, elementTypes, ObjectFlagsFromTypeNode)) } else { - links.resolvedType = c.createTypeReferenceEx(target, elementTypes, ObjectFlagsFromTypeNode) + links.resolvedType.set(c, c.createTypeReferenceEx(target, elementTypes, ObjectFlagsFromTypeNode)) } } } - return links.resolvedType + return links.resolvedType.get(c) } func (c *Checker) isVariadicTupleElement(node *ast.Node) bool { @@ -25302,14 +27059,14 @@ func (c *Checker) isReadonlyTypeOperator(node *ast.Node) bool { func (c *Checker) getTypeFromNamedTupleTypeNode(node *ast.Node) *Type { links := c.typeNodeLinks.Get(node) - if links.resolvedType == nil { + if links.resolvedType.get(c) == nil { if node.AsNamedTupleMember().DotDotDotToken != nil { - links.resolvedType = c.getTypeFromRestTypeNode(node) + links.resolvedType.set(c, c.getTypeFromRestTypeNode(node)) } else { - links.resolvedType = c.addOptionalityEx(c.getTypeFromTypeNode(node.Type()), true /*isProperty*/, node.QuestionToken() != nil) + links.resolvedType.set(c, c.addOptionalityEx(c.getTypeFromTypeNode(node.Type()), true /*isProperty*/, node.QuestionToken() != nil)) } } - return links.resolvedType + return links.resolvedType.get(c) } func (c *Checker) getTypeFromRestTypeNode(node *ast.Node) *Type { @@ -25347,16 +27104,16 @@ func (c *Checker) getTypeFromOptionalTypeNode(node *ast.Node) *Type { func (c *Checker) getTypeFromUnionTypeNode(node *ast.Node) *Type { links := c.typeNodeLinks.Get(node) - if links.resolvedType == nil { + if links.resolvedType.get(c) == nil { alias := c.getAliasForTypeNode(node) - links.resolvedType = c.getUnionTypeEx(core.Map(node.AsUnionTypeNode().Types.Nodes, c.getTypeFromTypeNode), UnionReductionLiteral, alias, nil /*origin*/) + links.resolvedType.set(c, c.getUnionTypeEx(core.Map(node.AsUnionTypeNode().Types.Nodes, c.getTypeFromTypeNode), UnionReductionLiteral, alias, nil /*origin*/)) } - return links.resolvedType + return links.resolvedType.get(c) } func (c *Checker) getTypeFromIntersectionTypeNode(node *ast.Node) *Type { links := c.typeNodeLinks.Get(node) - if links.resolvedType == nil { + if links.resolvedType.get(c) == nil { alias := c.getAliasForTypeNode(node) types := core.Map(node.AsIntersectionTypeNode().Types.Nodes, c.getTypeFromTypeNode) // We perform no supertype reduction for X & {} or {} & X, where X is one of string, number, bigint, @@ -25370,14 +27127,14 @@ func (c *Checker) getTypeFromIntersectionTypeNode(node *ast.Node) *Type { noSupertypeReduction = t.flags&(TypeFlagsString|TypeFlagsNumber|TypeFlagsBigInt) != 0 || t.flags&TypeFlagsTemplateLiteral != 0 && c.isPatternLiteralType(t) } } - links.resolvedType = c.getIntersectionTypeEx(types, core.IfElse(noSupertypeReduction, IntersectionFlagsNoSupertypeReduction, 0), alias) + links.resolvedType.set(c, c.getIntersectionTypeEx(types, core.IfElse(noSupertypeReduction, IntersectionFlagsNoSupertypeReduction, 0), alias)) } - return links.resolvedType + return links.resolvedType.get(c) } func (c *Checker) getTypeFromTemplateTypeNode(node *ast.Node) *Type { links := c.typeNodeLinks.Get(node) - if links.resolvedType == nil { + if links.resolvedType.get(c) == nil { spans := node.AsTemplateLiteralTypeNode().TemplateSpans texts := make([]string, len(spans.Nodes)+1) types := make([]*Type, len(spans.Nodes)) @@ -25386,28 +27143,28 @@ func (c *Checker) getTypeFromTemplateTypeNode(node *ast.Node) *Type { texts[i+1] = span.AsTemplateLiteralTypeSpan().Literal.Text() types[i] = c.getTypeFromTypeNode(span.Type()) } - links.resolvedType = c.getTemplateLiteralType(texts, types) + links.resolvedType.set(c, c.getTemplateLiteralType(texts, types)) } - return links.resolvedType + return links.resolvedType.get(c) } func (c *Checker) getTypeFromMappedTypeNode(node *ast.Node) *Type { links := c.typeNodeLinks.Get(node) - if links.resolvedType == nil { + if links.resolvedType.get(c) == nil { t := c.newObjectType(ObjectFlagsMapped, node.Symbol()) t.AsMappedType().declaration = node.AsMappedTypeNode() t.alias = c.getAliasForTypeNode(node) - links.resolvedType = t + links.resolvedType.set(c, t) // Eagerly resolve the constraint type which forces an error if the constraint type circularly // references itself through one or more type aliases. c.getConstraintTypeFromMappedType(t) } - return links.resolvedType + return links.resolvedType.get(c) } func (c *Checker) getTypeFromConditionalTypeNode(node *ast.Node) *Type { links := c.typeNodeLinks.Get(node) - if links.resolvedType == nil { + if links.resolvedType.get(c) == nil { checkType := c.getTypeFromTypeNode(node.AsConditionalTypeNode().CheckType) alias := c.getAliasForTypeNode(node) allOuterTypeParameters := c.getOuterTypeParameters(node, true /*includeThisTypes*/) @@ -25424,16 +27181,14 @@ func (c *Checker) getTypeFromConditionalTypeNode(node *ast.Node) *Type { isDistributive: checkType.flags&TypeFlagsTypeParameter != 0, inferTypeParameters: c.getInferTypeParameters(node), outerTypeParameters: outerTypeParameters, - instantiations: nil, alias: alias, } - links.resolvedType = c.getConditionalType(root, nil /*mapper*/, false /*forConstraint*/, nil) + links.resolvedType.set(c, c.getConditionalType(root, nil /*mapper*/, false /*forConstraint*/, nil)) if outerTypeParameters != nil { - root.instantiations = make(map[CacheHashKey]*Type) - root.instantiations[getConditionalTypeKey(outerTypeParameters, nil /*alias*/, false /*forConstraint*/)] = links.resolvedType + root.instantiations.intern(getConditionalTypeKey(outerTypeParameters, nil /*alias*/, false /*forConstraint*/), links.resolvedType.get(c)) } } - return links.resolvedType + return links.resolvedType.get(c) } func (c *Checker) getConditionalType(root *ConditionalRoot, mapper *TypeMapper, forConstraint bool, alias *TypeAlias) *Type { @@ -25447,7 +27202,7 @@ func (c *Checker) getConditionalType(root *ConditionalRoot, mapper *TypeMapper, // cases we increment the tail recursion counter and stop after 1000 iterations. for { if tailCount == 1000 { - c.error(c.currentNode, diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite) + c.errorAtCurrentNode(diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite) return c.errorType } checkType := c.instantiateType(c.getActualTypeVariable(root.checkType), mapper) @@ -25620,11 +27375,13 @@ func (c *Checker) getPermissiveInstantiation(t *Type) *Type { return t } key := CachedTypeKey{kind: CachedTypeKindPermissiveInstantiation, typeId: t.id} - if cached := c.cachedTypes[key]; cached != nil { + if cached, _ := c.cachedTypes.get(c, key); cached != nil { return cached } + c.cachedTypes.begin(c, key) result := c.instantiateType(t, c.permissiveMapper) - c.cachedTypes[key] = result + c.cachedTypes.set(c, key, result) + c.endMemo() return result } @@ -25633,32 +27390,41 @@ func (c *Checker) getRestrictiveInstantiation(t *Type) *Type { return t } key := CachedTypeKey{kind: CachedTypeKindRestrictiveInstantiation, typeId: t.id} - if cached := c.cachedTypes[key]; cached != nil { + if cached, _ := c.cachedTypes.get(c, key); cached != nil { return cached } + c.cachedTypes.begin(c, key) result := c.instantiateType(t, c.restrictiveMapper) - c.cachedTypes[key] = result + c.cachedTypes.set(c, key, result) + c.endMemo() // We set the following so we don't attempt to set the restrictive instance of a restrictive instance // which is redundant - we'll produce new type identities, but all type params have already been mapped. // This also gives us a way to detect restrictive instances upon comparisons and _disable_ the "distributeive constraint" // assignability check for them, which is distinctly unsafe, as once you have a restrctive instance, all the type parameters // are constrained to `unknown` and produce tons of false positives/negatives! - c.cachedTypes[CachedTypeKey{kind: CachedTypeKindRestrictiveInstantiation, typeId: result.id}] = result + c.cachedTypes.intern(CachedTypeKey{kind: CachedTypeKindRestrictiveInstantiation, typeId: result.id}, result) return result } +// isRestrictiveInstantiation reports whether t is the result of a restrictive +// instantiation, which is recorded purely from its construction. +func (c *Checker) isRestrictiveInstantiation(t *Type) bool { + r, _ := c.cachedTypes.get(c, CachedTypeKey{kind: CachedTypeKindRestrictiveInstantiation, typeId: t.id}) + return r == t +} + func (c *Checker) getRestrictiveTypeParameter(t *Type) *Type { - if t.AsTypeParameter().constraint == nil && c.getConstraintDeclaration(t) == nil || t.AsTypeParameter().constraint == c.noConstraintType { + if t.AsTypeParameter().constraint.get(c) == nil && c.getConstraintDeclaration(t) == nil || t.AsTypeParameter().constraint.get(c) == c.noConstraintType { return t } key := CachedTypeKey{kind: CachedTypeKindRestrictiveTypeParameter, typeId: t.id} - if cached := c.cachedTypes[key]; cached != nil { + if cached, _ := c.cachedTypes.get(c, key); cached != nil { return cached } result := c.newTypeParameter(t.symbol) - result.AsTypeParameter().constraint = c.noConstraintType + result.AsTypeParameter().constraint.intern(c.noConstraintType) result.AsTypeParameter().origin = t - c.cachedTypes[key] = result + c.cachedTypes.intern(key, result) return result } @@ -25678,57 +27444,53 @@ func (c *Checker) permissiveMapperWorker(t *Type) *Type { func (c *Checker) getTrueTypeFromConditionalType(t *Type) *Type { d := t.AsConditionalType() - if d.resolvedTrueType == nil { - d.resolvedTrueType = c.instantiateType(c.getTypeFromTypeNode(d.root.node.TrueType), d.mapper) - } - return d.resolvedTrueType + return c.memoCompute(&d.resolvedTrueType, func() *Type { + return c.instantiateType(c.getTypeFromTypeNode(d.root.node.TrueType), d.mapper) + }) } func (c *Checker) getFalseTypeFromConditionalType(t *Type) *Type { d := t.AsConditionalType() - if d.resolvedFalseType == nil { - d.resolvedFalseType = c.instantiateType(c.getTypeFromTypeNode(d.root.node.FalseType), d.mapper) - } - return d.resolvedFalseType + return c.memoCompute(&d.resolvedFalseType, func() *Type { + return c.instantiateType(c.getTypeFromTypeNode(d.root.node.FalseType), d.mapper) + }) } func (c *Checker) getInferredTrueTypeFromConditionalType(t *Type) *Type { d := t.AsConditionalType() - if d.resolvedInferredTrueType == nil { + return c.memoCompute(&d.resolvedInferredTrueType, func() *Type { if d.combinedMapper != nil { - d.resolvedInferredTrueType = c.instantiateType(c.getTypeFromTypeNode(d.root.node.TrueType), d.combinedMapper) - } else { - d.resolvedInferredTrueType = c.getTrueTypeFromConditionalType(t) + return c.instantiateType(c.getTypeFromTypeNode(d.root.node.TrueType), d.combinedMapper) } - } - return d.resolvedInferredTrueType + return c.getTrueTypeFromConditionalType(t) + }) } func (c *Checker) getTypeFromInferTypeNode(node *ast.Node) *Type { links := c.typeNodeLinks.Get(node) - if links.resolvedType == nil { - links.resolvedType = c.getDeclaredTypeOfTypeParameter(c.getSymbolOfDeclaration(node.AsInferTypeNode().TypeParameter)) + if links.resolvedType.get(c) == nil { + links.resolvedType.set(c, c.getDeclaredTypeOfTypeParameter(c.getSymbolOfDeclaration(node.AsInferTypeNode().TypeParameter))) } - return links.resolvedType + return links.resolvedType.get(c) } func (c *Checker) getTypeFromImportTypeNode(node *ast.Node) *Type { links := c.typeNodeLinks.Get(node) - if links.resolvedType == nil { + if links.resolvedType.get(c) == nil { n := node.AsImportTypeNode() if !ast.IsLiteralImportTypeNode(node) { c.error(n.Argument, diagnostics.String_literal_expected) - c.symbolNodeLinks.Get(node).resolvedSymbol = c.unknownSymbol - links.resolvedType = c.errorType - return links.resolvedType + c.symbolNodeLinks.Get(node).resolvedSymbol.set(c, c.unknownSymbol) + links.resolvedType.set(c, c.errorType) + return links.resolvedType.get(c) } targetMeaning := core.IfElse(n.IsTypeOf, ast.SymbolFlagsValue, ast.SymbolFlagsType) // TODO: Future work: support unions/generics/whatever via a deferred import-type innerModuleSymbol := c.resolveExternalModuleName(node, n.Argument.AsLiteralTypeNode().Literal, false /*ignoreErrors*/, c.getTypeFromImportAttributes(ast.GetImportAttributes(node))) if innerModuleSymbol == nil { - c.symbolNodeLinks.Get(node).resolvedSymbol = c.unknownSymbol - links.resolvedType = c.errorType - return links.resolvedType + c.symbolNodeLinks.Get(node).resolvedSymbol.set(c, c.unknownSymbol) + links.resolvedType.set(c, c.errorType) + return links.resolvedType.get(c) } moduleSymbol := c.resolveExternalModuleSymbol(innerModuleSymbol, false /*dontResolveAlias*/) if !ast.NodeIsMissing(n.Qualifier) { @@ -25763,28 +27525,28 @@ func (c *Checker) getTypeFromImportTypeNode(node *ast.Node) *Type { next := core.OrElse(symbolFromModule, symbolFromVariable) if next == nil { c.error(current, diagnostics.Namespace_0_has_no_exported_member_1, c.getFullyQualifiedName(currentNamespace, nil), scanner.DeclarationNameToString(current)) - links.resolvedType = c.errorType - return links.resolvedType + links.resolvedType.set(c, c.errorType) + return links.resolvedType.get(c) } - c.symbolNodeLinks.Get(current).resolvedSymbol = next - c.symbolNodeLinks.Get(current.Parent).resolvedSymbol = next + c.symbolNodeLinks.Get(current).resolvedSymbol.set(c, next) + c.symbolNodeLinks.Get(current.Parent).resolvedSymbol.set(c, next) currentNamespace = next } - links.resolvedType = c.resolveImportSymbolType(node, currentNamespace, targetMeaning) + links.resolvedType.set(c, c.resolveImportSymbolType(node, currentNamespace, targetMeaning)) } else { if c.getSymbolFlags(moduleSymbol)&targetMeaning != 0 { - links.resolvedType = c.resolveImportSymbolType(node, moduleSymbol, targetMeaning) + links.resolvedType.set(c, c.resolveImportSymbolType(node, moduleSymbol, targetMeaning)) } else { message := core.IfElse(targetMeaning == ast.SymbolFlagsValue, diagnostics.Module_0_does_not_refer_to_a_value_but_is_used_as_a_value_here, diagnostics.Module_0_does_not_refer_to_a_type_but_is_used_as_a_type_here_Did_you_mean_typeof_import_0) c.error(node, message, n.Argument.AsLiteralTypeNode().Literal.Text()) - c.symbolNodeLinks.Get(node).resolvedSymbol = c.unknownSymbol - links.resolvedType = c.errorType + c.symbolNodeLinks.Get(node).resolvedSymbol.set(c, c.unknownSymbol) + links.resolvedType.set(c, c.errorType) } } } - return links.resolvedType + return links.resolvedType.get(c) } func (c *Checker) getIdentifierChain(node *ast.Node) []*ast.Node { @@ -25796,7 +27558,7 @@ func (c *Checker) getIdentifierChain(node *ast.Node) []*ast.Node { func (c *Checker) resolveImportSymbolType(node *ast.Node, symbol *ast.Symbol, meaning ast.SymbolFlags) *Type { resolvedSymbol := c.resolveSymbol(symbol) - c.symbolNodeLinks.Get(node).resolvedSymbol = resolvedSymbol + c.symbolNodeLinks.Get(node).resolvedSymbol.set(c, resolvedSymbol) if meaning == ast.SymbolFlagsValue { // intentionally doesn't use resolved symbol so type is cached as expected on the alias return c.getInstantiationExpressionType(c.getTypeOfSymbol(symbol), node) @@ -25820,18 +27582,22 @@ func (c *Checker) getGlobalStrictFunctionType(name string) *Type { } func (c *Checker) getGlobalImportMetaExpressionType() *Type { - if c.deferredGlobalImportMetaExpressionType == nil { - // Create a synthetic type `ImportMetaExpression { meta: MetaProperty }` - symbol := c.newSymbol(ast.SymbolFlagsNone, "ImportMetaExpression") - importMetaType := c.getGlobalImportMetaType() - metaPropertySymbol := c.newSymbolEx(ast.SymbolFlagsProperty, "meta", ast.CheckFlagsReadonly) - metaPropertySymbol.SetParent(symbol) - c.valueSymbolLinks.Get(metaPropertySymbol).resolvedType = importMetaType - members := createSymbolTable([]*ast.Symbol{metaPropertySymbol}) - symbol.SetMembers(members, &c.symbolExtraArena) - c.deferredGlobalImportMetaExpressionType = c.newAnonymousType(symbol, members, nil, nil, nil) + if t := c.deferredGlobalImportMetaExpressionType.get(c); t != nil { + return t } - return c.deferredGlobalImportMetaExpressionType + c.deferredGlobalImportMetaExpressionType.begin(c) + // Create a synthetic type `ImportMetaExpression { meta: MetaProperty }` + symbol := c.newSymbol(ast.SymbolFlagsNone, "ImportMetaExpression") + importMetaType := c.getGlobalImportMetaType() + metaPropertySymbol := c.newSymbolEx(ast.SymbolFlagsProperty, "meta", ast.CheckFlagsReadonly) + metaPropertySymbol.SetParent(symbol) + c.valueSymbolLinks.Get(metaPropertySymbol).resolvedType.intern(importMetaType) + members := createSymbolTable([]*ast.Symbol{metaPropertySymbol}) + symbol.SetMembers(members, &c.symbolExtraArena) + t := c.newAnonymousType(symbol, members, nil, nil, nil) + c.deferredGlobalImportMetaExpressionType.set(c, t) + c.endMemo() + return t } func (c *Checker) createIterableType(iteratedType *Type) *Type { @@ -25897,10 +27663,10 @@ func (c *Checker) getTupleTargetType(elementInfos []TupleElementInfo, readonly b return c.globalArrayType } key := getTupleKey(elementInfos, readonly) - t := c.tupleTypes[key] + t, _ := c.tupleTypes.get(key) if t == nil { t = c.createTupleTargetType(elementInfos, readonly) - c.tupleTypes[key] = t + c.tupleTypes.intern(key, t) } return t } @@ -25929,7 +27695,7 @@ func (c *Checker) createTupleTargetType(elementInfos []TupleElementInfo, readonl combinedFlags |= flags if combinedFlags&ElementFlagsVariable == 0 { property := c.newSymbolEx(ast.SymbolFlagsProperty|core.IfElse(flags&ElementFlagsOptional != 0, ast.SymbolFlagsOptional, 0), strconv.Itoa(i), core.IfElse(readonly, ast.CheckFlagsReadonly, 0)) - c.valueSymbolLinks.Get(property).resolvedType = typeParameter + c.valueSymbolLinks.Get(property).resolvedType.intern(typeParameter) // c.valueSymbolLinks.get(property).tupleLabelDeclaration = elementInfos[i].labeledDeclaration members.Set(property.Name(), property) } @@ -25938,28 +27704,27 @@ func (c *Checker) createTupleTargetType(elementInfos []TupleElementInfo, readonl fixedLength := members.Len() lengthSymbol := c.newSymbolEx(ast.SymbolFlagsProperty, "length", core.IfElse(readonly, ast.CheckFlagsReadonly, 0)) if combinedFlags&ElementFlagsVariable != 0 { - c.valueSymbolLinks.Get(lengthSymbol).resolvedType = c.numberType + c.valueSymbolLinks.Get(lengthSymbol).resolvedType.intern(c.numberType) } else { var literalTypes []*Type for i := minLength; i <= arity; i++ { literalTypes = append(literalTypes, c.getNumberLiteralType(jsnum.Number(i))) } - c.valueSymbolLinks.Get(lengthSymbol).resolvedType = c.getUnionType(literalTypes) + c.valueSymbolLinks.Get(lengthSymbol).resolvedType.intern(c.getUnionType(literalTypes)) } members.Set(lengthSymbol.Name(), lengthSymbol) t := c.newObjectType(ObjectFlagsTuple|ObjectFlagsReference, nil) d := t.AsTupleType() d.thisType = c.newTypeParameter(nil) d.thisType.AsTypeParameter().isThisType = true - d.thisType.AsTypeParameter().constraint = t + d.thisType.AsTypeParameter().constraint.intern(t) for _, tp := range typeParameters { tp.AsTypeParameter().origin = t } d.allTypeParameters = append(typeParameters, d.thisType) - d.instantiations = make(map[CacheHashKey]*Type) - d.instantiations[getTypeListKey(d.TypeParameters())] = t + d.instantiations.intern(getTypeListKey(d.TypeParameters()), t) d.target = t - d.resolvedTypeArguments = d.TypeParameters() + d.resolvedTypeArguments.intern(d.TypeParameters()) d.declaredMembersResolved = true d.declaredMembers = members d.elementInfos = elementInfos @@ -26023,17 +27788,20 @@ func (c *Checker) isGenericIndexType(t *Type) bool { func (c *Checker) getGenericObjectFlags(t *Type) ObjectFlags { var combinedFlags ObjectFlags if t.flags&(TypeFlagsUnionOrIntersection|TypeFlagsSubstitution) != 0 { - if t.objectFlags&ObjectFlagsIsGenericTypeComputed == 0 { + if c.objectFlagsKnown(t, ObjectFlagsIsGenericTypeComputed) { + return t.objectFlags & ObjectFlagsIsGenericType + } + return c.objectFlagsCell(t, memoGenericObjectFlags, ObjectFlagsIsGenericTypeComputed, ObjectFlagsIsGenericType, false /*inProgress*/, func() ObjectFlags { + var flags ObjectFlags if t.flags&TypeFlagsUnionOrIntersection != 0 { for _, u := range t.Types() { - combinedFlags |= c.getGenericObjectFlags(u) + flags |= c.getGenericObjectFlags(u) } } else { - combinedFlags = c.getGenericObjectFlags(t.AsSubstitutionType().baseType) | c.getGenericObjectFlags(t.AsSubstitutionType().constraint) + flags = c.getGenericObjectFlags(t.AsSubstitutionType().baseType) | c.getGenericObjectFlags(t.AsSubstitutionType().constraint) } - t.objectFlags |= ObjectFlagsIsGenericTypeComputed | combinedFlags - } - return t.objectFlags & ObjectFlagsIsGenericType + return flags + }) } if t.flags&TypeFlagsInstantiableNonPrimitive != 0 || c.isGenericMappedType(t) || c.isGenericTupleType(t) { combinedFlags |= ObjectFlagsIsGenericObjectType @@ -26079,10 +27847,8 @@ func (c *Checker) isGenericReducibleType(t *Type) bool { func (c *Checker) isReducibleIntersection(t *Type) bool { d := t.AsIntersectionType() - if d.uniqueLiteralFilledInstantiation == nil { - d.uniqueLiteralFilledInstantiation = c.instantiateType(t, c.uniqueLiteralMapper) - } - return c.getReducedType(d.uniqueLiteralFilledInstantiation) != d.uniqueLiteralFilledInstantiation + instantiated := c.memoCompute(&d.uniqueLiteralFilledInstantiation, func() *Type { return c.instantiateType(t, c.uniqueLiteralMapper) }) + return c.getReducedType(instantiated) != instantiated } func (c *Checker) getUniqueLiteralTypeForTypeParameter(t *Type) *Type { @@ -26149,7 +27915,7 @@ func (c *Checker) newType(flags TypeFlags, objectFlags ObjectFlags, t *Type, kin t.objectFlags = objectFlags &^ (ObjectFlagsCouldContainTypeVariablesComputed | ObjectFlagsCouldContainTypeVariables | ObjectFlagsMembersResolved) t.id = TypeId(c.TypeCount) t.kind = kind //nolint:customlint - t.checker = c + t.checker = c.root if c.tracer != nil { c.tracer.RecordType(t) } @@ -26258,14 +28024,14 @@ func (c *Checker) createTypeReference(target *Type, typeArguments []*Type) *Type func (c *Checker) createTypeReferenceEx(target *Type, typeArguments []*Type, objectFlags ObjectFlags) *Type { id := getTypeListKey(typeArguments) intf := target.AsInterfaceType() - if t, ok := intf.instantiations[id]; ok { + if t, ok := intf.instantiations.get(c, id); ok { return t } t := c.newObjectType(ObjectFlagsReference|objectFlags|c.getPropagatingFlagsOfTypes(typeArguments, TypeFlagsNone), target.symbol) d := t.AsTypeReference() d.target = target - d.resolvedTypeArguments = typeArguments - intf.instantiations[id] = t + d.resolvedTypeArguments.intern(typeArguments) + intf.instantiations.intern(id, t) return t } @@ -26289,12 +28055,18 @@ func (c *Checker) cloneTypeReference(source *Type) *Type { t := c.newObjectType(ObjectFlagsReference, source.symbol) t.objectFlags = source.objectFlags &^ ObjectFlagsMembersResolved t.AsTypeReference().target = source.AsTypeReference().target - t.AsTypeReference().resolvedTypeArguments = source.AsTypeReference().resolvedTypeArguments + t.AsTypeReference().resolvedTypeArguments.intern(source.AsTypeReference().resolvedTypeArguments.peek()) return t } +// setStructuredTypeMembers sets the members of a type being created, which +// are then resolved. func (c *Checker) setStructuredTypeMembers(t *Type, members ast.SymbolTable, callSignatures []*Signature, constructSignatures []*Signature, indexInfos []*IndexInfo) { t.objectFlags |= ObjectFlagsMembersResolved + c.setStructuredTypeData(t, members, callSignatures, constructSignatures, indexInfos) +} + +func (c *Checker) setStructuredTypeData(t *Type, members ast.SymbolTable, callSignatures []*Signature, constructSignatures []*Signature, indexInfos []*IndexInfo) { data := t.AsStructuredType() data.members = members data.properties = c.getNamedMembers(members) @@ -26412,10 +28184,10 @@ func (c *Checker) newSignature(flags SignatureFlags, declaration *ast.Node, type sig.setTypeParameters(c, typeParameters) sig.parameters = parameters sig.setThisParameter(c, thisParameter) - sig.resolvedReturnType = resolvedReturnType - sig.setResolvedTypePredicate(c, resolvedTypePredicate) + sig.resolvedReturnType.intern(resolvedReturnType) + sig.initResolvedTypePredicate(c, resolvedTypePredicate) sig.minArgumentCount = int32(minArgumentCount) - sig.resolvedMinArgumentCount = -1 + sig.resolvedMinArgumentCount.intern(-1) return sig } @@ -26462,10 +28234,10 @@ func isFreshLiteralType(t *Type) bool { } func (c *Checker) getStringLiteralType(value string) *Type { - t := c.stringLiteralTypes[value] + t, _ := c.stringLiteralTypes.get(value) if t == nil { t = c.newLiteralType(TypeFlagsStringLiteral, value, nil) - c.stringLiteralTypes[value] = t + c.stringLiteralTypes.intern(value, t) } return t } @@ -26479,19 +28251,19 @@ func (c *Checker) getNumberLiteralType(value jsnum.Number) *Type { } return c.nanType } - t := c.numberLiteralTypes[value] + t, _ := c.numberLiteralTypes.get(value) if t == nil { t = c.newLiteralType(TypeFlagsNumberLiteral, value, nil) - c.numberLiteralTypes[value] = t + c.numberLiteralTypes.intern(value, t) } return t } func (c *Checker) getBigIntLiteralType(value jsnum.PseudoBigInt) *Type { - t := c.bigintLiteralTypes[value] + t, _ := c.bigintLiteralTypes.get(value) if t == nil { t = c.newLiteralType(TypeFlagsBigIntLiteral, value, nil) - c.bigintLiteralTypes[value] = t + c.bigintLiteralTypes.intern(value, t) } return t } @@ -26528,11 +28300,11 @@ func (c *Checker) getEnumLiteralType(value any, enumSymbol *ast.Symbol, symbol * // NaN cannot be used as a Go map key because NaN != NaN in IEEE 754, // so Go map lookups for NaN always miss. Cache NaN enum types separately by enum symbol. if v.IsNaN() { - t := c.enumNaNLiteralTypes[enumSymbol] + t, _ := c.enumNaNLiteralTypes.get(enumSymbol) if t == nil { t = c.newLiteralType(flags, value, nil) t.symbol = symbol - c.enumNaNLiteralTypes[enumSymbol] = t + c.enumNaNLiteralTypes.intern(enumSymbol, t) } return t } @@ -26540,11 +28312,11 @@ func (c *Checker) getEnumLiteralType(value any, enumSymbol *ast.Symbol, symbol * panic("Unhandled case in getEnumLiteralType") } key := EnumLiteralKey{enumSymbol: enumSymbol, value: value} - t := c.enumLiteralTypes[key] + t, _ := c.enumLiteralTypes.get(key) if t == nil { t = c.newLiteralType(flags, value, nil) t.symbol = symbol - c.enumLiteralTypes[key] = t + c.enumLiteralTypes.intern(key, t) } return t } @@ -26635,11 +28407,13 @@ func (c *Checker) getBaseTypeOfEnumLikeType(t *Type) *Type { func (c *Checker) getBaseTypeOfLiteralTypeUnion(t *Type) *Type { key := CachedTypeKey{kind: CachedTypeKindLiteralUnionBaseType, typeId: t.id} - if cached, ok := c.cachedTypes[key]; ok { + if cached, ok := c.cachedTypes.get(c, key); ok { return cached } + c.cachedTypes.begin(c, key) result := c.mapType(t, c.getBaseTypeOfLiteralType) - c.cachedTypes[key] = result + c.cachedTypes.set(c, key, result) + c.endMemo() return result } @@ -26809,10 +28583,12 @@ func (c *Checker) getUnionTypeEx(types []*Type, unionReduction UnionReduction, a id1, id2 = id2, id1 } key := UnionOfUnionKey{id1: id1, id2: id2, r: unionReduction, a: getAliasKey(alias)} - t := c.unionOfUnionTypes[key] + t, _ := c.unionOfUnionTypes.get(c, key) if t == nil { + c.unionOfUnionTypes.begin(c, key) t = c.getUnionTypeWorker(types, unionReduction, alias, nil /*origin*/) - c.unionOfUnionTypes[key] = t + c.unionOfUnionTypes.set(c, key, t) + c.endMemo() } return t } @@ -26913,7 +28689,7 @@ func (c *Checker) getUnionTypeFromSortedList(types []*Type, precomputedObjectFla return types[0] } key := getUnionKey(types, origin, alias) - t := c.unionTypes[key] + t, _ := c.unionTypes.get(key) if t == nil { t = c.newUnionType(precomputedObjectFlags|c.getPropagatingFlagsOfTypes(types, TypeFlagsNullable), types) t.AsUnionType().origin = origin @@ -26921,13 +28697,13 @@ func (c *Checker) getUnionTypeFromSortedList(types []*Type, precomputedObjectFla if len(types) == 2 && types[0].flags&TypeFlagsBooleanLiteral != 0 && types[1].flags&TypeFlagsBooleanLiteral != 0 { t.flags |= TypeFlagsBoolean } - c.unionTypes[key] = t + c.unionTypes.intern(key, t) } return t } func (c *Checker) UnionTypes() iter.Seq[*Type] { - return maps.Values(c.unionTypes) + return c.unionTypes.values() } func (c *Checker) addTypesToUnion(sourceTypes []*Type, types []*Type) ([]*Type, TypeFlags) { @@ -27108,9 +28884,10 @@ func (c *Checker) removeSubtypes(types []*Type, hasObjectTypes bool) []*Type { return types } key := getTypeListKey(types) - if cached := c.subtypeReductionCache[key]; cached != nil { + if cached, _ := c.subtypeReductionCache.get(c, key); cached != nil { return cached } + c.subtypeReductionCache.begin(c, key) // We assume that redundant primitive types have already been removed from the types array and that there // are no any and unknown types in the array. Thus, the only possible supertypes for primitive types are empty // object types, and if none of those are present we can exclude primitive types from the subtype check. @@ -27164,7 +28941,8 @@ func (c *Checker) removeSubtypes(types []*Type, hasObjectTypes bool) []*Type { if tr := c.tracer; tr != nil { tr.Instant(tracing.PhaseCheckTypes, "removeSubtypes_DepthLimit", map[string]any{"estimatedCount": estimatedCount}) } - c.error(c.currentNode, diagnostics.Expression_produces_a_union_type_that_is_too_complex_to_represent) + c.errorAtCurrentNode(diagnostics.Expression_produces_a_union_type_that_is_too_complex_to_represent) + c.endMemo() return nil } } @@ -27189,7 +28967,8 @@ func (c *Checker) removeSubtypes(types []*Type, hasObjectTypes bool) []*Type { } } reduced := c.typeListArena.Clone(types) - c.subtypeReductionCache[key] = reduced + c.subtypeReductionCache.set(c, key, reduced) + c.endMemo() return reduced } @@ -27336,8 +29115,9 @@ func (c *Checker) getIntersectionTypeEx(types []*Type, flags IntersectionFlags, } } key := getIntersectionKey(typeSet, flags, alias) - result := c.intersectionTypes[key] + result, _ := c.intersectionTypes.get(c, key) if result == nil { + c.intersectionTypes.begin(c, key) if includes&TypeFlagsUnion != 0 { var reduced bool typeSet, reduced = c.intersectUnionsOfPrimitiveTypes(typeSet) @@ -27373,6 +29153,7 @@ func (c *Checker) getIntersectionTypeEx(types []*Type, flags IntersectionFlags, // the form X & A & C | X & A & D | X & B & C | X & B & D. If the estimated size of the resulting union type // exceeds 100000 constituents, report an error. if !c.checkCrossProductUnion(typeSet) { + c.endMemo() return c.errorType } constituents := c.getCrossProductIntersections(typeSet, flags) @@ -27388,7 +29169,8 @@ func (c *Checker) getIntersectionTypeEx(types []*Type, flags IntersectionFlags, } else { result = c.getIntersectionTypeWithContent(objectFlags|c.getPropagatingFlagsOfTypes(types /*excludeKinds*/, TypeFlagsNullable), typeSet, alias) } - c.intersectionTypes[key] = result + c.intersectionTypes.setIdentity(c, key, result) + c.endMemo() } return result } @@ -27402,12 +29184,12 @@ func (c *Checker) getIntersectionTypeWithContent(objectFlags ObjectFlags, typeSe b.writeUint32(uint32(objectFlags)) b.writeAlias(alias) key := b.hash() - if existing := c.intersectionContentTypes[key]; existing != nil { + if existing, _ := c.intersectionContentTypes.get(key); existing != nil { return existing } result := c.newIntersectionType(objectFlags, typeSet) result.alias = alias - c.intersectionContentTypes[key] = result + c.intersectionContentTypes.intern(key, result) return result } @@ -27844,7 +29626,7 @@ func (c *Checker) checkCrossProductUnion(types []*Type) bool { if tr := c.tracer; tr != nil { tr.Instant(tracing.PhaseCheckTypes, "checkCrossProductUnion_DepthLimit", map[string]any{"size": size}) } - c.error(c.currentNode, diagnostics.Expression_produces_a_union_type_that_is_too_complex_to_represent) + c.errorAtCurrentNode(diagnostics.Expression_produces_a_union_type_that_is_too_complex_to_represent) return false } return true @@ -27909,9 +29691,10 @@ func (c *Checker) getExtractStringType(t *Type) *Type { func (c *Checker) getLiteralTypeFromProperties(t *Type, include TypeFlags, includeOrigin bool) *Type { key := PropertiesTypesKey{typeId: t.id, include: include, includeOrigin: includeOrigin} - if cached, ok := c.propertiesTypes[key]; ok { + if cached, ok := c.propertiesTypes.get(c, key); ok { return cached } + c.propertiesTypes.begin(c, key) var origin *Type if includeOrigin && t.objectFlags&(ObjectFlagsClassOrInterface|ObjectFlagsReference) != 0 || t.alias != nil { origin = c.newIndexType(t, IndexFlagsNone) @@ -27932,7 +29715,8 @@ func (c *Checker) getLiteralTypeFromProperties(t *Type, include TypeFlags, inclu } } result := c.getUnionTypeEx(types, UnionReductionLiteral, nil, origin) - c.propertiesTypes[key] = result + c.propertiesTypes.set(c, key, result) + c.endMemo() return result } @@ -27993,30 +29777,40 @@ func isInvalidComputedPropertyName(node *ast.Node) bool { } func (c *Checker) checkComputedPropertyName(node *ast.Node) *Type { + if c.memoActive() { + c.typeNodeLinks.Get(node).resolvedType.begin(c) + result := c.checkComputedPropertyNameUncached(node) + c.endMemo() + return result + } + return c.checkComputedPropertyNameUncached(node) +} + +func (c *Checker) checkComputedPropertyNameUncached(node *ast.Node) *Type { links := c.typeNodeLinks.Get(node) - if links.resolvedType == nil { + if links.resolvedType.get(c) == nil { if isInvalidComputedPropertyName(node) { - links.resolvedType = c.errorType - return links.resolvedType + links.resolvedType.set(c, c.errorType) + return links.resolvedType.get(c) } if !c.pushTypeResolution(node, TypeSystemPropertyNameComputedPropertyName) { return c.errorType } t := c.checkExpression(node.Expression()) c.popTypeResolution() - if links.resolvedType != nil { - return links.resolvedType + if links.resolvedType.get(c) != nil { + return links.resolvedType.get(c) } - links.resolvedType = t + links.resolvedType.set(c, t) // This will allow types number, string, symbol or any. It will also allow enums, the unknown // type, and any union of these types (like string | number). - if links.resolvedType.flags&TypeFlagsNullable != 0 || - !c.isTypeAssignableToKind(links.resolvedType, TypeFlagsStringLike|TypeFlagsNumberLike|TypeFlagsESSymbolLike) && - !c.isTypeAssignableTo(links.resolvedType, c.stringNumberSymbolType) { + if links.resolvedType.get(c).flags&TypeFlagsNullable != 0 || + !c.isTypeAssignableToKind(links.resolvedType.get(c), TypeFlagsStringLike|TypeFlagsNumberLike|TypeFlagsESSymbolLike) && + !c.isTypeAssignableTo(links.resolvedType.get(c), c.stringNumberSymbolType) { c.error(node, diagnostics.A_computed_property_name_must_be_of_type_string_number_symbol_or_any) } } - return links.resolvedType + return links.resolvedType.get(c) } func (c *Checker) isNoInferType(t *Type) bool { @@ -28055,11 +29849,11 @@ func (c *Checker) getIndexTypeForGenericType(t *Type, indexFlags IndexFlags) *Ty kind: core.IfElse(indexFlags&IndexFlagsStringsOnly != 0, CachedTypeKindStringIndexType, CachedTypeKindIndexType), typeId: t.id, } - if indexType := c.cachedTypes[key]; indexType != nil { + if indexType, _ := c.cachedTypes.get(c, key); indexType != nil { return indexType } indexType := c.newIndexType(t, indexFlags&IndexFlagsStringsOnly) - c.cachedTypes[key] = indexType + c.cachedTypes.intern(key, indexType) return indexType } @@ -28160,11 +29954,11 @@ func (c *Checker) getIndexedAccessTypeOrUndefined(objectType *Type, indexType *T // Defer the operation by creating an indexed access type. persistentAccessFlags := accessFlags & AccessFlagsPersistent key := getIndexedAccessKey(objectType, indexType, persistentAccessFlags, alias) - t := c.indexedAccessTypes[key] + t, _ := c.indexedAccessTypes.get(key) if t == nil { t = c.newIndexedAccessType(objectType, indexType, persistentAccessFlags) t.alias = alias - c.indexedAccessTypes[key] = t + c.indexedAccessTypes.intern(key, t) } return t } @@ -28237,7 +30031,7 @@ func (c *Checker) getPropertyTypeForIndexType(originalObjectType *Type, objectTy return nil } if accessFlags&AccessFlagsCacheSymbol != 0 { - c.symbolNodeLinks.Get(accessNode).resolvedSymbol = prop + c.symbolNodeLinks.Get(accessNode).resolvedSymbol.set(c, prop) } if c.isThisPropertyAccessInConstructor(accessExpression, prop) { return c.autoType @@ -28336,7 +30130,7 @@ func (c *Checker) getPropertyTypeForIndexType(originalObjectType *Type, objectTy c.addDiagnostic(createDiagnosticForNode(accessExpression, diagnostics.Property_0_does_not_exist_on_type_1, indexType.AsLiteralType().value, c.TypeToString(objectType))) return c.undefinedType } else if indexType.flags&(TypeFlagsNumber|TypeFlagsString) != 0 { - types := core.Map(objectType.AsStructuredType().getProperties(), func(prop *ast.Symbol) *Type { + types := core.Map(objectType.AsStructuredType().getProperties(c), func(prop *ast.Symbol) *Type { return c.getTypeOfSymbol(prop) }) return c.getUnionType(append(types, c.undefinedType)) @@ -28617,11 +30411,11 @@ func (c *Checker) getSubstitutionType(baseType *Type, constraint *Type) *Type { func (c *Checker) getOrCreateSubstitutionType(baseType *Type, constraint *Type) *Type { key := SubstitutionTypeKey{baseId: baseType.id, constraintId: constraint.id} - if cached := c.substitutionTypes[key]; cached != nil { + if cached, _ := c.substitutionTypes.get(key); cached != nil { return cached } result := c.newSubstitutionType(baseType, constraint) - c.substitutionTypes[key] = result + c.substitutionTypes.intern(key, result) return result } @@ -28645,12 +30439,23 @@ func (c *Checker) getBaseConstraintOfType(t *Type) *Type { } func (c *Checker) getResolvedBaseConstraint(t *Type, stack []RecursionId) *Type { + if constrained := t.AsConstrainedType(); constrained != nil && constrained.resolvedBaseConstraint.peek() != nil { + // A resolved base constraint is only read. + return constrained.resolvedBaseConstraint.get(c) + } + c.beginMemo(memoKeyOf(t, memoBaseConstraint)) + result := c.getResolvedBaseConstraintUncached(t, stack) + c.endMemo() + return result +} + +func (c *Checker) getResolvedBaseConstraintUncached(t *Type, stack []RecursionId) *Type { constrained := t.AsConstrainedType() if constrained == nil { return t } - if constrained.resolvedBaseConstraint != nil { - return constrained.resolvedBaseConstraint + if constrained.resolvedBaseConstraint.get(c) != nil { + return constrained.resolvedBaseConstraint.get(c) } if !c.pushTypeResolution(t, TypeSystemPropertyNameResolvedBaseConstraint) { return c.circularConstraintType @@ -28662,7 +30467,7 @@ func (c *Checker) getResolvedBaseConstraint(t *Type, stack []RecursionId) *Type // levels of nesting, we are presumably exploring a repeating pattern with a long cycle that hasn't // yet triggered the deeply nested limiter. We have no test cases that actually get to 50 levels of // nesting, so it is effectively just a safety stop. - identity := getRecursionIdentity(t) + identity := c.getRecursionIdentity(t) if len(stack) < 10 || len(stack) < 50 && !slices.Contains(stack, identity) { constraint = c.computeBaseConstraint(c.getSimplifiedType(t /*writing*/, false), append(stack, identity)) } @@ -28670,10 +30475,17 @@ func (c *Checker) getResolvedBaseConstraint(t *Type, stack []RecursionId) *Type if t.flags&TypeFlagsTypeParameter != 0 { errorNode := c.getConstraintDeclaration(t) if errorNode != nil { - diagnostic := c.error(errorNode, diagnostics.Type_parameter_0_has_a_circular_constraint, c.TypeToString(t)) - if c.currentNode != nil && !isNodeDescendantOf(errorNode, c.currentNode) && !isNodeDescendantOf(c.currentNode, errorNode) { - diagnostic.AddRelatedInfo(NewDiagnosticForNode(c.currentNode, diagnostics.Circularity_originates_in_type_at_this_location)) - } + typeString := c.TypeToString(t) + // The error is located at the constraint, not the current node, and the + // circularity is intrinsic to the declarations, so it is reported once + // and the circular result is kept. Discarding it would let a later + // computation reuse cycle participants that hide the cycle. + c.reportAtCurrentNodeOnce(func(c *Checker, node *ast.Node) { + diagnostic := c.error(errorNode, diagnostics.Type_parameter_0_has_a_circular_constraint, typeString) + if node != nil && !isNodeDescendantOf(errorNode, node) && !isNodeDescendantOf(node, errorNode) { + diagnostic.AddRelatedInfo(NewDiagnosticForNode(node, diagnostics.Circularity_originates_in_type_at_this_location)) + } + }) } } constraint = c.circularConstraintType @@ -28681,8 +30493,8 @@ func (c *Checker) getResolvedBaseConstraint(t *Type, stack []RecursionId) *Type if constraint == nil { constraint = c.noConstraintType } - if constrained.resolvedBaseConstraint == nil { - constrained.resolvedBaseConstraint = constraint + if constrained.resolvedBaseConstraint.get(c) == nil { + constrained.resolvedBaseConstraint.set(c, constraint) } return constraint } @@ -28760,12 +30572,12 @@ func (c *Checker) computeBaseConstraint(t *Type, stack []RecursionId) *Type { } return c.getNextBaseConstraint(c.getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType, t.AsIndexedAccessType().accessFlags, nil, nil), stack) case t.flags&TypeFlagsConditional != 0: - if c.conditionalConstraintDepth >= 100 { + if c.depthLimitReached(&c.conditionalConstraintDepth, 100) { return nil } - c.conditionalConstraintDepth++ + c.enterDepthLimit(&c.conditionalConstraintDepth) constraint := c.getConstraintFromConditionalType(t) - c.conditionalConstraintDepth-- + c.conditionalConstraintDepth.exit() return c.getNextBaseConstraint(constraint, stack) case t.flags&TypeFlagsSubstitution != 0: return c.getNextBaseConstraint(c.getSubstitutionIntersection(t), stack) @@ -28926,7 +30738,7 @@ func (c *Checker) markPropertyAsReferenced(prop *ast.Symbol, nodeForCheckWriteOn if prop.CheckFlags()&ast.CheckFlagsInstantiated != 0 { target = c.valueSymbolLinks.Get(prop).target } - c.symbolReferenceLinks.Get(target).referenceKinds |= ast.SymbolFlagsAll + c.symbolReferenced(target, ast.SymbolFlagsAll) } func (c *Checker) expandSignatureParametersWithTupleMembers(signature *Signature, restType *TypeReference, restIndex int, restSymbol *ast.Symbol) []*ast.Symbol { @@ -28946,9 +30758,9 @@ func (c *Checker) expandSignatureParametersWithTupleMembers(signature *Signature symbol := c.newSymbolEx(ast.SymbolFlagsFunctionScopedVariable, associatedNames[i], checkFlags) links := c.valueSymbolLinks.Get(symbol) if flags&ElementFlagsRest != 0 { - links.resolvedType = c.createArrayType(t) + links.resolvedType.setIdentity(c, c.createArrayType(t)) } else { - links.resolvedType = t + links.resolvedType.setIdentity(c, t) } expanded = append(expanded, symbol) } @@ -29002,14 +30814,16 @@ func getNameFromIndexInfo(info *IndexInfo) string { func (c *Checker) isUnknownLikeUnionType(t *Type) bool { if c.strictNullChecks && t.flags&TypeFlagsUnion != 0 { - if t.objectFlags&ObjectFlagsIsUnknownLikeUnionComputed == 0 { - t.objectFlags |= ObjectFlagsIsUnknownLikeUnionComputed + if c.objectFlagsKnown(t, ObjectFlagsIsUnknownLikeUnionComputed) { + return t.objectFlags&ObjectFlagsIsUnknownLikeUnion != 0 + } + return c.objectFlagsCell(t, memoUnknownLikeUnion, ObjectFlagsIsUnknownLikeUnionComputed, ObjectFlagsIsUnknownLikeUnion, true /*inProgress*/, func() ObjectFlags { types := t.Types() if len(types) >= 3 && types[0].flags&TypeFlagsUndefined != 0 && types[1].flags&TypeFlagsNull != 0 && core.Some(types, c.IsEmptyAnonymousObjectType) { - t.objectFlags |= ObjectFlagsIsUnknownLikeUnion + return ObjectFlagsIsUnknownLikeUnion } - } - return t.objectFlags&ObjectFlagsIsUnknownLikeUnion != 0 + return 0 + }) != 0 } return false } @@ -29124,16 +30938,24 @@ func (c *Checker) getSimplifiedIndexType(t *Type) *Type { // the target of an assignment. func (c *Checker) getSimplifiedIndexedAccessType(t *Type, writing bool) *Type { key := CachedTypeKey{kind: core.IfElse(writing, CachedTypeKindIndexedAccessForWriting, CachedTypeKindIndexedAccessForReading), typeId: t.id} - if cached := c.cachedTypes[key]; cached != nil { - return core.IfElse(cached == c.circularConstraintType, t, cached) + if cached, _ := c.cachedTypes.get(c, key); cached != nil { + return cached + } + propertyName := core.IfElse(writing, TypeSystemPropertyNameSimplifiedForWriting, TypeSystemPropertyNameSimplifiedForReading) + if c.isResolvingAnywhere(t, propertyName) { + // Resolve circular references to t itself while simplifying. + return t } - c.cachedTypes[key] = t + c.cachedTypes.begin(c, key) + c.beginTypeResolution(t, propertyName) result := c.getSimplifiedIndexedAccessTypeWorker(t, writing) if result != t { // If the simplification is a union type that includes t, remove t from the type. result = c.removeType(result, t) - c.cachedTypes[key] = result } + c.cachedTypes.set(c, key, result) + c.popTypeResolution() + c.endMemo() return result } @@ -29215,11 +31037,13 @@ func (c *Checker) distributeIndexOverObjectType(objectType *Type, indexType *Typ func (c *Checker) getSimplifiedConditionalType(t *Type, writing bool) *Type { key := CachedTypeKey{kind: core.IfElse(writing, CachedTypeKindSimplifiedConditionalTypeForWriting, CachedTypeKindSimplifiedConditionalTypeForReading), typeId: t.id} - if cached, ok := c.cachedTypes[key]; ok { + if cached, ok := c.cachedTypes.get(c, key); ok { return cached } + c.cachedTypes.begin(c, key) result := c.getSimplifiedConditionalTypeWorker(t, writing) - c.cachedTypes[key] = result + c.cachedTypes.set(c, key, result) + c.endMemo() return result } @@ -29309,10 +31133,17 @@ func (c *Checker) getSingleBaseForNonAugmentingSubtype(t *Type) *Type { return nil } key := CachedTypeKey{kind: CachedTypeKindEquivalentBaseType, typeId: t.id} - if t.objectFlags&ObjectFlagsIdenticalBaseTypeCalculated != 0 { - return c.cachedTypes[key] + if cached, ok := c.cachedTypes.get(c, key); ok { + return cached } - t.objectFlags |= ObjectFlagsIdenticalBaseTypeCalculated + c.cachedTypes.begin(c, key) + result := c.getSingleBaseForNonAugmentingSubtypeUncached(t) + c.cachedTypes.set(c, key, result) + c.endMemo() + return result +} + +func (c *Checker) getSingleBaseForNonAugmentingSubtypeUncached(t *Type) *Type { target := t.Target() if target.objectFlags&ObjectFlagsClass != 0 { baseTypeNode := getBaseTypeNodeOfClass(target) @@ -29340,36 +31171,32 @@ func (c *Checker) getSingleBaseForNonAugmentingSubtype(t *Type) *Type { if len(c.getTypeArguments(t)) > len(typeParameters) { instantiatedBase = c.getTypeWithThisArgument(instantiatedBase, core.LastOrNil(c.getTypeArguments(t)), false) } - c.cachedTypes[key] = instantiatedBase return instantiatedBase } func (c *Checker) getModifiersTypeFromMappedType(t *Type) *Type { m := t.AsMappedType() - if m.modifiersType == nil { + return c.memoCompute(&m.modifiersType, func() *Type { if c.isMappedTypeWithKeyofConstraintDeclaration(t) { // If the constraint declaration is a 'keyof T' node, the modifiers type is T. We check // AST nodes here because, when T is a non-generic type, the logic below eagerly resolves // 'keyof T' to a literal union type and we can't recover T from that type. - m.modifiersType = c.instantiateType(c.getTypeFromTypeNode(c.getConstraintDeclarationForMappedType(t).Type()), m.mapper) - } else { - // Otherwise, get the declared constraint type, and if the constraint type is a type parameter, - // get the constraint of that type parameter. If the resulting type is an indexed type 'keyof T', - // the modifiers type is T. Otherwise, the modifiers type is unknown. - declaredType := c.getTypeFromMappedTypeNode(m.declaration.AsNode()) - constraint := c.getConstraintTypeFromMappedType(declaredType) - extendedConstraint := constraint - if constraint != nil && constraint.flags&TypeFlagsTypeParameter != 0 { - extendedConstraint = c.getConstraintOfTypeParameter(getNonDistributedTypeParameter(constraint)) - } - if extendedConstraint != nil && extendedConstraint.flags&TypeFlagsIndex != 0 { - m.modifiersType = c.instantiateType(extendedConstraint.AsIndexType().target, m.mapper) - } else { - m.modifiersType = c.unknownType - } + return c.instantiateType(c.getTypeFromTypeNode(c.getConstraintDeclarationForMappedType(t).Type()), m.mapper) } - } - return m.modifiersType + // Otherwise, get the declared constraint type, and if the constraint type is a type parameter, + // get the constraint of that type parameter. If the resulting type is an indexed type 'keyof T', + // the modifiers type is T. Otherwise, the modifiers type is unknown. + declaredType := memoTypeNode(c, m.declaration.AsNode(), (*Checker).getTypeFromMappedTypeNode) + constraint := c.getConstraintTypeFromMappedType(declaredType) + extendedConstraint := constraint + if constraint != nil && constraint.flags&TypeFlagsTypeParameter != 0 { + extendedConstraint = c.getConstraintOfTypeParameter(getNonDistributedTypeParameter(constraint)) + } + if extendedConstraint != nil && extendedConstraint.flags&TypeFlagsIndex != 0 { + return c.instantiateType(extendedConstraint.AsIndexType().target, m.mapper) + } + return c.unknownType + }) } func (c *Checker) extractTypesOfKind(t *Type, kind TypeFlags) *Type { @@ -29381,9 +31208,10 @@ func (c *Checker) getRegularTypeOfObjectLiteral(t *Type) *Type { return t } key := CachedTypeKey{kind: CachedTypeKindRegularObjectLiteral, typeId: t.id} - if cached := c.cachedTypes[key]; cached != nil { + if cached, _ := c.cachedTypes.get(c, key); cached != nil { return cached } + c.cachedTypes.begin(c, key) resolved := c.resolveStructuredTypeMembers(t) members := c.transformTypeOfMembers(t, c.getRegularTypeOfObjectLiteral) create := func() *Type { @@ -29398,7 +31226,8 @@ func (c *Checker) getRegularTypeOfObjectLiteral(t *Type) *Type { } else { regular = create() } - c.cachedTypes[key] = regular + c.cachedTypes.set(c, key, regular) + c.endMemo() return regular } @@ -29805,6 +31634,45 @@ func (c *Checker) checkExternalEmitHelpers(location *ast.Node, helpers ExternalE if !ast.IsEffectiveExternalModule(sourceFile, c.compilerOptions) || location.Flags&ast.NodeFlagsAmbient != 0 { return } + if !c.checking { + c.recordEffect(func(c *Checker) { c.checkExternalEmitHelpers(location, helpers) }) + return + } + links := c.sourceFileLinks.Get(sourceFile) + if links.typeChecked { + c.checkExternalEmitHelpersAt(sourceFile, location, helpers) + return + } + links.externalHelperRequests = append(links.externalHelperRequests, externalHelperRequest{location, helpers}) +} + +type externalHelperRequest struct { + location *ast.Node + helpers ExternalEmitHelpers +} + +// checkExternalHelperRequests checks the helpers requested by checking +// sourceFile. Errors are reported once per file, at the first location +// requesting a helper in the file, rather than the first location checked, +// since the order in which checking requests helpers depends on what was +// computed before. +func (c *Checker) checkExternalHelperRequests(sourceFile *ast.SourceFile) { + links := c.sourceFileLinks.Get(sourceFile) + requests := links.externalHelperRequests + links.externalHelperRequests = nil + slices.SortStableFunc(requests, func(a, b externalHelperRequest) int { + return cmp.Or( + cmp.Compare(a.location.Pos(), b.location.Pos()), + cmp.Compare(b.location.End(), a.location.End()), + cmp.Compare(a.location.Kind, b.location.Kind), + ) + }) + for _, r := range requests { + c.checkExternalEmitHelpersAt(sourceFile, r.location, r.helpers) + } +} + +func (c *Checker) checkExternalEmitHelpersAt(sourceFile *ast.SourceFile, location *ast.Node, helpers ExternalEmitHelpers) { helpersModule := c.resolveHelpersModule(sourceFile, location) if helpersModule == c.unknownSymbol { return @@ -30050,8 +31918,23 @@ func (c *Checker) markAliasReferenced(symbol *ast.Symbol, location *ast.Node) { // we reach a non-alias or an exported entity (which is always considered referenced). We do this by checking the target of // the alias as an expression (which recursively takes us back here if the target references another alias). func (c *Checker) markAliasSymbolAsReferenced(symbol *ast.Symbol) { + if !c.checking { + c.recordReferenceMark(symbol) + return + } + c.referenceAliasSymbol(symbol) +} + +func (c *Checker) referenceAliasSymbol(symbol *ast.Symbol) { links := c.aliasSymbolLinks.Get(symbol) - if !links.referenced { + // Emit marks references too (see withReferenceMarks), but reports + // nothing, so checking must still follow them once to report errors. + followed := links.referenced + if c.checking { + followed = links.referenceChecked + links.referenceChecked = true + } + if !followed { links.referenced = true node := c.getDeclarationOfAliasSymbol(symbol) if node == nil { @@ -30163,13 +32046,29 @@ func (c *Checker) getPromisedTypeOfPromiseEx(t *Type, errorNode *ast.Node, thisT return nil } key := CachedTypeKey{kind: CachedTypeKindPromisedTypeOfPromise, typeId: t.id} - if cached := c.cachedTypes[key]; cached != nil { + if cached, _ := c.cachedTypes.get(c, key); cached != nil { return cached } + if errorNode != nil { + // Errors belong to errorNode; see getIterationTypesOfIterable. Only + // a missing result reports any, so a result is shared all the same. + if result := c.getPromisedTypeOfPromiseEx(t, nil /*errorNode*/, thisTypeForErrorOut); result != nil { + return result + } + return c.getPromisedTypeOfPromiseWorker(t, errorNode, thisTypeForErrorOut) + } + c.cachedTypes.begin(c, key) + defer c.endMemo() + result := c.getPromisedTypeOfPromiseWorker(t, nil /*errorNode*/, thisTypeForErrorOut) + if result != nil { + c.cachedTypes.set(c, key, result) + } + return result +} + +func (c *Checker) getPromisedTypeOfPromiseWorker(t *Type, errorNode *ast.Node, thisTypeForErrorOut **Type) *Type { if c.isReferenceToType(t, c.getGlobalPromiseType()) { - result := c.getTypeArguments(t)[0] - c.cachedTypes[key] = result - return result + return c.getTypeArguments(t)[0] } // primitives with a `{ then() }` won't be unwrapped/adopted. if c.allTypesAssignableToKind(c.getBaseConstraintOrType(t), TypeFlagsPrimitive|TypeFlagsNever) { @@ -30221,9 +32120,7 @@ func (c *Checker) getPromisedTypeOfPromiseEx(t *Type, errorNode *ast.Node, thisT } return nil } - result := c.getUnionTypeEx(core.Map(onfulfilledParameterSignatures, c.getTypeOfFirstParameterOfSignature), UnionReductionSubtype, nil, nil) - c.cachedTypes[key] = result - return result + return c.getUnionTypeEx(core.Map(onfulfilledParameterSignatures, c.getTypeOfFirstParameterOfSignature), UnionReductionSubtype, nil, nil) } func (c *Checker) getTypeOfFirstParameterOfSignature(signature *Signature) *Type { @@ -30430,7 +32327,7 @@ func (c *Checker) getTemplateLiteralType(texts []string, types []*Type) *Type { } if !addSpans(texts, types) { if tooLarge { - c.error(c.currentNode, diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite) + c.errorAtCurrentNode(diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite) return c.errorType } return c.stringType @@ -30449,10 +32346,10 @@ func (c *Checker) getTemplateLiteralType(texts []string, types []*Type) *Type { } } key := getTemplateTypeKey(newTexts, newTypes) - t := c.templateLiteralTypes[key] + t, _ := c.templateLiteralTypes.get(key) if t == nil { t = c.newTemplateLiteralType(newTexts, newTypes) - c.templateLiteralTypes[key] = t + c.templateLiteralTypes.intern(key, t) } return t } @@ -30522,10 +32419,10 @@ func (c *Checker) applyTemplateStringMapping(symbol *ast.Symbol, texts []string, func (c *Checker) getStringMappingTypeForGenericType(symbol *ast.Symbol, t *Type) *Type { key := StringMappingKey{s: symbol, t: t} - result := c.stringMappingTypes[key] + result, _ := c.stringMappingTypes.get(key) if result == nil { result = c.newStringMappingType(symbol, t) - c.stringMappingTypes[key] = result + c.stringMappingTypes.intern(key, result) } return result } @@ -30715,8 +32612,7 @@ func (c *Checker) getContextuallyTypedParameterType(parameter *ast.Node) *Type { return c.getSpreadArgumentType(args, indexOfParameter, len(args), c.anyType, nil /*context*/, CheckModeNormal) } links := c.signatureLinks.Get(iife) - cached := links.resolvedSignature - links.resolvedSignature = c.anySignature + cached := links.resolvedSignature.swap(c.anySignature) var t *Type switch { case indexOfParameter < len(args): @@ -30726,7 +32622,7 @@ func (c *Checker) getContextuallyTypedParameterType(parameter *ast.Node) *Type { default: t = c.undefinedWideningType } - links.resolvedSignature = cached + links.resolvedSignature.swap(cached) return t } contextualSignature := c.getContextualSignature(fn) @@ -31031,7 +32927,7 @@ func (c *Checker) getContextualTypeForArgumentAtIndex(callTarget *ast.Node, argI // If we're already in the process of resolving the given signature, don't resolve again as // that could cause infinite recursion. Instead, return anySignature. var signature *Signature - if c.signatureLinks.Get(callTarget).resolvedSignature == c.resolvingSignature { + if links := c.signatureLinks.Get(callTarget); links.resolvedSignature.get(c) == c.resolvingSignature && !c.isSignatureResolutionHidden(links) { signature = c.resolvingSignature } else { signature = c.getResolvedSignature(callTarget, nil, CheckModeNormal) @@ -31076,7 +32972,7 @@ func (c *Checker) getContextualTypeForBinaryOperand(node *ast.Node, contextFlags // by the type of the left operand, except for the special case of Javascript declarations of the form // `namespace.prop = namespace.prop || {}`. t := c.getContextualType(binary.AsNode(), contextFlags) - if node == binary.Right && (t == nil || c.patternForType[t] != nil) { + if node == binary.Right && (t == nil || c.patternForType.has(t)) { return c.getTypeOfExpression(binary.Left) } return t @@ -31410,15 +33306,16 @@ func (c *Checker) getDecoratorCallSignature(decorator *ast.Node) *Signature { func (c *Checker) getLegacyDecoratorCallSignature(decorator *ast.Node) *Signature { node := decorator.Parent links := c.signatureLinks.Get(node) - if links.decoratorSignature == nil { - links.decoratorSignature = c.anySignature + if links.decoratorSignature.get(c) == nil { + links.decoratorSignature.begin(c) + links.decoratorSignature.set(c, c.anySignature) switch node.Kind { case ast.KindClassDeclaration, ast.KindClassExpression: // For a class decorator, the `target` is the type of the class (e.g. the // "static" or "constructor" side of the class). targetType := c.getTypeOfSymbol(c.getSymbolOfDeclaration(node)) targetParam := c.newParameter("target", targetType) - links.decoratorSignature = c.newCallSignature(nil, nil, []*ast.Symbol{targetParam}, c.getUnionType([]*Type{targetType, c.voidType})) + links.decoratorSignature.set(c, c.newCallSignature(nil, nil, []*ast.Symbol{targetParam}, c.getUnionType([]*Type{targetType, c.voidType}))) case ast.KindParameter: if !ast.IsConstructorDeclaration(node.Parent) && !(ast.IsMethodDeclaration(node.Parent) || ast.IsSetAccessorDeclaration(node.Parent) && ast.IsClassLike(node.Parent.Parent)) { break @@ -31443,7 +33340,7 @@ func (c *Checker) getLegacyDecoratorCallSignature(decorator *ast.Node) *Signatur targetParam := c.newParameter("target", targetType) keyParam := c.newParameter("propertyKey", keyType) indexParam := c.newParameter("parameterIndex", indexType) - links.decoratorSignature = c.newCallSignature(nil, nil, []*ast.Symbol{targetParam, keyParam, indexParam}, c.voidType) + links.decoratorSignature.set(c, c.newCallSignature(nil, nil, []*ast.Symbol{targetParam, keyParam, indexParam}, c.voidType)) case ast.KindMethodDeclaration, ast.KindGetAccessor, ast.KindSetAccessor, ast.KindPropertyDeclaration: if !ast.IsClassLike(node.Parent) { break @@ -31462,16 +33359,17 @@ func (c *Checker) getLegacyDecoratorCallSignature(decorator *ast.Node) *Signatur if hasPropDesc { descriptorType := c.newTypedPropertyDescriptorType(c.getTypeOfNode(node)) descriptorParam := c.newParameter("descriptor", descriptorType) - links.decoratorSignature = c.newCallSignature(nil, nil, []*ast.Symbol{targetParam, keyParam, descriptorParam}, c.getUnionType([]*Type{returnType, c.voidType})) + links.decoratorSignature.set(c, c.newCallSignature(nil, nil, []*ast.Symbol{targetParam, keyParam, descriptorParam}, c.getUnionType([]*Type{returnType, c.voidType}))) } else { - links.decoratorSignature = c.newCallSignature(nil, nil, []*ast.Symbol{targetParam, keyParam}, c.getUnionType([]*Type{returnType, c.voidType})) + links.decoratorSignature.set(c, c.newCallSignature(nil, nil, []*ast.Symbol{targetParam, keyParam}, c.getUnionType([]*Type{returnType, c.voidType}))) } } + c.endMemo() } - if links.decoratorSignature == c.anySignature { + if links.decoratorSignature.get(c) == c.anySignature { return nil } - return links.decoratorSignature + return links.decoratorSignature.get(c) } func (c *Checker) getESDecoratorCallSignature(decorator *ast.Node) *Signature { @@ -31557,15 +33455,16 @@ func (c *Checker) getESDecoratorCallSignature(decorator *ast.Node) *Signature { // `undefined` then the "output type" is the "input type"). node := decorator.Parent links := c.signatureLinks.Get(node) - if links.decoratorSignature == nil { - links.decoratorSignature = c.anySignature + if links.decoratorSignature.get(c) == nil { + links.decoratorSignature.begin(c) + links.decoratorSignature.set(c, c.anySignature) switch node.Kind { case ast.KindClassDeclaration, ast.KindClassExpression: // Class decorators have a `context` of `ClassDecoratorContext`, where the `Class` type // argument will be the "final type" of the class after all decorators are applied. targetType := c.getTypeOfSymbol(c.getSymbolOfDeclaration(node)) contextType := c.newClassDecoratorContextType(targetType) - links.decoratorSignature = c.newESDecoratorCallSignature(targetType, contextType, targetType) + links.decoratorSignature.set(c, c.newESDecoratorCallSignature(targetType, contextType, targetType)) case ast.KindMethodDeclaration, ast.KindGetAccessor, ast.KindSetAccessor: if !ast.IsClassLike(node.Parent) { break @@ -31606,7 +33505,7 @@ func (c *Checker) getESDecoratorCallSignature(decorator *ast.Node) *Signature { targetType = valueType } contextType := c.newClassMemberDecoratorContextTypeForNode(node, thisType, valueType) - links.decoratorSignature = c.newESDecoratorCallSignature(targetType, contextType, targetType) + links.decoratorSignature.set(c, c.newESDecoratorCallSignature(targetType, contextType, targetType)) case ast.KindPropertyDeclaration: if !ast.IsClassLike(node.Parent) { break @@ -31643,13 +33542,14 @@ func (c *Checker) getESDecoratorCallSignature(decorator *ast.Node) *Signature { returnType = c.newClassFieldDecoratorInitializerMutatorType(thisType, valueType) } contextType := c.newClassMemberDecoratorContextTypeForNode(node, thisType, valueType) - links.decoratorSignature = c.newESDecoratorCallSignature(targetType, contextType, returnType) + links.decoratorSignature.set(c, c.newESDecoratorCallSignature(targetType, contextType, returnType)) } + c.endMemo() } - if links.decoratorSignature == c.anySignature { + if links.decoratorSignature.get(c) == c.anySignature { return nil } - return links.decoratorSignature + return links.decoratorSignature.get(c) } func (c *Checker) newClassDecoratorContextType(classType *Type) *Type { @@ -31685,7 +33585,7 @@ func (c *Checker) getClassMemberDecoratorContextOverrideType(nameType *Type, isP core.IfElse(isStatic, CachedTypeKindDecoratorContextStatic, CachedTypeKindDecoratorContext), ) key := CachedTypeKey{kind: kind, typeId: nameType.id} - if overrideType := c.cachedTypes[key]; overrideType != nil { + if overrideType, _ := c.cachedTypes.get(c, key); overrideType != nil { return overrideType } members := ast.NewSymbolTable() @@ -31693,7 +33593,7 @@ func (c *Checker) getClassMemberDecoratorContextOverrideType(nameType *Type, isP members.Set("private", c.newProperty("private", core.IfElse(isPrivate, c.trueType, c.falseType))) members.Set("static", c.newProperty("static", core.IfElse(isStatic, c.trueType, c.falseType))) overrideType := c.newAnonymousType(nil, members, nil, nil, nil) - c.cachedTypes[key] = overrideType + c.cachedTypes.intern(key, overrideType) return overrideType } @@ -31859,7 +33759,7 @@ func (c *Checker) getIndexedMappedTypeSubstitutedTypeOfContextualType(t *Type, n } constraint := c.getConstraintTypeFromMappedType(t) // special case for conditional types pretending to be negated types - if t.AsMappedType().nameType != nil && c.isExcludedMappedPropertyName(t.AsMappedType().nameType, propertyNameType) || c.isExcludedMappedPropertyName(constraint, propertyNameType) { + if mappedNameType := c.getNameTypeFromMappedType(t); mappedNameType != nil && c.isExcludedMappedPropertyName(mappedNameType, propertyNameType) || c.isExcludedMappedPropertyName(constraint, propertyNameType) { return nil } constraintOfConstraint := c.getBaseConstraintOrType(constraint) @@ -31911,7 +33811,7 @@ func (c *Checker) getTypeFromIndexInfosOfContextualType(t *Type, name string, na func (c *Checker) isCircularMappedProperty(symbol *ast.Symbol) bool { if symbol.CheckFlags()&ast.CheckFlagsMapped != 0 { links := c.valueSymbolLinks.Get(symbol) - return links.resolvedType == nil && c.findResolutionCycleStartIndex(symbol, TypeSystemPropertyNameType) >= 0 + return links.resolvedType.get(c) == nil && c.isResolutionInProgress(symbol, TypeSystemPropertyNameType) } return false } @@ -32002,9 +33902,10 @@ func (d *ObjectLiteralDiscriminator) matches(index int, t *Type) bool { func (c *Checker) discriminateContextualTypeByObjectMembers(node *ast.Node, contextualType *Type) *Type { key := DiscriminatedContextualTypeKey{nodeId: ast.GetNodeId(node), typeId: contextualType.id} - if discriminated := c.discriminatedContextualTypes[key]; discriminated != nil { + if discriminated, _ := c.discriminatedContextualTypes.get(c, key); discriminated != nil { return discriminated } + c.discriminatedContextualTypes.begin(c, key) discriminated := c.getMatchingUnionConstituentForObjectLiteral(contextualType, node) if discriminated == nil { discriminantProperties := core.Filter(node.Properties(), func(p *ast.Node) bool { @@ -32026,7 +33927,8 @@ func (c *Checker) discriminateContextualTypeByObjectMembers(node *ast.Node, cont discriminator := &ObjectLiteralDiscriminator{c: c, props: discriminantProperties, members: discriminantMembers} discriminated = c.discriminateTypeByDiscriminableItems(contextualType, discriminator) } - c.discriminatedContextualTypes[key] = discriminated + c.discriminatedContextualTypes.set(c, key, discriminated) + c.endMemo() return discriminated } @@ -32392,7 +34294,7 @@ func (c *Checker) isFunctionObjectType(t *Type) bool { // We do a quick check for a "bind" property before performing the more expensive subtype // check. This gives us a quicker out in the common case where an object type is not a function. resolved := c.resolveStructuredTypeMembers(t) - return len(resolved.getSignatures()) != 0 || resolved.getMember("bind") != nil && c.isTypeSubtypeOf(t, c.globalFunctionType) + return len(resolved.getSignatures()) != 0 || resolved.getMember(c, "bind") != nil && c.isTypeSubtypeOf(t, c.globalFunctionType) } func (c *Checker) getTypeWithFacts(t *Type, include TypeFacts) *Type { @@ -32525,32 +34427,53 @@ func (c *Checker) getAwaitedTypeNoAliasEx(t *Type, errorNode *ast.Node, diagnost } // If we've already cached an awaited type, return a possible `Awaited` for it. key := CachedTypeKey{kind: CachedTypeKindAwaitedType, typeId: t.id} - if awaitedType := c.cachedTypes[key]; awaitedType != nil { + if awaitedType, _ := c.cachedTypes.get(c, key); awaitedType != nil { return awaitedType } + if errorNode != nil { + // Errors belong to errorNode; see getIterationTypesOfIterable. Only a + // result computing which reports nothing is cached, so it is shared + // all the same. + if c.getAwaitedTypeNoAliasEx(t, nil /*errorNode*/, nil /*diagnosticMessage*/) != nil { + if awaitedType, _ := c.cachedTypes.get(c, key); awaitedType != nil { + return awaitedType + } + } + return c.getAwaitedTypeNoAliasWorker(t, errorNode, diagnosticMessage, args...) + } + c.cachedTypes.begin(c, key) + defer c.endMemo() + errors := c.awaitedTypeErrors + awaitedType := c.getAwaitedTypeNoAliasWorker(t, nil /*errorNode*/, nil /*diagnosticMessage*/) + if awaitedType != nil && c.awaitedTypeErrors == errors { + c.cachedTypes.set(c, key, awaitedType) + } + return awaitedType +} + +func (c *Checker) getAwaitedTypeNoAliasWorker(t *Type, errorNode *ast.Node, diagnosticMessage *diagnostics.Message, args ...any) *Type { // For a union, get a union of the awaited types of each constituent. if t.flags&TypeFlagsUnion != 0 { - if slices.Contains(c.awaitedTypeStack, t) { + if c.isAwaitingType(t) { + c.awaitedTypeErrors++ if errorNode != nil { c.error(errorNode, diagnostics.Type_is_referenced_directly_or_indirectly_in_the_fulfillment_callback_of_its_own_then_method) } return nil } - c.awaitedTypeStack = append(c.awaitedTypeStack, t) + c.pushAwaitingType(t) mapped := c.mapType(t, func(t *Type) *Type { return c.getAwaitedTypeNoAliasEx(t, errorNode, diagnosticMessage, args...) }) - c.awaitedTypeStack = c.awaitedTypeStack[:len(c.awaitedTypeStack)-1] - c.cachedTypes[key] = mapped + c.popTypeResolution() return mapped } // If `type` is generic and should be wrapped in `Awaited`, return it. if c.isAwaitedTypeNeeded(t) { - c.cachedTypes[key] = t return t } var thisTypeForError *Type promisedType := c.getPromisedTypeOfPromiseEx(t, nil /*errorNode*/, &thisTypeForError) if promisedType != nil { - if t == promisedType || slices.Contains(c.awaitedTypeStack, promisedType) { + if t == promisedType || c.isAwaitingType(promisedType) { // Verify that we don't have a bad actor in the form of a promise whose // promised type is the same as the promise type, or a mutually recursive // promise. If so, we return undefined as we cannot guess the shape. If this @@ -32584,6 +34507,7 @@ func (c *Checker) getAwaitedTypeNoAliasEx(t *Type, errorNode *ast.Node, diagnost // onrejected: (error: any) => any): BadPromiseA; // } // + c.awaitedTypeErrors++ if errorNode != nil { c.error(errorNode, diagnostics.Type_is_referenced_directly_or_indirectly_in_the_fulfillment_callback_of_its_own_then_method) } @@ -32591,13 +34515,9 @@ func (c *Checker) getAwaitedTypeNoAliasEx(t *Type, errorNode *ast.Node, diagnost } // Keep track of the type we're about to unwrap to avoid bad recursive promise types. // See the comments above for more information. - c.awaitedTypeStack = append(c.awaitedTypeStack, t) + c.pushAwaitingType(t) awaitedType := c.getAwaitedTypeNoAliasEx(promisedType, errorNode, diagnosticMessage, args...) - c.awaitedTypeStack = c.awaitedTypeStack[:len(c.awaitedTypeStack)-1] - if awaitedType == nil { - return nil - } - c.cachedTypes[key] = awaitedType + c.popTypeResolution() return awaitedType } // The type was not a promise, so it could not be unwrapped any further. @@ -32616,6 +34536,7 @@ func (c *Checker) getAwaitedTypeNoAliasEx(t *Type, errorNode *ast.Node, diagnost // function, they would need to wrap it in some other value. If they want it to // be treated as a promise, they can cast to . if c.isThenableType(t) { + c.awaitedTypeErrors++ if errorNode != nil { var diagnostic *ast.Diagnostic if thisTypeForError != nil { @@ -32625,10 +34546,21 @@ func (c *Checker) getAwaitedTypeNoAliasEx(t *Type, errorNode *ast.Node, diagnost } return nil } - c.cachedTypes[key] = t return t } +// pushAwaitingType records that the awaited type of t is being computed, until +// the matching popTypeResolution. +func (c *Checker) pushAwaitingType(t *Type) { + c.beginTypeResolution(t, TypeSystemPropertyNameAwaitedType) +} + +// isAwaitingType reports whether the awaited type of t is being computed; +// see isResolvingAnywhere. +func (c *Checker) isAwaitingType(t *Type) bool { + return c.isResolvingAnywhere(t, TypeSystemPropertyNameAwaitedType) +} + func (c *Checker) isAwaitedTypeInstantiation(t *Type) bool { if t.flags&TypeFlagsConditional != 0 { awaitedSymbol := c.getGlobalAwaitedSymbolOrNil() @@ -32888,7 +34820,7 @@ func (c *Checker) getSymbolAtLocation(node *ast.Node, ignoreErrors bool) *ast.Sy // member should more exactly be the kind of (declarationless) symbol we want. // (See #44364 and #45031 for relevant implementation PRs) if metaProp.KeywordToken == ast.KindImportKeyword && node.Text() == "meta" { - return c.getGlobalImportMetaExpressionType().AsObjectType().getMember("meta") + return c.getGlobalImportMetaExpressionType().AsObjectType().getMember(c, "meta") } // no other meta properties are valid syntax, thus no others should have symbols return nil @@ -32922,7 +34854,7 @@ func (c *Checker) getSymbolAtLocation(node *ast.Node, ignoreErrors bool) *ast.Sy } fallthrough case ast.KindThisType: - return c.getTypeFromThisTypeNode(node).symbol + return memoTypeNode(c, node, (*Checker).getTypeFromThisTypeNode).symbol case ast.KindSuperKeyword: return c.checkExpression(node).symbol case ast.KindConstructorKeyword: @@ -33121,12 +35053,16 @@ func (c *Checker) getSymbolOfNameOrPropertyAccessExpression(name *ast.Node) *ast return c.getSymbolForPrivateIdentifierExpression(name) } else if ast.IsPropertyAccessExpression(name) || ast.IsQualifiedName(name) { links := c.symbolNodeLinks.Get(name) - if links.resolvedSymbol != nil { - return links.resolvedSymbol + if links.resolvedSymbol.get(c) != nil { + return links.resolvedSymbol.get(c) } + // The symbol is read back from links after checking, before a query + // finishing would discard it. + query, ok := c.enterQuery() + defer c.exitQuery(query, ok) if ast.IsPropertyAccessExpression(name) { c.checkPropertyAccessExpression(name, CheckModeNormal, false /*writeOnly*/) - if links.resolvedSymbol == nil && !ast.IsPrivateIdentifier(name.Name()) { + if links.resolvedSymbol.get(c) == nil && !ast.IsPrivateIdentifier(name.Name()) { // Not cached in links, where checking would observe it as the // property the access resolved to. return c.getApplicableIndexSymbol( @@ -33137,10 +35073,10 @@ func (c *Checker) getSymbolOfNameOrPropertyAccessExpression(name *ast.Node) *ast } else { c.checkQualifiedName(name, CheckModeNormal) } - if links.resolvedSymbol == nil && isJSDoc && ast.IsQualifiedName(name) { + if links.resolvedSymbol.get(c) == nil && isJSDoc && ast.IsQualifiedName(name) { return c.resolveJSDocMemberName(name) } - return links.resolvedSymbol + return links.resolvedSymbol.get(c) } } else if ast.IsEntityName(name) && isTypeReferenceIdentifier(name) { meaning := core.IfElse(name.Parent.Kind == ast.KindTypeReference, ast.SymbolFlagsType, ast.SymbolFlagsNamespace) @@ -33357,7 +35293,7 @@ func (c *Checker) getApplicableIndexSymbol(t *Type, keyType *Type) *ast.Symbol { symbol.SetValueDeclaration(declarations[0]) symbol.SetParent(t.symbol) links := c.valueSymbolLinks.Get(symbol) - links.resolvedType = info.valueType + links.resolvedType.setIdentity(c, info.valueType) info.indexSymbol = symbol } } @@ -33378,9 +35314,10 @@ func (c *Checker) containsArgumentsReference(node *ast.Node) bool { return false } - if containsArguments, ok := c.cachedArgumentsReferenced[node]; ok { + if containsArguments, ok := c.cachedArgumentsReferenced.get(c, node); ok { return containsArguments } + c.cachedArgumentsReferenced.begin(c, node) var visit func(node *ast.Node) bool visit = func(node *ast.Node) bool { @@ -33406,7 +35343,8 @@ func (c *Checker) containsArgumentsReference(node *ast.Node) bool { } containsArguments := visit(node.Body()) - c.cachedArgumentsReferenced[node] = containsArguments + c.cachedArgumentsReferenced.set(c, node, containsArguments) + c.endMemo() return containsArguments } diff --git a/tsc/internal/checker/checker_test.go b/tsc/internal/checker/checker_test.go index 42ae368d35924..53c48fc111d6a 100644 --- a/tsc/internal/checker/checker_test.go +++ b/tsc/internal/checker/checker_test.go @@ -2,7 +2,11 @@ package checker_test import ( "fmt" + goast "go/ast" + "go/parser" + "go/token" "path/filepath" + "slices" "strings" "testing" @@ -177,6 +181,46 @@ func BenchmarkCheckFreshProgram(b *testing.B) { } } +func TestQueriesDoNotMoveErrorsAtCurrentNode(t *testing.T) { + t.Parallel() + + // Asking for the symbol of t.x computes T<"x"> at no node, reporting + // an excessive depth there. Asking for the type of tx then reuses it at + // t.x, so checking reports the error at t.x and not at tx. + content := `type T = { + x: T[K]; + y: number; +}; + +declare let t: T<"x">; +let tx = t.x; +` + diagnostics := func(query bool) []string { + fs := bundled.WrapFS(vfstest.FromMap(map[string]string{ + "/foo.ts": content, + "/tsconfig.json": `{"files": ["foo.ts"]}`, + }, tspath.CaseInsensitive)) + host := compiler.NewCompilerHost(fs, bundled.LibPath(), nil, nil, nil) + parsed, errors := tsoptions.GetParsedCommandLineOfConfigFile("/tsconfig.json", &core.CompilerOptions{}, nil, tsoptions.NewDefaultParseConfigHost(fs), nil) + assert.Equal(t, len(errors), 0) + p := compiler.NewProgram(compiler.ProgramOptions{Config: parsed, Host: host}) + file := p.GetSourceFile("/foo.ts") + if query { + c, done := p.GetTypeCheckerForFile(t.Context(), file) + decl := file.Statements.Nodes[2].AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes[0] + c.GetSymbolAtLocation(decl.Initializer().Name()) + c.GetTypeAtLocation(decl.Name()) + done() + } + var result []string + for _, d := range p.GetSemanticDiagnostics(t.Context(), file) { + result = append(result, fmt.Sprintf("%d: TS%d", d.Pos(), d.Code())) + } + return result + } + assert.DeepEqual(t, diagnostics(true), diagnostics(false)) +} + func TestTypeAllocationAcrossCheckerGrowth(t *testing.T) { t.Parallel() var content strings.Builder @@ -208,3 +252,467 @@ export type Initial = Box<"first">; assert.Equal(t, c.TypeToString(initial), "Initial") assert.Equal(t, len(c.GetDiagnostics(t.Context(), file)), 0) } + +// Memoized results must only be accessed through memo methods, which account +// for the effects of computing them. +func TestMemoValueOnlyAccessedThroughMethods(t *testing.T) { + t.Parallel() + fset := token.NewFileSet() + files, err := filepath.Glob("*.go") + assert.NilError(t, err) + for _, name := range files { + if name == "memo.go" || strings.HasSuffix(name, "_test.go") { + continue + } + file, err := parser.ParseFile(fset, name, nil, parser.SkipObjectResolution) + assert.NilError(t, err) + goast.Inspect(file, func(n goast.Node) bool { + if sel, ok := n.(*goast.SelectorExpr); ok && sel.Sel.Name == "memoValue" { + t.Errorf("%s: memo value accessed directly; use get or set", fset.Position(sel.Pos())) + } + return true + }) + } +} + +// Mutable cached state must live in memos, which account for the effects of +// computing it, or in internMaps, whose entries have none. Other fields must +// be listed in unmemoizedCacheFields with the reason they need not be. +func TestCachedStateIsMemoized(t *testing.T) { + t.Parallel() + fset := token.NewFileSet() + files, err := filepath.Glob("*.go") + assert.NilError(t, err) + seen := make(map[string]bool) + var parsed []*goast.File + // Entries interned by pointer own the results cached in them. + interned := make(map[string]bool) + for _, name := range files { + if strings.HasSuffix(name, "_test.go") { + continue + } + file, err := parser.ParseFile(fset, name, nil, parser.SkipObjectResolution) + assert.NilError(t, err) + parsed = append(parsed, file) + goast.Inspect(file, func(n goast.Node) bool { + if e, ok := n.(*goast.IndexListExpr); ok { + if ident, ok := e.X.(*goast.Ident); ok && ident.Name == "internMap" && len(e.Indices) == 2 { + if star, ok := e.Indices[1].(*goast.StarExpr); ok { + if elem, ok := star.X.(*goast.Ident); ok { + interned[elem.Name] = true + } + } + } + } + return true + }) + } + for _, file := range parsed { + name := filepath.Base(fset.File(file.Pos()).Name()) + goast.Inspect(file, func(n goast.Node) bool { + spec, ok := n.(*goast.TypeSpec) + if !ok { + return true + } + st, ok := spec.Type.(*goast.StructType) + if !ok || slices.Contains(notCacheOwners, spec.Name.Name) || name != "types.go" && !strings.HasSuffix(spec.Name.Name, "Links") && spec.Name.Name != "memoStore" && !interned[spec.Name.Name] { + return true + } + for _, field := range st.Fields.List { + if isMemoType(field.Type) { + continue + } + for _, fieldName := range field.Names { + key := spec.Name.Name + "." + fieldName.Name + seen[key] = true + if _, ok := unmemoizedCacheFields[key]; !ok { + t.Errorf("%s: %s is not a memo; make it one or list it in unmemoizedCacheFields", fset.Position(fieldName.Pos()), key) + } + } + } + return false + }) + } + for key := range unmemoizedCacheFields { + if !seen[key] { + t.Errorf("unmemoizedCacheFields lists %s, which is not an unmemoized field", key) + } + } +} + +func isMemoType(expr goast.Expr) bool { + if a, ok := expr.(*goast.ArrayType); ok { + expr = a.Elt + } + switch e := expr.(type) { + case *goast.IndexExpr: + expr = e.X + case *goast.IndexListExpr: + expr = e.X + } + ident, ok := expr.(*goast.Ident) + return ok && (strings.HasPrefix(ident.Name, "memo") || ident.Name == "internMap") +} + +const ( + unmemoizedImmutable = "Set when the store is created" + unmemoizedContainer = "Holds links, whose fields are checked separately" + unmemoizedCounter = "Allocation counter for IDs and statistics" + unmemoizedInterned = "Interned type whose creation has no effects" + unmemoizedProgram = "Derived from the program only; computing it has no effects" + unmemoizedMemoState = "State of the memos themselves" + unmemoizedChecking = "Written only by checks" + unmemoizedCreated = "Set when the entity owning the links is created" + unmemoizedSyntax = "Derived from syntax and options only; computing it has no effects" + unmemoizedGuarded = "Written only within the memoized computation it is part of" + unmemoizedEmit = "Emit resolver state, written only while resolving for emit" + unmemoizedMark = "Written only by checks and emit marking, and read only by emit" + unmemoizedNodeBuilder = "Node builder state, scoped to one node builder" + unmemoizedNodeFlags = "Each flag is memo-guarded, reporting-only, or set whenever a node is checked and read only by deferred diagnostics, after the whole file is checked" + unmemoizedFlagMemo = "Flag memo, accounted for like a memo under a key naming its entity and field" + unmemoizedEntityFlags = "Each flag is set when the entity is created, or is part of a flag memo" + unmemoizedDerived = "Derived only from fields set when the entity is created; computing it has no effects" + unmemoizedLoopFixpoint = "Fixed point of a loop, kept once complete; analyzing it again from another reference may differ" +) + +// notCacheOwners are the structs declared in types.go that are plain values +// rather than entities owning cached state. +var notCacheOwners = []string{"LanguageFeatureMinimumTargetMap", "accessibleChainCacheKey", "computedName", "TupleElementInfo", "MemberInfo"} + +var unmemoizedCacheFields = map[string]string{ + "valueSymbolExtra.nameType": unmemoizedCreated, + "valueSymbolExtra.containingType": unmemoizedCreated, + "TypeAlias.symbol": unmemoizedCreated, + "TypeAlias.typeArguments": unmemoizedCreated, + "Type.flags": unmemoizedCreated, + "Type.objectFlags": unmemoizedEntityFlags, + "Type.id": unmemoizedCounter, + "Type.kind": unmemoizedCreated, + "Type.generation": unmemoizedDerived, + "Type.symbol": unmemoizedCreated, + "Type.alias": unmemoizedCreated, + "Type.checker": unmemoizedImmutable, + "IntrinsicType.intrinsicName": unmemoizedCreated, + "LiteralType.value": unmemoizedCreated, + "LiteralType.freshType": unmemoizedInterned, + "LiteralType.regularType": unmemoizedCreated, + "UniqueESSymbolType.name": unmemoizedCreated, + "StructuredType.members": unmemoizedFlagMemo, + "StructuredType.properties": unmemoizedFlagMemo, + "StructuredType.extra": unmemoizedFlagMemo, + "structuredTypeExtra.signatures": unmemoizedFlagMemo, + "structuredTypeExtra.callSignatureCount": unmemoizedFlagMemo, + "structuredTypeExtra.indexInfos": unmemoizedFlagMemo, + "structuredTypeExtra.objectTypeWithoutAbstractConstructSignatures": unmemoizedInterned, + "ObjectType.target": unmemoizedCreated, + "ObjectType.mapper": unmemoizedCreated, + "TypeReference.node": unmemoizedCreated, + "TypeReference.lazy": unmemoizedFlagMemo, + "LazyState.layout": unmemoizedCreated, + "LazyState.mapper": unmemoizedCreated, + "LazyState.baseTypes": unmemoizedCreated, + "LazyState.propertiesResolved": unmemoizedFlagMemo, + "MemberLayout.id": unmemoizedCounter, + "MemberLayout.baseIndices": unmemoizedCreated, + "MemberLayout.members": unmemoizedCreated, + "MemberLayout.layoutProperties": unmemoizedDerived, + "InterfaceType.allTypeParameters": unmemoizedCreated, + "InterfaceType.outerTypeParameterCount": unmemoizedCreated, + "InterfaceType.thisType": unmemoizedCreated, + "InterfaceType.baseTypesResolved": unmemoizedFlagMemo, + "InterfaceType.declaredMembersResolved": unmemoizedFlagMemo, + "InterfaceType.declaredMembers": unmemoizedFlagMemo, + "InterfaceType.declaredCallSignatures": unmemoizedFlagMemo, + "InterfaceType.declaredConstructSignatures": unmemoizedFlagMemo, + "InterfaceType.declaredIndexInfos": unmemoizedFlagMemo, + "TupleType.elementInfos": unmemoizedCreated, + "TupleType.minLength": unmemoizedCreated, + "TupleType.fixedLength": unmemoizedCreated, + "TupleType.combinedFlags": unmemoizedCreated, + "TupleType.readonly": unmemoizedCreated, + "InstantiationExpressionType.node": unmemoizedCreated, + "MappedType.declaration": unmemoizedCreated, + "ReverseMappedType.source": unmemoizedCreated, + "ReverseMappedType.mappedType": unmemoizedCreated, + "ReverseMappedType.constraintType": unmemoizedCreated, + "EvolvingArrayType.elementType": unmemoizedCreated, + "UnionOrIntersectionType.types": unmemoizedCreated, + "UnionType.regularType": unmemoizedInterned, + "UnionType.origin": unmemoizedCreated, + "UnionType.constituentMap": unmemoizedGuarded, + "TypeParameter.target": unmemoizedCreated, + "TypeParameter.mapper": unmemoizedCreated, + "TypeParameter.isThisType": unmemoizedCreated, + "TypeParameter.isDistributed": unmemoizedCreated, + "TypeParameter.distributedType": unmemoizedInterned, + "TypeParameter.origin": unmemoizedCreated, + "IndexType.target": unmemoizedCreated, + "IndexType.indexFlags": unmemoizedCreated, + "IndexedAccessType.objectType": unmemoizedCreated, + "IndexedAccessType.indexType": unmemoizedCreated, + "IndexedAccessType.accessFlags": unmemoizedCreated, + "TemplateLiteralType.texts": unmemoizedCreated, + "TemplateLiteralType.types": unmemoizedCreated, + "StringMappingType.target": unmemoizedCreated, + "SubstitutionType.baseType": unmemoizedCreated, + "SubstitutionType.constraint": unmemoizedCreated, + "ConditionalRoot.node": unmemoizedCreated, + "ConditionalRoot.checkType": unmemoizedCreated, + "ConditionalRoot.extendsType": unmemoizedCreated, + "ConditionalRoot.isDistributive": unmemoizedCreated, + "ConditionalRoot.inferTypeParameters": unmemoizedCreated, + "ConditionalRoot.outerTypeParameters": unmemoizedCreated, + "ConditionalRoot.alias": unmemoizedCreated, + "ConditionalType.root": unmemoizedCreated, + "ConditionalType.checkType": unmemoizedCreated, + "ConditionalType.extendsType": unmemoizedCreated, + "ConditionalType.mapper": unmemoizedCreated, + "ConditionalType.combinedMapper": unmemoizedCreated, + "Signature.id": unmemoizedCreated, + "Signature.flags": unmemoizedEntityFlags, + "Signature.minArgumentCount": unmemoizedCreated, + "Signature.declaration": unmemoizedCreated, + "Signature.parameters": unmemoizedCreated, + "Signature.target": unmemoizedCreated, + "Signature.mapper": unmemoizedCreated, + "Signature.extra": unmemoizedCreated, + "signatureExtra.typeParameters": unmemoizedCreated, + "signatureExtra.thisParameter": unmemoizedCreated, + "signatureExtra.resolvedTypePredicate": unmemoizedFlagMemo, + "signatureExtra.isolatedSignatureType": unmemoizedInterned, + "signatureExtra.composite": unmemoizedCreated, + "CompositeSignature.isUnion": unmemoizedCreated, + "CompositeSignature.signatures": unmemoizedCreated, + "TypePredicate.kind": unmemoizedCreated, + "TypePredicate.parameterIndex": unmemoizedCreated, + "TypePredicate.parameterName": unmemoizedCreated, + "TypePredicate.t": unmemoizedCreated, + "IndexInfo.keyType": unmemoizedCreated, + "IndexInfo.valueType": unmemoizedCreated, + "IndexInfo.isReadonly": unmemoizedCreated, + "IndexInfo.declaration": unmemoizedCreated, + "IndexInfo.indexSymbol": unmemoizedInterned, + "IndexInfo.components": unmemoizedCreated, + "memoStore.root": unmemoizedImmutable, + "memoStore.id": unmemoizedImmutable, + "memoStore.program": unmemoizedImmutable, + "memoStore.compilerOptions": unmemoizedImmutable, + "memoStore.files": unmemoizedImmutable, + "memoStore.fileIndexMap": unmemoizedImmutable, + "memoStore.languageVersion": unmemoizedImmutable, + "memoStore.moduleKind": unmemoizedImmutable, + "memoStore.moduleResolutionKind": unmemoizedImmutable, + "memoStore.legacyDecorators": unmemoizedImmutable, + "memoStore.emitStandardClassFields": unmemoizedImmutable, + "memoStore.strictNullChecks": unmemoizedImmutable, + "memoStore.strictFunctionTypes": unmemoizedImmutable, + "memoStore.strictBindCallApply": unmemoizedImmutable, + "memoStore.strictPropertyInitialization": unmemoizedImmutable, + "memoStore.strictBuiltinIteratorReturn": unmemoizedImmutable, + "memoStore.noImplicitAny": unmemoizedImmutable, + "memoStore.noImplicitThis": unmemoizedImmutable, + "memoStore.useUnknownInCatchVariables": unmemoizedImmutable, + "memoStore.exactOptionalPropertyTypes": unmemoizedImmutable, + "memoStore.canCollectSymbolAliasAccessibilityData": unmemoizedImmutable, + "memoStore.arrayVariances": unmemoizedImmutable, + "memoStore.globals": unmemoizedImmutable, + "memoStore.undefinedSymbol": unmemoizedImmutable, + "memoStore.argumentsSymbol": unmemoizedImmutable, + "memoStore.requireSymbol": unmemoizedImmutable, + "memoStore.unknownSymbol": unmemoizedImmutable, + "memoStore.globalThisSymbol": unmemoizedImmutable, + "memoStore.mergedSymbols": unmemoizedImmutable, + "memoStore.mergesFrozen": unmemoizedImmutable, + "memoStore.anyType": unmemoizedImmutable, + "memoStore.autoType": unmemoizedImmutable, + "memoStore.wildcardType": unmemoizedImmutable, + "memoStore.blockedStringType": unmemoizedImmutable, + "memoStore.errorType": unmemoizedImmutable, + "memoStore.unresolvedType": unmemoizedImmutable, + "memoStore.nonInferrableAnyType": unmemoizedImmutable, + "memoStore.intrinsicMarkerType": unmemoizedImmutable, + "memoStore.unknownType": unmemoizedImmutable, + "memoStore.undefinedType": unmemoizedImmutable, + "memoStore.undefinedWideningType": unmemoizedImmutable, + "memoStore.missingType": unmemoizedImmutable, + "memoStore.undefinedOrMissingType": unmemoizedImmutable, + "memoStore.optionalType": unmemoizedImmutable, + "memoStore.nullType": unmemoizedImmutable, + "memoStore.nullWideningType": unmemoizedImmutable, + "memoStore.stringType": unmemoizedImmutable, + "memoStore.numberType": unmemoizedImmutable, + "memoStore.bigintType": unmemoizedImmutable, + "memoStore.regularFalseType": unmemoizedImmutable, + "memoStore.falseType": unmemoizedImmutable, + "memoStore.regularTrueType": unmemoizedImmutable, + "memoStore.trueType": unmemoizedImmutable, + "memoStore.booleanType": unmemoizedImmutable, + "memoStore.esSymbolType": unmemoizedImmutable, + "memoStore.voidType": unmemoizedImmutable, + "memoStore.neverType": unmemoizedImmutable, + "memoStore.silentNeverType": unmemoizedImmutable, + "memoStore.implicitNeverType": unmemoizedImmutable, + "memoStore.unreachableNeverType": unmemoizedImmutable, + "memoStore.nonPrimitiveType": unmemoizedImmutable, + "memoStore.stringOrNumberType": unmemoizedImmutable, + "memoStore.stringNumberSymbolType": unmemoizedImmutable, + "memoStore.numberOrBigIntType": unmemoizedImmutable, + "memoStore.templateConstraintType": unmemoizedImmutable, + "memoStore.numericStringType": unmemoizedImmutable, + "memoStore.uniqueLiteralType": unmemoizedImmutable, + "memoStore.emptyObjectType": unmemoizedImmutable, + "memoStore.emptyJsxObjectType": unmemoizedImmutable, + "memoStore.emptyFreshJsxObjectType": unmemoizedImmutable, + "memoStore.emptyTypeLiteralType": unmemoizedImmutable, + "memoStore.unknownEmptyObjectType": unmemoizedImmutable, + "memoStore.unknownUnionType": unmemoizedImmutable, + "memoStore.emptyGenericType": unmemoizedImmutable, + "memoStore.anyFunctionType": unmemoizedImmutable, + "memoStore.noConstraintType": unmemoizedImmutable, + "memoStore.circularConstraintType": unmemoizedImmutable, + "memoStore.resolvingDefaultType": unmemoizedImmutable, + "memoStore.markerSuperType": unmemoizedImmutable, + "memoStore.markerSubType": unmemoizedImmutable, + "memoStore.markerOtherType": unmemoizedImmutable, + "memoStore.markerSuperTypeForCheck": unmemoizedImmutable, + "memoStore.markerSubTypeForCheck": unmemoizedImmutable, + "memoStore.noTypePredicate": unmemoizedImmutable, + "memoStore.anySignature": unmemoizedImmutable, + "memoStore.unknownSignature": unmemoizedImmutable, + "memoStore.resolvingSignature": unmemoizedImmutable, + "memoStore.silentNeverSignature": unmemoizedImmutable, + "memoStore.enumNumberIndexInfo": unmemoizedImmutable, + "memoStore.anyBaseTypeIndexInfo": unmemoizedImmutable, + "memoStore.patternAmbientModules": unmemoizedImmutable, + "memoStore.patternAmbientModuleAugmentations": unmemoizedImmutable, + "memoStore.patternAmbientModuleAugmentationTargets": unmemoizedImmutable, + "memoStore.globalObjectType": unmemoizedImmutable, + "memoStore.globalFunctionType": unmemoizedImmutable, + "memoStore.globalCallableFunctionType": unmemoizedImmutable, + "memoStore.globalNewableFunctionType": unmemoizedImmutable, + "memoStore.globalArrayType": unmemoizedImmutable, + "memoStore.globalReadonlyArrayType": unmemoizedImmutable, + "memoStore.globalStringType": unmemoizedImmutable, + "memoStore.globalNumberType": unmemoizedImmutable, + "memoStore.globalBooleanType": unmemoizedImmutable, + "memoStore.globalRegExpType": unmemoizedImmutable, + "memoStore.globalThisType": unmemoizedImmutable, + "memoStore.anyArrayType": unmemoizedImmutable, + "memoStore.autoArrayType": unmemoizedImmutable, + "memoStore.anyReadonlyArrayType": unmemoizedImmutable, + "memoStore.emptyStringType": unmemoizedImmutable, + "memoStore.zeroType": unmemoizedImmutable, + "memoStore.zeroBigIntType": unmemoizedImmutable, + "memoStore.typeofType": unmemoizedImmutable, + "memoStore.subtypeRelation": unmemoizedImmutable, + "memoStore.strictSubtypeRelation": unmemoizedImmutable, + "memoStore.assignableRelation": unmemoizedImmutable, + "memoStore.comparableRelation": unmemoizedImmutable, + "memoStore.identityRelation": unmemoizedImmutable, + "memoStore.nodeLinks": unmemoizedContainer, + "memoStore.signatureLinks": unmemoizedContainer, + "memoStore.symbolNodeLinks": unmemoizedContainer, + "memoStore.typeNodeLinks": unmemoizedContainer, + "memoStore.enumMemberLinks": unmemoizedContainer, + "memoStore.arrayLiteralLinks": unmemoizedContainer, + "memoStore.switchStatementLinks": unmemoizedContainer, + "memoStore.jsxElementLinks": unmemoizedContainer, + "memoStore.computedNameLinks": unmemoizedContainer, + "memoStore.symbolReferenceLinks": unmemoizedContainer, + "memoStore.valueSymbolLinks": unmemoizedContainer, + "memoStore.mappedSymbolLinks": unmemoizedContainer, + "memoStore.deferredSymbolLinks": unmemoizedContainer, + "memoStore.aliasSymbolLinks": unmemoizedContainer, + "memoStore.moduleSymbolLinks": unmemoizedContainer, + "memoStore.lateBoundLinks": unmemoizedContainer, + "memoStore.exportTypeLinks": unmemoizedContainer, + "memoStore.membersAndExportsLinks": unmemoizedContainer, + "memoStore.typeAliasLinks": unmemoizedContainer, + "memoStore.declaredTypeLinks": unmemoizedContainer, + "memoStore.spreadLinks": unmemoizedContainer, + "memoStore.varianceLinks": unmemoizedContainer, + "memoStore.ReverseMappedSymbolLinks": unmemoizedContainer, + "memoStore.markedAssignmentSymbolLinks": unmemoizedContainer, + "memoStore.sourceFileLinks": unmemoizedContainer, + "memoStore.emitResolverLinks": unmemoizedContainer, + "EmitResolverLinks.jsxLinks": unmemoizedContainer, + "EmitResolverLinks.declarationLinks": unmemoizedContainer, + "EmitResolverLinks.declarationFileLinks": unmemoizedContainer, + "memoStore.TypeCount": unmemoizedCounter, + "memoStore.memberLayoutCount": unmemoizedCounter, + "memoStore.SymbolCount": unmemoizedCounter, + "memoStore.SignatureCount": unmemoizedCounter, + "memoStore.TotalInstantiationCount": unmemoizedCounter, + "memoStore.nanType": unmemoizedInterned, + "memoStore.parentLateBindableKinds": unmemoizedSyntax, + "memoStore.packagesMap": unmemoizedProgram, + "memoStore.ambientModulesOnce": unmemoizedProgram, + "memoStore.ambientModules": unmemoizedProgram, + "memoStore.memoStates": unmemoizedMemoState, + "memoStore.memoPendingCount": unmemoizedMemoState, + "memoStore.memoErrorStates": unmemoizedMemoState, + "memoStore.stateFilter": unmemoizedMemoState, + "memoStore.errorFilter": unmemoizedMemoState, + "memoStore.mu": unmemoizedMemoState, + "memoStore.diagnostics": unmemoizedChecking, + "memoStore.suggestionDiagnostics": unmemoizedChecking, + "memoStore.nonExistentPropertyNodes": unmemoizedChecking, + "memoStore.deferredDiagnosticCallbacks": unmemoizedChecking, + "SymbolReferenceLinks.referenceKinds": unmemoizedChecking, + "AliasSymbolLinks.referenced": unmemoizedMark, + "AliasSymbolLinks.referenceChecked": unmemoizedChecking, + "SourceFileLinks.typeChecked": unmemoizedChecking, + "SourceFileLinks.unusedChecked": unmemoizedChecking, + "SourceFileLinks.deferredNodes": unmemoizedChecking, + "SourceFileLinks.identifierCheckNodes": unmemoizedChecking, + "ValueSymbolLinks.target": unmemoizedCreated, + "ValueSymbolLinks.mapper": unmemoizedCreated, + "ValueSymbolLinks.extra": unmemoizedCreated, + "MappedSymbolLinks.keyType": unmemoizedCreated, + "MappedSymbolLinks.syntheticOrigin": unmemoizedCreated, + "DeferredSymbolLinks.parent": unmemoizedCreated, + "DeferredSymbolLinks.constituents": unmemoizedCreated, + "DeferredSymbolLinks.writeConstituents": unmemoizedCreated, + "ReverseMappedSymbolLinks.propertyType": unmemoizedCreated, + "ReverseMappedSymbolLinks.mappedType": unmemoizedCreated, + "ReverseMappedSymbolLinks.constraintType": unmemoizedCreated, + "ExportTypeLinks.target": unmemoizedCreated, + "ExportTypeLinks.originatingImport": unmemoizedCreated, + "SpreadLinks.leftSpread": unmemoizedCreated, + "SpreadLinks.rightSpread": unmemoizedCreated, + "JsxElementLinks.firstJSXTagInFile": unmemoizedSyntax, + "memoStore._jsxNamespace": unmemoizedSyntax, + "memoStore._jsxFactoryEntity": unmemoizedSyntax, + "SwitchStatementLinks.witnessesComputed": unmemoizedSyntax, + "SwitchStatementLinks.witnesses": unmemoizedSyntax, + "ArrayLiteralLinks.indicesComputed": unmemoizedSyntax, + "ArrayLiteralLinks.firstSpreadIndex": unmemoizedSyntax, + "ArrayLiteralLinks.lastSpreadIndex": unmemoizedSyntax, + "NodeLinks.declarationRequiresScopeChange": unmemoizedSyntax, + "SourceFileLinks.localJsxNamespace": unmemoizedSyntax, + "SourceFileLinks.localJsxFragmentNamespace": unmemoizedSyntax, + "SourceFileLinks.localJsxFactory": unmemoizedSyntax, + "SourceFileLinks.localJsxFragmentFactory": unmemoizedSyntax, + "JsxElementLinks.jsxFlags": unmemoizedFlagMemo, + "JsxElementLinks.jsxImplicitImportContainer": unmemoizedFlagMemo, + "flowLoopCacheEntry.t": unmemoizedLoopFixpoint, + "instantiationExpressionEntry.t": unmemoizedFlagMemo, + "ModuleSymbolLinks.resolvedExports": unmemoizedFlagMemo, + "ModuleSymbolLinks.typeOnlyExportStarMap": unmemoizedFlagMemo, + "LateBoundLinks.lateSymbol": unmemoizedGuarded, + "TypeAliasLinks.typeParameters": unmemoizedGuarded, + "MarkedAssignmentSymbolLinks.lastAssignmentPos": unmemoizedGuarded, + "MarkedAssignmentSymbolLinks.hasDefiniteAssignment": unmemoizedGuarded, + "EnumMemberLinks.value": unmemoizedGuarded, + "JSXLinks.importRef": unmemoizedEmit, + "DeclarationLinks.isVisible": unmemoizedEmit, + "DeclarationFileLinks.aliasesMarked": unmemoizedEmit, + "NodeBuilderLinks.serializedTypes": unmemoizedNodeBuilder, + "NodeBuilderLinks.fakeScopeForSignatureDeclaration": unmemoizedNodeBuilder, + "NodeBuilderSymbolLinks.specifierCache": unmemoizedNodeBuilder, + "SourceFileLinks.externalHelpersModule": unmemoizedChecking, + "SourceFileLinks.requestedExternalEmitHelpers": unmemoizedChecking, + "SourceFileLinks.externalHelperRequests": unmemoizedChecking, + "AliasSymbolLinks.typeOnlyDeclaration": unmemoizedGuarded, + "NodeLinks.flags": unmemoizedNodeFlags, +} diff --git a/tsc/internal/checker/emitresolver.go b/tsc/internal/checker/emitresolver.go index e4fad5c3f5b1f..7223d5d4b9167 100644 --- a/tsc/internal/checker/emitresolver.go +++ b/tsc/internal/checker/emitresolver.go @@ -398,7 +398,7 @@ func (r *EmitResolver) IsReferencedAliasDeclaration(node *ast.Node) bool { if aliasLinks.referenced { return true } - target := aliasLinks.aliasTarget + target := aliasLinks.aliasTarget.get(c) if target != nil && node.ModifierFlags()&ast.ModifierFlagsExport != 0 && c.getSymbolFlags(target)&ast.SymbolFlagsValue != 0 && (c.compilerOptions.ShouldPreserveConstEnums() || !isConstEnumOrConstEnumOnlyModule(target)) { @@ -502,6 +502,12 @@ func (r *EmitResolver) MarkLinkedReferencesRecursively(file *ast.SourceFile) { defer r.checkerMu.Unlock() if file != nil { + r.checker.withReferenceMarks(func() { r.markLinkedReferencesRecursively(file) }) + } +} + +func (r *EmitResolver) markLinkedReferencesRecursively(file *ast.SourceFile) { + { var visit ast.Visitor visit = func(n *ast.Node) bool { if ast.IsImportEqualsDeclaration(n) && n.ModifierFlags()&ast.ModifierFlagsExport == 0 { diff --git a/tsc/internal/checker/flow.go b/tsc/internal/checker/flow.go index 7ee926e0f48d5..d8a0632d00dce 100644 --- a/tsc/internal/checker/flow.go +++ b/tsc/internal/checker/flow.go @@ -47,6 +47,7 @@ type FlowState struct { flowContainer *ast.Node refKey CacheHashKey depth int + limitHits int sharedFlowStart int reduceLabels []*ast.FlowReduceLabelData next *FlowState @@ -96,6 +97,11 @@ func (c *Checker) getFlowTypeOfReferenceEx(reference *ast.Node, declaredType *Ty f.sharedFlowStart = len(c.sharedFlows) c.flowInvocationCount++ evolvedType := c.getTypeAtFlowNode(f, flowNode).t + if f.limitHits != 0 { + // Give up on the whole reference, so its type does not depend on how + // deep the walk got before giving up. + evolvedType = c.errorType + } c.sharedFlows = c.sharedFlows[:f.sharedFlowStart] c.putFlowState(f) // When the reference is 'x' in an 'x.length', 'x.push(value)', 'x.unshift(value)' or x[n] = value' operation, @@ -114,14 +120,64 @@ func (c *Checker) getFlowTypeOfReferenceEx(reference *ast.Node, declaredType *Ty return resultType } +const maxFlowDepth = 2000 + func (c *Checker) getTypeAtFlowNode(f *FlowState, flow *ast.FlowNode) FlowType { - if f.depth == 2000 { + // A walk that is too deep from a node is also too deep from it with less + // depth left, so remember the failure to avoid repeating it for every + // reference after the first. Loops being analyzed and reduce labels make + // walks depend on more than the node and reference, so such walks are not + // remembered. + budget := maxFlowDepth - f.depth + remember := len(c.flowLoopStack) == 0 && len(f.reduceLabels) == 0 + if remember && len(c.flowDepthFailures) != 0 && c.hasFlowReferenceKey(f) { + if b, ok := c.flowDepthFailures[FlowLoopKey{flowNode: flow, refKey: f.refKey}]; ok && budget <= b { + f.limitHits++ + c.reportFlowControlError(f.reference) + return FlowType{t: c.errorType} + } + } + hits := f.limitHits + t := c.getTypeAtFlowNodeWorker(f, flow) + if f.limitHits != hits && remember && c.hasFlowReferenceKey(f) { + key := FlowLoopKey{flowNode: flow, refKey: f.refKey} + if b, ok := c.flowDepthFailures[key]; !ok || b < budget { + if c.flowDepthFailures == nil { + c.flowDepthFailures = make(map[FlowLoopKey]int) + } + if c.logResults() { + // The walk may have depended on results a cycle in progress + // discards. + c.discardLog = append(c.discardLog, discardEntry{nil, func() { + if ok { + c.flowDepthFailures[key] = b + } else { + delete(c.flowDepthFailures, key) + } + }, false}) + } + c.flowDepthFailures[key] = budget + } + } + return t +} + +func (c *Checker) hasFlowReferenceKey(f *FlowState) bool { + if f.refKey.IsZero() { + f.refKey = c.getFlowReferenceKey(f) + } + return f.refKey != nonDottedNameCacheKey +} + +func (c *Checker) getTypeAtFlowNodeWorker(f *FlowState, flow *ast.FlowNode) FlowType { + if f.depth == maxFlowDepth { // We have made 2000 recursive invocations. To avoid overflowing the call stack we report an error // and give up on this reference. Each reference hits the limit on its own, so its type does not depend - // on which other references were analyzed first. + // on which other references were analyzed first; see getTypeAtFlowNode. if tr := c.tracer; tr != nil { tr.Instant(tracing.PhaseCheckTypes, "getTypeAtFlowNode_DepthLimit", map[string]any{"depth": f.depth}) } + f.limitHits++ c.reportFlowControlError(f.reference) return FlowType{t: c.errorType} } @@ -387,14 +443,14 @@ func (c *Checker) narrowType(f *FlowState, t *Type, expr *ast.Node, assumeTrue b case ast.KindIdentifier: // When narrowing a reference to a const variable, non-assigned parameter, or readonly property, we inline // up to five levels of aliased conditional expressions that are themselves declared as const variables. - if !c.isMatchingReference(f.reference, expr) && c.inlineLevel < 5 { + if !c.isMatchingReference(f.reference, expr) { symbol := c.getResolvedSymbol(expr) if c.isConstantVariable(symbol) { declaration := symbol.ValueDeclaration() - if declaration != nil && ast.IsVariableDeclaration(declaration) && declaration.Type() == nil && declaration.Initializer() != nil && c.isConstantReference(f.reference) { - c.inlineLevel++ + if declaration != nil && ast.IsVariableDeclaration(declaration) && declaration.Type() == nil && declaration.Initializer() != nil && c.isConstantReference(f.reference) && !c.depthLimitReached(&c.inlineLevel, 5) { + c.enterDepthLimit(&c.inlineLevel) result := c.narrowType(f, t, declaration.Initializer(), assumeTrue) - c.inlineLevel-- + c.inlineLevel.exit() return result } } @@ -852,11 +908,13 @@ func (c *Checker) getNarrowedType(t *Type, candidate *Type, assumeTrue bool, che return c.getNarrowedTypeWorker(t, candidate, assumeTrue, checkDerived) } key := NarrowedTypeKey{t, candidate, assumeTrue, checkDerived} - if narrowedType, ok := c.narrowedTypes[key]; ok { + if narrowedType, ok := c.narrowedTypes.get(c, key); ok { return narrowedType } + c.narrowedTypes.begin(c, key) narrowedType := c.getNarrowedTypeWorker(t, candidate, assumeTrue, checkDerived) - c.narrowedTypes[key] = narrowedType + c.narrowedTypes.set(c, key, narrowedType) + c.endMemo() return narrowedType } @@ -1346,10 +1404,22 @@ func (c *Checker) getTypeAtFlowLoopLabel(f *FlowState, flow *ast.FlowNode) FlowT return FlowType{t: f.declaredType} } key := FlowLoopKey{flowNode: flow, refKey: f.refKey} + entry, _ := c.flowLoopCache.get(key) + if entry == nil { + entry = &flowLoopCacheEntry{} + c.flowLoopCache.intern(key, entry) + } + c.beginMemo(memoKeyOf(entry, memoFlowLoopType)) + result := c.getTypeAtFlowLoopLabelCached(f, flow, key, entry) + c.endMemo() + return result +} + +func (c *Checker) getTypeAtFlowLoopLabelCached(f *FlowState, flow *ast.FlowNode, key FlowLoopKey, entry *flowLoopCacheEntry) FlowType { // If we have previously computed the control flow type for the reference at // this flow loop junction, return the cached type. - if cached := c.flowLoopCache[key]; cached != nil { - return FlowType{t: cached} + if entry.t != nil { + return FlowType{t: entry.t} } // If this flow loop junction and reference are already being processed, return // the union of the types computed for each branch so far, marked as incomplete. @@ -1389,8 +1459,8 @@ func (c *Checker) getTypeAtFlowLoopLabel(f *FlowState, flow *ast.FlowNode) FlowT // If we see a value appear in the cache it is a sign that control flow analysis // was restarted and completed by checkExpressionCached. We can simply pick up // the resulting type and bail out. - if cached := c.flowLoopCache[key]; cached != nil { - return FlowType{t: cached} + if entry.t != nil { + return FlowType{t: entry.t} } } antecedentTypes = core.AppendIfUnique(antecedentTypes, flowType.t) @@ -1413,7 +1483,10 @@ func (c *Checker) getTypeAtFlowLoopLabel(f *FlowState, flow *ast.FlowNode) FlowT if firstAntecedentType.incomplete { return c.newFlowType(result, true /*incomplete*/) } - c.flowLoopCache[key] = result + // Kept even if a cycle in progress discards what it computed: analyzing + // the loop again after the cycle starts from wherever the next reference + // is, and may then reach a different fixed point. + entry.t = result return FlowType{t: result} } @@ -1524,11 +1597,11 @@ func getCandidateVariableDeclarationInitializer(node *ast.Node) *ast.Node { // and never escape the getFlowTypeOfReference function. func (c *Checker) getEvolvingArrayType(elementType *Type) *Type { key := CachedTypeKey{kind: CachedTypeKindEvolvingArrayType, typeId: elementType.id} - result := c.cachedTypes[key] + result, _ := c.cachedTypes.get(c, key) if result == nil { result = c.newObjectType(ObjectFlagsEvolvingArray, nil) result.AsEvolvingArrayType().elementType = elementType - c.cachedTypes[key] = result + c.cachedTypes.intern(key, result) } return result } @@ -1587,10 +1660,7 @@ func (c *Checker) finalizeEvolvingArrayType(t *Type) *Type { } func (c *Checker) getFinalArrayType(t *EvolvingArrayType) *Type { - if t.finalArrayType == nil { - t.finalArrayType = c.createFinalArrayType(t.elementType) - } - return t.finalArrayType + return c.memoCompute(&t.finalArrayType, func() *Type { return c.createFinalArrayType(t.elementType) }) } func (c *Checker) createFinalArrayType(elementType *Type) *Type { @@ -1960,18 +2030,21 @@ func isCoercibleUnderDoubleEquals(source *Type, target *Type) bool { func (c *Checker) isExhaustiveSwitchStatement(node *ast.Node) bool { links := c.switchStatementLinks.Get(node) - if links.exhaustiveState == ExhaustiveStateUnknown { + switch links.exhaustiveState.get(c) { + case ExhaustiveStateUnknown: + links.exhaustiveState.begin(c) // Indicate resolution is in process - links.exhaustiveState = ExhaustiveStateComputing + links.exhaustiveState.set(c, ExhaustiveStateComputing) isExhaustive := c.computeExhaustiveSwitchStatement(node) - if links.exhaustiveState == ExhaustiveStateComputing { - links.exhaustiveState = core.IfElse(isExhaustive, ExhaustiveStateTrue, ExhaustiveStateFalse) + if links.exhaustiveState.get(c) == ExhaustiveStateComputing { + links.exhaustiveState.set(c, core.IfElse(isExhaustive, ExhaustiveStateTrue, ExhaustiveStateFalse)) } - } else if links.exhaustiveState == ExhaustiveStateComputing { + c.endMemo() + case ExhaustiveStateComputing: // Resolve circularity to false - links.exhaustiveState = ExhaustiveStateFalse + links.exhaustiveState.set(c, ExhaustiveStateFalse) } - return links.exhaustiveState == ExhaustiveStateTrue + return links.exhaustiveState.get(c) == ExhaustiveStateTrue } func (c *Checker) computeExhaustiveSwitchStatement(node *ast.Node) bool { @@ -2053,16 +2126,17 @@ func (c *Checker) getNotEqualFactsFromTypeofSwitch(start int, end int, witnesses func (c *Checker) getSwitchClauseTypes(node *ast.Node) []*Type { links := c.switchStatementLinks.Get(node) - if !links.switchTypesComputed { + if links.switchTypes.get(c) == nil { + links.switchTypes.begin(c) clauses := node.AsSwitchStatement().CaseBlock.AsCaseBlock().Clauses.Nodes types := make([]*Type, len(clauses)) for i, clause := range clauses { types[i] = c.getTypeOfSwitchClause(clause) } - links.switchTypes = types - links.switchTypesComputed = true + links.switchTypes.set(c, types) + c.endMemo() } - return links.switchTypes + return links.switchTypes.get(c) } func (c *Checker) getTypeOfSwitchClause(clause *ast.Node) *Type { @@ -2074,8 +2148,9 @@ func (c *Checker) getTypeOfSwitchClause(clause *ast.Node) *Type { func (c *Checker) getEffectsSignature(node *ast.Node) *Signature { links := c.signatureLinks.Get(node) - signature := links.effectsSignature + signature := links.effectsSignature.get(c) if signature == nil { + links.effectsSignature.begin(c) // A call expression parented by an expression statement is a potential assertion. Other call // expressions are potential type predicate function calls. In order to avoid triggering // circularities in control flow analysis, we use getTypeOfDottedName when resolving the call @@ -2107,7 +2182,8 @@ func (c *Checker) getEffectsSignature(node *ast.Node) *Signature { if !(signature != nil && c.hasTypePredicateOrNeverReturnType(signature)) { signature = c.unknownSignature } - links.effectsSignature = signature + links.effectsSignature.set(c, signature) + c.endMemo() } if signature == c.unknownSignature { return nil @@ -2182,10 +2258,11 @@ func (c *Checker) getTypeOfDottedName(node *ast.Node, diagnostic *ast.Diagnostic func (c *Checker) getExplicitTypeOfSymbol(symbol *ast.Symbol, diagnostic *ast.Diagnostic) *Type { symbol = c.resolveSymbol(symbol) - if !c.resolvingExplicitTypeOfSymbol.AddIfAbsent(symbol) { + if c.isResolvingAnywhere(symbol, TypeSystemPropertyNameExplicitType) { return nil } - defer c.resolvingExplicitTypeOfSymbol.Delete(symbol) + c.beginTypeResolution(symbol, TypeSystemPropertyNameExplicitType) + defer c.popTypeResolution() if symbol.Flags()&(ast.SymbolFlagsFunction|ast.SymbolFlagsMethod|ast.SymbolFlagsClass|ast.SymbolFlagsValueModule) != 0 { return c.getTypeOfSymbol(symbol) } @@ -2290,7 +2367,7 @@ func (c *Checker) getTypeOfInitializer(node *ast.Node) *Type { // from its initializer, we'll already have cached the type. Otherwise we compute it now // without caching such that transient types are reflected. if c.typeNodeLinks.Has(node) { - t := c.typeNodeLinks.Get(node).resolvedType + t := c.typeNodeLinks.Get(node).resolvedType.get(c) if t != nil { return t } @@ -2432,10 +2509,12 @@ func (c *Checker) getAssignmentReducedType(declaredType *Type, assignedType *Typ return assignedType } key := AssignmentReducedKey{id1: declaredType.id, id2: assignedType.id} - result := c.assignmentReducedTypes[key] + result, _ := c.assignmentReducedTypes.get(c, key) if result == nil { + c.assignmentReducedTypes.begin(c, key) result = c.getAssignmentReducedTypeWorker(declaredType, assignedType) - c.assignmentReducedTypes[key] = result + c.assignmentReducedTypes.set(c, key, result) + c.endMemo() } return result } @@ -2542,24 +2621,30 @@ func (c *Checker) isReachableFlowNode(flow *ast.FlowNode) bool { f := c.getFlowState() result := c.isReachableFlowNodeWorker(f, flow, false /*noCacheCheck*/) c.putFlowState(f) - c.lastFlowNode = flow - c.lastFlowNodeReachable = result + // The last result is reused without accounting for reading it, so it is + // only kept where nothing computed since could be discarded. + if !c.logResults() { + c.lastFlowNode = flow + c.lastFlowNodeReachable = result + } return result } func (c *Checker) isReachableFlowNodeWorker(f *FlowState, flow *ast.FlowNode, noCacheCheck bool) bool { for { - if flow == c.lastFlowNode { + if flow == c.lastFlowNode && !c.observing() { return c.lastFlowNodeReachable } flags := flow.Flags if flags&ast.FlowFlagsShared != 0 { if !noCacheCheck && len(f.reduceLabels) == 0 { - if reachable, ok := c.flowNodeReachable[flow]; ok { + if reachable, ok := c.flowNodeReachable.get(c, flow); ok { return reachable } + c.flowNodeReachable.begin(c, flow) reachable := c.isReachableFlowNodeWorker(f, flow, true /*noCacheCheck*/) - c.flowNodeReachable[flow] = reachable + c.flowNodeReachable.set(c, flow, reachable) + c.endMemo() return reachable } noCacheCheck = false @@ -2641,11 +2726,13 @@ func (c *Checker) isPostSuperFlowNodeWorker(f *FlowState, flow *ast.FlowNode, no flags := flow.Flags if flags&ast.FlowFlagsShared != 0 { if !noCacheCheck { - if postSuper, ok := c.flowNodePostSuper[flow]; ok { + if postSuper, ok := c.flowNodePostSuper.get(c, flow); ok { return postSuper } + c.flowNodePostSuper.begin(c, flow) postSuper := c.isPostSuperFlowNodeWorker(f, flow, true /*noCacheCheck*/) - c.flowNodePostSuper[flow] = postSuper + c.flowNodePostSuper.set(c, flow, postSuper) + c.endMemo() } noCacheCheck = false } @@ -2704,18 +2791,16 @@ func (c *Checker) ensureAssignmentsMarked(symbol *ast.Symbol) { if parent == nil { return } - links := c.nodeLinks.Get(parent) - if links.flags&NodeCheckFlagsAssignmentsMarked == 0 { - links.flags |= NodeCheckFlagsAssignmentsMarked + c.assignmentsMarked.once(c, parent, func() { if !c.hasParentWithAssignmentsMarked(parent) { c.markNodeAssignments(parent) } - } + }) } func (c *Checker) hasParentWithAssignmentsMarked(node *ast.Node) bool { return ast.FindAncestor(node.Parent, func(node *ast.Node) bool { - return ast.IsFunctionOrSourceFile(node) && c.nodeLinks.Get(node).flags&NodeCheckFlagsAssignmentsMarked != 0 + return ast.IsFunctionOrSourceFile(node) && c.assignmentsMarked.has(c, node) }) != nil } diff --git a/tsc/internal/checker/grammarchecks.go b/tsc/internal/checker/grammarchecks.go index f8997b8e361e1..afb6d2e63237d 100644 --- a/tsc/internal/checker/grammarchecks.go +++ b/tsc/internal/checker/grammarchecks.go @@ -2023,13 +2023,20 @@ func (c *Checker) checkGrammarSourceFile(node *ast.SourceFile) bool { return node.Flags&ast.NodeFlagsAmbient != 0 && c.checkGrammarTopLevelElementsForRequiredDeclareModifier(node) } +func (c *Checker) reportStatementInAmbientContext(links *NodeLinks, node *ast.Node, message *diagnostics.Message) bool { + links.hasReportedStatementInAmbientContext.begin(c) + reported := c.grammarErrorOnFirstToken(node, message) + links.hasReportedStatementInAmbientContext.set(c, reported) + c.endMemo() + return reported +} + func (c *Checker) checkGrammarStatementInAmbientContext(node *ast.Node) bool { if node.Flags&ast.NodeFlagsAmbient != 0 { // Find containing block which is either Block, ModuleBlock, SourceFile links := c.nodeLinks.Get(node) - if !links.hasReportedStatementInAmbientContext && (ast.IsFunctionLike(node.Parent) || ast.IsAccessor(node.Parent)) { - links.hasReportedStatementInAmbientContext = c.grammarErrorOnFirstToken(node, diagnostics.An_implementation_cannot_be_declared_in_ambient_contexts) - return links.hasReportedStatementInAmbientContext + if !links.hasReportedStatementInAmbientContext.get(c) && (ast.IsFunctionLike(node.Parent) || ast.IsAccessor(node.Parent)) { + return c.reportStatementInAmbientContext(links, node, diagnostics.An_implementation_cannot_be_declared_in_ambient_contexts) } // We are either parented by another statement, or some sort of block. @@ -2040,9 +2047,8 @@ func (c *Checker) checkGrammarStatementInAmbientContext(node *ast.Node) bool { if node.Parent.Kind == ast.KindBlock || node.Parent.Kind == ast.KindModuleBlock || node.Parent.Kind == ast.KindSourceFile { links := c.nodeLinks.Get(node.Parent) // Check if the containing block ever report this error - if !links.hasReportedStatementInAmbientContext { - links.hasReportedStatementInAmbientContext = c.grammarErrorOnFirstToken(node, diagnostics.Statements_are_not_allowed_in_ambient_contexts) - return links.hasReportedStatementInAmbientContext + if !links.hasReportedStatementInAmbientContext.get(c) { + return c.reportStatementInAmbientContext(links, node, diagnostics.Statements_are_not_allowed_in_ambient_contexts) } } else { // We must be parented by a statement. If so, there's no need diff --git a/tsc/internal/checker/inference.go b/tsc/internal/checker/inference.go index 8b0cc87aedef4..27ddc6aed15e6 100644 --- a/tsc/internal/checker/inference.go +++ b/tsc/internal/checker/inference.go @@ -285,11 +285,11 @@ func (c *Checker) inferFromAliasTypeArguments(n *InferenceState, source *Type, t nodeIsInJsFile := ast.IsInJSFile(source.alias.symbol.ValueDeclaration()) sourceTypes := c.fillMissingTypeArguments(source.alias.typeArguments, params, minParams, nodeIsInJsFile) targetTypes := c.fillMissingTypeArguments(target.alias.typeArguments, params, minParams, nodeIsInJsFile) - c.inferFromTypeArguments(n, sourceTypes, targetTypes, c.getAliasVariances(source.alias.symbol)) + c.inferFromTypeArguments(n, sourceTypes, targetTypes, c.getVariancesForInference(func() []VarianceFlags { return c.getAliasVariances(source.alias.symbol) })) } func (c *Checker) inferFromReferenceTypeArguments(n *InferenceState, source *Type, target *Type) { - c.inferFromTypeArguments(n, c.getTypeArguments(source), c.getTypeArguments(target), c.getVariances(source.AsTypeReference().target)) + c.inferFromTypeArguments(n, c.getTypeArguments(source), c.getTypeArguments(target), c.getVariancesForInference(func() []VarianceFlags { return c.getVariances(source.AsTypeReference().target) })) } func (c *Checker) inferFromTypeArguments(n *InferenceState, sourceTypes []*Type, targetTypes []*Type, variances []VarianceFlags) { @@ -399,7 +399,7 @@ func (c *Checker) inferFromMatchingTypes(n *InferenceState, sources []*Type, tar // when inferring from string[] | string[][] to T[] | T[][], the inference of string we make from // relating string[][] to T[][] is of higher quality than the inference of string[] we make relating // string[][] to T[]. - slices.SortFunc(matchedTargets, compareTypesAndDepth) + slices.SortFunc(matchedTargets, c.compareTypesAndDepth) for _, t := range matchedTargets { for _, s := range matchedSources { if matches(c, s, t) { @@ -418,9 +418,9 @@ func (c *Checker) inferFromMatchingTypes(n *InferenceState, sources []*Type, tar } // Compare two types first by depth and then by the regular type ordering. -func compareTypesAndDepth(t1, t2 *Type) int { - d1 := getTypeDepth(t1, 3) - d2 := getTypeDepth(t2, 3) +func (c *Checker) compareTypesAndDepth(t1, t2 *Type) int { + d1 := c.getTypeDepth(t1, 3) + d2 := c.getTypeDepth(t2, 3) if d1 != d2 { return d2 - d1 // Largest depth sorts first } @@ -431,27 +431,27 @@ func compareTypesAndDepth(t1, t2 *Type) int { // and type references, the depth is one plus the largest type argument depth. For union and // intersection types, the depth is the largest constituent type depth. For all other types, // the depth is zero. The maximum depth limits infinite recursion of circular types. -func getTypeDepth(t *Type, maxDepth int) int { +func (c *Checker) getTypeDepth(t *Type, maxDepth int) int { if maxDepth != 0 { if t.alias != nil && len(t.alias.typeArguments) != 0 { - return getTypeListDepth(t.alias.typeArguments, maxDepth-1) + 1 + return c.getTypeListDepth(t.alias.typeArguments, maxDepth-1) + 1 } if t.objectFlags&ObjectFlagsReference != 0 { - if typeArguments := t.checker.getTypeArguments(t); len(typeArguments) != 0 { - return getTypeListDepth(typeArguments, maxDepth-1) + 1 + if typeArguments := c.getTypeArguments(t); len(typeArguments) != 0 { + return c.getTypeListDepth(typeArguments, maxDepth-1) + 1 } } if t.flags&TypeFlagsUnionOrIntersection != 0 { - return getTypeListDepth(t.Types(), maxDepth) + return c.getTypeListDepth(t.Types(), maxDepth) } } return 0 } -func getTypeListDepth(types []*Type, maxDepth int) int { +func (c *Checker) getTypeListDepth(types []*Type, maxDepth int) int { depth := 0 for _, t := range types { - depth = max(depth, getTypeDepth(t, maxDepth)) + depth = max(depth, c.getTypeDepth(t, maxDepth)) } return depth } @@ -984,7 +984,7 @@ func (c *Checker) inferToMappedType(n *InferenceState, source *Type, target *Typ if constraintType.flags&TypeFlagsTypeParameter != 0 { // We're inferring from some source type S to a mapped type { [P in K]: X }, where K is a type // parameter. First infer from 'keyof S' to K. - c.inferWithPriority(n, c.getIndexTypeEx(source, core.IfElse(c.patternForType[source] != nil, IndexFlagsNoIndexSignatures, IndexFlagsNone)), constraintType, InferencePriorityMappedTypeConstraint) + c.inferWithPriority(n, c.getIndexTypeEx(source, core.IfElse(c.patternForType.has(source), IndexFlagsNoIndexSignatures, IndexFlagsNone)), constraintType, InferencePriorityMappedTypeConstraint) // If K is constrained to a type C, also infer to C. Thus, for a mapped type { [P in K]: X }, // where K extends keyof T, we make the same inferences as for a homomorphic mapped type // { [P in keyof T]: X }. This enables us to make meaningful inferences when the target is a @@ -1014,11 +1014,13 @@ func (c *Checker) inferToMappedType(n *InferenceState, source *Type, target *Typ // variable T[P] (i.e. we treat the type T[P] as the type variable we're inferring for). func (c *Checker) inferTypeForHomomorphicMappedType(source *Type, target *Type, constraint *Type) *Type { key := ReverseMappedTypeKey{sourceId: source.id, targetId: target.id, constraintId: constraint.id} - if cached := c.reverseHomomorphicMappedCache[key]; cached != nil { + if cached, _ := c.reverseHomomorphicMappedCache.get(c, key); cached != nil { return cached } + c.reverseHomomorphicMappedCache.begin(c, key) t := c.createReverseMappedType(source, target, constraint) - c.reverseHomomorphicMappedCache[key] = t + c.reverseHomomorphicMappedCache.set(c, key, t) + c.endMemo() return t } @@ -1076,9 +1078,13 @@ func (c *Checker) isPartiallyInferableType(t *Type) bool { func (c *Checker) inferReverseMappedType(source *Type, target *Type, constraint *Type) *Type { key := ReverseMappedTypeKey{sourceId: source.id, targetId: target.id, constraintId: constraint.id} - if cached, ok := c.reverseMappedCache[key]; ok { + if cached, ok := c.reverseMappedCache.get(c, key); ok { return core.OrElse(cached, c.unknownType) } + c.reverseMappedCache.begin(c, key) + if len(c.reverseMappedSourceStack) == 0 { + c.reverseMappedStart = int32(len(c.resolutions) - 1) + } c.reverseMappedSourceStack = append(c.reverseMappedSourceStack, source) c.reverseMappedTargetStack = append(c.reverseMappedTargetStack, target) saveExpandingFlags := c.reverseExpandingFlags @@ -1091,11 +1097,16 @@ func (c *Checker) inferReverseMappedType(source *Type, target *Type, constraint var t *Type if c.reverseExpandingFlags != ExpandingFlagsBoth { t = c.inferReverseMappedTypeWorker(source, target, constraint) + } else { + // Stopping depends on the enclosing reverse mappings, not just on key, so + // only the outermost one, which has none, may keep a result. + c.markCircular(int(c.reverseMappedStart)) } c.reverseMappedSourceStack = c.reverseMappedSourceStack[:len(c.reverseMappedSourceStack)-1] c.reverseMappedTargetStack = c.reverseMappedTargetStack[:len(c.reverseMappedTargetStack)-1] c.reverseExpandingFlags = saveExpandingFlags - c.reverseMappedCache[key] = t + c.reverseMappedCache.set(c, key, t) + c.endMemo() return t } @@ -1155,16 +1166,16 @@ func (c *Checker) resolveReverseMappedTypeMembers(t *Type) { } members.Set(prop.Name(), inferredProp) } - c.setStructuredTypeMembers(t, members, nil, nil, indexInfos) + c.setStructuredTypeData(t, members, nil, nil, indexInfos) } func (c *Checker) getTypeOfReverseMappedSymbol(symbol *ast.Symbol) *Type { links := c.valueSymbolLinks.Get(symbol) - if links.resolvedType == nil { + if links.resolvedType.get(c) == nil { reverseLinks := c.ReverseMappedSymbolLinks.Get(symbol) - links.resolvedType = core.OrElse(c.inferReverseMappedType(reverseLinks.propertyType, reverseLinks.mappedType, reverseLinks.constraintType), c.unknownType) + links.resolvedType.set(c, core.OrElse(c.inferReverseMappedType(reverseLinks.propertyType, reverseLinks.mappedType, reverseLinks.constraintType), c.unknownType)) } - return links.resolvedType + return links.resolvedType.get(c) } // If the original mapped type had an intersection constraint we extract its components, @@ -1250,7 +1261,7 @@ func (c *Checker) createEmptyObjectTypeFromStringLiteral(t *Type) *Type { } name := getStringLiteralValue(t) literalProp := c.newSymbol(ast.SymbolFlagsProperty, name) - c.valueSymbolLinks.Get(literalProp).resolvedType = c.anyType + c.valueSymbolLinks.Get(literalProp).resolvedType.setIdentity(c, c.anyType) if t.symbol != nil { literalProp.SetDeclarations(t.symbol.Declarations()) literalProp.SetValueDeclaration(t.symbol.ValueDeclaration()) diff --git a/tsc/internal/checker/jsx.go b/tsc/internal/checker/jsx.go index 1054f4d46f8c5..02c7ed54d54e1 100644 --- a/tsc/internal/checker/jsx.go +++ b/tsc/internal/checker/jsx.go @@ -32,11 +32,11 @@ const ( ) type JsxElementLinks struct { - jsxFlags JsxFlags // Flags for the JSX element - resolvedJsxElementAttributesType *Type // Resolved element attributes type of a JSX opening-like element - jsxNamespace *ast.Symbol // Resolved JSX namespace symbol for this node - jsxImplicitImportContainer *ast.Symbol // Resolved module symbol the implicit JSX import of this file should refer to - firstJSXTagInFile *ast.Node // The first JSX tag in the file + jsxFlags JsxFlags // Flags for the JSX element + resolvedJsxElementAttributesType memo[*Type] // Resolved element attributes type of a JSX opening-like element + jsxNamespace memo[*ast.Symbol] // Resolved JSX namespace symbol for this node + jsxImplicitImportContainer *ast.Symbol // Resolved module symbol the implicit JSX import of this file should refer to + firstJSXTagInFile *ast.Node // The first JSX tag in the file } var JsxNames = struct { @@ -265,9 +265,10 @@ func (c *Checker) getContextualTypeForChildJsxExpression(node *ast.Node, child * func (c *Checker) discriminateContextualTypeByJSXAttributes(node *ast.Node, contextualType *Type) *Type { key := DiscriminatedContextualTypeKey{nodeId: ast.GetNodeId(node), typeId: contextualType.id} - if discriminated := c.discriminatedContextualTypes[key]; discriminated != nil { + if discriminated, _ := c.discriminatedContextualTypes.get(c, key); discriminated != nil { return discriminated } + c.discriminatedContextualTypes.begin(c, key) jsxChildrenPropertyName := c.getJsxElementChildrenPropertyName(c.getJsxNamespaceAt(node)) discriminantProperties := core.Filter(node.Properties(), func(p *ast.Node) bool { symbol := p.Symbol() @@ -289,7 +290,8 @@ func (c *Checker) discriminateContextualTypeByJSXAttributes(node *ast.Node, cont }) discriminator := &ObjectLiteralDiscriminator{c: c, props: discriminantProperties, members: discriminantMembers} discriminated := c.discriminateTypeByDiscriminableItems(contextualType, discriminator) - c.discriminatedContextualTypes[key] = discriminated + c.discriminatedContextualTypes.set(c, key, discriminated) + c.endMemo() return discriminated } @@ -500,15 +502,22 @@ func (c *Checker) getSuggestedSymbolForNonexistentJSXAttribute(name string, cont func (c *Checker) getJSXFragmentType(node *ast.Node) *Type { // An opening fragment is required in order for `getJsxNamespace` to give the fragment factory links := c.sourceFileLinks.Get(ast.GetSourceFileOfNode(node)) - if links.jsxFragmentType != nil { - return links.jsxFragmentType + if t := links.jsxFragmentType.get(c); t != nil { + return t } + links.jsxFragmentType.begin(c) + t := c.getJSXFragmentTypeUncached(node) + links.jsxFragmentType.set(c, t) + c.endMemo() + return t +} + +func (c *Checker) getJSXFragmentTypeUncached(node *ast.Node) *Type { jsxFragmentFactoryName := c.getJsxNamespace(node) // #38720/60122, allow null as jsxFragmentFactory shouldResolveFactoryReference := (c.compilerOptions.Jsx == core.JsxEmitReact || c.compilerOptions.JsxFragmentFactory != "") && jsxFragmentFactoryName != "null" if !shouldResolveFactoryReference { - links.jsxFragmentType = c.anyType - return links.jsxFragmentType + return c.anyType } jsxFactorySymbol := c.getJsxNamespaceContainerForImplicitImport(node) if jsxFactorySymbol == nil { @@ -520,12 +529,10 @@ func (c *Checker) getJSXFragmentType(node *ast.Node) *Type { jsxFactorySymbol = c.resolveName(node, jsxFragmentFactoryName, flags, diagnostics.Using_JSX_fragments_requires_fragment_factory_0_to_be_in_scope_but_it_could_not_be_found, true /*isUse*/, false /*excludeGlobals*/) } if jsxFactorySymbol == nil { - links.jsxFragmentType = c.errorType - return links.jsxFragmentType + return c.errorType } if jsxFactorySymbol.Name() == ReactNames.Fragment { - links.jsxFragmentType = c.getTypeOfSymbol(jsxFactorySymbol) - return links.jsxFragmentType + return c.getTypeOfSymbol(jsxFactorySymbol) } resolvedAlias := jsxFactorySymbol if jsxFactorySymbol.Flags()&ast.SymbolFlagsAlias != 0 { @@ -535,11 +542,9 @@ func (c *Checker) getJSXFragmentType(node *ast.Node) *Type { reactExports := c.getExportsOfSymbol(resolvedAlias) typeSymbol := c.getSymbol(reactExports, ReactNames.Fragment, ast.SymbolFlagsBlockScopedVariable) if typeSymbol != nil { - links.jsxFragmentType = c.getTypeOfSymbol(typeSymbol) - } else { - links.jsxFragmentType = c.errorType + return c.getTypeOfSymbol(typeSymbol) } - return links.jsxFragmentType + return c.errorType } func (c *Checker) resolveJsxOpeningLikeElement(node *ast.Node, candidatesOutArray *[]*Signature, checkMode CheckMode) *Signature { @@ -748,7 +753,7 @@ func (c *Checker) createJsxAttributesTypeFromAttributesProperty(openingLikeEleme attributeSymbol.SetValueDeclaration(member.ValueDeclaration()) } links := c.valueSymbolLinks.Get(attributeSymbol) - links.resolvedType = exprType + links.resolvedType.set(c, exprType) links.target = member attributesTable.Set(attributeSymbol.Name(), attributeSymbol) if allAttributesTable != nil { @@ -836,11 +841,11 @@ func (c *Checker) createJsxAttributesTypeFromAttributesProperty(openingLikeEleme links := c.valueSymbolLinks.Get(childrenPropSymbol) switch { case len(childTypes) == 1: - links.resolvedType = childTypes[0] + links.resolvedType.set(c, childTypes[0]) case childrenContextualType != nil && someType(childrenContextualType, c.isTupleLikeType): - links.resolvedType = c.createTupleType(childTypes) + links.resolvedType.set(c, c.createTupleType(childTypes)) default: - links.resolvedType = c.createArrayType(c.getUnionType(childTypes)) + links.resolvedType.set(c, c.createArrayType(c.getUnionType(childTypes))) } // Fake up a property declaration for the children childrenPropSymbol.SetValueDeclaration(c.factory.NewPropertySignatureDeclaration(nil, c.factory.NewIdentifier(jsxChildrenPropertyName), nil /*postfixToken*/, nil /*type*/, nil /*initializer*/)) @@ -1178,7 +1183,7 @@ func (c *Checker) createSignatureForJSXIntrinsic(node *ast.Node, result *Type) * // returnNode := typeSymbol && c.nodeBuilder.symbolToEntityName(typeSymbol, ast.SymbolFlagsType, node) // declaration := factory.createFunctionTypeNode(nil, []ParameterDeclaration{factory.createParameterDeclaration(nil, nil /*dotDotDotToken*/, "props", nil /*questionToken*/, c.nodeBuilder.typeToTypeNode(result, node))}, ifElse(returnNode != nil, factory.createTypeReferenceNode(returnNode, nil /*typeArguments*/), factory.createKeywordTypeNode(ast.KindAnyKeyword))) parameterSymbol := c.newSymbol(ast.SymbolFlagsFunctionScopedVariable, "props") - c.valueSymbolLinks.Get(parameterSymbol).resolvedType = result + c.valueSymbolLinks.Get(parameterSymbol).resolvedType.set(c, result) return c.newSignature(SignatureFlagsNone, nil, nil, nil, []*ast.Symbol{parameterSymbol}, elementType, nil, 1) } @@ -1188,23 +1193,34 @@ func (c *Checker) createSignatureForJSXIntrinsic(node *ast.Node, result *Type) * func (c *Checker) getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node *ast.Node) *Type { debug.Assert(isJsxIntrinsicTagName(node.TagName())) links := c.jsxElementLinks.Get(node) - if links.resolvedJsxElementAttributesType != nil { - return links.resolvedJsxElementAttributesType + if t := links.resolvedJsxElementAttributesType.get(c); t != nil { + return t } + if c.memoActive() { + links.resolvedJsxElementAttributesType.begin(c) + result := c.getIntrinsicAttributesTypeFromJsxOpeningLikeElementUncached(node, links) + c.endMemo() + return result + } + return c.getIntrinsicAttributesTypeFromJsxOpeningLikeElementUncached(node, links) +} + +func (c *Checker) getIntrinsicAttributesTypeFromJsxOpeningLikeElementUncached(node *ast.Node, links *JsxElementLinks) *Type { + if t := links.resolvedJsxElementAttributesType.get(c); t != nil { + return t + } + result := c.errorType symbol := c.getIntrinsicTagSymbol(node) if links.jsxFlags&JsxFlagsIntrinsicNamedElement != 0 { - links.resolvedJsxElementAttributesType = core.OrElse(c.getTypeOfSymbol(symbol), c.errorType) - return links.resolvedJsxElementAttributesType - } - if links.jsxFlags&JsxFlagsIntrinsicIndexedElement != 0 { + result = core.OrElse(c.getTypeOfSymbol(symbol), c.errorType) + } else if links.jsxFlags&JsxFlagsIntrinsicIndexedElement != 0 { indexInfo := c.getApplicableIndexInfoForName(c.getJsxType(JsxNames.IntrinsicElements, node), node.TagName().Text()) if indexInfo != nil { - links.resolvedJsxElementAttributesType = indexInfo.valueType - return links.resolvedJsxElementAttributesType + result = indexInfo.valueType } } - links.resolvedJsxElementAttributesType = c.errorType - return links.resolvedJsxElementAttributesType + links.resolvedJsxElementAttributesType.set(c, result) + return result } // Looks up an intrinsic tag name and returns a symbol that either points to an intrinsic @@ -1212,9 +1228,25 @@ func (c *Checker) getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node *ast. // string index signature (in which case nodeLinks.jsxFlags will be IntrinsicIndexedElement). // May also return unknownSymbol if both of these lookups fail. func (c *Checker) getIntrinsicTagSymbol(node *ast.Node) *ast.Symbol { + if c.memoActive() { + c.symbolNodeLinks.Get(node).resolvedSymbol.begin(c) + result := c.getIntrinsicTagSymbolUncached(node) + c.endMemo() + return result + } + return c.getIntrinsicTagSymbolUncached(node) +} + +// setIntrinsicJsxFlag records how the intrinsic tag symbol of node was found, +// as part of resolving that symbol. +func (c *Checker) setIntrinsicJsxFlag(node *ast.Node, links *SymbolNodeLinks, flag JsxFlags) { + setFlagMemoBits(c, links.resolvedSymbol.key(), &c.jsxElementLinks.Get(node).jsxFlags, JsxFlagsIntrinsicNamedElement|JsxFlagsIntrinsicIndexedElement, flag) +} + +func (c *Checker) getIntrinsicTagSymbolUncached(node *ast.Node) *ast.Symbol { links := c.symbolNodeLinks.Get(node) - if links.resolvedSymbol != nil { - return links.resolvedSymbol + if links.resolvedSymbol.get(c) != nil { + return links.resolvedSymbol.get(c) } intrinsicElementsType := c.getJsxType(JsxNames.IntrinsicElements, node) if !c.isErrorType(intrinsicElementsType) { @@ -1226,32 +1258,32 @@ func (c *Checker) getIntrinsicTagSymbol(node *ast.Node) *ast.Symbol { propName := tagName.Text() intrinsicProp := c.getPropertyOfType(intrinsicElementsType, propName) if intrinsicProp != nil { - c.jsxElementLinks.Get(node).jsxFlags |= JsxFlagsIntrinsicNamedElement - links.resolvedSymbol = intrinsicProp - return links.resolvedSymbol + c.setIntrinsicJsxFlag(node, links, JsxFlagsIntrinsicNamedElement) + links.resolvedSymbol.set(c, intrinsicProp) + return links.resolvedSymbol.get(c) } // Intrinsic string indexer case indexSymbol := c.getApplicableIndexSymbol(intrinsicElementsType, c.getStringLiteralType(propName)) if indexSymbol != nil { - c.jsxElementLinks.Get(node).jsxFlags |= JsxFlagsIntrinsicIndexedElement - links.resolvedSymbol = indexSymbol - return links.resolvedSymbol + c.setIntrinsicJsxFlag(node, links, JsxFlagsIntrinsicIndexedElement) + links.resolvedSymbol.set(c, indexSymbol) + return links.resolvedSymbol.get(c) } if c.getTypeOfPropertyOrIndexSignatureOfType(intrinsicElementsType, propName) != nil { - c.jsxElementLinks.Get(node).jsxFlags |= JsxFlagsIntrinsicIndexedElement - links.resolvedSymbol = intrinsicElementsType.symbol - return links.resolvedSymbol + c.setIntrinsicJsxFlag(node, links, JsxFlagsIntrinsicIndexedElement) + links.resolvedSymbol.set(c, intrinsicElementsType.symbol) + return links.resolvedSymbol.get(c) } // Wasn't found c.error(node, diagnostics.Property_0_does_not_exist_on_type_1, tagName.Text(), "JSX."+JsxNames.IntrinsicElements) - links.resolvedSymbol = c.unknownSymbol - return links.resolvedSymbol + links.resolvedSymbol.set(c, c.unknownSymbol) + return links.resolvedSymbol.get(c) } if c.noImplicitAny { c.error(node, diagnostics.JSX_element_implicitly_has_type_any_because_no_interface_JSX_0_exists, JsxNames.IntrinsicElements) } - links.resolvedSymbol = c.unknownSymbol - return links.resolvedSymbol + links.resolvedSymbol.set(c, c.unknownSymbol) + return links.resolvedSymbol.get(c) } func (c *Checker) getJsxStatelessElementTypeAt(location *ast.Node) *Type { @@ -1302,14 +1334,31 @@ func (c *Checker) getJsxType(name string, location *ast.Node) *Type { } func (c *Checker) getJsxNamespaceAt(location *ast.Node) *ast.Symbol { - var links *JsxElementLinks - if location != nil { - links = c.jsxElementLinks.Get(location) + if location == nil { + return c.getJsxNamespaceAtUncached(location, nil) + } + links := c.jsxElementLinks.Get(location) + if s := links.jsxNamespace.get(c); s != nil && s != c.unknownSymbol { + return s } - if links != nil && links.jsxNamespace != nil && links.jsxNamespace != c.unknownSymbol { - return links.jsxNamespace + if c.memoActive() { + links.jsxNamespace.begin(c) + result := c.getJsxNamespaceAtUncached(location, links) + c.endMemo() + return result + } + return c.getJsxNamespaceAtUncached(location, links) +} + +func (c *Checker) getJsxNamespaceAtUncached(location *ast.Node, links *JsxElementLinks) *ast.Symbol { + var cached *ast.Symbol + if links != nil { + cached = links.jsxNamespace.get(c) + if cached != nil && cached != c.unknownSymbol { + return cached + } } - if links == nil || links.jsxNamespace != c.unknownSymbol { + if cached != c.unknownSymbol { resolvedNamespace := c.getJsxNamespaceContainerForImplicitImport(location) if resolvedNamespace == nil || resolvedNamespace == c.unknownSymbol { namespaceName := c.getJsxNamespace(location) @@ -1319,13 +1368,13 @@ func (c *Checker) getJsxNamespaceAt(location *ast.Node) *ast.Symbol { candidate := c.resolveSymbol(c.getSymbol(c.getExportsOfSymbol(c.resolveSymbol(resolvedNamespace)), JsxNames.JSX, ast.SymbolFlagsNamespace)) if candidate != nil && candidate != c.unknownSymbol { if links != nil { - links.jsxNamespace = candidate + links.jsxNamespace.set(c, candidate) } return candidate } } if links != nil { - links.jsxNamespace = c.unknownSymbol + links.jsxNamespace.set(c, c.unknownSymbol) } } // JSX global fallback @@ -1449,6 +1498,13 @@ func markAsSynthetic(node *ast.Node) bool { func (c *Checker) getJsxNamespaceContainerForImplicitImport(location *ast.Node) *ast.Symbol { file := ast.GetSourceFileOfNode(location) links := c.jsxElementLinks.Get(file.AsNode()) + c.beginMemo(memoKeyOf(links, memoJsxImplicitImportContainer)) + result := c.getJsxNamespaceContainerForImplicitImportCached(file, links) + c.endMemo() + return result +} + +func (c *Checker) getJsxNamespaceContainerForImplicitImportCached(file *ast.SourceFile, links *JsxElementLinks) *ast.Symbol { if links.jsxImplicitImportContainer != nil { return core.IfElse(links.jsxImplicitImportContainer == c.unknownSymbol, nil, links.jsxImplicitImportContainer) } @@ -1479,7 +1535,7 @@ func (c *Checker) getJsxNamespaceContainerForImplicitImport(location *ast.Node) if mod != nil && mod != c.unknownSymbol { result = c.getMergedSymbol(c.resolveSymbol(mod)) } - links.jsxImplicitImportContainer = core.OrElse(result, c.unknownSymbol) + c.setFlagMemoValue(memoKeyOf(links, memoJsxImplicitImportContainer), &links.jsxImplicitImportContainer, core.OrElse(result, c.unknownSymbol)) return result } diff --git a/tsc/internal/checker/links.go b/tsc/internal/checker/links.go index c21981cae9d80..e7a92438685bf 100644 --- a/tsc/internal/checker/links.go +++ b/tsc/internal/checker/links.go @@ -31,8 +31,13 @@ func (s *nodeLinkStore[V]) Has(node *ast.Node) bool { return s.TryGet(node) != nil } +// TryGet returns the links of node, or nil if it has none. A node without +// an id has no links, and looking it up does not give it one. func (s *nodeLinkStore[V]) TryGet(node *ast.Node) *V { - id := uint64(s.gen.GetNodeId(node)) + id := uint64(ast.PeekNodeId(node)) + if id == 0 { + return nil + } if id >= ast.BlockIdOffset && id < ast.BlockIdOffset+maxPageLinkCount { return s.pages.TryGet(id - ast.BlockIdOffset) } @@ -57,8 +62,13 @@ func (s *symbolLinkStore[V]) Has(symbol *ast.Symbol) bool { return s.TryGet(symbol) != nil } +// TryGet returns the links of symbol, or nil if it has none. A symbol +// without an id has no links, and looking it up does not give it one. func (s *symbolLinkStore[V]) TryGet(symbol *ast.Symbol) *V { - id := uint64(s.gen.GetSymbolId(symbol)) + id := uint64(ast.PeekSymbolId(symbol)) + if id == 0 { + return nil + } if id >= ast.BlockIdOffset && id < ast.BlockIdOffset+maxPageLinkCount { return s.pages.TryGet(id - ast.BlockIdOffset) } diff --git a/tsc/internal/checker/memo.go b/tsc/internal/checker/memo.go new file mode 100644 index 0000000000000..3d34083b97c4c --- /dev/null +++ b/tsc/internal/checker/memo.go @@ -0,0 +1,2190 @@ +package checker + +import ( + "iter" + "maps" + "math/bits" + "slices" + "unsafe" + + "github.com/microsoft/TypeScript/tsc/internal/ast" + + "github.com/microsoft/TypeScript/tsc/internal/debug" +) + +// Checker side effects and memoization +// +// Checking a file produces outputs other than types: diagnostics, reference +// marks used by unused-identifier checks, deferred nodes, and so on. Those +// outputs are produced while computing memoized results (the type of a symbol, +// the resolved symbol of an identifier, ...). If such a result is first computed +// for an API query rather than while checking, a later check reuses the cached +// result without recomputing it, so the outputs would historically either leak +// (reported for code that checking never visits) or be lost (never reported for +// code that checking does visit). Both make diagnostics depend on query history. +// +// To make checking independent of query history, a checker is either checking +// or answering queries. A query does not apply side effects. Instead it +// records them against the innermost memoized computation in progress, along +// with the memoized results that computation reused. When a check later +// reuses a memoized result, it replays the recorded effects of that result +// and everything it transitively reused, exactly as if it had computed the +// result itself. Effects produced outside any memoized computation belong +// solely to the query and are discarded. A check applies its own effects as +// they happen, except while results it computes may be discarded, when it +// records them as a query does (see recordFromCheck). + +type memoField uint8 + +const ( + memoCell memoField = iota // A memo[T], keyed by its own address + memoWriteType + memoDeclaredType + memoTypeNodeType + memoBaseTypes + memoStructuredMembers + memoTypeParameterConstraint + memoTypeParameterDefault + memoTypeAlias + memoEnumMemberValue + memoBaseConstraint + memoAccessorType + memoContextualParameters + memoContextChecked + memoInitializerIsUndefined + memoFlowExpressionType + memoFlowLoopType + memoJsxImplicitImportContainer + memoModuleExports + memoInstantiationExpressionType + memoTypePredicate + memoDeclaredMembers + memoCouldContainTypeVariables + memoGenericObjectFlags + memoUnknownLikeUnion + memoNeverIntersection + memoDiscriminant + memoResolvedMembers // followed by one field per MembersOrExportsResolutionKind + memoSymbolExports = memoResolvedMembers + memoField(MembersOrExportsResolutionKindResolvedExports) + memoSymbolMembers = memoResolvedMembers + memoField(MembersOrExportsResolutionKindResolvedMembers) + memoFieldCount = memoResolvedMembers + 2 +) + +// A reentry says what it means to re-enter a computation in progress, which +// can only observe whatever the computation has published so far. +type reentry uint8 + +const ( + // What re-entry observes is incomplete, so the results computed while + // the outer computation is in progress are discarded when it finishes, + // and computed again from its result. This is the default. + reentryCircular reentry = iota + // The computation detects re-entry with a type resolution of its own, + // which decides the head of the cycle and what to discard. + reentryResolution + // The computation publishes a partial result before it finishes, and + // re-entry observes that partial result. Results derived from it may + // differ depending on which computation starts first. + reentryPartial +) + +// memoReentry is the re-entry policy of each kind of computation. +var memoReentry = [memoFieldCount]reentry{ + // memoCompute keeps the result that a re-entrant computation cached, + // and many cells publish an in-progress marker for re-entry to read. + memoCell: reentryPartial, + // Many kinds of types make members available before they are complete. + memoStructuredMembers: reentryPartial, + // Circular defaults are reported at the default first in program order. + memoTypeParameterDefault: reentryResolution, + // Contextual parameter typing resolves a cycle through the enclosing call. + // Its type resolution ends once the parameters are assigned. + memoContextualParameters: reentryResolution, +} + +// memoKey identifies a memoized result: the address of the entity owning the +// cache slot (an *ast.Node, *ast.Symbol, *Type, or *Signature) shifted left by +// eight bits, plus the memoField of the slot. Packing into an integer keeps map +// operations on the fast path. Entities live as long as the program or the +// checker store, so an address cannot be reused while its keys exist. +type memoKey uint64 + +func (k memoKey) field() memoField { + return memoField(k) +} + +func memoKeyOf[T any](entity *T, field memoField) memoKey { + // SAFETY: Only the address is kept, as an identity; it is never turned back + // into a pointer except by symbol, whose entity outlives the key. + return memoKey(uint64(uintptr(unsafe.Pointer(entity)))<<8 | uint64(field)) +} + +// symbol returns the symbol identified by a key of a symbol's memo field. +func (k memoKey) symbol() *ast.Symbol { + p := uintptr(k >> 8) + // SAFETY: k was made by memoKeyOf from a symbol that lives as long as the + // program or checker store, which outlives every key made from it, so the + // address still points to that symbol. + return *(**ast.Symbol)(unsafe.Pointer(&p)) +} + +type memoEffects struct { + effects []func(c *Checker) + deps []*memoEffects + parent *memoEffects // Resolution d was most recently added to; avoids duplicate deps + entry *ast.Node // Current node when the computation started, if entered + rare *memoEffectsRare + entered bool // Whether this is the resolution of a computation, not just a set of deps + replayed bool + // Whether the result was discarded at the end of a cycle, so that its + // own effects are not replayed; see effectsToDrop. + dropped bool +} + +// memoEffectsRare holds the parts of memoEffects that few computations have, +// kept apart since the effects of every computation in a program can be +// retained at once. +type memoEffectsRare struct { + marks []*ast.Symbol // Aliases marked referenced, also among effects; see withReferenceMarks + cycles []memoCycleDep // The cycle heads memoCycle added to deps, from highest to lowest +} + +func (f *memoEffects) rareFields() *memoEffectsRare { + if f.rare == nil { + f.rare = &memoEffectsRare{} + } + return f.rare +} + +func (f *memoEffects) addDep(d *memoEffects) { + if f == d || d.parent == f { + return + } + d.parent = f + f.deps = append(f.deps, d) +} + +// covers reports whether replaying f replays deps, checking only the direct +// dependencies of f, and only of a few, to stay cheap. +func (f *memoEffects) covers(deps []*memoEffects) bool { + if len(deps) > 16 || len(f.deps) > 64 { + return false + } + for _, d := range deps { + if d != f && !slices.Contains(f.deps, d) { + return false + } + } + return true +} + +// memoState is the memo state of one memoized result. +type memoState struct { + pending *memoEffects // Effects not yet replayed by a check + errors *[]*currentNodeError // Errors the computation reported at the current node, out of line since few have any; see setErrors +} + +// memo is a cached result. Cached checker state must live in a memo so that +// every read accounts for the effects of the computation that produced it; +// see the package comment above. The value is only accessible through +// methods. The memo state of a result is kept in the store's memo state map, +// keyed by the address of the memo, so a memo costs nothing beyond its value. +type memo[T any] struct { + memoHeader + memoValue T +} + +// memoHeader is the part of a memo independent of its type, which identifies +// it. +type memoHeader struct { + _ memoNoCopy +} + +// memoNoCopy makes go vet reject copies of a memo, which would bypass it. +type memoNoCopy struct{} + +func (*memoNoCopy) Lock() {} +func (*memoNoCopy) Unlock() {} + +func (h *memoHeader) key() memoKey { + return memoKeyOf(h, memoCell) +} + +// get returns the cached result, accounting for the effects of computing it. +func (m *memo[T]) get(c *Checker) T { + if c.observing() { + c.observeCell(&m.memoHeader) + } + return m.memoValue +} + +// set caches a result. When set outside the result's own computation (for +// example, the types of binding elements assigned while resolving the type of +// their declaration), the result depends on the computation setting it. +func (m *memo[T]) set(c *Checker, v T) { + if c.logResults() { + m.logDiscard(c) + } + m.memoValue = v + if !c.checking { + c.attachCell(&m.memoHeader) + } +} + +// setAfterMarker is set for a result replacing the marker its computation put +// in place when it began. A marker put in place before a cycle began was not +// logged, so discarding the result would restore it; a restored marker that +// stale reports as no longer in progress is cleared instead. Stale is given x +// rather than capturing it so that nothing is allocated unless the set is +// logged. +func setAfterMarker[T comparable, X any](c *Checker, m *memo[T], v T, x X, stale func(*Checker, X, T) bool) { + n := len(c.discardLog) + m.set(c, v) + if len(c.discardLog) > n { + restore := c.discardLog[n].restore + c.discardLog[n].restore = func() { + restore() + if stale(c, x, m.memoValue) { + var zero T + m.memoValue = zero + } + } + } +} + +// setIfChanged is set for a result that every computation needing it sets +// again, such as the symbol a property access resolves to. Setting the value +// it already has comes from the same computation, whose effects are already +// retained, so it is only set when it changes. +func setIfChanged[T comparable](c *Checker, m *memo[T], v T) { + if m.memoValue != v { + m.set(c, v) + } +} + +// setIdentity is set for a result that defines the identity of what it +// caches, such as a declared type, or that is part of an entity being +// created, such as the type of a synthetic property. It is never discarded at +// the end of a cycle: discarding an identity would let a second, distinct +// result exist, and an entity can outlive the cycle in caches that are kept +// while having no way to compute its parts again. +func (m *memo[T]) setIdentity(c *Checker, v T) { + m.memoValue = v + if !c.checking { + c.attachCell(&m.memoHeader) + } +} + +// A memoReported is a memo whose result can have errors to replay; see +// setReported. Reading it accounts for them even when nothing else must be +// accounted for. +type memoReported[T comparable] struct { + memo[T] +} + +func (m *memoReported[T]) get(c *Checker) T { + if c.observing() || c.memoErrorStates != 0 { + c.observeCell(&m.memoHeader) + } + return m.memoValue +} + +// setReported is setIdentity for a result whose computation collected errors +// reported at the current node, as returned by endCurrentNodeErrors. Every +// node reusing the result reports them again, as it would had it computed the +// result itself. If computing the result elsewhere may not report them (see +// endCurrentNodeErrors), it is instead discarded with other results cached +// for the current node. +func (m *memoReported[T]) setReported(c *Checker, v T, r currentNodeErrors) { + if r.cyclic { + if c.logResults() { + m.logDiscard(c) + } + r.list = nil + } + m.setIdentity(c, v) + if r.list != nil || c.memoErrorStates != 0 && c.peekMemoState(m.key()) != nil { + if r.list != nil { + i := errorFilterIndex(m.key()) + c.errorFilter[i/64] |= 1 << (i % 64) + } + c.memoState(m.key()).setErrors(c, r.list) + } +} + +//go:noinline +func (m *memo[T]) logDiscard(c *Checker) { + drop := c.effectsToDropFor(m.key()) + old := m.memoValue + c.discardLog = append(c.discardLog, discardEntry{&m.memoHeader, func() { + m.memoValue = old + drop.drop() + }, false}) +} + +// peek returns the cached result without accounting for effects. It is only +// for a result whose computation is in progress, where reading it does not +// observe it. +func (m *memo[T]) peek() T { + return m.memoValue +} + +// exchange replaces the cached result without accounting for effects. It is +// only for speculative computations that restore the previous result after. +func (m *memo[T]) swap(v T) T { + old := m.memoValue + m.memoValue = v + return old +} + +// init sets the result of a cell whose owner is being created. Nothing can +// observe the owner before its creator returns it, so the result needs no +// dependency of its own. +func (m *memo[T]) intern(v T) { + m.memoValue = v +} + +// enter starts the computation of the result; see beginMemo. +func (m *memo[T]) begin(c *Checker) { + c.beginMemo(m.key()) +} + +// memoCompute returns the result cached in m, computing it in a computation +// of its own if there is none. A result cached while computing it, by a +// computation that re-entered it, is kept. +func (c *Checker) memoCompute[T comparable](m *memo[T], compute func() T) T { + var zero T + if v := m.get(c); v != zero { + return v + } + m.begin(c) + v := compute() + if cur := m.peek(); cur != zero { + v = cur + } else { + m.set(c, v) + } + c.endMemo() + return v +} + +// internMap is a map of results each constructed from the inputs in its key +// alone, without consulting any cached result, so caching or reusing one has +// no effects to account for. +type internMap[K comparable, V any] struct { + _ memoNoCopy + entries map[K]V +} + +func (m *internMap[K, V]) get(k K) (V, bool) { + v, ok := m.entries[k] + return v, ok +} + +func (m *internMap[K, V]) has(k K) bool { + _, ok := m.entries[k] + return ok +} + +func (m *internMap[K, V]) intern(k K, v V) { + if m.entries == nil { + m.entries = make(map[K]V) + } + m.entries[k] = v +} + +func (m *internMap[K, V]) isEmpty() bool { + return len(m.entries) == 0 +} + +func (m *internMap[K, V]) values() iter.Seq[V] { + return maps.Values(m.entries) +} + +// memoMap is a map of cached results, each accounted for like a memo. The +// entries are only accessible through methods. Memo state is kept per entry +// in a side map allocated on first use by a query. +// +// Most maps never need anything but their entries, and many are embedded in +// types, so everything else lives in a side table allocated on first use. +// +// An entry should be computed between enter and endMemo, so that reusing it +// replays the effects of computing that entry alone. An entry set outside its +// own computation depends on the whole enclosing computation instead. +type memoMap[K comparable, V any] struct { + _ memoNoCopy + memoEntries map[K]V + side *memoMapSide[K] + // The number of writes to memoEntries, with which a caller can tell + // that an entry it read is still current without reading it again. + writes uint32 +} + +type memoMapSide[K comparable] struct { + memoStates map[K]*memoState + resolvingKeys []K // Keys of computations in progress without a memo state yet + resolvingAt []int32 // Index in Checker.resolutions of each + // The occurrences of each key in resolvingKeys, kept while there are + // many, so that looking one up does not scan them all. + resolvingCount map[K]resolvingOccurrences + // If set, keyBits returns well-mixed bits of a key, with which stateKeys + // filters the keys that may have a memo state, since few do and looking + // up one that does not costs as much as one that does. + keyBits func(K) uint64 + stateKeys []uint64 + hasErrors bool // Whether an entry ever had reports; see setReported +} + +func (m *memoMap[K, V]) sideTable() *memoMapSide[K] { + if m.side == nil { + m.side = &memoMapSide[K]{} + } + return m.side +} + +// setKeyBits makes the map filter the keys that may have a memo state with +// keyBits; see memoMapSide. +func (m *memoMap[K, V]) setKeyBits(keyBits func(K) uint64) { + m.sideTable().keyBits = keyBits +} + +// states returns the memo states of the entries, or nil. +func (m *memoMap[K, V]) states() map[K]*memoState { + if m.side == nil { + return nil + } + return m.side.memoStates +} + +const memoMapStateFilterBits = 20 + +// stateOf returns the memo state of the entry for k, or nil. +func (m *memoMap[K, V]) stateOf(k K) *memoState { + s := m.side + if s == nil || s.memoStates == nil { + return nil + } + if s.keyBits != nil { + if i := s.keyBits(k) >> (64 - memoMapStateFilterBits); s.stateKeys[i/64]&(1<<(i%64)) == 0 { + return nil + } + } + return s.memoStates[k] +} + +// get returns the cached result for k, accounting for the effects of +// computing it. +func (m *memoMap[K, V]) get(c *Checker, k K) (V, bool) { + if m.side != nil && (c.observing() || m.side.hasErrors) { + m.observe(c, k) + } + v, ok := m.memoEntries[k] + return v, ok +} + +// peek returns the cached result for k without observing it, for results +// that are interned and never change. +func (m *memoMap[K, V]) peek(k K) (V, bool) { + v, ok := m.memoEntries[k] + return v, ok +} + +// observe is the out of line part of get, keeping get cheap enough to inline. +// +//go:noinline +func (m *memoMap[K, V]) observe(c *Checker, k K) { + if m.side.memoStates == nil && len(m.side.resolvingKeys) == 0 { + return + } + if c.checking && c.memoPendingCount == 0 && !m.side.hasErrors { + // Only reports are replayed; see observeMemo. + return + } + // A computation in progress may not have a memo state yet; see + // resolutionState. + if state := m.stateOf(k); c.observeState(state) || state == nil && !c.checking { + c.memoCycle(m.resolvingIndex(c, k)) + } +} + +// has reports whether a result is cached for k; see get. +func (m *memoMap[K, V]) has(c *Checker, k K) bool { + _, ok := m.get(c, k) + return ok +} + +// isEmpty reports whether no result is cached, without observing any. +func (m *memoMap[K, V]) isEmpty() bool { + return len(m.memoEntries) == 0 +} + +// values iterates the cached results without observing them, for +// inspection outside of checking. +func (m *memoMap[K, V]) values() iter.Seq[V] { + return maps.Values(m.memoEntries) +} + +// set caches the result for k; see memo.set. +func (m *memoMap[K, V]) set(c *Checker, k K, v V) { + if c.logResults() { + m.logDiscard(c, k) + } + m.setIdentity(c, k, v) +} + +//go:noinline +func (m *memoMap[K, V]) logDiscard(c *Checker, k K) { + drop := c.effectsToDrop(c.memoTop != 0 && c.resolutions[c.memoTop-1].source == memoStateSource(m) && m.side.resolvingKeys[c.resolutions[c.memoTop-1].index] == k) + old, ok := m.memoEntries[k] + c.discardLog = append(c.discardLog, discardEntry{nil, func() { + drop.drop() + m.writes++ + if ok { + m.memoEntries[k] = old + } else { + delete(m.memoEntries, k) + } + }, false}) +} + +// setReported is setIdentity for an entry; see memo.setReported. +func (m *memoMap[K, V]) setReported(c *Checker, k K, v V, r currentNodeErrors) { + if r.cyclic { + if c.logResults() { + m.logDiscard(c, k) + } + r.list = nil + } + m.setIdentity(c, k, v) + if r.list != nil || c.memoErrorStates != 0 && m.stateOf(k) != nil { + if r.list != nil { + m.sideTable().hasErrors = true + } + m.state(k).setErrors(c, r.list) + } +} + +// setIdentity is set for a result that defines the identity of what it +// caches; see memo.setIdentity. +func (m *memoMap[K, V]) setIdentity(c *Checker, k K, v V) { + if m.memoEntries == nil { + m.memoEntries = make(map[K]V) + } + m.memoEntries[k] = v + m.writes++ + m.attach(c, k) +} + +// attach is memoAttach for the entry for k. +func (m *memoMap[K, V]) attach(c *Checker, k K) { + if c.memoTop == 0 || c.checking { + return + } + if f := &c.resolutions[c.memoTop-1]; f.source == memoStateSource(m) && m.side.resolvingKeys[f.index] == k { + return + } + c.attachState(m.state(k)) +} + +// intern caches a result constructed from the inputs in k alone, without +// consulting any cached result, so computing it has no effects to account for. +func (m *memoMap[K, V]) intern(k K, v V) { + if m.memoEntries == nil { + m.memoEntries = make(map[K]V) + } + m.memoEntries[k] = v + m.writes++ +} + +// compute computes and caches the result for k in a computation of its own. +func (m *memoMap[K, V]) compute(c *Checker, k K, fn func() V) V { + m.begin(c, k) + v := fn() + m.set(c, k, v) + c.endMemo() + return v +} + +// enter starts the computation of the result for k; see beginMemo. +func (m *memoMap[K, V]) begin(c *Checker, k K) { + side := m.sideTable() + outer := 0 + if len(side.resolvingKeys) != 0 { + outer = m.resolvingIndex(c, k) + } + if outer != 0 { + // Re-entering an entry marks the cycle circular; see reentryCircular. + c.markCircular(outer - 1) + c.markReadIncomplete(outer - 1) + } + side.resolvingKeys = append(side.resolvingKeys, k) + side.resolvingAt = append(side.resolvingAt, int32(len(c.resolutions))) + if side.resolvingCount != nil { + side.countResolving(k, len(side.resolvingKeys)-1) + } else if len(side.resolvingKeys) > resolvingCountMin { + side.countAllResolving() + } + c.beginResolution(m, len(side.resolvingKeys)-1) + if c.checking && c.memoPendingCount == 0 && c.memoTop|c.currentNodeResolution != 0 && !c.logEffects() { + // beginCheckedMemo for the common case, without the calls. The memo + // state of the result is looked up only if needed; see + // resolutionState. + n := len(c.resolutions) - 1 + f := &c.resolutions[n] + f.memo = true + f.prevMemo = int32(c.memoTop) + f.entry = c.currentNode + f.attachStart = int32(len(c.deferredAttach)) + c.memoTop = n + 1 + return + } + // Looked up after marking a cycle, which may give the entry a state. + state := m.stateOf(k) + if c.checking { + if c.memoPendingCount != 0 && state != nil { + c.replayPending(state) + } + c.beginCheckedMemo(state) + return + } + c.beginState(state, outer) +} + +// resolvingIndex returns 1 + the index in resolutions of the computation of +// the entry for k in progress, or 0. +func (m *memoMap[K, V]) resolvingIndex(c *Checker, k K) int { + if m.side == nil { + return 0 + } + start := 0 + if m.side.resolvingCount != nil { + o, ok := m.side.resolvingCount[k] + if !ok { + return 0 + } + start = int(o.first) + if o.count == 1 { + if j := int(m.side.resolvingAt[start]); j >= c.visibleStart { + return j + 1 + } + return 0 + } + } + for i := start; i < len(m.side.resolvingKeys); i++ { + if m.side.resolvingKeys[i] != k { + continue + } + j := int(m.side.resolvingAt[i]) + debug.Assert(c.resolutions[j].source == memoStateSource(m) && int(c.resolutions[j].index) == i, "entry in progress without a resolution") + if j >= c.visibleStart { + return j + 1 + } + // Hidden; see yieldCycle. + } + return 0 +} + +// isOwnResolution reports whether the current resolution computes the entry +// for k. +func (m *memoMap[K, V]) isOwnResolution(c *Checker, k K) bool { + f := c.currentResolution() + return f.source == memoStateSource(m) && m.side.resolvingKeys[f.index] == k +} + +func (m *memoMap[K, V]) stateAt(i int) *memoState { + return m.state(m.side.resolvingKeys[i]) +} + +func (m *memoMap[K, V]) peekStateAt(i int) *memoState { + return m.stateOf(m.side.resolvingKeys[i]) +} + +func (m *memoMap[K, V]) popState(i int) { + if counts := m.side.resolvingCount; counts != nil { + if i < resolvingCountMin/4 { + m.side.resolvingCount = nil + } else { + for _, k := range m.side.resolvingKeys[i:] { + // Keys are popped last in first out, so the first + // occurrence is popped last. + if o := counts[k]; o.count == 1 { + delete(counts, k) + } else { + o.count-- + counts[k] = o + } + } + } + } + clear(m.side.resolvingKeys[i:]) + m.side.resolvingKeys = m.side.resolvingKeys[:i] + m.side.resolvingAt = m.side.resolvingAt[:i] +} + +// Number of computations of a memo map in progress above which they are +// counted by key; see resolvingCount. +const resolvingCountMin = 64 + +type resolvingOccurrences struct { + first int32 // Index in resolvingKeys of the first + count int32 +} + +func (side *memoMapSide[K]) countAllResolving() { + side.resolvingCount = make(map[K]resolvingOccurrences, 2*len(side.resolvingKeys)) + for i, k := range side.resolvingKeys { + side.countResolving(k, i) + } +} + +func (side *memoMapSide[K]) countResolving(k K, i int) { + o, ok := side.resolvingCount[k] + if !ok { + o.first = int32(i) + } + o.count++ + side.resolvingCount[k] = o +} + +func (m *memoMap[K, V]) state(k K) *memoState { + state := m.stateOf(k) + if state == nil { + s := m.sideTable() + if s.memoStates == nil { + s.memoStates = make(map[K]*memoState) + if s.keyBits != nil { + s.stateKeys = make([]uint64, 1<> (64 - memoMapStateFilterBits) + s.stateKeys[i/64] |= 1 << (i % 64) + } + } + return state +} + +// memoSet tracks computations that run at most once per key, such as checks +// whose only results are diagnostics. Each run is a computation of its own, so +// skipping a key that already ran accounts for the effects of that run. +type memoSet[K comparable] struct { + m memoMap[K, struct{}] +} + +// has reports whether k has run; see memoMap.get. +func (s *memoSet[K]) has(c *Checker, k K) bool { + return s.m.has(c, k) +} + +// once runs fn unless k has run already. k is marked before fn runs, so fn +// does not run again if it re-enters. +func (s *memoSet[K]) once(c *Checker, k K, fn func()) { + if s.m.has(c, k) { + return + } + s.m.begin(c, k) + s.m.set(c, k, struct{}{}) + fn() + c.endMemo() +} + +// memoActive reports whether reading a cached result must account for its +// effects: false in a check with nothing pending. +func (c *Checker) memoActive() bool { + return !c.checking || c.memoPendingCount|c.memoErrorStates != 0 +} + +// observing reports whether reading a cached result that cannot have errors +// to replay must account for its effects: memoActive, except for results +// with errors, which only memoReported and memoMap cache. It is kept in a +// field for the many reads that check it. +func (c *Checker) observing() bool { + return c.observingMemos +} + +// syncObserving updates observingMemos after a change to what it depends +// on: the checking flag or the counts it reads. The counts are shared with other checkers using the same +// store, but those only run while this one is not checking, and it syncs on +// starting to check. +func (c *Checker) syncObserving() { + c.observingMemos = !c.checking || c.memoPendingCount != 0 +} + +// readsWithoutMemo reports whether a memoized computation identified by key, +// whose result is cached, can read it without beginning the memo: when no +// memo is active, the memo records and replays nothing, and the computation +// only reads, unless it re-enters itself in progress, with the result +// published before it is complete. +func (c *Checker) readsWithoutMemo(key memoKey) bool { + if c.observing() { + return false + } + _, resolving := c.resolving.get(key) + return !resolving +} + +// beginMemo is called on entry to a memoized function whose result for this +// input is cached in the memo state identified by key. It accounts for +// effects recorded by a previous computation of that result, and starts a +// resolution for this computation, which in a query records its effects. It +// must be matched by endMemo. +func (c *Checker) beginMemo(key memoKey) { + resolving := c.beginResolving(key) + c.appendResolution(key, nil, 0, int32(len(c.instantiationStack))) + n := len(c.resolutions) - 1 + f := &c.resolutions[n] + f.resolving = resolving + if c.checking && c.memoPendingCount == 0 && c.memoTop|c.currentNodeResolution != 0 && !c.logEffects() { + // beginCheckedMemo for the common case, without the calls. The memo + // state of the result is looked up only if needed; see + // resolutionState. + f.memo = true + f.prevMemo = int32(c.memoTop) + f.entry = c.currentNode + f.attachStart = int32(len(c.deferredAttach)) + c.memoTop = n + 1 + return + } + state := c.peekMemoState(key) + if c.checking { + if c.memoPendingCount != 0 { + c.replayPending(state) + } + c.beginCheckedMemo(state) + return + } + outer := c.resolvingIndex(key) + if outer == len(c.resolutions) { + outer = 0 + } + c.beginState(state, outer) +} + +// beginState is beginMemo for the memo state of a result, in a query. outer +// is 1 + the index in resolutions of the computation of the same result in +// progress, if this one re-enters it, or 0. +func (c *Checker) beginState(state *memoState, outer int) { + if state != nil && state.pending != nil { + c.observePending(state.pending) + } + c.startEffects(state) + // Re-entering a computation in progress: the inner result is derived + // from whatever in-progress marker the outer computation left behind. + c.memoCycle(outer) +} + +// startEffects starts recording the effects of the current resolution, which +// computes the result in state. +func (c *Checker) startEffects(state *memoState) { + n := len(c.resolutions) - 1 + f := &c.resolutions[n] + f.memo = true + f.prevMemo = int32(c.memoTop) + f.result = state + f.entry = c.currentNode + f.attachStart = int32(len(c.deferredAttach)) + if c.memoTop == 0 && c.currentNodeResolution == 0 { + // A query made outside of checking: errors located at the current + // node are located wherever checking reuses the result, so results + // they depend on are discarded once the query finishes (see + // reportAtCurrentNode), and every reuse computes them again. The + // node's resolution is entered above the computation's resolution. + f.isQuery = true + debug.Assert(!c.inQuery, "nested query") + c.inQuery = true + c.querySave = c.enterCurrentNode(c.currentNode) + } + c.memoTop = n + 1 +} + +// stopEffects stops recording the effects of the current resolution, +// returning it if it recorded them. +func (c *Checker) stopEffects() *resolution { + n := len(c.resolutions) - 1 + if c.resolutions[n].isTypeResolution { + n-- + debug.Assert(c.resolutions[n].isQuery, "resolution left on the stack") + c.exitCurrentNode(c.querySave) + c.inQuery = false + } + f := &c.resolutions[n] + if !f.memo { + return nil + } + c.memoTop = int(f.prevMemo) + return f +} + +func (c *Checker) memoState(key memoKey) *memoState { + state := c.memoStates[key] + if state == nil { + if c.memoStates == nil { + c.memoStates = make(map[memoKey]*memoState) + } + state = &memoState{} + c.memoStates[key] = state + c.stateFilter.add(key, c.memoStates) + } + return state +} + +// stateKeyFilter is a filter of the keys of memoStates, sized to the number +// of states so that it stays in cache: few results have a memo state. +type stateKeyFilter struct { + words []uint64 + shift uint8 // 64 - log2 of the number of bits +} + +// Bits per state below which the filter grows, keeping false positives +// to a few percent. +const stateFilterBitsPerState = 16 + +func (f *stateKeyFilter) index(key memoKey) uint64 { + return uint64(key) * 0x9E3779B97F4A7C15 >> f.shift +} + +func (f *stateKeyFilter) mayContain(key memoKey) bool { + if f.words == nil { + return false + } + i := f.index(key) + return f.words[i/64]&(1<<(i%64)) != 0 +} + +// add adds key, which states has just been given a state for. +func (f *stateKeyFilter) add(key memoKey, states map[memoKey]*memoState) { + if n := 64 * len(f.words); n < stateFilterBitsPerState*len(states) { + n = max(2*n, 1<<15) + f.words = make([]uint64, n/64) + f.shift = uint8(65 - bits.Len(uint(n))) + for k := range states { + f.set(k) + } + return + } + f.set(key) +} + +func (f *stateKeyFilter) set(key memoKey) { + i := f.index(key) + f.words[i/64] |= 1 << (i % 64) +} + +const errorFilterBits = 12 + +type errorKeyFilter [1 << errorFilterBits / 64]uint64 + +func errorFilterIndex(key memoKey) uint64 { + return uint64(key) * 0x9E3779B97F4A7C15 >> (64 - errorFilterBits) +} + +// peekMemoState returns the memo state of the result identified by key, or +// nil if it has none, consulting a filter first since most results have none. +func (c *Checker) peekMemoState(key memoKey) *memoState { + if !c.stateFilter.mayContain(key) { + return nil + } + return c.memoStates[key] +} + +// memoAttach makes the result identified by key depend on the innermost +// computation in progress, unless that computation is the result's own. +func (c *Checker) memoAttach(key memoKey) { + if c.memoTop == 0 || c.checking { + return + } + if f := &c.resolutions[c.memoTop-1]; f.source == nil && f.key == key { + return + } + c.attachState(c.memoState(key)) +} + +// attachState is memoAttach for the memo state of a result. +func (c *Checker) attachState(state *memoState) { + n := c.memoTop - 1 + if n < 0 { + return + } + if c.peekResolutionState(&c.resolutions[n]) == state { + // Writers skip their own computation before creating its state, so + // a state created later is never the computation's own. + return + } + if c.resolutions[n].effects == nil && !c.logEffects() { + // Until the computation has effects, the result depends on nothing, + // and most computations never have any; memoEffectsAt attaches the + // result if it gets some. + c.deferredAttach = append(c.deferredAttach, state) + return + } + c.linkState(state, c.memoEffectsAt(n)) +} + +// linkState makes the result in state depend on the effects f of the +// computation that cached it. +func (c *Checker) linkState(state *memoState, f *memoEffects) { + if c.logResults() { + c.logPendingDiscard(state) + } + switch { + case state.pending == nil: + // Replaying f is replaying a set of deps holding only f. + state.pending = f + c.memoPendingCount++ + c.syncObserving() + if c.flushStates != nil { + *c.flushStates = append(*c.flushStates, state) + } + case state.pending == f: + // Already depends on f. + case state.pending.entered: + // The effects of a computation are shared with whatever else + // depends on it, so they cannot gain a dependency of the result. + deps := &memoEffects{} + deps.addDep(state.pending) + deps.addDep(f) + state.pending = deps + default: + state.pending.addDep(f) + } +} + +// observeCell and attachCell are observeMemo and memoAttach for a memo at +// address p. They are kept out of line so that memo reads and writes are +// cheap enough to inline. +// +//go:noinline +func (c *Checker) observeCell(h *memoHeader) { + c.observeMemo(h.key()) +} + +//go:noinline +func (c *Checker) attachCell(h *memoHeader) { + c.memoAttach(h.key()) +} + +// observeMemo accounts for reading a memoized result without computing it. +func (c *Checker) observeMemo(key memoKey) { + if !c.memoActive() { + return + } + if c.checking && c.memoPendingCount == 0 { + // Only reports are replayed, which few results have, so a filter + // small enough to stay in cache rules out most. + if i := errorFilterIndex(key); c.errorFilter[i/64]&(1<<(i%64)) == 0 { + return + } + } + // A computation in progress may not have a memo state yet; see + // resolutionState. + if state := c.peekMemoState(key); c.observeState(state) || state == nil && !c.checking && c.resolving.count != 0 { + c.memoCycle(c.resolvingIndex(key)) + } +} + +// observeState is observeMemo for the memo state of a result, if it has one. +// It reports whether the result is read in a query, in which case reading a +// computation in progress is left to the caller; see memoCycle. +func (c *Checker) observeState(state *memoState) bool { + if state == nil { + return false + } + if state.errors != nil { + c.replayCurrentNodeErrors(*state.errors) + } + if c.checking { + c.replayPending(state) + return false + } + if state.pending != nil { + c.observePending(state.pending) + } + return true +} + +// observePending adds the effects of a reused result to the enclosing +// computation. Those of a computation started at no node are located where +// it is reused, so if that is not where the enclosing computation started, +// the node is kept with them; replay would otherwise locate them wherever the +// enclosing computation is reused. +func (c *Checker) observePending(pending *memoEffects) { + parent := c.currentMemoEffects() + if parent == nil { + return + } + if c.currentNode != nil && c.currentNode != parent.entry && (!pending.entered || pending.entry == nil) { + pending = &memoEffects{deps: []*memoEffects{pending}, entry: c.currentNode, entered: true} + } + parent.addDep(pending) +} + +// A currentNodeError is an error reported at the current node; see +// reportAtCurrentNode. Replaying it reports the same *currentNodeError, so +// that reports collected more than once can be told apart from distinct ones. +type currentNodeError struct { + report func(c *Checker, node *ast.Node) +} + +type currentNodeErrorsMark struct { + errors int + cyclic bool +} + +// currentNodeErrors are the errors a computation reported at the current +// node. +type currentNodeErrors struct { + list []*currentNodeError + cyclic bool // Whether the computation observed a cycle, so computing it elsewhere may not report them +} + +// startCurrentNodeErrors starts collecting the errors a computation reports +// at the current node, to be passed to endCurrentNodeErrors when it finishes. +func (c *Checker) startCurrentNodeErrors() currentNodeErrorsMark { + c.currentNodeErrorCollectors++ + mark := currentNodeErrorsMark{len(c.currentNodeErrors), c.currentNodeErrorsCyclic} + c.currentNodeErrorsCyclic = false + return mark +} + +// endCurrentNodeErrors returns the errors reported at the current node since +// mark. +func (c *Checker) endCurrentNodeErrors(mark currentNodeErrorsMark) currentNodeErrors { + var r currentNodeErrors + for _, report := range c.currentNodeErrors[mark.errors:] { + // Reusing a result more than once reports its errors each time. + if !slices.Contains(r.list, report) { + r.list = append(r.list, report) + } + } + // Which errors a cycle reports depends on where it is entered, so they + // are only reported where the result is computed. + r.cyclic = r.list != nil && c.currentNodeErrorsCyclic + c.currentNodeErrorsCyclic = c.currentNodeErrorsCyclic || mark.cyclic + c.currentNodeErrorCollectors-- + if c.currentNodeErrorCollectors == 0 { + clear(c.currentNodeErrors) + c.currentNodeErrors = c.currentNodeErrors[:0] + c.currentNodeErrorsCyclic = false + } + return r +} + +func (s *memoState) setErrors(c *Checker, reports []*currentNodeError) { + switch { + case s.errors == nil && reports != nil: + c.memoErrorStates++ + c.syncObserving() + case s.errors != nil && reports == nil: + c.memoErrorStates-- + c.syncObserving() + } + if reports == nil { + s.errors = nil + } else { + s.errors = &reports + } +} + +// replayCurrentNodeErrors reports at the current node the errors that +// computing a reused result reported at the node it was computed for. +// +//go:noinline +func (c *Checker) replayCurrentNodeErrors(reports []*currentNodeError) { + for _, r := range reports { + c.reportNode(r) + } +} + +func (c *Checker) replayPending(state *memoState) { + if state != nil && state.pending != nil { + f := state.pending + state.pending = nil + c.memoPendingCount-- + c.syncObserving() + c.replayMemo(f) + } +} + +// endMemo finishes a computation started by beginMemo, retaining its effects +// for replay by later reuse of the result. The effects also become part of +// the enclosing computation, since it observed the result. +func (c *Checker) endMemo() { + n := len(c.resolutions) - 1 + if f := &c.resolutions[n]; f.memo && f.effects == nil && !f.fromCheck { + // stopEffects and endResolution for the common case, without the + // calls. + c.memoTop = int(f.prevMemo) + if f.resolving { + c.resolving.delete(f.key) + } + if int(f.attachStart) != len(c.deferredAttach) { + clear(c.deferredAttach[f.attachStart:]) + c.deferredAttach = c.deferredAttach[:f.attachStart] + } + if f.source != nil { + f.source.popState(int(f.index)) + } + *f = resolution{} + c.resolutions = c.resolutions[:n] + if c.circularityStart > n { + c.endCircularity() + } else if len(c.innerCycles) != 0 { + c.endInnerCycles(n) + } + return + } + if f := c.stopEffects(); f != nil && f.effects != nil { + c.retainEffects(c.resolutionState(f), f.effects) + } + c.endResolution() +} + +// markReadIncomplete marks the computations in progress above the one at +// index i, which is re-entered, as reading its incomplete result: either the +// in-progress marker of a circular computation or the part of a partial +// result published so far. What they compute from here on may be derived +// from it; see effectsToDrop. +func (c *Checker) markReadIncomplete(i int) { + for j := i + 1; j < len(c.resolutions); j++ { + c.resolutions[j].readIncomplete = true + } +} + +// effectsToDrop returns the effects of the innermost computation in +// progress if own, meaning it computes the result being logged in the discard +// log, and it read the incomplete result of a computation in progress (see +// markReadIncomplete). Discarding the result also drops those effects: they +// may be derived from the incomplete result, so they would report errors that +// computing from complete results does not, and that are not reported if the +// computation in progress is entered first. The results it reused are kept, +// so their effects are still replayed with it. A computation reporting an +// error located at the current node keeps its effects: such an error, like +// reaching a depth limit, describes the computation rather than what it read. +// Only discarded results drop their effects, since a kept result is reused +// with its effects. +func (c *Checker) effectsToDrop(own bool) *memoEffects { + if !own { + return nil + } + f := &c.resolutions[c.memoTop-1] + if !f.readIncomplete || f.reportsAtCurrentNode { + return nil + } + return f.effects +} + +// effectsToDropFor is effectsToDrop for the result of the memo or flag memo +// identified by key. +func (c *Checker) effectsToDropFor(key memoKey) *memoEffects { + return c.effectsToDrop(c.memoTop != 0 && c.resolutions[c.memoTop-1].source == nil && c.resolutions[c.memoTop-1].key == key) +} + +func (f *memoEffects) drop() { + if f != nil { + f.dropped = true + } +} + +// endMemoUncached is endMemo for a computation that did not cache its result. +// Nothing can reuse the result, so its effects belong only to the enclosing +// computation, and retaining them for the result would grow them each time it +// is computed again. +func (c *Checker) endMemoUncached() { + defer c.endResolution() + res := c.stopEffects() + c.forgetResult() + if res == nil || res.effects == nil { + return + } + f := res.effects + if parent := c.currentMemoEffects(); parent != nil { + if len(f.effects) == 0 && f.entry == parent.entry { + // Without effects of its own, and located like the enclosing + // computation, the computation adds nothing but what it observed. + for _, d := range f.deps { + parent.addDep(d) + } + } else { + parent.addDep(f) + } + } else if c.flushStates != nil { + // withoutChecking replays effects through the results computed. + c.retainEffects(c.resolutionState(res), f) + } +} + +// resolutionState returns the memo state of the result a memo resolution +// computes, creating it on first use, since most computations have no +// effects to retain. +func (c *Checker) resolutionState(f *resolution) *memoState { + if f.result == nil { + switch { + case f.source != nil: + f.result = f.source.stateAt(int(f.index)) + case f.key != 0: + f.result = c.memoState(f.key) + default: + // The computation caches no result; see forgetResult. + f.result = &memoState{} + } + } + return f.result +} + +// peekResolutionState is resolutionState without creating the memo state, +// returning nil if the result has none yet. +func (c *Checker) peekResolutionState(f *resolution) *memoState { + if f.result == nil { + switch { + case f.source != nil: + f.result = f.source.peekStateAt(int(f.index)) + case f.key != 0: + f.result = c.peekMemoState(f.key) + } + } + return f.result +} + +// retainEffects retains the effects of a computation that just finished for +// replay by later reuse of its result, and adds them to the enclosing +// computation. +func (c *Checker) retainEffects(state *memoState, f *memoEffects) { + if state.pending != nil && len(f.effects) == 0 && state.pending.covers(f.deps) { + // Computing the result again, typically to return it from a cache + // that the computation reads, observed only what computing it + // before did. Retaining that again would grow the effects of the + // result each time. + if parent := c.currentMemoEffects(); parent != nil { + parent.addDep(state.pending) + } + return + } + if state.pending == nil && len(f.effects) == 0 && f.rare == nil && len(f.deps) == 1 && f.deps[0].entered && f.deps[0].entry == f.entry { + // The computation only observed one result computed at the same + // node, whose effects replay the same way without it. + f = f.deps[0] + } + if c.logEffects() { + c.logPendingDiscard(state) + } + if state.pending != nil { + f.addDep(state.pending) + } else { + c.memoPendingCount++ + c.syncObserving() + if c.flushStates != nil { + *c.flushStates = append(*c.flushStates, state) + } + } + state.pending = f + if parent := c.currentMemoEffects(); parent != nil { + parent.addDep(f) + } +} + +func (c *Checker) replayMemo(f *memoEffects) { + c.replayEffects(f, nil, false) +} + +// replayEffects replays the effects of f and its dependencies. Effects +// located at the current node that a computation started with (see +// reportAtCurrentNode) are located at the node the reuse happens at instead, +// as they would be had the computation happened there, unless the computation +// started at a different node than the computation replaying it, which then +// fixes its location. +func (c *Checker) replayEffects(f *memoEffects, outerEntry *ast.Node, outerEntered bool) { + if f.replayed { + return + } + f.replayed = true + saveCurrentNode := c.currentNode + if f.entered { + // A computation that started at no node can be located anywhere, + // like one started at the node it is reused at. + if outerEntered && f.entry != outerEntry && f.entry != nil { + c.currentNode = f.entry + } + outerEntry, outerEntered = f.entry, true + } + if !f.dropped { + for _, e := range f.effects { + e(c) + } + } + for _, d := range f.deps { + c.replayEffects(d, outerEntry, outerEntered) + } + c.currentNode = saveCurrentNode + f.effects = nil + f.deps = nil + f.rare = nil +} + +// atMemoStartNode reports whether the current node is the one the innermost +// computation in progress started with. +func (c *Checker) atMemoStartNode() bool { + n := c.memoTop - 1 + return n >= 0 && c.resolutions[n].entry == c.currentNode +} + +func (c *Checker) currentMemoEffects() *memoEffects { + if c.memoTop == 0 { + return nil + } + return c.memoEffectsAt(c.memoTop - 1) +} + +// recordEffect records a side effect produced by a query against the +// innermost memoized computation, if any. Callers must only call this when +// !c.checking, and must not apply the effect themselves. +func (c *Checker) recordEffect(effect func(c *Checker)) { + if f := c.currentMemoEffects(); f != nil { + f.effects = append(f.effects, effect) + } +} + +// recordReferenceMark records marking an alias referenced, an effect that +// emit applies itself (see withReferenceMarks). +func (c *Checker) recordReferenceMark(symbol *ast.Symbol) { + if f := c.currentMemoEffects(); f != nil { + f.effects = append(f.effects, func(c *Checker) { c.markAliasSymbolAsReferenced(symbol) }) + rare := f.rareFields() + rare.marks = append(rare.marks, symbol) + } +} + +// withReferenceMarks runs fn as a query, then marks referenced the aliases +// that everything fn computed would mark, which emit relies on to elide +// imports. Unlike checking, it reports nothing, so diagnostics do not depend +// on whether a file was emitted before it was checked. Other effects stay +// pending for checking to replay. +func (c *Checker) withReferenceMarks(fn func()) { + debug.Assert(!c.checking && c.memoTop == 0, "withReferenceMarks inside a computation") + c.beginNodeResolution() + c.startEffects(&memoState{}) + fn() + f := c.stopEffects().effects + c.endResolution() + if f == nil { + return + } + var marks []*ast.Symbol + visited := make(map[*memoEffects]struct{}) + var collect func(f *memoEffects) + collect = func(f *memoEffects) { + if _, ok := visited[f]; ok { + return + } + visited[f] = struct{}{} + if f.rare != nil && !f.dropped { + marks = append(marks, f.rare.marks...) + } + for _, d := range f.deps { + collect(d) + } + } + collect(f) + for _, symbol := range marks { + c.withReferenceMarks(func() { c.referenceAliasSymbol(symbol) }) + } +} + +// withoutChecking runs fn in a query from a check, then replays the effects +// of everything fn computed, as if fn had run in the check. Unlike effects +// applied directly, recorded effects can be discarded with the results they +// belong to. +func (c *Checker) withoutChecking(fn func()) { + debug.Assert(c.checking && c.flushStates == nil, "withoutChecking outside a check") + if c.memoTop != 0 { + // The effects of the computations in progress are recorded from + // here on, and with them those of everything fn computes. + c.recordFromCheck() + fn() + return + } + saveMemoTop := c.memoTop + var states []*memoState + c.checking = false + c.syncObserving() + c.memoTop = 0 + c.flushStates = &states + fn() + c.flushStates = nil + c.checking = true + c.syncObserving() + c.memoTop = saveMemoTop + // Replaying is in order of computation, so effects apply in the order + // they would have in the check. They also happened at the current node, + // so only the effects of computations started there are located at it. + for _, state := range states { + if state.pending != nil { + f := state.pending + state.pending = nil + c.memoPendingCount-- + c.syncObserving() + c.replayEffects(f, c.currentNode, true) + } + } +} + +// beginCheckedMemo is beginMemo for a computation started in a check. Its +// effects apply as they happen, and reading cached results accounts for +// nothing, as every effect only adds to what the store has collected, which +// is the same whichever check applies it first. Effects of results that a +// cycle or a discard mark may discard are the exception (see recordFromCheck), +// so a computation starting while one is in progress records them. +func (c *Checker) beginCheckedMemo(state *memoState) { + c.startEffects(state) + if c.logEffects() { + c.recordFromCheck() + } +} + +// recordFromCheck makes the outermost computation in progress in a check a +// query from here on, like withoutChecking, because a cycle or a discard mark +// begins, after which results and their effects may be discarded. Effects +// are then recorded with the results computed and with every result +// observing them, and are replayed when the outermost computation finishes +// (see endQueryFromCheck), so that those of discarded results apply only if a +// result that is kept observed them. The computations in progress applied +// their earlier effects already, which is right since they are kept: a cycle +// discards only what is computed after it is found, and a mark only what is +// computed after it is taken. +func (c *Checker) recordFromCheck() { + debug.Assert(c.checking && c.memoTop != 0 && c.flushStates == nil, "recording from a check outside a computation") + i := c.memoTop - 1 + for c.resolutions[i].prevMemo != 0 { + i = int(c.resolutions[i].prevMemo) - 1 + } + c.resolutions[i].fromCheck = true + c.checking = false + c.syncObserving() + c.flushBuf = c.flushBuf[:0] + c.flushStates = &c.flushBuf +} + +// endQueryFromCheck ends the query begun by recordFromCheck, replaying +// the effects of everything it computed in the check. +func (c *Checker) endQueryFromCheck() { + if c.flushStates == nil { + // The query already ended; see resumeChecking. + return + } + states := *c.flushStates + c.flushStates = nil + c.checking = true + c.syncObserving() + defer clear(states) + c.replayFlushed(states) +} + +func (c *Checker) replayFlushed(states []*memoState) { + for _, state := range states { + if state.pending != nil { + f := state.pending + state.pending = nil + c.memoPendingCount-- + c.syncObserving() + c.replayEffects(f, c.currentNode, true) + } + } +} + +// resumeChecking ends the query begun by recordFromCheck as soon as nothing +// computed since can be discarded: no cycle is in progress and no effects +// mark is held. Recording until the outermost computation finishes would be +// equivalent, but makes every read until then account for effects. What the +// query recorded applies now, as it would have in the check, including what +// the computations in progress recorded, which continue as in a check. +func (c *Checker) resumeChecking() { + if c.checking || c.flushStates != &c.flushBuf || c.logEffects() || len(c.innerCycles) != 0 || len(c.discardLog) != 0 { + return + } + chain := c.resumeBuf[:0] + for i := c.memoTop - 1; i >= 0; i = int(c.resolutions[i].prevMemo) - 1 { + chain = append(chain, i) + } + saveCurrentNode := c.currentNode + if len(chain) != 0 { + root := &c.resolutions[chain[len(chain)-1]] + root.fromCheck = false + if root.entry != nil { + // The effects apply as they would when the outermost computation + // finishes, back at the node it started at. + c.currentNode = root.entry + } + } + states := c.flushBuf + c.flushStates = nil + c.checking = true + c.syncObserving() + c.replayFlushed(states) + clear(states) + loc := c.currentNode + for _, i := range slices.Backward(chain) { + r := &c.resolutions[i] + if f := r.effects; f != nil { + r.effects = nil + c.currentNode = loc + c.replayEffects(f, loc, true) + } + if r.entry != nil { + loc = r.entry + } + } + c.currentNode = saveCurrentNode + clear(chain) + c.resumeBuf = chain[:0] +} + +// withChecking runs fn in a check. +func (c *Checker) withChecking(fn func()) { + // Checking starts only from top-level requests, so any resolution left at + // this point was never exited and would absorb unrelated effects. + debug.Assert(c.memoTop == 0, "memo resolution leaked") + saveChecking := c.checking + c.checking = true + c.syncObserving() + fn() + c.checking = saveChecking + c.syncObserving() +} + +// memoCycle is called when a computation observes another computation that is +// still in progress, whose resolution is at index depth-1. Every computation +// started since then produced its result from the incomplete state of that +// computation, so reusing any of them must also replay the effects of the +// whole cycle (for example, its circularity error). +func (c *Checker) memoCycle(depth int) { + if depth == 0 || !c.resolutions[depth-1].memo { + // The outer computation does not record effects. + return + } + var outer *memoEffects + // A computation that depends on a cycle head depends on every computation + // in progress above the head, as their effects become part of the head's. + // So walk down from the top, stopping at a computation an earlier reentry + // already made depend on outer or a head below it: the computations below + // it were in progress then too, so they depend on that head as well. + // Without this, every reentry of a cycle would walk, and grow, the whole + // stack. + head := depth - 1 + for i := len(c.resolutions) - 1; i >= depth; i-- { + if !c.resolutions[i].memo { + continue + } + if outer == nil { + // Typically, the computation reads its own result, and no + // computation in between depends on it. + outer = c.memoEffectsAt(depth - 1) + } + f := c.memoEffectsAt(i) + if f == outer || outer.parent == f { + continue + } + if h := f.cycleHead(); h >= 0 && h <= head { + break + } + if i < c.effectsMarkDepth-1 { + // The resolution is in progress below the mark, so discarding to + // the mark must also discard this dependency. + c.logDepsDiscard(f) + } + rare := f.rareFields() + rare.cycles = append(rare.cycles, memoCycleDep{dep: int32(len(f.deps)), head: int32(head)}) + f.deps = append(f.deps, outer) + outer.parent = f + } +} + +// A memoCycleDep is a dependency memoCycle added on a cycle head. +type memoCycleDep struct { + dep int32 // Index in deps + head int32 // Index of the head's resolution +} + +// cycleHead returns the index of the resolution of the lowest cycle head that +// memoCycle made f depend on, or -1. +func (f *memoEffects) cycleHead() int { + if f.rare == nil || len(f.rare.cycles) == 0 { + return -1 + } + return int(f.rare.cycles[len(f.rare.cycles)-1].head) +} + +// logDepsDiscard logs the dependencies of a computation in progress below the +// innermost effects mark, which are about to grow, so discardSince can +// restore them. +// +//go:noinline +func (c *Checker) logDepsDiscard(f *memoEffects) { + n := len(f.deps) + c.discardLog = append(c.discardLog, discardEntry{effects: true, restore: func() { + if len(f.deps) > n { + f.truncateDeps(n) + } + }}) +} + +func (c *Checker) memoEffectsAt(i int) *memoEffects { + f := c.resolutions[i].effects + if f == nil { + f = &memoEffects{entry: c.resolutions[i].entry, entered: true} + c.resolutions[i].effects = f + c.attachDeferred(i, f) + } + return f +} + +// attachDeferred attaches the results the memo resolution at index i cached +// before it had effects f; see attachState. A result cached before an effects +// mark or a cycle is attached as if it had been attached then, so the discard +// log does not log it. +// +//go:noinline +func (c *Checker) attachDeferred(i int, f *memoEffects) { + start := int(c.resolutions[i].attachStart) + end := len(c.deferredAttach) + for j := i + 1; j < len(c.resolutions); j++ { + if c.resolutions[j].memo { + end = int(c.resolutions[j].attachStart) + break + } + } + if start == end { + return + } + saveStart, saveVarianceRoot := c.circularityStart, c.varianceCycleRoot + c.circularityStart, c.varianceCycleRoot = 0, 0 + for k := start; k < end; k++ { + if state := c.deferredAttach[k]; state != nil { + c.deferredAttach[k] = nil + c.linkState(state, f) + } + } + c.circularityStart, c.varianceCycleRoot = saveStart, saveVarianceRoot +} + +// Cycles +// +// A cached result must be the result of computing it from scratch, so that +// it does not depend on which computation happened to request it first. A +// computation that observes another computation still in progress (a +// circularity) sees a provisional state, so results cached from the moment +// the cycle is detected until the outermost computation of the cycle (its +// head) finishes may differ from computing them from scratch. Those results +// are discarded when the head finishes, and are recomputed when next +// requested. The computations that were in progress when the cycle was +// detected (its participants) are kept: each of them reaches the cycle when +// computed from scratch too, so each result is the fallback for a circularity +// whichever participant is computed first. +// +// Type comparisons are the exception: a comparison that reaches itself +// assumes it holds rather than being a circularity, since relations are +// coinductive. The Relater tracks those assumptions itself (see +// recursiveTypeRelatedTo), and caches only results that hold whatever they +// assumed. Each comparison is still a resolution, so circularities through +// other computations it needs are handled as above, discarding the relation +// results cached during them. A comparison that needs another relater, as in +// variance measurement, does not see the assumptions of the outer one, so it +// compares from scratch, as it would if run first. + +// Discard log +// +// Some computations run provisionally, and may have to be discarded as if +// they had never run: those in a cycle (see above), and those a computation +// that retries from a mark runs before retrying (see markDiscard). While any +// of them is in progress, what they cache is logged in the discard log, from +// which each discards to the position where it started. The discard log is +// cleared once nothing in progress can discard. + +type discardEntry struct { + memo *memoHeader // The memo restored, or nil + restore func() + effects bool // Restores the effects of a result rather than the result +} + +// logResults reports whether caching a result must be logged in the discard +// log. +func (c *Checker) logResults() bool { + return c.circularityStart != 0 || c.varianceCycleRoot != 0 +} + +// logEffects reports whether changing the effects of a result must be logged +// in the discard log. +func (c *Checker) logEffects() bool { + return c.circularityStart != 0 || c.effectsMarkDepth != 0 || c.varianceCycleRoot != 0 +} + +// discardLogSince restores everything logged in the discard log since +// position start. +func (c *Checker) discardLogSince(start int) { + start = min(start, len(c.discardLog)) + for i := range c.innerCycles { + c.innerCycles[i].mark = min(c.innerCycles[i].mark, start) + } + for i := len(c.discardLog) - 1; i >= start; i-- { + c.discardLog[i].restore() + } + clear(c.discardLog[start:]) + c.discardLog = c.discardLog[:start] +} + +// trimDiscardLog clears the discard log if nothing in progress can discard. +func (c *Checker) trimDiscardLog() { + if !c.logEffects() && len(c.discardLog) != 0 { + c.discardLogTrims++ + clear(c.discardLog) + c.discardLog = c.discardLog[:0] + } +} + +// A flag memo is a cached result held outside a memo, so that it costs no +// space of its own: bits of a flags word shared with other results, or a +// field of storage allocated only when needed. It is accounted for like a +// memo under a key naming its entity and field. Readers finding it cached +// call observeFlagMemo; it is computed between beginMemo and endMemo for the +// key, and cached with setFlagMemoBits, or with setFlagMemo after logging how +// to discard it with logFlagMemoDiscard. + +// observeFlagMemo accounts for reading the flag memo identified by key. +func (c *Checker) observeFlagMemo(key memoKey) { + if c.observing() { + c.observeMemo(key) + } +} + +// setFlagMemo accounts for caching the result of the flag memo identified by +// key, as memo.set does. +func (c *Checker) setFlagMemo(key memoKey) { + if !c.checking { + c.memoAttach(key) + } +} + +// logFlagMemoDiscard logs how to discard the result of a flag memo in the +// discard log; the caller checks logResults so that the closure is only +// allocated when needed. +// +//go:noinline +func (c *Checker) logFlagMemoDiscard(restore func()) { + c.discardLog = append(c.discardLog, discardEntry{nil, restore, false}) +} + +// objectFlagsKnown reports whether the flag memo of t whose result is cached +// once computed is set can be read without objectFlagsCell, which callers +// check first since objectFlagsCell is too large to inline. +func (c *Checker) objectFlagsKnown(t *Type, computed ObjectFlags) bool { + return t.objectFlags&computed != 0 && !c.observingMemos +} + +// objectFlagsCell returns the flags under value of t, a flag memo whose +// result is cached once the computed flag is set, computing them in a +// computation of their own if needed. With inProgress, the computed flag is +// set while computing, so that a computation re-entering it finds no flags. +func (c *Checker) objectFlagsCell(t *Type, field memoField, computed ObjectFlags, value ObjectFlags, inProgress bool, compute func() ObjectFlags) ObjectFlags { + key := memoKeyOf(t, field) + if t.objectFlags&computed != 0 { + c.observeMemo(key) + return t.objectFlags & value + } + c.beginMemo(key) + if inProgress { + setFlagMemoBits(c, key, &t.objectFlags, computed|value, computed) + } + result := compute() & value + if inProgress || t.objectFlags&computed == 0 { + setFlagMemoBits(c, key, &t.objectFlags, computed|value, computed|result) + } else { + result = t.objectFlags & value + } + c.endMemo() + return result +} + +// setFlagMemoBits caches the bits of *p under mask as the result of the flag +// memo identified by key. +func setFlagMemoBits[T ~uint32](c *Checker, key memoKey, p *T, mask T, bits T) { + if c.logResults() { + drop := c.effectsToDropFor(key) + old := *p & mask + c.logFlagMemoDiscard(func() { *p = *p&^mask | old; drop.drop() }) + } + *p = *p&^mask | bits&mask + c.setFlagMemo(key) +} + +// setFlagMemoValue caches v in *p as the result of the flag memo identified +// by key. +func (c *Checker) setFlagMemoValue[T any](key memoKey, p *T, v T) { + if c.logResults() { + drop := c.effectsToDropFor(key) + old := *p + c.logFlagMemoDiscard(func() { *p = old; drop.drop() }) + } + *p = v + c.setFlagMemo(key) +} + +// endCircularity discards the results cached since the outermost cycle in +// progress was detected, except those of its participants. +func (c *Checker) endCircularity() { + mark := min(c.circularityMark, len(c.discardLog)) + c.circularityStart = 0 + c.innerCycles = c.innerCycles[:0] + c.discardCycle(mark, false) + c.cycleParticipants = nil + c.trimDiscardLog() + c.resumeChecking() +} + +// discardCycle discards the results logged since mark, except those of the +// participants of the cycle in progress. If retain, the entries not undone +// stay in the log, as the outer cycle may still discard them. +func (c *Checker) discardCycle(mark int, retain bool) { + undo := c.discardLog[mark:] + participants := c.cycleParticipants + keep := make(map[*memoHeader]struct{}, len(participants)) + for i := range participants { + if p := c.typeResolutionMemo(&participants[i]); p != nil { + keep[p] = struct{}{} + } + } + n := mark + for i := len(undo) - 1; i >= 0; i-- { + u := undo[i] + if u.effects { + // A result recomputed after the cycle also replays the effects + // of the computation discarded here. + continue + } + if _, ok := keep[u.memo]; ok && u.memo != nil { + continue + } + u.restore() + undo[i] = discardEntry{} + } + if retain { + for _, u := range undo { + if u.restore != nil { + c.discardLog[n] = u + n++ + } + } + } + clear(c.discardLog[n:]) + c.discardLog = c.discardLog[:n] +} + +// extendCircularity makes the resolution at index head-1 part of the cycle in +// progress, starting one if there is none. A cycle whose head is inside the +// one in progress is an inner cycle: what is computed from its head while it +// is incomplete is discarded when it finishes, as for the outermost, rather +// than read by the rest of the outer cycle. +func (c *Checker) extendCircularity(head int) { + if c.checking && c.memoTop != 0 { + c.recordFromCheck() + } + switch { + case c.circularityStart == 0: + c.circularityMark = len(c.discardLog) + c.circularityStart = head + case head < c.circularityStart: + c.addInnerCycle(c.circularityStart, c.circularityMark) + c.circularityStart = head + case head > c.circularityStart: + c.addInnerCycle(head, len(c.discardLog)) + } +} + +type innerCycle struct { + head int // As Checker.circularityStart + mark int // Length of the discard log when detected +} + +func (c *Checker) addInnerCycle(head int, mark int) { + for _, ic := range c.innerCycles { + if ic.head == head { + return + } + } + c.innerCycles = append(c.innerCycles, innerCycle{head, mark}) +} + +// endInnerCycles ends the inner cycles whose head is the resolution at index +// n, which just finished. +func (c *Checker) endInnerCycles(n int) { + for i, ic := range slices.Backward(c.innerCycles) { + if ic.head > n { + c.innerCycles = slices.Delete(c.innerCycles, i, i+1) + c.discardCycle(min(ic.mark, len(c.discardLog)), true) + } + } +} + +// A discardMark is a position in the computation in progress to which it can +// discard, so that it can run again from the same state, as if what it +// computed since had never run. From markDiscard to releaseDiscardMark, the +// discard log logs all that may have to be discarded. +type discardMark struct { + discardLog int + participants int + circularityStart int + effectsMarkDepth int // Checker.effectsMarkDepth to restore on release + + // The effects recorded so far by the innermost computation in progress, + // which must have a memo resolution. + recording *memoEffects + effects int + deps int + resolutionIndex int +} + +func (c *Checker) markDiscard() discardMark { + if c.checking { + c.recordFromCheck() + } + n := c.memoTop - 1 + f := c.memoEffectsAt(n) + m := discardMark{ + discardLog: len(c.discardLog), + participants: len(c.cycleParticipants), + circularityStart: c.circularityStart, + effectsMarkDepth: c.effectsMarkDepth, + recording: f, + effects: len(f.effects), + deps: len(f.deps), + resolutionIndex: n, + } + c.effectsMarkDepth = n + 1 + return m +} + +// discardSince discards the results cached since m, with the effects of +// computing them, restoring the cycle in progress then, and discards the +// effects recorded by the computation in progress. +func (c *Checker) discardSince(m discardMark) { + c.discardLogSince(m.discardLog) + c.cycleParticipants = c.cycleParticipants[:min(m.participants, len(c.cycleParticipants))] + c.circularityStart = m.circularityStart + debug.Assert(c.memoTop-1 == m.resolutionIndex && c.resolutions[m.resolutionIndex].effects == m.recording, "effects discarded outside their computation") + f := m.recording + clear(f.effects[m.effects:]) + f.effects = f.effects[:m.effects] + f.truncateDeps(m.deps) +} + +// releaseDiscardMark ends the period during which the computation in progress +// can discard to m. +func (c *Checker) releaseDiscardMark(m discardMark) { + c.effectsMarkDepth = m.effectsMarkDepth + c.trimDiscardLog() + c.resumeChecking() +} + +func (f *memoEffects) truncateDeps(n int) { + for _, d := range f.deps[n:] { + if d.parent == f { + d.parent = nil + } + } + clear(f.deps[n:]) + f.deps = f.deps[:n] + if rare := f.rare; rare != nil { + for len(rare.cycles) != 0 && int(rare.cycles[len(rare.cycles)-1].dep) >= n { + rare.cycles = rare.cycles[:len(rare.cycles)-1] + } + } +} + +// logPendingDiscard logs the effects of the result in the memo state, which +// are about to change, so discardSince can restore them. +// +//go:noinline +func (c *Checker) logPendingDiscard(state *memoState) { + old := state.pending + n := 0 + if old != nil { + n = len(old.deps) + } + c.discardLog = append(c.discardLog, discardEntry{effects: true, restore: func() { + switch { + case old == nil && state.pending != nil: + c.memoPendingCount-- + c.syncObserving() + case old != nil && state.pending == nil: + c.memoPendingCount++ + c.syncObserving() + } + state.pending = old + // Only a set of deps (see linkState) gains deps through its state. + if old != nil && !old.entered && len(old.deps) > n { + old.truncateDeps(n) + } + }}) +} + +// typeResolutionMemo returns the address of the memo caching the result of +// a type resolution, or nil if it is not cached in a memo. +func (c *Checker) typeResolutionMemo(r *TypeResolution) *memoHeader { + switch r.propertyName { + case TypeSystemPropertyNameType: + return &c.valueSymbolLinks.Get(r.target.(*ast.Symbol)).resolvedType.memoHeader + case TypeSystemPropertyNameDeclaredType: + return &c.typeAliasLinks.Get(r.target.(*ast.Symbol)).declaredType.memoHeader + case TypeSystemPropertyNameResolvedTypeArguments: + return &r.target.(*Type).AsTypeReference().resolvedTypeArguments.memoHeader + case TypeSystemPropertyNameResolvedBaseTypes: + return &r.target.(*Type).AsInterfaceType().resolvedBaseTypes.memoHeader + case TypeSystemPropertyNameResolvedBaseConstructorType: + return &r.target.(*Type).AsInterfaceType().resolvedBaseConstructorType.memoHeader + case TypeSystemPropertyNameResolvedReturnType: + return &r.target.(*Signature).resolvedReturnType.memoHeader + case TypeSystemPropertyNameResolvedBaseConstraint: + return &r.target.(*Type).AsConstrainedType().resolvedBaseConstraint.memoHeader + case TypeSystemPropertyNameWriteType: + if extra := c.valueSymbolLinks.Get(r.target.(*ast.Symbol)).extra; extra != nil { + return &extra.writeType.memoHeader + } + case TypeSystemPropertyNameAliasTarget: + return &c.aliasSymbolLinks.Get(r.target.(*ast.Symbol)).aliasTarget.memoHeader + case TypeSystemPropertyNameComputedPropertyName: + return &c.typeNodeLinks.Get(r.target.(*ast.Node)).resolvedType.memoHeader + case TypeSystemPropertyNameVariances: + return &c.varianceLinks.Get(r.target.(*ast.Symbol)).variances.memoHeader + } + return nil +} + +// Instantiation limits +// +// Instantiation stops with an error when nested too deeply or after too +// many instantiations, as the instantiation is then very likely infinite. +// For the result not to depend on what was cached before, each memoized +// computation (including each cached comparison of two types) measures +// depth from where it started, whether or not it reports errors, and a +// reused result adds no depth. Chains that start a new memoized computation +// at every level are only stopped by a much larger limit on the whole +// outermost instantiation, which is only reached by runaway instantiations, +// and which does depend on what was cached before. +// +// The limit on the number of instantiations is likewise only reached by +// runaway instantiations, and depends on what was cached before: it counts +// the instantiations performed since the start of the current statement or +// expression, or of the outermost computation in progress, and reused +// results add none. Making it independent would mean +// keeping, for every cached result, the work needed to compute it from +// scratch, which costs far more than the limit is worth. + +const ( + instantiationDepthLimit = 100 + instantiationTotalDepthLimit = 200 + instantiationCountLimit = 5_000_000 +) + +func (c *Checker) instantiationDepthLimitReached() bool { + depth := len(c.instantiationStack) + return depth-int(c.currentResolution().depthStart) == instantiationDepthLimit || depth == instantiationTotalDepthLimit +} diff --git a/tsc/internal/checker/nodebuilder_hover.go b/tsc/internal/checker/nodebuilder_hover.go index b49ac981fbd41..9abe67c6b1ed4 100644 --- a/tsc/internal/checker/nodebuilder_hover.go +++ b/tsc/internal/checker/nodebuilder_hover.go @@ -263,7 +263,7 @@ func (b *NodeBuilderImpl) expandInterfaceDecl(symbol *ast.Symbol) *ast.Node { members = append(members, b.signatureToSignatureDeclarationHelper(sig, ast.KindCallSignature, nil)) } // Properties, filtering inherited - filteredProps := b.filterInheritedProperties(interfaceType, baseTypes, resolved.getProperties()) + filteredProps := b.filterInheritedProperties(interfaceType, baseTypes, resolved.getProperties(b.ch)) members = b.serializePropertiesWithTruncation(filteredProps, members) // Heritage clauses diff --git a/tsc/internal/checker/nodebuilderimpl.go b/tsc/internal/checker/nodebuilderimpl.go index 7c90f3f320fbc..595d2aecbf887 100644 --- a/tsc/internal/checker/nodebuilderimpl.go +++ b/tsc/internal/checker/nodebuilderimpl.go @@ -519,7 +519,7 @@ func (b *NodeBuilderImpl) tryGetResolvedSymbolFromTypeNode(node *ast.Node) *ast. if links == nil { return nil } - return links.resolvedSymbol + return links.resolvedSymbol.get(b.ch) } func (b *NodeBuilderImpl) existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(existing *ast.TypeNode, t *Type) bool { @@ -576,7 +576,7 @@ func (b *NodeBuilderImpl) getResolvedTypeWithoutAbstractConstructSignatures(t *S t.extra.objectTypeWithoutAbstractConstructSignatures = t.AsType() return t.AsType() } - typeCopy := b.ch.newAnonymousType(t.symbol, t.getMembers(), t.CallSignatures(), core.IfElse(len(constructSignatures) > 0, constructSignatures, []*Signature{}), t.getIndexInfos()) + typeCopy := b.ch.newAnonymousType(t.symbol, t.getMembers(b.ch), t.CallSignatures(), core.IfElse(len(constructSignatures) > 0, constructSignatures, []*Signature{}), t.getIndexInfos()) t.extra.objectTypeWithoutAbstractConstructSignatures = typeCopy typeCopy.AsStructuredType().ensureExtra().objectTypeWithoutAbstractConstructSignatures = typeCopy return typeCopy @@ -2063,9 +2063,9 @@ func (c *Checker) getExpandedParameters(sig *Signature, skipUnionExpanding bool) symbol := c.newSymbolEx(ast.SymbolFlagsFunctionScopedVariable, name, checkFlags) links := c.valueSymbolLinks.Get(symbol) if flags&ElementFlagsRest != 0 { - links.resolvedType = c.createArrayType(t) + links.resolvedType.set(c, c.createArrayType(t)) } else { - links.resolvedType = t + links.resolvedType.set(c, t) } return symbol }) @@ -2591,6 +2591,16 @@ func (b *NodeBuilderImpl) getPropertyNameNodeForSymbolFromNameType(symbol *ast.S return nil } +// approximateNameLength returns the length of a property name, without the +// symbol ID a late-bound name ends with, which depends on when the symbol was +// created and so would make truncation depend on the order of checking. +func approximateNameLength(name string) int { + if isLateBoundName(name) { + return strings.LastIndexByte(name, '@') + } + return len(name) +} + func (b *NodeBuilderImpl) addPropertyToElementList(propertySymbol *ast.Symbol, typeElements []*ast.TypeElement) []*ast.TypeElement { propertyIsReverseMapped := propertySymbol.CheckFlags()&ast.CheckFlagsReverseMapped != 0 var propertyType *Type @@ -2627,7 +2637,7 @@ func (b *NodeBuilderImpl) addPropertyToElementList(propertySymbol *ast.Symbol, t } propertyName := b.getPropertyNameNodeForSymbol(propertySymbol, saveEnclosingDeclaration) b.ctx.enclosingDeclaration = saveEnclosingDeclaration - b.ctx.approximateLength += len(ast.SymbolName(propertySymbol)) + 1 + b.ctx.approximateLength += approximateNameLength(ast.SymbolName(propertySymbol)) + 1 if propertySymbol.Flags()&ast.SymbolFlagsAccessor != 0 { writeType := b.ch.getWriteTypeOfSymbol(propertySymbol) @@ -2669,7 +2679,7 @@ func (b *NodeBuilderImpl) addPropertyToElementList(propertySymbol *ast.Symbol, t typeElements = append(typeElements, fakeGetterDeclaration) setterParam := b.ch.newSymbol(ast.SymbolFlagsFunctionScopedVariable, "arg") - b.ch.valueSymbolLinks.Get(setterParam).resolvedType = writeType + b.ch.valueSymbolLinks.Get(setterParam).resolvedType.set(b.ch, writeType) fakeSetterSignature := b.ch.newSignature(SignatureFlagsNone, nil, nil, nil, []*ast.Symbol{setterParam}, b.ch.voidType, nil, 0) fakeSetterDeclaration := b.signatureToSignatureDeclarationHelper(fakeSetterSignature, ast.KindSetAccessor, &SignatureToSignatureDeclarationOptions{ name: propertyName, @@ -2758,7 +2768,7 @@ func (b *NodeBuilderImpl) createTypeNodesFromResolvedType(resolvedType *Structur typeElements = slices.Concat(typeElements, b.indexInfoToObjectComputedNamesOrSignatureDeclaration(info, typeNode)) } - properties := resolvedType.getProperties() + properties := resolvedType.getProperties(b.ch) if len(properties) == 0 { return b.f.NewNodeList(typeElements) } @@ -2800,14 +2810,14 @@ func (b *NodeBuilderImpl) createTypeNodesFromResolvedType(resolvedType *Structur } func (b *NodeBuilderImpl) createTypeNodeFromObjectType(t *Type) *ast.TypeNode { - if b.ch.isGenericMappedType(t) || (t.objectFlags&ObjectFlagsMapped != 0 && t.AsMappedType().containsError) { + if b.ch.isGenericMappedType(t) || (t.objectFlags&ObjectFlagsMapped != 0 && t.AsMappedType().containsError.get(b.ch)) { return b.createMappedTypeNodeFromType(t) } resolved := b.ch.resolveStructuredTypeMembers(t) callSigs := resolved.CallSignatures() ctorSigs := resolved.ConstructSignatures() - if len(resolved.getProperties()) == 0 && len(resolved.getIndexInfos()) == 0 { + if len(resolved.getProperties(b.ch)) == 0 && len(resolved.getIndexInfos()) == 0 { if len(callSigs) == 0 && len(ctorSigs) == 0 { b.ctx.approximateLength += 2 result := b.f.NewTypeLiteralNode(b.f.NewNodeList([]*ast.Node{})) @@ -2836,9 +2846,9 @@ func (b *NodeBuilderImpl) createTypeNodeFromObjectType(t *Type) *ast.TypeNode { return b.ch.getOrCreateTypeFromSignature(s) }) // count the number of type elements excluding abstract constructors - typeElementCount := len(callSigs) + (len(ctorSigs) - len(abstractSignatures)) + len(resolved.getIndexInfos()) + core.IfElse(b.ctx.flags&nodebuilder.FlagsWriteClassExpressionAsTypeLiteral != 0, core.CountWhere(resolved.getProperties(), func(p *ast.Symbol) bool { + typeElementCount := len(callSigs) + (len(ctorSigs) - len(abstractSignatures)) + len(resolved.getIndexInfos()) + core.IfElse(b.ctx.flags&nodebuilder.FlagsWriteClassExpressionAsTypeLiteral != 0, core.CountWhere(resolved.getProperties(b.ch), func(p *ast.Symbol) bool { return p.Flags()&ast.SymbolFlagsPrototype == 0 - }), len(resolved.getProperties())) + }), len(resolved.getProperties(b.ch))) // don't include an empty object literal if there were no other static-side // properties to write, i.e. `abstract class C { }` becomes `abstract new () => {}` // and not `(abstract new () => {}) & {}` diff --git a/tsc/internal/checker/printer.go b/tsc/internal/checker/printer.go index b84c967883d88..7132ae615e70e 100644 --- a/tsc/internal/checker/printer.go +++ b/tsc/internal/checker/printer.go @@ -60,7 +60,7 @@ func (c *Checker) typeToStringEx(t *Type, enclosingDeclaration *ast.Node, flags // Serialization of types can lead to (lazy) resolution of members, which can cause diagnostics that again require // serialization of types. This can potentially result in infinite recursion and stack overflows. To prevent that, // after a certain number of recursive invocations the function simply returns "?". - if c.serializationLevel >= maxSerializationLevel { + if c.depthLimitReached(&c.serializationLevel, maxSerializationLevel) { return "?" } newLine := "" @@ -80,9 +80,9 @@ func (c *Checker) typeToStringEx(t *Type, enclosingDeclaration *ast.Node, flags defer func() { nodeBuilder.verbosity = oldVerbosity }() - c.serializationLevel++ + c.enterDepthLimit(&c.serializationLevel) typeNode := nodeBuilder.TypeToTypeNode(t, enclosingDeclaration, combinedFlags, nodebuilder.InternalFlagsNone, nil) - c.serializationLevel-- + c.serializationLevel.exit() if typeNode == nil { panic("should always get typenode") } diff --git a/tsc/internal/checker/relater.go b/tsc/internal/checker/relater.go index e5b173a299b10..0b620146e6c06 100644 --- a/tsc/internal/checker/relater.go +++ b/tsc/internal/checker/relater.go @@ -97,23 +97,26 @@ func asRecursionId[T *ast.Node | *ast.Symbol | *Type](value T) RecursionId { return RecursionId{value: value} } -type Relation struct { - results map[CacheHashKey]RelationComparisonResult +// relationCountLimit is the number of results a single comparison may add to +// a relation before it is considered too complex. +const relationCountLimit = 16_000_000 / 8 + +// relationKeyBits returns well-mixed bits of a relation key, which is a hash. +func relationKeyBits(k CacheHashKey) uint64 { + return k.Lo } -func (r *Relation) get(key CacheHashKey) RelationComparisonResult { - return r.results[key] +type Relation struct { + results memoMap[CacheHashKey, RelationComparisonResult] } -func (r *Relation) set(key CacheHashKey, result RelationComparisonResult) { - if r.results == nil { - r.results = make(map[CacheHashKey]RelationComparisonResult) - } - r.results[key] = result +func (r *Relation) get(c *Checker, key CacheHashKey) RelationComparisonResult { + result, _ := r.results.get(c, key) + return result } -func (r *Relation) size() int { - return len(r.results) +func (r *Relation) set(c *Checker, key CacheHashKey, result RelationComparisonResult) { + r.results.set(c, key, result) } func (c *Checker) isTypeIdenticalTo(source *Type, target *Type) bool { @@ -192,11 +195,14 @@ func (c *Checker) isTypeRelatedTo(source *Type, target *Type, relation *Relation } } if source.flags&TypeFlagsObject != 0 && target.flags&TypeFlagsObject != 0 { - id, _ := getRelationKey(source, target, IntersectionStateNone, relation == c.identityRelation, false) - related := relation.get(id) - if related != RelationComparisonResultNone { + id, constrained := c.getRelationKey(source, target, IntersectionStateNone, relation == c.identityRelation, false) + // An overflow is reported by each comparison that reaches it, so it + // is left to checkTypeRelatedTo. + related := relation.get(c, id) + if related != RelationComparisonResultNone && related&RelationComparisonResultOverflow == 0 { return related&RelationComparisonResultSucceeded != 0 } + return c.checkTypeRelatedToWithKey(source, target, relation, nil /*errorNode*/, nil, nil, true, id, constrained, related) } if source.flags&TypeFlagsStructuredOrInstantiable != 0 || target.flags&TypeFlagsStructuredOrInstantiable != 0 { return c.checkTypeRelatedTo(source, target, relation, nil /*errorNode*/) @@ -290,9 +296,22 @@ func (c *Checker) isEnumTypeRelatedTo(source *ast.Symbol, target *ast.Symbol, er return false } key := EnumRelationKey{sourceId: ast.GetSymbolId(sourceSymbol), targetId: ast.GetSymbolId(targetSymbol)} - if entry := c.enumRelation[key]; entry != RelationComparisonResultNone && !(entry&RelationComparisonResultFailed != 0 && errorReporter != nil) { - return entry&RelationComparisonResultSucceeded != 0 - } + entry, _ := c.enumRelation.get(c, key) + if entry != RelationComparisonResultNone { + if entry&RelationComparisonResultFailed == 0 || errorReporter == nil { + return entry&RelationComparisonResultSucceeded != 0 + } + // Recompute a cached failure only to elaborate it. + return c.isEnumTypeRelatedToUncached(sourceSymbol, targetSymbol, errorReporter) + } + c.enumRelation.begin(c, key) + related := c.isEnumTypeRelatedToUncached(sourceSymbol, targetSymbol, errorReporter) + c.enumRelation.set(c, key, core.IfElse(related, RelationComparisonResultSucceeded, RelationComparisonResultFailed)) + c.endMemo() + return related +} + +func (c *Checker) isEnumTypeRelatedToUncached(sourceSymbol *ast.Symbol, targetSymbol *ast.Symbol, errorReporter ErrorReporter) bool { targetEnumType := c.getTypeOfSymbol(targetSymbol) for _, sourceProperty := range c.getPropertiesOfType(c.getTypeOfSymbol(sourceSymbol)) { if sourceProperty.Flags()&ast.SymbolFlagsEnumMember != 0 { @@ -301,7 +320,6 @@ func (c *Checker) isEnumTypeRelatedTo(source *ast.Symbol, target *ast.Symbol, er if errorReporter != nil { errorReporter(diagnostics.Property_0_is_missing_in_type_1, c.symbolToString(sourceProperty), c.TypeToStringEx(c.getDeclaredTypeOfSymbol(targetSymbol), nil /*enclosingDeclaration*/, TypeFormatFlagsUseFullyQualifiedType, nil)) } - c.enumRelation[key] = RelationComparisonResultFailed return false } sourceValue := c.getEnumMemberValue(ast.GetDeclarationOfKind(sourceProperty, ast.KindEnumMember)).Value @@ -312,7 +330,6 @@ func (c *Checker) isEnumTypeRelatedTo(source *ast.Symbol, target *ast.Symbol, er if errorReporter != nil { errorReporter(diagnostics.Each_declaration_of_0_1_differs_in_its_value_where_2_was_expected_but_3_was_given, c.symbolToString(targetSymbol), c.symbolToString(targetProperty), c.valueToString(targetValue), c.valueToString(sourceValue)) } - c.enumRelation[key] = RelationComparisonResultFailed return false } // At this point we know that at least one of the values is 'undefined'. @@ -328,13 +345,11 @@ func (c *Checker) isEnumTypeRelatedTo(source *ast.Symbol, target *ast.Symbol, er knownStringValue := core.OrElse(sourceValue, targetValue) errorReporter(diagnostics.One_value_of_0_1_is_the_string_2_and_the_other_is_assumed_to_be_an_unknown_numeric_value, c.symbolToString(targetSymbol), c.symbolToString(targetProperty), c.valueToString(knownStringValue)) } - c.enumRelation[key] = RelationComparisonResultFailed return false } } } } - c.enumRelation[key] = RelationComparisonResultSucceeded return true } @@ -364,23 +379,76 @@ func (c *Checker) checkTypeRelatedToEx( errorNode *ast.Node, headMessage *diagnostics.Message, diagnosticOutput *[]*ast.Diagnostic, +) bool { + return c.checkTypeRelatedToWithKey(source, target, relation, errorNode, headMessage, diagnosticOutput, false, CacheHashKey{}, false, RelationComparisonResultNone) +} + +// checkTypeRelatedToWithKey is checkTypeRelatedToEx given, if hasKey, the +// relation key of source and target, as returned by getRelationKey, and the +// result cached under it. +func (c *Checker) checkTypeRelatedToWithKey( + source *Type, + target *Type, + relation *Relation, + errorNode *ast.Node, + headMessage *diagnostics.Message, + diagnosticOutput *[]*ast.Diagnostic, + hasKey bool, + id CacheHashKey, + constrained bool, + entry RelationComparisonResult, ) bool { r := c.getRelater() r.relation = relation r.errorNode = errorNode - r.relationCount = (16_000_000 - relation.size()) / 8 - result := r.isRelatedToEx(source, target, RecursionFlagsBoth, errorNode != nil /*reportErrors*/, headMessage, IntersectionStateNone) + // The budget does not depend on how many results are cached, so that whether + // a comparison overflows does not depend on what was compared before it. + r.relationCount = relationCountLimit + r.topSource, r.topTarget = source, target + if hasKey { + r.topKey, r.topConstrained, r.topHasKey = id, constrained, true + r.topEntry, r.topEntryWrites, r.topEntryRead = entry, relation.results.writes, true + } + var result Ternary + if entry&RelationComparisonResultOverflow != 0 { + // The comparison overflowed before, and would again. + r.overflow = true + } else { + measuring := c.measuringVariances + c.measuringVariances = false + if measuring { + e := c.varianceStack[len(c.varianceStack)-1] + r.measured, r.measuredResolution = c.varianceLinks.Get(e.symbol), e.resolution + } + saveIncomplete := c.incompleteVariances + c.incompleteVariances = 0 + result = r.isRelatedToEx(source, target, RecursionFlagsBoth, errorNode != nil /*reportErrors*/, headMessage, IntersectionStateNone) + incomplete := c.incompleteVariances + c.incompleteVariances = saveIncomplete + if incomplete != 0 && !measuring { + c.markIncompleteVariances(incomplete) + } + if r.topBegun { + if r.overflow { + // Record this relation as having failed such that we don't attempt the overflowing operation again. + relation.set(c, r.topKey, RelationComparisonResultFailed|RelationComparisonResultComplexityOverflow) + } + c.endMemo() + } + } if r.overflow { - // Record this relation as having failed such that we don't attempt the overflowing operation again. - id, _ := getRelationKey(source, target, IntersectionStateNone, relation == c.identityRelation, false /*ignoreConstraints*/) - relation.set(id, RelationComparisonResultFailed|RelationComparisonResultComplexityOverflow) if tr := c.tracer; tr != nil { tr.Instant(tracing.PhaseCheckTypes, "checkTypeRelatedTo_DepthLimit", map[string]any{"sourceId": source.id, "targetId": target.id, "depth": len(r.sourceStack), "targetDepth": len(r.targetStack)}) } - if errorNode == nil { - errorNode = c.currentNode + message, sourceString, targetString := diagnostics.Excessive_complexity_comparing_types_0_and_1, c.TypeToString(source), c.TypeToString(target) + switch { + case errorNode != nil: + c.reportDiagnostic(NewDiagnosticForNode(errorNode, message, sourceString, targetString), diagnosticOutput) + case diagnosticOutput != nil: + c.reportDiagnostic(NewDiagnosticForNode(c.currentNode, message, sourceString, targetString), diagnosticOutput) + default: + c.errorAtCurrentNode(message, sourceString, targetString) } - c.reportDiagnostic(NewDiagnosticForNode(errorNode, diagnostics.Excessive_complexity_comparing_types_0_and_1, c.TypeToString(source), c.TypeToString(target)), diagnosticOutput) } else if r.errorChain != nil { // Check if we should issue an extra diagnostic to produce a quickfix for a slightly incorrect import statement if headMessage != nil && errorNode != nil && result == TernaryFalse && source.symbol != nil && c.exportTypeLinks.Has(source.symbol) { @@ -767,7 +835,7 @@ func isExcessPropertyCheckTarget(t *Type) bool { // structurally equal to at least maxDepth levels, but unequal at some level beyond that. func (c *Checker) isDeeplyNestedType(t *Type, stack []*Type, maxDepth int) bool { if len(stack) >= maxDepth { - target := getRecursionIdentityTarget(t) + target := c.getRecursionIdentityTarget(t) if target.flags&TypeFlagsIntersection != 0 { for _, t := range target.Types() { if c.isDeeplyNestedType(t, stack, maxDepth) { @@ -779,7 +847,7 @@ func (c *Checker) isDeeplyNestedType(t *Type, stack []*Type, maxDepth int) bool count := 0 lastGeneration := 0 for _, t := range stack { - if hasMatchingRecursionIdentity(t, identity) { + if c.hasMatchingRecursionIdentity(t, identity) { // We only count occurrences of a generation at least that of the previous occurrence, since // newer instantiations caused by recursion are built from the previous ones, whereas nested // type arguments are older than the types they are arguments of. @@ -818,7 +886,7 @@ func (c *Checker) typeGeneration(t *Type) int { switch { case t.flags&TypeFlagsObject != 0: if t.objectFlags&ObjectFlagsReference != 0 && t.AsTypeReference().node == nil { - generation = c.typesGeneration(t.AsTypeReference().resolvedTypeArguments) + generation = c.typesGeneration(t.AsTypeReference().resolvedTypeArguments.peek()) } else if m := t.AsObjectType().mapper; m != nil { generation = c.mapperGeneration(m) } else if t.symbol != nil && t.symbol.Flags()&ast.SymbolFlagsObjectLiteral != 0 { @@ -828,7 +896,7 @@ func (c *Checker) typeGeneration(t *Type) int { if prop.Flags()&ast.SymbolFlagsTransient == 0 || prop.CheckFlags()&(ast.CheckFlagsInstantiated|ast.CheckFlagsMapped|ast.CheckFlagsReverseMapped|ast.CheckFlagsDeferredType) != 0 { continue } - if propType := c.valueSymbolLinks.Get(prop).resolvedType; propType != nil { + if propType := c.valueSymbolLinks.Get(prop).resolvedType.get(c); propType != nil { generation = max(generation, c.typeGeneration(propType)+1) } } @@ -896,11 +964,11 @@ func (c *Checker) deeplyNestedStart(t *Type, stack []*Type, maxDepth int) int { return 0 } -func hasMatchingRecursionIdentity(t *Type, identity RecursionId) bool { - target := getRecursionIdentityTarget(t) +func (c *Checker) hasMatchingRecursionIdentity(t *Type, identity RecursionId) bool { + target := c.getRecursionIdentityTarget(t) if target.flags&TypeFlagsIntersection != 0 { for _, t := range target.Types() { - if hasMatchingRecursionIdentity(t, identity) { + if c.hasMatchingRecursionIdentity(t, identity) { return true } } @@ -909,8 +977,8 @@ func hasMatchingRecursionIdentity(t *Type, identity RecursionId) bool { return getRecursionIdentityFromTarget(target) == identity } -func getRecursionIdentity(t *Type) RecursionId { - return getRecursionIdentityFromTarget(getRecursionIdentityTarget(t)) +func (c *Checker) getRecursionIdentity(t *Type) RecursionId { + return getRecursionIdentityFromTarget(c.getRecursionIdentityTarget(t)) } // Get the recursion identity target type from a type. Recursively (a) obtain the target object type of an @@ -919,15 +987,15 @@ func getRecursionIdentity(t *Type) RecursionId { // to explicitly written object literals. For example in `Mapped<{ x: Mapped<{ x: Mapped<{ x: string }>}>}>`, // each of the mapped type applications will have a unique recursion identity (that of their target object type // literal) and thus avoid appearing deeply nested. -func getRecursionIdentityTarget(t *Type) *Type { +func (c *Checker) getRecursionIdentityTarget(t *Type) *Type { if t.flags&TypeFlagsIndexedAccess != 0 { - return getRecursionIdentityTarget(t.AsIndexedAccessType().objectType) + return c.getRecursionIdentityTarget(t.AsIndexedAccessType().objectType) } if t.objectFlags&ObjectFlagsInstantiatedMapped == ObjectFlagsInstantiatedMapped { - target := t.checker.getModifiersTypeFromMappedType(t) + target := c.getModifiersTypeFromMappedType(t) if target != nil && (target.symbol != nil || target.flags&TypeFlagsIntersection != 0 && core.Some(target.Types(), func(t *Type) bool { return t.symbol != nil })) { - return getRecursionIdentityTarget(target) + return c.getRecursionIdentityTarget(target) } } return t @@ -1185,18 +1253,35 @@ func (c *Checker) isDiscriminantProperty(t *Type, name string) bool { if t != nil && t.flags&TypeFlagsUnion != 0 { prop := c.getUnionOrIntersectionProperty(t, name, false /*skipObjectFunctionPropertyAugment*/) if prop != nil && prop.CheckFlags()&ast.CheckFlagsSyntheticProperty != 0 { - if prop.CheckFlags()&ast.CheckFlagsIsDiscriminantComputed == 0 { - prop.SetCheckFlags(prop.CheckFlags() | ast.CheckFlagsIsDiscriminantComputed) - if prop.CheckFlags()&ast.CheckFlagsNonUniformAndLiteral == ast.CheckFlagsNonUniformAndLiteral && !c.isGenericType(c.getTypeOfSymbol(prop)) { - prop.SetCheckFlags(prop.CheckFlags() | ast.CheckFlagsIsDiscriminant) - } + key := memoKeyOf(prop, memoDiscriminant) + if prop.CheckFlags()&ast.CheckFlagsIsDiscriminantComputed != 0 { + c.observeFlagMemo(key) + return prop.CheckFlags()&ast.CheckFlagsIsDiscriminant != 0 } + c.beginMemo(key) + c.setDiscriminantFlags(prop, ast.CheckFlagsIsDiscriminantComputed) + if prop.CheckFlags()&ast.CheckFlagsNonUniformAndLiteral == ast.CheckFlagsNonUniformAndLiteral && !c.isGenericType(c.getTypeOfSymbol(prop)) { + c.setDiscriminantFlags(prop, ast.CheckFlagsIsDiscriminantComputed|ast.CheckFlagsIsDiscriminant) + } + c.endMemo() return prop.CheckFlags()&ast.CheckFlagsIsDiscriminant != 0 } } return false } +// setDiscriminantFlags caches whether the synthetic property prop is a +// discriminant, a flag memo. +func (c *Checker) setDiscriminantFlags(prop *ast.Symbol, flags ast.CheckFlags) { + const mask = ast.CheckFlagsIsDiscriminantComputed | ast.CheckFlagsIsDiscriminant + if c.logResults() { + old := prop.CheckFlags() & mask + c.logFlagMemoDiscard(func() { prop.SetCheckFlags(prop.CheckFlags()&^mask | old) }) + } + prop.SetCheckFlags(prop.CheckFlags()&^mask | flags) + c.setFlagMemo(memoKeyOf(prop, memoDiscriminant)) +} + func (c *Checker) getMatchingUnionConstituentForType(unionType *Type, t *Type) *Type { keyPropertyName := c.getKeyPropertyName(unionType) if keyPropertyName == "" { @@ -1214,13 +1299,15 @@ func (c *Checker) getMatchingUnionConstituentForType(unionType *Type, t *Type) * // an empty string if no such discriminant property exists. func (c *Checker) getKeyPropertyName(t *Type) string { u := t.AsUnionType() - if u.keyPropertyName == "" { - u.keyPropertyName, u.constituentMap = c.computeKeyPropertyNameAndMap(t) - } - if u.keyPropertyName == ast.InternalSymbolNameMissing { + name := c.memoCompute(&u.keyPropertyName, func() string { + var name string + name, u.constituentMap = c.computeKeyPropertyNameAndMap(t) + return name + }) + if name == ast.InternalSymbolNameMissing { return "" } - return u.keyPropertyName + return name } // Given a union type for which getKeyPropertyName returned a non-empty string, return the constituent @@ -1427,7 +1514,7 @@ func (c *Checker) getVariances(t *Type) []VarianceFlags { } func (c *Checker) getAliasVariances(symbol *ast.Symbol) []VarianceFlags { - return c.getVariancesWorker(symbol, c.typeAliasLinks.Get(symbol).typeParameters) + return c.getVariancesWorker(symbol, c.getTypeParametersOfTypeAlias(symbol)) } // Return an array containing the variance of each type parameter. The variance is effectively @@ -1437,114 +1524,266 @@ func (c *Checker) getAliasVariances(symbol *ast.Symbol) []VarianceFlags { // returns an empty slice when invoked recursively for the given generic type. func (c *Checker) getVariancesWorker(symbol *ast.Symbol, typeParameters []*Type) []VarianceFlags { links := c.varianceLinks.Get(symbol) - if links.variances == nil { - var traceArgs map[string]any - if tr := c.tracer; tr != nil { - traceArgs = map[string]any{"arity": len(typeParameters), "id": c.getDeclaredTypeOfSymbol(symbol).id} - popFn := tr.Push(tracing.PhaseCheckTypes, "getVariancesWorker", traceArgs, true) - defer func() { - formatted := make([]string, len(links.variances)) - for i, v := range links.variances { - formatted[i] = v.String() - } - traceArgs["variances"] = formatted - popFn() - }() - } - stackIndex := c.getVarianceStackIndex(symbol) - if stackIndex < 0 { - saveResolutionStart := c.resolutionStart - if len(c.varianceStack) == 0 { - c.resolutionStart = len(c.typeResolutions) - } - c.varianceStack = append(c.varianceStack, VarianceStackEntry{symbol, typeParameters}) - variances := make([]VarianceFlags, len(typeParameters)) - for i, tp := range typeParameters { - modifiers := c.getTypeParameterModifiers(tp) - var variance VarianceFlags - switch { - case modifiers&ast.ModifierFlagsOut != 0: - if modifiers&ast.ModifierFlagsIn != 0 { - variance = VarianceFlagsInvariant - } else { - variance = VarianceFlagsCovariant - } - case modifiers&ast.ModifierFlagsIn != 0: - variance = VarianceFlagsContravariant - default: - saveReliabilityFlags := c.reliabilityFlags - c.reliabilityFlags = 0 - // We first compare instantiations where the type parameter is replaced with - // marker types that have a known subtype relationship. From this we can infer - // invariance, covariance, contravariance or bivariance. - typeWithSuper := c.createMarkerType(symbol, tp, c.markerSuperType) - typeWithSub := c.createMarkerType(symbol, tp, c.markerSubType) - variance = core.IfElse(c.isTypeAssignableTo(typeWithSub, typeWithSuper), VarianceFlagsCovariant, 0) | - core.IfElse(c.isTypeAssignableTo(typeWithSuper, typeWithSub), VarianceFlagsContravariant, 0) - // If the instantiations appear to be related bivariantly it may be because the - // type parameter is independent (i.e. it isn't witnessed anywhere in the generic - // type). To determine this we compare instantiations where the type parameter is - // replaced with marker types that are known to be unrelated. - if variance == VarianceFlagsBivariant && c.isTypeAssignableTo(c.createMarkerType(symbol, tp, c.markerOtherType), typeWithSuper) { - variance = VarianceFlagsIndependent - } - if c.reliabilityFlags&RelationComparisonResultReportsUnmeasurable != 0 { - variance |= VarianceFlagsUnmeasurable - } - if c.reliabilityFlags&RelationComparisonResultReportsUnreliable != 0 { - variance |= VarianceFlagsUnreliable - } - c.reliabilityFlags = saveReliabilityFlags - } - // If variance computation was restarted due to a circularity we may have already - // computed variances for this generic type. If so, we exit early. - if len(links.variances) != 0 { - break - } - variances[i] = variance - } - // Store the results unless a restarted computation has already stored them. - if len(links.variances) == 0 { - links.variances = variances - } - c.varianceStack = c.varianceStack[:len(c.varianceStack)-1] - if len(c.varianceStack) == 0 { - c.resolutionStart = saveResolutionStart - } - } else { - // We've detected a circularity. Since we may compute different variances depending on where - // we enter a circularity, we find the generic type with the "smallest" symbol in the circular - // region of the variance stack and restart the computation from there if necessary. This - // ensures stable results for circular generic types. - minIndex := stackIndex - for i := stackIndex + 1; i < len(c.varianceStack); i++ { - if c.compareSymbols(c.varianceStack[i].symbol, c.varianceStack[minIndex].symbol) < 0 { - minIndex = i - } - } - if minIndex > stackIndex { - saveVarianceStack := c.varianceStack - c.varianceStack = nil - c.getVariancesWorker(saveVarianceStack[minIndex].symbol, saveVarianceStack[minIndex].typeParameters) - c.varianceStack = saveVarianceStack - } - // Store an empty slice to mark that we can't compute variances for this type. We treat type - // parameters as co-variant in this case. - if len(links.variances) == 0 { - links.variances = []VarianceFlags{} + if variances := links.variances.get(c); variances != nil { + return variances + } + if stackIndex := c.getVarianceStackIndex(symbol); stackIndex >= 0 { + return c.getRecursiveVariances(stackIndex) + } + var traceArgs map[string]any + if tr := c.tracer; tr != nil { + traceArgs = map[string]any{"arity": len(typeParameters), "id": c.getDeclaredTypeOfSymbol(symbol).id} + popFn := tr.Push(tracing.PhaseCheckTypes, "getVariancesWorker", traceArgs, true) + defer func() { + variances := links.variances.get(c) + formatted := make([]string, len(variances)) + for i, v := range variances { + formatted[i] = v.String() + } + traceArgs["variances"] = formatted + popFn() + }() + } + links.variances.begin(c) + // The measurement may have to be discarded; see below. What it caches is + // logged from when it re-enters a measurement in progress, as what it + // caches before observes nothing in progress. + mark := c.markVarianceDiscard() + saveResolutionStart := c.resolutionStart + if len(c.varianceStack) == 0 { + c.resolutionStart = len(c.resolutions) + } + index := len(c.varianceStack) + c.varianceStack = append(c.varianceStack, VarianceStackEntry{symbol: symbol, typeParameters: typeParameters, resolution: len(c.resolutions), prevStackIndex: c.varianceStackIndex[symbol], low: index, min: symbol, minTypeParameters: typeParameters}) + if c.varianceStackIndex == nil { + c.varianceStackIndex = make(map[*ast.Symbol]int32) + } + c.varianceStackIndex[symbol] = int32(index + 1) + c.beginTypeResolution(symbol, TypeSystemPropertyNameVariances) + variances := c.measureVariances(symbol, typeParameters, links) + e := c.varianceStack[index] + if e.low == index && e.min != symbol && !c.isYieldingTo(e.min, TypeSystemPropertyNameVariances) { + // The measurement entered a circularity, of which this type is the + // first entered, at a type other than the canonical entry. What it + // measured depends on where the circularity was entered, so it is + // discarded, and the circularity measured again from the canonical + // entry, as if that type had been measured first, with nothing in + // progress visible. That measures this type as it reaches it. + c.discardVariancesSince(mark) + saveVarianceStack, saveCycleRoot := c.varianceStack, c.varianceCycleRoot + c.varianceStack, c.varianceCycleRoot = nil, 0 + c.beginCycleYield(e.min, TypeSystemPropertyNameVariances, -1, len(c.resolutions)) + c.getVariancesWorker(e.min, e.minTypeParameters) + c.endCycleYield() + c.varianceStack, c.varianceCycleRoot = saveVarianceStack, saveCycleRoot + variances = links.variances.get(c) + if len(variances) == 0 { + variances = c.measureVariances(symbol, typeParameters, links) + } + } else if restarted := links.variances.get(c); len(restarted) != 0 { + // A measurement from the canonical entry of a circularity measured + // this type while its own measurement was in progress, hidden. + variances = restarted + } + links.variances.set(c, variances) + if c.varianceCycleRoot == index+1 { + c.varianceCycleRoot = 0 + c.trimDiscardLog() + c.resumeChecking() + } + c.varianceStack = c.varianceStack[:index] + if e.prevStackIndex != 0 { + c.varianceStackIndex[symbol] = e.prevStackIndex + } else { + delete(c.varianceStackIndex, symbol) + } + if index != 0 && e.low < index { + // This type is in a circularity with the measurement that reached it. + parent := &c.varianceStack[index-1] + parent.low = min(parent.low, e.low) + if c.compareSymbols(e.min, parent.min) < 0 { + parent.min, parent.minTypeParameters = e.min, e.minTypeParameters + } + } + c.popTypeResolution() + if len(c.varianceStack) == 0 { + c.resolutionStart = saveResolutionStart + } + c.endMemo() + return variances +} + +// getRecursiveVariances handles a measurement needing the variances of the type at the given +// index of the variance stack, whose measurement is in progress. Occurrences of type parameters +// nested in such recursive references are not measured: the variances are an empty slice, which +// comparisons treat as unknown. Since the variances measured then depend on where the +// circularity was entered, each circularity (a strongly connected component of types whose +// measurements need each other's variances) is measured from a canonical entry, the type with +// the "smallest" symbol, as if that type had been measured first: the measurement of the type +// at which it was entered finds the canonical entry once it finishes (see getVariancesWorker). +func (c *Checker) getRecursiveVariances(stackIndex int) []VarianceFlags { + top := &c.varianceStack[len(c.varianceStack)-1] + top.low = min(top.low, stackIndex) + // The variances are incomplete. Only comparisons and inferences read + // variances: a comparison observing them is not cached, and once the + // outermost one completes, what reads its result is discarded when the + // measurement re-entered finishes, unless it is one measuring variances, + // which are kept if measured from the canonical entry. What is cached + // from now on until the measurement re-entered finishes is logged, so + // that it can be discarded otherwise. + if c.varianceCycleRoot == 0 { + if c.checking && c.memoTop != 0 { + c.recordFromCheck() + } + c.varianceCycleRoot = stackIndex + 1 + } else { + c.varianceCycleRoot = min(c.varianceCycleRoot, stackIndex+1) + } + c.incompleteVariances = minIncompleteVariances(c.incompleteVariances, c.varianceStack[stackIndex].resolution+1) + return []VarianceFlags{} +} + +// A varianceDiscardMark is the state when a measurement of variances began, +// to which it is discarded if it entered a circularity at a type other than +// the canonical entry; see getVariancesWorker. +type varianceDiscardMark struct { + discardLog int + discardLogTrims int + participants int + circularityStart int + resolutionIndex int // Index in resolutions of the measurement's memo +} + +func (c *Checker) markVarianceDiscard() varianceDiscardMark { + return varianceDiscardMark{ + discardLog: len(c.discardLog), + discardLogTrims: c.discardLogTrims, + participants: len(c.cycleParticipants), + circularityStart: c.circularityStart, + resolutionIndex: c.memoTop - 1, + } +} + +// discardVariancesSince discards the results cached since m, like +// discardSince, and all the effects the measurement recorded. +func (c *Checker) discardVariancesSince(m varianceDiscardMark) { + start := m.discardLog + if c.discardLogTrims != m.discardLogTrims { + // Everything logged is from after the mark. + start = 0 + } + c.discardLogSince(start) + c.cycleParticipants = c.cycleParticipants[:min(m.participants, len(c.cycleParticipants))] + c.circularityStart = m.circularityStart + if f := c.resolutions[m.resolutionIndex].effects; f != nil { + clear(f.effects) + f.effects = f.effects[:0] + f.truncateDeps(0) + } +} + +func minIncompleteVariances(a int, b int) int { + if a == 0 || b != 0 && b < a { + return b + } + return a +} + +// markIncompleteVariances marks circular the measurement of variances in +// progress at index head-1 in resolutions, whose incomplete variances were +// observed, keeping the variances of it and those measured above it. +func (c *Checker) markIncompleteVariances(head int) { + for _, e := range c.varianceStack { + if e.resolution >= head-1 { + c.cycleParticipants = append(c.cycleParticipants, c.resolutions[e.resolution].typeResolution) + } + } + c.markTypeResolutionCircular(head - 1) +} + +// getVariancesForInference returns the variances get returns, for an +// inference, which is discarded if they are incomplete. +func (c *Checker) getVariancesForInference(get func() []VarianceFlags) []VarianceFlags { + saveIncomplete := c.incompleteVariances + c.incompleteVariances = 0 + variances := get() + if c.incompleteVariances != 0 { + c.markIncompleteVariances(c.incompleteVariances) + } + c.incompleteVariances = saveIncomplete + return variances +} + +func (c *Checker) measureVariances(symbol *ast.Symbol, typeParameters []*Type, links *VarianceLinks) []VarianceFlags { + variances := make([]VarianceFlags, len(typeParameters)) + for i, tp := range typeParameters { + modifiers := c.getTypeParameterModifiers(tp) + var variance VarianceFlags + switch { + case modifiers&ast.ModifierFlagsOut != 0: + if modifiers&ast.ModifierFlagsIn != 0 { + variance = VarianceFlagsInvariant + } else { + variance = VarianceFlagsCovariant } + case modifiers&ast.ModifierFlagsIn != 0: + variance = VarianceFlagsContravariant + default: + saveReliabilityFlags := c.reliabilityFlags + c.reliabilityFlags = 0 + // We first compare instantiations where the type parameter is replaced with + // marker types that have a known subtype relationship. From this we can infer + // invariance, covariance, contravariance or bivariance. + typeWithSuper := c.createMarkerType(symbol, tp, c.markerSuperType) + typeWithSub := c.createMarkerType(symbol, tp, c.markerSubType) + variance = core.IfElse(c.isMarkerTypeAssignableTo(typeWithSub, typeWithSuper), VarianceFlagsCovariant, 0) | + core.IfElse(c.isMarkerTypeAssignableTo(typeWithSuper, typeWithSub), VarianceFlagsContravariant, 0) + // If the instantiations appear to be related bivariantly it may be because the + // type parameter is independent (i.e. it isn't witnessed anywhere in the generic + // type). To determine this we compare instantiations where the type parameter is + // replaced with marker types that are known to be unrelated. + if variance == VarianceFlagsBivariant && c.isMarkerTypeAssignableTo(c.createMarkerType(symbol, tp, c.markerOtherType), typeWithSuper) { + variance = VarianceFlagsIndependent + } + if c.reliabilityFlags&RelationComparisonResultReportsUnmeasurable != 0 { + variance |= VarianceFlagsUnmeasurable + } + if c.reliabilityFlags&RelationComparisonResultReportsUnreliable != 0 { + variance |= VarianceFlagsUnreliable + } + c.reliabilityFlags = saveReliabilityFlags + } + // If variance computation was restarted due to a circularity we may have already + // computed variances for this generic type. If so, we exit early. + if len(links.variances.get(c)) != 0 { + break } + variances[i] = variance } - return links.variances + return variances +} + +// isMarkerTypeAssignableTo is isTypeAssignableTo for a comparison measuring +// variances. +func (c *Checker) isMarkerTypeAssignableTo(source *Type, target *Type) bool { + c.measuringVariances = true + result := c.isTypeAssignableTo(source, target) + c.measuringVariances = false + return result } +// getVarianceStackIndex returns the index in the variance stack of the +// measurement of the variances of symbol in progress, or -1. A measurement +// hidden with the rest of the stack is not in progress. func (c *Checker) getVarianceStackIndex(symbol *ast.Symbol) int { - for i, entry := range c.varianceStack { - if entry.symbol == symbol { - return i - } + if len(c.varianceStack) == 0 { + return -1 + } + i := int(c.varianceStackIndex[symbol]) - 1 + if i < 0 || i >= len(c.varianceStack) || c.varianceStack[i].symbol != symbol { + return -1 } - return -1 + return i } func (c *Checker) createMarkerType(symbol *ast.Symbol, source *Type, target *Type) *Type { @@ -1555,16 +1794,67 @@ func (c *Checker) createMarkerType(symbol *ast.Symbol, source *Type, target *Typ } var result *Type if symbol.Flags()&ast.SymbolFlagsTypeAlias != 0 { - result = c.getTypeAliasInstantiation(symbol, c.instantiateTypes(c.typeAliasLinks.Get(symbol).typeParameters, mapper), nil) + result = c.getTypeAliasInstantiation(symbol, c.instantiateTypes(c.getTypeParametersOfTypeAlias(symbol), mapper), nil) } else { result = c.createTypeReference(t, c.instantiateTypes(t.AsInterfaceType().TypeParameters(), mapper)) } - c.markerTypes.Add(result) return result } +// isMarkerType reports whether t is a marker type of the innermost variance +// computation or variance annotation check in progress: an instantiation of +// the generic type or type alias being measured with one of its type +// parameters replaced by a variance marker. Instantiations of other types with +// markers, such as those nested in the type being measured, are not marker +// types, whether or not their own variances have been measured before. Nor +// are those of measurements further down the variance stack: a reference to +// them must get their variances, so that the circularity is found and +// measured from its canonical entry (see getRecursiveVariances). func (c *Checker) isMarkerType(t *Type) bool { - return c.markerTypes.Has(t) + if len(c.varianceStack) == 0 && c.varianceTypeParameter == nil { + return false + } + if t.alias != nil && t.alias.symbol.Flags()&ast.SymbolFlagsTypeAlias != 0 && + c.isMarkerInstantiation(t.alias.symbol, c.typeAliasLinks.Get(t.alias.symbol).typeParameters, t.alias.typeArguments) { + return true + } + if t.objectFlags&ObjectFlagsReference != 0 && t.AsTypeReference().node == nil { + target := t.Target() + if target != nil && target != t && target.objectFlags&ObjectFlagsClassOrInterface != 0 { + return c.isMarkerInstantiation(target.symbol, target.AsInterfaceType().TypeParameters(), t.AsTypeReference().resolvedTypeArguments.peek()) + } + } + return false +} + +func (c *Checker) isMarkerInstantiation(symbol *ast.Symbol, typeParameters []*Type, typeArguments []*Type) bool { + if len(typeParameters) == 0 || len(typeParameters) != len(typeArguments) { + return false + } + replaced := -1 + for i, tp := range typeParameters { + if typeArguments[i] == tp { + continue + } + if replaced >= 0 || !c.isVarianceMarker(typeArguments[i]) { + return false + } + replaced = i + } + if replaced < 0 { + return false + } + switch typeArguments[replaced] { + case c.markerSuperTypeForCheck, c.markerSubTypeForCheck: + return typeParameters[replaced] == c.varianceTypeParameter + default: + return len(c.varianceStack) != 0 && c.varianceStack[len(c.varianceStack)-1].symbol == symbol + } +} + +func (c *Checker) isVarianceMarker(t *Type) bool { + return t == c.markerSuperType || t == c.markerSubType || t == c.markerOtherType || + t == c.markerSuperTypeForCheck || t == c.markerSubTypeForCheck } func (c *Checker) getTypeParameterModifiers(tp *Type) ast.ModifierFlags { @@ -1843,9 +2133,23 @@ func (c *Checker) getMinArgumentCount(signature *Signature) int { } func (c *Checker) getMinArgumentCountEx(signature *Signature, flags MinArgumentCountFlags) int { + voidIsNonOptional := flags & MinArgumentCountFlagsVoidIsNonOptional + if voidIsNonOptional != 0 { + return c.getMinArgumentCountUncached(signature, flags) + } + if count := signature.resolvedMinArgumentCount.get(c); count != -1 { + return int(count) + } + signature.resolvedMinArgumentCount.begin(c) + result := c.getMinArgumentCountUncached(signature, flags) + c.endMemo() + return result +} + +func (c *Checker) getMinArgumentCountUncached(signature *Signature, flags MinArgumentCountFlags) int { strongArityForUntypedJS := flags & MinArgumentCountFlagsStrongArityForUntypedJS voidIsNonOptional := flags & MinArgumentCountFlagsVoidIsNonOptional - if voidIsNonOptional != 0 || signature.resolvedMinArgumentCount == -1 { + { minArgumentCount := -1 if signatureHasRestParameter(signature) { restType := c.getTypeOfSymbol(signature.parameters[len(signature.parameters)-1]) @@ -1878,9 +2182,9 @@ func (c *Checker) getMinArgumentCountEx(signature *Signature, flags MinArgumentC } minArgumentCount = i } - signature.resolvedMinArgumentCount = int32(minArgumentCount) + signature.resolvedMinArgumentCount.set(c, int32(minArgumentCount)) + return minArgumentCount } - return int(signature.resolvedMinArgumentCount) } func (c *Checker) hasEffectiveRestParameter(signature *Signature) bool { @@ -2151,6 +2455,22 @@ func (c *Checker) getTupleElementLabelFromBindingElement(node *ast.Node, index i } func (c *Checker) getTypePredicateOfSignature(sig *Signature) *TypePredicate { + key := memoKeyOf(sig, memoTypePredicate) + if sig.getResolvedTypePredicate(c) != nil { + c.observeFlagMemo(key) + } else { + c.beginMemo(key) + c.resolveTypePredicateOfSignature(sig) + c.endMemo() + } + predicate := sig.getResolvedTypePredicate(c) + if predicate == c.noTypePredicate { + return nil + } + return predicate +} + +func (c *Checker) resolveTypePredicateOfSignature(sig *Signature) { if sig.getResolvedTypePredicate(c) == nil { switch { case sig.target != nil: @@ -2168,7 +2488,7 @@ func (c *Checker) getTypePredicateOfSignature(sig *Signature) *TypePredicate { if ast.IsTypePredicateNode(typeNode) { sig.setResolvedTypePredicate(c, c.createTypePredicateFromTypePredicateNode(typeNode, sig)) } - case ast.IsFunctionLikeDeclaration(sig.declaration) && (sig.resolvedReturnType == nil || sig.resolvedReturnType.flags&TypeFlagsBoolean != 0) && c.getParameterCount(sig) > 0: + case ast.IsFunctionLikeDeclaration(sig.declaration) && (sig.resolvedReturnType.get(c) == nil || sig.resolvedReturnType.get(c).flags&TypeFlagsBoolean != 0) && c.getParameterCount(sig) > 0: sig.setResolvedTypePredicate(c, c.noTypePredicate) // avoid infinite loop sig.setResolvedTypePredicate(c, c.getTypePredicateFromBody(sig.declaration)) } @@ -2178,11 +2498,6 @@ func (c *Checker) getTypePredicateOfSignature(sig *Signature) *TypePredicate { sig.setResolvedTypePredicate(c, c.noTypePredicate) } } - predicate := sig.getResolvedTypePredicate(c) - if predicate == c.noTypePredicate { - return nil - } - return predicate } func (c *Checker) getUnionOrIntersectionTypePredicate(signatures []*Signature, isUnion bool) *TypePredicate { @@ -2251,7 +2566,7 @@ func (c *Checker) isResolvingReturnTypeOfSignature(signature *Signature) bool { if signature.getComposite() != nil && core.Some(signature.getComposite().signatures, c.isResolvingReturnTypeOfSignature) { return true } - return signature.resolvedReturnType == nil && c.findResolutionCycleStartIndex(signature, TypeSystemPropertyNameResolvedReturnType) >= 0 + return signature.resolvedReturnType.get(c) == nil && c.isResolutionInProgress(signature, TypeSystemPropertyNameResolvedReturnType) } func (c *Checker) findMatchingSignatures(signatureLists [][]*Signature, signature *Signature, listIndex int) []*Signature { @@ -2693,8 +3008,18 @@ type Relater struct { // them by key. maybeKeys []maybeKey maybeKeysSet map[CacheHashKey]int - sourceStack []*Type - targetStack []*Type + // The outermost comparison and its relation key. + topSource, topTarget *Type + topKey CacheHashKey + topConstrained bool + topEntry RelationComparisonResult // The result cached for topKey when the comparison began + topEntryValid bool // Whether topEntry is unused, and so still the cached result + topHasKey bool // Whether topKey and topConstrained are known + topEntryRead bool // Whether the caller read topEntry, when the relation had topEntryWrites writes + topEntryWrites uint32 + topBegun bool // Whether the comparison began its computation; see beginTop + sourceStack []*Type + targetStack []*Type // sourceDepths and targetDepths are the depths of the comparisons that // pushed each entry of sourceStack and targetStack. sourceDepths []int @@ -2715,7 +3040,12 @@ type Relater struct { expandingTargetDepth int overflow bool relationCount int - next *Relater + // measured is the variances of the type whose measurement the + // comparison is part of, if any, and measuredResolution the index in + // Checker.resolutions of that measurement. + measured *VarianceLinks + measuredResolution int + next *Relater } type maybeKey struct { @@ -2727,6 +3057,37 @@ type maybeKey struct { truncated bool } +// beginTop makes the outermost comparison a computation of its own, so that +// reusing any result it caches accounts for the effects of the whole +// comparison. Errors are reported after it, since they belong to the caller +// rather than the result. Only recursiveTypeRelatedTo caches results, so the +// comparison begins on its first call there, and most comparisons, which +// finish before, need neither the computation nor the relation key. What the +// comparison did before then cannot affect a result it caches other than +// through the types it compares. beginTop returns false if the comparison +// overflowed before, and would again. +func (r *Relater) beginTop() bool { + r.topBegun = true + c := r.c + if !r.topHasKey { + r.topKey, r.topConstrained = c.getRelationKey(r.topSource, r.topTarget, IntersectionStateNone, r.relation == c.identityRelation, false /*ignoreConstraints*/) + r.topHasKey = true + } + // A comparison made before now may have cached a result for the key, + // unless nothing was cached since the caller read it. + if !r.topEntryRead || r.topEntryWrites != r.relation.results.writes { + r.topEntry = r.relation.get(c, r.topKey) + } + r.topEntryValid = true + if r.topEntry&RelationComparisonResultOverflow != 0 { + r.topBegun = false + r.overflow = true + return false + } + r.relation.results.begin(c, r.topKey) + return true +} + func (c *Checker) getRelater() *Relater { r := c.freeRelater if r == nil { @@ -2737,17 +3098,26 @@ func (c *Checker) getRelater() *Relater { } func (c *Checker) putRelater(r *Relater) { - clear(r.maybeKeysSet) - *r = Relater{ - c: c, - maybeKeys: r.maybeKeys[:0], - maybeKeysSet: r.maybeKeysSet, - sourceStack: r.sourceStack[:0], - targetStack: r.targetStack[:0], - sourceDepths: r.sourceDepths[:0], - targetDepths: r.targetDepths[:0], - next: c.freeRelater, - } + if len(r.maybeKeysSet) != 0 { + clear(r.maybeKeysSet) + } + // Reset field by field, which is cheaper than assigning a whole Relater. + r.relation = nil + r.errorNode = nil + r.errorChain = nil + r.relatedInfo = nil + r.maybeKeys = r.maybeKeys[:0] + r.sourceStack, r.targetStack = r.sourceStack[:0], r.targetStack[:0] + r.sourceDepths, r.targetDepths = r.sourceDepths[:0], r.targetDepths[:0] + r.depth, r.low, r.truncated = 0, 0, false + r.topSource, r.topTarget = nil, nil + r.topKey, r.topConstrained = CacheHashKey{}, false + r.topEntry, r.topEntryValid, r.topHasKey, r.topBegun, r.topEntryRead = RelationComparisonResultNone, false, false, false, false + r.expandingFlags, r.expandingSourceDepth, r.expandingTargetDepth = ExpandingFlagsNone, 0, 0 + r.overflow = false + r.relationCount = 0 + r.measured, r.measuredResolution = nil, 0 + r.next = c.freeRelater c.freeRelater = r } @@ -3249,16 +3619,47 @@ func (r *Relater) recursiveTypeRelatedTo(source *Type, target *Type, reportError // important to have well-defined behavior even in cases that shouldn't normally occur. return TernaryFalse } - id, constrained := getRelationKey(source, target, intersectionState, r.relation == r.c.identityRelation, false /*ignoreConstraints*/) - if entry := r.relation.get(id); entry != RelationComparisonResultNone { - if reportErrors && entry&RelationComparisonResultFailed != 0 && entry&RelationComparisonResultOverflow == 0 { + if r.measured != nil && len(r.measured.variances.peek()) != 0 { + // A restart from a circularity measured the type from the canonical + // entry (see getRecursiveVariances), so the measurement in progress is + // superseded and its result unused. It stops here, as reading the + // incomplete variances of the measurement, so that what it compared + // is not cached. + r.c.incompleteVariances = minIncompleteVariances(r.c.incompleteVariances, r.measuredResolution+1) + return TernaryFalse + } + if !r.topBegun && !r.beginTop() { + return TernaryFalse + } + var id CacheHashKey + var constrained bool + var entry RelationComparisonResult + if source == r.topSource && target == r.topTarget && intersectionState == IntersectionStateNone { + id, constrained = r.topKey, r.topConstrained + // The comparison read the cached result on starting, and nothing + // caches one for it before its first visit to its own pair. + if r.topEntryValid { + r.topEntryValid = false + entry = r.topEntry + } else { + entry = r.relation.get(r.c, id) + } + } else { + id, constrained = r.c.getRelationKey(source, target, intersectionState, r.relation == r.c.identityRelation, false /*ignoreConstraints*/) + entry = r.relation.get(r.c, id) + } + if entry != RelationComparisonResultNone { + if entry&RelationComparisonResultOverflow != 0 { + // Computing the comparison overflowed, so computing this one, + // which needs it, overflows too. + r.overflow = true + return TernaryFalse + } + if reportErrors && entry&RelationComparisonResultFailed != 0 { // We are elaborating errors and the cached result is a failure not due to a comparison overflow, // so we will do the comparison again to generate an error message. } else { r.c.reliabilityFlags |= entry & (RelationComparisonResultReportsUnmeasurable | RelationComparisonResultReportsUnreliable) - if reportErrors && entry&RelationComparisonResultOverflow != 0 { - r.reportError(diagnostics.Excessive_complexity_comparing_types_0_and_1, r.c.TypeToString(source), r.c.TypeToString(target)) - } if entry&RelationComparisonResultSucceeded != 0 { return TernaryTrue } @@ -3277,13 +3678,13 @@ func (r *Relater) recursiveTypeRelatedTo(source *Type, target *Type, reportError // type parameters. For such keys we also check against the key we would have gotten if all type parameters // were unconstrained. if constrained { - broadestEquivalentId, _ := getRelationKey(source, target, intersectionState, r.relation == r.c.identityRelation, true /*ignoreConstraints*/) + broadestEquivalentId, _ := r.c.getRelationKey(source, target, intersectionState, r.relation == r.c.identityRelation, true /*ignoreConstraints*/) if i, ok := r.maybeKeysSet[broadestEquivalentId]; ok { return r.assume(i) } // What relates with unconstrained type parameters also relates with // constrained ones. - if entry := r.relation.get(broadestEquivalentId); entry&RelationComparisonResultSucceeded != 0 { + if entry := r.relation.get(r.c, broadestEquivalentId); entry&RelationComparisonResultSucceeded != 0 { r.c.reliabilityFlags |= entry & (RelationComparisonResultReportsUnmeasurable | RelationComparisonResultReportsUnreliable) return TernaryTrue } @@ -3335,6 +3736,8 @@ func (r *Relater) recursiveTypeRelatedTo(source *Type, target *Type, reportError } saveReliabilityFlags := r.c.reliabilityFlags r.c.reliabilityFlags = 0 + saveIncomplete := r.c.incompleteVariances + r.c.incompleteVariances = 0 var result Ternary if r.expandingFlags == ExpandingFlagsBoth { if tr := r.c.tracer; tr != nil { @@ -3346,10 +3749,17 @@ func (r *Relater) recursiveTypeRelatedTo(source *Type, target *Type, reportError if tr := r.c.tracer; tr != nil { defer tr.Push(tracing.PhaseCheckTypes, "structuredTypeRelatedTo", map[string]any{"sourceId": source.id, "targetId": target.id}, false)() } + // The result is cached, so instantiations it needs count against + // the depth limit from here, not from wherever it is first compared + // (see "Instantiation limits" in memo.go). + r.c.beginResolution(nil, 0) result = r.structuredTypeRelatedTo(source, target, reportErrors, intersectionState) + r.c.endResolution() } propagatingVarianceFlags := r.c.reliabilityFlags r.c.reliabilityFlags |= saveReliabilityFlags + incomplete := r.c.incompleteVariances + r.c.incompleteVariances = minIncompleteVariances(saveIncomplete, incomplete) if recursionFlags&RecursionFlagsSource != 0 { r.sourceStack = r.sourceStack[:len(r.sourceStack)-1] r.sourceDepths = r.sourceDepths[:len(r.sourceDepths)-1] @@ -3363,10 +3773,27 @@ func (r *Relater) recursiveTypeRelatedTo(source *Type, target *Type, reportError low, truncated := r.low, r.truncated r.low, r.truncated = saveLow, saveTruncated switch { + case r.overflow: + // The comparison was cut short, so its result and those of the + // comparisons that completed assuming it are not what computing them + // from scratch would give. Only the overflow is cached. + r.resetMaybeStack(maybeStart, propagatingVarianceFlags, false) + result = TernaryFalse + case incomplete != 0 && low >= depth: + // The result observed incomplete variances, so it is not what + // comparing from scratch would give (see getRecursiveVariances). + r.resetMaybeStack(maybeStart, propagatingVarianceFlags, false) + if result == TernaryMaybe { + result = TernaryTrue + } + case incomplete != 0: + // As above, once the outermost comparison this one assumed completes. + r.low = min(r.low, low) + r.truncated = r.truncated || truncated case result == TernaryFalse: // A false result goes straight into global cache (when something is false under // assumptions it will also be false without assumptions) - r.relation.set(id, RelationComparisonResultFailed|propagatingVarianceFlags) + r.relation.set(r.c, id, RelationComparisonResultFailed|propagatingVarianceFlags) r.relationCount-- r.resetMaybeStack(maybeStart, propagatingVarianceFlags, false) case low >= depth: @@ -3379,7 +3806,7 @@ func (r *Relater) recursiveTypeRelatedTo(source *Type, target *Type, reportError r.resetMaybeStack(maybeStart, propagatingVarianceFlags, false) case truncated: r.resetMaybeStack(maybeStart, propagatingVarianceFlags, false) - r.relation.set(id, RelationComparisonResultSucceeded|propagatingVarianceFlags) + r.relation.set(r.c, id, RelationComparisonResultSucceeded|propagatingVarianceFlags) r.relationCount-- default: r.resetMaybeStack(maybeStart, propagatingVarianceFlags, true) @@ -3428,7 +3855,7 @@ func (r *Relater) resetMaybeStack(maybeStart int, propagatingVarianceFlags Relat key := r.maybeKeys[i].key delete(r.maybeKeysSet, key) if markAllAsSucceeded { - r.relation.set(key, RelationComparisonResultSucceeded|propagatingVarianceFlags) + r.relation.set(r.c, key, RelationComparisonResultSucceeded|propagatingVarianceFlags) r.relationCount-- } } @@ -3521,10 +3948,23 @@ func (r *Relater) structuredTypeRelatedToWorker(source *Type, target *Type, repo var varianceCheckFailed bool var originalErrorChain *ErrorChain saveErrorState := r.getErrorState() - relateVariances := func(sourceTypeArguments []*Type, targetTypeArguments []*Type, variances []VarianceFlags, intersectionState IntersectionState) (Ternary, bool) { + relateVariances := func(sourceTypeArguments []*Type, targetTypeArguments []*Type, variances []VarianceFlags, guessed bool, intersectionState IntersectionState) (Ternary, bool) { if result = r.typeArgumentsRelatedTo(sourceTypeArguments, targetTypeArguments, variances, reportErrors, intersectionState); result != TernaryFalse { return result, true } + if guessed { + // The variances are a guess for a generic type whose variances are being + // measured (see getVariancesWorker). There is no structural fallback for + // them, so that a stricter guess never makes a comparison succeed, and the + // measurement depends on the guess monotonically. The measurement still + // learns that it observed unreliable or unmeasurable variances. + for i, v := range variances { + if v&VarianceFlagsUnmeasurable != 0 && i < len(sourceTypeArguments) { + r.c.instantiateType(sourceTypeArguments[i], r.c.reportUnmeasurableMapper) + } + } + return TernaryFalse, true + } if core.Some(variances, func(v VarianceFlags) bool { return v&VarianceFlagsAllowsStructuralFallback != 0 }) { // If some type parameter was `Unmeasurable` or `Unreliable`, and we couldn't pass by assuming it was identical, then we // have to allow a structural fallback check @@ -3658,7 +4098,7 @@ func (r *Relater) structuredTypeRelatedToWorker(source *Type, target *Type, repo nodeIsInJsFile := ast.IsInJSFile(source.alias.symbol.ValueDeclaration()) sourceTypes := r.c.fillMissingTypeArguments(source.alias.typeArguments, params, minParams, nodeIsInJsFile) targetTypes := r.c.fillMissingTypeArguments(target.alias.typeArguments, params, minParams, nodeIsInJsFile) - varianceResult, ok := relateVariances(sourceTypes, targetTypes, variances, intersectionState) + varianceResult, ok := relateVariances(sourceTypes, targetTypes, variances, r.c.getVarianceStackIndex(source.alias.symbol) >= 0, intersectionState) if ok { return varianceResult } @@ -4088,13 +4528,10 @@ func (r *Relater) structuredTypeRelatedToWorker(source *Type, target *Type, repo // type references (which are intended by be compared structurally). Obtain the variance // information for the type parameters and relate the type arguments accordingly. variances := r.c.getVariances(source.Target()) - // We return Ternary.Maybe for a recursive invocation of getVariances (signaled by emptyArray). This - // effectively means we measure variance only from type parameter occurrences that aren't nested in - // recursive instantiations of the generic type. if len(variances) == 0 { return TernaryUnknown } - varianceResult, ok := relateVariances(r.c.getTypeArguments(source), r.c.getTypeArguments(target), variances, intersectionState) + varianceResult, ok := relateVariances(r.c.getTypeArguments(source), r.c.getTypeArguments(target), variances, r.c.getVarianceStackIndex(source.Target().symbol) >= 0, intersectionState) if ok { return varianceResult } @@ -5000,7 +5437,7 @@ func (r *Relater) reportErrorResults(originalSource *Type, originalTarget *Type, r.reportRelationError(headMessage, source, target) if source.flags&TypeFlagsTypeParameter != 0 && source.symbol != nil && len(source.symbol.Declarations()) != 0 && r.c.getConstraintOfType(source) == nil { syntheticParam := r.c.cloneTypeParameter(source) - syntheticParam.AsTypeParameter().constraint = r.c.instantiateType(target, newSimpleTypeMapper(source, syntheticParam)) + syntheticParam.AsTypeParameter().constraint.intern(r.c.instantiateType(target, newSimpleTypeMapper(source, syntheticParam))) if r.c.hasNonCircularBaseConstraint(syntheticParam) { targetConstraintString := r.c.TypeToString(target) r.relatedInfo = append(r.relatedInfo, NewDiagnosticForNode(source.symbol.Declarations()[0], diagnostics.This_type_parameter_might_need_an_extends_0_constraint, targetConstraintString)) @@ -5028,7 +5465,7 @@ func (r *Relater) reportRelationError(message *diagnostics.Message, source *Type if targetFlags&TypeFlagsTypeParameter != 0 && target != r.c.markerSuperTypeForCheck && target != r.c.markerSubTypeForCheck { constraint := r.c.getBaseConstraintOfType(target) switch { - case IsDistributedTypeParameter(target) && r.c.isTypeAssignableTo(generalizedSource, target.AsTypeParameter().constraint): + case IsDistributedTypeParameter(target) && r.c.isTypeAssignableTo(generalizedSource, target.AsTypeParameter().constraint.get(r.c)): r.reportError(diagnostics.X_0_is_only_assignable_to_the_non_distributed_1_but_1_has_been_distributed_here, generalizedSourceType, targetType) case constraint != nil && r.c.isTypeAssignableTo(generalizedSource, constraint): r.reportError(diagnostics.X_0_is_assignable_to_the_constraint_of_type_1_but_1_could_be_instantiated_with_a_different_subtype_of_constraint_2, generalizedSourceType, targetType, r.c.TypeToString(constraint)) diff --git a/tsc/internal/checker/resolutions.go b/tsc/internal/checker/resolutions.go new file mode 100644 index 0000000000000..a42f40f350b1e --- /dev/null +++ b/tsc/internal/checker/resolutions.go @@ -0,0 +1,321 @@ +package checker + +import ( + "slices" + + "github.com/microsoft/TypeScript/tsc/internal/ast" + "github.com/microsoft/TypeScript/tsc/internal/debug" +) + +// A resolution is a computation in progress: a memoized computation, a cached +// comparison, or a checked node. The innermost is the last in +// Checker.resolutions, above a root resolution for work outside any of them. +type resolution struct { + // The result computed is the memo identified by key, or the one whose + // memo state source creates for index, if either is given. + key memoKey + source memoStateSource + index int32 + // Length of the instantiation stack from which the depth limit counts: + // that on entry for a computation, or that of the enclosing resolution + // for a node. + depthStart int32 + serial uint32 // Identifies the resolution among all entered + + // A query records the effects of a memoized computation, for replay by + // reuse of its result; see memo.go. + memo bool + isQuery bool + resolving bool // The resolution is the one in Checker.resolving for key + fromCheck bool // The query began in a check; see recordFromCheck + // The computation read the incomplete result of a computation in + // progress, or was started by one that did; see effectsToDrop. + readIncomplete bool + // The computation reported an error located at the current node; see + // effectsToDrop. + reportsAtCurrentNode bool + // The structured members the outermost resolution of key computes are + // available before it completes; see resolveStructuredTypeMembersUncached. + membersPublished bool + prevMemo int32 // Checker.memoTop on entry + // Index in Checker.deferredAttach of the first result cached by the + // computation before it had effects; see attachState. + attachStart int32 + result *memoState // Memo state of the result computed + effects *memoEffects // Allocated on first use + entry *ast.Node // Current node when the computation started + + // A type resolution is a computation that re-entering detects as a cycle; + // see beginTypeResolution. Its other fields are those of the resolution + // below it, whose work it shares. + typeResolution TypeResolution + isTypeResolution bool + prevTypeResolution int32 // Checker.typeResolutionTop on entry + // Checker.searchableTop on entry, for a type resolution other than that + // of a current node. + prevSearchable int32 +} + +// A memoStateSource creates the memo state of a computation in progress on +// demand, since few computations do work worth storing. +type memoStateSource interface { + stateAt(i int) *memoState + peekStateAt(i int) *memoState + popState(i int) +} + +func (c *Checker) initResolutions() { + c.resolutions = []resolution{{}} + c.syncObserving() +} + +// pushResolution pushes a resolution with every field zero and returns it. +// Popping a resolution zeroes it, so space reused above the stack needs no +// zeroing, and the caller sets only the fields it needs in place rather than +// copying in a whole resolution. +func (c *Checker) pushResolution() *resolution { + n := len(c.resolutions) + if n == 1 { + // An outermost computation, such as a query, counts its own + // instantiations; see "Instantiation limits" in memo.go. + c.instantiationCount = 0 + } + if n == cap(c.resolutions) { + c.resolutions = append(c.resolutions, resolution{}) + } else { + c.resolutions = c.resolutions[:n+1] + } + f := &c.resolutions[n] + // What a computation started by one that read an incomplete result + // computes may be derived from it too. + f.readIncomplete = n != 0 && c.resolutions[n-1].readIncomplete + return f +} + +func (c *Checker) currentResolution() *resolution { + return &c.resolutions[len(c.resolutions)-1] +} + +// beginResolution starts a resolution for a computation, which also starts the +// depth limit of instantiations (see "Instantiation limits" in memo.go). +func (c *Checker) beginResolution(source memoStateSource, index int) { + c.appendResolution(0, source, index, int32(len(c.instantiationStack))) +} + +// beginNodeResolution starts a resolution that is not a computation, which +// continues the depth limit of the enclosing resolution. +func (c *Checker) beginNodeResolution() { + c.appendResolution(0, nil, 0, c.currentResolution().depthStart) +} + +func (c *Checker) appendResolution(key memoKey, source memoStateSource, index int, depthBase int32) { + c.resolutionSerials++ + f := c.pushResolution() + f.key = key + f.source = source + f.index = int32(index) + f.depthStart = depthBase + f.serial = c.resolutionSerials +} + +// endResolution finishes the current resolution. +func (c *Checker) endResolution() { + n := len(c.resolutions) - 1 + f := &c.resolutions[n] + debug.Assert(!f.isTypeResolution, "resolution left on the stack") + c.endResolving(f) + fromCheck := f.fromCheck + if f.memo { + clear(c.deferredAttach[f.attachStart:]) + c.deferredAttach = c.deferredAttach[:f.attachStart] + } + if f.source != nil { + f.source.popState(int(f.index)) + } + c.resolutions[n] = resolution{} + c.resolutions = c.resolutions[:n] + if c.circularityStart > n { + c.endCircularity() + } else if len(c.innerCycles) != 0 { + c.endInnerCycles(n) + } + if fromCheck { + c.endQueryFromCheck() + } +} + +// forgetResult makes the current resolution compute no result, for a +// computation that did not cache its result. +func (c *Checker) forgetResult() { + f := c.currentResolution() + if f.source != nil { + f.source.popState(int(f.index)) + } + c.endResolving(f) + f.key, f.source = 0, nil +} + +// resolvingIndex returns 1 + the index in resolutions of the outermost +// computation of key in progress, or 0. +func (c *Checker) resolvingIndex(key memoKey) int { + if i, ok := c.resolving.get(key); ok && int(i) >= c.visibleStart { + return int(i) + 1 + } + return 0 +} + +// resolvingTable maps the keys of computations in progress to the index in +// resolutions of the outermost one. It only holds keys on the resolution +// stack, so it stays small, and it is looked up far more often than it +// changes. It uses open addressing with linear probing, with zero marking an +// empty slot, as no memo key is zero. +type resolvingTable struct { + slots []resolvingSlot + count int +} + +type resolvingSlot struct { + key memoKey + index int32 +} + +func (t *resolvingTable) home(key memoKey) int { + return int(uint64(key) * 0x9E3779B97F4A7C15 >> 32 & uint64(len(t.slots)-1)) +} + +func (t *resolvingTable) get(key memoKey) (int32, bool) { + if t.count == 0 { + return 0, false + } + mask := len(t.slots) - 1 + for i := t.home(key); ; i = (i + 1) & mask { + switch t.slots[i].key { + case key: + return t.slots[i].index, true + case 0: + return 0, false + } + } +} + +// slot returns the slot holding key, or else the empty slot where it would +// be inserted, with room for the insertion. +func (t *resolvingTable) slot(key memoKey) *resolvingSlot { + if 2*(t.count+1) > len(t.slots) { + t.grow() + } + mask := len(t.slots) - 1 + for i := t.home(key); ; i = (i + 1) & mask { + if k := t.slots[i].key; k == key || k == 0 { + return &t.slots[i] + } + } +} + +func (t *resolvingTable) set(key memoKey, index int32) { + if 2*(t.count+1) > len(t.slots) { + t.grow() + } + mask := len(t.slots) - 1 + for i := t.home(key); ; i = (i + 1) & mask { + switch t.slots[i].key { + case key: + t.slots[i].index = index + return + case 0: + t.slots[i] = resolvingSlot{key, index} + t.count++ + return + } + } +} + +func (t *resolvingTable) delete(key memoKey) { + if t.count == 0 { + return + } + mask := len(t.slots) - 1 + i := t.home(key) + for t.slots[i].key != key { + if t.slots[i].key == 0 { + return + } + i = (i + 1) & mask + } + // Shift later entries of the probe sequence back into the hole, so that + // lookups need no tombstones. + for j := (i + 1) & mask; t.slots[j].key != 0; j = (j + 1) & mask { + if h := t.home(t.slots[j].key); (j-h)&mask >= (j-i)&mask { + t.slots[i] = t.slots[j] + i = j + } + } + t.slots[i] = resolvingSlot{} + t.count-- +} + +func (t *resolvingTable) grow() { + old := t.slots + t.slots = make([]resolvingSlot, max(2*len(old), 64)) + t.count = 0 + for _, slot := range old { + if slot.key != 0 { + t.set(slot.key, slot.index) + } + } +} + +// beginResolving records that the resolution about to be pushed computes key, +// unless a computation of key is already in progress, in which case the new +// one re-enters it as the policy of its kind says; see memoReentry. It reports +// whether the resolution is recorded. +func (c *Checker) beginResolving(key memoKey) bool { + slot := c.resolving.slot(key) + if slot.key == 0 { + *slot = resolvingSlot{key, int32(len(c.resolutions))} + c.resolving.count++ + return true + } + i := slot.index + if int(i) >= c.visibleStart { + if memoReentry[key.field()] == reentryCircular { + c.markCircular(int(i)) + } + if memoReentry[key.field()] == reentryCircular || key.field() == memoStructuredMembers { + c.markReadIncomplete(int(i)) + } + return false + } + y := &c.cycleYields[len(c.cycleYields)-1] + y.shadowed = append(y.shadowed, shadowedResolving{key, i}) + slot.index = int32(len(c.resolutions)) + return true +} + +// resolvingAnyIndex returns the index in resolutions of the computation of +// key in progress, if any, whether or not it is visible. A computation hidden +// by a cycle yield is shadowed in Checker.resolving by one re-entering it, +// until the yield ends, even once that one has ended. +func (c *Checker) resolvingAnyIndex(key memoKey) (int, bool) { + if i, ok := c.resolving.get(key); ok { + return int(i), true + } + for _, y := range slices.Backward(c.cycleYields) { + for _, sh := range y.shadowed { + if sh.key == key { + return int(sh.index), true + } + } + } + return 0, false +} + +// endResolving records that the resolution no longer computes its key. A +// resolution that shadows another during a cycle yield ends before the yield +// restores the one it shadows, so it is still the one recorded. +func (c *Checker) endResolving(f *resolution) { + if f.resolving { + c.resolving.delete(f.key) + f.resolving = false + } +} diff --git a/tsc/internal/checker/services.go b/tsc/internal/checker/services.go index 24cd180cb3e84..85e7155c730cc 100644 --- a/tsc/internal/checker/services.go +++ b/tsc/internal/checker/services.go @@ -153,7 +153,7 @@ func (c *Checker) ForEachExportAndPropertyOfModule(moduleSymbol *ast.Symbol, cb if reducedType.flags&TypeFlagsStructuredType == 0 { return } - for name, symbol := range c.resolveStructuredTypeMembers(reducedType).getMembers().All() { + for name, symbol := range c.resolveStructuredTypeMembers(reducedType).getMembers(c).All() { if c.isNamedMember(symbol, name) { cb(symbol, name) } @@ -369,22 +369,19 @@ func (c *Checker) runWithoutResolvedSignatureCaching[T any](node *ast.Node, fn f cachedTypes := make(map[*ValueSymbolLinks]*Type) for ancestorNode != nil { signatureLinks := c.signatureLinks.Get(ancestorNode) - cachedResolvedSignatures[signatureLinks] = signatureLinks.resolvedSignature - signatureLinks.resolvedSignature = nil + cachedResolvedSignatures[signatureLinks] = signatureLinks.resolvedSignature.swap(nil) if ast.IsFunctionExpressionOrArrowFunction(ancestorNode) { symbolLinks := c.valueSymbolLinks.Get(c.getSymbolOfDeclaration(ancestorNode)) - resolvedType := symbolLinks.resolvedType - cachedTypes[symbolLinks] = resolvedType - symbolLinks.resolvedType = nil + cachedTypes[symbolLinks] = symbolLinks.resolvedType.swap(nil) } ancestorNode = ast.FindAncestor(ancestorNode.Parent, ast.IsCallLikeOrFunctionLikeExpression) } result := fn() for signatureLinks, resolvedSignature := range cachedResolvedSignatures { - signatureLinks.resolvedSignature = resolvedSignature + signatureLinks.resolvedSignature.swap(resolvedSignature) } for symbolLinks, resolvedType := range cachedTypes { - symbolLinks.resolvedType = resolvedType + symbolLinks.resolvedType.swap(resolvedType) } return result } @@ -870,10 +867,10 @@ func (c *Checker) GetConstantValue(node *ast.Node) any { return c.getEnumMemberValue(node).Value } - if c.symbolNodeLinks.Get(node).resolvedSymbol == nil { + if c.symbolNodeLinks.Get(node).resolvedSymbol.get(c) == nil { c.checkExpressionCached(node) // ensure cached resolved symbol is set } - symbol := c.symbolNodeLinks.Get(node).resolvedSymbol + symbol := c.symbolNodeLinks.Get(node).resolvedSymbol.get(c) if symbol == nil && ast.IsEntityNameExpression(node) { symbol = c.resolveEntityName( node, diff --git a/tsc/internal/checker/symbolaccessibility.go b/tsc/internal/checker/symbolaccessibility.go index 204062db49de0..e05c9cdf1b8d1 100644 --- a/tsc/internal/checker/symbolaccessibility.go +++ b/tsc/internal/checker/symbolaccessibility.go @@ -171,18 +171,37 @@ func (c *Checker) getAlternativeContainingModules(symbol *ast.Symbol, enclosingD return nil } containingFile := ast.GetSourceFileOfNode(enclosingDeclaration) - id := ast.GetNodeId(containingFile.AsNode()) - links := c.symbolContainerLinks.Get(symbol) - if links.extendedContainersByFile == nil { - links.extendedContainersByFile = make(map[ast.NodeId][]*ast.Symbol) + // Try to make an import using an import already in the enclosing file, if possible + if results := c.getImportedContainingModules(symbol, containingFile, enclosingDeclaration); len(results) > 0 { + return results } - existing, ok := links.extendedContainersByFile[id] - if ok && existing != nil { - return existing + // No results from files already being imported by this file - expand search (not location-specific, so cached) + if results, ok := c.extendedContainers.get(c, symbol); ok { + return results + } + return c.extendedContainers.compute(c, symbol, func() []*ast.Symbol { + return c.getExternalModuleContainers(symbol) + }) +} + +type symbolFileKey struct { + symbol *ast.Symbol + file *ast.SourceFile +} + +// getImportedContainingModules returns the modules imported by a file that +// export a symbol. The result only depends on the file, as the module names +// are resolved relative to it. +func (c *Checker) getImportedContainingModules(symbol *ast.Symbol, containingFile *ast.SourceFile, enclosingDeclaration *ast.Node) []*ast.Symbol { + if len(containingFile.Imports()) == 0 { + return nil } - var results []*ast.Symbol - if len(containingFile.Imports()) > 0 { - // Try to make an import using an import already in the enclosing file, if possible + key := symbolFileKey{symbol, containingFile} + if results, ok := c.importedContainingModules.get(c, key); ok { + return results + } + return c.importedContainingModules.compute(c, key, func() []*ast.Symbol { + var results []*ast.Symbol for _, importRef := range containingFile.Imports() { if ast.NodeIsSynthesized(importRef) { // Synthetic names can't be resolved by `resolveExternalModuleName` - they'll cause a debug assert if they error @@ -198,23 +217,11 @@ func (c *Checker) getAlternativeContainingModules(symbol *ast.Symbol, enclosingD } results = append(results, resolvedModule) } - if len(results) > 0 { - links.extendedContainersByFile[id] = results - return results - } - } - - if links.extendedContainers != nil { - return *links.extendedContainers - } - // No results from files already being imported by this file - expand search (not location-specific, so cached) - results = c.getExternalModuleContainers(symbol) - links.extendedContainers = &results - return results + return results + }) } type externalModuleContainerIndex struct { - complete bool containersByTarget map[*ast.Symbol][]*ast.Symbol moduleOrder map[*ast.Symbol]int } @@ -227,14 +234,14 @@ func (index *externalModuleContainerIndex) add(target *ast.Symbol, container *as } func (c *Checker) getExternalModuleContainers(symbol *ast.Symbol) []*ast.Symbol { - if c.externalModuleContainers == nil { - c.buildExternalModuleContainerIndex() - } - index := c.externalModuleContainers - if !index.complete { + if c.buildingExternalModuleContainers { // Re-entered from an alias resolved while building the index; answer this query without it. return c.scanExternalModuleContainers(symbol) } + index := c.externalModuleContainers.get(c) + if index == nil { + index = c.buildExternalModuleContainerIndex() + } containers := index.containersByTarget[c.getResolvedTarget(symbol)] parent := c.getParentOfSymbol(symbol) parentOrder, parentIsModule := index.moduleOrder[parent] @@ -250,12 +257,13 @@ func (c *Checker) getExternalModuleContainers(symbol *ast.Symbol) []*ast.Symbol return containers } -func (c *Checker) buildExternalModuleContainerIndex() { +func (c *Checker) buildExternalModuleContainerIndex() *externalModuleContainerIndex { + c.externalModuleContainers.begin(c) + c.buildingExternalModuleContainers = true index := &externalModuleContainerIndex{ containersByTarget: make(map[*ast.Symbol][]*ast.Symbol), moduleOrder: make(map[*ast.Symbol]int, len(c.program.SourceFiles())), } - c.externalModuleContainers = index for _, file := range c.program.SourceFiles() { if !ast.IsExternalModule(file) { continue @@ -269,7 +277,10 @@ func (c *Checker) buildExternalModuleContainerIndex() { index.add(c.getResolvedTarget(exportEquals), container) } } - index.complete = true + c.buildingExternalModuleContainers = false + c.externalModuleContainers.set(c, index) + c.endMemo() + return index } func (c *Checker) scanExternalModuleContainers(symbol *ast.Symbol) []*ast.Symbol { @@ -374,7 +385,7 @@ func (c *Checker) getContainersOfSymbol(symbol *ast.Symbol, enclosingDeclaration continue } c.checkExpressionCached(d.Parent.AsBinaryExpression().Left.Expression()) - sym := c.symbolNodeLinks.Get(d.Parent.AsBinaryExpression().Left.Expression()).resolvedSymbol + sym := c.symbolNodeLinks.Get(d.Parent.AsBinaryExpression().Left.Expression()).resolvedSymbol.get(c) if sym != nil && !slices.Contains(candidates, sym) { candidates = append(candidates, sym) } @@ -513,15 +524,11 @@ func (c *Checker) getAccessibleSymbolChainEx(ctx accessibleSymbolChainContext) [ firstRelevantLocation = node return true }) - links := c.symbolContainerLinks.Get(ctx.symbol) - linkKey := accessibleChainCacheKey{ctx.useOnlyExternalAliasing, firstRelevantLocation, ctx.meaning} - if links.accessibleChainCache == nil { - links.accessibleChainCache = make(map[accessibleChainCacheKey][]*ast.Symbol) - } - existing, ok := links.accessibleChainCache[linkKey] - if ok { + key := accessibleChainCacheKey{ctx.symbol, ctx.useOnlyExternalAliasing, firstRelevantLocation, ctx.meaning} + if existing, ok := c.accessibleChains.get(c, key); ok { return existing } + c.accessibleChains.begin(c, key) var result []*ast.Symbol @@ -533,7 +540,8 @@ func (c *Checker) getAccessibleSymbolChainEx(ctx accessibleSymbolChainContext) [ } return false }) - links.accessibleChainCache[linkKey] = result + c.accessibleChains.set(c, key, result) + c.endMemo() return result } @@ -573,10 +581,8 @@ func (c *Checker) getSymbolTableAliases(symbols ast.SymbolTable, tableId symbolT // Cache globals and exports tables (which are large and revisited often). // Locals tables are small and per-scope, so they are filtered but not cached. if kind == stKindGlobals || kind == stKindExports || kind == stKindResolvedExports { - if c.symbolTableAliasCache != nil { - if aliases, ok := c.symbolTableAliasCache[tableId]; ok { - return aliases - } + if aliases, ok := c.symbolTableAliasCache.get(tableId); ok { + return aliases } } var aliases []*ast.Symbol @@ -586,10 +592,7 @@ func (c *Checker) getSymbolTableAliases(symbols ast.SymbolTable, tableId symbolT } } if kind == stKindGlobals || kind == stKindExports || kind == stKindResolvedExports { - if c.symbolTableAliasCache == nil { - c.symbolTableAliasCache = make(map[symbolTableID][]*ast.Symbol) - } - c.symbolTableAliasCache[tableId] = aliases + c.symbolTableAliasCache.intern(tableId, aliases) } return aliases } @@ -882,10 +885,8 @@ func (c *Checker) someSymbolTableInScope( // synthesized table lets someSymbolTableInScope expose them during accessibility checks. func (c *Checker) getClassExpressionNameTable(location *ast.Node) ast.SymbolTable { nodeId := ast.GetNodeId(location) - if c.classExpressionNameTables != nil { - if table, ok := c.classExpressionNameTables[nodeId]; ok { - return table - } + if table, ok := c.classExpressionNameTables.get(nodeId); ok { + return table } classSymbol := c.getSymbolOfDeclaration(location) nameText := location.AsClassExpression().Name().Text() @@ -893,10 +894,7 @@ func (c *Checker) getClassExpressionNameTable(location *ast.Node) ast.SymbolTabl return nil } table := ast.SymbolTableOf(nameText, classSymbol) - if c.classExpressionNameTables == nil { - c.classExpressionNameTables = make(map[ast.NodeId]ast.SymbolTable) - } - c.classExpressionNameTables[nodeId] = table + c.classExpressionNameTables.intern(nodeId, table) return table } diff --git a/tsc/internal/checker/tracer.go b/tsc/internal/checker/tracer.go index 0fea57f8d4d70..1b82937a26c55 100644 --- a/tsc/internal/checker/tracer.go +++ b/tsc/internal/checker/tracer.go @@ -191,7 +191,7 @@ func (a *tracedTypeAdapter) ConditionalTrueType() tracing.TracedType { if a.t.flags&TypeFlagsConditional == 0 { return nil } - t := a.t.AsConditionalType().resolvedTrueType + t := a.t.AsConditionalType().resolvedTrueType.peek() if t == nil { return nil } @@ -202,7 +202,7 @@ func (a *tracedTypeAdapter) ConditionalFalseType() tracing.TracedType { if a.t.flags&TypeFlagsConditional == 0 { return nil } - t := a.t.AsConditionalType().resolvedFalseType + t := a.t.AsConditionalType().resolvedFalseType.peek() if t == nil { return nil } @@ -246,7 +246,7 @@ func (a *tracedTypeAdapter) ReferenceTypeArguments() []tracing.TracedType { if a.t.flags&TypeFlagsObject == 0 || a.t.objectFlags&ObjectFlagsReference == 0 { return nil } - return wrapTypes(a.t.AsTypeReference().resolvedTypeArguments) + return wrapTypes(a.t.AsTypeReference().resolvedTypeArguments.peek()) } func (a *tracedTypeAdapter) ReferenceNode() *ast.Node { @@ -304,7 +304,7 @@ func (a *tracedTypeAdapter) EvolvingArrayFinalType() tracing.TracedType { if a.t.flags&TypeFlagsObject == 0 || a.t.objectFlags&ObjectFlagsEvolvingArray == 0 { return nil } - t := a.t.AsEvolvingArrayType().finalArrayType + t := a.t.AsEvolvingArrayType().finalArrayType.peek() if t == nil { return nil } @@ -319,11 +319,12 @@ func (a *tracedTypeAdapter) Pattern() *ast.Node { if a.checker == nil { return nil } - return a.checker.patternForType[a.t] + p, _ := a.checker.patternForType.get(a.t) + return p } func (a *tracedTypeAdapter) RecursionIdentity() any { - return getRecursionIdentity(a.t).value + return a.checker.getRecursionIdentity(a.t).value } func (a *tracedTypeAdapter) Display() string { diff --git a/tsc/internal/checker/types.go b/tsc/internal/checker/types.go index c8f130693f83d..1f10d278c194b 100644 --- a/tsc/internal/checker/types.go +++ b/tsc/internal/checker/types.go @@ -181,23 +181,23 @@ type SymbolReferenceLinks struct { // Links for value symbols type ValueSymbolLinks struct { - resolvedType *Type // Type of value symbol + resolvedType memo[*Type] // Type of value symbol target *ast.Symbol mapper *TypeMapper extra *valueSymbolExtra } type valueSymbolExtra struct { - writeType *Type + writeType memo[*Type] nameType *Type containingType *Type // Mapped type for mapped type property, containing union or intersection type for synthetic property } -func (links *ValueSymbolLinks) getWriteType() *Type { +func (links *ValueSymbolLinks) getWriteType(c *Checker) *Type { if links.extra == nil { return nil } - return links.extra.writeType + return links.extra.writeType.get(c) } func (links *ValueSymbolLinks) getNameType() *Type { @@ -221,7 +221,19 @@ func (links *ValueSymbolLinks) setWriteType(c *Checker, typ *Type) { } links.extra = c.valueSymbolExtraArena.New() } - links.extra.writeType = typ + links.extra.writeType.set(c, typ) +} + +// setNewWriteType sets the write type of a symbol being created; see +// memo.setIdentity. +func (links *ValueSymbolLinks) setNewWriteType(c *Checker, typ *Type) { + if links.extra == nil { + if typ == nil { + return + } + links.extra = c.valueSymbolExtraArena.New() + } + links.extra.writeType.setIdentity(c, typ) } func (links *ValueSymbolLinks) setNameType(c *Checker, typ *Type) { @@ -262,10 +274,11 @@ type DeferredSymbolLinks struct { // Links for alias symbols type AliasSymbolLinks struct { - immediateTarget *ast.Symbol // Immediate target of an alias. May be another alias. Do not access directly, use `checker.getImmediateAliasedSymbol` instead. - aliasTarget *ast.Symbol // Resolved (non-alias) target of an alias - referenced bool // True if alias symbol has been referenced as a value that can be emitted - typeOnlyDeclaration *ast.Node // First resolved alias declaration that makes the symbol only usable in type constructs + immediateTarget memo[*ast.Symbol] // Immediate target of an alias. May be another alias. Do not access directly, use `checker.getImmediateAliasedSymbol` instead. + aliasTarget memo[*ast.Symbol] // Resolved (non-alias) target of an alias + referenced bool // True if alias symbol has been referenced as a value that can be emitted + referenceChecked bool // True if checking has followed the reference; see referenceAliasSymbol + typeOnlyDeclaration *ast.Node // First resolved alias declaration that makes the symbol only usable in type constructs } // Links for module symbols @@ -273,7 +286,6 @@ type AliasSymbolLinks struct { type ModuleSymbolLinks struct { resolvedExports ast.SymbolTable // Resolved exports of module or combined early- and late-bound static members of a class. typeOnlyExportStarMap map[string]*ast.Node // Set on a module symbol when some of its exports were resolved through a 'export type * from "mod"' declaration - exportsChecked bool } type ReverseMappedSymbolLinks struct { @@ -298,20 +310,15 @@ type ExportTypeLinks struct { // Links for type aliases type TypeAliasLinks struct { - declaredType *Type - typeParameters []*Type // Type parameters of type alias (undefined if non-generic) - instantiations map[CacheHashKey]*Type // Instantiations of generic type alias (undefined if non-generic) - isConstructorDeclaredProperty bool + declaredType memoReported[*Type] + typeParameters []*Type // Type parameters of type alias (undefined if non-generic) + instantiations memoMap[CacheHashKey, *Type] // Instantiations of generic type alias (undefined if non-generic) } // Links for declared types (type parameters, class types, interface types, enums) type DeclaredTypeLinks struct { - declaredType *Type - interfaceChecked bool - indexSignaturesChecked bool - typeParametersChecked bool - enumChecked bool + declaredType memo[*Type] } // Links for switch clauses @@ -326,11 +333,10 @@ const ( ) type SwitchStatementLinks struct { - exhaustiveState ExhaustiveState // Switch statement exhaustiveness - switchTypesComputed bool - witnessesComputed bool - switchTypes []*Type - witnesses []string + exhaustiveState memo[ExhaustiveState] // Switch statement exhaustiveness + witnessesComputed bool + switchTypes memo[[]*Type] + witnesses []string // Syntactic, so needs no memo } type ArrayLiteralLinks struct { @@ -360,7 +366,7 @@ type SpreadLinks struct { // Links for variances of type aliases and interface types type VarianceLinks struct { - variances []VarianceFlags + variances memo[[]VarianceFlags] } type VarianceFlags uint32 @@ -383,17 +389,12 @@ type MarkedAssignmentSymbolLinks struct { } type accessibleChainCacheKey struct { + symbol *ast.Symbol useOnlyExternalAliasing bool location *ast.Node meaning ast.SymbolFlags } -type ContainingSymbolLinks struct { - extendedContainersByFile map[ast.NodeId][]*ast.Symbol // Symbols of nodes which which logically contain this one, cached by file the request is made within - extendedContainers *[]*ast.Symbol // Containers (other than the parent) which this symbol is aliased in - accessibleChainCache map[accessibleChainCacheKey][]*ast.Symbol -} - type AccessFlags uint32 const ( @@ -414,14 +415,11 @@ type NodeCheckFlags uint32 const ( NodeCheckFlagsNone NodeCheckFlags = 0 - NodeCheckFlagsTypeChecked NodeCheckFlags = 1 << 0 // Node has been type checked NodeCheckFlagsContextChecked NodeCheckFlags = 1 << 6 // Contextual types have been assigned NodeCheckFlagsSignatureContextChecked NodeCheckFlags = 1 << 7 // Signature of a contextually typed function has been checked NodeCheckFlagsEnumValuesComputed NodeCheckFlags = 1 << 10 // Values for enum members have been computed, and any errors have been reported for them. - NodeCheckFlagsAssignmentsMarked NodeCheckFlags = 1 << 17 // Parameter assignments have been marked NodeCheckFlagsContainsClassWithPrivateIdentifiers NodeCheckFlags = 1 << 20 // Marked on all block-scoped containers containing a class with private identifiers. NodeCheckFlagsContainsSuperPropertyInStaticInitializer NodeCheckFlags = 1 << 21 // Marked on all block-scoped containers containing a static initializer with 'super.x' or 'super[x]'. - NodeCheckFlagsInCheckIdentifier NodeCheckFlags = 1 << 22 NodeCheckFlagsInitializerIsUndefined NodeCheckFlags = 1 << 24 NodeCheckFlagsInitializerIsUndefinedComputed NodeCheckFlags = 1 << 25 ) @@ -431,39 +429,42 @@ const ( type NodeLinks struct { flags NodeCheckFlags // Set of flags specific to Node declarationRequiresScopeChange core.Tristate // Set by `useOuterVariableScopeInParameter` in checker when downlevel emit would change the name resolution scope inside of a parameter. - hasReportedStatementInAmbientContext bool // Cache boolean if we report statements in ambient context + hasReportedStatementInAmbientContext memo[bool] // Cache boolean if we report statements in ambient context } type SymbolNodeLinks struct { - resolvedSymbol *ast.Symbol // Resolved symbol associated with node + resolvedSymbol memo[*ast.Symbol] // Resolved symbol associated with node } type TypeNodeLinks struct { - resolvedType *Type // Resolved type associated with node - outerTypeParameters *[]*Type // Outer type parameters of anonymous object type + resolvedType memo[*Type] // Resolved type associated with node + outerTypeParameters memo[*[]*Type] // Outer type parameters of anonymous object type } -func (links *TypeNodeLinks) getOuterTypeParameters() []*Type { - if links.outerTypeParameters == nil { - return nil +func (links *TypeNodeLinks) getOuterTypeParameters(c *Checker) []*Type { + if p := links.outerTypeParameters.get(c); p != nil { + return *p } - return *links.outerTypeParameters + return nil } func (links *TypeNodeLinks) setOuterTypeParameters(c *Checker, types []*Type) { if types == nil { - links.outerTypeParameters = nil + links.outerTypeParameters.set(c, nil) return } - if links.outerTypeParameters == nil { - links.outerTypeParameters = c.outerTypeParameterListArena.New() - } - *links.outerTypeParameters = types + p := c.outerTypeParameterListArena.New() + *p = types + links.outerTypeParameters.set(c, p) } type ComputedNameNodeLinks struct { - hasName core.Tristate // If the node has a computable name - name string // Resolved name associated with the type of the node + name memo[computedName] // Resolved name associated with the type of the node +} + +type computedName struct { + name string + exists core.Tristate // If the node has a computable name; unknown until computed } // Links for enum members @@ -478,6 +479,7 @@ type SourceFileLinks struct { typeChecked bool unusedChecked bool externalHelpersModule *ast.Symbol + externalHelperRequests []externalHelperRequest requestedExternalEmitHelpers ExternalEmitHelpers deferredNodes collections.OrderedSet[*ast.Node] identifierCheckNodes []*ast.Node @@ -485,15 +487,15 @@ type SourceFileLinks struct { localJsxFragmentNamespace string localJsxFactory *ast.EntityName localJsxFragmentFactory *ast.EntityName - jsxFragmentType *Type + jsxFragmentType memo[*Type] } // Signature specific links type SignatureLinks struct { - resolvedSignature *Signature // Cached signature of signature node or call expression - effectsSignature *Signature // Signature with possible control flow effects - decoratorSignature *Signature // Signature for decorator as if invoked by the runtime + resolvedSignature memo[*Signature] // Cached signature of signature node or call expression + effectsSignature memo[*Signature] // Signature with possible control flow effects + decoratorSignature memo[*Signature] // Signature for decorator as if invoked by the runtime } type TypeFlags uint32 @@ -707,9 +709,8 @@ const ( ObjectFlagsSingleSignatureType = 1 << 25 // A single signature type extracted from a potentially broader type ObjectFlagsIsClassInstanceClone = 1 << 26 // Type is a clone of a class instance type // Flags that require TypeFlags.Object and ObjectFlags.Reference - ObjectFlagsIdenticalBaseTypeCalculated = 1 << 27 // has had `getSingleBaseForNonAugmentingSubtype` invoked on it already - ObjectFlagsIdenticalBaseTypeExists = 1 << 28 // has a defined cachedEquivalentBaseType member - ObjectFlagsFromTypeNode = 1 << 29 // Originates in resolution of AST type node + ObjectFlagsIdenticalBaseTypeExists = 1 << 28 // has a defined cachedEquivalentBaseType member + ObjectFlagsFromTypeNode = 1 << 29 // Originates in resolution of AST type node // Flags that require TypeFlags.UnionOrIntersection or TypeFlags.Substitution ObjectFlagsIsGenericTypeComputed = 1 << 22 // IsGenericObjectType flag has been computed ObjectFlagsIsGenericObjectType = 1 << 23 // Union or intersection contains generic object type @@ -900,6 +901,14 @@ func (t *Type) AsStructuredType() *StructuredType { return nil } +// asResolvedStructuredType is AsStructuredType for a type known to be +// structured, such as one with resolved members, without checking. +func (t *Type) asResolvedStructuredType() *StructuredType { + // SAFETY: Callers only pass types of the kinds AsStructuredType accepts, + // which embed StructuredType at offset zero. + return (*StructuredType)(unsafe.Pointer(t)) +} + func (t *Type) AsObjectType() *ObjectType { if t.kind >= typeKindObject && t.kind <= typeKindEvolvingArray { // SAFETY: Types of these kinds are structs that embed ObjectType at offset zero. @@ -1109,7 +1118,7 @@ type UniqueESSymbolType struct { type ConstrainedType struct { TypeBase - resolvedBaseConstraint *Type + resolvedBaseConstraint memo[*Type] } func (t *ConstrainedType) AsConstrainedType() *ConstrainedType { return t } @@ -1147,7 +1156,7 @@ func (t *StructuredType) ConstructSignatures() []*Signature { return slices.Clip(t.extra.signatures[t.extra.callSignatureCount:]) } -func (t *StructuredType) getMember(name string) *ast.Symbol { +func (t *StructuredType) getMember(c *Checker, name string) *ast.Symbol { if t.members != nil { return t.members.Get(name) } @@ -1159,12 +1168,12 @@ func (t *StructuredType) getMember(name string) *ast.Symbol { if index < 0 { return nil } - return t.getPropertyAt(index) + return t.getPropertyAt(c, index) } -func (t *StructuredType) getMembers() ast.SymbolTable { +func (t *StructuredType) getMembers(c *Checker) ast.SymbolTable { if t.members == nil { - if properties := t.getProperties(); len(properties) != 0 { + if properties := t.getProperties(c); len(properties) != 0 { t.members = ast.NewSymbolTableWithCapacity(len(properties)) for _, member := range properties { t.members.Set(member.Name(), member) @@ -1174,7 +1183,7 @@ func (t *StructuredType) getMembers() ast.SymbolTable { return t.members } -func (t *StructuredType) getPropertyAt(index int) *ast.Symbol { +func (t *StructuredType) getPropertyAt(c *Checker, index int) *ast.Symbol { lazy := t.getLazyState() if lazy == nil { return t.properties[index] @@ -1187,19 +1196,22 @@ func (t *StructuredType) getPropertyAt(index int) *ast.Symbol { symbol := lazy.layout.members.SymbolAt(index) baseIndex := lazy.layout.baseIndices[index] if baseIndex < 0 { - member = t.checker.instantiateSymbol(symbol, lazy.mapper) + member = c.instantiateSymbol(symbol, lazy.mapper) } else { - member = t.checker.getPropertyOfType(lazy.baseTypes[baseIndex], symbol.Name()) + member = c.getPropertyOfType(lazy.baseTypes[baseIndex], symbol.Name()) } t.properties[index] = member + if c.logResults() { + c.logPropertySlotDiscard(t, lazy, index) + } } return member } -func (t *StructuredType) getProperties() []*ast.Symbol { +func (t *StructuredType) getProperties(c *Checker) []*ast.Symbol { if lazy := t.getLazyState(); lazy != nil && !lazy.propertiesResolved { for i := range lazy.layout.members.Len() { - t.getPropertyAt(i) + t.getPropertyAt(c, i) } lazy.propertiesResolved = true } @@ -1246,7 +1258,7 @@ func (t *StructuredType) ensureExtra() *structuredTypeExtra { } func (t *StructuredType) Properties() []*ast.Symbol { - return t.getProperties() + return t.getProperties(t.checker) } // Except for tuple type references and reverse mapped types, all object types have an associated symbol. @@ -1284,9 +1296,9 @@ func (t *StructuredType) Properties() []*ast.Symbol { type ObjectType struct { StructuredType - target *Type // Target of instantiated type - mapper *TypeMapper // Type mapper for instantiated type - instantiations map[CacheHashKey]*Type // Map of type instantiations + target *Type // Target of instantiated type + mapper *TypeMapper // Type mapper for instantiated type + instantiations memoMap[CacheHashKey, *Type] // Map of type instantiations } func (t *ObjectType) AsObjectType() *ObjectType { return t } @@ -1296,7 +1308,7 @@ func (t *ObjectType) AsObjectType() *ObjectType { return t } type TypeReference struct { ObjectType node *ast.Node // TypeReferenceNode | ArrayTypeNode | TupleTypeNode when deferred, else nil - resolvedTypeArguments []*Type + resolvedTypeArguments memo[[]*Type] lazy *LazyState // Resolved member layout and instantiation state } @@ -1325,8 +1337,8 @@ type InterfaceType struct { thisType *Type // The "this" type (nil if none) baseTypesResolved bool declaredMembersResolved bool - resolvedBaseConstructorType *Type - resolvedBaseTypes []*Type + resolvedBaseConstructorType memo[*Type] + resolvedBaseTypes memo[[]*Type] declaredMembers ast.SymbolTable // Declared members declaredCallSignatures []*Signature // Declared call signatures declaredConstructSignatures []*Signature // Declared construct signatures @@ -1416,19 +1428,19 @@ type InstantiationExpressionType struct { type MappedType struct { ObjectType declaration *ast.MappedTypeNode - typeParameter *Type - constraintType *Type - nameType *Type - templateType *Type - modifiersType *Type - resolvedApparentType *Type - containsError bool -} - -func (t *MappedType) TypeParameter() *Type { return t.typeParameter } -func (t *MappedType) ConstraintType() *Type { return t.constraintType } -func (t *MappedType) NameType() *Type { return t.nameType } -func (t *MappedType) TemplateType() *Type { return t.templateType } + typeParameter memo[*Type] + constraintType memo[*Type] + nameType memo[*Type] + templateType memo[*Type] + modifiersType memo[*Type] + resolvedApparentType memo[*Type] + containsError memo[bool] +} + +func (t *MappedType) TypeParameter() *Type { return t.typeParameter.peek() } +func (t *MappedType) ConstraintType() *Type { return t.constraintType.peek() } +func (t *MappedType) NameType() *Type { return t.nameType.peek() } +func (t *MappedType) TemplateType() *Type { return t.templateType.peek() } func (t *MappedType) ResolveComponents(c *Checker, typ *Type) { c.getTypeParameterFromMappedType(typ) c.getConstraintTypeFromMappedType(typ) @@ -1450,7 +1462,7 @@ type ReverseMappedType struct { type EvolvingArrayType struct { ObjectType elementType *Type - finalArrayType *Type + finalArrayType memo[*Type] } // UnionOrIntersectionTypeData @@ -1458,9 +1470,9 @@ type EvolvingArrayType struct { type UnionOrIntersectionType struct { StructuredType types []*Type - propertyCache ast.SymbolTable // Augmented entries, except complete ones already in the non-augmented cache. - propertyCacheWithoutFunctionPropertyAugment ast.SymbolTable - resolvedProperties []*ast.Symbol + propertyCache memoMap[string, *ast.Symbol] // Augmented entries, except complete ones already in the non-augmented cache. + propertyCacheWithoutFunctionPropertyAugment memoMap[string, *ast.Symbol] + resolvedProperties memo[[]*ast.Symbol] } func (t *UnionOrIntersectionType) AsUnionOrIntersectionType() *UnionOrIntersectionType { return t } @@ -1473,10 +1485,10 @@ func (t *UnionOrIntersectionType) Types() []*Type { type UnionType struct { UnionOrIntersectionType - resolvedReducedType *Type + resolvedReducedType memo[*Type] regularType *Type origin *Type // Denormalized union, intersection, or index type in which union originates - keyPropertyName string // Property with unique unit type that exists in every object/intersection in union type + keyPropertyName memo[string] // Property with unique unit type that exists in every object/intersection in union type constituentMap map[*Type]*Type // Constituents keyed by unit type discriminants } @@ -1484,21 +1496,21 @@ type UnionType struct { type IntersectionType struct { UnionOrIntersectionType - resolvedApparentType *Type - uniqueLiteralFilledInstantiation *Type // Instantiation with type parameters mapped to never type + resolvedApparentType memo[*Type] + uniqueLiteralFilledInstantiation memo[*Type] // Instantiation with type parameters mapped to never type } // TypeParameter type TypeParameter struct { ConstrainedType - constraint *Type + constraint memo[*Type] target *Type mapper *TypeMapper isThisType bool isDistributed bool - resolvedDefaultType *Type - distributedType *Type + resolvedDefaultType memo[*Type] + distributedType *Type // The distributed counterpart, or for a distributed type parameter, its original origin *Type // The type parameter a restrictive type parameter was made from, or the tuple target of a tuple element type parameter } @@ -1569,7 +1581,7 @@ type ConditionalRoot struct { isDistributive bool inferTypeParameters []*Type outerTypeParameters []*Type - instantiations map[CacheHashKey]*Type + instantiations memoMap[CacheHashKey, *Type] alias *TypeAlias } @@ -1578,11 +1590,11 @@ type ConditionalType struct { root *ConditionalRoot checkType *Type extendsType *Type - resolvedTrueType *Type - resolvedFalseType *Type - resolvedInferredTrueType *Type // The `trueType` instantiated with the `combinedMapper`, if present - resolvedDefaultConstraint *Type - resolvedConstraintOfDistributive *Type + resolvedTrueType memo[*Type] + resolvedFalseType memo[*Type] + resolvedInferredTrueType memo[*Type] // The `trueType` instantiated with the `combinedMapper`, if present + resolvedDefaultConstraint memo[*Type] + resolvedConstraintOfDistributive memo[*Type] mapper *TypeMapper combinedMapper *TypeMapper } @@ -1621,10 +1633,10 @@ type Signature struct { id SignatureId flags SignatureFlags minArgumentCount int32 - resolvedMinArgumentCount int32 + resolvedMinArgumentCount memo[int32] declaration *ast.Node parameters []*ast.Symbol - resolvedReturnType *Type + resolvedReturnType memo[*Type] target *Signature mapper *TypeMapper extra *signatureExtra @@ -1667,7 +1679,20 @@ func (s *Signature) getResolvedTypePredicate(c *Checker) *TypePredicate { return s.extra.resolvedTypePredicate } +// setResolvedTypePredicate caches the type predicate of the signature, a flag +// memo; see initResolvedTypePredicate. func (s *Signature) setResolvedTypePredicate(c *Checker, predicate *TypePredicate) { + if c.logResults() { + old := s.getResolvedTypePredicate(c) + c.logFlagMemoDiscard(func() { s.initResolvedTypePredicate(c, old) }) + } + s.initResolvedTypePredicate(c, predicate) + c.setFlagMemo(memoKeyOf(s, memoTypePredicate)) +} + +// initResolvedTypePredicate sets the type predicate of a signature being +// created. +func (s *Signature) initResolvedTypePredicate(c *Checker, predicate *TypePredicate) { s.flags &^= signatureFlagsNoTypePredicate if predicate != nil && predicate == c.noTypePredicate { s.flags |= signatureFlagsNoTypePredicate diff --git a/tsc/internal/checker/types_test.go b/tsc/internal/checker/types_test.go index 6e0f19446fe00..63ec6affc72df 100644 --- a/tsc/internal/checker/types_test.go +++ b/tsc/internal/checker/types_test.go @@ -2,6 +2,7 @@ package checker import ( "encoding/binary" + "math/rand/v2" "runtime" "slices" "strconv" @@ -18,8 +19,8 @@ import ( func TestCheckerNodeLinkStores(t *testing.T) { t.Parallel() f := ast.NewNodeFactory(ast.NodeFactoryHooks{}) - first := &Checker{} - second := &Checker{} + first := newBareTestChecker() + second := newBareTestChecker() nodes := make([]*ast.Node, 4096) for i := range nodes { node := f.NewIdentifier(strconv.Itoa(i)) @@ -29,18 +30,18 @@ func TestCheckerNodeLinkStores(t *testing.T) { nodes[i] = node assert.Assert(t, !first.typeNodeLinks.Has(node)) assert.Assert(t, !first.signatureLinks.Has(node)) - first.typeNodeLinks.Get(node).resolvedType = &Type{id: TypeId(i + 1)} - first.signatureLinks.Get(node).resolvedSignature = &Signature{id: SignatureId(i + 1)} - second.typeNodeLinks.Get(node).resolvedType = &Type{id: TypeId(i + 2)} - second.signatureLinks.Get(node).resolvedSignature = &Signature{id: SignatureId(i + 2)} + first.typeNodeLinks.Get(node).resolvedType.intern(&Type{id: TypeId(i + 1)}) + first.signatureLinks.Get(node).resolvedSignature.intern(&Signature{id: SignatureId(i + 1)}) + second.typeNodeLinks.Get(node).resolvedType.intern(&Type{id: TypeId(i + 2)}) + second.signatureLinks.Get(node).resolvedSignature.intern(&Signature{id: SignatureId(i + 2)}) } f.ReleaseArenas() runtime.GC() for i, node := range nodes { - assert.Equal(t, first.typeNodeLinks.Get(node).resolvedType.id, TypeId(i+1)) - assert.Equal(t, first.signatureLinks.Get(node).resolvedSignature.id, SignatureId(i+1)) - assert.Equal(t, second.typeNodeLinks.Get(node).resolvedType.id, TypeId(i+2)) - assert.Equal(t, second.signatureLinks.Get(node).resolvedSignature.id, SignatureId(i+2)) + assert.Equal(t, first.typeNodeLinks.Get(node).resolvedType.get(first).id, TypeId(i+1)) + assert.Equal(t, first.signatureLinks.Get(node).resolvedSignature.get(first).id, SignatureId(i+1)) + assert.Equal(t, second.typeNodeLinks.Get(node).resolvedType.get(second).id, TypeId(i+2)) + assert.Equal(t, second.signatureLinks.Get(node).resolvedSignature.get(second).id, SignatureId(i+2)) } clone := nodes[0].Clone(f) assert.Assert(t, !first.typeNodeLinks.Has(clone)) @@ -85,7 +86,7 @@ func TestTypeCasts(t *testing.T) { {typeKindIntersection, &IntersectionType{}, func(t *Type) any { return t.AsIntersectionType() }}, } for _, test := range tests { - c := &Checker{} + c := newBareTestChecker() typ := c.newType(TypeFlagsAny, ObjectFlagsNone, test.data.AsType(), test.kind) runtime.GC() assert.Equal(t, test.cast(typ), any(test.data)) @@ -175,15 +176,17 @@ func BenchmarkTypeListKey(b *testing.B) { } } +// newBareTestChecker returns an uninitialized checker with an empty store. +func newBareTestChecker() *Checker { + c := &Checker{memoStore: &memoStore{}} + c.initResolutions() + c.root = c + return c +} + func newCompositeTestChecker() (*Checker, []*Type) { - c := &Checker{ - strictNullChecks: true, - unionTypes: make(map[CacheHashKey]*Type), - unionOfUnionTypes: make(map[UnionOfUnionKey]*Type), - intersectionTypes: make(map[CacheHashKey]*Type), - intersectionContentTypes: make(map[CacheHashKey]*Type), - subtypeReductionCache: make(map[CacheHashKey][]*Type), - } + c := newBareTestChecker() + c.strictNullChecks = true c.compareSymbols = c.compareSymbolsWorker c.neverType = c.newIntrinsicType(TypeFlagsNever, "never") c.unknownType = c.newIntrinsicType(TypeFlagsUnknown, "unknown") @@ -307,7 +310,7 @@ func TestIntersectionReductionAllocations(t *testing.T) { //nolint:paralleltest func TestTemporaryPropertyCounts(t *testing.T) { t.Parallel() - c := &Checker{} + c := newBareTestChecker() names := func(p *temporaryPropertyCounts) []string { var result []string for _, e := range p.entries { @@ -349,7 +352,7 @@ func TestTemporaryPropertyCounts(t *testing.T) { assert.Equal(t, counts.Size(), 0) } assert.Assert(t, c.getTemporaryPropertyCounts() != reused) - other := &Checker{} + other := newBareTestChecker() assert.Assert(t, other.getTemporaryPropertyCounts() != outer) } @@ -367,7 +370,7 @@ func TestIntersectionReductionReentrancy(t *testing.T) { assert.Assert(t, c.freePropertyCounts.next != nil) workspace := c.freePropertyCounts conflicting := c.newSymbolEx(ast.SymbolFlagsProperty, "value", ast.CheckFlagsContainsPrivate) - outer.AsUnionOrIntersectionType().propertyCacheWithoutFunctionPropertyAugment.Set("value", conflicting) + outer.AsUnionOrIntersectionType().propertyCacheWithoutFunctionPropertyAugment.intern("value", conflicting) assert.Assert(t, c.somePropertyReducesToNever(outer)) assert.Equal(t, c.freePropertyCounts, workspace) for counts := c.freePropertyCounts; counts != nil; counts = counts.next { @@ -489,7 +492,7 @@ func TestCompactValueSymbolLinksLayout(t *testing.T) { assert.Equal(t, unsafe.Sizeof(valueSymbolExtra{}), 3*unsafe.Sizeof(uintptr(0))) c, types := newCompositeTestChecker() var links ValueSymbolLinks - assert.Assert(t, links.getWriteType() == nil) + assert.Assert(t, links.getWriteType(c) == nil) assert.Assert(t, links.getNameType() == nil) assert.Assert(t, links.getContainingType() == nil) links.setWriteType(c, nil) @@ -506,12 +509,12 @@ func TestCompactValueSymbolLinksLayout(t *testing.T) { other.setNameType(c, types[2]) } runtime.GC() - assert.Equal(t, links.getWriteType(), types[0]) + assert.Equal(t, links.getWriteType(c), types[0]) assert.Equal(t, links.getNameType(), types[1]) assert.Equal(t, links.getContainingType(), types[2]) links.setNameType(c, nil) assert.Assert(t, links.getNameType() == nil) - assert.Equal(t, links.getWriteType(), types[0]) + assert.Equal(t, links.getWriteType(c), types[0]) assert.Equal(t, links.getContainingType(), types[2]) } @@ -540,9 +543,9 @@ func TestUnionIntersectionPropertyCacheSharing(t *testing.T) { assert.Equal(t, c.getUnionOrIntersectionProperty(typ, name, !augmentedFirst), symbol) assert.Equal(t, c.getUnionOrIntersectionProperty(typ, name, augmentedFirst), symbol) } - assert.Equal(t, data.propertyCacheWithoutFunctionPropertyAugment.Len(), members.Len()) + assert.Equal(t, len(data.propertyCacheWithoutFunctionPropertyAugment.memoEntries), members.Len()) if !augmentedFirst { - assert.Assert(t, data.propertyCache == nil) + assert.Assert(t, data.propertyCache.isEmpty()) } runtime.GC() for name, symbol := range members.All() { @@ -570,25 +573,23 @@ func TestUnionIntersectionPropertyCacheModes(t *testing.T) { data := typ.AsUnionOrIntersectionType() assert.Assert(t, c.getUnionOrIntersectionProperty(typ, "missing", true) == nil) assert.Assert(t, c.getUnionOrIntersectionProperty(typ, "missing", false) == nil) - assert.Assert(t, data.propertyCache == nil) - assert.Assert(t, data.propertyCacheWithoutFunctionPropertyAugment == nil) - data.propertyCacheWithoutFunctionPropertyAugment = ast.NewSymbolTable() - data.propertyCache = ast.NewSymbolTable() + assert.Assert(t, data.propertyCache.isEmpty()) + assert.Assert(t, data.propertyCacheWithoutFunctionPropertyAugment.isEmpty()) for _, flags := range []ast.CheckFlags{ast.CheckFlagsReadPartial, ast.CheckFlagsWritePartial} { name := strconv.Itoa(int(flags)) partial := c.newSymbolEx(ast.SymbolFlagsProperty, name, flags) complete := c.newSymbol(ast.SymbolFlagsProperty, name) - data.propertyCacheWithoutFunctionPropertyAugment.Set(name, partial) + data.propertyCacheWithoutFunctionPropertyAugment.intern(name, partial) assert.Equal(t, c.getUnionOrIntersectionProperty(typ, name, true), partial) assert.Assert(t, c.getUnionOrIntersectionProperty(typ, name, false) == nil) assert.Equal(t, c.getPropertyOfUnionOrIntersectionType(typ, name, true) == nil, flags&ast.CheckFlagsReadPartial != 0) - data.propertyCache.Set(name, complete) + data.propertyCache.intern(name, complete) assert.Equal(t, c.getUnionOrIntersectionProperty(typ, name, false), complete) } nonAugmented := c.newSymbol(ast.SymbolFlagsProperty, "different") augmented := c.newSymbol(ast.SymbolFlagsProperty, "different") - data.propertyCacheWithoutFunctionPropertyAugment.Set("different", nonAugmented) - data.propertyCache.Set("different", augmented) + data.propertyCacheWithoutFunctionPropertyAugment.intern("different", nonAugmented) + data.propertyCache.intern("different", augmented) assert.Equal(t, c.getUnionOrIntersectionProperty(typ, "different", true), nonAugmented) assert.Equal(t, c.getUnionOrIntersectionProperty(typ, "different", false), augmented) }) @@ -650,30 +651,28 @@ func TestCompactTypeNodeLinksLayout(t *testing.T) { assert.Equal(t, unsafe.Sizeof(TypeNodeLinks{}), 2*unsafe.Sizeof(uintptr(0))) c, types := newCompositeTestChecker() var links TypeNodeLinks - links.resolvedType = types[0] - assert.Assert(t, links.getOuterTypeParameters() == nil) + links.resolvedType.intern(types[0]) + assert.Assert(t, links.getOuterTypeParameters(c) == nil) links.setOuterTypeParameters(c, nil) - assert.Assert(t, links.outerTypeParameters == nil) + assert.Assert(t, links.outerTypeParameters.memoValue == nil) links.setOuterTypeParameters(c, []*Type{}) - assert.Assert(t, links.getOuterTypeParameters() != nil) - assert.Equal(t, len(links.getOuterTypeParameters()), 0) - entry := links.outerTypeParameters + assert.Assert(t, links.getOuterTypeParameters(c) != nil) + assert.Equal(t, len(links.getOuterTypeParameters(c)), 0) list := slices.Clone(types) links.setOuterTypeParameters(c, list) - assert.Equal(t, links.outerTypeParameters, entry) - assert.Equal(t, links.getOuterTypeParameters()[0], types[0]) + assert.Equal(t, links.getOuterTypeParameters(c)[0], types[0]) list[0] = types[1] - assert.Equal(t, links.getOuterTypeParameters()[0], types[1]) + assert.Equal(t, links.getOuterTypeParameters(c)[0], types[1]) for range 1024 { var other TypeNodeLinks other.setOuterTypeParameters(c, types) } runtime.GC() - assert.Equal(t, links.getOuterTypeParameters()[0], types[1]) - assert.Equal(t, links.resolvedType, types[0]) + assert.Equal(t, links.getOuterTypeParameters(c)[0], types[1]) + assert.Equal(t, links.resolvedType.get(c), types[0]) links.setOuterTypeParameters(c, nil) - assert.Assert(t, links.getOuterTypeParameters() == nil) - assert.Equal(t, links.resolvedType, types[0]) + assert.Assert(t, links.getOuterTypeParameters(c) == nil) + assert.Equal(t, links.resolvedType.get(c), types[0]) runtime.KeepAlive(c) } @@ -694,7 +693,7 @@ func TestSignatureExtra(t *testing.T) { assert.Assert(t, sig.getComposite() == nil) assert.Assert(t, sig.getIsolatedSignatureType() == nil) assert.Assert(t, sig.getResolvedTypePredicate(c) == nil) - assert.Equal(t, sig.resolvedMinArgumentCount, int32(-1)) + assert.Equal(t, sig.resolvedMinArgumentCount.peek(), int32(-1)) sig.setTypeParameters(c, nil) sig.setThisParameter(c, nil) sig.setComposite(c, nil) @@ -749,8 +748,36 @@ func TestSignatureExtra(t *testing.T) { sig.setComposite(c, nil) assert.Equal(t, sig.extra, entry) assert.Assert(t, sig.TypeParameters() == nil) - assert.Equal(t, sig.resolvedReturnType, types[0]) + assert.Equal(t, sig.resolvedReturnType.get(c), types[0]) assert.Equal(t, sig.minArgumentCount, int32(1)) assert.Equal(t, sig.getResolvedTypePredicate(c), predicate) runtime.KeepAlive(c) } + +func TestResolvingTable(t *testing.T) { + t.Parallel() + var table resolvingTable + want := map[memoKey]int32{} + rng := rand.New(rand.NewPCG(1, 2)) + for i := range 200000 { + key := memoKey(rng.IntN(300)+1) << 8 + switch rng.IntN(3) { + case 0: + table.set(key, int32(i)) + want[key] = int32(i) + case 1: + table.delete(key) + delete(want, key) + } + got, ok := table.get(key) + wantIndex, wantOk := want[key] + assert.Equal(t, ok, wantOk) + assert.Equal(t, got, wantIndex) + assert.Equal(t, table.count, len(want)) + } + for key, index := range want { + got, ok := table.get(key) + assert.Assert(t, ok) + assert.Equal(t, got, index) + } +} diff --git a/tsc/internal/checker/utilities.go b/tsc/internal/checker/utilities.go index 53258a46c795b..cd9da35086fde 100644 --- a/tsc/internal/checker/utilities.go +++ b/tsc/internal/checker/utilities.go @@ -375,6 +375,15 @@ func (c *Checker) compareSymbolsWorker(s1, s2 *ast.Symbol) int { if r := c.compareSymbolDeclarationsAndNames(s1, s2); r != 0 { return r } + // Symbols the checker derives from a declared one, such as the members of + // mapped types, share its declaration and name. Symbol IDs are assigned in + // the order symbols are first used, so order those by how they were made. + if r := cmp.Compare(s1.Flags()&ast.SymbolFlagsTransient, s2.Flags()&ast.SymbolFlagsTransient); r != 0 { + return r + } + if r := cmp.Compare(s1.CheckFlags(), s2.CheckFlags()); 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 cmp.Compare(ast.GetSymbolId(s1), ast.GetSymbolId(s2)) @@ -478,7 +487,7 @@ func CompareTypes(t1, t2 *Type) int { // 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 { + if c := compareTypeLists(t1.AsTypeReference().resolvedTypeArguments.peek(), t2.AsTypeReference().resolvedTypeArguments.peek()); c != 0 { return c } } else { @@ -657,8 +666,8 @@ func CompareTypes(t1, t2 *Type) int { } // Types implied by binding patterns are ordered by their patterns. if t1.flags&TypeFlagsObject != 0 { - p1 := t1.checker.patternForType[t1] - p2 := t2.checker.patternForType[t2] + p1, _ := t1.checker.patternForType.get(t1) + p2, _ := t2.checker.patternForType.get(t2) if c := t1.checker.compareNodes(p1, p2); c != 0 { return c } @@ -835,7 +844,7 @@ func compareObjectLiteralMembers(t1, t2 *Type) int { // created. func (c *Checker) peekTypeOfObjectLiteralMember(s *ast.Symbol) *Type { if links := c.valueSymbolLinks.TryGet(s); links != nil { - return links.resolvedType + return links.resolvedType.peek() } return nil } @@ -882,7 +891,7 @@ func compareTypeParameterOrigins(t1, t2 *Type) int { // Tuple this types have no symbol; their constraint is the tuple target, // initialized at creation. if t1.symbol == nil { - return CompareTypes(p1.constraint, p2.constraint) + return CompareTypes(p1.constraint.peek(), p2.constraint.peek()) } case typeParameterOriginDistributed: return CompareTypes(p1.distributedType, p2.distributedType) diff --git a/tsc/internal/execute/tsctests/tsc_test.go b/tsc/internal/execute/tsctests/tsc_test.go index ee429a826f94a..befb7578e9184 100644 --- a/tsc/internal/execute/tsctests/tsc_test.go +++ b/tsc/internal/execute/tsctests/tsc_test.go @@ -2406,12 +2406,10 @@ func TestTscIncremental(t *testing.T) { edit: func(sys *TestSys) { sys.appendFile("/home/src/workspaces/project/repro.ts", "\n// comment-only edit\n") }, - expectedDiff: "Like Strada, signature generation produces the missing-global diagnostic before semantic checking, so it is excluded from the file's semantic diagnostics.", }, { - caption: "no change", - edit: noChange.edit, - expectedDiff: "Like Strada, the cached semantic diagnostics do not include the missing-global diagnostic produced during signature generation.", + caption: "no change", + edit: noChange.edit, }, { caption: "delete build info to restore the semantic diagnostic", diff --git a/tsc/internal/testrunner/compiler_runner.go b/tsc/internal/testrunner/compiler_runner.go index 3ea3d64238042..3efc2ec3e71ca 100644 --- a/tsc/internal/testrunner/compiler_runner.go +++ b/tsc/internal/testrunner/compiler_runner.go @@ -167,21 +167,8 @@ func (r *CompilerBaselineRunner) runTest(t *testing.T, filename string) { } } -// When a program checks its files on several checkers, these tests can see different results depending on which -// checker answers a query first. -var concurrentSkippedTests = map[string]string{ - "checkOrderCircularDefaults.ts": "Circular default results depend on which checker resolves the cycle first.", - "checkOrderSpreadCycle.ts": "Spread cycle results depend on which checker resolves the cycle first.", - "checkOrderStableInstantiationErrors.ts": "Instantiation limit errors depend on which checker instantiates first.", -} - func (r *CompilerBaselineRunner) runSingleConfigTest(t *testing.T, testName string, test *compilerFileBasedTest, config *harnessutil.NamedTestConfiguration) { t.Parallel() - if !testutil.TestProgramIsSingleThreaded() { - if msg, ok := concurrentSkippedTests[tspath.GetBaseFileName(test.filename)]; ok { - t.Skipf("Skipping in concurrent mode: %s", msg) - } - } defer testutil.RecoverAndFail(t, "Panic on compiler test "+test.filename) payload := makeUnitsFromTest(test.content, test.filename) diff --git a/tsc/testdata/baselines/reference/compiler/bigintArbirtraryIdentifier(checkorder=typesfirst).errors.txt b/tsc/testdata/baselines/reference/compiler/bigintArbirtraryIdentifier(checkorder=typesfirst).errors.txt index e70af53237c1f..5a959d4e04b62 100644 --- a/tsc/testdata/baselines/reference/compiler/bigintArbirtraryIdentifier(checkorder=typesfirst).errors.txt +++ b/tsc/testdata/baselines/reference/compiler/bigintArbirtraryIdentifier(checkorder=typesfirst).errors.txt @@ -6,7 +6,6 @@ badExport2.ts(1,16): error TS2304: Cannot find name 'foo'. badExport2.ts(1,20): error TS1128: Declaration or statement expected. badImport.ts(1,10): error TS1003: Identifier expected. badImport.ts(1,10): error TS1141: String literal expected. -badImport.ts(1,16): error TS2304: Cannot find name 'foo'. badImport.ts(1,20): error TS1128: Declaration or statement expected. badImport.ts(1,22): error TS1434: Unexpected keyword or identifier. badImport.ts(1,22): error TS2304: Cannot find name 'from'. @@ -25,14 +24,12 @@ badImport2.ts(1,22): error TS2304: Cannot find name 'from'. import { "0n" as foo } from "./foo"; export { foo as "0n" }; -==== badImport.ts (6 errors) ==== +==== badImport.ts (5 errors) ==== import { 0n as foo } from "./foo"; ~~ !!! error TS1003: Identifier expected. ~~~~~~~~~ !!! error TS1141: String literal expected. - ~~~ -!!! error TS2304: Cannot find name 'foo'. ~ !!! error TS1128: Declaration or statement expected. ~~~~ diff --git a/tsc/testdata/baselines/reference/compiler/checkOrderAwaitedCycle(checkorder=default).errors.txt b/tsc/testdata/baselines/reference/compiler/checkOrderAwaitedCycle(checkorder=default).errors.txt index fbdc7623ca157..126b9485926f5 100644 --- a/tsc/testdata/baselines/reference/compiler/checkOrderAwaitedCycle(checkorder=default).errors.txt +++ b/tsc/testdata/baselines/reference/compiler/checkOrderAwaitedCycle(checkorder=default).errors.txt @@ -1,6 +1,7 @@ a.ts(5,11): error TS2322: Type 'string' is not assignable to type 'number'. a.ts(5,23): error TS1062: Type is referenced directly or indirectly in the fulfillment callback of its own 'then' method. b.ts(2,11): error TS2322: Type 'string' is not assignable to type 'number'. +b.ts(2,23): error TS1062: Type is referenced directly or indirectly in the fulfillment callback of its own 'then' method. ==== a.ts (2 errors) ==== @@ -15,10 +16,12 @@ b.ts(2,11): error TS2322: Type 'string' is not assignable to type 'number'. !!! error TS1062: Type is referenced directly or indirectly in the fulfillment callback of its own 'then' method. } -==== b.ts (1 errors) ==== +==== b.ts (2 errors) ==== async function f2() { const b: number = await s; ~ !!! error TS2322: Type 'string' is not assignable to type 'number'. + ~~~~~~~ +!!! error TS1062: Type is referenced directly or indirectly in the fulfillment callback of its own 'then' method. } \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/checkOrderAwaitedCycle(checkorder=reverse).errors.txt b/tsc/testdata/baselines/reference/compiler/checkOrderAwaitedCycle(checkorder=reverse).errors.txt index 55a3e6d752ab1..126b9485926f5 100644 --- a/tsc/testdata/baselines/reference/compiler/checkOrderAwaitedCycle(checkorder=reverse).errors.txt +++ b/tsc/testdata/baselines/reference/compiler/checkOrderAwaitedCycle(checkorder=reverse).errors.txt @@ -1,9 +1,10 @@ a.ts(5,11): error TS2322: Type 'string' is not assignable to type 'number'. +a.ts(5,23): error TS1062: Type is referenced directly or indirectly in the fulfillment callback of its own 'then' method. b.ts(2,11): error TS2322: Type 'string' is not assignable to type 'number'. b.ts(2,23): error TS1062: Type is referenced directly or indirectly in the fulfillment callback of its own 'then' method. -==== a.ts (1 errors) ==== +==== a.ts (2 errors) ==== type S = string | Promise; declare const s: S; declare const p: Promise; @@ -11,6 +12,8 @@ b.ts(2,23): error TS1062: Type is referenced directly or indirectly in the fulfi const a: number = await p; ~ !!! error TS2322: Type 'string' is not assignable to type 'number'. + ~~~~~~~ +!!! error TS1062: Type is referenced directly or indirectly in the fulfillment callback of its own 'then' method. } ==== b.ts (2 errors) ==== diff --git a/tsc/testdata/baselines/reference/compiler/checkOrderAwaitedCycle(checkorder=typesfirst).errors.txt b/tsc/testdata/baselines/reference/compiler/checkOrderAwaitedCycle(checkorder=typesfirst).errors.txt index 55a3e6d752ab1..126b9485926f5 100644 --- a/tsc/testdata/baselines/reference/compiler/checkOrderAwaitedCycle(checkorder=typesfirst).errors.txt +++ b/tsc/testdata/baselines/reference/compiler/checkOrderAwaitedCycle(checkorder=typesfirst).errors.txt @@ -1,9 +1,10 @@ a.ts(5,11): error TS2322: Type 'string' is not assignable to type 'number'. +a.ts(5,23): error TS1062: Type is referenced directly or indirectly in the fulfillment callback of its own 'then' method. b.ts(2,11): error TS2322: Type 'string' is not assignable to type 'number'. b.ts(2,23): error TS1062: Type is referenced directly or indirectly in the fulfillment callback of its own 'then' method. -==== a.ts (1 errors) ==== +==== a.ts (2 errors) ==== type S = string | Promise; declare const s: S; declare const p: Promise; @@ -11,6 +12,8 @@ b.ts(2,23): error TS1062: Type is referenced directly or indirectly in the fulfi const a: number = await p; ~ !!! error TS2322: Type 'string' is not assignable to type 'number'. + ~~~~~~~ +!!! error TS1062: Type is referenced directly or indirectly in the fulfillment callback of its own 'then' method. } ==== b.ts (2 errors) ==== diff --git a/tsc/testdata/baselines/reference/compiler/checkOrderCircularConstraint(checkorder=default).errors.txt b/tsc/testdata/baselines/reference/compiler/checkOrderCircularConstraint(checkorder=default).errors.txt index 938f4b7bb63d2..9309ae4c7eb15 100644 --- a/tsc/testdata/baselines/reference/compiler/checkOrderCircularConstraint(checkorder=default).errors.txt +++ b/tsc/testdata/baselines/reference/compiler/checkOrderCircularConstraint(checkorder=default).errors.txt @@ -15,7 +15,6 @@ checkOrderCircularConstraint.ts(10,17): error TS2349: This expression is not cal class A { ~ !!! error TS2313: Type parameter 'T' has a circular constraint. -!!! related TS2751 checkOrderCircularConstraint.ts:7:17: Circularity originates in type at this location. foo() { var x!: T; var a = x.foo(); diff --git a/tsc/testdata/baselines/reference/compiler/checkOrderCircularConstraint(checkorder=reverse).errors.txt b/tsc/testdata/baselines/reference/compiler/checkOrderCircularConstraint(checkorder=reverse).errors.txt index 938f4b7bb63d2..9309ae4c7eb15 100644 --- a/tsc/testdata/baselines/reference/compiler/checkOrderCircularConstraint(checkorder=reverse).errors.txt +++ b/tsc/testdata/baselines/reference/compiler/checkOrderCircularConstraint(checkorder=reverse).errors.txt @@ -15,7 +15,6 @@ checkOrderCircularConstraint.ts(10,17): error TS2349: This expression is not cal class A { ~ !!! error TS2313: Type parameter 'T' has a circular constraint. -!!! related TS2751 checkOrderCircularConstraint.ts:7:17: Circularity originates in type at this location. foo() { var x!: T; var a = x.foo(); diff --git a/tsc/testdata/baselines/reference/compiler/checkOrderCircularConstraint(checkorder=typesfirst).errors.txt b/tsc/testdata/baselines/reference/compiler/checkOrderCircularConstraint(checkorder=typesfirst).errors.txt index ef7991cfbe407..9309ae4c7eb15 100644 --- a/tsc/testdata/baselines/reference/compiler/checkOrderCircularConstraint(checkorder=typesfirst).errors.txt +++ b/tsc/testdata/baselines/reference/compiler/checkOrderCircularConstraint(checkorder=typesfirst).errors.txt @@ -15,7 +15,6 @@ checkOrderCircularConstraint.ts(10,17): error TS2349: This expression is not cal class A { ~ !!! error TS2313: Type parameter 'T' has a circular constraint. -!!! related TS2751 checkOrderCircularConstraint.ts:10:17: Circularity originates in type at this location. foo() { var x!: T; var a = x.foo(); diff --git a/tsc/testdata/baselines/reference/compiler/checkOrderCircularDefaults(checkorder=default).errors.txt b/tsc/testdata/baselines/reference/compiler/checkOrderCircularDefaults(checkorder=default).errors.txt index 5c28d3ee0a214..4dee4171e029f 100644 --- a/tsc/testdata/baselines/reference/compiler/checkOrderCircularDefaults(checkorder=default).errors.txt +++ b/tsc/testdata/baselines/reference/compiler/checkOrderCircularDefaults(checkorder=default).errors.txt @@ -1,15 +1,15 @@ -a.ts(2,24): error TS2716: Type parameter 'T' has a circular default. +b.ts(2,24): error TS2716: Type parameter 'U' has a circular default. -==== a.ts (1 errors) ==== +==== a.ts (0 errors) ==== import type { B } from "./b"; export interface A { a: T } - ~ -!!! error TS2716: Type parameter 'T' has a circular default. export declare const a: A; -==== b.ts (0 errors) ==== +==== b.ts (1 errors) ==== import type { A } from "./a"; export interface B { b: U } + ~ +!!! error TS2716: Type parameter 'U' has a circular default. export declare const b: B; \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/checkOrderCircularDefaults(checkorder=default).types b/tsc/testdata/baselines/reference/compiler/checkOrderCircularDefaults(checkorder=default).types index 145f91d78caf3..a88d998c374ec 100644 --- a/tsc/testdata/baselines/reference/compiler/checkOrderCircularDefaults(checkorder=default).types +++ b/tsc/testdata/baselines/reference/compiler/checkOrderCircularDefaults(checkorder=default).types @@ -8,7 +8,7 @@ export interface A { a: T } >a : T export declare const a: A; ->a : A +>a : A> === b.ts === import type { A } from "./a"; @@ -18,5 +18,5 @@ export interface B { b: U } >b : U export declare const b: B; ->b : B> +>b : B diff --git a/tsc/testdata/baselines/reference/compiler/checkOrderCircularDefaults(checkorder=isolated).errors.txt b/tsc/testdata/baselines/reference/compiler/checkOrderCircularDefaults(checkorder=isolated).errors.txt new file mode 100644 index 0000000000000..4dee4171e029f --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/checkOrderCircularDefaults(checkorder=isolated).errors.txt @@ -0,0 +1,15 @@ +b.ts(2,24): error TS2716: Type parameter 'U' has a circular default. + + +==== a.ts (0 errors) ==== + import type { B } from "./b"; + export interface A { a: T } + export declare const a: A; + +==== b.ts (1 errors) ==== + import type { A } from "./a"; + export interface B { b: U } + ~ +!!! error TS2716: Type parameter 'U' has a circular default. + export declare const b: B; + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/checkOrderCircularDefaults(checkorder=isolated).types b/tsc/testdata/baselines/reference/compiler/checkOrderCircularDefaults(checkorder=isolated).types index 145f91d78caf3..a88d998c374ec 100644 --- a/tsc/testdata/baselines/reference/compiler/checkOrderCircularDefaults(checkorder=isolated).types +++ b/tsc/testdata/baselines/reference/compiler/checkOrderCircularDefaults(checkorder=isolated).types @@ -8,7 +8,7 @@ export interface A { a: T } >a : T export declare const a: A; ->a : A +>a : A> === b.ts === import type { A } from "./a"; @@ -18,5 +18,5 @@ export interface B { b: U } >b : U export declare const b: B; ->b : B> +>b : B diff --git a/tsc/testdata/baselines/reference/compiler/checkOrderCircularDefaults(checkorder=typesfirst).errors.txt b/tsc/testdata/baselines/reference/compiler/checkOrderCircularDefaults(checkorder=typesfirst).errors.txt index 5c28d3ee0a214..4dee4171e029f 100644 --- a/tsc/testdata/baselines/reference/compiler/checkOrderCircularDefaults(checkorder=typesfirst).errors.txt +++ b/tsc/testdata/baselines/reference/compiler/checkOrderCircularDefaults(checkorder=typesfirst).errors.txt @@ -1,15 +1,15 @@ -a.ts(2,24): error TS2716: Type parameter 'T' has a circular default. +b.ts(2,24): error TS2716: Type parameter 'U' has a circular default. -==== a.ts (1 errors) ==== +==== a.ts (0 errors) ==== import type { B } from "./b"; export interface A { a: T } - ~ -!!! error TS2716: Type parameter 'T' has a circular default. export declare const a: A; -==== b.ts (0 errors) ==== +==== b.ts (1 errors) ==== import type { A } from "./a"; export interface B { b: U } + ~ +!!! error TS2716: Type parameter 'U' has a circular default. export declare const b: B; \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/checkOrderCircularDefaults(checkorder=typesfirst).types b/tsc/testdata/baselines/reference/compiler/checkOrderCircularDefaults(checkorder=typesfirst).types index 145f91d78caf3..a88d998c374ec 100644 --- a/tsc/testdata/baselines/reference/compiler/checkOrderCircularDefaults(checkorder=typesfirst).types +++ b/tsc/testdata/baselines/reference/compiler/checkOrderCircularDefaults(checkorder=typesfirst).types @@ -8,7 +8,7 @@ export interface A { a: T } >a : T export declare const a: A; ->a : A +>a : A> === b.ts === import type { A } from "./a"; @@ -18,5 +18,5 @@ export interface B { b: U } >b : U export declare const b: B; ->b : B> +>b : B diff --git a/tsc/testdata/baselines/reference/compiler/checkOrderEmitBeforeCheck(checkorder=default).errors.txt b/tsc/testdata/baselines/reference/compiler/checkOrderEmitBeforeCheck(checkorder=default).errors.txt index 0570bff3e21f0..81bd46c132406 100644 --- a/tsc/testdata/baselines/reference/compiler/checkOrderEmitBeforeCheck(checkorder=default).errors.txt +++ b/tsc/testdata/baselines/reference/compiler/checkOrderEmitBeforeCheck(checkorder=default).errors.txt @@ -1,5 +1,5 @@ checkOrderEmitBeforeCheck.ts(9,5): error TS2502: '"each"' is referenced directly or indirectly in its own type annotation. -checkOrderEmitBeforeCheck.ts(15,1): error TS2615: Type of property '"each"' circularly references itself in mapped type '{ [P in keyof ListWidget]: undefined extends ListWidget[P] ? never : P; }'. +checkOrderEmitBeforeCheck.ts(9,13): error TS2615: Type of property '"each"' circularly references itself in mapped type '{ type: "type"; each: any; }'. ==== checkOrderEmitBeforeCheck.ts (2 errors) ==== @@ -14,12 +14,12 @@ checkOrderEmitBeforeCheck.ts(15,1): error TS2615: Type of property '"each"' circ "each": Child; ~~~~~~ !!! error TS2502: '"each"' is referenced directly or indirectly in its own type annotation. + ~~~~~~~~~~~~~~~~~ +!!! error TS2615: Type of property '"each"' circularly references itself in mapped type '{ type: "type"; each: any; }'. } type ListChild = Child declare let x: ListChild; x.type; - ~ -!!! error TS2615: Type of property '"each"' circularly references itself in mapped type '{ [P in keyof ListWidget]: undefined extends ListWidget[P] ? never : P; }'. \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/checkOrderEmitBeforeCheck(checkorder=isolated).errors.txt b/tsc/testdata/baselines/reference/compiler/checkOrderEmitBeforeCheck(checkorder=isolated).errors.txt index c220c7e1092af..81bd46c132406 100644 --- a/tsc/testdata/baselines/reference/compiler/checkOrderEmitBeforeCheck(checkorder=isolated).errors.txt +++ b/tsc/testdata/baselines/reference/compiler/checkOrderEmitBeforeCheck(checkorder=isolated).errors.txt @@ -1,5 +1,5 @@ checkOrderEmitBeforeCheck.ts(9,5): error TS2502: '"each"' is referenced directly or indirectly in its own type annotation. -checkOrderEmitBeforeCheck.ts(9,13): error TS2615: Type of property '"each"' circularly references itself in mapped type '{ [P in keyof ListWidget]: undefined extends ListWidget[P] ? never : P; }'. +checkOrderEmitBeforeCheck.ts(9,13): error TS2615: Type of property '"each"' circularly references itself in mapped type '{ type: "type"; each: any; }'. ==== checkOrderEmitBeforeCheck.ts (2 errors) ==== @@ -15,7 +15,7 @@ checkOrderEmitBeforeCheck.ts(9,13): error TS2615: Type of property '"each"' circ ~~~~~~ !!! error TS2502: '"each"' is referenced directly or indirectly in its own type annotation. ~~~~~~~~~~~~~~~~~ -!!! error TS2615: Type of property '"each"' circularly references itself in mapped type '{ [P in keyof ListWidget]: undefined extends ListWidget[P] ? never : P; }'. +!!! error TS2615: Type of property '"each"' circularly references itself in mapped type '{ type: "type"; each: any; }'. } type ListChild = Child diff --git a/tsc/testdata/baselines/reference/compiler/checkOrderSpreadCycle(checkorder=default).js b/tsc/testdata/baselines/reference/compiler/checkOrderSpreadCycle(checkorder=default).js index 433d0bfbb69b5..8d70e46a3bf67 100644 --- a/tsc/testdata/baselines/reference/compiler/checkOrderSpreadCycle(checkorder=default).js +++ b/tsc/testdata/baselines/reference/compiler/checkOrderSpreadCycle(checkorder=default).js @@ -57,7 +57,10 @@ module.exports = { v: { ...M } }; //// [xb.d.ts] declare const _exports: { - v: {}; + v: { + pa: {}; + ma: 1; + }; }; export = _exports; //// [mb.d.ts] @@ -65,10 +68,7 @@ export { v as pb } from "./xb"; export declare const mb = 1; //// [xa.d.ts] declare const _exports: { - v: { - pb: {}; - mb: 1; - }; + v: {}; }; export = _exports; //// [ma.d.ts] @@ -77,10 +77,7 @@ export declare const ma = 1; //// [ia.d.ts] declare const _exports: { v: { - pa: { - pb: {}; - mb: 1; - }; + pa: {}; ma: 1; }; }; diff --git a/tsc/testdata/baselines/reference/compiler/checkOrderSpreadCycle(checkorder=default).types b/tsc/testdata/baselines/reference/compiler/checkOrderSpreadCycle(checkorder=default).types index 216ad9304dfd7..b82d88007b600 100644 --- a/tsc/testdata/baselines/reference/compiler/checkOrderSpreadCycle(checkorder=default).types +++ b/tsc/testdata/baselines/reference/compiler/checkOrderSpreadCycle(checkorder=default).types @@ -7,19 +7,19 @@ const M = /** @type {any} */ (null); >(null) : any module.exports = { v: { ...M } }; ->module.exports = { v: { ...M } } : { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; } ->module.exports : { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; } ->module : { exports: { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; }; } ->exports : { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; } ->{ v: { ...M } } : { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; } ->v : { pa: { pb: {}; mb: 1; }; ma: 1; } ->{ ...M } : { pa: { pb: {}; mb: 1; }; ma: 1; } +>module.exports = { v: { ...M } } : { v: { pa: {}; ma: 1; }; } +>module.exports : { v: { pa: {}; ma: 1; }; } +>module : { exports: { v: { pa: {}; ma: 1; }; }; } +>exports : { v: { pa: {}; ma: 1; }; } +>{ v: { ...M } } : { v: { pa: {}; ma: 1; }; } +>v : { pa: {}; ma: 1; } +>{ ...M } : { pa: {}; ma: 1; } >M : typeof import("./ma") === /ma.js === export { v as pa } from "./xa"; ->v : { pb: {}; mb: 1; } ->pa : { pb: {}; mb: 1; } +>v : {} +>pa : {} export const ma = 1; >ma : 1 @@ -27,8 +27,8 @@ export const ma = 1; === /mb.js === export { v as pb } from "./xb"; ->v : {} ->pb : {} +>v : { pa: {}; ma: 1; } +>pb : { pa: {}; ma: 1; } export const mb = 1; >mb : 1 @@ -41,13 +41,13 @@ const M = /** @type {any} */ (null); >(null) : any module.exports = { v: { ...M } }; ->module.exports = { v: { ...M } } : { v: { pb: {}; mb: 1; }; } ->module.exports : { v: { pb: {}; mb: 1; }; } ->module : { exports: { v: { pb: {}; mb: 1; }; }; } ->exports : { v: { pb: {}; mb: 1; }; } ->{ v: { ...M } } : { v: { pb: {}; mb: 1; }; } ->v : { pb: {}; mb: 1; } ->{ ...M } : { pb: {}; mb: 1; } +>module.exports = { v: { ...M } } : { v: { pb: { pa: {}; ma: 1; }; mb: 1; }; } +>module.exports : { v: {}; } +>module : { exports: { v: {}; }; } +>exports : { v: {}; } +>{ v: { ...M } } : { v: { pb: { pa: {}; ma: 1; }; mb: 1; }; } +>v : { pb: { pa: {}; ma: 1; }; mb: 1; } +>{ ...M } : { pb: { pa: {}; ma: 1; }; mb: 1; } >M : typeof import("./mb") === /xb.js === @@ -57,12 +57,12 @@ const M = /** @type {any} */ (null); >(null) : any module.exports = { v: { ...M } }; ->module.exports = { v: { ...M } } : { v: {}; } ->module.exports : { v: {}; } ->module : { exports: { v: {}; }; } ->exports : { v: {}; } ->{ v: { ...M } } : { v: {}; } ->v : {} ->{ ...M } : {} +>module.exports = { v: { ...M } } : { v: { pa: {}; ma: 1; }; } +>module.exports : { v: { pa: {}; ma: 1; }; } +>module : { exports: { v: { pa: {}; ma: 1; }; }; } +>exports : { v: { pa: {}; ma: 1; }; } +>{ v: { ...M } } : { v: { pa: {}; ma: 1; }; } +>v : { pa: {}; ma: 1; } +>{ ...M } : { pa: {}; ma: 1; } >M : typeof import("./ma") diff --git a/tsc/testdata/baselines/reference/compiler/checkOrderSpreadCycle(checkorder=isolated).js b/tsc/testdata/baselines/reference/compiler/checkOrderSpreadCycle(checkorder=isolated).js index 8621be455c50d..8d70e46a3bf67 100644 --- a/tsc/testdata/baselines/reference/compiler/checkOrderSpreadCycle(checkorder=isolated).js +++ b/tsc/testdata/baselines/reference/compiler/checkOrderSpreadCycle(checkorder=isolated).js @@ -56,13 +56,20 @@ module.exports = { v: { ...M } }; //// [xb.d.ts] -declare const _exports: any; +declare const _exports: { + v: { + pa: {}; + ma: 1; + }; +}; export = _exports; //// [mb.d.ts] export { v as pb } from "./xb"; export declare const mb = 1; //// [xa.d.ts] -declare const _exports: any; +declare const _exports: { + v: {}; +}; export = _exports; //// [ma.d.ts] export { v as pa } from "./xa"; @@ -70,10 +77,7 @@ export declare const ma = 1; //// [ia.d.ts] declare const _exports: { v: { - pa: { - pb: {}; - mb: 1; - }; + pa: {}; ma: 1; }; }; diff --git a/tsc/testdata/baselines/reference/compiler/checkOrderSpreadCycle(checkorder=isolated).types b/tsc/testdata/baselines/reference/compiler/checkOrderSpreadCycle(checkorder=isolated).types index 216ad9304dfd7..b82d88007b600 100644 --- a/tsc/testdata/baselines/reference/compiler/checkOrderSpreadCycle(checkorder=isolated).types +++ b/tsc/testdata/baselines/reference/compiler/checkOrderSpreadCycle(checkorder=isolated).types @@ -7,19 +7,19 @@ const M = /** @type {any} */ (null); >(null) : any module.exports = { v: { ...M } }; ->module.exports = { v: { ...M } } : { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; } ->module.exports : { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; } ->module : { exports: { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; }; } ->exports : { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; } ->{ v: { ...M } } : { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; } ->v : { pa: { pb: {}; mb: 1; }; ma: 1; } ->{ ...M } : { pa: { pb: {}; mb: 1; }; ma: 1; } +>module.exports = { v: { ...M } } : { v: { pa: {}; ma: 1; }; } +>module.exports : { v: { pa: {}; ma: 1; }; } +>module : { exports: { v: { pa: {}; ma: 1; }; }; } +>exports : { v: { pa: {}; ma: 1; }; } +>{ v: { ...M } } : { v: { pa: {}; ma: 1; }; } +>v : { pa: {}; ma: 1; } +>{ ...M } : { pa: {}; ma: 1; } >M : typeof import("./ma") === /ma.js === export { v as pa } from "./xa"; ->v : { pb: {}; mb: 1; } ->pa : { pb: {}; mb: 1; } +>v : {} +>pa : {} export const ma = 1; >ma : 1 @@ -27,8 +27,8 @@ export const ma = 1; === /mb.js === export { v as pb } from "./xb"; ->v : {} ->pb : {} +>v : { pa: {}; ma: 1; } +>pb : { pa: {}; ma: 1; } export const mb = 1; >mb : 1 @@ -41,13 +41,13 @@ const M = /** @type {any} */ (null); >(null) : any module.exports = { v: { ...M } }; ->module.exports = { v: { ...M } } : { v: { pb: {}; mb: 1; }; } ->module.exports : { v: { pb: {}; mb: 1; }; } ->module : { exports: { v: { pb: {}; mb: 1; }; }; } ->exports : { v: { pb: {}; mb: 1; }; } ->{ v: { ...M } } : { v: { pb: {}; mb: 1; }; } ->v : { pb: {}; mb: 1; } ->{ ...M } : { pb: {}; mb: 1; } +>module.exports = { v: { ...M } } : { v: { pb: { pa: {}; ma: 1; }; mb: 1; }; } +>module.exports : { v: {}; } +>module : { exports: { v: {}; }; } +>exports : { v: {}; } +>{ v: { ...M } } : { v: { pb: { pa: {}; ma: 1; }; mb: 1; }; } +>v : { pb: { pa: {}; ma: 1; }; mb: 1; } +>{ ...M } : { pb: { pa: {}; ma: 1; }; mb: 1; } >M : typeof import("./mb") === /xb.js === @@ -57,12 +57,12 @@ const M = /** @type {any} */ (null); >(null) : any module.exports = { v: { ...M } }; ->module.exports = { v: { ...M } } : { v: {}; } ->module.exports : { v: {}; } ->module : { exports: { v: {}; }; } ->exports : { v: {}; } ->{ v: { ...M } } : { v: {}; } ->v : {} ->{ ...M } : {} +>module.exports = { v: { ...M } } : { v: { pa: {}; ma: 1; }; } +>module.exports : { v: { pa: {}; ma: 1; }; } +>module : { exports: { v: { pa: {}; ma: 1; }; }; } +>exports : { v: { pa: {}; ma: 1; }; } +>{ v: { ...M } } : { v: { pa: {}; ma: 1; }; } +>v : { pa: {}; ma: 1; } +>{ ...M } : { pa: {}; ma: 1; } >M : typeof import("./ma") diff --git a/tsc/testdata/baselines/reference/compiler/checkOrderSpreadCycle(checkorder=reverse).js b/tsc/testdata/baselines/reference/compiler/checkOrderSpreadCycle(checkorder=reverse).js index 433d0bfbb69b5..8d70e46a3bf67 100644 --- a/tsc/testdata/baselines/reference/compiler/checkOrderSpreadCycle(checkorder=reverse).js +++ b/tsc/testdata/baselines/reference/compiler/checkOrderSpreadCycle(checkorder=reverse).js @@ -57,7 +57,10 @@ module.exports = { v: { ...M } }; //// [xb.d.ts] declare const _exports: { - v: {}; + v: { + pa: {}; + ma: 1; + }; }; export = _exports; //// [mb.d.ts] @@ -65,10 +68,7 @@ export { v as pb } from "./xb"; export declare const mb = 1; //// [xa.d.ts] declare const _exports: { - v: { - pb: {}; - mb: 1; - }; + v: {}; }; export = _exports; //// [ma.d.ts] @@ -77,10 +77,7 @@ export declare const ma = 1; //// [ia.d.ts] declare const _exports: { v: { - pa: { - pb: {}; - mb: 1; - }; + pa: {}; ma: 1; }; }; diff --git a/tsc/testdata/baselines/reference/compiler/checkOrderSpreadCycle(checkorder=reverse).types b/tsc/testdata/baselines/reference/compiler/checkOrderSpreadCycle(checkorder=reverse).types index 216ad9304dfd7..b82d88007b600 100644 --- a/tsc/testdata/baselines/reference/compiler/checkOrderSpreadCycle(checkorder=reverse).types +++ b/tsc/testdata/baselines/reference/compiler/checkOrderSpreadCycle(checkorder=reverse).types @@ -7,19 +7,19 @@ const M = /** @type {any} */ (null); >(null) : any module.exports = { v: { ...M } }; ->module.exports = { v: { ...M } } : { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; } ->module.exports : { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; } ->module : { exports: { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; }; } ->exports : { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; } ->{ v: { ...M } } : { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; } ->v : { pa: { pb: {}; mb: 1; }; ma: 1; } ->{ ...M } : { pa: { pb: {}; mb: 1; }; ma: 1; } +>module.exports = { v: { ...M } } : { v: { pa: {}; ma: 1; }; } +>module.exports : { v: { pa: {}; ma: 1; }; } +>module : { exports: { v: { pa: {}; ma: 1; }; }; } +>exports : { v: { pa: {}; ma: 1; }; } +>{ v: { ...M } } : { v: { pa: {}; ma: 1; }; } +>v : { pa: {}; ma: 1; } +>{ ...M } : { pa: {}; ma: 1; } >M : typeof import("./ma") === /ma.js === export { v as pa } from "./xa"; ->v : { pb: {}; mb: 1; } ->pa : { pb: {}; mb: 1; } +>v : {} +>pa : {} export const ma = 1; >ma : 1 @@ -27,8 +27,8 @@ export const ma = 1; === /mb.js === export { v as pb } from "./xb"; ->v : {} ->pb : {} +>v : { pa: {}; ma: 1; } +>pb : { pa: {}; ma: 1; } export const mb = 1; >mb : 1 @@ -41,13 +41,13 @@ const M = /** @type {any} */ (null); >(null) : any module.exports = { v: { ...M } }; ->module.exports = { v: { ...M } } : { v: { pb: {}; mb: 1; }; } ->module.exports : { v: { pb: {}; mb: 1; }; } ->module : { exports: { v: { pb: {}; mb: 1; }; }; } ->exports : { v: { pb: {}; mb: 1; }; } ->{ v: { ...M } } : { v: { pb: {}; mb: 1; }; } ->v : { pb: {}; mb: 1; } ->{ ...M } : { pb: {}; mb: 1; } +>module.exports = { v: { ...M } } : { v: { pb: { pa: {}; ma: 1; }; mb: 1; }; } +>module.exports : { v: {}; } +>module : { exports: { v: {}; }; } +>exports : { v: {}; } +>{ v: { ...M } } : { v: { pb: { pa: {}; ma: 1; }; mb: 1; }; } +>v : { pb: { pa: {}; ma: 1; }; mb: 1; } +>{ ...M } : { pb: { pa: {}; ma: 1; }; mb: 1; } >M : typeof import("./mb") === /xb.js === @@ -57,12 +57,12 @@ const M = /** @type {any} */ (null); >(null) : any module.exports = { v: { ...M } }; ->module.exports = { v: { ...M } } : { v: {}; } ->module.exports : { v: {}; } ->module : { exports: { v: {}; }; } ->exports : { v: {}; } ->{ v: { ...M } } : { v: {}; } ->v : {} ->{ ...M } : {} +>module.exports = { v: { ...M } } : { v: { pa: {}; ma: 1; }; } +>module.exports : { v: { pa: {}; ma: 1; }; } +>module : { exports: { v: { pa: {}; ma: 1; }; }; } +>exports : { v: { pa: {}; ma: 1; }; } +>{ v: { ...M } } : { v: { pa: {}; ma: 1; }; } +>v : { pa: {}; ma: 1; } +>{ ...M } : { pa: {}; ma: 1; } >M : typeof import("./ma") diff --git a/tsc/testdata/baselines/reference/compiler/checkOrderSpreadCycle(checkorder=typesfirst).js b/tsc/testdata/baselines/reference/compiler/checkOrderSpreadCycle(checkorder=typesfirst).js index 433d0bfbb69b5..8d70e46a3bf67 100644 --- a/tsc/testdata/baselines/reference/compiler/checkOrderSpreadCycle(checkorder=typesfirst).js +++ b/tsc/testdata/baselines/reference/compiler/checkOrderSpreadCycle(checkorder=typesfirst).js @@ -57,7 +57,10 @@ module.exports = { v: { ...M } }; //// [xb.d.ts] declare const _exports: { - v: {}; + v: { + pa: {}; + ma: 1; + }; }; export = _exports; //// [mb.d.ts] @@ -65,10 +68,7 @@ export { v as pb } from "./xb"; export declare const mb = 1; //// [xa.d.ts] declare const _exports: { - v: { - pb: {}; - mb: 1; - }; + v: {}; }; export = _exports; //// [ma.d.ts] @@ -77,10 +77,7 @@ export declare const ma = 1; //// [ia.d.ts] declare const _exports: { v: { - pa: { - pb: {}; - mb: 1; - }; + pa: {}; ma: 1; }; }; diff --git a/tsc/testdata/baselines/reference/compiler/checkOrderSpreadCycle(checkorder=typesfirst).types b/tsc/testdata/baselines/reference/compiler/checkOrderSpreadCycle(checkorder=typesfirst).types index 216ad9304dfd7..b82d88007b600 100644 --- a/tsc/testdata/baselines/reference/compiler/checkOrderSpreadCycle(checkorder=typesfirst).types +++ b/tsc/testdata/baselines/reference/compiler/checkOrderSpreadCycle(checkorder=typesfirst).types @@ -7,19 +7,19 @@ const M = /** @type {any} */ (null); >(null) : any module.exports = { v: { ...M } }; ->module.exports = { v: { ...M } } : { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; } ->module.exports : { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; } ->module : { exports: { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; }; } ->exports : { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; } ->{ v: { ...M } } : { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; } ->v : { pa: { pb: {}; mb: 1; }; ma: 1; } ->{ ...M } : { pa: { pb: {}; mb: 1; }; ma: 1; } +>module.exports = { v: { ...M } } : { v: { pa: {}; ma: 1; }; } +>module.exports : { v: { pa: {}; ma: 1; }; } +>module : { exports: { v: { pa: {}; ma: 1; }; }; } +>exports : { v: { pa: {}; ma: 1; }; } +>{ v: { ...M } } : { v: { pa: {}; ma: 1; }; } +>v : { pa: {}; ma: 1; } +>{ ...M } : { pa: {}; ma: 1; } >M : typeof import("./ma") === /ma.js === export { v as pa } from "./xa"; ->v : { pb: {}; mb: 1; } ->pa : { pb: {}; mb: 1; } +>v : {} +>pa : {} export const ma = 1; >ma : 1 @@ -27,8 +27,8 @@ export const ma = 1; === /mb.js === export { v as pb } from "./xb"; ->v : {} ->pb : {} +>v : { pa: {}; ma: 1; } +>pb : { pa: {}; ma: 1; } export const mb = 1; >mb : 1 @@ -41,13 +41,13 @@ const M = /** @type {any} */ (null); >(null) : any module.exports = { v: { ...M } }; ->module.exports = { v: { ...M } } : { v: { pb: {}; mb: 1; }; } ->module.exports : { v: { pb: {}; mb: 1; }; } ->module : { exports: { v: { pb: {}; mb: 1; }; }; } ->exports : { v: { pb: {}; mb: 1; }; } ->{ v: { ...M } } : { v: { pb: {}; mb: 1; }; } ->v : { pb: {}; mb: 1; } ->{ ...M } : { pb: {}; mb: 1; } +>module.exports = { v: { ...M } } : { v: { pb: { pa: {}; ma: 1; }; mb: 1; }; } +>module.exports : { v: {}; } +>module : { exports: { v: {}; }; } +>exports : { v: {}; } +>{ v: { ...M } } : { v: { pb: { pa: {}; ma: 1; }; mb: 1; }; } +>v : { pb: { pa: {}; ma: 1; }; mb: 1; } +>{ ...M } : { pb: { pa: {}; ma: 1; }; mb: 1; } >M : typeof import("./mb") === /xb.js === @@ -57,12 +57,12 @@ const M = /** @type {any} */ (null); >(null) : any module.exports = { v: { ...M } }; ->module.exports = { v: { ...M } } : { v: {}; } ->module.exports : { v: {}; } ->module : { exports: { v: {}; }; } ->exports : { v: {}; } ->{ v: { ...M } } : { v: {}; } ->v : {} ->{ ...M } : {} +>module.exports = { v: { ...M } } : { v: { pa: {}; ma: 1; }; } +>module.exports : { v: { pa: {}; ma: 1; }; } +>module : { exports: { v: { pa: {}; ma: 1; }; }; } +>exports : { v: { pa: {}; ma: 1; }; } +>{ v: { ...M } } : { v: { pa: {}; ma: 1; }; } +>v : { pa: {}; ma: 1; } +>{ ...M } : { pa: {}; ma: 1; } >M : typeof import("./ma") diff --git a/tsc/testdata/baselines/reference/compiler/checkOrderStableInstantiationErrors(checkorder=default).errors.txt b/tsc/testdata/baselines/reference/compiler/checkOrderStableInstantiationErrors(checkorder=default).errors.txt index 735d4e676a17d..a547413430b10 100644 --- a/tsc/testdata/baselines/reference/compiler/checkOrderStableInstantiationErrors(checkorder=default).errors.txt +++ b/tsc/testdata/baselines/reference/compiler/checkOrderStableInstantiationErrors(checkorder=default).errors.txt @@ -1,4 +1,7 @@ +a.ts(2,12): error TS2589: Type instantiation is excessively deep and possibly infinite. a.ts(2,15): error TS2589: Type instantiation is excessively deep and possibly infinite. +b.ts(2,12): error TS2589: Type instantiation is excessively deep and possibly infinite. +b.ts(2,15): error TS2589: Type instantiation is excessively deep and possibly infinite. ==== lib.ts (0 errors) ==== @@ -9,15 +12,21 @@ a.ts(2,15): error TS2589: Type instantiation is excessively deep and possibly in export type Recur = N extends 0 ? S : Recur, `${S}_${S}`>; -==== a.ts (1 errors) ==== +==== a.ts (2 errors) ==== import { Recur } from "./lib"; export let a: Recur<5, "a">; + ~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. ~~~~~~~~~~~~~ !!! error TS2589: Type instantiation is excessively deep and possibly infinite. -==== b.ts (0 errors) ==== +==== b.ts (2 errors) ==== import { Recur } from "./lib"; export let b: Recur<5, "a">; + ~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. + ~~~~~~~~~~~~~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. ==== c.ts (0 errors) ==== class T { diff --git a/tsc/testdata/baselines/reference/compiler/checkOrderStableInstantiationErrors(checkorder=reverse).errors.txt b/tsc/testdata/baselines/reference/compiler/checkOrderStableInstantiationErrors(checkorder=reverse).errors.txt index 6e370b78d30f9..a547413430b10 100644 --- a/tsc/testdata/baselines/reference/compiler/checkOrderStableInstantiationErrors(checkorder=reverse).errors.txt +++ b/tsc/testdata/baselines/reference/compiler/checkOrderStableInstantiationErrors(checkorder=reverse).errors.txt @@ -1,3 +1,6 @@ +a.ts(2,12): error TS2589: Type instantiation is excessively deep and possibly infinite. +a.ts(2,15): error TS2589: Type instantiation is excessively deep and possibly infinite. +b.ts(2,12): error TS2589: Type instantiation is excessively deep and possibly infinite. b.ts(2,15): error TS2589: Type instantiation is excessively deep and possibly infinite. @@ -9,13 +12,19 @@ b.ts(2,15): error TS2589: Type instantiation is excessively deep and possibly in export type Recur = N extends 0 ? S : Recur, `${S}_${S}`>; -==== a.ts (0 errors) ==== +==== a.ts (2 errors) ==== import { Recur } from "./lib"; export let a: Recur<5, "a">; + ~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. + ~~~~~~~~~~~~~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. -==== b.ts (1 errors) ==== +==== b.ts (2 errors) ==== import { Recur } from "./lib"; export let b: Recur<5, "a">; + ~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. ~~~~~~~~~~~~~ !!! error TS2589: Type instantiation is excessively deep and possibly infinite. diff --git a/tsc/testdata/baselines/reference/compiler/checkOrderStableInstantiationErrors(checkorder=typesfirst).errors.txt b/tsc/testdata/baselines/reference/compiler/checkOrderStableInstantiationErrors(checkorder=typesfirst).errors.txt index 9b99a0b62969b..a547413430b10 100644 --- a/tsc/testdata/baselines/reference/compiler/checkOrderStableInstantiationErrors(checkorder=typesfirst).errors.txt +++ b/tsc/testdata/baselines/reference/compiler/checkOrderStableInstantiationErrors(checkorder=typesfirst).errors.txt @@ -1,7 +1,9 @@ -error TS2589: Type instantiation is excessively deep and possibly infinite. +a.ts(2,12): error TS2589: Type instantiation is excessively deep and possibly infinite. +a.ts(2,15): error TS2589: Type instantiation is excessively deep and possibly infinite. +b.ts(2,12): error TS2589: Type instantiation is excessively deep and possibly infinite. +b.ts(2,15): error TS2589: Type instantiation is excessively deep and possibly infinite. -!!! error TS2589: Type instantiation is excessively deep and possibly infinite. ==== lib.ts (0 errors) ==== export type Dec = N extends 5 ? 4 : N extends 4 ? 3 : N extends 3 ? 2 : @@ -10,13 +12,21 @@ error TS2589: Type instantiation is excessively deep and possibly infinite. export type Recur = N extends 0 ? S : Recur, `${S}_${S}`>; -==== a.ts (0 errors) ==== +==== a.ts (2 errors) ==== import { Recur } from "./lib"; export let a: Recur<5, "a">; + ~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. + ~~~~~~~~~~~~~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. -==== b.ts (0 errors) ==== +==== b.ts (2 errors) ==== import { Recur } from "./lib"; export let b: Recur<5, "a">; + ~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. + ~~~~~~~~~~~~~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. ==== c.ts (0 errors) ==== class T { diff --git a/tsc/testdata/baselines/reference/compiler/circularAccessorAnnotations(checkorder=typesfirst).types b/tsc/testdata/baselines/reference/compiler/circularAccessorAnnotations(checkorder=typesfirst).types index e6b4d1fca90ca..2b01358a33dca 100644 --- a/tsc/testdata/baselines/reference/compiler/circularAccessorAnnotations(checkorder=typesfirst).types +++ b/tsc/testdata/baselines/reference/compiler/circularAccessorAnnotations(checkorder=typesfirst).types @@ -2,23 +2,23 @@ === circularAccessorAnnotations.ts === declare const c1: { ->c1 : { readonly foo: any; } +>c1 : { readonly foo: typeof c1.foo; } get foo(): typeof c1.foo; >foo : any >c1.foo : any ->c1 : { readonly foo: any; } +>c1 : { readonly foo: typeof c1.foo; } >foo : any } declare const c2: { ->c2 : { foo: any; } +>c2 : { foo: typeof c2.foo; } set foo(value: typeof c2.foo); >foo : any >value : any >c2.foo : any ->c2 : { foo: any; } +>c2 : { foo: typeof c2.foo; } >foo : any } diff --git a/tsc/testdata/baselines/reference/compiler/circularIndexedPropertyInstantiation.errors.txt b/tsc/testdata/baselines/reference/compiler/circularIndexedPropertyInstantiation.errors.txt index 3322a7ea25b66..1d225438007a0 100644 --- a/tsc/testdata/baselines/reference/compiler/circularIndexedPropertyInstantiation.errors.txt +++ b/tsc/testdata/baselines/reference/compiler/circularIndexedPropertyInstantiation.errors.txt @@ -1,12 +1,15 @@ +circularIndexedPropertyInstantiation.ts(4,5): error TS5114: Instantiations of type 'Foo' appear infinitely circular. circularIndexedPropertyInstantiation.ts(4,9): error TS5114: Instantiations of type 'Foo' appear infinitely circular. circularIndexedPropertyInstantiation.ts(5,13): error TS2344: Type '"false"' does not satisfy the constraint '"true"'. -==== circularIndexedPropertyInstantiation.ts (2 errors) ==== +==== circularIndexedPropertyInstantiation.ts (3 errors) ==== // Repro from #14837 type Foo = { "true": Foo> }[T]; let f1: Foo<"true", {}>; + ~~ +!!! error TS5114: Instantiations of type 'Foo' appear infinitely circular. ~~~~~~~~~~~~~~~ !!! error TS5114: Instantiations of type 'Foo' appear infinitely circular. let f2: Foo<"false", {}>; diff --git a/tsc/testdata/baselines/reference/compiler/circularInlineMappedGenericTupleTypeNoCrash(checkorder=default).errors.txt b/tsc/testdata/baselines/reference/compiler/circularInlineMappedGenericTupleTypeNoCrash(checkorder=default).errors.txt index 8d568d19a5373..61ac052a41c80 100644 --- a/tsc/testdata/baselines/reference/compiler/circularInlineMappedGenericTupleTypeNoCrash(checkorder=default).errors.txt +++ b/tsc/testdata/baselines/reference/compiler/circularInlineMappedGenericTupleTypeNoCrash(checkorder=default).errors.txt @@ -1,7 +1,8 @@ circularInlineMappedGenericTupleTypeNoCrash.ts(11,12): error TS2589: Type instantiation is excessively deep and possibly infinite. +circularInlineMappedGenericTupleTypeNoCrash.ts(11,60): error TS2589: Type instantiation is excessively deep and possibly infinite. -==== circularInlineMappedGenericTupleTypeNoCrash.ts (1 errors) ==== +==== circularInlineMappedGenericTupleTypeNoCrash.ts (2 errors) ==== class Foo { public readonly elements: { [P in keyof Elements]: { bar: Elements[P] } }; @@ -14,6 +15,8 @@ circularInlineMappedGenericTupleTypeNoCrash.ts(11,12): error TS2589: Type instan public add(): Foo<[...Elements, "abc"]> { return new Foo<[...Elements, "abc"]>(...this.elements, { bar: "abc" }); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. + ~~~~~~~~~~~~~~ !!! error TS2589: Type instantiation is excessively deep and possibly infinite. } } diff --git a/tsc/testdata/baselines/reference/compiler/circularInlineMappedGenericTupleTypeNoCrash(checkorder=typesfirst).errors.txt b/tsc/testdata/baselines/reference/compiler/circularInlineMappedGenericTupleTypeNoCrash(checkorder=typesfirst).errors.txt index 8d568d19a5373..61ac052a41c80 100644 --- a/tsc/testdata/baselines/reference/compiler/circularInlineMappedGenericTupleTypeNoCrash(checkorder=typesfirst).errors.txt +++ b/tsc/testdata/baselines/reference/compiler/circularInlineMappedGenericTupleTypeNoCrash(checkorder=typesfirst).errors.txt @@ -1,7 +1,8 @@ circularInlineMappedGenericTupleTypeNoCrash.ts(11,12): error TS2589: Type instantiation is excessively deep and possibly infinite. +circularInlineMappedGenericTupleTypeNoCrash.ts(11,60): error TS2589: Type instantiation is excessively deep and possibly infinite. -==== circularInlineMappedGenericTupleTypeNoCrash.ts (1 errors) ==== +==== circularInlineMappedGenericTupleTypeNoCrash.ts (2 errors) ==== class Foo { public readonly elements: { [P in keyof Elements]: { bar: Elements[P] } }; @@ -14,6 +15,8 @@ circularInlineMappedGenericTupleTypeNoCrash.ts(11,12): error TS2589: Type instan public add(): Foo<[...Elements, "abc"]> { return new Foo<[...Elements, "abc"]>(...this.elements, { bar: "abc" }); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. + ~~~~~~~~~~~~~~ !!! error TS2589: Type instantiation is excessively deep and possibly infinite. } } diff --git a/tsc/testdata/baselines/reference/compiler/circularReturnTypeSubtypeReductionOrder.errors.txt b/tsc/testdata/baselines/reference/compiler/circularReturnTypeSubtypeReductionOrder.errors.txt index f0b2f978c0228..b93093c154d7d 100644 --- a/tsc/testdata/baselines/reference/compiler/circularReturnTypeSubtypeReductionOrder.errors.txt +++ b/tsc/testdata/baselines/reference/compiler/circularReturnTypeSubtypeReductionOrder.errors.txt @@ -1,10 +1,12 @@ expression.ts(10,7): error TS2322: Type 'typeof A' is not assignable to type 'new () => A'. Type 'A' is not assignable to type 'A'. Two different types with this name exist, but they are unrelated. Types of property 'c' are incompatible. - Type '() => () => A | A' is not assignable to type 'A'. + Type '() => () => any' is not assignable to type 'A'. +expression.ts(11,5): error TS7023: 'rebuild' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. +expression.ts(14,5): error TS7023: 'b' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. -==== expression.ts (1 errors) ==== +==== expression.ts (3 errors) ==== // Subtype reduction of rebuild's return type relates the class to A, // which needs b's return type, which needs rebuild's: a cycle. Both // members report it, whichever return type is requested first, as if @@ -19,11 +21,15 @@ expression.ts(10,7): error TS2322: Type 'typeof A' is not assignable to type 'ne !!! error TS2322: Type 'typeof A' is not assignable to type 'new () => A'. !!! error TS2322: Type 'A' is not assignable to type 'A'. Two different types with this name exist, but they are unrelated. !!! error TS2322: Types of property 'c' are incompatible. -!!! error TS2322: Type '() => () => A | A' is not assignable to type 'A'. +!!! error TS2322: Type '() => () => any' is not assignable to type 'A'. rebuild() { + ~~~~~~~ +!!! error TS7023: 'rebuild' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. return flag ? this : new A(); } b() { + ~ +!!! error TS7023: 'b' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. return this.rebuild(); } c() { diff --git a/tsc/testdata/baselines/reference/compiler/circularReturnTypeSubtypeReductionOrder.types b/tsc/testdata/baselines/reference/compiler/circularReturnTypeSubtypeReductionOrder.types index 9088517456379..a44fd2123e1c2 100644 --- a/tsc/testdata/baselines/reference/compiler/circularReturnTypeSubtypeReductionOrder.types +++ b/tsc/testdata/baselines/reference/compiler/circularReturnTypeSubtypeReductionOrder.types @@ -20,7 +20,7 @@ const A: new () => A = class { >class { rebuild() { return flag ? this : new A(); } b() { return this.rebuild(); } c() { return this.rebuild; }} : typeof A rebuild() { ->rebuild : () => this | A +>rebuild : () => any return flag ? this : new A(); >flag ? this : new A() : this | A @@ -30,21 +30,21 @@ const A: new () => A = class { >A : new () => A } b() { ->b : () => this | A +>b : () => any return this.rebuild(); ->this.rebuild() : this | A ->this.rebuild : () => this | A +>this.rebuild() : any +>this.rebuild : () => any >this : this ->rebuild : () => this | A +>rebuild : () => any } c() { ->c : () => () => this | A +>c : () => () => any return this.rebuild; ->this.rebuild : () => this | A +>this.rebuild : () => any >this : this ->rebuild : () => this | A +>rebuild : () => any } }; export {}; @@ -61,23 +61,23 @@ class K { >K : K rebuild() { ->rebuild : () => this | B +>rebuild : () => B return flag ? this : new B(); ->flag ? this : new B() : this | B +>flag ? this : new B() : B >flag : boolean >this : this >new B() : B >B : new () => B } b() { ->b : () => this | B +>b : () => B return this.rebuild(); ->this.rebuild() : this | B ->this.rebuild : () => this | B +>this.rebuild() : B +>this.rebuild : () => B >this : this ->rebuild : () => this | B +>rebuild : () => B } } const B: new () => B = K; diff --git a/tsc/testdata/baselines/reference/compiler/circularReturnTypeThroughCallbackArgument(strict=false).errors.txt b/tsc/testdata/baselines/reference/compiler/circularReturnTypeThroughCallbackArgument(strict=false).errors.txt new file mode 100644 index 0000000000000..3c7a84d051ca9 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/circularReturnTypeThroughCallbackArgument(strict=false).errors.txt @@ -0,0 +1,38 @@ +circularReturnTypeThroughCallbackArgument.ts(23,25): error TS2322: Type 'Promise' is not assignable to type 'Promise'. + Type 'Other | Result' is not assignable to type 'Result'. + Type 'Other' is not assignable to type 'Result'. + Index signature for type 'string' is missing in type 'Other'. + + +==== circularReturnTypeThroughCallbackArgument.ts (1 errors) ==== + declare function fan(g: () => number): number; + + export function a(n: number) { + return fan(() => a(n)); + } + + declare function fanAll(g: () => Promise<{ ok: boolean }>): Promise<{ ok: boolean }>; + + export async function b(n: number) { + if (n > 0) { + return fanAll(() => b(n - 1)); + } + return { ok: true }; + } + + type Result = { success: boolean; [key: string]: unknown }; + interface Other { success: boolean; url: string } + declare function fanOut(generateOne: () => Promise): Promise; + declare const other: Other; + + export async function c(n: number) { + if (n) return other; + return fanOut(() => c(n - 1)); + ~~~~~~~~ +!!! error TS2322: Type 'Promise' is not assignable to type 'Promise'. +!!! error TS2322: Type 'Other | Result' is not assignable to type 'Result'. +!!! error TS2322: Type 'Other' is not assignable to type 'Result'. +!!! error TS2322: Index signature for type 'string' is missing in type 'Other'. +!!! related TS6502 circularReturnTypeThroughCallbackArgument.ts:18:38: The expected type comes from the return type of this signature. + } + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/circularReturnTypeThroughCallbackArgument(strict=false).types b/tsc/testdata/baselines/reference/compiler/circularReturnTypeThroughCallbackArgument(strict=false).types index fb9e85bf2b1d7..c3e8f0316281f 100644 --- a/tsc/testdata/baselines/reference/compiler/circularReturnTypeThroughCallbackArgument(strict=false).types +++ b/tsc/testdata/baselines/reference/compiler/circularReturnTypeThroughCallbackArgument(strict=false).types @@ -6,15 +6,15 @@ declare function fan(g: () => number): number; >g : () => number export function a(n: number) { ->a : (n: number) => any +>a : (n: number) => number >n : number return fan(() => a(n)); >fan(() => a(n)) : number >fan : (g: () => number) => number ->() => a(n) : () => any ->a(n) : any ->a : (n: number) => any +>() => a(n) : () => number +>a(n) : number +>a : (n: number) => number >n : number } @@ -25,7 +25,7 @@ declare function fanAll(g: () => Promise<{ ok: boolean }>): Promise<{ ok: boolea >ok : boolean export async function b(n: number) { ->b : (n: number) => any +>b : (n: number) => Promise<{ ok: boolean; }> >n : number if (n > 0) { @@ -36,9 +36,9 @@ export async function b(n: number) { return fanAll(() => b(n - 1)); >fanAll(() => b(n - 1)) : Promise<{ ok: boolean; }> >fanAll : (g: () => Promise<{ ok: boolean; }>) => Promise<{ ok: boolean; }> ->() => b(n - 1) : () => any ->b(n - 1) : any ->b : (n: number) => any +>() => b(n - 1) : () => Promise<{ ok: boolean; }> +>b(n - 1) : Promise<{ ok: boolean; }> +>b : (n: number) => Promise<{ ok: boolean; }> >n - 1 : number >n : number >1 : 1 @@ -66,7 +66,7 @@ declare const other: Other; >other : Other export async function c(n: number) { ->c : (n: number) => any +>c : (n: number) => Promise >n : number if (n) return other; @@ -76,9 +76,9 @@ export async function c(n: number) { return fanOut(() => c(n - 1)); >fanOut(() => c(n - 1)) : Promise >fanOut : (generateOne: () => Promise) => Promise ->() => c(n - 1) : () => any ->c(n - 1) : any ->c : (n: number) => any +>() => c(n - 1) : () => Promise +>c(n - 1) : Promise +>c : (n: number) => Promise >n - 1 : number >n : number >1 : 1 diff --git a/tsc/testdata/baselines/reference/compiler/circularReturnTypeThroughCallbackArgument(strict=true).errors.txt b/tsc/testdata/baselines/reference/compiler/circularReturnTypeThroughCallbackArgument(strict=true).errors.txt index a10fc6bcbe59c..3c7a84d051ca9 100644 --- a/tsc/testdata/baselines/reference/compiler/circularReturnTypeThroughCallbackArgument(strict=true).errors.txt +++ b/tsc/testdata/baselines/reference/compiler/circularReturnTypeThroughCallbackArgument(strict=true).errors.txt @@ -1,31 +1,21 @@ -circularReturnTypeThroughCallbackArgument.ts(3,17): error TS7023: 'a' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. -circularReturnTypeThroughCallbackArgument.ts(4,16): error TS7024: Function implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. -circularReturnTypeThroughCallbackArgument.ts(9,23): error TS7023: 'b' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. -circularReturnTypeThroughCallbackArgument.ts(11,23): error TS7024: Function implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. -circularReturnTypeThroughCallbackArgument.ts(21,23): error TS7023: 'c' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. -circularReturnTypeThroughCallbackArgument.ts(23,19): error TS7024: Function implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. +circularReturnTypeThroughCallbackArgument.ts(23,25): error TS2322: Type 'Promise' is not assignable to type 'Promise'. + Type 'Other | Result' is not assignable to type 'Result'. + Type 'Other' is not assignable to type 'Result'. + Index signature for type 'string' is missing in type 'Other'. -==== circularReturnTypeThroughCallbackArgument.ts (6 errors) ==== +==== circularReturnTypeThroughCallbackArgument.ts (1 errors) ==== declare function fan(g: () => number): number; export function a(n: number) { - ~ -!!! error TS7023: 'a' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. return fan(() => a(n)); - ~~~~~~~~~~ -!!! error TS7024: Function implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. } declare function fanAll(g: () => Promise<{ ok: boolean }>): Promise<{ ok: boolean }>; export async function b(n: number) { - ~ -!!! error TS7023: 'b' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. if (n > 0) { return fanAll(() => b(n - 1)); - ~~~~~~~~~~~~~~ -!!! error TS7024: Function implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. } return { ok: true }; } @@ -36,11 +26,13 @@ circularReturnTypeThroughCallbackArgument.ts(23,19): error TS7024: Function impl declare const other: Other; export async function c(n: number) { - ~ -!!! error TS7023: 'c' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. if (n) return other; return fanOut(() => c(n - 1)); - ~~~~~~~~~~~~~~ -!!! error TS7024: Function implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. + ~~~~~~~~ +!!! error TS2322: Type 'Promise' is not assignable to type 'Promise'. +!!! error TS2322: Type 'Other | Result' is not assignable to type 'Result'. +!!! error TS2322: Type 'Other' is not assignable to type 'Result'. +!!! error TS2322: Index signature for type 'string' is missing in type 'Other'. +!!! related TS6502 circularReturnTypeThroughCallbackArgument.ts:18:38: The expected type comes from the return type of this signature. } \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/circularReturnTypeThroughCallbackArgument(strict=true).types b/tsc/testdata/baselines/reference/compiler/circularReturnTypeThroughCallbackArgument(strict=true).types index fb9e85bf2b1d7..c3e8f0316281f 100644 --- a/tsc/testdata/baselines/reference/compiler/circularReturnTypeThroughCallbackArgument(strict=true).types +++ b/tsc/testdata/baselines/reference/compiler/circularReturnTypeThroughCallbackArgument(strict=true).types @@ -6,15 +6,15 @@ declare function fan(g: () => number): number; >g : () => number export function a(n: number) { ->a : (n: number) => any +>a : (n: number) => number >n : number return fan(() => a(n)); >fan(() => a(n)) : number >fan : (g: () => number) => number ->() => a(n) : () => any ->a(n) : any ->a : (n: number) => any +>() => a(n) : () => number +>a(n) : number +>a : (n: number) => number >n : number } @@ -25,7 +25,7 @@ declare function fanAll(g: () => Promise<{ ok: boolean }>): Promise<{ ok: boolea >ok : boolean export async function b(n: number) { ->b : (n: number) => any +>b : (n: number) => Promise<{ ok: boolean; }> >n : number if (n > 0) { @@ -36,9 +36,9 @@ export async function b(n: number) { return fanAll(() => b(n - 1)); >fanAll(() => b(n - 1)) : Promise<{ ok: boolean; }> >fanAll : (g: () => Promise<{ ok: boolean; }>) => Promise<{ ok: boolean; }> ->() => b(n - 1) : () => any ->b(n - 1) : any ->b : (n: number) => any +>() => b(n - 1) : () => Promise<{ ok: boolean; }> +>b(n - 1) : Promise<{ ok: boolean; }> +>b : (n: number) => Promise<{ ok: boolean; }> >n - 1 : number >n : number >1 : 1 @@ -66,7 +66,7 @@ declare const other: Other; >other : Other export async function c(n: number) { ->c : (n: number) => any +>c : (n: number) => Promise >n : number if (n) return other; @@ -76,9 +76,9 @@ export async function c(n: number) { return fanOut(() => c(n - 1)); >fanOut(() => c(n - 1)) : Promise >fanOut : (generateOne: () => Promise) => Promise ->() => c(n - 1) : () => any ->c(n - 1) : any ->c : (n: number) => any +>() => c(n - 1) : () => Promise +>c(n - 1) : Promise +>c : (n: number) => Promise >n - 1 : number >n : number >1 : 1 diff --git a/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=default).errors.txt b/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=default).errors.txt new file mode 100644 index 0000000000000..495583f737e9e --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=default).errors.txt @@ -0,0 +1,40 @@ +circularScopeThunkReverseMapped.ts(12,14): error TS7022: '$' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. +circularScopeThunkReverseMapped.ts(14,5): error TS7023: 'b' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. +circularScopeThunkReverseMapped.ts(14,41): error TS7006: Parameter 'n' implicitly has an 'any' type. +circularScopeThunkReverseMapped.ts(18,14): error TS7022: '$2' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. +circularScopeThunkReverseMapped.ts(20,5): error TS7023: 'b' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. + + +==== circularScopeThunkReverseMapped.ts (5 errors) ==== + type Resolve = T extends () => infer R ? R : T; + type Validate = { [K in keyof def]: def[K] extends () => infer R ? () => R : def[K] }; + interface Scope { + type(k: k): Resolve; + } + interface Type { + pipe(f: (x: T) => U): Type; + } + declare function scope(def: Validate): Scope; + declare function type(x: T): Type; + + export const $ = scope({ + ~ +!!! error TS7022: '$' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. + a: type("string").pipe(function (s) { return s.length; }), + b: () => $.type("a").pipe(function (n) { return n === 0; }), + ~ +!!! error TS7023: 'b' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. + ~ +!!! error TS7006: Parameter 'n' implicitly has an 'any' type. + }); + + // Entered from the function in the first property when queried in reverse + export const $2 = scope({ + ~~ +!!! error TS7022: '$2' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. + a: type("string").pipe(function (s) { return s.length; }), + b: () => $2.type("a").pipe((n: number) => n === 0), + ~ +!!! error TS7023: 'b' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. + }); + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=default).symbols b/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=default).symbols index cefed83354abf..162296d91ea40 100644 --- a/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=default).symbols +++ b/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=default).symbols @@ -82,11 +82,7 @@ export const $ = scope({ b: () => $.type("a").pipe(function (n) { return n === 0; }), >b : Symbol(b, Decl(circularScopeThunkReverseMapped.ts, 12, 62)) ->$.type("a").pipe : Symbol(Type.pipe, Decl(circularScopeThunkReverseMapped.ts, 5, 19)) ->$.type : Symbol(Scope.type, Decl(circularScopeThunkReverseMapped.ts, 2, 22)) >$ : Symbol($, Decl(circularScopeThunkReverseMapped.ts, 11, 12)) ->type : Symbol(Scope.type, Decl(circularScopeThunkReverseMapped.ts, 2, 22)) ->pipe : Symbol(Type.pipe, Decl(circularScopeThunkReverseMapped.ts, 5, 19)) >n : Symbol(n, Decl(circularScopeThunkReverseMapped.ts, 13, 40)) >n : Symbol(n, Decl(circularScopeThunkReverseMapped.ts, 13, 40)) @@ -109,11 +105,7 @@ export const $2 = scope({ b: () => $2.type("a").pipe((n: number) => n === 0), >b : Symbol(b, Decl(circularScopeThunkReverseMapped.ts, 18, 62)) ->$2.type("a").pipe : Symbol(Type.pipe, Decl(circularScopeThunkReverseMapped.ts, 5, 19)) ->$2.type : Symbol(Scope.type, Decl(circularScopeThunkReverseMapped.ts, 2, 22)) >$2 : Symbol($2, Decl(circularScopeThunkReverseMapped.ts, 17, 12)) ->type : Symbol(Scope.type, Decl(circularScopeThunkReverseMapped.ts, 2, 22)) ->pipe : Symbol(Type.pipe, Decl(circularScopeThunkReverseMapped.ts, 5, 19)) >n : Symbol(n, Decl(circularScopeThunkReverseMapped.ts, 19, 32)) >n : Symbol(n, Decl(circularScopeThunkReverseMapped.ts, 19, 32)) diff --git a/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=default).types b/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=default).types index 41f30b7dbe3f2..cd86ca9ddbbf9 100644 --- a/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=default).types +++ b/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=default).types @@ -27,10 +27,10 @@ declare function type(x: T): Type; >x : T export const $ = scope({ ->$ : Scope<{ a: Type; b: unknown; }> ->scope({ a: type("string").pipe(function (s) { return s.length; }), b: () => $.type("a").pipe(function (n) { return n === 0; }),}) : Scope<{ a: Type; b: unknown; }> +>$ : any +>scope({ a: type("string").pipe(function (s) { return s.length; }), b: () => $.type("a").pipe(function (n) { return n === 0; }),}) : Scope<{ readonly a: Type; readonly b: () => any; }> >scope : (def: Validate) => Scope ->{ a: type("string").pipe(function (s) { return s.length; }), b: () => $.type("a").pipe(function (n) { return n === 0; }),} : { a: Type; b: () => Type; } +>{ a: type("string").pipe(function (s) { return s.length; }), b: () => $.type("a").pipe(function (n) { return n === 0; }),} : { a: Type; b: () => any; } a: type("string").pipe(function (s) { return s.length; }), >a : Type @@ -47,30 +47,30 @@ export const $ = scope({ >length : number b: () => $.type("a").pipe(function (n) { return n === 0; }), ->b : () => Type ->() => $.type("a").pipe(function (n) { return n === 0; }) : () => Type ->$.type("a").pipe(function (n) { return n === 0; }) : Type ->$.type("a").pipe : (f: (x: number) => U) => Type ->$.type("a") : Type ->$.type : (k: k_1) => Resolve<{ a: Type; b: unknown; }[k_1]> ->$ : Scope<{ a: Type; b: unknown; }> ->type : (k: k_1) => Resolve<{ a: Type; b: unknown; }[k_1]> +>b : () => any +>() => $.type("a").pipe(function (n) { return n === 0; }) : () => any +>$.type("a").pipe(function (n) { return n === 0; }) : any +>$.type("a").pipe : any +>$.type("a") : any +>$.type : any +>$ : any +>type : any >"a" : "a" ->pipe : (f: (x: number) => U) => Type ->function (n) { return n === 0; } : (n: number) => n is 0 ->n : number +>pipe : any +>function (n) { return n === 0; } : (n: any) => boolean +>n : any >n === 0 : boolean ->n : number +>n : any >0 : 0 }); // Entered from the function in the first property when queried in reverse export const $2 = scope({ ->$2 : Scope<{ a: Type; b: unknown; }> ->scope({ a: type("string").pipe(function (s) { return s.length; }), b: () => $2.type("a").pipe((n: number) => n === 0),}) : Scope<{ a: Type; b: unknown; }> +>$2 : any +>scope({ a: type("string").pipe(function (s) { return s.length; }), b: () => $2.type("a").pipe((n: number) => n === 0),}) : Scope<{ readonly a: Type; readonly b: () => any; }> >scope : (def: Validate) => Scope ->{ a: type("string").pipe(function (s) { return s.length; }), b: () => $2.type("a").pipe((n: number) => n === 0),} : { a: Type; b: () => Type; } +>{ a: type("string").pipe(function (s) { return s.length; }), b: () => $2.type("a").pipe((n: number) => n === 0),} : { a: Type; b: () => any; } a: type("string").pipe(function (s) { return s.length; }), >a : Type @@ -87,16 +87,16 @@ export const $2 = scope({ >length : number b: () => $2.type("a").pipe((n: number) => n === 0), ->b : () => Type ->() => $2.type("a").pipe((n: number) => n === 0) : () => Type ->$2.type("a").pipe((n: number) => n === 0) : Type ->$2.type("a").pipe : (f: (x: number) => U) => Type ->$2.type("a") : Type ->$2.type : (k: k_1) => Resolve<{ a: Type; b: unknown; }[k_1]> ->$2 : Scope<{ a: Type; b: unknown; }> ->type : (k: k_1) => Resolve<{ a: Type; b: unknown; }[k_1]> +>b : () => any +>() => $2.type("a").pipe((n: number) => n === 0) : () => any +>$2.type("a").pipe((n: number) => n === 0) : any +>$2.type("a").pipe : any +>$2.type("a") : any +>$2.type : any +>$2 : any +>type : any >"a" : "a" ->pipe : (f: (x: number) => U) => Type +>pipe : any >(n: number) => n === 0 : (n: number) => n is 0 >n : number >n === 0 : boolean diff --git a/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=typesfirst).errors.txt b/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=typesfirst).errors.txt new file mode 100644 index 0000000000000..495583f737e9e --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=typesfirst).errors.txt @@ -0,0 +1,40 @@ +circularScopeThunkReverseMapped.ts(12,14): error TS7022: '$' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. +circularScopeThunkReverseMapped.ts(14,5): error TS7023: 'b' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. +circularScopeThunkReverseMapped.ts(14,41): error TS7006: Parameter 'n' implicitly has an 'any' type. +circularScopeThunkReverseMapped.ts(18,14): error TS7022: '$2' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. +circularScopeThunkReverseMapped.ts(20,5): error TS7023: 'b' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. + + +==== circularScopeThunkReverseMapped.ts (5 errors) ==== + type Resolve = T extends () => infer R ? R : T; + type Validate = { [K in keyof def]: def[K] extends () => infer R ? () => R : def[K] }; + interface Scope { + type(k: k): Resolve; + } + interface Type { + pipe(f: (x: T) => U): Type; + } + declare function scope(def: Validate): Scope; + declare function type(x: T): Type; + + export const $ = scope({ + ~ +!!! error TS7022: '$' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. + a: type("string").pipe(function (s) { return s.length; }), + b: () => $.type("a").pipe(function (n) { return n === 0; }), + ~ +!!! error TS7023: 'b' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. + ~ +!!! error TS7006: Parameter 'n' implicitly has an 'any' type. + }); + + // Entered from the function in the first property when queried in reverse + export const $2 = scope({ + ~~ +!!! error TS7022: '$2' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. + a: type("string").pipe(function (s) { return s.length; }), + b: () => $2.type("a").pipe((n: number) => n === 0), + ~ +!!! error TS7023: 'b' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. + }); + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=typesfirst).symbols b/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=typesfirst).symbols index cefed83354abf..162296d91ea40 100644 --- a/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=typesfirst).symbols +++ b/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=typesfirst).symbols @@ -82,11 +82,7 @@ export const $ = scope({ b: () => $.type("a").pipe(function (n) { return n === 0; }), >b : Symbol(b, Decl(circularScopeThunkReverseMapped.ts, 12, 62)) ->$.type("a").pipe : Symbol(Type.pipe, Decl(circularScopeThunkReverseMapped.ts, 5, 19)) ->$.type : Symbol(Scope.type, Decl(circularScopeThunkReverseMapped.ts, 2, 22)) >$ : Symbol($, Decl(circularScopeThunkReverseMapped.ts, 11, 12)) ->type : Symbol(Scope.type, Decl(circularScopeThunkReverseMapped.ts, 2, 22)) ->pipe : Symbol(Type.pipe, Decl(circularScopeThunkReverseMapped.ts, 5, 19)) >n : Symbol(n, Decl(circularScopeThunkReverseMapped.ts, 13, 40)) >n : Symbol(n, Decl(circularScopeThunkReverseMapped.ts, 13, 40)) @@ -109,11 +105,7 @@ export const $2 = scope({ b: () => $2.type("a").pipe((n: number) => n === 0), >b : Symbol(b, Decl(circularScopeThunkReverseMapped.ts, 18, 62)) ->$2.type("a").pipe : Symbol(Type.pipe, Decl(circularScopeThunkReverseMapped.ts, 5, 19)) ->$2.type : Symbol(Scope.type, Decl(circularScopeThunkReverseMapped.ts, 2, 22)) >$2 : Symbol($2, Decl(circularScopeThunkReverseMapped.ts, 17, 12)) ->type : Symbol(Scope.type, Decl(circularScopeThunkReverseMapped.ts, 2, 22)) ->pipe : Symbol(Type.pipe, Decl(circularScopeThunkReverseMapped.ts, 5, 19)) >n : Symbol(n, Decl(circularScopeThunkReverseMapped.ts, 19, 32)) >n : Symbol(n, Decl(circularScopeThunkReverseMapped.ts, 19, 32)) diff --git a/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=typesfirst).types b/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=typesfirst).types index 41f30b7dbe3f2..cd86ca9ddbbf9 100644 --- a/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=typesfirst).types +++ b/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=typesfirst).types @@ -27,10 +27,10 @@ declare function type(x: T): Type; >x : T export const $ = scope({ ->$ : Scope<{ a: Type; b: unknown; }> ->scope({ a: type("string").pipe(function (s) { return s.length; }), b: () => $.type("a").pipe(function (n) { return n === 0; }),}) : Scope<{ a: Type; b: unknown; }> +>$ : any +>scope({ a: type("string").pipe(function (s) { return s.length; }), b: () => $.type("a").pipe(function (n) { return n === 0; }),}) : Scope<{ readonly a: Type; readonly b: () => any; }> >scope : (def: Validate) => Scope ->{ a: type("string").pipe(function (s) { return s.length; }), b: () => $.type("a").pipe(function (n) { return n === 0; }),} : { a: Type; b: () => Type; } +>{ a: type("string").pipe(function (s) { return s.length; }), b: () => $.type("a").pipe(function (n) { return n === 0; }),} : { a: Type; b: () => any; } a: type("string").pipe(function (s) { return s.length; }), >a : Type @@ -47,30 +47,30 @@ export const $ = scope({ >length : number b: () => $.type("a").pipe(function (n) { return n === 0; }), ->b : () => Type ->() => $.type("a").pipe(function (n) { return n === 0; }) : () => Type ->$.type("a").pipe(function (n) { return n === 0; }) : Type ->$.type("a").pipe : (f: (x: number) => U) => Type ->$.type("a") : Type ->$.type : (k: k_1) => Resolve<{ a: Type; b: unknown; }[k_1]> ->$ : Scope<{ a: Type; b: unknown; }> ->type : (k: k_1) => Resolve<{ a: Type; b: unknown; }[k_1]> +>b : () => any +>() => $.type("a").pipe(function (n) { return n === 0; }) : () => any +>$.type("a").pipe(function (n) { return n === 0; }) : any +>$.type("a").pipe : any +>$.type("a") : any +>$.type : any +>$ : any +>type : any >"a" : "a" ->pipe : (f: (x: number) => U) => Type ->function (n) { return n === 0; } : (n: number) => n is 0 ->n : number +>pipe : any +>function (n) { return n === 0; } : (n: any) => boolean +>n : any >n === 0 : boolean ->n : number +>n : any >0 : 0 }); // Entered from the function in the first property when queried in reverse export const $2 = scope({ ->$2 : Scope<{ a: Type; b: unknown; }> ->scope({ a: type("string").pipe(function (s) { return s.length; }), b: () => $2.type("a").pipe((n: number) => n === 0),}) : Scope<{ a: Type; b: unknown; }> +>$2 : any +>scope({ a: type("string").pipe(function (s) { return s.length; }), b: () => $2.type("a").pipe((n: number) => n === 0),}) : Scope<{ readonly a: Type; readonly b: () => any; }> >scope : (def: Validate) => Scope ->{ a: type("string").pipe(function (s) { return s.length; }), b: () => $2.type("a").pipe((n: number) => n === 0),} : { a: Type; b: () => Type; } +>{ a: type("string").pipe(function (s) { return s.length; }), b: () => $2.type("a").pipe((n: number) => n === 0),} : { a: Type; b: () => any; } a: type("string").pipe(function (s) { return s.length; }), >a : Type @@ -87,16 +87,16 @@ export const $2 = scope({ >length : number b: () => $2.type("a").pipe((n: number) => n === 0), ->b : () => Type ->() => $2.type("a").pipe((n: number) => n === 0) : () => Type ->$2.type("a").pipe((n: number) => n === 0) : Type ->$2.type("a").pipe : (f: (x: number) => U) => Type ->$2.type("a") : Type ->$2.type : (k: k_1) => Resolve<{ a: Type; b: unknown; }[k_1]> ->$2 : Scope<{ a: Type; b: unknown; }> ->type : (k: k_1) => Resolve<{ a: Type; b: unknown; }[k_1]> +>b : () => any +>() => $2.type("a").pipe((n: number) => n === 0) : () => any +>$2.type("a").pipe((n: number) => n === 0) : any +>$2.type("a").pipe : any +>$2.type("a") : any +>$2.type : any +>$2 : any +>type : any >"a" : "a" ->pipe : (f: (x: number) => U) => Type +>pipe : any >(n: number) => n === 0 : (n: number) => n is 0 >n : number >n === 0 : boolean diff --git a/tsc/testdata/baselines/reference/compiler/classVarianceResolveCircularity2.errors.txt b/tsc/testdata/baselines/reference/compiler/classVarianceResolveCircularity2.errors.txt deleted file mode 100644 index 502975549c191..0000000000000 --- a/tsc/testdata/baselines/reference/compiler/classVarianceResolveCircularity2.errors.txt +++ /dev/null @@ -1,24 +0,0 @@ -classVarianceResolveCircularity2.ts(7,5): error TS7022: 'Value' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. - - -==== classVarianceResolveCircularity2.ts (1 errors) ==== - // Issue #52813 - - export {}; - - class Bar { - num!: number; - Value = callme(new Foo(this)).bar.num; - ~~~~~ -!!! error TS7022: 'Value' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. - Field: number = callme(new Foo(this)).bar.num; - } - declare function callme(x: Foo): Foo; - declare function callme(x: object): string; - - class Foo { - bar!: Bar; - constructor(bar: Bar) { - this.bar = bar; - } - } \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/classVarianceResolveCircularity2.types b/tsc/testdata/baselines/reference/compiler/classVarianceResolveCircularity2.types index d1bd8c01269ff..1aa4a1481a92c 100644 --- a/tsc/testdata/baselines/reference/compiler/classVarianceResolveCircularity2.types +++ b/tsc/testdata/baselines/reference/compiler/classVarianceResolveCircularity2.types @@ -12,7 +12,7 @@ class Bar { >num : number Value = callme(new Foo(this)).bar.num; ->Value : any +>Value : number >callme(new Foo(this)).bar.num : number >callme(new Foo(this)).bar : Bar >callme(new Foo(this)) : Foo diff --git a/tsc/testdata/baselines/reference/compiler/constAssertionBindingPatternQueryOrder(checkorder=typesfirst).types b/tsc/testdata/baselines/reference/compiler/constAssertionBindingPatternQueryOrder(checkorder=typesfirst).types index d263c604b0052..4d7301bd19e06 100644 --- a/tsc/testdata/baselines/reference/compiler/constAssertionBindingPatternQueryOrder(checkorder=typesfirst).types +++ b/tsc/testdata/baselines/reference/compiler/constAssertionBindingPatternQueryOrder(checkorder=typesfirst).types @@ -11,12 +11,12 @@ async function f() { const [[a], b] = await settle(() => [Promise.resolve(0), Promise.reject(1)] as const); >a : number >b : [never] ->await settle(() => [Promise.resolve(0), Promise.reject(1)] as const) : [[number], [never]] ->settle(() => [Promise.resolve(0), Promise.reject(1)] as const) : Promise<[[number], [never]]> +>await settle(() => [Promise.resolve(0), Promise.reject(1)] as const) : readonly [[number], [never]] +>settle(() => [Promise.resolve(0), Promise.reject(1)] as const) : Promise >settle : (fn: () => T) => Promise<{ [K in keyof T]: [Awaited]; }> ->() => [Promise.resolve(0), Promise.reject(1)] as const : () => [Promise, Promise] ->[Promise.resolve(0), Promise.reject(1)] as const : [Promise, Promise] ->[Promise.resolve(0), Promise.reject(1)] : [Promise, Promise] +>() => [Promise.resolve(0), Promise.reject(1)] as const : () => readonly [Promise, Promise] +>[Promise.resolve(0), Promise.reject(1)] as const : readonly [Promise, Promise] +>[Promise.resolve(0), Promise.reject(1)] : readonly [Promise, Promise] >Promise.resolve(0) : Promise >Promise.resolve : { (): Promise; (value: T): Promise>; (value: T | PromiseLike): Promise>; } >Promise : PromiseConstructor diff --git a/tsc/testdata/baselines/reference/compiler/contextualCheckInDiscardedCycle.symbols b/tsc/testdata/baselines/reference/compiler/contextualCheckInDiscardedCycle.symbols new file mode 100644 index 0000000000000..3169a3c3cbd08 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contextualCheckInDiscardedCycle.symbols @@ -0,0 +1,150 @@ +//// [tests/cases/compiler/contextualCheckInDiscardedCycle.ts] //// + +=== returnTypeCycle.ts === +// Callbacks contextually checked while a cycle is in progress are checked +// again once it is discarded, with their contextual parameter types. + +declare function useCallback(callback: T): T; +>useCallback : Symbol(useCallback, Decl(returnTypeCycle.ts, 0, 0)) +>T : Symbol(T, Decl(returnTypeCycle.ts, 3, 29)) +>Function : Symbol(Function, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.esnext.decorators.d.ts, --, --)) +>callback : Symbol(callback, Decl(returnTypeCycle.ts, 3, 49)) +>T : Symbol(T, Decl(returnTypeCycle.ts, 3, 29)) +>T : Symbol(T, Decl(returnTypeCycle.ts, 3, 29)) + +declare function refreshAll(options: { ids: string[]; extra: T }): Promise; +>refreshAll : Symbol(refreshAll, Decl(returnTypeCycle.ts, 3, 65)) +>T : Symbol(T, Decl(returnTypeCycle.ts, 4, 28)) +>options : Symbol(options, Decl(returnTypeCycle.ts, 4, 31)) +>ids : Symbol(ids, Decl(returnTypeCycle.ts, 4, 41)) +>extra : Symbol(extra, Decl(returnTypeCycle.ts, 4, 56)) +>T : Symbol(T, Decl(returnTypeCycle.ts, 4, 28)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --)) +>T : Symbol(T, Decl(returnTypeCycle.ts, 4, 28)) + +interface View { +>View : Symbol(View, Decl(returnTypeCycle.ts, 4, 81)) + + refresh: (force?: boolean) => Promise; +>refresh : Symbol(View.refresh, Decl(returnTypeCycle.ts, 6, 16)) +>force : Symbol(force, Decl(returnTypeCycle.ts, 7, 14)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --)) +} + +declare const items: { id: string }[]; +>items : Symbol(items, Decl(returnTypeCycle.ts, 10, 13)) +>id : Symbol(id, Decl(returnTypeCycle.ts, 10, 22)) + +function useView(): View { +>useView : Symbol(useView, Decl(returnTypeCycle.ts, 10, 38)) +>View : Symbol(View, Decl(returnTypeCycle.ts, 4, 81)) + + const refresh = useCallback((force?: boolean) => +>refresh : Symbol(refresh, Decl(returnTypeCycle.ts, 13, 9)) +>useCallback : Symbol(useCallback, Decl(returnTypeCycle.ts, 0, 0)) +>force : Symbol(force, Decl(returnTypeCycle.ts, 13, 33)) + + refreshAll({ +>refreshAll : Symbol(refreshAll, Decl(returnTypeCycle.ts, 3, 65)) + + ids: items.map(({ id }) => id), +>ids : Symbol(ids, Decl(returnTypeCycle.ts, 14, 20)) +>items.map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --)) +>items : Symbol(items, Decl(returnTypeCycle.ts, 10, 13)) +>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --)) +>id : Symbol(id, Decl(returnTypeCycle.ts, 15, 29)) +>id : Symbol(id, Decl(returnTypeCycle.ts, 15, 29)) + + extra: 1, +>extra : Symbol(extra, Decl(returnTypeCycle.ts, 15, 43)) + + })); + return { refresh }; +>refresh : Symbol(refresh, Decl(returnTypeCycle.ts, 18, 12)) +} + +=== yieldOperandCycle.ts === +interface Eff { +>Eff : Symbol(Eff, Decl(yieldOperandCycle.ts, 0, 0)) +>A : Symbol(A, Decl(yieldOperandCycle.ts, 0, 14)) +>E : Symbol(E, Decl(yieldOperandCycle.ts, 0, 16)) + + [Symbol.iterator](): Generator, A, any>; +>[Symbol.iterator] : Symbol(Eff[Symbol.iterator], Decl(yieldOperandCycle.ts, 0, 21)) +>Symbol.iterator : Symbol(SymbolConstructor.iterator, Decl(lib.es2015.iterable.d.ts, --, --)) +>Symbol : Symbol(Symbol, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2019.symbol.d.ts, --, --)) +>iterator : Symbol(SymbolConstructor.iterator, Decl(lib.es2015.iterable.d.ts, --, --)) +>Generator : Symbol(Generator, Decl(lib.es2015.generator.d.ts, --, --)) +>Eff : Symbol(Eff, Decl(yieldOperandCycle.ts, 0, 0)) +>A : Symbol(A, Decl(yieldOperandCycle.ts, 0, 14)) +>E : Symbol(E, Decl(yieldOperandCycle.ts, 0, 16)) +>A : Symbol(A, Decl(yieldOperandCycle.ts, 0, 14)) + + pipe(f: (self: Eff) => B): B; +>pipe : Symbol(Eff.pipe, Decl(yieldOperandCycle.ts, 1, 54)) +>B : Symbol(B, Decl(yieldOperandCycle.ts, 2, 9)) +>f : Symbol(f, Decl(yieldOperandCycle.ts, 2, 12)) +>self : Symbol(self, Decl(yieldOperandCycle.ts, 2, 16)) +>Eff : Symbol(Eff, Decl(yieldOperandCycle.ts, 0, 0)) +>A : Symbol(A, Decl(yieldOperandCycle.ts, 0, 14)) +>E : Symbol(E, Decl(yieldOperandCycle.ts, 0, 16)) +>B : Symbol(B, Decl(yieldOperandCycle.ts, 2, 9)) +>B : Symbol(B, Decl(yieldOperandCycle.ts, 2, 9)) +} +declare function mapError(f: (e: E) => E2): (self: Eff) => Eff; +>mapError : Symbol(mapError, Decl(yieldOperandCycle.ts, 3, 1)) +>E : Symbol(E, Decl(yieldOperandCycle.ts, 4, 26)) +>E2 : Symbol(E2, Decl(yieldOperandCycle.ts, 4, 28)) +>f : Symbol(f, Decl(yieldOperandCycle.ts, 4, 33)) +>e : Symbol(e, Decl(yieldOperandCycle.ts, 4, 37)) +>E : Symbol(E, Decl(yieldOperandCycle.ts, 4, 26)) +>E2 : Symbol(E2, Decl(yieldOperandCycle.ts, 4, 28)) +>A : Symbol(A, Decl(yieldOperandCycle.ts, 4, 52)) +>self : Symbol(self, Decl(yieldOperandCycle.ts, 4, 55)) +>Eff : Symbol(Eff, Decl(yieldOperandCycle.ts, 0, 0)) +>A : Symbol(A, Decl(yieldOperandCycle.ts, 4, 52)) +>E : Symbol(E, Decl(yieldOperandCycle.ts, 4, 26)) +>Eff : Symbol(Eff, Decl(yieldOperandCycle.ts, 0, 0)) +>A : Symbol(A, Decl(yieldOperandCycle.ts, 4, 52)) +>E2 : Symbol(E2, Decl(yieldOperandCycle.ts, 4, 28)) + +declare function fn(body: (input: number) => Generator): (input: number) => Eff; +>fn : Symbol(fn, Decl(yieldOperandCycle.ts, 4, 86)) +>Y : Symbol(Y, Decl(yieldOperandCycle.ts, 5, 20)) +>R : Symbol(R, Decl(yieldOperandCycle.ts, 5, 22)) +>body : Symbol(body, Decl(yieldOperandCycle.ts, 5, 26)) +>input : Symbol(input, Decl(yieldOperandCycle.ts, 5, 33)) +>Generator : Symbol(Generator, Decl(lib.es2015.generator.d.ts, --, --)) +>Y : Symbol(Y, Decl(yieldOperandCycle.ts, 5, 20)) +>R : Symbol(R, Decl(yieldOperandCycle.ts, 5, 22)) +>input : Symbol(input, Decl(yieldOperandCycle.ts, 5, 75)) +>Eff : Symbol(Eff, Decl(yieldOperandCycle.ts, 0, 0)) +>R : Symbol(R, Decl(yieldOperandCycle.ts, 5, 22)) + +declare function make(s: T): T; +>make : Symbol(make, Decl(yieldOperandCycle.ts, 5, 107)) +>T : Symbol(T, Decl(yieldOperandCycle.ts, 6, 22)) +>s : Symbol(s, Decl(yieldOperandCycle.ts, 6, 40)) +>T : Symbol(T, Decl(yieldOperandCycle.ts, 6, 22)) +>T : Symbol(T, Decl(yieldOperandCycle.ts, 6, 22)) + +declare const uuid: Eff; +>uuid : Symbol(uuid, Decl(yieldOperandCycle.ts, 7, 13)) +>Eff : Symbol(Eff, Decl(yieldOperandCycle.ts, 0, 0)) +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2022.error.d.ts, --, --)) + +fn(function* (input) { +>fn : Symbol(fn, Decl(yieldOperandCycle.ts, 4, 86)) +>input : Symbol(input, Decl(yieldOperandCycle.ts, 9, 14)) + + make(yield* uuid.pipe(mapError((cause) => ({ cause })))); +>make : Symbol(make, Decl(yieldOperandCycle.ts, 5, 107)) +>uuid.pipe : Symbol(Eff.pipe, Decl(yieldOperandCycle.ts, 1, 54)) +>uuid : Symbol(uuid, Decl(yieldOperandCycle.ts, 7, 13)) +>pipe : Symbol(Eff.pipe, Decl(yieldOperandCycle.ts, 1, 54)) +>mapError : Symbol(mapError, Decl(yieldOperandCycle.ts, 3, 1)) +>cause : Symbol(cause, Decl(yieldOperandCycle.ts, 10, 36)) +>cause : Symbol(cause, Decl(yieldOperandCycle.ts, 10, 48)) + +}); + diff --git a/tsc/testdata/baselines/reference/compiler/contextualCheckInDiscardedCycle.types b/tsc/testdata/baselines/reference/compiler/contextualCheckInDiscardedCycle.types new file mode 100644 index 0000000000000..7506ea748391e --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contextualCheckInDiscardedCycle.types @@ -0,0 +1,117 @@ +//// [tests/cases/compiler/contextualCheckInDiscardedCycle.ts] //// + +=== returnTypeCycle.ts === +// Callbacks contextually checked while a cycle is in progress are checked +// again once it is discarded, with their contextual parameter types. + +declare function useCallback(callback: T): T; +>useCallback : (callback: T) => T +>callback : T + +declare function refreshAll(options: { ids: string[]; extra: T }): Promise; +>refreshAll : (options: { ids: string[]; extra: T; }) => Promise +>options : { ids: string[]; extra: T; } +>ids : string[] +>extra : T + +interface View { + refresh: (force?: boolean) => Promise; +>refresh : (force?: boolean) => Promise +>force : boolean | undefined +} + +declare const items: { id: string }[]; +>items : { id: string; }[] +>id : string + +function useView(): View { +>useView : () => View + + const refresh = useCallback((force?: boolean) => +>refresh : (force?: boolean) => Promise +>useCallback((force?: boolean) => refreshAll({ ids: items.map(({ id }) => id), extra: 1, })) : (force?: boolean) => Promise +>useCallback : (callback: T) => T +>(force?: boolean) => refreshAll({ ids: items.map(({ id }) => id), extra: 1, }) : (force?: boolean) => Promise +>force : boolean | undefined + + refreshAll({ +>refreshAll({ ids: items.map(({ id }) => id), extra: 1, }) : Promise +>refreshAll : (options: { ids: string[]; extra: T; }) => Promise +>{ ids: items.map(({ id }) => id), extra: 1, } : { ids: string[]; extra: number; } + + ids: items.map(({ id }) => id), +>ids : string[] +>items.map(({ id }) => id) : string[] +>items.map : (callbackfn: (value: { id: string; }, index: number, array: { id: string; }[]) => U, thisArg?: any) => U[] +>items : { id: string; }[] +>map : (callbackfn: (value: { id: string; }, index: number, array: { id: string; }[]) => U, thisArg?: any) => U[] +>({ id }) => id : ({ id }: { id: string; }) => string +>id : string +>id : string + + extra: 1, +>extra : number +>1 : 1 + + })); + return { refresh }; +>{ refresh } : { refresh: (force?: boolean) => Promise; } +>refresh : (force?: boolean) => Promise +} + +=== yieldOperandCycle.ts === +interface Eff { + [Symbol.iterator](): Generator, A, any>; +>[Symbol.iterator] : () => Generator, A, any> +>Symbol.iterator : unique symbol +>Symbol : SymbolConstructor +>iterator : unique symbol + + pipe(f: (self: Eff) => B): B; +>pipe : (f: (self: Eff) => B) => B +>f : (self: Eff) => B +>self : Eff +} +declare function mapError(f: (e: E) => E2): (self: Eff) => Eff; +>mapError : (f: (e: E) => E2) => (self: Eff) => Eff +>f : (e: E) => E2 +>e : E +>self : Eff + +declare function fn(body: (input: number) => Generator): (input: number) => Eff; +>fn : (body: (input: number) => Generator) => (input: number) => Eff +>body : (input: number) => Generator +>input : number +>input : number + +declare function make(s: T): T; +>make : (s: T) => T +>s : T + +declare const uuid: Eff; +>uuid : Eff + +fn(function* (input) { +>fn(function* (input) { make(yield* uuid.pipe(mapError((cause) => ({ cause }))));}) : (input: number) => Eff +>fn : (body: (input: number) => Generator) => (input: number) => Eff +>function* (input) { make(yield* uuid.pipe(mapError((cause) => ({ cause }))));} : (input: number) => Generator, void, any> +>input : number + + make(yield* uuid.pipe(mapError((cause) => ({ cause })))); +>make(yield* uuid.pipe(mapError((cause) => ({ cause })))) : string +>make : (s: T) => T +>yield* uuid.pipe(mapError((cause) => ({ cause }))) : string +>uuid.pipe(mapError((cause) => ({ cause }))) : Eff +>uuid.pipe : (f: (self: Eff) => B) => B +>uuid : Eff +>pipe : (f: (self: Eff) => B) => B +>mapError((cause) => ({ cause })) : (self: Eff) => Eff +>mapError : (f: (e: E) => E2) => (self: Eff) => Eff +>(cause) => ({ cause }) : (cause: Error) => { cause: Error; } +>cause : Error +>({ cause }) : { cause: Error; } +>{ cause } : { cause: Error; } +>cause : Error + +}); + diff --git a/tsc/testdata/baselines/reference/compiler/contextualCheckReentryAfterParameters.symbols b/tsc/testdata/baselines/reference/compiler/contextualCheckReentryAfterParameters.symbols new file mode 100644 index 0000000000000..695dd85162edd --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contextualCheckReentryAfterParameters.symbols @@ -0,0 +1,181 @@ +//// [tests/cases/compiler/contextualCheckReentryAfterParameters.ts] //// + +=== contextualCheckReentryAfterParameters.ts === +// Instantiating the type predicate inside the nested callback re-enters the +// contextual check of the enclosing functions through their outer type +// parameters. That must not discard the parameter types assigned to them. + +type Surface = { id: string; kind: "file"; path: string } | { id: string; kind: "terminal" }; +>Surface : Symbol(Surface, Decl(contextualCheckReentryAfterParameters.ts, 0, 0)) +>id : Symbol(id, Decl(contextualCheckReentryAfterParameters.ts, 4, 16)) +>kind : Symbol(kind, Decl(contextualCheckReentryAfterParameters.ts, 4, 28)) +>path : Symbol(path, Decl(contextualCheckReentryAfterParameters.ts, 4, 42)) +>id : Symbol(id, Decl(contextualCheckReentryAfterParameters.ts, 4, 61)) +>kind : Symbol(kind, Decl(contextualCheckReentryAfterParameters.ts, 4, 73)) + +interface Panel { +>Panel : Symbol(Panel, Decl(contextualCheckReentryAfterParameters.ts, 4, 93)) + + surfaces: Surface[]; +>surfaces : Symbol(Panel.surfaces, Decl(contextualCheckReentryAfterParameters.ts, 5, 17)) +>Surface : Symbol(Surface, Decl(contextualCheckReentryAfterParameters.ts, 0, 0)) +} +interface Store { +>Store : Symbol(Store, Decl(contextualCheckReentryAfterParameters.ts, 7, 1)) + + panel: Panel; +>panel : Symbol(Store.panel, Decl(contextualCheckReentryAfterParameters.ts, 8, 17)) +>Panel : Symbol(Panel, Decl(contextualCheckReentryAfterParameters.ts, 4, 93)) + + openFile: (path: string) => void; +>openFile : Symbol(Store.openFile, Decl(contextualCheckReentryAfterParameters.ts, 9, 17)) +>path : Symbol(path, Decl(contextualCheckReentryAfterParameters.ts, 10, 15)) + + close: (id: string) => void; +>close : Symbol(Store.close, Decl(contextualCheckReentryAfterParameters.ts, 10, 37)) +>id : Symbol(id, Decl(contextualCheckReentryAfterParameters.ts, 11, 12)) +} +type Creator = (set: (update: (state: T) => Partial) => void) => T; +>Creator : Symbol(Creator, Decl(contextualCheckReentryAfterParameters.ts, 12, 1)) +>T : Symbol(T, Decl(contextualCheckReentryAfterParameters.ts, 13, 13)) +>set : Symbol(set, Decl(contextualCheckReentryAfterParameters.ts, 13, 19)) +>update : Symbol(update, Decl(contextualCheckReentryAfterParameters.ts, 13, 25)) +>state : Symbol(state, Decl(contextualCheckReentryAfterParameters.ts, 13, 34)) +>T : Symbol(T, Decl(contextualCheckReentryAfterParameters.ts, 13, 13)) +>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(contextualCheckReentryAfterParameters.ts, 13, 13)) +>T : Symbol(T, Decl(contextualCheckReentryAfterParameters.ts, 13, 13)) + +declare function create(init: Creator): T; +>create : Symbol(create, Decl(contextualCheckReentryAfterParameters.ts, 13, 73)) +>T : Symbol(T, Decl(contextualCheckReentryAfterParameters.ts, 14, 24)) +>init : Symbol(init, Decl(contextualCheckReentryAfterParameters.ts, 14, 27)) +>Creator : Symbol(Creator, Decl(contextualCheckReentryAfterParameters.ts, 12, 1)) +>T : Symbol(T, Decl(contextualCheckReentryAfterParameters.ts, 14, 24)) +>T : Symbol(T, Decl(contextualCheckReentryAfterParameters.ts, 14, 24)) + +declare function wrap(init: Creator): Creator; +>wrap : Symbol(wrap, Decl(contextualCheckReentryAfterParameters.ts, 14, 48)) +>T : Symbol(T, Decl(contextualCheckReentryAfterParameters.ts, 15, 22)) +>init : Symbol(init, Decl(contextualCheckReentryAfterParameters.ts, 15, 25)) +>Creator : Symbol(Creator, Decl(contextualCheckReentryAfterParameters.ts, 12, 1)) +>T : Symbol(T, Decl(contextualCheckReentryAfterParameters.ts, 15, 22)) +>Creator : Symbol(Creator, Decl(contextualCheckReentryAfterParameters.ts, 12, 1)) +>T : Symbol(T, Decl(contextualCheckReentryAfterParameters.ts, 15, 22)) + +const update = (state: Store, updater: (current: Panel) => Panel): Partial => ({ panel: updater(state.panel) }); +>update : Symbol(update, Decl(contextualCheckReentryAfterParameters.ts, 17, 5)) +>state : Symbol(state, Decl(contextualCheckReentryAfterParameters.ts, 17, 16)) +>Store : Symbol(Store, Decl(contextualCheckReentryAfterParameters.ts, 7, 1)) +>updater : Symbol(updater, Decl(contextualCheckReentryAfterParameters.ts, 17, 29)) +>current : Symbol(current, Decl(contextualCheckReentryAfterParameters.ts, 17, 40)) +>Panel : Symbol(Panel, Decl(contextualCheckReentryAfterParameters.ts, 4, 93)) +>Panel : Symbol(Panel, Decl(contextualCheckReentryAfterParameters.ts, 4, 93)) +>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --)) +>Store : Symbol(Store, Decl(contextualCheckReentryAfterParameters.ts, 7, 1)) +>panel : Symbol(panel, Decl(contextualCheckReentryAfterParameters.ts, 17, 87)) +>updater : Symbol(updater, Decl(contextualCheckReentryAfterParameters.ts, 17, 29)) +>state.panel : Symbol(Store.panel, Decl(contextualCheckReentryAfterParameters.ts, 8, 17)) +>state : Symbol(state, Decl(contextualCheckReentryAfterParameters.ts, 17, 16)) +>panel : Symbol(Store.panel, Decl(contextualCheckReentryAfterParameters.ts, 8, 17)) + +export const store = create(wrap((set) => ({ +>store : Symbol(store, Decl(contextualCheckReentryAfterParameters.ts, 19, 12)) +>create : Symbol(create, Decl(contextualCheckReentryAfterParameters.ts, 13, 73)) +>Store : Symbol(Store, Decl(contextualCheckReentryAfterParameters.ts, 7, 1)) +>wrap : Symbol(wrap, Decl(contextualCheckReentryAfterParameters.ts, 14, 48)) +>set : Symbol(set, Decl(contextualCheckReentryAfterParameters.ts, 19, 41)) + + panel: { surfaces: [] }, +>panel : Symbol(panel, Decl(contextualCheckReentryAfterParameters.ts, 19, 51)) +>surfaces : Symbol(surfaces, Decl(contextualCheckReentryAfterParameters.ts, 20, 12)) + + openFile: (path) => +>openFile : Symbol(openFile, Decl(contextualCheckReentryAfterParameters.ts, 20, 28)) +>path : Symbol(path, Decl(contextualCheckReentryAfterParameters.ts, 21, 15)) + + set((state) => +>set : Symbol(set, Decl(contextualCheckReentryAfterParameters.ts, 19, 41)) +>state : Symbol(state, Decl(contextualCheckReentryAfterParameters.ts, 22, 13)) + + update(state, (current) => { +>update : Symbol(update, Decl(contextualCheckReentryAfterParameters.ts, 17, 5)) +>state : Symbol(state, Decl(contextualCheckReentryAfterParameters.ts, 22, 13)) +>current : Symbol(current, Decl(contextualCheckReentryAfterParameters.ts, 23, 27)) + + const existing = current.surfaces.find( +>existing : Symbol(existing, Decl(contextualCheckReentryAfterParameters.ts, 24, 21)) +>current.surfaces.find : Symbol(Array.find, Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>current.surfaces : Symbol(Panel.surfaces, Decl(contextualCheckReentryAfterParameters.ts, 5, 17)) +>current : Symbol(current, Decl(contextualCheckReentryAfterParameters.ts, 23, 27)) +>surfaces : Symbol(Panel.surfaces, Decl(contextualCheckReentryAfterParameters.ts, 5, 17)) +>find : Symbol(Array.find, Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) + + (surface): surface is Extract => surface.kind === "file" && surface.path === path, +>surface : Symbol(surface, Decl(contextualCheckReentryAfterParameters.ts, 25, 21)) +>surface : Symbol(surface, Decl(contextualCheckReentryAfterParameters.ts, 25, 21)) +>Extract : Symbol(Extract, Decl(lib.es5.d.ts, --, --)) +>Surface : Symbol(Surface, Decl(contextualCheckReentryAfterParameters.ts, 0, 0)) +>kind : Symbol(kind, Decl(contextualCheckReentryAfterParameters.ts, 25, 60)) +>surface.kind : Symbol(kind, Decl(contextualCheckReentryAfterParameters.ts, 4, 28), Decl(contextualCheckReentryAfterParameters.ts, 4, 73)) +>surface : Symbol(surface, Decl(contextualCheckReentryAfterParameters.ts, 25, 21)) +>kind : Symbol(kind, Decl(contextualCheckReentryAfterParameters.ts, 4, 28), Decl(contextualCheckReentryAfterParameters.ts, 4, 73)) +>surface.path : Symbol(path, Decl(contextualCheckReentryAfterParameters.ts, 4, 42)) +>surface : Symbol(surface, Decl(contextualCheckReentryAfterParameters.ts, 25, 21)) +>path : Symbol(path, Decl(contextualCheckReentryAfterParameters.ts, 4, 42)) +>path : Symbol(path, Decl(contextualCheckReentryAfterParameters.ts, 21, 15)) + + ); + return existing ? current : { surfaces: [...current.surfaces, { id: path, kind: "file", path }] }; +>existing : Symbol(existing, Decl(contextualCheckReentryAfterParameters.ts, 24, 21)) +>current : Symbol(current, Decl(contextualCheckReentryAfterParameters.ts, 23, 27)) +>surfaces : Symbol(surfaces, Decl(contextualCheckReentryAfterParameters.ts, 27, 45)) +>current.surfaces : Symbol(Panel.surfaces, Decl(contextualCheckReentryAfterParameters.ts, 5, 17)) +>current : Symbol(current, Decl(contextualCheckReentryAfterParameters.ts, 23, 27)) +>surfaces : Symbol(Panel.surfaces, Decl(contextualCheckReentryAfterParameters.ts, 5, 17)) +>id : Symbol(id, Decl(contextualCheckReentryAfterParameters.ts, 27, 79)) +>path : Symbol(path, Decl(contextualCheckReentryAfterParameters.ts, 21, 15)) +>kind : Symbol(kind, Decl(contextualCheckReentryAfterParameters.ts, 27, 89)) +>path : Symbol(path, Decl(contextualCheckReentryAfterParameters.ts, 27, 103)) + + }) + ), + close: (id) => +>close : Symbol(close, Decl(contextualCheckReentryAfterParameters.ts, 29, 10)) +>id : Symbol(id, Decl(contextualCheckReentryAfterParameters.ts, 30, 12)) + + set((state) => +>set : Symbol(set, Decl(contextualCheckReentryAfterParameters.ts, 19, 41)) +>state : Symbol(state, Decl(contextualCheckReentryAfterParameters.ts, 31, 13)) + + update(state, (current) => { +>update : Symbol(update, Decl(contextualCheckReentryAfterParameters.ts, 17, 5)) +>state : Symbol(state, Decl(contextualCheckReentryAfterParameters.ts, 31, 13)) +>current : Symbol(current, Decl(contextualCheckReentryAfterParameters.ts, 32, 27)) + + const index = current.surfaces.findIndex((entry) => entry.id === id); +>index : Symbol(index, Decl(contextualCheckReentryAfterParameters.ts, 33, 21)) +>current.surfaces.findIndex : Symbol(Array.findIndex, Decl(lib.es2015.core.d.ts, --, --)) +>current.surfaces : Symbol(Panel.surfaces, Decl(contextualCheckReentryAfterParameters.ts, 5, 17)) +>current : Symbol(current, Decl(contextualCheckReentryAfterParameters.ts, 32, 27)) +>surfaces : Symbol(Panel.surfaces, Decl(contextualCheckReentryAfterParameters.ts, 5, 17)) +>findIndex : Symbol(Array.findIndex, Decl(lib.es2015.core.d.ts, --, --)) +>entry : Symbol(entry, Decl(contextualCheckReentryAfterParameters.ts, 33, 58)) +>entry.id : Symbol(id, Decl(contextualCheckReentryAfterParameters.ts, 4, 16), Decl(contextualCheckReentryAfterParameters.ts, 4, 61)) +>entry : Symbol(entry, Decl(contextualCheckReentryAfterParameters.ts, 33, 58)) +>id : Symbol(id, Decl(contextualCheckReentryAfterParameters.ts, 4, 16), Decl(contextualCheckReentryAfterParameters.ts, 4, 61)) +>id : Symbol(id, Decl(contextualCheckReentryAfterParameters.ts, 30, 12)) + + return { surfaces: current.surfaces.slice(index) }; +>surfaces : Symbol(surfaces, Decl(contextualCheckReentryAfterParameters.ts, 34, 24)) +>current.surfaces.slice : Symbol(Array.slice, Decl(lib.es5.d.ts, --, --)) +>current.surfaces : Symbol(Panel.surfaces, Decl(contextualCheckReentryAfterParameters.ts, 5, 17)) +>current : Symbol(current, Decl(contextualCheckReentryAfterParameters.ts, 32, 27)) +>surfaces : Symbol(Panel.surfaces, Decl(contextualCheckReentryAfterParameters.ts, 5, 17)) +>slice : Symbol(Array.slice, Decl(lib.es5.d.ts, --, --)) +>index : Symbol(index, Decl(contextualCheckReentryAfterParameters.ts, 33, 21)) + + }) + ), +}))); + diff --git a/tsc/testdata/baselines/reference/compiler/contextualCheckReentryAfterParameters.types b/tsc/testdata/baselines/reference/compiler/contextualCheckReentryAfterParameters.types new file mode 100644 index 0000000000000..0fcb1000bcab9 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contextualCheckReentryAfterParameters.types @@ -0,0 +1,190 @@ +//// [tests/cases/compiler/contextualCheckReentryAfterParameters.ts] //// + +=== contextualCheckReentryAfterParameters.ts === +// Instantiating the type predicate inside the nested callback re-enters the +// contextual check of the enclosing functions through their outer type +// parameters. That must not discard the parameter types assigned to them. + +type Surface = { id: string; kind: "file"; path: string } | { id: string; kind: "terminal" }; +>Surface : Surface +>id : string +>kind : "file" +>path : string +>id : string +>kind : "terminal" + +interface Panel { + surfaces: Surface[]; +>surfaces : Surface[] +} +interface Store { + panel: Panel; +>panel : Panel + + openFile: (path: string) => void; +>openFile : (path: string) => void +>path : string + + close: (id: string) => void; +>close : (id: string) => void +>id : string +} +type Creator = (set: (update: (state: T) => Partial) => void) => T; +>Creator : Creator +>set : (update: (state: T) => Partial) => void +>update : (state: T) => Partial +>state : T + +declare function create(init: Creator): T; +>create : (init: Creator) => T +>init : Creator + +declare function wrap(init: Creator): Creator; +>wrap : (init: Creator) => Creator +>init : Creator + +const update = (state: Store, updater: (current: Panel) => Panel): Partial => ({ panel: updater(state.panel) }); +>update : (state: Store, updater: (current: Panel) => Panel) => Partial +>(state: Store, updater: (current: Panel) => Panel): Partial => ({ panel: updater(state.panel) }) : (state: Store, updater: (current: Panel) => Panel) => Partial +>state : Store +>updater : (current: Panel) => Panel +>current : Panel +>({ panel: updater(state.panel) }) : { panel: Panel; } +>{ panel: updater(state.panel) } : { panel: Panel; } +>panel : Panel +>updater(state.panel) : Panel +>updater : (current: Panel) => Panel +>state.panel : Panel +>state : Store +>panel : Panel + +export const store = create(wrap((set) => ({ +>store : Store +>create(wrap((set) => ({ panel: { surfaces: [] }, openFile: (path) => set((state) => update(state, (current) => { const existing = current.surfaces.find( (surface): surface is Extract => surface.kind === "file" && surface.path === path, ); return existing ? current : { surfaces: [...current.surfaces, { id: path, kind: "file", path }] }; }) ), close: (id) => set((state) => update(state, (current) => { const index = current.surfaces.findIndex((entry) => entry.id === id); return { surfaces: current.surfaces.slice(index) }; }) ),}))) : Store +>create : (init: Creator) => T +>wrap((set) => ({ panel: { surfaces: [] }, openFile: (path) => set((state) => update(state, (current) => { const existing = current.surfaces.find( (surface): surface is Extract => surface.kind === "file" && surface.path === path, ); return existing ? current : { surfaces: [...current.surfaces, { id: path, kind: "file", path }] }; }) ), close: (id) => set((state) => update(state, (current) => { const index = current.surfaces.findIndex((entry) => entry.id === id); return { surfaces: current.surfaces.slice(index) }; }) ),})) : Creator +>wrap : (init: Creator) => Creator +>(set) => ({ panel: { surfaces: [] }, openFile: (path) => set((state) => update(state, (current) => { const existing = current.surfaces.find( (surface): surface is Extract => surface.kind === "file" && surface.path === path, ); return existing ? current : { surfaces: [...current.surfaces, { id: path, kind: "file", path }] }; }) ), close: (id) => set((state) => update(state, (current) => { const index = current.surfaces.findIndex((entry) => entry.id === id); return { surfaces: current.surfaces.slice(index) }; }) ),}) : (set: (update: (state: Store) => Partial) => void) => { panel: { surfaces: never[]; }; openFile: (path: string) => void; close: (id: string) => void; } +>set : (update: (state: Store) => Partial) => void +>({ panel: { surfaces: [] }, openFile: (path) => set((state) => update(state, (current) => { const existing = current.surfaces.find( (surface): surface is Extract => surface.kind === "file" && surface.path === path, ); return existing ? current : { surfaces: [...current.surfaces, { id: path, kind: "file", path }] }; }) ), close: (id) => set((state) => update(state, (current) => { const index = current.surfaces.findIndex((entry) => entry.id === id); return { surfaces: current.surfaces.slice(index) }; }) ),}) : { panel: { surfaces: never[]; }; openFile: (path: string) => void; close: (id: string) => void; } +>{ panel: { surfaces: [] }, openFile: (path) => set((state) => update(state, (current) => { const existing = current.surfaces.find( (surface): surface is Extract => surface.kind === "file" && surface.path === path, ); return existing ? current : { surfaces: [...current.surfaces, { id: path, kind: "file", path }] }; }) ), close: (id) => set((state) => update(state, (current) => { const index = current.surfaces.findIndex((entry) => entry.id === id); return { surfaces: current.surfaces.slice(index) }; }) ),} : { panel: { surfaces: never[]; }; openFile: (path: string) => void; close: (id: string) => void; } + + panel: { surfaces: [] }, +>panel : { surfaces: never[]; } +>{ surfaces: [] } : { surfaces: never[]; } +>surfaces : never[] +>[] : never[] + + openFile: (path) => +>openFile : (path: string) => void +>(path) => set((state) => update(state, (current) => { const existing = current.surfaces.find( (surface): surface is Extract => surface.kind === "file" && surface.path === path, ); return existing ? current : { surfaces: [...current.surfaces, { id: path, kind: "file", path }] }; }) ) : (path: string) => void +>path : string + + set((state) => +>set((state) => update(state, (current) => { const existing = current.surfaces.find( (surface): surface is Extract => surface.kind === "file" && surface.path === path, ); return existing ? current : { surfaces: [...current.surfaces, { id: path, kind: "file", path }] }; }) ) : void +>set : (update: (state: Store) => Partial) => void +>(state) => update(state, (current) => { const existing = current.surfaces.find( (surface): surface is Extract => surface.kind === "file" && surface.path === path, ); return existing ? current : { surfaces: [...current.surfaces, { id: path, kind: "file", path }] }; }) : (state: Store) => Partial +>state : Store + + update(state, (current) => { +>update(state, (current) => { const existing = current.surfaces.find( (surface): surface is Extract => surface.kind === "file" && surface.path === path, ); return existing ? current : { surfaces: [...current.surfaces, { id: path, kind: "file", path }] }; }) : Partial +>update : (state: Store, updater: (current: Panel) => Panel) => Partial +>state : Store +>(current) => { const existing = current.surfaces.find( (surface): surface is Extract => surface.kind === "file" && surface.path === path, ); return existing ? current : { surfaces: [...current.surfaces, { id: path, kind: "file", path }] }; } : (current: Panel) => Panel +>current : Panel + + const existing = current.surfaces.find( +>existing : { id: string; kind: "file"; path: string; } | undefined +>current.surfaces.find( (surface): surface is Extract => surface.kind === "file" && surface.path === path, ) : { id: string; kind: "file"; path: string; } | undefined +>current.surfaces.find : { (predicate: (value: Surface, index: number, obj: Surface[]) => value is S, thisArg?: any): S | undefined; (predicate: (value: Surface, index: number, obj: Surface[]) => unknown, thisArg?: any): Surface | undefined; } +>current.surfaces : Surface[] +>current : Panel +>surfaces : Surface[] +>find : { (predicate: (value: Surface, index: number, obj: Surface[]) => value is S, thisArg?: any): S | undefined; (predicate: (value: Surface, index: number, obj: Surface[]) => unknown, thisArg?: any): Surface | undefined; } + + (surface): surface is Extract => surface.kind === "file" && surface.path === path, +>(surface): surface is Extract => surface.kind === "file" && surface.path === path : (surface: Surface) => surface is Extract +>surface : Surface +>kind : "file" +>surface.kind === "file" && surface.path === path : boolean +>surface.kind === "file" : boolean +>surface.kind : "file" | "terminal" +>surface : Surface +>kind : "file" | "terminal" +>"file" : "file" +>surface.path === path : boolean +>surface.path : string +>surface : { id: string; kind: "file"; path: string; } +>path : string +>path : string + + ); + return existing ? current : { surfaces: [...current.surfaces, { id: path, kind: "file", path }] }; +>existing ? current : { surfaces: [...current.surfaces, { id: path, kind: "file", path }] } : Panel +>existing : { id: string; kind: "file"; path: string; } | undefined +>current : Panel +>{ surfaces: [...current.surfaces, { id: path, kind: "file", path }] } : { surfaces: Surface[]; } +>surfaces : Surface[] +>[...current.surfaces, { id: path, kind: "file", path }] : Surface[] +>...current.surfaces : Surface +>current.surfaces : Surface[] +>current : Panel +>surfaces : Surface[] +>{ id: path, kind: "file", path } : { id: string; kind: "file"; path: string; } +>id : string +>path : string +>kind : "file" +>"file" : "file" +>path : string + + }) + ), + close: (id) => +>close : (id: string) => void +>(id) => set((state) => update(state, (current) => { const index = current.surfaces.findIndex((entry) => entry.id === id); return { surfaces: current.surfaces.slice(index) }; }) ) : (id: string) => void +>id : string + + set((state) => +>set((state) => update(state, (current) => { const index = current.surfaces.findIndex((entry) => entry.id === id); return { surfaces: current.surfaces.slice(index) }; }) ) : void +>set : (update: (state: Store) => Partial) => void +>(state) => update(state, (current) => { const index = current.surfaces.findIndex((entry) => entry.id === id); return { surfaces: current.surfaces.slice(index) }; }) : (state: Store) => Partial +>state : Store + + update(state, (current) => { +>update(state, (current) => { const index = current.surfaces.findIndex((entry) => entry.id === id); return { surfaces: current.surfaces.slice(index) }; }) : Partial +>update : (state: Store, updater: (current: Panel) => Panel) => Partial +>state : Store +>(current) => { const index = current.surfaces.findIndex((entry) => entry.id === id); return { surfaces: current.surfaces.slice(index) }; } : (current: Panel) => { surfaces: Surface[]; } +>current : Panel + + const index = current.surfaces.findIndex((entry) => entry.id === id); +>index : number +>current.surfaces.findIndex((entry) => entry.id === id) : number +>current.surfaces.findIndex : (predicate: (value: Surface, index: number, obj: Surface[]) => unknown, thisArg?: any) => number +>current.surfaces : Surface[] +>current : Panel +>surfaces : Surface[] +>findIndex : (predicate: (value: Surface, index: number, obj: Surface[]) => unknown, thisArg?: any) => number +>(entry) => entry.id === id : (entry: Surface) => boolean +>entry : Surface +>entry.id === id : boolean +>entry.id : string +>entry : Surface +>id : string +>id : string + + return { surfaces: current.surfaces.slice(index) }; +>{ surfaces: current.surfaces.slice(index) } : { surfaces: Surface[]; } +>surfaces : Surface[] +>current.surfaces.slice(index) : Surface[] +>current.surfaces.slice : (start?: number, end?: number) => Surface[] +>current.surfaces : Surface[] +>current : Panel +>surfaces : Surface[] +>slice : (start?: number, end?: number) => Surface[] +>index : number + + }) + ), +}))); + diff --git a/tsc/testdata/baselines/reference/compiler/contextualReturnTypeInSpreadArgument.js b/tsc/testdata/baselines/reference/compiler/contextualReturnTypeInSpreadArgument.js new file mode 100644 index 0000000000000..ca1d3b91449da --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contextualReturnTypeInSpreadArgument.js @@ -0,0 +1,36 @@ +//// [tests/cases/compiler/contextualReturnTypeInSpreadArgument.ts] //// + +//// [contextualReturnTypeInSpreadArgument.ts] +enum Kind { A, B } + +interface ItemA { kind: Kind.A; name: string } +interface ItemB { kind: Kind.B; size: number } +type Item = ItemA | ItemB; + +declare function create(options: { items?: () => readonly Item[]; label: string }): void; + +function test(flag: boolean) { + create({ + label: "x", + ...(flag ? {} : { + items: () => [{ kind: Kind.A, name: "a" }], + }), + }); +} + + +//// [contextualReturnTypeInSpreadArgument.js] +"use strict"; +var Kind; +(function (Kind) { + Kind[Kind["A"] = 0] = "A"; + Kind[Kind["B"] = 1] = "B"; +})(Kind || (Kind = {})); +function test(flag) { + create({ + label: "x", + ...(flag ? {} : { + items: () => [{ kind: Kind.A, name: "a" }], + }), + }); +} diff --git a/tsc/testdata/baselines/reference/compiler/contextualReturnTypeInSpreadArgument.symbols b/tsc/testdata/baselines/reference/compiler/contextualReturnTypeInSpreadArgument.symbols new file mode 100644 index 0000000000000..0802a21d1a19b --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contextualReturnTypeInSpreadArgument.symbols @@ -0,0 +1,59 @@ +//// [tests/cases/compiler/contextualReturnTypeInSpreadArgument.ts] //// + +=== contextualReturnTypeInSpreadArgument.ts === +enum Kind { A, B } +>Kind : Symbol(Kind, Decl(contextualReturnTypeInSpreadArgument.ts, 0, 0)) +>A : Symbol(Kind.A, Decl(contextualReturnTypeInSpreadArgument.ts, 0, 11)) +>B : Symbol(Kind.B, Decl(contextualReturnTypeInSpreadArgument.ts, 0, 14)) + +interface ItemA { kind: Kind.A; name: string } +>ItemA : Symbol(ItemA, Decl(contextualReturnTypeInSpreadArgument.ts, 0, 18)) +>kind : Symbol(ItemA.kind, Decl(contextualReturnTypeInSpreadArgument.ts, 2, 17)) +>Kind : Symbol(Kind, Decl(contextualReturnTypeInSpreadArgument.ts, 0, 0)) +>A : Symbol(Kind.A, Decl(contextualReturnTypeInSpreadArgument.ts, 0, 11)) +>name : Symbol(ItemA.name, Decl(contextualReturnTypeInSpreadArgument.ts, 2, 31)) + +interface ItemB { kind: Kind.B; size: number } +>ItemB : Symbol(ItemB, Decl(contextualReturnTypeInSpreadArgument.ts, 2, 46)) +>kind : Symbol(ItemB.kind, Decl(contextualReturnTypeInSpreadArgument.ts, 3, 17)) +>Kind : Symbol(Kind, Decl(contextualReturnTypeInSpreadArgument.ts, 0, 0)) +>B : Symbol(Kind.B, Decl(contextualReturnTypeInSpreadArgument.ts, 0, 14)) +>size : Symbol(ItemB.size, Decl(contextualReturnTypeInSpreadArgument.ts, 3, 31)) + +type Item = ItemA | ItemB; +>Item : Symbol(Item, Decl(contextualReturnTypeInSpreadArgument.ts, 3, 46)) +>ItemA : Symbol(ItemA, Decl(contextualReturnTypeInSpreadArgument.ts, 0, 18)) +>ItemB : Symbol(ItemB, Decl(contextualReturnTypeInSpreadArgument.ts, 2, 46)) + +declare function create(options: { items?: () => readonly Item[]; label: string }): void; +>create : Symbol(create, Decl(contextualReturnTypeInSpreadArgument.ts, 4, 26)) +>options : Symbol(options, Decl(contextualReturnTypeInSpreadArgument.ts, 6, 24)) +>items : Symbol(items, Decl(contextualReturnTypeInSpreadArgument.ts, 6, 34)) +>Item : Symbol(Item, Decl(contextualReturnTypeInSpreadArgument.ts, 3, 46)) +>label : Symbol(label, Decl(contextualReturnTypeInSpreadArgument.ts, 6, 65)) + +function test(flag: boolean) { +>test : Symbol(test, Decl(contextualReturnTypeInSpreadArgument.ts, 6, 89)) +>flag : Symbol(flag, Decl(contextualReturnTypeInSpreadArgument.ts, 8, 14)) + + create({ +>create : Symbol(create, Decl(contextualReturnTypeInSpreadArgument.ts, 4, 26)) + + label: "x", +>label : Symbol(label, Decl(contextualReturnTypeInSpreadArgument.ts, 9, 12)) + + ...(flag ? {} : { +>flag : Symbol(flag, Decl(contextualReturnTypeInSpreadArgument.ts, 8, 14)) + + items: () => [{ kind: Kind.A, name: "a" }], +>items : Symbol(items, Decl(contextualReturnTypeInSpreadArgument.ts, 11, 25)) +>kind : Symbol(kind, Decl(contextualReturnTypeInSpreadArgument.ts, 12, 27)) +>Kind.A : Symbol(Kind.A, Decl(contextualReturnTypeInSpreadArgument.ts, 0, 11)) +>Kind : Symbol(Kind, Decl(contextualReturnTypeInSpreadArgument.ts, 0, 0)) +>A : Symbol(Kind.A, Decl(contextualReturnTypeInSpreadArgument.ts, 0, 11)) +>name : Symbol(name, Decl(contextualReturnTypeInSpreadArgument.ts, 12, 41)) + + }), + }); +} + diff --git a/tsc/testdata/baselines/reference/compiler/contextualReturnTypeInSpreadArgument.types b/tsc/testdata/baselines/reference/compiler/contextualReturnTypeInSpreadArgument.types new file mode 100644 index 0000000000000..6c1780590bad8 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contextualReturnTypeInSpreadArgument.types @@ -0,0 +1,63 @@ +//// [tests/cases/compiler/contextualReturnTypeInSpreadArgument.ts] //// + +=== contextualReturnTypeInSpreadArgument.ts === +enum Kind { A, B } +>Kind : Kind +>A : Kind.A +>B : Kind.B + +interface ItemA { kind: Kind.A; name: string } +>kind : Kind.A +>Kind : any +>name : string + +interface ItemB { kind: Kind.B; size: number } +>kind : Kind.B +>Kind : any +>size : number + +type Item = ItemA | ItemB; +>Item : Item + +declare function create(options: { items?: () => readonly Item[]; label: string }): void; +>create : (options: { items?: () => readonly Item[]; label: string; }) => void +>options : { items?: () => readonly Item[]; label: string; } +>items : (() => readonly Item[]) | undefined +>label : string + +function test(flag: boolean) { +>test : (flag: boolean) => void +>flag : boolean + + create({ +>create({ label: "x", ...(flag ? {} : { items: () => [{ kind: Kind.A, name: "a" }], }), }) : void +>create : (options: { items?: () => readonly Item[]; label: string; }) => void +>{ label: "x", ...(flag ? {} : { items: () => [{ kind: Kind.A, name: "a" }], }), } : { label: string; items?: (() => { kind: Kind.A; name: string; }[]) | undefined; } + + label: "x", +>label : string +>"x" : "x" + + ...(flag ? {} : { +>(flag ? {} : { items: () => [{ kind: Kind.A, name: "a" }], }) : {} | { items: () => { kind: Kind.A; name: string; }[]; } +>flag ? {} : { items: () => [{ kind: Kind.A, name: "a" }], } : {} | { items: () => { kind: Kind.A; name: string; }[]; } +>flag : boolean +>{} : {} +>{ items: () => [{ kind: Kind.A, name: "a" }], } : { items: () => { kind: Kind.A; name: string; }[]; } + + items: () => [{ kind: Kind.A, name: "a" }], +>items : () => { kind: Kind.A; name: string; }[] +>() => [{ kind: Kind.A, name: "a" }] : () => { kind: Kind.A; name: string; }[] +>[{ kind: Kind.A, name: "a" }] : { kind: Kind.A; name: string; }[] +>{ kind: Kind.A, name: "a" } : { kind: Kind.A; name: string; } +>kind : Kind.A +>Kind.A : Kind.A +>Kind : typeof Kind +>A : Kind.A +>name : string +>"a" : "a" + + }), + }); +} + diff --git a/tsc/testdata/baselines/reference/compiler/declarationEmitExternalModuleContainerIndexDeclarationOrder.js b/tsc/testdata/baselines/reference/compiler/declarationEmitExternalModuleContainerIndexDeclarationOrder.js index 935e9baeed1ad..c43a610ffef9d 100644 --- a/tsc/testdata/baselines/reference/compiler/declarationEmitExternalModuleContainerIndexDeclarationOrder.js +++ b/tsc/testdata/baselines/reference/compiler/declarationEmitExternalModuleContainerIndexDeclarationOrder.js @@ -77,5 +77,7 @@ export declare const obsA: { v: 0; }; export declare const obsB: { + pa: 0; + readonly ma: 1; v: 0; }; diff --git a/tsc/testdata/baselines/reference/compiler/declarationEmitExternalModuleContainerIndexDeclarationOrder.types b/tsc/testdata/baselines/reference/compiler/declarationEmitExternalModuleContainerIndexDeclarationOrder.types index f7cb243d2a23e..ed5fe02f28302 100644 --- a/tsc/testdata/baselines/reference/compiler/declarationEmitExternalModuleContainerIndexDeclarationOrder.types +++ b/tsc/testdata/baselines/reference/compiler/declarationEmitExternalModuleContainerIndexDeclarationOrder.types @@ -26,8 +26,8 @@ export const obsA = expand(); >expand : () => { [K in keyof X]: X[K]; } export const obsB = expand(); ->obsB : { v: 0; } ->expand() : { v: 0; } +>obsB : { pa: 0; readonly ma: 1; v: 0; } +>expand() : { pa: 0; readonly ma: 1; v: 0; } >expand : () => { [K in keyof X]: X[K]; } === /ambient.d.ts === diff --git a/tsc/testdata/baselines/reference/compiler/excessivelyLargeTupleSpread(checkorder=typesfirst).errors.txt b/tsc/testdata/baselines/reference/compiler/excessivelyLargeTupleSpread(checkorder=typesfirst).errors.txt index 5bb90d57b8a95..6ec6fc6629d8a 100644 --- a/tsc/testdata/baselines/reference/compiler/excessivelyLargeTupleSpread(checkorder=typesfirst).errors.txt +++ b/tsc/testdata/baselines/reference/compiler/excessivelyLargeTupleSpread(checkorder=typesfirst).errors.txt @@ -1,15 +1,17 @@ -error TS2800: Expression produces a tuple type that is too large to represent. +excessivelyLargeTupleSpread.ts(6,10): error TS2799: Type produces a tuple type that is too large to represent. +excessivelyLargeTupleSpread.ts(22,12): error TS2799: Type produces a tuple type that is too large to represent. excessivelyLargeTupleSpread.ts(38,13): error TS2800: Expression produces a tuple type that is too large to represent. -!!! error TS2800: Expression produces a tuple type that is too large to represent. -==== excessivelyLargeTupleSpread.ts (1 errors) ==== +==== excessivelyLargeTupleSpread.ts (3 errors) ==== // #41771 type BuildTuple = T['length'] extends L ? T : BuildTuple; type A = BuildTuple<3> + ~~~~~~~~~~~~~ +!!! error TS2799: Type produces a tuple type that is too large to represent. type T0 = [any]; type T1 = [...T0, ...T0]; @@ -26,6 +28,8 @@ excessivelyLargeTupleSpread.ts(38,13): error TS2800: Expression produces a tuple type T12 = [...T11, ...T11]; type T13 = [...T12, ...T12]; type T14 = [...T13, ...T13]; // 2^14 > 10,000 + ~~~~~~~~~~~~~~~~ +!!! error TS2799: Type produces a tuple type that is too large to represent. const a0 = [0] as const; const a1 = [...a0, ...a0] as const; diff --git a/tsc/testdata/baselines/reference/compiler/incorrectRecursiveMappedTypeConstraint.errors.txt b/tsc/testdata/baselines/reference/compiler/incorrectRecursiveMappedTypeConstraint.errors.txt index 41f682e455f8d..4fc215b0dd52d 100644 --- a/tsc/testdata/baselines/reference/compiler/incorrectRecursiveMappedTypeConstraint.errors.txt +++ b/tsc/testdata/baselines/reference/compiler/incorrectRecursiveMappedTypeConstraint.errors.txt @@ -8,10 +8,8 @@ incorrectRecursiveMappedTypeConstraint.ts(3,5): error TS2365: Operator '+=' cann function sum(n: number, v: T, k: K) { ~~~~~~~~~~~~~~~~~~~~ !!! error TS2313: Type parameter 'T' has a circular constraint. -!!! related TS2751 incorrectRecursiveMappedTypeConstraint.ts:3:10: Circularity originates in type at this location. ~ !!! error TS2313: Type parameter 'P' has a circular constraint. -!!! related TS2751 incorrectRecursiveMappedTypeConstraint.ts:3:10: Circularity originates in type at this location. n += v[k]; ~~~~~~~~~ !!! error TS2365: Operator '+=' cannot be applied to types 'number' and 'T[K]'. diff --git a/tsc/testdata/baselines/reference/compiler/jsExpandoIndexedAssignmentDiagnostic.errors.txt b/tsc/testdata/baselines/reference/compiler/jsExpandoIndexedAssignmentDiagnostic.errors.txt new file mode 100644 index 0000000000000..108d7440f9df4 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/jsExpandoIndexedAssignmentDiagnostic.errors.txt @@ -0,0 +1,25 @@ +file.js(4,11): error TS7022: 'pixel' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. +file.js(4,19): error TS7053: Element implicitly has an 'any' type because expression of type 'any' can't be used to index type '{ skip: any; }'. +file.js(5,11): error TS7022: 'otherPixel' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. +file.js(5,24): error TS7053: Element implicitly has an 'any' type because expression of type 'any' can't be used to index type '{ skip: any; }'. +file.js(6,5): error TS7022: 'skip' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. + + +==== file.js (5 errors) ==== + /** @param {any} axis @param {any} value */ + function updateElements(axis, value) { + const properties = {}; + const pixel = properties[axis] = value; + ~~~~~ +!!! error TS7022: 'pixel' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. + ~~~~~~~~~~~~~~~~ +!!! error TS7053: Element implicitly has an 'any' type because expression of type 'any' can't be used to index type '{ skip: any; }'. + const otherPixel = properties[axis] = value; + ~~~~~~~~~~ +!!! error TS7022: 'otherPixel' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. + ~~~~~~~~~~~~~~~~ +!!! error TS7053: Element implicitly has an 'any' type because expression of type 'any' can't be used to index type '{ skip: any; }'. + properties.skip = isNaN(pixel) || isNaN(otherPixel); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS7022: 'skip' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. + } \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/jsExpandoIndexedAssignmentDiagnostic.js b/tsc/testdata/baselines/reference/compiler/jsExpandoIndexedAssignmentDiagnostic.js new file mode 100644 index 0000000000000..d660e1f2146fc --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/jsExpandoIndexedAssignmentDiagnostic.js @@ -0,0 +1,20 @@ +//// [tests/cases/compiler/jsExpandoIndexedAssignmentDiagnostic.ts] //// + +//// [file.js] +/** @param {any} axis @param {any} value */ +function updateElements(axis, value) { + const properties = {}; + const pixel = properties[axis] = value; + const otherPixel = properties[axis] = value; + properties.skip = isNaN(pixel) || isNaN(otherPixel); +} + +//// [file.js] +"use strict"; +/** @param {any} axis @param {any} value */ +function updateElements(axis, value) { + const properties = {}; + const pixel = properties[axis] = value; + const otherPixel = properties[axis] = value; + properties.skip = isNaN(pixel) || isNaN(otherPixel); +} diff --git a/tsc/testdata/baselines/reference/compiler/jsExpandoIndexedAssignmentDiagnostic.symbols b/tsc/testdata/baselines/reference/compiler/jsExpandoIndexedAssignmentDiagnostic.symbols new file mode 100644 index 0000000000000..373963a684ab3 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/jsExpandoIndexedAssignmentDiagnostic.symbols @@ -0,0 +1,33 @@ +//// [tests/cases/compiler/jsExpandoIndexedAssignmentDiagnostic.ts] //// + +=== file.js === +/** @param {any} axis @param {any} value */ +function updateElements(axis, value) { +>updateElements : Symbol(updateElements, Decl(file.js, 0, 0)) +>axis : Symbol(axis, Decl(file.js, 1, 24)) +>value : Symbol(value, Decl(file.js, 1, 29)) + + const properties = {}; +>properties : Symbol(properties, Decl(file.js, 2, 9)) + + const pixel = properties[axis] = value; +>pixel : Symbol(pixel, Decl(file.js, 3, 9)) +>properties : Symbol(properties, Decl(file.js, 2, 9)) +>axis : Symbol(axis, Decl(file.js, 1, 24)) +>value : Symbol(value, Decl(file.js, 1, 29)) + + const otherPixel = properties[axis] = value; +>otherPixel : Symbol(otherPixel, Decl(file.js, 4, 9)) +>properties : Symbol(properties, Decl(file.js, 2, 9)) +>axis : Symbol(axis, Decl(file.js, 1, 24)) +>value : Symbol(value, Decl(file.js, 1, 29)) + + properties.skip = isNaN(pixel) || isNaN(otherPixel); +>properties.skip : Symbol(skip, Decl(file.js, 4, 48)) +>properties : Symbol(properties, Decl(file.js, 2, 9)) +>skip : Symbol(skip, Decl(file.js, 4, 48)) +>isNaN : Symbol(isNaN, Decl(lib.es5.d.ts, --, --)) +>pixel : Symbol(pixel, Decl(file.js, 3, 9)) +>isNaN : Symbol(isNaN, Decl(lib.es5.d.ts, --, --)) +>otherPixel : Symbol(otherPixel, Decl(file.js, 4, 9)) +} diff --git a/tsc/testdata/baselines/reference/compiler/jsExpandoIndexedAssignmentDiagnostic.types b/tsc/testdata/baselines/reference/compiler/jsExpandoIndexedAssignmentDiagnostic.types new file mode 100644 index 0000000000000..607a8ed056027 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/jsExpandoIndexedAssignmentDiagnostic.types @@ -0,0 +1,42 @@ +//// [tests/cases/compiler/jsExpandoIndexedAssignmentDiagnostic.ts] //// + +=== file.js === +/** @param {any} axis @param {any} value */ +function updateElements(axis, value) { +>updateElements : (axis: any, value: any) => void +>axis : any +>value : any + + const properties = {}; +>properties : { skip: any; } +>{} : { skip: any; } + + const pixel = properties[axis] = value; +>pixel : any +>properties[axis] = value : any +>properties[axis] : any +>properties : { skip: any; } +>axis : any +>value : any + + const otherPixel = properties[axis] = value; +>otherPixel : any +>properties[axis] = value : any +>properties[axis] : any +>properties : { skip: any; } +>axis : any +>value : any + + properties.skip = isNaN(pixel) || isNaN(otherPixel); +>properties.skip = isNaN(pixel) || isNaN(otherPixel) : boolean +>properties.skip : any +>properties : { skip: any; } +>skip : any +>isNaN(pixel) || isNaN(otherPixel) : boolean +>isNaN(pixel) : boolean +>isNaN : (number: number) => boolean +>pixel : any +>isNaN(otherPixel) : boolean +>isNaN : (number: number) => boolean +>otherPixel : any +} diff --git a/tsc/testdata/baselines/reference/compiler/jsxImportSourceNonPragmaComment(checkorder=typesfirst).errors.txt b/tsc/testdata/baselines/reference/compiler/jsxImportSourceNonPragmaComment(checkorder=typesfirst).errors.txt index b45ab387a625b..191dc50e4166c 100644 --- a/tsc/testdata/baselines/reference/compiler/jsxImportSourceNonPragmaComment(checkorder=typesfirst).errors.txt +++ b/tsc/testdata/baselines/reference/compiler/jsxImportSourceNonPragmaComment(checkorder=typesfirst).errors.txt @@ -1,10 +1,9 @@ jsxImportSourceNonPragmaComment.tsx(3,21): error TS2307: Cannot find module '@emotion/react' or its corresponding type declarations. jsxImportSourceNonPragmaComment.tsx(7,5): error TS2875: This JSX tag requires the module path '@emotion/react/jsx-runtime' to exist, but none could be found. Make sure you have types for the appropriate package installed. jsxImportSourceNonPragmaComment.tsx(7,5): error TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists. -jsxImportSourceNonPragmaComment.tsx(7,6): error TS2552: Cannot find name 'input'. Did you mean 'oninput'? -==== jsxImportSourceNonPragmaComment.tsx (4 errors) ==== +==== jsxImportSourceNonPragmaComment.tsx (3 errors) ==== /* eslint-disable react/react-in-jsx-scope -- Unaware of @jsxImportSource */ /** @jsxImportSource @emotion/react */ import { css } from "@emotion/react"; @@ -16,9 +15,6 @@ jsxImportSourceNonPragmaComment.tsx(7,6): error TS2552: Cannot find name 'input' {} - } - - ; - ~ -!!! error TS2304: Cannot find name 'a'. - \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/jsxIntrinsicElementsExtendsRecord(checkorder=typesfirst).types b/tsc/testdata/baselines/reference/compiler/jsxIntrinsicElementsExtendsRecord(checkorder=typesfirst).types index a2a76b6e77eed..30632f23d7dc4 100644 --- a/tsc/testdata/baselines/reference/compiler/jsxIntrinsicElementsExtendsRecord(checkorder=typesfirst).types +++ b/tsc/testdata/baselines/reference/compiler/jsxIntrinsicElementsExtendsRecord(checkorder=typesfirst).types @@ -6,6 +6,6 @@ declare namespace JSX { } ; -> : any +> : error >a : any diff --git a/tsc/testdata/baselines/reference/compiler/keyofGenericExtendingClassDoubleLayer.errors.txt b/tsc/testdata/baselines/reference/compiler/keyofGenericExtendingClassDoubleLayer.errors.txt index 9808b4f1f9122..5babffc1080a2 100644 --- a/tsc/testdata/baselines/reference/compiler/keyofGenericExtendingClassDoubleLayer.errors.txt +++ b/tsc/testdata/baselines/reference/compiler/keyofGenericExtendingClassDoubleLayer.errors.txt @@ -1,9 +1,10 @@ keyofGenericExtendingClassDoubleLayer.ts(2,12): error TS2564: Property 'createdAt' has no initializer and is not definitely assigned in the constructor. keyofGenericExtendingClassDoubleLayer.ts(9,7): error TS5115: Instantiations of the following types appear infinitely circular: 'Model', 'ModelAttributes', 'Exclude'. keyofGenericExtendingClassDoubleLayer.ts(10,12): error TS2564: Property 'age' has no initializer and is not definitely assigned in the constructor. +keyofGenericExtendingClassDoubleLayer.ts(13,13): error TS5115: Instantiations of the following types appear infinitely circular: 'Model', 'ModelAttributes', 'Exclude'. -==== keyofGenericExtendingClassDoubleLayer.ts (3 errors) ==== +==== keyofGenericExtendingClassDoubleLayer.ts (4 errors) ==== class Model { public createdAt: Date; ~~~~~~~~~ @@ -23,6 +24,8 @@ keyofGenericExtendingClassDoubleLayer.ts(10,12): error TS2564: Property 'age' ha toJson() { let x: keyof this = 'createdAt'; + ~ +!!! error TS5115: Instantiations of the following types appear infinitely circular: 'Model', 'ModelAttributes', 'Exclude'. } } \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/lateBoundMutualMemberTables.errors.txt b/tsc/testdata/baselines/reference/compiler/lateBoundMutualMemberTables.errors.txt new file mode 100644 index 0000000000000..c1b01f83c3c86 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/lateBoundMutualMemberTables.errors.txt @@ -0,0 +1,26 @@ +lateBoundMutualMemberTables.ts(6,8): error TS2339: Property 'by' does not exist on type 'B<"b">'. +lateBoundMutualMemberTables.ts(15,21): error TS2339: Property 'fromA2' does not exist on type 'A<"a">'. + + +==== lateBoundMutualMemberTables.ts (2 errors) ==== + declare const a: A<"a">; + declare const b: B<"b">; + declare class A { + x: T; + [b.y]: "fromB"; + [b.by]: "fromB2"; + ~~ +!!! error TS2339: Property 'by' does not exist on type 'B<"b">'. + } + declare class B { + y: T; + [a.x]: "fromA"; + [a.b]: "fromA2"; + } + export const v1 = a.b; + export const v2 = b.a; + export const v3 = a.fromA2; + ~~~~~~ +!!! error TS2339: Property 'fromA2' does not exist on type 'A<"a">'. + export const v4 = b.fromB; + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/lateBoundMutualMemberTables.js b/tsc/testdata/baselines/reference/compiler/lateBoundMutualMemberTables.js new file mode 100644 index 0000000000000..aea4e1ea51859 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/lateBoundMutualMemberTables.js @@ -0,0 +1,33 @@ +//// [tests/cases/compiler/lateBoundMutualMemberTables.ts] //// + +//// [lateBoundMutualMemberTables.ts] +declare const a: A<"a">; +declare const b: B<"b">; +declare class A { + x: T; + [b.y]: "fromB"; + [b.by]: "fromB2"; +} +declare class B { + y: T; + [a.x]: "fromA"; + [a.b]: "fromA2"; +} +export const v1 = a.b; +export const v2 = b.a; +export const v3 = a.fromA2; +export const v4 = b.fromB; + + +//// [lateBoundMutualMemberTables.js] +export const v1 = a.b; +export const v2 = b.a; +export const v3 = a.fromA2; +export const v4 = b.fromB; + + +//// [lateBoundMutualMemberTables.d.ts] +export declare const v1: "fromB"; +export declare const v2: "fromA"; +export declare const v3: any; +export declare const v4: "fromA2"; diff --git a/tsc/testdata/baselines/reference/compiler/lateBoundMutualMemberTables.symbols b/tsc/testdata/baselines/reference/compiler/lateBoundMutualMemberTables.symbols new file mode 100644 index 0000000000000..24eba3a29f260 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/lateBoundMutualMemberTables.symbols @@ -0,0 +1,71 @@ +//// [tests/cases/compiler/lateBoundMutualMemberTables.ts] //// + +=== lateBoundMutualMemberTables.ts === +declare const a: A<"a">; +>a : Symbol(a, Decl(lateBoundMutualMemberTables.ts, 0, 13)) +>A : Symbol(A, Decl(lateBoundMutualMemberTables.ts, 1, 24)) + +declare const b: B<"b">; +>b : Symbol(b, Decl(lateBoundMutualMemberTables.ts, 1, 13)) +>B : Symbol(B, Decl(lateBoundMutualMemberTables.ts, 6, 1)) + +declare class A { +>A : Symbol(A, Decl(lateBoundMutualMemberTables.ts, 1, 24)) +>T : Symbol(T, Decl(lateBoundMutualMemberTables.ts, 2, 16)) + + x: T; +>x : Symbol(A.x, Decl(lateBoundMutualMemberTables.ts, 2, 20)) +>T : Symbol(T, Decl(lateBoundMutualMemberTables.ts, 2, 16)) + + [b.y]: "fromB"; +>[b.y] : Symbol(A[b.y], Decl(lateBoundMutualMemberTables.ts, 3, 9)) +>b.y : Symbol(B.y, Decl(lateBoundMutualMemberTables.ts, 7, 20)) +>b : Symbol(b, Decl(lateBoundMutualMemberTables.ts, 1, 13)) +>y : Symbol(B.y, Decl(lateBoundMutualMemberTables.ts, 7, 20)) + + [b.by]: "fromB2"; +>[b.by] : Symbol(A[b.by], Decl(lateBoundMutualMemberTables.ts, 4, 19)) +>b : Symbol(b, Decl(lateBoundMutualMemberTables.ts, 1, 13)) +} +declare class B { +>B : Symbol(B, Decl(lateBoundMutualMemberTables.ts, 6, 1)) +>T : Symbol(T, Decl(lateBoundMutualMemberTables.ts, 7, 16)) + + y: T; +>y : Symbol(B.y, Decl(lateBoundMutualMemberTables.ts, 7, 20)) +>T : Symbol(T, Decl(lateBoundMutualMemberTables.ts, 7, 16)) + + [a.x]: "fromA"; +>[a.x] : Symbol(B[a.x], Decl(lateBoundMutualMemberTables.ts, 8, 9)) +>a.x : Symbol(A.x, Decl(lateBoundMutualMemberTables.ts, 2, 20)) +>a : Symbol(a, Decl(lateBoundMutualMemberTables.ts, 0, 13)) +>x : Symbol(A.x, Decl(lateBoundMutualMemberTables.ts, 2, 20)) + + [a.b]: "fromA2"; +>[a.b] : Symbol(B[a.b], Decl(lateBoundMutualMemberTables.ts, 9, 19)) +>a.b : Symbol(A[b.y], Decl(lateBoundMutualMemberTables.ts, 3, 9)) +>a : Symbol(a, Decl(lateBoundMutualMemberTables.ts, 0, 13)) +>b : Symbol(A[b.y], Decl(lateBoundMutualMemberTables.ts, 3, 9)) +} +export const v1 = a.b; +>v1 : Symbol(v1, Decl(lateBoundMutualMemberTables.ts, 12, 12)) +>a.b : Symbol(A[b.y], Decl(lateBoundMutualMemberTables.ts, 3, 9)) +>a : Symbol(a, Decl(lateBoundMutualMemberTables.ts, 0, 13)) +>b : Symbol(A[b.y], Decl(lateBoundMutualMemberTables.ts, 3, 9)) + +export const v2 = b.a; +>v2 : Symbol(v2, Decl(lateBoundMutualMemberTables.ts, 13, 12)) +>b.a : Symbol(B[a.x], Decl(lateBoundMutualMemberTables.ts, 8, 9)) +>b : Symbol(b, Decl(lateBoundMutualMemberTables.ts, 1, 13)) +>a : Symbol(B[a.x], Decl(lateBoundMutualMemberTables.ts, 8, 9)) + +export const v3 = a.fromA2; +>v3 : Symbol(v3, Decl(lateBoundMutualMemberTables.ts, 14, 12)) +>a : Symbol(a, Decl(lateBoundMutualMemberTables.ts, 0, 13)) + +export const v4 = b.fromB; +>v4 : Symbol(v4, Decl(lateBoundMutualMemberTables.ts, 15, 12)) +>b.fromB : Symbol(B[a.b], Decl(lateBoundMutualMemberTables.ts, 9, 19)) +>b : Symbol(b, Decl(lateBoundMutualMemberTables.ts, 1, 13)) +>fromB : Symbol(B[a.b], Decl(lateBoundMutualMemberTables.ts, 9, 19)) + diff --git a/tsc/testdata/baselines/reference/compiler/lateBoundMutualMemberTables.types b/tsc/testdata/baselines/reference/compiler/lateBoundMutualMemberTables.types new file mode 100644 index 0000000000000..24f60ac31fc98 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/lateBoundMutualMemberTables.types @@ -0,0 +1,69 @@ +//// [tests/cases/compiler/lateBoundMutualMemberTables.ts] //// + +=== lateBoundMutualMemberTables.ts === +declare const a: A<"a">; +>a : A<"a"> + +declare const b: B<"b">; +>b : B<"b"> + +declare class A { +>A : A + + x: T; +>x : T + + [b.y]: "fromB"; +>[b.y] : "fromB" +>b.y : "b" +>b : B<"b"> +>y : "b" + + [b.by]: "fromB2"; +>[b.by] : "fromB2" +>b.by : any +>b : B<"b"> +>by : any +} +declare class B { +>B : B + + y: T; +>y : T + + [a.x]: "fromA"; +>[a.x] : "fromA" +>a.x : "a" +>a : A<"a"> +>x : "a" + + [a.b]: "fromA2"; +>[a.b] : "fromA2" +>a.b : "fromB" +>a : A<"a"> +>b : "fromB" +} +export const v1 = a.b; +>v1 : "fromB" +>a.b : "fromB" +>a : A<"a"> +>b : "fromB" + +export const v2 = b.a; +>v2 : "fromA" +>b.a : "fromA" +>b : B<"b"> +>a : "fromA" + +export const v3 = a.fromA2; +>v3 : any +>a.fromA2 : any +>a : A<"a"> +>fromA2 : any + +export const v4 = b.fromB; +>v4 : "fromA2" +>b.fromB : "fromA2" +>b : B<"b"> +>fromB : "fromA2" + diff --git a/tsc/testdata/baselines/reference/compiler/limitDeepInstantiations(checkorder=default).errors.txt b/tsc/testdata/baselines/reference/compiler/limitDeepInstantiations(checkorder=default).errors.txt index a1b033edf54ba..b908bb32df049 100644 --- a/tsc/testdata/baselines/reference/compiler/limitDeepInstantiations(checkorder=default).errors.txt +++ b/tsc/testdata/baselines/reference/compiler/limitDeepInstantiations(checkorder=default).errors.txt @@ -1,12 +1,15 @@ +limitDeepInstantiations.ts(4,5): error TS5114: Instantiations of type 'Foo' appear infinitely circular. limitDeepInstantiations.ts(4,9): error TS5114: Instantiations of type 'Foo' appear infinitely circular. limitDeepInstantiations.ts(5,13): error TS2344: Type '"false"' does not satisfy the constraint '"true"'. -==== limitDeepInstantiations.ts (2 errors) ==== +==== limitDeepInstantiations.ts (3 errors) ==== // Repro from #14837 type Foo = { "true": Foo> }[T]; let f1: Foo<"true", {}>; + ~~ +!!! error TS5114: Instantiations of type 'Foo' appear infinitely circular. ~~~~~~~~~~~~~~~ !!! error TS5114: Instantiations of type 'Foo' appear infinitely circular. let f2: Foo<"false", {}>; diff --git a/tsc/testdata/baselines/reference/compiler/limitDeepInstantiations(checkorder=typesfirst).errors.txt b/tsc/testdata/baselines/reference/compiler/limitDeepInstantiations(checkorder=typesfirst).errors.txt index 2998e94608985..b908bb32df049 100644 --- a/tsc/testdata/baselines/reference/compiler/limitDeepInstantiations(checkorder=typesfirst).errors.txt +++ b/tsc/testdata/baselines/reference/compiler/limitDeepInstantiations(checkorder=typesfirst).errors.txt @@ -1,13 +1,17 @@ -error TS5114: Instantiations of type 'Foo' appear infinitely circular. +limitDeepInstantiations.ts(4,5): error TS5114: Instantiations of type 'Foo' appear infinitely circular. +limitDeepInstantiations.ts(4,9): error TS5114: Instantiations of type 'Foo' appear infinitely circular. limitDeepInstantiations.ts(5,13): error TS2344: Type '"false"' does not satisfy the constraint '"true"'. -!!! error TS5114: Instantiations of type 'Foo' appear infinitely circular. -==== limitDeepInstantiations.ts (1 errors) ==== +==== limitDeepInstantiations.ts (3 errors) ==== // Repro from #14837 type Foo = { "true": Foo> }[T]; let f1: Foo<"true", {}>; + ~~ +!!! error TS5114: Instantiations of type 'Foo' appear infinitely circular. + ~~~~~~~~~~~~~~~ +!!! error TS5114: Instantiations of type 'Foo' appear infinitely circular. let f2: Foo<"false", {}>; ~~~~~~~ !!! error TS2344: Type '"false"' does not satisfy the constraint '"true"'. diff --git a/tsc/testdata/baselines/reference/compiler/namespaceMembersResolutionOrderExportStar.errors.txt b/tsc/testdata/baselines/reference/compiler/namespaceMembersResolutionOrderExportStar.errors.txt index f9e0d6ab4eac5..bff4827ef673c 100644 --- a/tsc/testdata/baselines/reference/compiler/namespaceMembersResolutionOrderExportStar.errors.txt +++ b/tsc/testdata/baselines/reference/compiler/namespaceMembersResolutionOrderExportStar.errors.txt @@ -1,4 +1,4 @@ -/main.ts(7,32): error TS2339: Property 'mb' does not exist on type '{}'. +/main.ts(8,32): error TS2339: Property 'ma' does not exist on type '{}'. ==== /main.ts (1 errors) ==== @@ -9,9 +9,9 @@ export const fromA = MA.pa; export const fromB = MB.pb; export const checkA: 1 = MA.pa.mb; - ~~ -!!! error TS2339: Property 'mb' does not exist on type '{}'. export const checkB: 1 = MB.pb.ma; + ~~ +!!! error TS2339: Property 'ma' does not exist on type '{}'. ==== /ma.js (0 errors) ==== export { v as pa } from "./xa"; diff --git a/tsc/testdata/baselines/reference/compiler/namespaceMembersResolutionOrderExportStar.js b/tsc/testdata/baselines/reference/compiler/namespaceMembersResolutionOrderExportStar.js index b99218d5bfd65..e7f345491f402 100644 --- a/tsc/testdata/baselines/reference/compiler/namespaceMembersResolutionOrderExportStar.js +++ b/tsc/testdata/baselines/reference/compiler/namespaceMembersResolutionOrderExportStar.js @@ -185,7 +185,10 @@ exports.checkB = MB.pb.ma; //// [xa.d.ts] export = o; declare const o: { - v: {}; + v: { + pb: {}; + mb: 1; + }; }; //// [ma.d.ts] export { v as pa } from "./xa"; @@ -193,10 +196,7 @@ export declare const ma = 1; //// [xb.d.ts] export = o; declare const o: { - v: { - pa: {}; - ma: 1; - }; + v: {}; }; //// [mb.d.ts] export { v as pb } from "./xb"; @@ -205,10 +205,7 @@ export declare const mb = 1; export = o; declare const o: { v: { - pb: { - pa: {}; - ma: 1; - }; + pb: {}; mb: 1; }; }; @@ -216,7 +213,10 @@ declare const o: { export = o; declare const o: { v: { - pa: {}; + pa: { + pb: {}; + mb: 1; + }; ma: 1; }; }; @@ -236,10 +236,10 @@ export declare const f8 = 8; export declare const f9 = 9; export declare const f10 = 10; //// [main.d.ts] -export declare const fromA: {}; -export declare const fromB: { - pa: {}; - ma: 1; +export declare const fromA: { + pb: {}; + mb: 1; }; +export declare const fromB: {}; export declare const checkA: 1; export declare const checkB: 1; diff --git a/tsc/testdata/baselines/reference/compiler/namespaceMembersResolutionOrderExportStar.symbols b/tsc/testdata/baselines/reference/compiler/namespaceMembersResolutionOrderExportStar.symbols index 4f7c5a9b9f1d4..6997e4d5a273d 100644 --- a/tsc/testdata/baselines/reference/compiler/namespaceMembersResolutionOrderExportStar.symbols +++ b/tsc/testdata/baselines/reference/compiler/namespaceMembersResolutionOrderExportStar.symbols @@ -28,17 +28,17 @@ export const fromB = MB.pb; export const checkA: 1 = MA.pa.mb; >checkA : Symbol(checkA, Decl(main.ts, 6, 12)) +>MA.pa.mb : Symbol(mb, Decl(mb.js, 1, 12)) >MA.pa : Symbol(MA.pa, Decl(ma.js, 0, 8)) >MA : Symbol(MA, Decl(main.ts, 2, 6)) >pa : Symbol(MA.pa, Decl(ma.js, 0, 8)) +>mb : Symbol(mb, Decl(mb.js, 1, 12)) export const checkB: 1 = MB.pb.ma; >checkB : Symbol(checkB, Decl(main.ts, 7, 12)) ->MB.pb.ma : Symbol(ma, Decl(ma.js, 1, 12)) >MB.pb : Symbol(MB.pb, Decl(mb.js, 0, 8)) >MB : Symbol(MB, Decl(main.ts, 3, 6)) >pb : Symbol(MB.pb, Decl(mb.js, 0, 8)) ->ma : Symbol(ma, Decl(ma.js, 1, 12)) === /ma.js === export { v as pa } from "./xa"; diff --git a/tsc/testdata/baselines/reference/compiler/namespaceMembersResolutionOrderExportStar.types b/tsc/testdata/baselines/reference/compiler/namespaceMembersResolutionOrderExportStar.types index 0153880cec8d7..49f1de0b3ac44 100644 --- a/tsc/testdata/baselines/reference/compiler/namespaceMembersResolutionOrderExportStar.types +++ b/tsc/testdata/baselines/reference/compiler/namespaceMembersResolutionOrderExportStar.types @@ -5,8 +5,8 @@ import * as idx from "./index"; >idx : typeof idx const all = { ...idx }; ->all : { a: { pa: {}; ma: 1; }; b: { pb: { pa: {}; ma: 1; }; mb: 1; }; f1: 1; f2: 2; f3: 3; f4: 4; f5: 5; f6: 6; f7: 7; f8: 8; f9: 9; f10: 10; } ->{ ...idx } : { a: { pa: {}; ma: 1; }; b: { pb: { pa: {}; ma: 1; }; mb: 1; }; f1: 1; f2: 2; f3: 3; f4: 4; f5: 5; f6: 6; f7: 7; f8: 8; f9: 9; f10: 10; } +>all : { a: { pa: { pb: {}; mb: 1; }; ma: 1; }; b: { pb: {}; mb: 1; }; f1: 1; f2: 2; f3: 3; f4: 4; f5: 5; f6: 6; f7: 7; f8: 8; f9: 9; f10: 10; } +>{ ...idx } : { a: { pa: { pb: {}; mb: 1; }; ma: 1; }; b: { pb: {}; mb: 1; }; f1: 1; f2: 2; f3: 3; f4: 4; f5: 5; f6: 6; f7: 7; f8: 8; f9: 9; f10: 10; } >idx : typeof idx import * as MA from "./ma"; @@ -16,37 +16,37 @@ import * as MB from "./mb"; >MB : typeof MB export const fromA = MA.pa; ->fromA : {} ->MA.pa : {} +>fromA : { pb: {}; mb: 1; } +>MA.pa : { pb: {}; mb: 1; } >MA : typeof MA ->pa : {} +>pa : { pb: {}; mb: 1; } export const fromB = MB.pb; ->fromB : { pa: {}; ma: 1; } ->MB.pb : { pa: {}; ma: 1; } +>fromB : {} +>MB.pb : {} >MB : typeof MB ->pb : { pa: {}; ma: 1; } +>pb : {} export const checkA: 1 = MA.pa.mb; >checkA : 1 ->MA.pa.mb : any ->MA.pa : {} +>MA.pa.mb : 1 +>MA.pa : { pb: {}; mb: 1; } >MA : typeof MA ->pa : {} ->mb : any +>pa : { pb: {}; mb: 1; } +>mb : 1 export const checkB: 1 = MB.pb.ma; >checkB : 1 ->MB.pb.ma : 1 ->MB.pb : { pa: {}; ma: 1; } +>MB.pb.ma : any +>MB.pb : {} >MB : typeof MB ->pb : { pa: {}; ma: 1; } ->ma : 1 +>pb : {} +>ma : any === /ma.js === export { v as pa } from "./xa"; ->v : {} ->pa : {} +>v : { pb: {}; mb: 1; } +>pa : { pb: {}; mb: 1; } export const ma = 1; >ma : 1 @@ -54,8 +54,8 @@ export const ma = 1; === /mb.js === export { v as pb } from "./xb"; ->v : { pa: {}; ma: 1; } ->pb : { pa: {}; ma: 1; } +>v : {} +>pb : {} export const mb = 1; >mb : 1 @@ -68,18 +68,18 @@ const M = /** @type {any} */ (null); >(null) : any const o = { v: { ...M } }; ->o : { v: {}; } ->{ v: { ...M } } : { v: {}; } ->v : {} ->{ ...M } : {} +>o : { v: { pb: {}; mb: 1; }; } +>{ v: { ...M } } : { v: { pb: {}; mb: 1; }; } +>v : { pb: {}; mb: 1; } +>{ ...M } : { pb: {}; mb: 1; } >M : typeof import("./mb") module.exports = o; ->module.exports = o : { v: {}; } ->module.exports : { v: {}; } ->module : { exports: { v: {}; }; } ->exports : { v: {}; } ->o : { v: {}; } +>module.exports = o : { v: { pb: {}; mb: 1; }; } +>module.exports : { v: { pb: {}; mb: 1; }; } +>module : { exports: { v: { pb: {}; mb: 1; }; }; } +>exports : { v: { pb: {}; mb: 1; }; } +>o : { v: { pb: {}; mb: 1; }; } === /xb.js === /** @type {typeof import("./ma")} */ @@ -88,18 +88,18 @@ const M = /** @type {any} */ (null); >(null) : any const o = { v: { ...M } }; ->o : { v: { pa: {}; ma: 1; }; } ->{ v: { ...M } } : { v: { pa: {}; ma: 1; }; } ->v : { pa: {}; ma: 1; } ->{ ...M } : { pa: {}; ma: 1; } +>o : { v: {}; } +>{ v: { ...M } } : { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; } +>v : { pa: { pb: {}; mb: 1; }; ma: 1; } +>{ ...M } : { pa: { pb: {}; mb: 1; }; ma: 1; } >M : typeof import("./ma") module.exports = o; ->module.exports = o : { v: { pa: {}; ma: 1; }; } ->module.exports : { v: { pa: {}; ma: 1; }; } ->module : { exports: { v: { pa: {}; ma: 1; }; }; } ->exports : { v: { pa: {}; ma: 1; }; } ->o : { v: { pa: {}; ma: 1; }; } +>module.exports = o : { v: {}; } +>module.exports : { v: {}; } +>module : { exports: { v: {}; }; } +>exports : { v: {}; } +>o : { v: {}; } === /ia.js === /** @type {typeof import("./ma")} */ @@ -108,18 +108,18 @@ const M = /** @type {any} */ (null); >(null) : any const o = { v: { ...M } }; ->o : { v: { pa: {}; ma: 1; }; } ->{ v: { ...M } } : { v: { pa: {}; ma: 1; }; } ->v : { pa: {}; ma: 1; } ->{ ...M } : { pa: {}; ma: 1; } +>o : { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; } +>{ v: { ...M } } : { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; } +>v : { pa: { pb: {}; mb: 1; }; ma: 1; } +>{ ...M } : { pa: { pb: {}; mb: 1; }; ma: 1; } >M : typeof import("./ma") module.exports = o; ->module.exports = o : { v: { pa: {}; ma: 1; }; } ->module.exports : { v: { pa: {}; ma: 1; }; } ->module : { exports: { v: { pa: {}; ma: 1; }; }; } ->exports : { v: { pa: {}; ma: 1; }; } ->o : { v: { pa: {}; ma: 1; }; } +>module.exports = o : { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; } +>module.exports : { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; } +>module : { exports: { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; }; } +>exports : { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; } +>o : { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; } === /ib.js === /** @type {typeof import("./mb")} */ @@ -128,28 +128,28 @@ const M = /** @type {any} */ (null); >(null) : any const o = { v: { ...M } }; ->o : { v: { pb: { pa: {}; ma: 1; }; mb: 1; }; } ->{ v: { ...M } } : { v: { pb: { pa: {}; ma: 1; }; mb: 1; }; } ->v : { pb: { pa: {}; ma: 1; }; mb: 1; } ->{ ...M } : { pb: { pa: {}; ma: 1; }; mb: 1; } +>o : { v: { pb: {}; mb: 1; }; } +>{ v: { ...M } } : { v: { pb: {}; mb: 1; }; } +>v : { pb: {}; mb: 1; } +>{ ...M } : { pb: {}; mb: 1; } >M : typeof import("./mb") module.exports = o; ->module.exports = o : { v: { pb: { pa: {}; ma: 1; }; mb: 1; }; } ->module.exports : { v: { pb: { pa: {}; ma: 1; }; mb: 1; }; } ->module : { exports: { v: { pb: { pa: {}; ma: 1; }; mb: 1; }; }; } ->exports : { v: { pb: { pa: {}; ma: 1; }; mb: 1; }; } ->o : { v: { pb: { pa: {}; ma: 1; }; mb: 1; }; } +>module.exports = o : { v: { pb: {}; mb: 1; }; } +>module.exports : { v: { pb: {}; mb: 1; }; } +>module : { exports: { v: { pb: {}; mb: 1; }; }; } +>exports : { v: { pb: {}; mb: 1; }; } +>o : { v: { pb: {}; mb: 1; }; } === /star.js === export { v as a } from "./ia"; ->v : { pa: {}; ma: 1; } ->a : { pa: {}; ma: 1; } +>v : { pa: { pb: {}; mb: 1; }; ma: 1; } +>a : { pa: { pb: {}; mb: 1; }; ma: 1; } === /index.js === export { v as b } from "./ib"; ->v : { pb: { pa: {}; ma: 1; }; mb: 1; } ->b : { pb: { pa: {}; ma: 1; }; mb: 1; } +>v : { pb: {}; mb: 1; } +>b : { pb: {}; mb: 1; } export * from "./star"; export const f1 = 1; diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitExportEqualsQualifiedNameOrder.js b/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitExportEqualsQualifiedNameOrder.js index 2413425ee0027..613464e8a9ce9 100644 --- a/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitExportEqualsQualifiedNameOrder.js +++ b/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitExportEqualsQualifiedNameOrder.js @@ -78,5 +78,7 @@ export declare const obsA: { v: 0; }; export declare const obsB: { + pa: 0; + readonly ma: 1; v: 0; }; diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitExportEqualsQualifiedNameOrder.types b/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitExportEqualsQualifiedNameOrder.types index 6e3734d808ac2..d2fb088ff22ab 100644 --- a/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitExportEqualsQualifiedNameOrder.types +++ b/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitExportEqualsQualifiedNameOrder.types @@ -22,8 +22,8 @@ export const obsA = expand(); >oxa : O export const obsB = expand(); ->obsB : { v: 0; } ->expand() : { v: 0; } +>obsB : { pa: 0; readonly ma: 1; v: 0; } +>expand() : { pa: 0; readonly ma: 1; v: 0; } >expand : () => { [K in keyof X]: X[K]; } >oxb : O diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitExternalModuleContainerIndexOrder.js b/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitExternalModuleContainerIndexOrder.js index b04d7f8d88c86..06233f3cd3615 100644 --- a/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitExternalModuleContainerIndexOrder.js +++ b/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitExternalModuleContainerIndexOrder.js @@ -77,5 +77,7 @@ export declare const obsA: { v: 0; }; export declare const obsB: { + pa: 0; + readonly ma: 1; v: 0; }; diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitExternalModuleContainerIndexOrder.types b/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitExternalModuleContainerIndexOrder.types index 351c0f5869478..236cd6a2af6c0 100644 --- a/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitExternalModuleContainerIndexOrder.types +++ b/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitExternalModuleContainerIndexOrder.types @@ -26,8 +26,8 @@ export const obsA = expand(); >expand : () => { [K in keyof X]: X[K]; } export const obsB = expand(); ->obsB : { v: 0; } ->expand() : { v: 0; } +>obsB : { pa: 0; readonly ma: 1; v: 0; } +>expand() : { pa: 0; readonly ma: 1; v: 0; } >expand : () => { [K in keyof X]: X[K]; } === /ambient.d.ts === diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitLateBoundIndexSignatureSiblingsOrder.js b/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitLateBoundIndexSignatureSiblingsOrder.js index da41b8a0dc439..09f23b9e441c9 100644 --- a/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitLateBoundIndexSignatureSiblingsOrder.js +++ b/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitLateBoundIndexSignatureSiblingsOrder.js @@ -90,6 +90,8 @@ export declare const obsA: { v: 0; }; export declare const obsB: { + pa: 0; + readonly ma: 1; v: 0; }; export {}; diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitLateBoundIndexSignatureSiblingsOrder.types b/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitLateBoundIndexSignatureSiblingsOrder.types index 9ca145132f072..a3e841f76db76 100644 --- a/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitLateBoundIndexSignatureSiblingsOrder.types +++ b/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitLateBoundIndexSignatureSiblingsOrder.types @@ -82,8 +82,8 @@ export const obsA = expand(); >oxa : O export const obsB = expand(); ->obsB : { v: 0; } ->expand() : { v: 0; } +>obsB : { pa: 0; readonly ma: 1; v: 0; } +>expand() : { pa: 0; readonly ma: 1; v: 0; } >expand : () => { [K in keyof X]: X[K]; } >oxb : O diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitRenamedExportOrder.js b/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitRenamedExportOrder.js index ea5bdabd550b2..593f5c6192e79 100644 --- a/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitRenamedExportOrder.js +++ b/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitRenamedExportOrder.js @@ -75,5 +75,7 @@ export declare const obsA: { v: 0; }; export declare const obsB: { + pa: 0; + readonly ma: 1; v: 0; }; diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitRenamedExportOrder.types b/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitRenamedExportOrder.types index ad400806b641d..59c394c4943b9 100644 --- a/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitRenamedExportOrder.types +++ b/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitRenamedExportOrder.types @@ -21,8 +21,8 @@ export const obsA = expand(); >oxa : O export const obsB = expand(); ->obsB : { v: 0; } ->expand() : { v: 0; } +>obsB : { pa: 0; readonly ma: 1; v: 0; } +>expand() : { pa: 0; readonly ma: 1; v: 0; } >expand : () => { [K in keyof X]: X[K]; } >oxb : O diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitSharedCheckerOrder.types b/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitSharedCheckerOrder.types index 07b1f23ab8990..cde44436bf6e0 100644 --- a/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitSharedCheckerOrder.types +++ b/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitSharedCheckerOrder.types @@ -26,9 +26,9 @@ const M = /** @type {any} */ (null); const o = { v: { ...M } }; >o : { v: {}; } ->{ v: { ...M } } : { v: {}; } ->v : {} ->{ ...M } : {} +>{ v: { ...M } } : { v: { pb: { pa: {}; ma: 1; }; mb: 1; }; } +>v : { pb: { pa: {}; ma: 1; }; mb: 1; } +>{ ...M } : { pb: { pa: {}; ma: 1; }; mb: 1; } >M : typeof import("./mb") module.exports = o; diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitUmdGlobalAliasesOrder.js b/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitUmdGlobalAliasesOrder.js index f297255bfad65..0cd47efb71bf9 100644 --- a/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitUmdGlobalAliasesOrder.js +++ b/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitUmdGlobalAliasesOrder.js @@ -139,5 +139,7 @@ declare const obsA: { v: 0; }; declare const obsB: { + pa: 0; + readonly ma: 1; v: 0; }; diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitUmdGlobalAliasesOrder.types b/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitUmdGlobalAliasesOrder.types index cb6e79ef1518c..64131b7405d04 100644 --- a/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitUmdGlobalAliasesOrder.types +++ b/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitUmdGlobalAliasesOrder.types @@ -34,8 +34,8 @@ const obsA = expand(); >oxa : O const obsB = expand(); ->obsB : { v: 0; } ->expand() : { v: 0; } +>obsB : { pa: 0; readonly ma: 1; v: 0; } +>expand() : { pa: 0; readonly ma: 1; v: 0; } >expand : () => { [K in keyof X]: X[K]; } >oxb : O diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitVariableMatchesInScopeOrder.js b/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitVariableMatchesInScopeOrder.js index 30a76df171c66..b4b3b1191b09c 100644 --- a/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitVariableMatchesInScopeOrder.js +++ b/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitVariableMatchesInScopeOrder.js @@ -55,5 +55,7 @@ export declare const obsA: { v: 0; }; export declare const obsB: { + pa: 0; + readonly ma: 1; v: 0; }; diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitVariableMatchesInScopeOrder.types b/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitVariableMatchesInScopeOrder.types index 2e41e20ad32be..74c433948a034 100644 --- a/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitVariableMatchesInScopeOrder.types +++ b/tsc/testdata/baselines/reference/compiler/noFlakyDeclarationEmitVariableMatchesInScopeOrder.types @@ -19,8 +19,8 @@ export const obsA = expand(); >oxa : O export const obsB = expand(); ->obsB : { v: 0; } ->expand() : { v: 0; } +>obsB : { pa: 0; readonly ma: 1; v: 0; } +>expand() : { pa: 0; readonly ma: 1; v: 0; } >expand : () => { [K in keyof X]: X[K]; } >oxb : O diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsExportStarCollisionOrder.errors.txt b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsExportStarCollisionOrder.errors.txt index a2ecba905fe18..62a7166294745 100644 --- a/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsExportStarCollisionOrder.errors.txt +++ b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsExportStarCollisionOrder.errors.txt @@ -1,5 +1,5 @@ /main.ts(7,14): error TS2322: Type '{ pb: 0; readonly mb: 1; v: 0; }' is not assignable to type 'never'. -/main.ts(8,14): error TS2322: Type '{ v: 0; }' is not assignable to type 'never'. +/main.ts(8,14): error TS2322: Type '{ pa: 0; readonly ma: 1; v: 0; }' is not assignable to type 'never'. ==== /main.ts (2 errors) ==== @@ -14,7 +14,7 @@ !!! error TS2322: Type '{ pb: 0; readonly mb: 1; v: 0; }' is not assignable to type 'never'. export const obsB: never = expand(); ~~~~ -!!! error TS2322: Type '{ v: 0; }' is not assignable to type 'never'. +!!! error TS2322: Type '{ pa: 0; readonly ma: 1; v: 0; }' is not assignable to type 'never'. ==== /ambient.d.ts (0 errors) ==== declare module "ma" { diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsExportStarCollisionOrder.types b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsExportStarCollisionOrder.types index 925b972b915c6..b42d11f662f49 100644 --- a/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsExportStarCollisionOrder.types +++ b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsExportStarCollisionOrder.types @@ -26,7 +26,7 @@ export const obsA: never = expand(); export const obsB: never = expand(); >obsB : never ->expand() : { v: 0; } +>expand() : { pa: 0; readonly ma: 1; v: 0; } >expand : () => { [K in keyof X]: X[K]; } >oxb : O diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsExportStarDeclarationCollisionOrder.errors.txt b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsExportStarDeclarationCollisionOrder.errors.txt index a05bda29acf17..45ba492a4b355 100644 --- a/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsExportStarDeclarationCollisionOrder.errors.txt +++ b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsExportStarDeclarationCollisionOrder.errors.txt @@ -1,5 +1,5 @@ /main.ts(7,14): error TS2322: Type '{ pb: 0; readonly mb: 1; v: 0; }' is not assignable to type 'never'. -/main.ts(8,14): error TS2322: Type '{ v: 0; }' is not assignable to type 'never'. +/main.ts(8,14): error TS2322: Type '{ pa: 0; readonly ma: 1; v: 0; }' is not assignable to type 'never'. ==== /main.ts (2 errors) ==== @@ -14,7 +14,7 @@ !!! error TS2322: Type '{ pb: 0; readonly mb: 1; v: 0; }' is not assignable to type 'never'. export const obsB: never = expand(); ~~~~ -!!! error TS2322: Type '{ v: 0; }' is not assignable to type 'never'. +!!! error TS2322: Type '{ pa: 0; readonly ma: 1; v: 0; }' is not assignable to type 'never'. ==== /ambient.d.ts (0 errors) ==== declare module "ma" { diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsExportStarDeclarationCollisionOrder.types b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsExportStarDeclarationCollisionOrder.types index 58541f8aa49ff..101b6db6c4dee 100644 --- a/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsExportStarDeclarationCollisionOrder.types +++ b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsExportStarDeclarationCollisionOrder.types @@ -26,7 +26,7 @@ export const obsA: never = expand(); export const obsB: never = expand(); >obsB : never ->expand() : { v: 0; } +>expand() : { pa: 0; readonly ma: 1; v: 0; } >expand : () => { [K in keyof X]: X[K]; } >oxb : O diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNamespaceMembersResolutionOrder.errors.txt b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNamespaceMembersResolutionOrder.errors.txt index fb286e6d925c7..a15ea5b841fce 100644 --- a/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNamespaceMembersResolutionOrder.errors.txt +++ b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNamespaceMembersResolutionOrder.errors.txt @@ -1,4 +1,4 @@ -/main.ts(8,32): error TS2339: Property 'ma' does not exist on type '{}'. +/main.ts(7,32): error TS2339: Property 'mb' does not exist on type '{}'. ==== /main.ts (1 errors) ==== @@ -9,9 +9,9 @@ export const fromA = MA.pa; export const fromB = MB.pb; export const checkA: 1 = MA.pa.mb; - export const checkB: 1 = MB.pb.ma; ~~ -!!! error TS2339: Property 'ma' does not exist on type '{}'. +!!! error TS2339: Property 'mb' does not exist on type '{}'. + export const checkB: 1 = MB.pb.ma; ==== /ma.js (0 errors) ==== export { v as pa } from "./xa"; diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNamespaceMembersResolutionOrder.js b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNamespaceMembersResolutionOrder.js index 85b0024d3067d..4881af4a1d031 100644 --- a/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNamespaceMembersResolutionOrder.js +++ b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNamespaceMembersResolutionOrder.js @@ -163,7 +163,10 @@ exports.checkB = MB.pb.ma; //// [xb.d.ts] export = o; declare const o: { - v: {}; + v: { + pa: {}; + ma: 1; + }; }; //// [mb.d.ts] export { v as pb } from "./xb"; @@ -171,10 +174,7 @@ export declare const mb = 1; //// [xa.d.ts] export = o; declare const o: { - v: { - pb: {}; - mb: 1; - }; + v: {}; }; //// [ma.d.ts] export { v as pa } from "./xa"; @@ -183,10 +183,7 @@ export declare const ma = 1; export = o; declare const o: { v: { - pa: { - pb: {}; - mb: 1; - }; + pa: {}; ma: 1; }; }; @@ -194,7 +191,10 @@ declare const o: { export = o; declare const o: { v: { - pb: {}; + pb: { + pa: {}; + ma: 1; + }; mb: 1; }; }; @@ -212,10 +212,10 @@ export declare const f8 = 8; export declare const f9 = 9; export declare const f10 = 10; //// [main.d.ts] -export declare const fromA: { - pb: {}; - mb: 1; +export declare const fromA: {}; +export declare const fromB: { + pa: {}; + ma: 1; }; -export declare const fromB: {}; export declare const checkA: 1; export declare const checkB: 1; diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNamespaceMembersResolutionOrder.symbols b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNamespaceMembersResolutionOrder.symbols index aaa25b0469d2f..74e3cd92e4248 100644 --- a/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNamespaceMembersResolutionOrder.symbols +++ b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNamespaceMembersResolutionOrder.symbols @@ -28,17 +28,17 @@ export const fromB = MB.pb; export const checkA: 1 = MA.pa.mb; >checkA : Symbol(checkA, Decl(main.ts, 6, 12)) ->MA.pa.mb : Symbol(mb, Decl(mb.js, 1, 12)) >MA.pa : Symbol(MA.pa, Decl(ma.js, 0, 8)) >MA : Symbol(MA, Decl(main.ts, 2, 6)) >pa : Symbol(MA.pa, Decl(ma.js, 0, 8)) ->mb : Symbol(mb, Decl(mb.js, 1, 12)) export const checkB: 1 = MB.pb.ma; >checkB : Symbol(checkB, Decl(main.ts, 7, 12)) +>MB.pb.ma : Symbol(ma, Decl(ma.js, 1, 12)) >MB.pb : Symbol(MB.pb, Decl(mb.js, 0, 8)) >MB : Symbol(MB, Decl(main.ts, 3, 6)) >pb : Symbol(MB.pb, Decl(mb.js, 0, 8)) +>ma : Symbol(ma, Decl(ma.js, 1, 12)) === /ma.js === export { v as pa } from "./xa"; diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNamespaceMembersResolutionOrder.types b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNamespaceMembersResolutionOrder.types index 753e075741287..f7142131ca798 100644 --- a/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNamespaceMembersResolutionOrder.types +++ b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNamespaceMembersResolutionOrder.types @@ -5,8 +5,8 @@ import * as idx from "./index"; >idx : typeof idx const all = { ...idx }; ->all : { a: { pa: { pb: {}; mb: 1; }; ma: 1; }; b: { pb: {}; mb: 1; }; f1: 1; f2: 2; f3: 3; f4: 4; f5: 5; f6: 6; f7: 7; f8: 8; f9: 9; f10: 10; } ->{ ...idx } : { a: { pa: { pb: {}; mb: 1; }; ma: 1; }; b: { pb: {}; mb: 1; }; f1: 1; f2: 2; f3: 3; f4: 4; f5: 5; f6: 6; f7: 7; f8: 8; f9: 9; f10: 10; } +>all : { a: { pa: {}; ma: 1; }; b: { pb: { pa: {}; ma: 1; }; mb: 1; }; f1: 1; f2: 2; f3: 3; f4: 4; f5: 5; f6: 6; f7: 7; f8: 8; f9: 9; f10: 10; } +>{ ...idx } : { a: { pa: {}; ma: 1; }; b: { pb: { pa: {}; ma: 1; }; mb: 1; }; f1: 1; f2: 2; f3: 3; f4: 4; f5: 5; f6: 6; f7: 7; f8: 8; f9: 9; f10: 10; } >idx : typeof idx import * as MA from "./ma"; @@ -16,37 +16,37 @@ import * as MB from "./mb"; >MB : typeof MB export const fromA = MA.pa; ->fromA : { pb: {}; mb: 1; } ->MA.pa : { pb: {}; mb: 1; } +>fromA : {} +>MA.pa : {} >MA : typeof MA ->pa : { pb: {}; mb: 1; } +>pa : {} export const fromB = MB.pb; ->fromB : {} ->MB.pb : {} +>fromB : { pa: {}; ma: 1; } +>MB.pb : { pa: {}; ma: 1; } >MB : typeof MB ->pb : {} +>pb : { pa: {}; ma: 1; } export const checkA: 1 = MA.pa.mb; >checkA : 1 ->MA.pa.mb : 1 ->MA.pa : { pb: {}; mb: 1; } +>MA.pa.mb : any +>MA.pa : {} >MA : typeof MA ->pa : { pb: {}; mb: 1; } ->mb : 1 +>pa : {} +>mb : any export const checkB: 1 = MB.pb.ma; >checkB : 1 ->MB.pb.ma : any ->MB.pb : {} +>MB.pb.ma : 1 +>MB.pb : { pa: {}; ma: 1; } >MB : typeof MB ->pb : {} ->ma : any +>pb : { pa: {}; ma: 1; } +>ma : 1 === /ma.js === export { v as pa } from "./xa"; ->v : { pb: {}; mb: 1; } ->pa : { pb: {}; mb: 1; } +>v : {} +>pa : {} export const ma = 1; >ma : 1 @@ -54,8 +54,8 @@ export const ma = 1; === /mb.js === export { v as pb } from "./xb"; ->v : {} ->pb : {} +>v : { pa: {}; ma: 1; } +>pb : { pa: {}; ma: 1; } export const mb = 1; >mb : 1 @@ -68,18 +68,18 @@ const M = /** @type {any} */ (null); >(null) : any const o = { v: { ...M } }; ->o : { v: { pb: {}; mb: 1; }; } ->{ v: { ...M } } : { v: { pb: {}; mb: 1; }; } ->v : { pb: {}; mb: 1; } ->{ ...M } : { pb: {}; mb: 1; } +>o : { v: {}; } +>{ v: { ...M } } : { v: { pb: { pa: {}; ma: 1; }; mb: 1; }; } +>v : { pb: { pa: {}; ma: 1; }; mb: 1; } +>{ ...M } : { pb: { pa: {}; ma: 1; }; mb: 1; } >M : typeof import("./mb") module.exports = o; ->module.exports = o : { v: { pb: {}; mb: 1; }; } ->module.exports : { v: { pb: {}; mb: 1; }; } ->module : { exports: { v: { pb: {}; mb: 1; }; }; } ->exports : { v: { pb: {}; mb: 1; }; } ->o : { v: { pb: {}; mb: 1; }; } +>module.exports = o : { v: {}; } +>module.exports : { v: {}; } +>module : { exports: { v: {}; }; } +>exports : { v: {}; } +>o : { v: {}; } === /xb.js === /** @type {typeof import("./ma")} */ @@ -88,18 +88,18 @@ const M = /** @type {any} */ (null); >(null) : any const o = { v: { ...M } }; ->o : { v: {}; } ->{ v: { ...M } } : { v: {}; } ->v : {} ->{ ...M } : {} +>o : { v: { pa: {}; ma: 1; }; } +>{ v: { ...M } } : { v: { pa: {}; ma: 1; }; } +>v : { pa: {}; ma: 1; } +>{ ...M } : { pa: {}; ma: 1; } >M : typeof import("./ma") module.exports = o; ->module.exports = o : { v: {}; } ->module.exports : { v: {}; } ->module : { exports: { v: {}; }; } ->exports : { v: {}; } ->o : { v: {}; } +>module.exports = o : { v: { pa: {}; ma: 1; }; } +>module.exports : { v: { pa: {}; ma: 1; }; } +>module : { exports: { v: { pa: {}; ma: 1; }; }; } +>exports : { v: { pa: {}; ma: 1; }; } +>o : { v: { pa: {}; ma: 1; }; } === /ia.js === /** @type {typeof import("./ma")} */ @@ -108,18 +108,18 @@ const M = /** @type {any} */ (null); >(null) : any const o = { v: { ...M } }; ->o : { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; } ->{ v: { ...M } } : { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; } ->v : { pa: { pb: {}; mb: 1; }; ma: 1; } ->{ ...M } : { pa: { pb: {}; mb: 1; }; ma: 1; } +>o : { v: { pa: {}; ma: 1; }; } +>{ v: { ...M } } : { v: { pa: {}; ma: 1; }; } +>v : { pa: {}; ma: 1; } +>{ ...M } : { pa: {}; ma: 1; } >M : typeof import("./ma") module.exports = o; ->module.exports = o : { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; } ->module.exports : { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; } ->module : { exports: { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; }; } ->exports : { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; } ->o : { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; } +>module.exports = o : { v: { pa: {}; ma: 1; }; } +>module.exports : { v: { pa: {}; ma: 1; }; } +>module : { exports: { v: { pa: {}; ma: 1; }; }; } +>exports : { v: { pa: {}; ma: 1; }; } +>o : { v: { pa: {}; ma: 1; }; } === /ib.js === /** @type {typeof import("./mb")} */ @@ -128,27 +128,27 @@ const M = /** @type {any} */ (null); >(null) : any const o = { v: { ...M } }; ->o : { v: { pb: {}; mb: 1; }; } ->{ v: { ...M } } : { v: { pb: {}; mb: 1; }; } ->v : { pb: {}; mb: 1; } ->{ ...M } : { pb: {}; mb: 1; } +>o : { v: { pb: { pa: {}; ma: 1; }; mb: 1; }; } +>{ v: { ...M } } : { v: { pb: { pa: {}; ma: 1; }; mb: 1; }; } +>v : { pb: { pa: {}; ma: 1; }; mb: 1; } +>{ ...M } : { pb: { pa: {}; ma: 1; }; mb: 1; } >M : typeof import("./mb") module.exports = o; ->module.exports = o : { v: { pb: {}; mb: 1; }; } ->module.exports : { v: { pb: {}; mb: 1; }; } ->module : { exports: { v: { pb: {}; mb: 1; }; }; } ->exports : { v: { pb: {}; mb: 1; }; } ->o : { v: { pb: {}; mb: 1; }; } +>module.exports = o : { v: { pb: { pa: {}; ma: 1; }; mb: 1; }; } +>module.exports : { v: { pb: { pa: {}; ma: 1; }; mb: 1; }; } +>module : { exports: { v: { pb: { pa: {}; ma: 1; }; mb: 1; }; }; } +>exports : { v: { pb: { pa: {}; ma: 1; }; mb: 1; }; } +>o : { v: { pb: { pa: {}; ma: 1; }; mb: 1; }; } === /index.js === export { v as a } from "./ia"; ->v : { pa: { pb: {}; mb: 1; }; ma: 1; } ->a : { pa: { pb: {}; mb: 1; }; ma: 1; } +>v : { pa: {}; ma: 1; } +>a : { pa: {}; ma: 1; } export { v as b } from "./ib"; ->v : { pb: {}; mb: 1; } ->b : { pb: {}; mb: 1; } +>v : { pb: { pa: {}; ma: 1; }; mb: 1; } +>b : { pb: { pa: {}; ma: 1; }; mb: 1; } export const f1 = 1; >f1 : 1 diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNamespaceMembersResolutionOrderReexportedTypes.errors.txt b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNamespaceMembersResolutionOrderReexportedTypes.errors.txt index 408176b1c59d1..c3d47c451cd56 100644 --- a/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNamespaceMembersResolutionOrderReexportedTypes.errors.txt +++ b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNamespaceMembersResolutionOrderReexportedTypes.errors.txt @@ -1,4 +1,4 @@ -/main.ts(8,32): error TS2339: Property 'ma' does not exist on type '{}'. +/main.ts(7,32): error TS2339: Property 'mb' does not exist on type '{}'. ==== /main.ts (1 errors) ==== @@ -9,9 +9,9 @@ export const fromA = MA.pa; export const fromB = MB.pb; export const checkA: 1 = MA.pa.mb; - export const checkB: 1 = MB.pb.ma; ~~ -!!! error TS2339: Property 'ma' does not exist on type '{}'. +!!! error TS2339: Property 'mb' does not exist on type '{}'. + export const checkB: 1 = MB.pb.ma; ==== /ma.js (0 errors) ==== export { v as pa } from "./xa"; diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNamespaceMembersResolutionOrderReexportedTypes.js b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNamespaceMembersResolutionOrderReexportedTypes.js index beb6f57cf711b..78caebf29bfbb 100644 --- a/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNamespaceMembersResolutionOrderReexportedTypes.js +++ b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNamespaceMembersResolutionOrderReexportedTypes.js @@ -161,7 +161,10 @@ exports.checkB = MB.pb.ma; //// [xb.d.ts] export = o; declare const o: { - v: {}; + v: { + pa: {}; + ma: 1; + }; }; //// [mb.d.ts] export { v as pb } from "./xb"; @@ -169,10 +172,7 @@ export declare const mb = 1; //// [xa.d.ts] export = o; declare const o: { - v: { - pb: {}; - mb: 1; - }; + v: {}; }; //// [ma.d.ts] export { v as pa } from "./xa"; @@ -181,10 +181,7 @@ export declare const ma = 1; export = o; declare const o: { v: { - pa: { - pb: {}; - mb: 1; - }; + pa: {}; ma: 1; }; }; @@ -192,15 +189,18 @@ declare const o: { export = o; declare const o: { v: { - pb: {}; + pb: { + pa: {}; + ma: 1; + }; mb: 1; }; }; //// [main.d.ts] -export declare const fromA: { - pb: {}; - mb: 1; +export declare const fromA: {}; +export declare const fromB: { + pa: {}; + ma: 1; }; -export declare const fromB: {}; export declare const checkA: 1; export declare const checkB: 1; diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNamespaceMembersResolutionOrderReexportedTypes.symbols b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNamespaceMembersResolutionOrderReexportedTypes.symbols index e737ff2cbfb8a..5d199dbcf8abb 100644 --- a/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNamespaceMembersResolutionOrderReexportedTypes.symbols +++ b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNamespaceMembersResolutionOrderReexportedTypes.symbols @@ -28,17 +28,17 @@ export const fromB = MB.pb; export const checkA: 1 = MA.pa.mb; >checkA : Symbol(checkA, Decl(main.ts, 6, 12)) ->MA.pa.mb : Symbol(mb, Decl(mb.js, 1, 12)) >MA.pa : Symbol(MA.pa, Decl(ma.js, 0, 8)) >MA : Symbol(MA, Decl(main.ts, 2, 6)) >pa : Symbol(MA.pa, Decl(ma.js, 0, 8)) ->mb : Symbol(mb, Decl(mb.js, 1, 12)) export const checkB: 1 = MB.pb.ma; >checkB : Symbol(checkB, Decl(main.ts, 7, 12)) +>MB.pb.ma : Symbol(ma, Decl(ma.js, 1, 12)) >MB.pb : Symbol(MB.pb, Decl(mb.js, 0, 8)) >MB : Symbol(MB, Decl(main.ts, 3, 6)) >pb : Symbol(MB.pb, Decl(mb.js, 0, 8)) +>ma : Symbol(ma, Decl(ma.js, 1, 12)) === /ma.js === export { v as pa } from "./xa"; diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNamespaceMembersResolutionOrderReexportedTypes.types b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNamespaceMembersResolutionOrderReexportedTypes.types index 53a815348e451..7354cbcd52d6f 100644 --- a/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNamespaceMembersResolutionOrderReexportedTypes.types +++ b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNamespaceMembersResolutionOrderReexportedTypes.types @@ -5,8 +5,8 @@ import * as idx from "./d"; >idx : typeof idx const all = { ...idx }; ->all : { a: { pa: { pb: {}; mb: 1; }; ma: 1; }; b: { pb: {}; mb: 1; }; f1: 1; f2: 2; f3: 3; f4: 4; f5: 5; f6: 6; f7: 7; f8: 8; f9: 9; f10: 10; } ->{ ...idx } : { a: { pa: { pb: {}; mb: 1; }; ma: 1; }; b: { pb: {}; mb: 1; }; f1: 1; f2: 2; f3: 3; f4: 4; f5: 5; f6: 6; f7: 7; f8: 8; f9: 9; f10: 10; } +>all : { a: { pa: {}; ma: 1; }; b: { pb: { pa: {}; ma: 1; }; mb: 1; }; f1: 1; f2: 2; f3: 3; f4: 4; f5: 5; f6: 6; f7: 7; f8: 8; f9: 9; f10: 10; } +>{ ...idx } : { a: { pa: {}; ma: 1; }; b: { pb: { pa: {}; ma: 1; }; mb: 1; }; f1: 1; f2: 2; f3: 3; f4: 4; f5: 5; f6: 6; f7: 7; f8: 8; f9: 9; f10: 10; } >idx : typeof idx import * as MA from "./ma"; @@ -16,37 +16,37 @@ import * as MB from "./mb"; >MB : typeof MB export const fromA = MA.pa; ->fromA : { pb: {}; mb: 1; } ->MA.pa : { pb: {}; mb: 1; } +>fromA : {} +>MA.pa : {} >MA : typeof MA ->pa : { pb: {}; mb: 1; } +>pa : {} export const fromB = MB.pb; ->fromB : {} ->MB.pb : {} +>fromB : { pa: {}; ma: 1; } +>MB.pb : { pa: {}; ma: 1; } >MB : typeof MB ->pb : {} +>pb : { pa: {}; ma: 1; } export const checkA: 1 = MA.pa.mb; >checkA : 1 ->MA.pa.mb : 1 ->MA.pa : { pb: {}; mb: 1; } +>MA.pa.mb : any +>MA.pa : {} >MA : typeof MA ->pa : { pb: {}; mb: 1; } ->mb : 1 +>pa : {} +>mb : any export const checkB: 1 = MB.pb.ma; >checkB : 1 ->MB.pb.ma : any ->MB.pb : {} +>MB.pb.ma : 1 +>MB.pb : { pa: {}; ma: 1; } >MB : typeof MB ->pb : {} ->ma : any +>pb : { pa: {}; ma: 1; } +>ma : 1 === /ma.js === export { v as pa } from "./xa"; ->v : { pb: {}; mb: 1; } ->pa : { pb: {}; mb: 1; } +>v : {} +>pa : {} export const ma = 1; >ma : 1 @@ -54,8 +54,8 @@ export const ma = 1; === /mb.js === export { v as pb } from "./xb"; ->v : {} ->pb : {} +>v : { pa: {}; ma: 1; } +>pb : { pa: {}; ma: 1; } export const mb = 1; >mb : 1 @@ -68,18 +68,18 @@ const M = /** @type {any} */ (null); >(null) : any const o = { v: { ...M } }; ->o : { v: { pb: {}; mb: 1; }; } ->{ v: { ...M } } : { v: { pb: {}; mb: 1; }; } ->v : { pb: {}; mb: 1; } ->{ ...M } : { pb: {}; mb: 1; } +>o : { v: {}; } +>{ v: { ...M } } : { v: { pb: { pa: {}; ma: 1; }; mb: 1; }; } +>v : { pb: { pa: {}; ma: 1; }; mb: 1; } +>{ ...M } : { pb: { pa: {}; ma: 1; }; mb: 1; } >M : typeof import("./mb") module.exports = o; ->module.exports = o : { v: { pb: {}; mb: 1; }; } ->module.exports : { v: { pb: {}; mb: 1; }; } ->module : { exports: { v: { pb: {}; mb: 1; }; }; } ->exports : { v: { pb: {}; mb: 1; }; } ->o : { v: { pb: {}; mb: 1; }; } +>module.exports = o : { v: {}; } +>module.exports : { v: {}; } +>module : { exports: { v: {}; }; } +>exports : { v: {}; } +>o : { v: {}; } === /xb.js === /** @type {typeof import("./ma")} */ @@ -88,18 +88,18 @@ const M = /** @type {any} */ (null); >(null) : any const o = { v: { ...M } }; ->o : { v: {}; } ->{ v: { ...M } } : { v: {}; } ->v : {} ->{ ...M } : {} +>o : { v: { pa: {}; ma: 1; }; } +>{ v: { ...M } } : { v: { pa: {}; ma: 1; }; } +>v : { pa: {}; ma: 1; } +>{ ...M } : { pa: {}; ma: 1; } >M : typeof import("./ma") module.exports = o; ->module.exports = o : { v: {}; } ->module.exports : { v: {}; } ->module : { exports: { v: {}; }; } ->exports : { v: {}; } ->o : { v: {}; } +>module.exports = o : { v: { pa: {}; ma: 1; }; } +>module.exports : { v: { pa: {}; ma: 1; }; } +>module : { exports: { v: { pa: {}; ma: 1; }; }; } +>exports : { v: { pa: {}; ma: 1; }; } +>o : { v: { pa: {}; ma: 1; }; } === /ia.js === /** @type {typeof import("./ma")} */ @@ -108,18 +108,18 @@ const M = /** @type {any} */ (null); >(null) : any const o = { v: { ...M } }; ->o : { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; } ->{ v: { ...M } } : { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; } ->v : { pa: { pb: {}; mb: 1; }; ma: 1; } ->{ ...M } : { pa: { pb: {}; mb: 1; }; ma: 1; } +>o : { v: { pa: {}; ma: 1; }; } +>{ v: { ...M } } : { v: { pa: {}; ma: 1; }; } +>v : { pa: {}; ma: 1; } +>{ ...M } : { pa: {}; ma: 1; } >M : typeof import("./ma") module.exports = o; ->module.exports = o : { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; } ->module.exports : { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; } ->module : { exports: { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; }; } ->exports : { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; } ->o : { v: { pa: { pb: {}; mb: 1; }; ma: 1; }; } +>module.exports = o : { v: { pa: {}; ma: 1; }; } +>module.exports : { v: { pa: {}; ma: 1; }; } +>module : { exports: { v: { pa: {}; ma: 1; }; }; } +>exports : { v: { pa: {}; ma: 1; }; } +>o : { v: { pa: {}; ma: 1; }; } === /ib.js === /** @type {typeof import("./mb")} */ @@ -128,41 +128,41 @@ const M = /** @type {any} */ (null); >(null) : any const o = { v: { ...M } }; ->o : { v: { pb: {}; mb: 1; }; } ->{ v: { ...M } } : { v: { pb: {}; mb: 1; }; } ->v : { pb: {}; mb: 1; } ->{ ...M } : { pb: {}; mb: 1; } +>o : { v: { pb: { pa: {}; ma: 1; }; mb: 1; }; } +>{ v: { ...M } } : { v: { pb: { pa: {}; ma: 1; }; mb: 1; }; } +>v : { pb: { pa: {}; ma: 1; }; mb: 1; } +>{ ...M } : { pb: { pa: {}; ma: 1; }; mb: 1; } >M : typeof import("./mb") module.exports = o; ->module.exports = o : { v: { pb: {}; mb: 1; }; } ->module.exports : { v: { pb: {}; mb: 1; }; } ->module : { exports: { v: { pb: {}; mb: 1; }; }; } ->exports : { v: { pb: {}; mb: 1; }; } ->o : { v: { pb: {}; mb: 1; }; } +>module.exports = o : { v: { pb: { pa: {}; ma: 1; }; mb: 1; }; } +>module.exports : { v: { pb: { pa: {}; ma: 1; }; mb: 1; }; } +>module : { exports: { v: { pb: { pa: {}; ma: 1; }; mb: 1; }; }; } +>exports : { v: { pb: { pa: {}; ma: 1; }; mb: 1; }; } +>o : { v: { pb: { pa: {}; ma: 1; }; mb: 1; }; } === /ia2.d.ts === export { v } from "./ia"; ->v : { pa: { pb: {}; mb: 1; }; ma: 1; } +>v : { pa: {}; ma: 1; } export type v = number; >v : number === /ib2.d.ts === export { v } from "./ib"; ->v : { pb: {}; mb: 1; } +>v : { pb: { pa: {}; ma: 1; }; mb: 1; } export type v = number; >v : number === /index.d.ts === export { v as a } from "./ia2"; ->v : { pa: { pb: {}; mb: 1; }; ma: 1; } ->a : { pa: { pb: {}; mb: 1; }; ma: 1; } +>v : { pa: {}; ma: 1; } +>a : { pa: {}; ma: 1; } export { v as b } from "./ib2"; ->v : { pb: {}; mb: 1; } ->b : { pb: {}; mb: 1; } +>v : { pb: { pa: {}; ma: 1; }; mb: 1; } +>b : { pb: { pa: {}; ma: 1; }; mb: 1; } export const f1: 1; >f1 : 1 @@ -196,12 +196,12 @@ export const f10: 10; === /index2.d.ts === export { v as a } from "./ia2"; ->v : { pa: { pb: {}; mb: 1; }; ma: 1; } ->a : { pa: { pb: {}; mb: 1; }; ma: 1; } +>v : { pa: {}; ma: 1; } +>a : { pa: {}; ma: 1; } export { v as b } from "./ib2"; ->v : { pb: {}; mb: 1; } ->b : { pb: {}; mb: 1; } +>v : { pb: { pa: {}; ma: 1; }; mb: 1; } +>b : { pb: { pa: {}; ma: 1; }; mb: 1; } === /d.d.ts === diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNonExportedMemberOrder.errors.txt b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNonExportedMemberOrder.errors.txt index aba9bd814f87b..9d0c5d89f5be4 100644 --- a/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNonExportedMemberOrder.errors.txt +++ b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNonExportedMemberOrder.errors.txt @@ -1,6 +1,6 @@ /main.ts(2,10): error TS2459: Module '"mt"' declares 'zz' locally, but it is not exported. /main.ts(6,14): error TS2322: Type '{ pb: 0; readonly mb: 1; v: 0; }' is not assignable to type 'never'. -/main.ts(7,14): error TS2322: Type '{ v: 0; }' is not assignable to type 'never'. +/main.ts(7,14): error TS2322: Type '{ pa: 0; readonly ma: 1; v: 0; }' is not assignable to type 'never'. ==== /main.ts (3 errors) ==== @@ -17,7 +17,7 @@ !!! error TS2322: Type '{ pb: 0; readonly mb: 1; v: 0; }' is not assignable to type 'never'. export const obsB: never = expand(); ~~~~ -!!! error TS2322: Type '{ v: 0; }' is not assignable to type 'never'. +!!! error TS2322: Type '{ pa: 0; readonly ma: 1; v: 0; }' is not assignable to type 'never'. ==== /ambient.d.ts (0 errors) ==== declare module "ma" { diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNonExportedMemberOrder.types b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNonExportedMemberOrder.types index 00ba3854da277..ce4272bde5283 100644 --- a/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNonExportedMemberOrder.types +++ b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsNonExportedMemberOrder.types @@ -22,7 +22,7 @@ export const obsA: never = expand(); export const obsB: never = expand(); >obsB : never ->expand() : { v: 0; } +>expand() : { pa: 0; readonly ma: 1; v: 0; } >expand : () => { [K in keyof X]: X[K]; } >oxb : O diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyExportEqualsWithOtherExportsCheckOrder.errors.txt b/tsc/testdata/baselines/reference/compiler/noFlakyExportEqualsWithOtherExportsCheckOrder.errors.txt index d81eb346f2055..c291bb9504fc7 100644 --- a/tsc/testdata/baselines/reference/compiler/noFlakyExportEqualsWithOtherExportsCheckOrder.errors.txt +++ b/tsc/testdata/baselines/reference/compiler/noFlakyExportEqualsWithOtherExportsCheckOrder.errors.txt @@ -1,8 +1,7 @@ /mm.ts(5,1): error TS2309: An export assignment cannot be used in a module with other exported elements. -/mm.ts(19,30): error TS2339: Property 'pa' does not exist on type 'O'. -==== /mm.ts (2 errors) ==== +==== /mm.ts (1 errors) ==== /// declare const oxa: typeof import("xa"); declare const oxb: typeof import("xb"); @@ -24,8 +23,6 @@ export interface F10 {} export const checkA: 0 = oxa.pb; export const checkB: 0 = oxb.pa; - ~~ -!!! error TS2339: Property 'pa' does not exist on type 'O'. ==== /ambient.d.ts (0 errors) ==== declare module "ma" { diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyExportEqualsWithOtherExportsCheckOrder.symbols b/tsc/testdata/baselines/reference/compiler/noFlakyExportEqualsWithOtherExportsCheckOrder.symbols index 74a9fa86901eb..dc3daa982e602 100644 --- a/tsc/testdata/baselines/reference/compiler/noFlakyExportEqualsWithOtherExportsCheckOrder.symbols +++ b/tsc/testdata/baselines/reference/compiler/noFlakyExportEqualsWithOtherExportsCheckOrder.symbols @@ -61,7 +61,9 @@ export const checkA: 0 = oxa.pb; export const checkB: 0 = oxb.pa; >checkB : Symbol(checkB, Decl(mm.ts, 18, 12)) +>oxb.pa : Symbol(pa, Decl(ambient.d.ts, 1, 12)) >oxb : Symbol(oxb, Decl(mm.ts, 2, 13)) +>pa : Symbol(pa, Decl(ambient.d.ts, 1, 12)) === /ambient.d.ts === declare module "ma" { diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyExportEqualsWithOtherExportsCheckOrder.types b/tsc/testdata/baselines/reference/compiler/noFlakyExportEqualsWithOtherExportsCheckOrder.types index 44664dcadfb80..d50ab18d6826e 100644 --- a/tsc/testdata/baselines/reference/compiler/noFlakyExportEqualsWithOtherExportsCheckOrder.types +++ b/tsc/testdata/baselines/reference/compiler/noFlakyExportEqualsWithOtherExportsCheckOrder.types @@ -41,9 +41,9 @@ export const checkA: 0 = oxa.pb; export const checkB: 0 = oxb.pa; >checkB : 0 ->oxb.pa : any +>oxb.pa : 0 >oxb : O ->pa : any +>pa : 0 === /ambient.d.ts === declare module "ma" { diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyExportEqualsWithOtherExportsImportOrder.js b/tsc/testdata/baselines/reference/compiler/noFlakyExportEqualsWithOtherExportsImportOrder.js index 37e7a9ff94890..527e0cf1c555a 100644 --- a/tsc/testdata/baselines/reference/compiler/noFlakyExportEqualsWithOtherExportsImportOrder.js +++ b/tsc/testdata/baselines/reference/compiler/noFlakyExportEqualsWithOtherExportsImportOrder.js @@ -71,5 +71,7 @@ export declare const obsA: { v: 0; }; export declare const obsB: { + pa: 0; + readonly ma: 1; v: 0; }; diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyExportEqualsWithOtherExportsImportOrder.types b/tsc/testdata/baselines/reference/compiler/noFlakyExportEqualsWithOtherExportsImportOrder.types index c0ba114c78355..03579880e27c4 100644 --- a/tsc/testdata/baselines/reference/compiler/noFlakyExportEqualsWithOtherExportsImportOrder.types +++ b/tsc/testdata/baselines/reference/compiler/noFlakyExportEqualsWithOtherExportsImportOrder.types @@ -25,8 +25,8 @@ export const obsA = expand(); >oxa : O export const obsB = expand(); ->obsB : { v: 0; } ->expand() : { v: 0; } +>obsB : { pa: 0; readonly ma: 1; v: 0; } +>expand() : { pa: 0; readonly ma: 1; v: 0; } >expand : () => { [K in keyof X]: X[K]; } >oxb : O diff --git a/tsc/testdata/baselines/reference/compiler/relationComplexityError(checkorder=default).errors.txt b/tsc/testdata/baselines/reference/compiler/relationComplexityError(checkorder=default).errors.txt index e8f947ec283b1..e208e03814c1f 100644 --- a/tsc/testdata/baselines/reference/compiler/relationComplexityError(checkorder=default).errors.txt +++ b/tsc/testdata/baselines/reference/compiler/relationComplexityError(checkorder=default).errors.txt @@ -1,13 +1,14 @@ relationComplexityError.ts(10,5): error TS2859: Excessive complexity comparing types '{ p: T1 & T2; }' and '{ p: T1 | null; }'. relationComplexityError.ts(10,5): error TS2859: Excessive complexity comparing types '{ p: T1 & T2; }' and '{ p: T1 | null; }'. relationComplexityError.ts(18,5): error TS2859: Excessive complexity comparing types 'T1 & T2' and 'T1 | null'. +relationComplexityError.ts(18,5): error TS2859: Excessive complexity comparing types 'T1 & T2' and 'T1 | null'. +relationComplexityError.ts(22,5): error TS2859: Excessive complexity comparing types 'T1 & T2' and 'T1 | null'. relationComplexityError.ts(22,5): error TS2859: Excessive complexity comparing types 'T1 & T2' and 'T1 | null'. -relationComplexityError.ts(26,5): error TS2322: Type '{ p: T1 & T2; }' is not assignable to type '{ p: T1 | null; }'. - Types of property 'p' are incompatible. - Excessive complexity comparing types 'T1 & T2' and 'T1 | null'. +relationComplexityError.ts(26,5): error TS2859: Excessive complexity comparing types '{ p: T1 & T2; }' and '{ p: T1 | null; }'. +relationComplexityError.ts(26,5): error TS2859: Excessive complexity comparing types '{ p: T1 & T2; }' and '{ p: T1 | null; }'. -==== relationComplexityError.ts (5 errors) ==== +==== relationComplexityError.ts (8 errors) ==== // Repro from #55630 // Every comparison that needs a comparison that is too complex is too complex // itself, whether or not that comparison happened before. @@ -31,20 +32,24 @@ relationComplexityError.ts(26,5): error TS2322: Type '{ p: T1 & T2; }' is not as function f2(x: T1 | null, y: T1 & T2) { x = y; // Complexity error ~ +!!! error TS2859: Excessive complexity comparing types 'T1 & T2' and 'T1 | null'. + ~~~~~ !!! error TS2859: Excessive complexity comparing types 'T1 & T2' and 'T1 | null'. } function f3(x: T1 | null, y: T1 & T2) { x = y; // Complexity error ~ +!!! error TS2859: Excessive complexity comparing types 'T1 & T2' and 'T1 | null'. + ~~~~~ !!! error TS2859: Excessive complexity comparing types 'T1 & T2' and 'T1 | null'. } function f4(x: { p: T1 | null }, y: { p: T1 & T2 }) { x = y; // Complexity error ~ -!!! error TS2322: Type '{ p: T1 & T2; }' is not assignable to type '{ p: T1 | null; }'. -!!! error TS2322: Types of property 'p' are incompatible. -!!! error TS2322: Excessive complexity comparing types 'T1 & T2' and 'T1 | null'. +!!! error TS2859: Excessive complexity comparing types '{ p: T1 & T2; }' and '{ p: T1 | null; }'. + ~~~~~ +!!! error TS2859: Excessive complexity comparing types '{ p: T1 & T2; }' and '{ p: T1 | null; }'. } \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/relationComplexityError(checkorder=typesfirst).errors.txt b/tsc/testdata/baselines/reference/compiler/relationComplexityError(checkorder=typesfirst).errors.txt index 8a92179def8a5..e208e03814c1f 100644 --- a/tsc/testdata/baselines/reference/compiler/relationComplexityError(checkorder=typesfirst).errors.txt +++ b/tsc/testdata/baselines/reference/compiler/relationComplexityError(checkorder=typesfirst).errors.txt @@ -1,13 +1,14 @@ -relationComplexityError.ts(10,5): error TS2322: Type '{ p: T1 & T2; }' is not assignable to type '{ p: T1 | null; }'. - Types of property 'p' are incompatible. - Excessive complexity comparing types 'T1 & T2' and 'T1 | null'. +relationComplexityError.ts(10,5): error TS2859: Excessive complexity comparing types '{ p: T1 & T2; }' and '{ p: T1 | null; }'. +relationComplexityError.ts(10,5): error TS2859: Excessive complexity comparing types '{ p: T1 & T2; }' and '{ p: T1 | null; }'. relationComplexityError.ts(18,5): error TS2859: Excessive complexity comparing types 'T1 & T2' and 'T1 | null'. +relationComplexityError.ts(18,5): error TS2859: Excessive complexity comparing types 'T1 & T2' and 'T1 | null'. +relationComplexityError.ts(22,5): error TS2859: Excessive complexity comparing types 'T1 & T2' and 'T1 | null'. relationComplexityError.ts(22,5): error TS2859: Excessive complexity comparing types 'T1 & T2' and 'T1 | null'. relationComplexityError.ts(26,5): error TS2859: Excessive complexity comparing types '{ p: T1 & T2; }' and '{ p: T1 | null; }'. relationComplexityError.ts(26,5): error TS2859: Excessive complexity comparing types '{ p: T1 & T2; }' and '{ p: T1 | null; }'. -==== relationComplexityError.ts (5 errors) ==== +==== relationComplexityError.ts (8 errors) ==== // Repro from #55630 // Every comparison that needs a comparison that is too complex is too complex // itself, whether or not that comparison happened before. @@ -19,9 +20,9 @@ relationComplexityError.ts(26,5): error TS2859: Excessive complexity comparing t function f0(x: { p: T1 | null }, y: { p: T1 & T2 }) { x = y; // Complexity error ~ -!!! error TS2322: Type '{ p: T1 & T2; }' is not assignable to type '{ p: T1 | null; }'. -!!! error TS2322: Types of property 'p' are incompatible. -!!! error TS2322: Excessive complexity comparing types 'T1 & T2' and 'T1 | null'. +!!! error TS2859: Excessive complexity comparing types '{ p: T1 & T2; }' and '{ p: T1 | null; }'. + ~~~~~ +!!! error TS2859: Excessive complexity comparing types '{ p: T1 & T2; }' and '{ p: T1 | null; }'. } function f1(x: T1, y: T1 & T2) { @@ -31,12 +32,16 @@ relationComplexityError.ts(26,5): error TS2859: Excessive complexity comparing t function f2(x: T1 | null, y: T1 & T2) { x = y; // Complexity error ~ +!!! error TS2859: Excessive complexity comparing types 'T1 & T2' and 'T1 | null'. + ~~~~~ !!! error TS2859: Excessive complexity comparing types 'T1 & T2' and 'T1 | null'. } function f3(x: T1 | null, y: T1 & T2) { x = y; // Complexity error ~ +!!! error TS2859: Excessive complexity comparing types 'T1 & T2' and 'T1 | null'. + ~~~~~ !!! error TS2859: Excessive complexity comparing types 'T1 & T2' and 'T1 | null'. } diff --git a/tsc/testdata/baselines/reference/compiler/relationDeeplyNestedCheckOrder.errors.txt b/tsc/testdata/baselines/reference/compiler/relationDeeplyNestedCheckOrder.errors.txt deleted file mode 100644 index b12e3d7fff298..0000000000000 --- a/tsc/testdata/baselines/reference/compiler/relationDeeplyNestedCheckOrder.errors.txt +++ /dev/null @@ -1,32 +0,0 @@ -relationDeeplyNestedCheckOrder.ts(13,1): error TS2322: Type 'A<{ a: string; }>' is not assignable to type 'A<{ a: "lit"; }>'. - The types of 'm1.a.m0.m1' are incompatible between these types. - Type 'string' is not assignable to type '"lit"'. -relationDeeplyNestedCheckOrder.ts(14,1): error TS2322: Type 'B<{ a: string; }>' is not assignable to type 'B<{ a: "lit"; }>'. - The types of 'm0.m1.a.m0.m1' are incompatible between these types. - Type 'string' is not assignable to type '"lit"'. - - -==== relationDeeplyNestedCheckOrder.ts (2 errors) ==== - // Relating 'B<{ a: string }>' to 'B<{ a: "lit" }>' eventually relates 'string' - // to '"lit"' through 'm0.m1.a.m0.m1', but only after measuring the variances - // of 'A', which gives up on deeply nested types. TypeScript reports an error - // for the 'B' assignment only when it is checked before the 'A' assignment; - // the result must not depend on which is checked first. - - type A = { m0: (x: B) => void; m1: { [K in keyof T]: B } }; - type B = { m0: A }; - - declare let a1: A<{ a: string }>, a2: A<{ a: "lit" }>; - declare let b1: B<{ a: string }>, b2: B<{ a: "lit" }>; - - a2 = a1; - ~~ -!!! error TS2322: Type 'A<{ a: string; }>' is not assignable to type 'A<{ a: "lit"; }>'. -!!! error TS2322: The types of 'm1.a.m0.m1' are incompatible between these types. -!!! error TS2322: Type 'string' is not assignable to type '"lit"'. - b2 = b1; - ~~ -!!! error TS2322: Type 'B<{ a: string; }>' is not assignable to type 'B<{ a: "lit"; }>'. -!!! error TS2322: The types of 'm0.m1.a.m0.m1' are incompatible between these types. -!!! error TS2322: Type 'string' is not assignable to type '"lit"'. - \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength(checkorder=default).errors.txt b/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength(checkorder=default).errors.txt index 2c10283c820ef..afb511fd91c39 100644 --- a/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength(checkorder=default).errors.txt +++ b/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength(checkorder=default).errors.txt @@ -1,9 +1,18 @@ +templateLiteralTypeExcessiveLength.ts(14,16): error TS2589: Type instantiation is excessively deep and possibly infinite. +templateLiteralTypeExcessiveLength.ts(15,6): error TS2589: Type instantiation is excessively deep and possibly infinite. templateLiteralTypeExcessiveLength.ts(15,11): error TS2589: Type instantiation is excessively deep and possibly infinite. +templateLiteralTypeExcessiveLength.ts(15,28): error TS2589: Type instantiation is excessively deep and possibly infinite. +templateLiteralTypeExcessiveLength.ts(19,21): error TS2589: Type instantiation is excessively deep and possibly infinite. +templateLiteralTypeExcessiveLength.ts(20,6): error TS2589: Type instantiation is excessively deep and possibly infinite. templateLiteralTypeExcessiveLength.ts(20,11): error TS2589: Type instantiation is excessively deep and possibly infinite. +templateLiteralTypeExcessiveLength.ts(20,31): error TS2589: Type instantiation is excessively deep and possibly infinite. +templateLiteralTypeExcessiveLength.ts(33,24): error TS2589: Type instantiation is excessively deep and possibly infinite. +templateLiteralTypeExcessiveLength.ts(34,6): error TS2589: Type instantiation is excessively deep and possibly infinite. templateLiteralTypeExcessiveLength.ts(34,11): error TS2589: Type instantiation is excessively deep and possibly infinite. +templateLiteralTypeExcessiveLength.ts(34,38): error TS2589: Type instantiation is excessively deep and possibly infinite. -==== templateLiteralTypeExcessiveLength.ts (3 errors) ==== +==== templateLiteralTypeExcessiveLength.ts (12 errors) ==== // Regression test for #63271: `Dec<2>` is `any`, so `Recur` produces both conditional branches and // keeps tail-recursing forever. The checker must report an error instead of building an unbounded // template literal type. @@ -18,17 +27,33 @@ templateLiteralTypeExcessiveLength.ts(34,11): error TS2589: Type instantiation i // The string literal doubles on every iteration. type Explode = { + ~ [P in Recur<5, "a"> as `${P}_key`]: any; + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. ~~~~~~~~~~~~~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. + ~~~~~~~~~~ !!! error TS2589: Type instantiation is excessively deep and possibly infinite. }; + ~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. // The number of placeholders doubles on every iteration. type ExplodeSpans = { + ~ [P in Recur<5, string> as `${P}_key`]: any; + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. ~~~~~~~~~~~~~~~~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. + ~~~~~~~~~~ !!! error TS2589: Type instantiation is excessively deep and possibly infinite. }; + ~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. // Every text segment stays small, but the number of segments doubles on every iteration, // so the combined text length grows without bound. @@ -41,8 +66,16 @@ templateLiteralTypeExcessiveLength.ts(34,11): error TS2589: Type instantiation i N extends 0 ? S : RecurSegments, `${S}${string}${S}`>; type ExplodeSegments = { + ~ [P in RecurSegments<5, A1024> as `${P}_key`]: any; + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. ~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. + ~~~~~~~~~~ !!! error TS2589: Type instantiation is excessively deep and possibly infinite. }; + ~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength(checkorder=typesfirst).errors.txt b/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength(checkorder=typesfirst).errors.txt index a7a577d5080f1..75017c0b48808 100644 --- a/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength(checkorder=typesfirst).errors.txt +++ b/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength(checkorder=typesfirst).errors.txt @@ -1,8 +1,15 @@ -error TS2589: Type instantiation is excessively deep and possibly infinite. +templateLiteralTypeExcessiveLength.ts(15,6): error TS2589: Type instantiation is excessively deep and possibly infinite. +templateLiteralTypeExcessiveLength.ts(15,11): error TS2589: Type instantiation is excessively deep and possibly infinite. +templateLiteralTypeExcessiveLength.ts(15,28): error TS2589: Type instantiation is excessively deep and possibly infinite. +templateLiteralTypeExcessiveLength.ts(20,6): error TS2589: Type instantiation is excessively deep and possibly infinite. +templateLiteralTypeExcessiveLength.ts(20,11): error TS2589: Type instantiation is excessively deep and possibly infinite. +templateLiteralTypeExcessiveLength.ts(20,31): error TS2589: Type instantiation is excessively deep and possibly infinite. +templateLiteralTypeExcessiveLength.ts(34,6): error TS2589: Type instantiation is excessively deep and possibly infinite. +templateLiteralTypeExcessiveLength.ts(34,11): error TS2589: Type instantiation is excessively deep and possibly infinite. +templateLiteralTypeExcessiveLength.ts(34,38): error TS2589: Type instantiation is excessively deep and possibly infinite. -!!! error TS2589: Type instantiation is excessively deep and possibly infinite. -==== templateLiteralTypeExcessiveLength.ts (0 errors) ==== +==== templateLiteralTypeExcessiveLength.ts (9 errors) ==== // Regression test for #63271: `Dec<2>` is `any`, so `Recur` produces both conditional branches and // keeps tail-recursing forever. The checker must report an error instead of building an unbounded // template literal type. @@ -18,11 +25,23 @@ error TS2589: Type instantiation is excessively deep and possibly infinite. // The string literal doubles on every iteration. type Explode = { [P in Recur<5, "a"> as `${P}_key`]: any; + ~~~~~~~~~~~~~~~~~~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. + ~~~~~~~~~~~~~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. + ~~~~~~~~~~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. }; // The number of placeholders doubles on every iteration. type ExplodeSpans = { [P in Recur<5, string> as `${P}_key`]: any; + ~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. + ~~~~~~~~~~~~~~~~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. + ~~~~~~~~~~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. }; // Every text segment stays small, but the number of segments doubles on every iteration, @@ -37,5 +56,11 @@ error TS2589: Type instantiation is excessively deep and possibly infinite. type ExplodeSegments = { [P in RecurSegments<5, A1024> as `${P}_key`]: any; + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. + ~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. + ~~~~~~~~~~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. }; \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/typeParameterHasSelfAsConstraint(checkorder=typesfirst).errors.txt b/tsc/testdata/baselines/reference/compiler/typeParameterHasSelfAsConstraint(checkorder=typesfirst).errors.txt index 83997ce99f298..b7a0b982aceb6 100644 --- a/tsc/testdata/baselines/reference/compiler/typeParameterHasSelfAsConstraint(checkorder=typesfirst).errors.txt +++ b/tsc/testdata/baselines/reference/compiler/typeParameterHasSelfAsConstraint(checkorder=typesfirst).errors.txt @@ -6,7 +6,6 @@ typeParameterHasSelfAsConstraint.ts(2,5): error TS2322: Type 'T' is not assignab function foo(x: T): number { ~ !!! error TS2313: Type parameter 'T' has a circular constraint. -!!! related TS2751 typeParameterHasSelfAsConstraint.ts:2:12: Circularity originates in type at this location. return x; ~~~~~~ !!! error TS2322: Type 'T' is not assignable to type 'number'. diff --git a/tsc/testdata/baselines/reference/compiler/typeParameterWithInvalidConstraintType.errors.txt b/tsc/testdata/baselines/reference/compiler/typeParameterWithInvalidConstraintType.errors.txt index 7ee5828671802..342cb732ba6ff 100644 --- a/tsc/testdata/baselines/reference/compiler/typeParameterWithInvalidConstraintType.errors.txt +++ b/tsc/testdata/baselines/reference/compiler/typeParameterWithInvalidConstraintType.errors.txt @@ -10,7 +10,6 @@ typeParameterWithInvalidConstraintType.ts(7,17): error TS2349: This expression i class A { ~ !!! error TS2313: Type parameter 'T' has a circular constraint. -!!! related TS2751 typeParameterWithInvalidConstraintType.ts:4:17: Circularity originates in type at this location. foo() { var x!: T; var a = x.foo(); diff --git a/tsc/testdata/baselines/reference/compiler/unusedLocalsOnFunctionDeclarationWithinFunctionDeclaration1(checkorder=typesfirst).errors.txt b/tsc/testdata/baselines/reference/compiler/unusedLocalsOnFunctionDeclarationWithinFunctionDeclaration1(checkorder=typesfirst).errors.txt index 387fe2f14d0be..f2bff262ffc44 100644 --- a/tsc/testdata/baselines/reference/compiler/unusedLocalsOnFunctionDeclarationWithinFunctionDeclaration1(checkorder=typesfirst).errors.txt +++ b/tsc/testdata/baselines/reference/compiler/unusedLocalsOnFunctionDeclarationWithinFunctionDeclaration1(checkorder=typesfirst).errors.txt @@ -1,14 +1,17 @@ unusedLocalsOnFunctionDeclarationWithinFunctionDeclaration1.ts(1,18): error TS6133: 'person' is declared but its value is never read. +unusedLocalsOnFunctionDeclarationWithinFunctionDeclaration1.ts(1,34): error TS6133: 'person2' is declared but its value is never read. unusedLocalsOnFunctionDeclarationWithinFunctionDeclaration1.ts(2,9): error TS6133: 'unused' is declared but its value is never read. unusedLocalsOnFunctionDeclarationWithinFunctionDeclaration1.ts(3,14): error TS6133: 'maker' is declared but its value is never read. unusedLocalsOnFunctionDeclarationWithinFunctionDeclaration1.ts(3,20): error TS6133: 'child' is declared but its value is never read. unusedLocalsOnFunctionDeclarationWithinFunctionDeclaration1.ts(4,13): error TS6133: 'unused2' is declared but its value is never read. -==== unusedLocalsOnFunctionDeclarationWithinFunctionDeclaration1.ts (5 errors) ==== +==== unusedLocalsOnFunctionDeclarationWithinFunctionDeclaration1.ts (6 errors) ==== function greeter(person: string, person2: string) { ~~~~~~ !!! error TS6133: 'person' is declared but its value is never read. + ~~~~~~~ +!!! error TS6133: 'person2' is declared but its value is never read. var unused = 20; ~~~~~~ !!! error TS6133: 'unused' is declared but its value is never read. diff --git a/tsc/testdata/baselines/reference/compiler/varianceCircularConsistency.errors.txt b/tsc/testdata/baselines/reference/compiler/varianceCircularConsistency.errors.txt index a4725d41bf2bf..07e45f9bb2d02 100644 --- a/tsc/testdata/baselines/reference/compiler/varianceCircularConsistency.errors.txt +++ b/tsc/testdata/baselines/reference/compiler/varianceCircularConsistency.errors.txt @@ -1,3 +1,13 @@ +varianceCircularConsistency.ts(25,7): error TS2322: Type 'EntryPoint void; }, {}>, { slug: string; }>' is not assignable to type 'EntryPoint'. + Types of property 'root' are incompatible. + Type 'Ref>' is not assignable to type 'Ref, never, never, {}>>'. + Type 'EntryPointComponent<{ q: { readonly variables: { slug: string; }; }; }, never, never, {}>' is not assignable to type 'EntryPointComponent, never, never, {}>'. + Type '(props: Props<{ q: { readonly variables: { slug: string; }; }; }, never, never, {}>) => void' is not assignable to type 'EntryPointComponent, never, never, {}>'. + Type '(props: Props<{ q: { readonly variables: { slug: string; }; }; }, never, never, {}>) => void' is not assignable to type '(props: Props, never, never, {}>) => void'. + Types of parameters 'props' and 'props' are incompatible. + Type 'Props, never, never, {}>' is not assignable to type 'Props<{ q: { readonly variables: { slug: string; }; }; }, never, never, {}>'. + Types of property 'queries' are incompatible. + Property 'q' is missing in type 'Record' but required in type '{ q: { readonly variables: { slug: string; }; }; }'. varianceCircularConsistency.ts(26,7): error TS2322: Type 'Ref>' is not assignable to type 'Ref, never, never, {}>>'. Type 'EntryPointComponent<{ q: { readonly variables: { slug: string; }; }; }, never, never, {}>' is not assignable to type 'EntryPointComponent, never, never, {}>'. Type '(props: Props<{ q: { readonly variables: { slug: string; }; }; }, never, never, {}>) => void' is not assignable to type 'EntryPointComponent, never, never, {}>'. @@ -8,7 +18,7 @@ varianceCircularConsistency.ts(26,7): error TS2322: Type 'Ref' but required in type '{ q: { readonly variables: { slug: string; }; }; }'. -==== varianceCircularConsistency.ts (1 errors) ==== +==== varianceCircularConsistency.ts (2 errors) ==== // Variances of these mutually recursive generics must not depend on values // measured mid-cycle. Assigning `e` to `EntryPoint` must agree with // assigning `e.root` to `EntryPoint["root"]`. @@ -34,6 +44,18 @@ varianceCircularConsistency.ts(26,7): error TS2322: Type 'Ref void }>; declare const e: EntryPoint; const whole: EntryPoint = e; + ~~~~~ +!!! error TS2322: Type 'EntryPoint void; }, {}>, { slug: string; }>' is not assignable to type 'EntryPoint'. +!!! error TS2322: Types of property 'root' are incompatible. +!!! error TS2322: Type 'Ref>' is not assignable to type 'Ref, never, never, {}>>'. +!!! error TS2322: Type 'EntryPointComponent<{ q: { readonly variables: { slug: string; }; }; }, never, never, {}>' is not assignable to type 'EntryPointComponent, never, never, {}>'. +!!! error TS2322: Type '(props: Props<{ q: { readonly variables: { slug: string; }; }; }, never, never, {}>) => void' is not assignable to type 'EntryPointComponent, never, never, {}>'. +!!! error TS2322: Type '(props: Props<{ q: { readonly variables: { slug: string; }; }; }, never, never, {}>) => void' is not assignable to type '(props: Props, never, never, {}>) => void'. +!!! error TS2322: Types of parameters 'props' and 'props' are incompatible. +!!! error TS2322: Type 'Props, never, never, {}>' is not assignable to type 'Props<{ q: { readonly variables: { slug: string; }; }; }, never, never, {}>'. +!!! error TS2322: Types of property 'queries' are incompatible. +!!! error TS2322: Property 'q' is missing in type 'Record' but required in type '{ q: { readonly variables: { slug: string; }; }; }'. +!!! related TS2728 varianceCircularConsistency.ts:23:40: 'q' is declared here. const root: EntryPoint["root"] = e.root; ~~~~ !!! error TS2322: Type 'Ref>' is not assignable to type 'Ref, never, never, {}>>'. diff --git a/tsc/testdata/baselines/reference/compiler/varianceCycleDiscardedDiagnostics.errors.txt b/tsc/testdata/baselines/reference/compiler/varianceCycleDiscardedDiagnostics.errors.txt index 570f693fae100..67a74da6b9b6d 100644 --- a/tsc/testdata/baselines/reference/compiler/varianceCycleDiscardedDiagnostics.errors.txt +++ b/tsc/testdata/baselines/reference/compiler/varianceCycleDiscardedDiagnostics.errors.txt @@ -1,4 +1,4 @@ -varianceCycleDiscardedDiagnostics.ts(14,5): error TS7022: 'Value' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. +varianceCycleDiscardedDiagnostics.ts(14,33): error TS2339: Property 'foo' does not exist on type 'A'. ==== varianceCycleDiscardedDiagnostics.ts (1 errors) ==== @@ -16,8 +16,8 @@ varianceCycleDiscardedDiagnostics.ts(14,5): error TS7022: 'Value' implicitly has class Bar { x!: T; Value = pick(new Foo(this)).foo; - ~~~~~ -!!! error TS7022: 'Value' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. + ~~~ +!!! error TS2339: Property 'foo' does not exist on type 'A'. } class Foo { diff --git a/tsc/testdata/baselines/reference/compiler/varianceCycleDiscardedDiagnostics.symbols b/tsc/testdata/baselines/reference/compiler/varianceCycleDiscardedDiagnostics.symbols index 437eb30b0dc81..e08928ce4a02d 100644 --- a/tsc/testdata/baselines/reference/compiler/varianceCycleDiscardedDiagnostics.symbols +++ b/tsc/testdata/baselines/reference/compiler/varianceCycleDiscardedDiagnostics.symbols @@ -36,11 +36,9 @@ class Bar { Value = pick(new Foo(this)).foo; >Value : Symbol(Bar.Value, Decl(varianceCycleDiscardedDiagnostics.ts, 12, 10)) ->pick(new Foo(this)).foo : Symbol(B.foo, Decl(varianceCycleDiscardedDiagnostics.ts, 7, 13)) >pick : Symbol(pick, Decl(varianceCycleDiscardedDiagnostics.ts, 7, 27), Decl(varianceCycleDiscardedDiagnostics.ts, 8, 41)) >Foo : Symbol(Foo, Decl(varianceCycleDiscardedDiagnostics.ts, 14, 1)) >this : Symbol(Bar, Decl(varianceCycleDiscardedDiagnostics.ts, 9, 36)) ->foo : Symbol(B.foo, Decl(varianceCycleDiscardedDiagnostics.ts, 7, 13)) } class Foo { diff --git a/tsc/testdata/baselines/reference/compiler/varianceCycleDiscardedDiagnostics.types b/tsc/testdata/baselines/reference/compiler/varianceCycleDiscardedDiagnostics.types index 6d56cf12120d4..ad994f6a17985 100644 --- a/tsc/testdata/baselines/reference/compiler/varianceCycleDiscardedDiagnostics.types +++ b/tsc/testdata/baselines/reference/compiler/varianceCycleDiscardedDiagnostics.types @@ -29,13 +29,13 @@ class Bar { Value = pick(new Foo(this)).foo; >Value : any ->pick(new Foo(this)).foo : string ->pick(new Foo(this)) : B +>pick(new Foo(this)).foo : any +>pick(new Foo(this)) : A >pick : { (x: Foo): A; (x: object): B; } >new Foo(this) : Foo >Foo : typeof Foo >this : this ->foo : string +>foo : any } class Foo { diff --git a/tsc/testdata/baselines/reference/conformance/dependentDestructuredVariablesReferenceInDeclaration1(checkorder=typesfirst).errors.txt b/tsc/testdata/baselines/reference/conformance/dependentDestructuredVariablesReferenceInDeclaration1(checkorder=typesfirst).errors.txt index 981a83e22c019..5529a3e5e68c6 100644 --- a/tsc/testdata/baselines/reference/conformance/dependentDestructuredVariablesReferenceInDeclaration1(checkorder=typesfirst).errors.txt +++ b/tsc/testdata/baselines/reference/conformance/dependentDestructuredVariablesReferenceInDeclaration1(checkorder=typesfirst).errors.txt @@ -22,12 +22,11 @@ dependentDestructuredVariablesReferenceInDeclaration1.ts(25,14): error TS2339: P dependentDestructuredVariablesReferenceInDeclaration1.ts(25,17): error TS2339: Property 'f' does not exist on type 'string | number'. dependentDestructuredVariablesReferenceInDeclaration1.ts(30,14): error TS7022: 'f' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. dependentDestructuredVariablesReferenceInDeclaration1.ts(30,71): error TS2448: Block-scoped variable 'f' used before its declaration. -dependentDestructuredVariablesReferenceInDeclaration1.ts(35,14): error TS7022: 'f' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. dependentDestructuredVariablesReferenceInDeclaration1.ts(58,14): error TS2451: Cannot redeclare block-scoped variable 'f'. dependentDestructuredVariablesReferenceInDeclaration1.ts(58,17): error TS2451: Cannot redeclare block-scoped variable 'f'. -==== dependentDestructuredVariablesReferenceInDeclaration1.ts (27 errors) ==== +==== dependentDestructuredVariablesReferenceInDeclaration1.ts (26 errors) ==== // https://github.com/microsoft/TypeScript/issues/62993 { @@ -113,8 +112,6 @@ dependentDestructuredVariablesReferenceInDeclaration1.ts(58,17): error TS2451: C { const { c, f }: { c: 0; f: () => unknown } | { c: 1; f: string } = { - ~ -!!! error TS7022: 'f' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. c: 0, f: () => f, }; diff --git a/tsc/testdata/baselines/reference/conformance/dependentDestructuredVariablesReferenceInDeclaration1(checkorder=typesfirst).types b/tsc/testdata/baselines/reference/conformance/dependentDestructuredVariablesReferenceInDeclaration1(checkorder=typesfirst).types index fda8b5b052297..1e1c61a26976d 100644 --- a/tsc/testdata/baselines/reference/conformance/dependentDestructuredVariablesReferenceInDeclaration1(checkorder=typesfirst).types +++ b/tsc/testdata/baselines/reference/conformance/dependentDestructuredVariablesReferenceInDeclaration1(checkorder=typesfirst).types @@ -103,7 +103,7 @@ { const { c, f }: { c: 0; f: () => unknown } | { c: 1; f: string } = { >c : 0 | 1 ->f : any +>f : string | (() => unknown) >c : 0 >f : () => unknown >c : 1 diff --git a/tsc/testdata/baselines/reference/conformance/importTag13(checkorder=typesfirst).errors.txt b/tsc/testdata/baselines/reference/conformance/importTag13(checkorder=typesfirst).errors.txt index de660063858df..3427c0e358148 100644 --- a/tsc/testdata/baselines/reference/conformance/importTag13(checkorder=typesfirst).errors.txt +++ b/tsc/testdata/baselines/reference/conformance/importTag13(checkorder=typesfirst).errors.txt @@ -1,16 +1,13 @@ /foo.js(1,15): error TS1005: 'from' expected. /foo.js(1,15): error TS1141: String literal expected. -/foo.js(1,25): error TS2307: Cannot find module 'types' or its corresponding type declarations. -==== /foo.js (3 errors) ==== +==== /foo.js (2 errors) ==== /** @import x = require("types") */ ~ !!! error TS1005: 'from' expected. ~~~~~~~~~~~~~~~~~~ !!! error TS1141: String literal expected. - ~~~~~~~ -!!! error TS2307: Cannot find module 'types' or its corresponding type declarations. ==== /types.ts (0 errors) ==== export interface Foo { diff --git a/tsc/testdata/baselines/reference/conformance/privateNameNestedClassFieldShadowing(checkorder=typesfirst).errors.txt b/tsc/testdata/baselines/reference/conformance/privateNameNestedClassFieldShadowing(checkorder=typesfirst).errors.txt index d4d3aede55faf..634c2349e1ecb 100644 --- a/tsc/testdata/baselines/reference/conformance/privateNameNestedClassFieldShadowing(checkorder=typesfirst).errors.txt +++ b/tsc/testdata/baselines/reference/conformance/privateNameNestedClassFieldShadowing(checkorder=typesfirst).errors.txt @@ -11,8 +11,8 @@ privateNameNestedClassFieldShadowing.ts(7,31): error TS18014: The property '#x' console.log(x.#x); ~~ !!! error TS18014: The property '#x' cannot be accessed on type 'Base' within this class because it is shadowed by another private identifier with the same spelling. -!!! related TS18018 privateNameNestedClassFieldShadowing.ts:2:5: The declaration of '#x' that you probably intended to use is defined here !!! related TS18017 privateNameNestedClassFieldShadowing.ts:5:13: The shadowing declaration of '#x' is defined here +!!! related TS18018 privateNameNestedClassFieldShadowing.ts:2:5: The declaration of '#x' that you probably intended to use is defined here } testDerived(x: Derived) { console.log(x.#x); diff --git a/tsc/testdata/baselines/reference/conformance/recursiveMappedTypes.errors.txt b/tsc/testdata/baselines/reference/conformance/recursiveMappedTypes.errors.txt index 5d6e45f1ffa2e..efdd2fe453ad5 100644 --- a/tsc/testdata/baselines/reference/conformance/recursiveMappedTypes.errors.txt +++ b/tsc/testdata/baselines/reference/conformance/recursiveMappedTypes.errors.txt @@ -4,12 +4,14 @@ recursiveMappedTypes.ts(7,6): error TS2456: Type alias 'Recurse1' circularly ref recursiveMappedTypes.ts(8,11): error TS2313: Type parameter 'K' has a circular constraint. recursiveMappedTypes.ts(11,6): error TS2456: Type alias 'Recurse2' circularly references itself. recursiveMappedTypes.ts(12,11): error TS2313: Type parameter 'K' has a circular constraint. +recursiveMappedTypes.ts(20,14): error TS5114: Instantiations of type 'Circular' appear infinitely circular. recursiveMappedTypes.ts(20,19): error TS5114: Instantiations of type 'Circular' appear infinitely circular. +recursiveMappedTypes.ts(21,10): error TS5114: Instantiations of type 'Circular' appear infinitely circular. recursiveMappedTypes.ts(73,5): error TS2502: '"each"' is referenced directly or indirectly in its own type annotation. -recursiveMappedTypes.ts(79,1): error TS2615: Type of property '"each"' circularly references itself in mapped type '{ [P in keyof ListWidget]: undefined extends ListWidget[P] ? never : P; }'. +recursiveMappedTypes.ts(73,13): error TS2615: Type of property '"each"' circularly references itself in mapped type '{ type: "type"; minimum_count: "minimum_count"; maximum_count: "maximum_count"; collapsable?: undefined; each: any; }'. -==== recursiveMappedTypes.ts (9 errors) ==== +==== recursiveMappedTypes.ts (11 errors) ==== // Recursive mapped types simply appear empty type Recurse = { @@ -43,9 +45,13 @@ recursiveMappedTypes.ts(79,1): error TS2615: Type of property '"each"' circularl type tup = [number, number, number, number]; function foo(arg: Circular): tup { + ~~~~~~~~~~~~~~~~~~ +!!! error TS5114: Instantiations of type 'Circular' appear infinitely circular. ~~~~~~~~~~~~~ !!! error TS5114: Instantiations of type 'Circular' appear infinitely circular. return arg; + ~~~ +!!! error TS5114: Instantiations of type 'Circular' appear infinitely circular. } // Repro from #29442 @@ -100,14 +106,14 @@ recursiveMappedTypes.ts(79,1): error TS2615: Type of property '"each"' circularl "each": Child; ~~~~~~ !!! error TS2502: '"each"' is referenced directly or indirectly in its own type annotation. + ~~~~~~~~~~~~~~~~~ +!!! error TS2615: Type of property '"each"' circularly references itself in mapped type '{ type: "type"; minimum_count: "minimum_count"; maximum_count: "maximum_count"; collapsable?: undefined; each: any; }'. } type ListChild = Child declare let x: ListChild; x.type; - ~ -!!! error TS2615: Type of property '"each"' circularly references itself in mapped type '{ [P in keyof ListWidget]: undefined extends ListWidget[P] ? never : P; }'. // Repros from #41790 diff --git a/tsc/testdata/baselines/reference/conformance/templateLiteralTypes1.errors.txt b/tsc/testdata/baselines/reference/conformance/templateLiteralTypes1.errors.txt index ed921a5dbfe66..a77ac80910723 100644 --- a/tsc/testdata/baselines/reference/conformance/templateLiteralTypes1.errors.txt +++ b/tsc/testdata/baselines/reference/conformance/templateLiteralTypes1.errors.txt @@ -6,10 +6,13 @@ templateLiteralTypes1.ts(165,15): error TS1338: 'infer' declarations are only pe templateLiteralTypes1.ts(197,16): error TS2590: Expression produces a union type that is too complex to represent. templateLiteralTypes1.ts(201,16): error TS2590: Expression produces a union type that is too complex to represent. templateLiteralTypes1.ts(205,16): error TS2590: Expression produces a union type that is too complex to represent. +templateLiteralTypes1.ts(250,5): error TS2590: Expression produces a union type that is too complex to represent. templateLiteralTypes1.ts(252,7): error TS2590: Expression produces a union type that is too complex to represent. +templateLiteralTypes1.ts(254,7): error TS2590: Expression produces a union type that is too complex to represent. +templateLiteralTypes1.ts(254,14): error TS2590: Expression produces a union type that is too complex to represent. -==== templateLiteralTypes1.ts (7 errors) ==== +==== templateLiteralTypes1.ts (10 errors) ==== // Template types example from #12754 const createScopedActionType = (scope: S) => (type: T) => `${scope}/${type}` as `${S}/${T}`; @@ -275,10 +278,18 @@ templateLiteralTypes1.ts(252,7): error TS2590: Expression produces a union type export type SpacingShorthand = | `${Spacing} ${Spacing}` + ~~~~~~~~~~~~~~~~~~~~~~~~~ | `${Spacing} ${Spacing} ${Spacing}` + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | `${Spacing} ${Spacing} ${Spacing} ${Spacing}`; + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2590: Expression produces a union type that is too complex to represent. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ !!! error TS2590: Expression produces a union type that is too complex to represent. const test1: SpacingShorthand = "0 0 0"; + ~~~~~ +!!! error TS2590: Expression produces a union type that is too complex to represent. + ~~~~~~~~~~~~~~~~ +!!! error TS2590: Expression produces a union type that is too complex to represent. \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/tsc/incremental/global-diagnostics-produced-during-semantic-checking.js b/tsc/testdata/baselines/reference/tsc/incremental/global-diagnostics-produced-during-semantic-checking.js index 3c629aeec7fcb..98bd4a0d2e0e1 100644 --- a/tsc/testdata/baselines/reference/tsc/incremental/global-diagnostics-produced-during-semantic-checking.js +++ b/tsc/testdata/baselines/reference/tsc/incremental/global-diagnostics-produced-during-semantic-checking.js @@ -191,10 +191,14 @@ export function* values() { yield 1; } tsgo -ExitStatus:: Success +ExitStatus:: DiagnosticsPresent_OutputsGenerated Output:: +error TS2318: Cannot find global type 'IterableIterator'. + +Found 1 error. + //// [/home/src/workspaces/project/tsconfig.tsbuildinfo] *modified* -{"version":"FakeTSVersion","errors":true,"root":[2],"fileNames":["lib.es2026.full.d.ts","./repro.ts"],"fileInfos":[{"version":"8859c12c614ce56ba9a18e58384a198f-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"303d5119b8941b887ef975780b83caec-export function* values() { yield 1; }\n// comment-only edit\n","signature":"b9f7a0d32d9887fdd864bf1c0dcd3591-export declare function values(): {};\n","impliedNodeFormat":1}],"affectedFilesPendingEmit":[2]} +{"version":"FakeTSVersion","errors":true,"root":[2],"fileNames":["lib.es2026.full.d.ts","./repro.ts"],"fileInfos":[{"version":"8859c12c614ce56ba9a18e58384a198f-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"303d5119b8941b887ef975780b83caec-export function* values() { yield 1; }\n// comment-only edit\n","signature":"b9f7a0d32d9887fdd864bf1c0dcd3591-export declare function values(): {};\n","impliedNodeFormat":1}],"semanticDiagnosticsPerFile":[[2,[{"noFile":true,"code":2318,"category":1,"messageKey":"Cannot_find_global_type_0_2318","messageArgs":["IterableIterator"]}]]],"affectedFilesPendingEmit":[2]} //// [/home/src/workspaces/project/tsconfig.tsbuildinfo.readable.baseline.txt] *modified* { "version": "FakeTSVersion", @@ -236,6 +240,22 @@ Output:: } } ], + "semanticDiagnosticsPerFile": [ + [ + "./repro.ts", + [ + { + "noFile": true, + "code": 2318, + "category": 1, + "messageKey": "Cannot_find_global_type_0_2318", + "messageArgs": [ + "IterableIterator" + ] + } + ] + ] + ], "affectedFilesPendingEmit": [ [ "./repro.ts", @@ -243,7 +263,7 @@ Output:: 2 ] ], - "size": 1136 + "size": 1295 } tsconfig.json:: @@ -253,22 +273,17 @@ Signatures:: (computed .d.ts) /home/src/workspaces/project/repro.ts -Diff:: Like Strada, signature generation produces the missing-global diagnostic before semantic checking, so it is excluded from the file's semantic diagnostics. ---- nonIncremental.output.txt -+++ incremental.output.txt -@@ -1,4 +0,0 @@ --error TS2318: Cannot find global type 'IterableIterator'. -- --Found 1 error. -- - Edit [2]:: no change tsgo -ExitStatus:: Success +ExitStatus:: DiagnosticsPresent_OutputsGenerated Output:: +error TS2318: Cannot find global type 'IterableIterator'. + +Found 1 error. + //// [/home/src/workspaces/project/tsconfig.tsbuildinfo] *modified* -{"version":"FakeTSVersion","root":[2],"fileNames":["lib.es2026.full.d.ts","./repro.ts"],"fileInfos":[{"version":"8859c12c614ce56ba9a18e58384a198f-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"303d5119b8941b887ef975780b83caec-export function* values() { yield 1; }\n// comment-only edit\n","signature":"b9f7a0d32d9887fdd864bf1c0dcd3591-export declare function values(): {};\n","impliedNodeFormat":1}],"affectedFilesPendingEmit":[2]} +{"version":"FakeTSVersion","root":[2],"fileNames":["lib.es2026.full.d.ts","./repro.ts"],"fileInfos":[{"version":"8859c12c614ce56ba9a18e58384a198f-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"303d5119b8941b887ef975780b83caec-export function* values() { yield 1; }\n// comment-only edit\n","signature":"b9f7a0d32d9887fdd864bf1c0dcd3591-export declare function values(): {};\n","impliedNodeFormat":1}],"semanticDiagnosticsPerFile":[[2,[{"noFile":true,"code":2318,"category":1,"messageKey":"Cannot_find_global_type_0_2318","messageArgs":["IterableIterator"]}]]],"affectedFilesPendingEmit":[2]} //// [/home/src/workspaces/project/tsconfig.tsbuildinfo.readable.baseline.txt] *modified* { "version": "FakeTSVersion", @@ -309,6 +324,22 @@ Output:: } } ], + "semanticDiagnosticsPerFile": [ + [ + "./repro.ts", + [ + { + "noFile": true, + "code": 2318, + "category": 1, + "messageKey": "Cannot_find_global_type_0_2318", + "messageArgs": [ + "IterableIterator" + ] + } + ] + ] + ], "affectedFilesPendingEmit": [ [ "./repro.ts", @@ -316,7 +347,7 @@ Output:: 2 ] ], - "size": 1122 + "size": 1281 } tsconfig.json:: @@ -324,15 +355,6 @@ SemanticDiagnostics:: Signatures:: -Diff:: Like Strada, the cached semantic diagnostics do not include the missing-global diagnostic produced during signature generation. ---- nonIncremental.output.txt -+++ incremental.output.txt -@@ -1,4 +0,0 @@ --error TS2318: Cannot find global type 'IterableIterator'. -- --Found 1 error. -- - Edit [3]:: delete build info to restore the semantic diagnostic //// [/home/src/workspaces/project/tsconfig.tsbuildinfo] *deleted* diff --git a/tsc/testdata/baselines/reference/tsc/incremental/recursive-mapped-type-after-comment-only-edit.js b/tsc/testdata/baselines/reference/tsc/incremental/recursive-mapped-type-after-comment-only-edit.js index 7e3f0da04d4e6..610f7fa7e72b5 100644 --- a/tsc/testdata/baselines/reference/tsc/incremental/recursive-mapped-type-after-comment-only-edit.js +++ b/tsc/testdata/baselines/reference/tsc/incremental/recursive-mapped-type-after-comment-only-edit.js @@ -105,11 +105,10 @@ tsgo ExitStatus:: Success Output:: //// [/home/src/workspaces/project/tsconfig.tsbuildinfo] *modified* -{"version":"FakeTSVersion","errors":true,"root":[2],"fileNames":["lib.es2026.full.d.ts","./repro.ts"],"fileInfos":[{"version":"0e330bc1e98a7e2601e4b38548df5e2b-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray { readonly length: number; readonly [n: number]: T; }\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"05e6c83fc809e0d08962e5b3425da798-type Json = string | Json[];\ntype Parsed = T extends object ? { [K in keyof T]: Parsed } : T;\ndeclare function wrap(value: T): Parsed;\nexport const value = wrap({ items: [] as Json[] });\n// comment-only edit\n","signature":"93e92d0786a923286e91eb7834b5ae1a-type Json = string | Json[];\nexport declare const value: {\n items: Json[];\n};\nexport {};\n","impliedNodeFormat":1}],"options":{"strict":true},"affectedFilesPendingEmit":[2]} +{"version":"FakeTSVersion","root":[2],"fileNames":["lib.es2026.full.d.ts","./repro.ts"],"fileInfos":[{"version":"0e330bc1e98a7e2601e4b38548df5e2b-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray { readonly length: number; readonly [n: number]: T; }\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"05e6c83fc809e0d08962e5b3425da798-type Json = string | Json[];\ntype Parsed = T extends object ? { [K in keyof T]: Parsed } : T;\ndeclare function wrap(value: T): Parsed;\nexport const value = wrap({ items: [] as Json[] });\n// comment-only edit\n","signature":"93e92d0786a923286e91eb7834b5ae1a-type Json = string | Json[];\nexport declare const value: {\n items: Json[];\n};\nexport {};\n","impliedNodeFormat":1}],"options":{"strict":true},"affectedFilesPendingEmit":[2]} //// [/home/src/workspaces/project/tsconfig.tsbuildinfo.readable.baseline.txt] *modified* { "version": "FakeTSVersion", - "errors": true, "root": [ { "files": [ @@ -157,7 +156,7 @@ Output:: 2 ] ], - "size": 1437 + "size": 1423 } tsconfig.json:: @@ -172,60 +171,6 @@ Edit [1]:: no change tsgo ExitStatus:: Success Output:: -//// [/home/src/workspaces/project/tsconfig.tsbuildinfo] *modified* -{"version":"FakeTSVersion","root":[2],"fileNames":["lib.es2026.full.d.ts","./repro.ts"],"fileInfos":[{"version":"0e330bc1e98a7e2601e4b38548df5e2b-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray { readonly length: number; readonly [n: number]: T; }\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"05e6c83fc809e0d08962e5b3425da798-type Json = string | Json[];\ntype Parsed = T extends object ? { [K in keyof T]: Parsed } : T;\ndeclare function wrap(value: T): Parsed;\nexport const value = wrap({ items: [] as Json[] });\n// comment-only edit\n","signature":"93e92d0786a923286e91eb7834b5ae1a-type Json = string | Json[];\nexport declare const value: {\n items: Json[];\n};\nexport {};\n","impliedNodeFormat":1}],"options":{"strict":true},"affectedFilesPendingEmit":[2]} -//// [/home/src/workspaces/project/tsconfig.tsbuildinfo.readable.baseline.txt] *modified* -{ - "version": "FakeTSVersion", - "root": [ - { - "files": [ - "./repro.ts" - ], - "original": 2 - } - ], - "fileNames": [ - "lib.es2026.full.d.ts", - "./repro.ts" - ], - "fileInfos": [ - { - "fileName": "lib.es2026.full.d.ts", - "version": "0e330bc1e98a7e2601e4b38548df5e2b-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray { readonly length: number; readonly [n: number]: T; }\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };", - "signature": "0e330bc1e98a7e2601e4b38548df5e2b-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray { readonly length: number; readonly [n: number]: T; }\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };", - "affectsGlobalScope": true, - "impliedNodeFormat": "CommonJS", - "original": { - "version": "0e330bc1e98a7e2601e4b38548df5e2b-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray { readonly length: number; readonly [n: number]: T; }\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };", - "affectsGlobalScope": true, - "impliedNodeFormat": 1 - } - }, - { - "fileName": "./repro.ts", - "version": "05e6c83fc809e0d08962e5b3425da798-type Json = string | Json[];\ntype Parsed = T extends object ? { [K in keyof T]: Parsed } : T;\ndeclare function wrap(value: T): Parsed;\nexport const value = wrap({ items: [] as Json[] });\n// comment-only edit\n", - "signature": "93e92d0786a923286e91eb7834b5ae1a-type Json = string | Json[];\nexport declare const value: {\n items: Json[];\n};\nexport {};\n", - "impliedNodeFormat": "CommonJS", - "original": { - "version": "05e6c83fc809e0d08962e5b3425da798-type Json = string | Json[];\ntype Parsed = T extends object ? { [K in keyof T]: Parsed } : T;\ndeclare function wrap(value: T): Parsed;\nexport const value = wrap({ items: [] as Json[] });\n// comment-only edit\n", - "signature": "93e92d0786a923286e91eb7834b5ae1a-type Json = string | Json[];\nexport declare const value: {\n items: Json[];\n};\nexport {};\n", - "impliedNodeFormat": 1 - } - } - ], - "options": { - "strict": true - }, - "affectedFilesPendingEmit": [ - [ - "./repro.ts", - "Js", - 2 - ] - ], - "size": 1423 -} tsconfig.json:: SemanticDiagnostics:: @@ -247,11 +192,10 @@ tsgo ExitStatus:: Success Output:: //// [/home/src/workspaces/project/tsconfig.tsbuildinfo] *modified* -{"version":"FakeTSVersion","errors":true,"root":[2],"fileNames":["lib.es2026.full.d.ts","./repro.ts"],"fileInfos":[{"version":"0e330bc1e98a7e2601e4b38548df5e2b-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray { readonly length: number; readonly [n: number]: T; }\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"989f15878b40fd4e572d65e47a8cc845-type Json = string | Json[];\ntype Parsed = T extends object ? { [K in keyof T]: Parsed } : T;\ndeclare function wrap(value: T): Parsed;\nexport const value = wrap({ items: [] as Json[] });\n// comment-only edit\n\n// another comment\n","signature":"93e92d0786a923286e91eb7834b5ae1a-type Json = string | Json[];\nexport declare const value: {\n items: Json[];\n};\nexport {};\n","impliedNodeFormat":1}],"options":{"strict":true},"affectedFilesPendingEmit":[2]} +{"version":"FakeTSVersion","root":[2],"fileNames":["lib.es2026.full.d.ts","./repro.ts"],"fileInfos":[{"version":"0e330bc1e98a7e2601e4b38548df5e2b-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray { readonly length: number; readonly [n: number]: T; }\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"989f15878b40fd4e572d65e47a8cc845-type Json = string | Json[];\ntype Parsed = T extends object ? { [K in keyof T]: Parsed } : T;\ndeclare function wrap(value: T): Parsed;\nexport const value = wrap({ items: [] as Json[] });\n// comment-only edit\n\n// another comment\n","signature":"93e92d0786a923286e91eb7834b5ae1a-type Json = string | Json[];\nexport declare const value: {\n items: Json[];\n};\nexport {};\n","impliedNodeFormat":1}],"options":{"strict":true},"affectedFilesPendingEmit":[2]} //// [/home/src/workspaces/project/tsconfig.tsbuildinfo.readable.baseline.txt] *modified* { "version": "FakeTSVersion", - "errors": true, "root": [ { "files": [ @@ -299,7 +243,7 @@ Output:: 2 ] ], - "size": 1459 + "size": 1445 } tsconfig.json:: @@ -314,60 +258,6 @@ Edit [3]:: no change tsgo ExitStatus:: Success Output:: -//// [/home/src/workspaces/project/tsconfig.tsbuildinfo] *modified* -{"version":"FakeTSVersion","root":[2],"fileNames":["lib.es2026.full.d.ts","./repro.ts"],"fileInfos":[{"version":"0e330bc1e98a7e2601e4b38548df5e2b-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray { readonly length: number; readonly [n: number]: T; }\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"989f15878b40fd4e572d65e47a8cc845-type Json = string | Json[];\ntype Parsed = T extends object ? { [K in keyof T]: Parsed } : T;\ndeclare function wrap(value: T): Parsed;\nexport const value = wrap({ items: [] as Json[] });\n// comment-only edit\n\n// another comment\n","signature":"93e92d0786a923286e91eb7834b5ae1a-type Json = string | Json[];\nexport declare const value: {\n items: Json[];\n};\nexport {};\n","impliedNodeFormat":1}],"options":{"strict":true},"affectedFilesPendingEmit":[2]} -//// [/home/src/workspaces/project/tsconfig.tsbuildinfo.readable.baseline.txt] *modified* -{ - "version": "FakeTSVersion", - "root": [ - { - "files": [ - "./repro.ts" - ], - "original": 2 - } - ], - "fileNames": [ - "lib.es2026.full.d.ts", - "./repro.ts" - ], - "fileInfos": [ - { - "fileName": "lib.es2026.full.d.ts", - "version": "0e330bc1e98a7e2601e4b38548df5e2b-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray { readonly length: number; readonly [n: number]: T; }\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };", - "signature": "0e330bc1e98a7e2601e4b38548df5e2b-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray { readonly length: number; readonly [n: number]: T; }\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };", - "affectsGlobalScope": true, - "impliedNodeFormat": "CommonJS", - "original": { - "version": "0e330bc1e98a7e2601e4b38548df5e2b-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray { readonly length: number; readonly [n: number]: T; }\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };", - "affectsGlobalScope": true, - "impliedNodeFormat": 1 - } - }, - { - "fileName": "./repro.ts", - "version": "989f15878b40fd4e572d65e47a8cc845-type Json = string | Json[];\ntype Parsed = T extends object ? { [K in keyof T]: Parsed } : T;\ndeclare function wrap(value: T): Parsed;\nexport const value = wrap({ items: [] as Json[] });\n// comment-only edit\n\n// another comment\n", - "signature": "93e92d0786a923286e91eb7834b5ae1a-type Json = string | Json[];\nexport declare const value: {\n items: Json[];\n};\nexport {};\n", - "impliedNodeFormat": "CommonJS", - "original": { - "version": "989f15878b40fd4e572d65e47a8cc845-type Json = string | Json[];\ntype Parsed = T extends object ? { [K in keyof T]: Parsed } : T;\ndeclare function wrap(value: T): Parsed;\nexport const value = wrap({ items: [] as Json[] });\n// comment-only edit\n\n// another comment\n", - "signature": "93e92d0786a923286e91eb7834b5ae1a-type Json = string | Json[];\nexport declare const value: {\n items: Json[];\n};\nexport {};\n", - "impliedNodeFormat": 1 - } - } - ], - "options": { - "strict": true - }, - "affectedFilesPendingEmit": [ - [ - "./repro.ts", - "Js", - 2 - ] - ], - "size": 1445 -} tsconfig.json:: SemanticDiagnostics:: diff --git a/tsc/testdata/baselines/reference/tsc/incremental/recursive-readonly-mapped-type-after-comment-only-edit.js b/tsc/testdata/baselines/reference/tsc/incremental/recursive-readonly-mapped-type-after-comment-only-edit.js index 009ea164deed4..211fd77b970ec 100644 --- a/tsc/testdata/baselines/reference/tsc/incremental/recursive-readonly-mapped-type-after-comment-only-edit.js +++ b/tsc/testdata/baselines/reference/tsc/incremental/recursive-readonly-mapped-type-after-comment-only-edit.js @@ -105,11 +105,10 @@ tsgo ExitStatus:: Success Output:: //// [/home/src/workspaces/project/tsconfig.tsbuildinfo] *modified* -{"version":"FakeTSVersion","errors":true,"root":[2],"fileNames":["lib.es2026.full.d.ts","./repro.ts"],"fileInfos":[{"version":"0e330bc1e98a7e2601e4b38548df5e2b-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray { readonly length: number; readonly [n: number]: T; }\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"44adeee334483dab0e3927db949cf78a-type Json = string | readonly Json[];\ntype Parsed = T extends object ? { [K in keyof T]: Parsed } : T;\ndeclare function wrap(value: T): Parsed;\nexport const value = wrap({ items: [] as readonly Json[] });\n// comment-only edit\n","signature":"ac9ba36fbad04eb70b8f2b14cdf772ef-type Json = string | readonly Json[];\nexport declare const value: {\n items: readonly Json[];\n};\nexport {};\n","impliedNodeFormat":1}],"options":{"strict":true},"affectedFilesPendingEmit":[2]} +{"version":"FakeTSVersion","root":[2],"fileNames":["lib.es2026.full.d.ts","./repro.ts"],"fileInfos":[{"version":"0e330bc1e98a7e2601e4b38548df5e2b-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray { readonly length: number; readonly [n: number]: T; }\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"44adeee334483dab0e3927db949cf78a-type Json = string | readonly Json[];\ntype Parsed = T extends object ? { [K in keyof T]: Parsed } : T;\ndeclare function wrap(value: T): Parsed;\nexport const value = wrap({ items: [] as readonly Json[] });\n// comment-only edit\n","signature":"ac9ba36fbad04eb70b8f2b14cdf772ef-type Json = string | readonly Json[];\nexport declare const value: {\n items: readonly Json[];\n};\nexport {};\n","impliedNodeFormat":1}],"options":{"strict":true},"affectedFilesPendingEmit":[2]} //// [/home/src/workspaces/project/tsconfig.tsbuildinfo.readable.baseline.txt] *modified* { "version": "FakeTSVersion", - "errors": true, "root": [ { "files": [ @@ -157,7 +156,7 @@ Output:: 2 ] ], - "size": 1473 + "size": 1459 } tsconfig.json:: @@ -172,60 +171,6 @@ Edit [1]:: no change tsgo ExitStatus:: Success Output:: -//// [/home/src/workspaces/project/tsconfig.tsbuildinfo] *modified* -{"version":"FakeTSVersion","root":[2],"fileNames":["lib.es2026.full.d.ts","./repro.ts"],"fileInfos":[{"version":"0e330bc1e98a7e2601e4b38548df5e2b-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray { readonly length: number; readonly [n: number]: T; }\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"44adeee334483dab0e3927db949cf78a-type Json = string | readonly Json[];\ntype Parsed = T extends object ? { [K in keyof T]: Parsed } : T;\ndeclare function wrap(value: T): Parsed;\nexport const value = wrap({ items: [] as readonly Json[] });\n// comment-only edit\n","signature":"ac9ba36fbad04eb70b8f2b14cdf772ef-type Json = string | readonly Json[];\nexport declare const value: {\n items: readonly Json[];\n};\nexport {};\n","impliedNodeFormat":1}],"options":{"strict":true},"affectedFilesPendingEmit":[2]} -//// [/home/src/workspaces/project/tsconfig.tsbuildinfo.readable.baseline.txt] *modified* -{ - "version": "FakeTSVersion", - "root": [ - { - "files": [ - "./repro.ts" - ], - "original": 2 - } - ], - "fileNames": [ - "lib.es2026.full.d.ts", - "./repro.ts" - ], - "fileInfos": [ - { - "fileName": "lib.es2026.full.d.ts", - "version": "0e330bc1e98a7e2601e4b38548df5e2b-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray { readonly length: number; readonly [n: number]: T; }\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };", - "signature": "0e330bc1e98a7e2601e4b38548df5e2b-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray { readonly length: number; readonly [n: number]: T; }\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };", - "affectsGlobalScope": true, - "impliedNodeFormat": "CommonJS", - "original": { - "version": "0e330bc1e98a7e2601e4b38548df5e2b-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray { readonly length: number; readonly [n: number]: T; }\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };", - "affectsGlobalScope": true, - "impliedNodeFormat": 1 - } - }, - { - "fileName": "./repro.ts", - "version": "44adeee334483dab0e3927db949cf78a-type Json = string | readonly Json[];\ntype Parsed = T extends object ? { [K in keyof T]: Parsed } : T;\ndeclare function wrap(value: T): Parsed;\nexport const value = wrap({ items: [] as readonly Json[] });\n// comment-only edit\n", - "signature": "ac9ba36fbad04eb70b8f2b14cdf772ef-type Json = string | readonly Json[];\nexport declare const value: {\n items: readonly Json[];\n};\nexport {};\n", - "impliedNodeFormat": "CommonJS", - "original": { - "version": "44adeee334483dab0e3927db949cf78a-type Json = string | readonly Json[];\ntype Parsed = T extends object ? { [K in keyof T]: Parsed } : T;\ndeclare function wrap(value: T): Parsed;\nexport const value = wrap({ items: [] as readonly Json[] });\n// comment-only edit\n", - "signature": "ac9ba36fbad04eb70b8f2b14cdf772ef-type Json = string | readonly Json[];\nexport declare const value: {\n items: readonly Json[];\n};\nexport {};\n", - "impliedNodeFormat": 1 - } - } - ], - "options": { - "strict": true - }, - "affectedFilesPendingEmit": [ - [ - "./repro.ts", - "Js", - 2 - ] - ], - "size": 1459 -} tsconfig.json:: SemanticDiagnostics:: @@ -247,11 +192,10 @@ tsgo ExitStatus:: Success Output:: //// [/home/src/workspaces/project/tsconfig.tsbuildinfo] *modified* -{"version":"FakeTSVersion","errors":true,"root":[2],"fileNames":["lib.es2026.full.d.ts","./repro.ts"],"fileInfos":[{"version":"0e330bc1e98a7e2601e4b38548df5e2b-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray { readonly length: number; readonly [n: number]: T; }\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"123beaefc70ba63a9447b94fb12d09b5-type Json = string | readonly Json[];\ntype Parsed = T extends object ? { [K in keyof T]: Parsed } : T;\ndeclare function wrap(value: T): Parsed;\nexport const value = wrap({ items: [] as readonly Json[] });\n// comment-only edit\n\n// another comment\n","signature":"ac9ba36fbad04eb70b8f2b14cdf772ef-type Json = string | readonly Json[];\nexport declare const value: {\n items: readonly Json[];\n};\nexport {};\n","impliedNodeFormat":1}],"options":{"strict":true},"affectedFilesPendingEmit":[2]} +{"version":"FakeTSVersion","root":[2],"fileNames":["lib.es2026.full.d.ts","./repro.ts"],"fileInfos":[{"version":"0e330bc1e98a7e2601e4b38548df5e2b-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray { readonly length: number; readonly [n: number]: T; }\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"123beaefc70ba63a9447b94fb12d09b5-type Json = string | readonly Json[];\ntype Parsed = T extends object ? { [K in keyof T]: Parsed } : T;\ndeclare function wrap(value: T): Parsed;\nexport const value = wrap({ items: [] as readonly Json[] });\n// comment-only edit\n\n// another comment\n","signature":"ac9ba36fbad04eb70b8f2b14cdf772ef-type Json = string | readonly Json[];\nexport declare const value: {\n items: readonly Json[];\n};\nexport {};\n","impliedNodeFormat":1}],"options":{"strict":true},"affectedFilesPendingEmit":[2]} //// [/home/src/workspaces/project/tsconfig.tsbuildinfo.readable.baseline.txt] *modified* { "version": "FakeTSVersion", - "errors": true, "root": [ { "files": [ @@ -299,7 +243,7 @@ Output:: 2 ] ], - "size": 1495 + "size": 1481 } tsconfig.json:: @@ -314,60 +258,6 @@ Edit [3]:: no change tsgo ExitStatus:: Success Output:: -//// [/home/src/workspaces/project/tsconfig.tsbuildinfo] *modified* -{"version":"FakeTSVersion","root":[2],"fileNames":["lib.es2026.full.d.ts","./repro.ts"],"fileInfos":[{"version":"0e330bc1e98a7e2601e4b38548df5e2b-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray { readonly length: number; readonly [n: number]: T; }\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"123beaefc70ba63a9447b94fb12d09b5-type Json = string | readonly Json[];\ntype Parsed = T extends object ? { [K in keyof T]: Parsed } : T;\ndeclare function wrap(value: T): Parsed;\nexport const value = wrap({ items: [] as readonly Json[] });\n// comment-only edit\n\n// another comment\n","signature":"ac9ba36fbad04eb70b8f2b14cdf772ef-type Json = string | readonly Json[];\nexport declare const value: {\n items: readonly Json[];\n};\nexport {};\n","impliedNodeFormat":1}],"options":{"strict":true},"affectedFilesPendingEmit":[2]} -//// [/home/src/workspaces/project/tsconfig.tsbuildinfo.readable.baseline.txt] *modified* -{ - "version": "FakeTSVersion", - "root": [ - { - "files": [ - "./repro.ts" - ], - "original": 2 - } - ], - "fileNames": [ - "lib.es2026.full.d.ts", - "./repro.ts" - ], - "fileInfos": [ - { - "fileName": "lib.es2026.full.d.ts", - "version": "0e330bc1e98a7e2601e4b38548df5e2b-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray { readonly length: number; readonly [n: number]: T; }\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };", - "signature": "0e330bc1e98a7e2601e4b38548df5e2b-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray { readonly length: number; readonly [n: number]: T; }\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };", - "affectsGlobalScope": true, - "impliedNodeFormat": "CommonJS", - "original": { - "version": "0e330bc1e98a7e2601e4b38548df5e2b-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray { readonly length: number; readonly [n: number]: T; }\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };", - "affectsGlobalScope": true, - "impliedNodeFormat": 1 - } - }, - { - "fileName": "./repro.ts", - "version": "123beaefc70ba63a9447b94fb12d09b5-type Json = string | readonly Json[];\ntype Parsed = T extends object ? { [K in keyof T]: Parsed } : T;\ndeclare function wrap(value: T): Parsed;\nexport const value = wrap({ items: [] as readonly Json[] });\n// comment-only edit\n\n// another comment\n", - "signature": "ac9ba36fbad04eb70b8f2b14cdf772ef-type Json = string | readonly Json[];\nexport declare const value: {\n items: readonly Json[];\n};\nexport {};\n", - "impliedNodeFormat": "CommonJS", - "original": { - "version": "123beaefc70ba63a9447b94fb12d09b5-type Json = string | readonly Json[];\ntype Parsed = T extends object ? { [K in keyof T]: Parsed } : T;\ndeclare function wrap(value: T): Parsed;\nexport const value = wrap({ items: [] as readonly Json[] });\n// comment-only edit\n\n// another comment\n", - "signature": "ac9ba36fbad04eb70b8f2b14cdf772ef-type Json = string | readonly Json[];\nexport declare const value: {\n items: readonly Json[];\n};\nexport {};\n", - "impliedNodeFormat": 1 - } - } - ], - "options": { - "strict": true - }, - "affectedFilesPendingEmit": [ - [ - "./repro.ts", - "Js", - 2 - ] - ], - "size": 1481 -} tsconfig.json:: SemanticDiagnostics:: diff --git a/tsc/testdata/tests/cases/compiler/contextualCheckInDiscardedCycle.ts b/tsc/testdata/tests/cases/compiler/contextualCheckInDiscardedCycle.ts new file mode 100644 index 0000000000000..f7ecd882156e7 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/contextualCheckInDiscardedCycle.ts @@ -0,0 +1,39 @@ +// @strict: true +// @target: esnext +// @noEmit: true + +// @filename: returnTypeCycle.ts +// Callbacks contextually checked while a cycle is in progress are checked +// again once it is discarded, with their contextual parameter types. + +declare function useCallback(callback: T): T; +declare function refreshAll(options: { ids: string[]; extra: T }): Promise; + +interface View { + refresh: (force?: boolean) => Promise; +} + +declare const items: { id: string }[]; + +function useView(): View { + const refresh = useCallback((force?: boolean) => + refreshAll({ + ids: items.map(({ id }) => id), + extra: 1, + })); + return { refresh }; +} + +// @filename: yieldOperandCycle.ts +interface Eff { + [Symbol.iterator](): Generator, A, any>; + pipe(f: (self: Eff) => B): B; +} +declare function mapError(f: (e: E) => E2): (self: Eff) => Eff; +declare function fn(body: (input: number) => Generator): (input: number) => Eff; +declare function make(s: T): T; +declare const uuid: Eff; + +fn(function* (input) { + make(yield* uuid.pipe(mapError((cause) => ({ cause })))); +}); diff --git a/tsc/testdata/tests/cases/compiler/contextualCheckReentryAfterParameters.ts b/tsc/testdata/tests/cases/compiler/contextualCheckReentryAfterParameters.ts new file mode 100644 index 0000000000000..3d4cac45ed898 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/contextualCheckReentryAfterParameters.ts @@ -0,0 +1,41 @@ +// @strict: true +// @noEmit: true + +// Instantiating the type predicate inside the nested callback re-enters the +// contextual check of the enclosing functions through their outer type +// parameters. That must not discard the parameter types assigned to them. + +type Surface = { id: string; kind: "file"; path: string } | { id: string; kind: "terminal" }; +interface Panel { + surfaces: Surface[]; +} +interface Store { + panel: Panel; + openFile: (path: string) => void; + close: (id: string) => void; +} +type Creator = (set: (update: (state: T) => Partial) => void) => T; +declare function create(init: Creator): T; +declare function wrap(init: Creator): Creator; + +const update = (state: Store, updater: (current: Panel) => Panel): Partial => ({ panel: updater(state.panel) }); + +export const store = create(wrap((set) => ({ + panel: { surfaces: [] }, + openFile: (path) => + set((state) => + update(state, (current) => { + const existing = current.surfaces.find( + (surface): surface is Extract => surface.kind === "file" && surface.path === path, + ); + return existing ? current : { surfaces: [...current.surfaces, { id: path, kind: "file", path }] }; + }) + ), + close: (id) => + set((state) => + update(state, (current) => { + const index = current.surfaces.findIndex((entry) => entry.id === id); + return { surfaces: current.surfaces.slice(index) }; + }) + ), +}))); diff --git a/tsc/testdata/tests/cases/compiler/contextualReturnTypeInSpreadArgument.ts b/tsc/testdata/tests/cases/compiler/contextualReturnTypeInSpreadArgument.ts new file mode 100644 index 0000000000000..d047462f7c312 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/contextualReturnTypeInSpreadArgument.ts @@ -0,0 +1,18 @@ +// @strict: true + +enum Kind { A, B } + +interface ItemA { kind: Kind.A; name: string } +interface ItemB { kind: Kind.B; size: number } +type Item = ItemA | ItemB; + +declare function create(options: { items?: () => readonly Item[]; label: string }): void; + +function test(flag: boolean) { + create({ + label: "x", + ...(flag ? {} : { + items: () => [{ kind: Kind.A, name: "a" }], + }), + }); +} diff --git a/tsc/testdata/tests/cases/compiler/jsExpandoIndexedAssignmentDiagnostic.ts b/tsc/testdata/tests/cases/compiler/jsExpandoIndexedAssignmentDiagnostic.ts new file mode 100644 index 0000000000000..33c25e649c64e --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/jsExpandoIndexedAssignmentDiagnostic.ts @@ -0,0 +1,13 @@ +// @target: esnext +// @checkJs: true +// @noImplicitAny: true +// @outDir: out +// @filename: file.js + +/** @param {any} axis @param {any} value */ +function updateElements(axis, value) { + const properties = {}; + const pixel = properties[axis] = value; + const otherPixel = properties[axis] = value; + properties.skip = isNaN(pixel) || isNaN(otherPixel); +} \ No newline at end of file diff --git a/tsc/testdata/tests/cases/compiler/lateBoundMutualMemberTables.ts b/tsc/testdata/tests/cases/compiler/lateBoundMutualMemberTables.ts new file mode 100644 index 0000000000000..24336a6a9155b --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/lateBoundMutualMemberTables.ts @@ -0,0 +1,20 @@ +// @target: esnext +// @strict: true +// @declaration: true + +declare const a: A<"a">; +declare const b: B<"b">; +declare class A { + x: T; + [b.y]: "fromB"; + [b.by]: "fromB2"; +} +declare class B { + y: T; + [a.x]: "fromA"; + [a.b]: "fromA2"; +} +export const v1 = a.b; +export const v2 = b.a; +export const v3 = a.fromA2; +export const v4 = b.fromB; From 017b2e5d23b0476b2ee168d789115cf4d82101d7 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 22 Oct 2026 09:49:12 -0700 Subject: [PATCH 2/9] Do not blame a use for the circular constraint of a declaration A declared type parameter with a circular constraint is circular no matter how it is used, and checking its declaration reports it. Which use happened to resolve the constraint before that is an accident of check order, so pointing at it as the origin of the cycle made the diagnostic depend on that order. Only an instantiated type parameter has a cycle that originates at a use. --- tsc/internal/checker/checker.go | 6 +++++- .../reference/conformance/recursiveMappedTypes.errors.txt | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index c199490f34389..ae5423b3a16d0 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -30480,9 +30480,13 @@ func (c *Checker) getResolvedBaseConstraintUncached(t *Type, stack []RecursionId // circularity is intrinsic to the declarations, so it is reported once // and the circular result is kept. Discarding it would let a later // computation reuse cycle participants that hide the cycle. + // Only the cycle of an instantiation originates at a use; that of a + // declared type parameter is found by checking its declaration, and + // whichever use happens to find it first is no origin. + instantiated := t.AsTypeParameter().target != nil c.reportAtCurrentNodeOnce(func(c *Checker, node *ast.Node) { diagnostic := c.error(errorNode, diagnostics.Type_parameter_0_has_a_circular_constraint, typeString) - if node != nil && !isNodeDescendantOf(errorNode, node) && !isNodeDescendantOf(node, errorNode) { + if instantiated && node != nil && !isNodeDescendantOf(errorNode, node) && !isNodeDescendantOf(node, errorNode) { diagnostic.AddRelatedInfo(NewDiagnosticForNode(node, diagnostics.Circularity_originates_in_type_at_this_location)) } }) diff --git a/tsc/testdata/baselines/reference/conformance/recursiveMappedTypes.errors.txt b/tsc/testdata/baselines/reference/conformance/recursiveMappedTypes.errors.txt index efdd2fe453ad5..9d4e77b1bfb1f 100644 --- a/tsc/testdata/baselines/reference/conformance/recursiveMappedTypes.errors.txt +++ b/tsc/testdata/baselines/reference/conformance/recursiveMappedTypes.errors.txt @@ -36,7 +36,6 @@ recursiveMappedTypes.ts(73,13): error TS2615: Type of property '"each"' circular [K in keyof Recurse1]: Recurse1[K] ~~~~~~~~~~~~~~ !!! error TS2313: Type parameter 'K' has a circular constraint. -!!! related TS2751 recursiveMappedTypes.ts:8:17: Circularity originates in type at this location. } // Repro from #27881 From bbb99ab2e9f63b7f1cbbbf324440e7850893f6f5 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 22 Oct 2026 16:11:34 -0700 Subject: [PATCH 3/9] Resolve mapped type constraints whenever the mapped type is requested A mapped type resolves its constraint eagerly so that errors in the constraint are reported at the mapped type. It did so only when the type was created, so when the type was first created while checking another node, the mapped type itself never reported those errors. --- tsc/internal/checker/checker.go | 11 +++++++---- ...siveLength(checkorder=typesfirst).errors.txt | 17 ++++++++++++++++- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index ae5423b3a16d0..fcdadccc7acfc 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -27155,11 +27155,14 @@ func (c *Checker) getTypeFromMappedTypeNode(node *ast.Node) *Type { t.AsMappedType().declaration = node.AsMappedTypeNode() t.alias = c.getAliasForTypeNode(node) links.resolvedType.set(c, t) - // Eagerly resolve the constraint type which forces an error if the constraint type circularly - // references itself through one or more type aliases. - c.getConstraintTypeFromMappedType(t) } - return links.resolvedType.get(c) + t := links.resolvedType.get(c) + // Eagerly resolve the constraint type which forces an error if the constraint type circularly + // references itself through one or more type aliases. This happens on every request rather + // than only when the type is created, so its errors do not depend on whether the type was + // created while checking another node. + c.getConstraintTypeFromMappedType(t) + return t } func (c *Checker) getTypeFromConditionalTypeNode(node *ast.Node) *Type { diff --git a/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength(checkorder=typesfirst).errors.txt b/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength(checkorder=typesfirst).errors.txt index 75017c0b48808..afb511fd91c39 100644 --- a/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength(checkorder=typesfirst).errors.txt +++ b/tsc/testdata/baselines/reference/compiler/templateLiteralTypeExcessiveLength(checkorder=typesfirst).errors.txt @@ -1,15 +1,18 @@ +templateLiteralTypeExcessiveLength.ts(14,16): error TS2589: Type instantiation is excessively deep and possibly infinite. templateLiteralTypeExcessiveLength.ts(15,6): error TS2589: Type instantiation is excessively deep and possibly infinite. templateLiteralTypeExcessiveLength.ts(15,11): error TS2589: Type instantiation is excessively deep and possibly infinite. templateLiteralTypeExcessiveLength.ts(15,28): error TS2589: Type instantiation is excessively deep and possibly infinite. +templateLiteralTypeExcessiveLength.ts(19,21): error TS2589: Type instantiation is excessively deep and possibly infinite. templateLiteralTypeExcessiveLength.ts(20,6): error TS2589: Type instantiation is excessively deep and possibly infinite. templateLiteralTypeExcessiveLength.ts(20,11): error TS2589: Type instantiation is excessively deep and possibly infinite. templateLiteralTypeExcessiveLength.ts(20,31): error TS2589: Type instantiation is excessively deep and possibly infinite. +templateLiteralTypeExcessiveLength.ts(33,24): error TS2589: Type instantiation is excessively deep and possibly infinite. templateLiteralTypeExcessiveLength.ts(34,6): error TS2589: Type instantiation is excessively deep and possibly infinite. templateLiteralTypeExcessiveLength.ts(34,11): error TS2589: Type instantiation is excessively deep and possibly infinite. templateLiteralTypeExcessiveLength.ts(34,38): error TS2589: Type instantiation is excessively deep and possibly infinite. -==== templateLiteralTypeExcessiveLength.ts (9 errors) ==== +==== templateLiteralTypeExcessiveLength.ts (12 errors) ==== // Regression test for #63271: `Dec<2>` is `any`, so `Recur` produces both conditional branches and // keeps tail-recursing forever. The checker must report an error instead of building an unbounded // template literal type. @@ -24,7 +27,9 @@ templateLiteralTypeExcessiveLength.ts(34,38): error TS2589: Type instantiation i // The string literal doubles on every iteration. type Explode = { + ~ [P in Recur<5, "a"> as `${P}_key`]: any; + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~ !!! error TS2589: Type instantiation is excessively deep and possibly infinite. ~~~~~~~~~~~~~ @@ -32,10 +37,14 @@ templateLiteralTypeExcessiveLength.ts(34,38): error TS2589: Type instantiation i ~~~~~~~~~~ !!! error TS2589: Type instantiation is excessively deep and possibly infinite. }; + ~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. // The number of placeholders doubles on every iteration. type ExplodeSpans = { + ~ [P in Recur<5, string> as `${P}_key`]: any; + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~ !!! error TS2589: Type instantiation is excessively deep and possibly infinite. ~~~~~~~~~~~~~~~~ @@ -43,6 +52,8 @@ templateLiteralTypeExcessiveLength.ts(34,38): error TS2589: Type instantiation i ~~~~~~~~~~ !!! error TS2589: Type instantiation is excessively deep and possibly infinite. }; + ~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. // Every text segment stays small, but the number of segments doubles on every iteration, // so the combined text length grows without bound. @@ -55,7 +66,9 @@ templateLiteralTypeExcessiveLength.ts(34,38): error TS2589: Type instantiation i N extends 0 ? S : RecurSegments, `${S}${string}${S}`>; type ExplodeSegments = { + ~ [P in RecurSegments<5, A1024> as `${P}_key`]: any; + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ !!! error TS2589: Type instantiation is excessively deep and possibly infinite. ~~~~~~~~~~~~~~~~~~~~~~~ @@ -63,4 +76,6 @@ templateLiteralTypeExcessiveLength.ts(34,38): error TS2589: Type instantiation i ~~~~~~~~~~ !!! error TS2589: Type instantiation is excessively deep and possibly infinite. }; + ~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. \ No newline at end of file From 15cf58f6bbbbc50e02eca59e4a9fa4a7e40eb35c Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Sat, 31 Oct 2026 21:32:24 -0700 Subject: [PATCH 4/9] Resolve return type cycles as if entered at their first member A cycle through return types gave different results depending on where it was entered. Entered at rebuild's return type, re-entering it fails rebuild, so both members report an implicit any. Entered at b's return type, subtype reduction of rebuild's return type finds b in progress and compares as if it returned any, without failing anything, so rebuild returns B and was kept as a participant of the cycle, though computing it from scratch fails. Cycles through the types of symbols are already resolved from the member declared first (see yieldCycle). The same now applies to the return types of declared signatures, both when a cycle re-enters a return type and when it only observes one in progress, so that each order gives the result of resolving the first declared member first (seen in effect's SchemaAST). Contextually checking a function expression infers its return type from its body, and that inference was pushed as a return type resolution of the function's signature. When the body calls a function whose return type depends on the call enclosing the function expression, resolving that call requests the function expression's return type, which, seen in progress, returned any without detecting a cycle, so the enclosing function's return type was inferred from a provisional any instead of reporting the circularity (seen in OmniRoute). The inference now runs without marking the signature in progress, as in TypeScript: the nested request computes the return type itself, reaches the enclosing function, and the cycle is reported on both. The result is cached only if no request has already cached one, the same as when the return type is requested directly. The flag marking a function as contextually checked is set before its return type is inferred, and is TypeScript's guard against re-checking the function from within its own check, so it is never discarded. The inferred return type is now kept whenever the flag is, so that a discarded cycle in between cannot leave the flag without the return type, which made a const assertion in the body readonly when queried before its binding pattern. --- tsc/internal/checker/checker.go | 147 ++++++++++++++---- tsc/internal/checker/relater.go | 13 +- ...ReturnTypeSubtypeReductionOrder.errors.txt | 8 +- ...cularReturnTypeSubtypeReductionOrder.types | 12 +- ...hCallbackArgument(strict=false).errors.txt | 38 ----- ...hroughCallbackArgument(strict=false).types | 24 +-- ...ghCallbackArgument(strict=true).errors.txt | 30 ++-- ...ThroughCallbackArgument(strict=true).types | 24 +-- ...ernQueryOrder(checkorder=typesfirst).types | 10 +- 9 files changed, 190 insertions(+), 116 deletions(-) delete mode 100644 tsc/testdata/baselines/reference/compiler/circularReturnTypeThroughCallbackArgument(strict=false).errors.txt diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index fcdadccc7acfc..13cb25169c98a 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -10808,23 +10808,30 @@ func (c *Checker) contextuallyCheckFunctionExpressionOrObjectLiteralMethodUncach if links.flags&NodeCheckFlagsContextChecked == 0 || links.flags&NodeCheckFlagsSignatureContextChecked != 0 { return } + // The flag guards against checking the function again from within the + // check, so it is set now, and is only discarded if a cycle in progress + // logs it. + markerLogged := c.logResults() setFlagMemoBits(c, memoKeyOf(node, memoContextChecked), &links.flags, NodeCheckFlagsSignatureContextChecked, NodeCheckFlagsSignatureContextChecked) signature := c.getSignatureFromDeclarationUncontextual(node) contextualSignature := c.getContextualSignature(node) // The return type is inferred now, while the contextual types of the // function's enclosing expressions are in place; a later request may // come from where they are not. - if contextualSignature != nil && checkMode&^CheckModeTypeOnly == CheckModeNormal && c.getReturnTypeFromAnnotation(node) == nil && !c.isResolvingReturnTypeOfSignature(signature) { - c.getReturnTypeOfSignature(signature) - } else if contextualSignature != nil && c.getReturnTypeFromAnnotation(node) == nil && signature.resolvedReturnType.get(c) == nil && !c.isResolvingReturnTypeOfSignature(signature) && c.pushTypeResolution(signature, TypeSystemPropertyNameResolvedReturnType) { + if contextualSignature != nil && c.getReturnTypeFromAnnotation(node) == nil && signature.resolvedReturnType.get(c) == nil && !c.isResolvingReturnTypeOfSignature(signature) { // resolvedReturnType is cached indefinitely, so the return type here has to be computed without CheckModeSkipContextSensitive; // otherwise anyFunctionType could leak as part of the computed (and cached) return type. returnType := c.getReturnTypeFromBody(node, checkMode&^CheckModeSkipContextSensitive) - if !c.popTypeResolution() { - returnType = c.circularReturnType(signature) - } if signature.resolvedReturnType.get(c) == nil { - signature.resolvedReturnType.set(c, returnType) + if markerLogged { + signature.resolvedReturnType.set(c, returnType) + } else { + // A cycle detected since the flag was set would discard the + // return type but keep the flag, so a later request would + // infer the return type without the contextual types in + // place now. Keep the return type with the flag instead. + signature.resolvedReturnType.setIdentity(c, returnType) + } } } c.checkSignatureDeclaration(node) @@ -20305,36 +20312,116 @@ type shadowedResolving struct { // yieldCycle handles re-entering the resolution in progress at start, a // cycle that would fail the resolution re-entered. Which resolution is -// re-entered depends on which was entered first, so a cycle through the -// types of symbols declared before the one re-entered (if it is one) -// fails at the first of those instead, as if that symbol had been resolved -// first: its type is computed again from scratch, with nothing in progress -// visible, and the resolution re-entered is then computed again likewise, -// by the caller, which pushes it. The computations in progress then continue -// from those results. Termination: a resolution is never computed again -// while it is already being computed again. +// re-entered depends on which was entered first, so a cycle through members +// declared before the one re-entered (see compareCycleMembers) fails at the +// first of those instead, as if it had been resolved first: it is computed +// again from scratch, with nothing in progress visible, and the resolution +// re-entered is then computed again likewise, by the caller, which pushes it. +// The computations in progress then continue from those results. +// Termination: a resolution is never computed again while it is already +// being computed again. func (c *Checker) yieldCycle(start int) bool { - r := &c.resolutions[start].typeResolution - var first *ast.Symbol - if r.propertyName == TypeSystemPropertyNameType { - first = r.target.(*ast.Symbol) + r := c.resolutions[start].typeResolution + if c.isYieldingTo(r.target, r.propertyName) { + return false + } + first := c.firstCycleMember(start) + if first == nil { + return false + } + c.resolveCycleMember(*first) + c.beginCycleYield(r.target, r.propertyName, len(c.resolutions), len(c.resolutions)) + return true +} + +// yieldObservedCycle handles observing the resolution in progress at start +// without failing it, as yieldCycle handles re-entering it: if members of +// the cycle are declared before the resolution observed, the first of them +// is computed again from scratch, as if it had been resolved first, so that +// the caller can use its result. +func (c *Checker) yieldObservedCycle(start int) { + if r := &c.resolutions[start].typeResolution; c.isYieldingTo(r.target, r.propertyName) { + return + } + if first := c.firstCycleMember(start); first != nil { + c.resolveCycleMember(*first) + } +} + +// firstCycleMember returns the member of the cycle from the resolution at +// start to the top of the stack that is resolved first, if it is not the +// resolution at start, and is not already being computed again. +func (c *Checker) firstCycleMember(start int) *TypeResolution { + entered := &c.resolutions[start].typeResolution + var first *TypeResolution + if isCycleMember(entered) { + first = entered } - entered := first for i := start + 1; i < len(c.resolutions); i++ { - if f := &c.resolutions[i]; f.isTypeResolution && f.typeResolution.propertyName == TypeSystemPropertyNameType { - if s := f.typeResolution.target.(*ast.Symbol); first == nil || c.compareSymbols(s, first) < 0 { - first = s - } + if f := &c.resolutions[i]; f.isTypeResolution && isCycleMember(&f.typeResolution) && (first == nil || c.compareCycleMembers(&f.typeResolution, first) < 0) { + first = &f.typeResolution } } - if first == nil || first == entered || c.isYieldingTo(first, TypeSystemPropertyNameType) || c.isYieldingTo(r.target, r.propertyName) { - return false + if first == nil || first == entered || c.isYieldingTo(first.target, first.propertyName) { + return nil + } + return first +} + +// isCycleMember reports whether a type resolution may be resolved first in +// place of the one a cycle re-entered: the type of a symbol, or the return +// type of a declared signature. +func isCycleMember(r *TypeResolution) bool { + switch r.propertyName { + case TypeSystemPropertyNameType: + return true + case TypeSystemPropertyNameResolvedReturnType: + sig := r.target.(*Signature) + return sig.target == nil && sig.getComposite() == nil && sig.declaration != nil + } + return false +} + +// compareCycleMembers orders the members of a cycle in program order of +// their declarations, symbols first among members with the same one. +func (c *Checker) compareCycleMembers(a, b *TypeResolution) int { + aSymbol, aIsSymbol := a.target.(*ast.Symbol) + bSymbol, bIsSymbol := b.target.(*ast.Symbol) + switch { + case aIsSymbol && bIsSymbol: + return c.compareSymbols(aSymbol, bSymbol) + case aIsSymbol: + return c.compareSymbolToNode(aSymbol, b.target.(*Signature).declaration) + case bIsSymbol: + return -c.compareSymbolToNode(bSymbol, a.target.(*Signature).declaration) + } + return c.compareNodes(a.target.(*Signature).declaration, b.target.(*Signature).declaration) +} + +// compareSymbolToNode orders a symbol by its first declaration against node, +// the symbol first if that is node. +func (c *Checker) compareSymbolToNode(s *ast.Symbol, node *ast.Node) int { + if len(s.Declarations()) == 0 { + return 1 + } + if r := c.compareNodes(s.Declarations()[0], node); r != 0 { + return r + } + return -1 +} + +// resolveCycleMember computes the result of a member of a cycle again from +// scratch, with nothing in progress visible. +func (c *Checker) resolveCycleMember(r TypeResolution) { + target, propertyName := r.target, r.propertyName + c.beginCycleYield(target, propertyName, -1, len(c.resolutions)) + switch propertyName { + case TypeSystemPropertyNameType: + c.getTypeOfSymbol(target.(*ast.Symbol)) + case TypeSystemPropertyNameResolvedReturnType: + c.getReturnTypeOfSignature(target.(*Signature)) } - c.beginCycleYield(first, TypeSystemPropertyNameType, -1, len(c.resolutions)) - c.getTypeOfSymbol(first) c.endCycleYield() - c.beginCycleYield(r.target, r.propertyName, len(c.resolutions), len(c.resolutions)) - return true } // yieldCycleToMembers handles re-entering the type resolution in progress diff --git a/tsc/internal/checker/relater.go b/tsc/internal/checker/relater.go index 0b620146e6c06..45bfc16971541 100644 --- a/tsc/internal/checker/relater.go +++ b/tsc/internal/checker/relater.go @@ -2566,7 +2566,18 @@ func (c *Checker) isResolvingReturnTypeOfSignature(signature *Signature) bool { if signature.getComposite() != nil && core.Some(signature.getComposite().signatures, c.isResolvingReturnTypeOfSignature) { return true } - return signature.resolvedReturnType.get(c) == nil && c.isResolutionInProgress(signature, TypeSystemPropertyNameResolvedReturnType) + if signature.resolvedReturnType.get(c) != nil { + return false + } + start := c.findResolutionCycleStartIndex(signature, TypeSystemPropertyNameResolvedReturnType) + if start < 0 { + return false + } + c.markTypeResolutionCircular(start) + // Computed from the first member of the cycle, the return type may be + // available after all. + c.yieldObservedCycle(start) + return signature.resolvedReturnType.get(c) == nil } func (c *Checker) findMatchingSignatures(signatureLists [][]*Signature, signature *Signature, listIndex int) []*Signature { diff --git a/tsc/testdata/baselines/reference/compiler/circularReturnTypeSubtypeReductionOrder.errors.txt b/tsc/testdata/baselines/reference/compiler/circularReturnTypeSubtypeReductionOrder.errors.txt index b93093c154d7d..5f36a6dd6ba9d 100644 --- a/tsc/testdata/baselines/reference/compiler/circularReturnTypeSubtypeReductionOrder.errors.txt +++ b/tsc/testdata/baselines/reference/compiler/circularReturnTypeSubtypeReductionOrder.errors.txt @@ -1,3 +1,5 @@ +declaration.ts(6,5): error TS7023: 'rebuild' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. +declaration.ts(9,5): error TS7023: 'b' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. expression.ts(10,7): error TS2322: Type 'typeof A' is not assignable to type 'new () => A'. Type 'A' is not assignable to type 'A'. Two different types with this name exist, but they are unrelated. Types of property 'c' are incompatible. @@ -38,16 +40,20 @@ expression.ts(14,5): error TS7023: 'b' implicitly has return type 'any' because }; export {}; -==== declaration.ts (0 errors) ==== +==== declaration.ts (2 errors) ==== interface B { b(): B; } declare const flag: boolean; class K { rebuild() { + ~~~~~~~ +!!! error TS7023: 'rebuild' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. return flag ? this : new B(); } b() { + ~ +!!! error TS7023: 'b' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. return this.rebuild(); } } diff --git a/tsc/testdata/baselines/reference/compiler/circularReturnTypeSubtypeReductionOrder.types b/tsc/testdata/baselines/reference/compiler/circularReturnTypeSubtypeReductionOrder.types index a44fd2123e1c2..421302078810c 100644 --- a/tsc/testdata/baselines/reference/compiler/circularReturnTypeSubtypeReductionOrder.types +++ b/tsc/testdata/baselines/reference/compiler/circularReturnTypeSubtypeReductionOrder.types @@ -61,23 +61,23 @@ class K { >K : K rebuild() { ->rebuild : () => B +>rebuild : () => any return flag ? this : new B(); ->flag ? this : new B() : B +>flag ? this : new B() : this | B >flag : boolean >this : this >new B() : B >B : new () => B } b() { ->b : () => B +>b : () => any return this.rebuild(); ->this.rebuild() : B ->this.rebuild : () => B +>this.rebuild() : any +>this.rebuild : () => any >this : this ->rebuild : () => B +>rebuild : () => any } } const B: new () => B = K; diff --git a/tsc/testdata/baselines/reference/compiler/circularReturnTypeThroughCallbackArgument(strict=false).errors.txt b/tsc/testdata/baselines/reference/compiler/circularReturnTypeThroughCallbackArgument(strict=false).errors.txt deleted file mode 100644 index 3c7a84d051ca9..0000000000000 --- a/tsc/testdata/baselines/reference/compiler/circularReturnTypeThroughCallbackArgument(strict=false).errors.txt +++ /dev/null @@ -1,38 +0,0 @@ -circularReturnTypeThroughCallbackArgument.ts(23,25): error TS2322: Type 'Promise' is not assignable to type 'Promise'. - Type 'Other | Result' is not assignable to type 'Result'. - Type 'Other' is not assignable to type 'Result'. - Index signature for type 'string' is missing in type 'Other'. - - -==== circularReturnTypeThroughCallbackArgument.ts (1 errors) ==== - declare function fan(g: () => number): number; - - export function a(n: number) { - return fan(() => a(n)); - } - - declare function fanAll(g: () => Promise<{ ok: boolean }>): Promise<{ ok: boolean }>; - - export async function b(n: number) { - if (n > 0) { - return fanAll(() => b(n - 1)); - } - return { ok: true }; - } - - type Result = { success: boolean; [key: string]: unknown }; - interface Other { success: boolean; url: string } - declare function fanOut(generateOne: () => Promise): Promise; - declare const other: Other; - - export async function c(n: number) { - if (n) return other; - return fanOut(() => c(n - 1)); - ~~~~~~~~ -!!! error TS2322: Type 'Promise' is not assignable to type 'Promise'. -!!! error TS2322: Type 'Other | Result' is not assignable to type 'Result'. -!!! error TS2322: Type 'Other' is not assignable to type 'Result'. -!!! error TS2322: Index signature for type 'string' is missing in type 'Other'. -!!! related TS6502 circularReturnTypeThroughCallbackArgument.ts:18:38: The expected type comes from the return type of this signature. - } - \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/circularReturnTypeThroughCallbackArgument(strict=false).types b/tsc/testdata/baselines/reference/compiler/circularReturnTypeThroughCallbackArgument(strict=false).types index c3e8f0316281f..fb9e85bf2b1d7 100644 --- a/tsc/testdata/baselines/reference/compiler/circularReturnTypeThroughCallbackArgument(strict=false).types +++ b/tsc/testdata/baselines/reference/compiler/circularReturnTypeThroughCallbackArgument(strict=false).types @@ -6,15 +6,15 @@ declare function fan(g: () => number): number; >g : () => number export function a(n: number) { ->a : (n: number) => number +>a : (n: number) => any >n : number return fan(() => a(n)); >fan(() => a(n)) : number >fan : (g: () => number) => number ->() => a(n) : () => number ->a(n) : number ->a : (n: number) => number +>() => a(n) : () => any +>a(n) : any +>a : (n: number) => any >n : number } @@ -25,7 +25,7 @@ declare function fanAll(g: () => Promise<{ ok: boolean }>): Promise<{ ok: boolea >ok : boolean export async function b(n: number) { ->b : (n: number) => Promise<{ ok: boolean; }> +>b : (n: number) => any >n : number if (n > 0) { @@ -36,9 +36,9 @@ export async function b(n: number) { return fanAll(() => b(n - 1)); >fanAll(() => b(n - 1)) : Promise<{ ok: boolean; }> >fanAll : (g: () => Promise<{ ok: boolean; }>) => Promise<{ ok: boolean; }> ->() => b(n - 1) : () => Promise<{ ok: boolean; }> ->b(n - 1) : Promise<{ ok: boolean; }> ->b : (n: number) => Promise<{ ok: boolean; }> +>() => b(n - 1) : () => any +>b(n - 1) : any +>b : (n: number) => any >n - 1 : number >n : number >1 : 1 @@ -66,7 +66,7 @@ declare const other: Other; >other : Other export async function c(n: number) { ->c : (n: number) => Promise +>c : (n: number) => any >n : number if (n) return other; @@ -76,9 +76,9 @@ export async function c(n: number) { return fanOut(() => c(n - 1)); >fanOut(() => c(n - 1)) : Promise >fanOut : (generateOne: () => Promise) => Promise ->() => c(n - 1) : () => Promise ->c(n - 1) : Promise ->c : (n: number) => Promise +>() => c(n - 1) : () => any +>c(n - 1) : any +>c : (n: number) => any >n - 1 : number >n : number >1 : 1 diff --git a/tsc/testdata/baselines/reference/compiler/circularReturnTypeThroughCallbackArgument(strict=true).errors.txt b/tsc/testdata/baselines/reference/compiler/circularReturnTypeThroughCallbackArgument(strict=true).errors.txt index 3c7a84d051ca9..a10fc6bcbe59c 100644 --- a/tsc/testdata/baselines/reference/compiler/circularReturnTypeThroughCallbackArgument(strict=true).errors.txt +++ b/tsc/testdata/baselines/reference/compiler/circularReturnTypeThroughCallbackArgument(strict=true).errors.txt @@ -1,21 +1,31 @@ -circularReturnTypeThroughCallbackArgument.ts(23,25): error TS2322: Type 'Promise' is not assignable to type 'Promise'. - Type 'Other | Result' is not assignable to type 'Result'. - Type 'Other' is not assignable to type 'Result'. - Index signature for type 'string' is missing in type 'Other'. +circularReturnTypeThroughCallbackArgument.ts(3,17): error TS7023: 'a' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. +circularReturnTypeThroughCallbackArgument.ts(4,16): error TS7024: Function implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. +circularReturnTypeThroughCallbackArgument.ts(9,23): error TS7023: 'b' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. +circularReturnTypeThroughCallbackArgument.ts(11,23): error TS7024: Function implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. +circularReturnTypeThroughCallbackArgument.ts(21,23): error TS7023: 'c' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. +circularReturnTypeThroughCallbackArgument.ts(23,19): error TS7024: Function implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. -==== circularReturnTypeThroughCallbackArgument.ts (1 errors) ==== +==== circularReturnTypeThroughCallbackArgument.ts (6 errors) ==== declare function fan(g: () => number): number; export function a(n: number) { + ~ +!!! error TS7023: 'a' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. return fan(() => a(n)); + ~~~~~~~~~~ +!!! error TS7024: Function implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. } declare function fanAll(g: () => Promise<{ ok: boolean }>): Promise<{ ok: boolean }>; export async function b(n: number) { + ~ +!!! error TS7023: 'b' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. if (n > 0) { return fanAll(() => b(n - 1)); + ~~~~~~~~~~~~~~ +!!! error TS7024: Function implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. } return { ok: true }; } @@ -26,13 +36,11 @@ circularReturnTypeThroughCallbackArgument.ts(23,25): error TS2322: Type 'Promise declare const other: Other; export async function c(n: number) { + ~ +!!! error TS7023: 'c' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. if (n) return other; return fanOut(() => c(n - 1)); - ~~~~~~~~ -!!! error TS2322: Type 'Promise' is not assignable to type 'Promise'. -!!! error TS2322: Type 'Other | Result' is not assignable to type 'Result'. -!!! error TS2322: Type 'Other' is not assignable to type 'Result'. -!!! error TS2322: Index signature for type 'string' is missing in type 'Other'. -!!! related TS6502 circularReturnTypeThroughCallbackArgument.ts:18:38: The expected type comes from the return type of this signature. + ~~~~~~~~~~~~~~ +!!! error TS7024: Function implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. } \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/circularReturnTypeThroughCallbackArgument(strict=true).types b/tsc/testdata/baselines/reference/compiler/circularReturnTypeThroughCallbackArgument(strict=true).types index c3e8f0316281f..fb9e85bf2b1d7 100644 --- a/tsc/testdata/baselines/reference/compiler/circularReturnTypeThroughCallbackArgument(strict=true).types +++ b/tsc/testdata/baselines/reference/compiler/circularReturnTypeThroughCallbackArgument(strict=true).types @@ -6,15 +6,15 @@ declare function fan(g: () => number): number; >g : () => number export function a(n: number) { ->a : (n: number) => number +>a : (n: number) => any >n : number return fan(() => a(n)); >fan(() => a(n)) : number >fan : (g: () => number) => number ->() => a(n) : () => number ->a(n) : number ->a : (n: number) => number +>() => a(n) : () => any +>a(n) : any +>a : (n: number) => any >n : number } @@ -25,7 +25,7 @@ declare function fanAll(g: () => Promise<{ ok: boolean }>): Promise<{ ok: boolea >ok : boolean export async function b(n: number) { ->b : (n: number) => Promise<{ ok: boolean; }> +>b : (n: number) => any >n : number if (n > 0) { @@ -36,9 +36,9 @@ export async function b(n: number) { return fanAll(() => b(n - 1)); >fanAll(() => b(n - 1)) : Promise<{ ok: boolean; }> >fanAll : (g: () => Promise<{ ok: boolean; }>) => Promise<{ ok: boolean; }> ->() => b(n - 1) : () => Promise<{ ok: boolean; }> ->b(n - 1) : Promise<{ ok: boolean; }> ->b : (n: number) => Promise<{ ok: boolean; }> +>() => b(n - 1) : () => any +>b(n - 1) : any +>b : (n: number) => any >n - 1 : number >n : number >1 : 1 @@ -66,7 +66,7 @@ declare const other: Other; >other : Other export async function c(n: number) { ->c : (n: number) => Promise +>c : (n: number) => any >n : number if (n) return other; @@ -76,9 +76,9 @@ export async function c(n: number) { return fanOut(() => c(n - 1)); >fanOut(() => c(n - 1)) : Promise >fanOut : (generateOne: () => Promise) => Promise ->() => c(n - 1) : () => Promise ->c(n - 1) : Promise ->c : (n: number) => Promise +>() => c(n - 1) : () => any +>c(n - 1) : any +>c : (n: number) => any >n - 1 : number >n : number >1 : 1 diff --git a/tsc/testdata/baselines/reference/compiler/constAssertionBindingPatternQueryOrder(checkorder=typesfirst).types b/tsc/testdata/baselines/reference/compiler/constAssertionBindingPatternQueryOrder(checkorder=typesfirst).types index 4d7301bd19e06..d263c604b0052 100644 --- a/tsc/testdata/baselines/reference/compiler/constAssertionBindingPatternQueryOrder(checkorder=typesfirst).types +++ b/tsc/testdata/baselines/reference/compiler/constAssertionBindingPatternQueryOrder(checkorder=typesfirst).types @@ -11,12 +11,12 @@ async function f() { const [[a], b] = await settle(() => [Promise.resolve(0), Promise.reject(1)] as const); >a : number >b : [never] ->await settle(() => [Promise.resolve(0), Promise.reject(1)] as const) : readonly [[number], [never]] ->settle(() => [Promise.resolve(0), Promise.reject(1)] as const) : Promise +>await settle(() => [Promise.resolve(0), Promise.reject(1)] as const) : [[number], [never]] +>settle(() => [Promise.resolve(0), Promise.reject(1)] as const) : Promise<[[number], [never]]> >settle : (fn: () => T) => Promise<{ [K in keyof T]: [Awaited]; }> ->() => [Promise.resolve(0), Promise.reject(1)] as const : () => readonly [Promise, Promise] ->[Promise.resolve(0), Promise.reject(1)] as const : readonly [Promise, Promise] ->[Promise.resolve(0), Promise.reject(1)] : readonly [Promise, Promise] +>() => [Promise.resolve(0), Promise.reject(1)] as const : () => [Promise, Promise] +>[Promise.resolve(0), Promise.reject(1)] as const : [Promise, Promise] +>[Promise.resolve(0), Promise.reject(1)] : [Promise, Promise] >Promise.resolve(0) : Promise >Promise.resolve : { (): Promise; (value: T): Promise>; (value: T | PromiseLike): Promise>; } >Promise : PromiseConstructor From 2153dbc7fbb84074360eb3ff9c55620804e04336 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Sat, 31 Oct 2026 23:48:50 -0700 Subject: [PATCH 5/9] Use JSDoc type tags when assigning contextual parameter types Completing contextual signatures before anyone reads them switched parameter assignment from the signature of the function's symbol to the signature built from its declaration. In JavaScript the symbol's signature comes from a @type tag when one is present, and the function's parameters take their types from that tag. Using the declaration's signature wrote the outer contextual types onto those parameters instead, producing errors inside the function body that the tag rules out (seen in webpack). Prefer the tag's signature, as before, and fall back to the declaration's signature otherwise. --- tsc/internal/checker/checker.go | 7 ++++- ...eTagOnContextuallyTypedFunction.errors.txt | 12 +------- ...TypeTagOnContextuallyTypedFunction.symbols | 8 +++-- ...ocTypeTagOnContextuallyTypedFunction.types | 30 +++++++++---------- 4 files changed, 28 insertions(+), 29 deletions(-) diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 13cb25169c98a..91b16eb0619c5 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -10857,7 +10857,12 @@ func (c *Checker) assignContextualParameterTypesOfFunctionUncached(node *ast.Nod // already assigned contextual types. if links.flags&NodeCheckFlagsContextChecked == 0 { setFlagMemoBits(c, memoKeyOf(node, memoContextualParameters), &links.flags, NodeCheckFlagsContextChecked, NodeCheckFlagsContextChecked) - signature := c.getSignatureFromDeclarationUncontextual(node) + // A JSDoc @type tag on the function supplies the signature of its + // symbol, and the types of its parameters. + signature := c.getSignatureOfFullSignatureType(node) + if signature == nil { + signature = c.getSignatureFromDeclarationUncontextual(node) + } c.pushTypeResolution(node, TypeSystemPropertyNameContextChecked) if c.isContextSensitive(node) { if contextualSignature != nil { diff --git a/tsc/testdata/baselines/reference/compiler/jsdocTypeTagOnContextuallyTypedFunction.errors.txt b/tsc/testdata/baselines/reference/compiler/jsdocTypeTagOnContextuallyTypedFunction.errors.txt index f19e5b3afede1..f9f9a2d8d5885 100644 --- a/tsc/testdata/baselines/reference/compiler/jsdocTypeTagOnContextuallyTypedFunction.errors.txt +++ b/tsc/testdata/baselines/reference/compiler/jsdocTypeTagOnContextuallyTypedFunction.errors.txt @@ -2,17 +2,13 @@ a.js(4,1): error TS2322: Type '(content: string, rest_0?: boolean | undefined) = Types of parameters 'content' and 'a' are incompatible. Type 'string | number' is not assignable to type 'string'. Type 'number' is not assignable to type 'string'. -a.js(6,35): error TS2339: Property 'toUpperCase' does not exist on type 'string | number'. - Property 'toUpperCase' does not exist on type 'number'. a.js(9,7): error TS2322: Type '(content: string) => string' is not assignable to type '(content: string | number) => string'. Types of parameters 'content' and 'content' are incompatible. Type 'string | number' is not assignable to type 'string'. Type 'number' is not assignable to type 'string'. -a.js(11,26): error TS2339: Property 'toUpperCase' does not exist on type 'string | number'. - Property 'toUpperCase' does not exist on type 'number'. -==== a.js (4 errors) ==== +==== a.js (2 errors) ==== /** @type {{ f: (a: string | number, ...rest: unknown[]) => string }} */ const context = { f: (a) => "" }; @@ -24,9 +20,6 @@ a.js(11,26): error TS2339: Property 'toUpperCase' does not exist on type 'string !!! error TS2322: Type 'number' is not assignable to type 'string'. /** @type {(content: string, ...rest: [boolean?]) => string} */ (content, ...rest) => content.toUpperCase() + rest.length; - ~~~~~~~~~~~ -!!! error TS2339: Property 'toUpperCase' does not exist on type 'string | number'. -!!! error TS2339: Property 'toUpperCase' does not exist on type 'number'. /** @type {(content: string | number) => string} */ const g = @@ -37,7 +30,4 @@ a.js(11,26): error TS2339: Property 'toUpperCase' does not exist on type 'string !!! error TS2322: Type 'number' is not assignable to type 'string'. /** @type {(content: string) => string} */ (content) => content.toUpperCase(); - ~~~~~~~~~~~ -!!! error TS2339: Property 'toUpperCase' does not exist on type 'string | number'. -!!! error TS2339: Property 'toUpperCase' does not exist on type 'number'. \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/jsdocTypeTagOnContextuallyTypedFunction.symbols b/tsc/testdata/baselines/reference/compiler/jsdocTypeTagOnContextuallyTypedFunction.symbols index 17ba749362f25..15ffe15bb65e2 100644 --- a/tsc/testdata/baselines/reference/compiler/jsdocTypeTagOnContextuallyTypedFunction.symbols +++ b/tsc/testdata/baselines/reference/compiler/jsdocTypeTagOnContextuallyTypedFunction.symbols @@ -16,10 +16,12 @@ context.f = (content, ...rest) => content.toUpperCase() + rest.length; >content : Symbol(content, Decl(a.js, 5, 5)) >rest : Symbol(rest, Decl(a.js, 5, 13)) +>content.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) >content : Symbol(content, Decl(a.js, 5, 5)) ->rest.length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>rest.length : Symbol(length) >rest : Symbol(rest, Decl(a.js, 5, 13)) ->length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --)) +>length : Symbol(length) /** @type {(content: string | number) => string} */ const g = @@ -28,5 +30,7 @@ const g = /** @type {(content: string) => string} */ (content) => content.toUpperCase(); >content : Symbol(content, Decl(a.js, 10, 5)) +>content.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) >content : Symbol(content, Decl(a.js, 10, 5)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) diff --git a/tsc/testdata/baselines/reference/compiler/jsdocTypeTagOnContextuallyTypedFunction.types b/tsc/testdata/baselines/reference/compiler/jsdocTypeTagOnContextuallyTypedFunction.types index 7511697864aa1..6defe743de587 100644 --- a/tsc/testdata/baselines/reference/compiler/jsdocTypeTagOnContextuallyTypedFunction.types +++ b/tsc/testdata/baselines/reference/compiler/jsdocTypeTagOnContextuallyTypedFunction.types @@ -19,16 +19,16 @@ context.f = /** @type {(content: string, ...rest: [boolean?]) => string} */ (content, ...rest) => content.toUpperCase() + rest.length; >(content, ...rest) => content.toUpperCase() + rest.length : (content: string, rest_0?: boolean | undefined) => string ->content : string | number ->rest : unknown[] ->content.toUpperCase() + rest.length : any ->content.toUpperCase() : any ->content.toUpperCase : any ->content : string | number ->toUpperCase : any ->rest.length : number ->rest : unknown[] ->length : number +>content : string +>rest : [(boolean | undefined)?] +>content.toUpperCase() + rest.length : string +>content.toUpperCase() : string +>content.toUpperCase : () => string +>content : string +>toUpperCase : () => string +>rest.length : 0 | 1 +>rest : [(boolean | undefined)?] +>length : 0 | 1 /** @type {(content: string | number) => string} */ const g = @@ -37,9 +37,9 @@ const g = /** @type {(content: string) => string} */ (content) => content.toUpperCase(); >(content) => content.toUpperCase() : (content: string) => string ->content : string | number ->content.toUpperCase() : any ->content.toUpperCase : any ->content : string | number ->toUpperCase : any +>content : string +>content.toUpperCase() : string +>content.toUpperCase : () => string +>content : string +>toUpperCase : () => string From 51ac09b2b0c2aa22ded0b9bee1d3e6ec2e6570f4 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Sun, 1 Nov 2026 03:25:56 -0800 Subject: [PATCH 6/9] Name an instantiation cycle from its first declared type An instantiation stopped by the limit on the depth of the whole instantiation names every type that recurs in it, in the order in which they recur. Which type of the cycle the instantiation entered first depends on what was cached before, so checkers that had checked different files named the same cycle starting from different types, and reported the same error twice with different messages (seen in zod, with checkers running concurrently). The names now start from the type declared first, so the message is the same wherever the cycle is entered. The limit on the depth of a single computation already names only what that computation instantiated, which does not depend on where it was entered, so its messages keep the order in which the types recur. --- tsc/internal/checker/checker.go | 29 +++++++++++++++---- ...enericExtendingClassDoubleLayer.errors.txt | 8 ++--- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 91b16eb0619c5..8332e765a2196 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -24927,12 +24927,13 @@ func getActiveMapperCacheKey(t *Type, alias *TypeAlias) CacheHashKey { func (c *Checker) getCircularTypeNames() []string { typeCounts := make(map[*Type]int) - var circularTypeNames []string + var circularSymbols []*ast.Symbol // A computation reaching the depth limit names only its own // instantiations, which do not depend on where it was first computed; // see "Instantiation limits". stack := c.instantiationStack - if len(stack) != instantiationTotalDepthLimit { + total := len(stack) == instantiationTotalDepthLimit + if !total { stack = stack[c.currentResolution().depthStart:] } for _, t := range stack { @@ -24942,12 +24943,28 @@ func (c *Checker) getCircularTypeNames() []string { if t.alias != nil { symbol = t.alias.symbol } - if symbol != nil && len(symbol.Name()) != 0 && symbol.Name()[0] != '\xFE' { - if name := c.SymbolToString(symbol); !slices.Contains(circularTypeNames, name) { - circularTypeNames = append(circularTypeNames, name) - } + if symbol != nil && len(symbol.Name()) != 0 && symbol.Name()[0] != '\xFE' && !slices.Contains(circularSymbols, symbol) { + circularSymbols = append(circularSymbols, symbol) + } + } + } + if total && len(circularSymbols) > 1 { + // The whole instantiation is named, and which of the types in the + // cycle it entered first depends on what was cached before, so the + // cycle is named starting from the type declared first. + first := 0 + for i, s := range circularSymbols { + if c.compareSymbols(s, circularSymbols[first]) < 0 { + first = i } } + circularSymbols = append(circularSymbols[first:], circularSymbols[:first]...) + } + var circularTypeNames []string + for _, s := range circularSymbols { + if name := c.SymbolToString(s); !slices.Contains(circularTypeNames, name) { + circularTypeNames = append(circularTypeNames, name) + } } return circularTypeNames } diff --git a/tsc/testdata/baselines/reference/compiler/keyofGenericExtendingClassDoubleLayer.errors.txt b/tsc/testdata/baselines/reference/compiler/keyofGenericExtendingClassDoubleLayer.errors.txt index 5babffc1080a2..8c8a4f0003e86 100644 --- a/tsc/testdata/baselines/reference/compiler/keyofGenericExtendingClassDoubleLayer.errors.txt +++ b/tsc/testdata/baselines/reference/compiler/keyofGenericExtendingClassDoubleLayer.errors.txt @@ -1,7 +1,7 @@ keyofGenericExtendingClassDoubleLayer.ts(2,12): error TS2564: Property 'createdAt' has no initializer and is not definitely assigned in the constructor. -keyofGenericExtendingClassDoubleLayer.ts(9,7): error TS5115: Instantiations of the following types appear infinitely circular: 'Model', 'ModelAttributes', 'Exclude'. +keyofGenericExtendingClassDoubleLayer.ts(9,7): error TS5115: Instantiations of the following types appear infinitely circular: 'Exclude', 'Model', 'ModelAttributes'. keyofGenericExtendingClassDoubleLayer.ts(10,12): error TS2564: Property 'age' has no initializer and is not definitely assigned in the constructor. -keyofGenericExtendingClassDoubleLayer.ts(13,13): error TS5115: Instantiations of the following types appear infinitely circular: 'Model', 'ModelAttributes', 'Exclude'. +keyofGenericExtendingClassDoubleLayer.ts(13,13): error TS5115: Instantiations of the following types appear infinitely circular: 'Exclude', 'Model', 'ModelAttributes'. ==== keyofGenericExtendingClassDoubleLayer.ts (4 errors) ==== @@ -17,7 +17,7 @@ keyofGenericExtendingClassDoubleLayer.ts(13,13): error TS5115: Instantiations of class PersonModel extends AutoModel { ~~~~~~~~~~~ -!!! error TS5115: Instantiations of the following types appear infinitely circular: 'Model', 'ModelAttributes', 'Exclude'. +!!! error TS5115: Instantiations of the following types appear infinitely circular: 'Exclude', 'Model', 'ModelAttributes'. public age: number; ~~~ !!! error TS2564: Property 'age' has no initializer and is not definitely assigned in the constructor. @@ -25,7 +25,7 @@ keyofGenericExtendingClassDoubleLayer.ts(13,13): error TS5115: Instantiations of toJson() { let x: keyof this = 'createdAt'; ~ -!!! error TS5115: Instantiations of the following types appear infinitely circular: 'Model', 'ModelAttributes', 'Exclude'. +!!! error TS5115: Instantiations of the following types appear infinitely circular: 'Exclude', 'Model', 'ModelAttributes'. } } \ No newline at end of file From aa4076ffecbeb61f662a97989515631e0251961c Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Sun, 1 Nov 2026 20:24:28 -0800 Subject: [PATCH 7/9] Discard results computed from a binding pattern's own elements While the type implied by a binding pattern is computed as the contextual type of its initializer, references to the pattern's elements from the elements' defaults are any. The calls and types computed from them were cached anyway, so asking for the contextual type of the initializer first left `extra` in const { kind, payload, extra = kind === "a" ? ... : ... } = u; as any rather than unknown. TypeScript behaves the same way. Track the patterns as resolutions in progress rather than a separate stack, so that a reference returning any marks the computation circular and what was cached from it is discarded. (seen in check order fuzzing) --- tsc/internal/checker/checker.go | 16 ++++++++++------ ...ementInitializer(checkorder=typesfirst).types | 4 ++-- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 8332e765a2196..7e2e38068ac18 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -80,6 +80,7 @@ const ( TypeSystemPropertyNameCallResolution TypeSystemPropertyNameAssertionOperand TypeSystemPropertyNamePatternInitializer + TypeSystemPropertyNameContextualBindingPattern ) type TypeResolution struct { @@ -686,7 +687,6 @@ type Checker struct { reportUnmeasurableMapper *TypeMapper restrictiveMapper *TypeMapper permissiveMapper *TypeMapper - contextualBindingPatterns []*ast.Node resolving resolvingTable // Index in resolutions of the outermost computation of each key in progress typeResolutionTop int // 1-based index in resolutions of the innermost resolution, or 0 // Like typeResolutionTop, but skipping the type resolutions of current @@ -11791,8 +11791,9 @@ func (c *Checker) checkIdentifier(node *ast.Node, checkMode CheckMode) *Type { // If the identifier is declared in a binding pattern for which we're currently computing the implied type and the // reference occurs with the same binding pattern, return the non-inferrable any type. This for example occurs in // 'const [a, b = a + 1] = [2]' when we're computing the contextual type for the array literal '[2]'. - if declaration != nil && declaration.Kind == ast.KindBindingElement && slices.Contains(c.contextualBindingPatterns, declaration.Parent) && - ast.FindAncestor(node, func(parent *ast.Node) bool { return parent == declaration.Parent }) != nil { + if declaration != nil && declaration.Kind == ast.KindBindingElement && + ast.FindAncestor(node, func(parent *ast.Node) bool { return parent == declaration.Parent }) != nil && + c.isResolvingAnywhere(declaration.Parent, TypeSystemPropertyNameContextualBindingPattern) { return c.nonInferrableAnyType } t := c.getNarrowedTypeOfSymbol(localOrExportSymbol, node) @@ -19384,7 +19385,10 @@ func (c *Checker) getParentElementAccess(node *ast.Node) *ast.Node { // the parameter. func (c *Checker) getTypeFromBindingPattern(pattern *ast.Node, includePatternInType bool, reportErrors bool) *Type { if includePatternInType { - c.contextualBindingPatterns = append(c.contextualBindingPatterns, pattern) + // While the implied type is computed, references to the pattern's own + // elements are any (see checkIdentifier), so nothing computed from + // them may be cached; see isResolvingAnywhere. + c.beginTypeResolution(pattern, TypeSystemPropertyNameContextualBindingPattern) } var result *Type if ast.IsObjectBindingPattern(pattern) { @@ -19393,7 +19397,7 @@ func (c *Checker) getTypeFromBindingPattern(pattern *ast.Node, includePatternInT result = c.getTypeFromArrayBindingPattern(pattern, includePatternInType, reportErrors) } if includePatternInType { - c.contextualBindingPatterns = c.contextualBindingPatterns[:len(c.contextualBindingPatterns)-1] + c.popTypeResolution() } return result } @@ -20945,7 +20949,7 @@ func (c *Checker) typeResolutionHasProperty(r *TypeResolution) bool { TypeSystemPropertyNameSimplifiedForReading, TypeSystemPropertyNameSimplifiedForWriting, TypeSystemPropertyNameDefaultType, TypeSystemPropertyNameAwaitedType, TypeSystemPropertyNameExplicitType, TypeSystemPropertyNameBindingElementParent, TypeSystemPropertyNameCallResolution, - TypeSystemPropertyNameAssertionOperand, TypeSystemPropertyNamePatternInitializer: + TypeSystemPropertyNameAssertionOperand, TypeSystemPropertyNamePatternInitializer, TypeSystemPropertyNameContextualBindingPattern: // Marked as checked or resolved on entry, so it is only on the stack // while in progress. return false diff --git a/tsc/testdata/baselines/reference/compiler/dependentDestructuringBindingElementInitializer(checkorder=typesfirst).types b/tsc/testdata/baselines/reference/compiler/dependentDestructuringBindingElementInitializer(checkorder=typesfirst).types index 265e9b075cacf..0dcd3d8a64cef 100644 --- a/tsc/testdata/baselines/reference/compiler/dependentDestructuringBindingElementInitializer(checkorder=typesfirst).types +++ b/tsc/testdata/baselines/reference/compiler/dependentDestructuringBindingElementInitializer(checkorder=typesfirst).types @@ -25,8 +25,8 @@ const { >payload : string | number extra = kind === "a" ? payload.toFixed() : payload.toUpperCase(), ->extra : any ->kind === "a" ? payload.toFixed() : payload.toUpperCase() : any +>extra : unknown +>kind === "a" ? payload.toFixed() : payload.toUpperCase() : string >kind === "a" : boolean >kind : "a" | "b" >"a" : "a" From 6e6a28744232cd63b87b740e0717ec27c06e47d5 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Sun, 1 Nov 2026 02:57:06 -0800 Subject: [PATCH 8/9] Keep the result of a resolution nested in a hidden one of itself A call resolution hides the resolutions below it, so a symbol whose initializer reaches itself through a call argument is computed again, nested in itself, as in main. The nested computation observes things in progress that the outer one entered: here, a mapped property whose contextual type is skipped, and the reverse mapping whose depth cutoff it reaches. Those mark everything since as circular, so the nested result was discarded, and the outer computation, reaching the same types again outside that context, then failed with a circularity. When everything the nested computation observed in progress was entered by the outermost hidden resolution of the same target, the nested computation is what that one always computes first from scratch, whichever entry led to it, so its result is kept as a cycle participant, as main keeps the first result set. Main also keeps the reverse-mapped property type computed under the depth cutoff, so it types the property as unknown. That result still depends on the cutoff and is discarded, so the property is computed again from scratch afterwards, giving its full type: an improvement over main rather than a match (seen in arktype). --- tsc/internal/checker/checker.go | 34 +++++++++++- ...verseMapped(checkorder=default).errors.txt | 40 -------------- ...kReverseMapped(checkorder=default).symbols | 8 +++ ...unkReverseMapped(checkorder=default).types | 54 +++++++++---------- 4 files changed, 68 insertions(+), 68 deletions(-) delete mode 100644 tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=default).errors.txt diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 7e2e38068ac18..4d7180ff60d2d 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -95,6 +95,10 @@ type TypeResolution struct { restartHidden bool // participant records that the resolution is in cycleParticipants. participant bool + // outerSelf is one more than the index in resolutions of the outermost + // resolution of the same property of target hidden below this one, or 0; + // see popTypeResolution. + outerSelf int32 } // ContextualInfo @@ -20300,9 +20304,29 @@ func (c *Checker) pushTypeResolution(target TypeSystemEntity, propertyName TypeS } } c.beginTypeResolution(target, propertyName) + if resolutionCycleStartIndex < 0 && c.resolutionStart > 0 { + if i := c.findHiddenResolution(target, propertyName); i >= 0 { + c.resolutions[len(c.resolutions)-1].typeResolution.outerSelf = int32(i + 1) + } + } return true } +// findHiddenResolution returns the index in resolutions of the outermost +// resolution of the property of target in progress below resolutionStart, +// or -1. +func (c *Checker) findHiddenResolution(target TypeSystemEntity, propertyName TypeSystemPropertyName) int { + found := -1 + for i := int(c.resolutions[len(c.resolutions)-1].prevSearchable) - 1; i >= 0; i = int(c.resolutions[i].prevSearchable) - 1 { + if i < c.resolutionStart { + if r := &c.resolutions[i].typeResolution; r.target == target && r.propertyName == propertyName { + found = i + } + } + } + return found +} + // cycleYield is a canonical recomputation in progress; see yieldCycle. type cycleYield struct { end int // Index in resolutions of the resolution whose pop ends it, or -1 @@ -20831,7 +20855,15 @@ func (c *Checker) popTypeResolution() bool { debug.Assert(c.typeResolutionTop == n+1, "resolutions left above a type resolution") f := &c.resolutions[n] result := f.typeResolution.result - if r := &f.typeResolution; result && !r.participant && c.observedOnlyOutermostMeasurement(n) { + if r := &f.typeResolution; r.outerSelf != 0 && result && c.circularityStart != 0 && c.circularityStart <= n && c.circularityStart >= int(r.outerSelf) && !r.participant { + // The resolution re-entered a hidden resolution of itself, which a + // computation from scratch of the outer one always does, and what it + // observed in progress was all entered by the outer one, so its result + // is what the outer one computes first from scratch: the outer one + // keeps it, as must the cycle. + r.participant = true + c.cycleParticipants = append(c.cycleParticipants, *r) + } else if r := &f.typeResolution; result && !r.participant && c.observedOnlyOutermostMeasurement(n) { // The outermost measurement of variances hides what is below it and // is measured from the canonical entry of its circularity, so what // it computes is the same whatever led to it. This resolution only diff --git a/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=default).errors.txt b/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=default).errors.txt deleted file mode 100644 index 495583f737e9e..0000000000000 --- a/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=default).errors.txt +++ /dev/null @@ -1,40 +0,0 @@ -circularScopeThunkReverseMapped.ts(12,14): error TS7022: '$' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. -circularScopeThunkReverseMapped.ts(14,5): error TS7023: 'b' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. -circularScopeThunkReverseMapped.ts(14,41): error TS7006: Parameter 'n' implicitly has an 'any' type. -circularScopeThunkReverseMapped.ts(18,14): error TS7022: '$2' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. -circularScopeThunkReverseMapped.ts(20,5): error TS7023: 'b' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. - - -==== circularScopeThunkReverseMapped.ts (5 errors) ==== - type Resolve = T extends () => infer R ? R : T; - type Validate = { [K in keyof def]: def[K] extends () => infer R ? () => R : def[K] }; - interface Scope { - type(k: k): Resolve; - } - interface Type { - pipe(f: (x: T) => U): Type; - } - declare function scope(def: Validate): Scope; - declare function type(x: T): Type; - - export const $ = scope({ - ~ -!!! error TS7022: '$' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. - a: type("string").pipe(function (s) { return s.length; }), - b: () => $.type("a").pipe(function (n) { return n === 0; }), - ~ -!!! error TS7023: 'b' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. - ~ -!!! error TS7006: Parameter 'n' implicitly has an 'any' type. - }); - - // Entered from the function in the first property when queried in reverse - export const $2 = scope({ - ~~ -!!! error TS7022: '$2' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. - a: type("string").pipe(function (s) { return s.length; }), - b: () => $2.type("a").pipe((n: number) => n === 0), - ~ -!!! error TS7023: 'b' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. - }); - \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=default).symbols b/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=default).symbols index 162296d91ea40..cefed83354abf 100644 --- a/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=default).symbols +++ b/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=default).symbols @@ -82,7 +82,11 @@ export const $ = scope({ b: () => $.type("a").pipe(function (n) { return n === 0; }), >b : Symbol(b, Decl(circularScopeThunkReverseMapped.ts, 12, 62)) +>$.type("a").pipe : Symbol(Type.pipe, Decl(circularScopeThunkReverseMapped.ts, 5, 19)) +>$.type : Symbol(Scope.type, Decl(circularScopeThunkReverseMapped.ts, 2, 22)) >$ : Symbol($, Decl(circularScopeThunkReverseMapped.ts, 11, 12)) +>type : Symbol(Scope.type, Decl(circularScopeThunkReverseMapped.ts, 2, 22)) +>pipe : Symbol(Type.pipe, Decl(circularScopeThunkReverseMapped.ts, 5, 19)) >n : Symbol(n, Decl(circularScopeThunkReverseMapped.ts, 13, 40)) >n : Symbol(n, Decl(circularScopeThunkReverseMapped.ts, 13, 40)) @@ -105,7 +109,11 @@ export const $2 = scope({ b: () => $2.type("a").pipe((n: number) => n === 0), >b : Symbol(b, Decl(circularScopeThunkReverseMapped.ts, 18, 62)) +>$2.type("a").pipe : Symbol(Type.pipe, Decl(circularScopeThunkReverseMapped.ts, 5, 19)) +>$2.type : Symbol(Scope.type, Decl(circularScopeThunkReverseMapped.ts, 2, 22)) >$2 : Symbol($2, Decl(circularScopeThunkReverseMapped.ts, 17, 12)) +>type : Symbol(Scope.type, Decl(circularScopeThunkReverseMapped.ts, 2, 22)) +>pipe : Symbol(Type.pipe, Decl(circularScopeThunkReverseMapped.ts, 5, 19)) >n : Symbol(n, Decl(circularScopeThunkReverseMapped.ts, 19, 32)) >n : Symbol(n, Decl(circularScopeThunkReverseMapped.ts, 19, 32)) diff --git a/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=default).types b/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=default).types index cd86ca9ddbbf9..f1af064690eee 100644 --- a/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=default).types +++ b/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=default).types @@ -27,10 +27,10 @@ declare function type(x: T): Type; >x : T export const $ = scope({ ->$ : any ->scope({ a: type("string").pipe(function (s) { return s.length; }), b: () => $.type("a").pipe(function (n) { return n === 0; }),}) : Scope<{ readonly a: Type; readonly b: () => any; }> +>$ : Scope<{ a: Type; b: () => Type; }> +>scope({ a: type("string").pipe(function (s) { return s.length; }), b: () => $.type("a").pipe(function (n) { return n === 0; }),}) : Scope<{ readonly a: Type; readonly b: () => Type; }> >scope : (def: Validate) => Scope ->{ a: type("string").pipe(function (s) { return s.length; }), b: () => $.type("a").pipe(function (n) { return n === 0; }),} : { a: Type; b: () => any; } +>{ a: type("string").pipe(function (s) { return s.length; }), b: () => $.type("a").pipe(function (n) { return n === 0; }),} : { a: Type; b: () => Type; } a: type("string").pipe(function (s) { return s.length; }), >a : Type @@ -47,30 +47,30 @@ export const $ = scope({ >length : number b: () => $.type("a").pipe(function (n) { return n === 0; }), ->b : () => any ->() => $.type("a").pipe(function (n) { return n === 0; }) : () => any ->$.type("a").pipe(function (n) { return n === 0; }) : any ->$.type("a").pipe : any ->$.type("a") : any ->$.type : any ->$ : any ->type : any +>b : () => Type +>() => $.type("a").pipe(function (n) { return n === 0; }) : () => Type +>$.type("a").pipe(function (n) { return n === 0; }) : Type +>$.type("a").pipe : (f: (x: number) => U) => Type +>$.type("a") : Type +>$.type : (k: k_1) => Resolve<{ a: Type; b: () => Type; }[k_1]> +>$ : Scope<{ a: Type; b: () => Type; }> +>type : (k: k_1) => Resolve<{ a: Type; b: () => Type; }[k_1]> >"a" : "a" ->pipe : any ->function (n) { return n === 0; } : (n: any) => boolean ->n : any +>pipe : (f: (x: number) => U) => Type +>function (n) { return n === 0; } : (n: number) => n is 0 +>n : number >n === 0 : boolean ->n : any +>n : number >0 : 0 }); // Entered from the function in the first property when queried in reverse export const $2 = scope({ ->$2 : any ->scope({ a: type("string").pipe(function (s) { return s.length; }), b: () => $2.type("a").pipe((n: number) => n === 0),}) : Scope<{ readonly a: Type; readonly b: () => any; }> +>$2 : Scope<{ a: Type; b: () => Type; }> +>scope({ a: type("string").pipe(function (s) { return s.length; }), b: () => $2.type("a").pipe((n: number) => n === 0),}) : Scope<{ readonly a: Type; readonly b: () => Type; }> >scope : (def: Validate) => Scope ->{ a: type("string").pipe(function (s) { return s.length; }), b: () => $2.type("a").pipe((n: number) => n === 0),} : { a: Type; b: () => any; } +>{ a: type("string").pipe(function (s) { return s.length; }), b: () => $2.type("a").pipe((n: number) => n === 0),} : { a: Type; b: () => Type; } a: type("string").pipe(function (s) { return s.length; }), >a : Type @@ -87,16 +87,16 @@ export const $2 = scope({ >length : number b: () => $2.type("a").pipe((n: number) => n === 0), ->b : () => any ->() => $2.type("a").pipe((n: number) => n === 0) : () => any ->$2.type("a").pipe((n: number) => n === 0) : any ->$2.type("a").pipe : any ->$2.type("a") : any ->$2.type : any ->$2 : any ->type : any +>b : () => Type +>() => $2.type("a").pipe((n: number) => n === 0) : () => Type +>$2.type("a").pipe((n: number) => n === 0) : Type +>$2.type("a").pipe : (f: (x: number) => U) => Type +>$2.type("a") : Type +>$2.type : (k: k_1) => Resolve<{ a: Type; b: () => Type; }[k_1]> +>$2 : Scope<{ a: Type; b: () => Type; }> +>type : (k: k_1) => Resolve<{ a: Type; b: () => Type; }[k_1]> >"a" : "a" ->pipe : any +>pipe : (f: (x: number) => U) => Type >(n: number) => n === 0 : (n: number) => n is 0 >n : number >n === 0 : boolean From 8c625726d704170c73ae3a7117b46eeb97367e44 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Sun, 1 Nov 2026 23:09:01 -0800 Subject: [PATCH 9/9] Keep results a cycle yield computes from scratch When a query enters a cycle somewhere other than its program-order entry, the cycle yields to its first member and recomputes it with the resolutions below hidden. Two things made that recomputation differ from computing the member first: - The reverse mapping stacks, contextual types and inference contexts of the hidden resolutions stayed visible, so the recomputation saw contexts that a computation from scratch never has. A yield now hides them along with the resolutions and restores them when it ends. - Results were kept as cycle participants only by comparing the outermost cycle head in progress against the resolution. A cycle headed below the yield, which the yield cannot observe, caused the yield root and the hidden re-entries of itself to be discarded, so the member was computed again and genuinely re-entered itself. Each type resolution now tracks the lowest cycle head it observed, and a result is kept when what it observed was entered from scratch: by the outer resolution of itself, by the yield it is the root of, or by the outermost measurement of variances. (seen in circularScopeThunkReverseMapped) --- tsc/internal/checker/checker.go | 79 +++++++++++++++---- tsc/internal/checker/memo.go | 1 + tsc/internal/checker/resolutions.go | 1 + ...seMapped(checkorder=typesfirst).errors.txt | 40 ---------- ...verseMapped(checkorder=typesfirst).symbols | 8 ++ ...ReverseMapped(checkorder=typesfirst).types | 54 ++++++------- 6 files changed, 101 insertions(+), 82 deletions(-) delete mode 100644 tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=typesfirst).errors.txt diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 4d7180ff60d2d..266190f3ccb91 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -708,6 +708,7 @@ type Checker struct { circularityMark int // Length of discard log when the cycle in progress was detected innerCycles []innerCycle // Cycles detected inside the one in progress; see extendCircularity flowDepthFailures map[FlowLoopKey]int // Largest depth budget known to be too small for a flow walk from a node; see getTypeAtFlowNode + observedHead int32 // 1 + index in resolutions of the lowest head of a cycle observed by the innermost type resolution in progress, or 0 cycleParticipants []TypeResolution // Computations that observed a cycle in progress effectsMarkDepth int // 1 + resolution index of the innermost effects mark that may be discarded, or 0 varianceStack []VarianceStackEntry @@ -20335,9 +20336,20 @@ type cycleYield struct { savedResolutionStart int savedVisibleStart int savedDepths [3]depthLimit + savedReverseMapped reverseMappedState + savedContextual []ContextualInfo + savedInference []InferenceContextInfo + floor int shadowed []shadowedResolving } +type reverseMappedState struct { + sourceStack []*Type + targetStack []*Type + expandingFlags ExpandingFlags + start int32 +} + type shadowedResolving struct { key memoKey index int32 @@ -20521,7 +20533,15 @@ func (c *Checker) beginCycleYield(target TypeSystemEntity, propertyName TypeSyst savedResolutionStart: c.resolutionStart, savedVisibleStart: c.visibleStart, savedDepths: [3]depthLimit{c.serializationLevel, c.inlineLevel, c.conditionalConstraintDepth}, + savedReverseMapped: reverseMappedState{c.reverseMappedSourceStack, c.reverseMappedTargetStack, c.reverseExpandingFlags, c.reverseMappedStart}, + savedContextual: c.contextualInfos, + savedInference: c.inferenceContextInfos, + floor: floor, }) + // The contexts the hidden resolutions entered are hidden too, so that the + // recomputation is from scratch. + c.reverseMappedSourceStack, c.reverseMappedTargetStack, c.reverseExpandingFlags, c.reverseMappedStart = nil, nil, 0, 0 + c.contextualInfos, c.inferenceContextInfos = nil, nil c.resolutionStart = n c.visibleStart = n c.serializationLevel, c.inlineLevel, c.conditionalConstraintDepth = depthLimit{}, depthLimit{}, depthLimit{} @@ -20532,6 +20552,9 @@ func (c *Checker) endCycleYield() { c.resolutionStart = y.savedResolutionStart c.visibleStart = y.savedVisibleStart c.serializationLevel, c.inlineLevel, c.conditionalConstraintDepth = y.savedDepths[0], y.savedDepths[1], y.savedDepths[2] + r := y.savedReverseMapped + c.reverseMappedSourceStack, c.reverseMappedTargetStack, c.reverseExpandingFlags, c.reverseMappedStart = r.sourceStack, r.targetStack, r.expandingFlags, r.start + c.contextualInfos, c.inferenceContextInfos = y.savedContextual, y.savedInference for _, sh := range y.shadowed { c.resolving.set(sh.key, sh.index) } @@ -20797,6 +20820,42 @@ func (c *Checker) markTypeResolutionCircular(start int) { c.markCircular(start) } +// keepsResultInCycle reports whether the type resolution at index n, which +// observed the cycles whose lowest head is observed (see Checker.observedHead), +// computes what it computes first from scratch although a cycle is in +// progress, so that its result is kept as a participant of the cycle: +// - The resolution re-entered a hidden resolution of itself, which a +// computation from scratch of the outer one always does, and what it +// observed in progress was all entered by the outer one, so its result is +// what the outer one computes first from scratch. +// - The resolution is the one a cycle yield computes again from scratch, +// with everything below it hidden, and it observed nothing hidden. +// - The resolution is inside the outermost measurement of variances, which +// hides what is below it and is measured from the canonical entry of its +// circularity, so what it computes is the same whatever led to it, and it +// only observed its incomplete variances. Computing it first from scratch +// reaches the measurement and computes it again inside, the same way. +func (c *Checker) keepsResultInCycle(n int, f *resolution, observed int32) bool { + r := &f.typeResolution + if r.outerSelf != 0 && observed != 0 && observed >= r.outerSelf && int(observed) <= n { + return true + } + if len(c.cycleYields) != 0 { + y := &c.cycleYields[len(c.cycleYields)-1] + if n >= y.floor && int(f.prevTypeResolution) <= y.floor && (observed == 0 || int(observed) > y.floor) { + return true + } + } + return c.observedOnlyOutermostMeasurement(n) +} + +func minObservedHead(a int32, b int32) int32 { + if a == 0 || b != 0 && b < a { + return b + } + return a +} + // observedOnlyOutermostMeasurement reports whether the cycle in progress is // the one headed by the outermost measurement of variances, which the // resolution at index n is inside, and no other. @@ -20855,21 +20914,9 @@ func (c *Checker) popTypeResolution() bool { debug.Assert(c.typeResolutionTop == n+1, "resolutions left above a type resolution") f := &c.resolutions[n] result := f.typeResolution.result - if r := &f.typeResolution; r.outerSelf != 0 && result && c.circularityStart != 0 && c.circularityStart <= n && c.circularityStart >= int(r.outerSelf) && !r.participant { - // The resolution re-entered a hidden resolution of itself, which a - // computation from scratch of the outer one always does, and what it - // observed in progress was all entered by the outer one, so its result - // is what the outer one computes first from scratch: the outer one - // keeps it, as must the cycle. - r.participant = true - c.cycleParticipants = append(c.cycleParticipants, *r) - } else if r := &f.typeResolution; result && !r.participant && c.observedOnlyOutermostMeasurement(n) { - // The outermost measurement of variances hides what is below it and - // is measured from the canonical entry of its circularity, so what - // it computes is the same whatever led to it. This resolution only - // observed its incomplete variances, so its result is what it - // computes first from scratch too, which reaches the measurement and - // computes it again inside, as main keeps the first result set. + observed := c.observedHead + c.observedHead = minObservedHead(f.prevObservedHead, observed) + if r := &f.typeResolution; result && !r.participant && c.circularityStart != 0 && c.keepsResultInCycle(n, f, observed) { r.participant = true c.cycleParticipants = append(c.cycleParticipants, *r) } @@ -20901,6 +20948,8 @@ func (c *Checker) beginTypeResolution(target TypeSystemEntity, propertyName Type f.prevMemo = int32(c.memoTop) f.isTypeResolution = true f.prevTypeResolution = int32(c.typeResolutionTop) + f.prevObservedHead = c.observedHead + c.observedHead = 0 f.typeResolution.target = target f.typeResolution.propertyName = propertyName f.typeResolution.result = true diff --git a/tsc/internal/checker/memo.go b/tsc/internal/checker/memo.go index 3d34083b97c4c..f81a23df83356 100644 --- a/tsc/internal/checker/memo.go +++ b/tsc/internal/checker/memo.go @@ -1984,6 +1984,7 @@ func (c *Checker) discardCycle(mark int, retain bool) { // is incomplete is discarded when it finishes, as for the outermost, rather // than read by the rest of the outer cycle. func (c *Checker) extendCircularity(head int) { + c.observedHead = minObservedHead(c.observedHead, int32(head)) if c.checking && c.memoTop != 0 { c.recordFromCheck() } diff --git a/tsc/internal/checker/resolutions.go b/tsc/internal/checker/resolutions.go index a42f40f350b1e..ca86cf70c5e64 100644 --- a/tsc/internal/checker/resolutions.go +++ b/tsc/internal/checker/resolutions.go @@ -51,6 +51,7 @@ type resolution struct { typeResolution TypeResolution isTypeResolution bool prevTypeResolution int32 // Checker.typeResolutionTop on entry + prevObservedHead int32 // Checker.observedHead on entry // Checker.searchableTop on entry, for a type resolution other than that // of a current node. prevSearchable int32 diff --git a/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=typesfirst).errors.txt b/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=typesfirst).errors.txt deleted file mode 100644 index 495583f737e9e..0000000000000 --- a/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=typesfirst).errors.txt +++ /dev/null @@ -1,40 +0,0 @@ -circularScopeThunkReverseMapped.ts(12,14): error TS7022: '$' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. -circularScopeThunkReverseMapped.ts(14,5): error TS7023: 'b' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. -circularScopeThunkReverseMapped.ts(14,41): error TS7006: Parameter 'n' implicitly has an 'any' type. -circularScopeThunkReverseMapped.ts(18,14): error TS7022: '$2' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. -circularScopeThunkReverseMapped.ts(20,5): error TS7023: 'b' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. - - -==== circularScopeThunkReverseMapped.ts (5 errors) ==== - type Resolve = T extends () => infer R ? R : T; - type Validate = { [K in keyof def]: def[K] extends () => infer R ? () => R : def[K] }; - interface Scope { - type(k: k): Resolve; - } - interface Type { - pipe(f: (x: T) => U): Type; - } - declare function scope(def: Validate): Scope; - declare function type(x: T): Type; - - export const $ = scope({ - ~ -!!! error TS7022: '$' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. - a: type("string").pipe(function (s) { return s.length; }), - b: () => $.type("a").pipe(function (n) { return n === 0; }), - ~ -!!! error TS7023: 'b' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. - ~ -!!! error TS7006: Parameter 'n' implicitly has an 'any' type. - }); - - // Entered from the function in the first property when queried in reverse - export const $2 = scope({ - ~~ -!!! error TS7022: '$2' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. - a: type("string").pipe(function (s) { return s.length; }), - b: () => $2.type("a").pipe((n: number) => n === 0), - ~ -!!! error TS7023: 'b' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. - }); - \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=typesfirst).symbols b/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=typesfirst).symbols index 162296d91ea40..cefed83354abf 100644 --- a/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=typesfirst).symbols +++ b/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=typesfirst).symbols @@ -82,7 +82,11 @@ export const $ = scope({ b: () => $.type("a").pipe(function (n) { return n === 0; }), >b : Symbol(b, Decl(circularScopeThunkReverseMapped.ts, 12, 62)) +>$.type("a").pipe : Symbol(Type.pipe, Decl(circularScopeThunkReverseMapped.ts, 5, 19)) +>$.type : Symbol(Scope.type, Decl(circularScopeThunkReverseMapped.ts, 2, 22)) >$ : Symbol($, Decl(circularScopeThunkReverseMapped.ts, 11, 12)) +>type : Symbol(Scope.type, Decl(circularScopeThunkReverseMapped.ts, 2, 22)) +>pipe : Symbol(Type.pipe, Decl(circularScopeThunkReverseMapped.ts, 5, 19)) >n : Symbol(n, Decl(circularScopeThunkReverseMapped.ts, 13, 40)) >n : Symbol(n, Decl(circularScopeThunkReverseMapped.ts, 13, 40)) @@ -105,7 +109,11 @@ export const $2 = scope({ b: () => $2.type("a").pipe((n: number) => n === 0), >b : Symbol(b, Decl(circularScopeThunkReverseMapped.ts, 18, 62)) +>$2.type("a").pipe : Symbol(Type.pipe, Decl(circularScopeThunkReverseMapped.ts, 5, 19)) +>$2.type : Symbol(Scope.type, Decl(circularScopeThunkReverseMapped.ts, 2, 22)) >$2 : Symbol($2, Decl(circularScopeThunkReverseMapped.ts, 17, 12)) +>type : Symbol(Scope.type, Decl(circularScopeThunkReverseMapped.ts, 2, 22)) +>pipe : Symbol(Type.pipe, Decl(circularScopeThunkReverseMapped.ts, 5, 19)) >n : Symbol(n, Decl(circularScopeThunkReverseMapped.ts, 19, 32)) >n : Symbol(n, Decl(circularScopeThunkReverseMapped.ts, 19, 32)) diff --git a/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=typesfirst).types b/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=typesfirst).types index cd86ca9ddbbf9..f1af064690eee 100644 --- a/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=typesfirst).types +++ b/tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=typesfirst).types @@ -27,10 +27,10 @@ declare function type(x: T): Type; >x : T export const $ = scope({ ->$ : any ->scope({ a: type("string").pipe(function (s) { return s.length; }), b: () => $.type("a").pipe(function (n) { return n === 0; }),}) : Scope<{ readonly a: Type; readonly b: () => any; }> +>$ : Scope<{ a: Type; b: () => Type; }> +>scope({ a: type("string").pipe(function (s) { return s.length; }), b: () => $.type("a").pipe(function (n) { return n === 0; }),}) : Scope<{ readonly a: Type; readonly b: () => Type; }> >scope : (def: Validate) => Scope ->{ a: type("string").pipe(function (s) { return s.length; }), b: () => $.type("a").pipe(function (n) { return n === 0; }),} : { a: Type; b: () => any; } +>{ a: type("string").pipe(function (s) { return s.length; }), b: () => $.type("a").pipe(function (n) { return n === 0; }),} : { a: Type; b: () => Type; } a: type("string").pipe(function (s) { return s.length; }), >a : Type @@ -47,30 +47,30 @@ export const $ = scope({ >length : number b: () => $.type("a").pipe(function (n) { return n === 0; }), ->b : () => any ->() => $.type("a").pipe(function (n) { return n === 0; }) : () => any ->$.type("a").pipe(function (n) { return n === 0; }) : any ->$.type("a").pipe : any ->$.type("a") : any ->$.type : any ->$ : any ->type : any +>b : () => Type +>() => $.type("a").pipe(function (n) { return n === 0; }) : () => Type +>$.type("a").pipe(function (n) { return n === 0; }) : Type +>$.type("a").pipe : (f: (x: number) => U) => Type +>$.type("a") : Type +>$.type : (k: k_1) => Resolve<{ a: Type; b: () => Type; }[k_1]> +>$ : Scope<{ a: Type; b: () => Type; }> +>type : (k: k_1) => Resolve<{ a: Type; b: () => Type; }[k_1]> >"a" : "a" ->pipe : any ->function (n) { return n === 0; } : (n: any) => boolean ->n : any +>pipe : (f: (x: number) => U) => Type +>function (n) { return n === 0; } : (n: number) => n is 0 +>n : number >n === 0 : boolean ->n : any +>n : number >0 : 0 }); // Entered from the function in the first property when queried in reverse export const $2 = scope({ ->$2 : any ->scope({ a: type("string").pipe(function (s) { return s.length; }), b: () => $2.type("a").pipe((n: number) => n === 0),}) : Scope<{ readonly a: Type; readonly b: () => any; }> +>$2 : Scope<{ a: Type; b: () => Type; }> +>scope({ a: type("string").pipe(function (s) { return s.length; }), b: () => $2.type("a").pipe((n: number) => n === 0),}) : Scope<{ readonly a: Type; readonly b: () => Type; }> >scope : (def: Validate) => Scope ->{ a: type("string").pipe(function (s) { return s.length; }), b: () => $2.type("a").pipe((n: number) => n === 0),} : { a: Type; b: () => any; } +>{ a: type("string").pipe(function (s) { return s.length; }), b: () => $2.type("a").pipe((n: number) => n === 0),} : { a: Type; b: () => Type; } a: type("string").pipe(function (s) { return s.length; }), >a : Type @@ -87,16 +87,16 @@ export const $2 = scope({ >length : number b: () => $2.type("a").pipe((n: number) => n === 0), ->b : () => any ->() => $2.type("a").pipe((n: number) => n === 0) : () => any ->$2.type("a").pipe((n: number) => n === 0) : any ->$2.type("a").pipe : any ->$2.type("a") : any ->$2.type : any ->$2 : any ->type : any +>b : () => Type +>() => $2.type("a").pipe((n: number) => n === 0) : () => Type +>$2.type("a").pipe((n: number) => n === 0) : Type +>$2.type("a").pipe : (f: (x: number) => U) => Type +>$2.type("a") : Type +>$2.type : (k: k_1) => Resolve<{ a: Type; b: () => Type; }[k_1]> +>$2 : Scope<{ a: Type; b: () => Type; }> +>type : (k: k_1) => Resolve<{ a: Type; b: () => Type; }[k_1]> >"a" : "a" ->pipe : any +>pipe : (f: (x: number) => U) => Type >(n: number) => n === 0 : (n: number) => n is 0 >n : number >n === 0 : boolean