From b3d6e3372e9bfce1af94825849825e5724f8f01d Mon Sep 17 00:00:00 2001 From: Steven Chong <25894545+teamchong@users.noreply.github.com> Date: Mon, 26 Oct 2026 14:19:06 -0400 Subject: [PATCH 01/10] perf(core,tsoptions): optimize Arena chunk sizing and add pointer-free PagedLinkStore - Fix Arena.New and Arena.NewSlice passing len(a.data) instead of cap(a.data) to nextArenaSize, and raise steady-state chunk ceiling to 32 KiB (1,024 elements). - Add IndexedArena (1,024-element blocks) and PagedValueLinkStore so PagedLinkStore pages store 32-bit uint32 indices instead of 64-bit pointers, halving page size and eliminating GC pointer scanning of link pages. - Split page-allocation slow paths out of PagedLinkStore.Get, PagedLinkStore.TryGet, and PagedValueLinkStore.Get so fast-path lookups inline. - Add core.SuspendGC and --maxMemory compiler option. --- tools/scripts/tsc/options.ts | 12 ++ tsc/internal/core/arena.go | 71 +++++++-- tsc/internal/core/bfs.go | 88 ++++++----- tsc/internal/core/core.go | 61 +++++++- tsc/internal/core/linkstore.go | 117 +++++++++++--- tsc/internal/core/linkstore_test.go | 145 ++++++++++++++++++ tsc/internal/core/options_generated.go | 5 + tsc/internal/core/workgroup.go | 11 +- .../diagnosticMessages.generated.json | 1 + .../diagnostics/diagnosticMessages.json | 4 + .../diagnostics/diagnostics_generated.go | 3 + .../tsoptions/declarations_generated.go | 7 + tsc/internal/tsoptions/options_generated.go | 7 + .../reference/tsbuild/commandLine/help.js | 3 + .../reference/tsbuild/commandLine/locale.js | 3 + .../reference/tsc/commandLine/help-all.js | 3 + 16 files changed, 464 insertions(+), 77 deletions(-) create mode 100644 tsc/internal/core/linkstore_test.go diff --git a/tools/scripts/tsc/options.ts b/tools/scripts/tsc/options.ts index 6ebec633e1931..3f27b59602fbe 100644 --- a/tools/scripts/tsc/options.ts +++ b/tools/scripts/tsc/options.ts @@ -1626,6 +1626,17 @@ export const options: OptionsModel = { minValue: 1, }, }, + { + name: "maxMemory", + type: "*int", + internal: true, + declaration: { + group: "commonOptionsWithBuild", + category: diagnostic("Command-line Options"), + description: diagnostic("Set the memory limit in MiB for recycling checkers during no-emit type checking."), + minValue: 1, + }, + }, ], // Preserve help and config output order independently of compiler fields. declarationOrder: { @@ -1657,6 +1668,7 @@ export const options: OptionsModel = { "singleThreaded", "pprofDir", "checkers", + "maxMemory", "runExternalCode", ], optionsForCompiler: [ diff --git a/tsc/internal/core/arena.go b/tsc/internal/core/arena.go index 082572ae35e81..82d5da8d61908 100644 --- a/tsc/internal/core/arena.go +++ b/tsc/internal/core/arena.go @@ -1,6 +1,7 @@ package core import ( + "runtime" "slices" "unsafe" ) @@ -16,14 +17,26 @@ type Arena[T any] struct { func (a *Arena[T]) New() *T { if len(a.data) == cap(a.data) { nextSize := nextArenaSize[T](cap(a.data)) - // Use the same trick as slices.Concat; Grow rounds up to the next size class. - a.data = slices.Grow[[]T](nil, nextSize) + if runtime.GOARCH == "wasm" { + a.data = make([]T, 0, nextSize) + } else { + // Use the same trick as slices.Concat; Grow rounds up to the next size class. + a.data = slices.Grow[[]T](nil, nextSize) + } } index := len(a.data) a.data = a.data[:index+1] return &a.data[index] } +func (a *Arena[T]) Pop(p *T) { + if n := len(a.data); n > 0 && &a.data[n-1] == p { + var zero T + a.data[n-1] = zero + a.data = a.data[:n-1] + } +} + // Allocate a slice of the given size in the arena. If the requested size is beyond the capacity of the arena // and an arena of the next size up still wouldn't fit the slice, make a separate memory allocation for the slice. // Otherwise, grow the arena if necessary and allocate a slice out of it. The length and capacity of the resulting @@ -37,8 +50,12 @@ func (a *Arena[T]) NewSlice(size int) []T { if size > nextSize { return make([]T, size) } - // Use the same trick as slices.Concat; Grow rounds up to the next size class. - a.data = slices.Grow[[]T](nil, nextSize) + if runtime.GOARCH == "wasm" { + a.data = make([]T, 0, nextSize) + } else { + // Use the same trick as slices.Concat; Grow rounds up to the next size class. + a.data = slices.Grow[[]T](nil, nextSize) + } } newLen := len(a.data) + size slice := a.data[len(a.data):newLen:newLen] @@ -62,10 +79,46 @@ func (a *Arena[T]) Clone(t []T) []T { } func nextArenaSize[T any](size int) int { - // Target 512 bytes before allocator size-class rounding, with at least one - // value. A surviving interior pointer retains and scans the whole chunk. - maxSize := max(512/max(int(unsafe.Sizeof(*new(T))), 1), 1) - size = max(size, 1) - size = min(size*2, 256, maxSize) + if runtime.GOARCH == "wasm" { + maxSize := max(4096/max(int(unsafe.Sizeof(*new(T))), 1), 1) + size = max(size, 8) + size = min(size*2, 1024, maxSize) + return size + } + // Target 32 KiB (four Go pages) before allocator size-class rounding, with at + // least one value. Small arenas start at 8 elements and double up to maxSize. + maxSize := max(32768/max(int(unsafe.Sizeof(*new(T))), 1), 1) + size = max(size, 4) + size = min(size*2, 1024, maxSize) return size } + +// IndexedArena keeps values at stable addresses and references them with uint32 +// indexes instead of Go pointers. Index zero is nil. Unlike Arena, it retains all +// blocks so that an index alone keeps its value reachable through the arena. +type IndexedArena[T any] struct { + blocks []*[1 << indexedArenaPageShift]T + size uint32 +} + +const indexedArenaPageShift = 10 + +func (a *IndexedArena[T]) New() (uint32, *T) { + if a.size == ^uint32(0) { + panic("IndexedArena exhausted") + } + index := a.size + if int(index>>indexedArenaPageShift) >= len(a.blocks) { + a.blocks = append(a.blocks, new([1 << indexedArenaPageShift]T)) + } + a.size = index + 1 + return index + 1, &a.blocks[index>>indexedArenaPageShift][index&((1<>indexedArenaPageShift][i&((1<= lowestGoal.Load() { - return // Stop processing if we already found a lower result - } - - // If we have already visited this node, skip it. - if !visited.AddIfAbsent(getKey(j.node)) { - // Note that if we are here, we already visited this node at a - // previous *level*, which means `visit` must have returned false, - // so we don't need to update our result indices. This holds true - // because we deduplicated jobs before queuing the level. - return - } + runJob := func(i int, j *breadthFirstSearchJob[N]) { + if int64(i) >= lowestGoal.Load() { + return // Stop processing if we already found a lower result + } - isResult, stop := visit(j.node) - if isResult { - // We found a result, so we will stop at this level, but an - // earlier job may still find a true result at a lower index. - if stop { - updateMin(&lowestGoal, int64(i)) - return - } - if fallback == nil { - updateMin(&lowestFallback, int64(i)) - } - } + // If we have already visited this node, skip it. + if !visited.AddIfAbsent(getKey(j.node)) { + // Note that if we are here, we already visited this node at a + // previous *level*, which means `visit` must have returned false, + // so we don't need to update our result indices. This holds true + // because we deduplicated jobs before queuing the level. + return + } - if int64(i) >= lowestGoal.Load() { - // If `visit` is expensive, it's likely that by the time we get here, - // a different job has already found a lower index result, so we - // don't even need to collect the next jobs. + isResult, stop := visit(j.node) + if isResult { + // We found a result, so we will stop at this level, but an + // earlier job may still find a true result at a lower index. + if stop { + updateMin(&lowestGoal, int64(i)) return } - // Add the next level jobs - neighborNodes := neighbors(j.node) - if len(neighborNodes) > 0 { - nextJobCount.Add(int64(len(neighborNodes))) - next[i] = Map(neighborNodes, func(child N) *breadthFirstSearchJob[N] { - return &breadthFirstSearchJob[N]{node: child, parent: j} - }) + if fallback == nil { + updateMin(&lowestFallback, int64(i)) } - }(i, j) + } + + if int64(i) >= lowestGoal.Load() { + // If `visit` is expensive, it's likely that by the time we get here, + // a different job has already found a lower index result, so we + // don't even need to collect the next jobs. + return + } + // Add the next level jobs + neighborNodes := neighbors(j.node) + if len(neighborNodes) > 0 { + nextJobCount.Add(int64(len(neighborNodes))) + next[i] = Map(neighborNodes, func(child N) *breadthFirstSearchJob[N] { + return &breadthFirstSearchJob[N]{node: child, parent: j} + }) + } + } + for j := range jobs.Values() { + if runtime.GOARCH == "wasm" { + runJob(i, j) + } else { + wg.Add(1) + go func(i int, j *breadthFirstSearchJob[N]) { + defer wg.Done() + runJob(i, j) + }(i, j) + } i++ } wg.Wait() diff --git a/tsc/internal/core/core.go b/tsc/internal/core/core.go index f5c296b9b5bb0..3bd7b9187cde3 100644 --- a/tsc/internal/core/core.go +++ b/tsc/internal/core/core.go @@ -33,10 +33,41 @@ func ApplyDebugStackLimit() { rtdebug.SetMaxStack(n) } +func SuspendGC() func() { + if os.Getenv("GOGC") != "" { //nolint:forbidigo + return nil + } + prev := rtdebug.SetGCPercent(-1) + if prev < 0 { + return nil + } + return func() { + rtdebug.SetGCPercent(prev) + } +} + func Filter[T any](slice []T, f func(T) bool) []T { for i, value := range slice { if !f(value) { - result := slices.Clone(slice[:i]) + if i == 0 { + for i++; i < len(slice); i++ { + value = slice[i] + if f(value) { + result := make([]T, 1, len(slice)-i) + result[0] = value + for i++; i < len(slice); i++ { + value = slice[i] + if f(value) { + result = append(result, value) + } + } + return result + } + } + return slice[:0:0] + } + result := make([]T, i, len(slice)-1) + copy(result, slice[:i]) for i++; i < len(slice); i++ { value = slice[i] if f(value) { @@ -64,7 +95,25 @@ func FilterSeq[T any](slice []T, f func(T) bool) iter.Seq[T] { func FilterIndex[T any](slice []T, f func(T, int, []T) bool) []T { for i, value := range slice { if !f(value, i, slice) { - result := slices.Clone(slice[:i]) + if i == 0 { + for i++; i < len(slice); i++ { + value = slice[i] + if f(value, i, slice) { + result := make([]T, 1, len(slice)-i) + result[0] = value + for i++; i < len(slice); i++ { + value = slice[i] + if f(value, i, slice) { + result = append(result, value) + } + } + return result + } + } + return slice[:0:0] + } + result := make([]T, i, len(slice)-1) + copy(result, slice[:i]) for i++; i < len(slice); i++ { value = slice[i] if f(value, i, slice) { @@ -309,7 +358,10 @@ func Concatenate[T any](s1 []T, s2 []T) []T { if len(s1) == 0 { return s2 } - return slices.Concat(s1, s2) + result := make([]T, len(s1)+len(s2)) + copy(result, s1) + copy(result[len(s1):], s2) + return result } func Splice[T any](s1 []T, start int, deleteCount int, items ...T) []T { @@ -825,7 +877,8 @@ func Deduplicate[T comparable](slice []T) []T { if len(slice) > 1 { for i, value := range slice { if slices.Contains(slice[:i], value) { - result := slices.Clone(slice[:i]) + result := make([]T, i, len(slice)-1) + copy(result, slice[:i]) for i++; i < len(slice); i++ { value = slice[i] if !slices.Contains(result, value) { diff --git a/tsc/internal/core/linkstore.go b/tsc/internal/core/linkstore.go index 1866a1a2638f2..6eeeaaff48edc 100644 --- a/tsc/internal/core/linkstore.go +++ b/tsc/internal/core/linkstore.go @@ -1,9 +1,5 @@ package core -import ( - "slices" -) - // Links store type LinkStore[K comparable, V any] struct { @@ -12,14 +8,17 @@ type LinkStore[K comparable, V any] struct { } func (s *LinkStore[K, V]) Get(key K) *V { - value := s.entries[key] - if value != nil { + if value := s.entries[key]; value != nil { return value } + return s.getSlow(key) +} + +func (s *LinkStore[K, V]) getSlow(key K) *V { if s.entries == nil { s.entries = make(map[K]*V) } - value = s.arena.New() + value := s.arena.New() s.entries[key] = value return value } @@ -34,48 +33,120 @@ func (s *LinkStore[K, V]) TryGet(key K) *V { } const ( - LinkPageShift = 8 + LinkPageShift = 6 LinkPageSize = 1 << LinkPageShift LinkPageMask = LinkPageSize - 1 ) // PagedLinkStore implements a sparse-array-like structure for storing elements keyed by dense uint64 keys. -// Elements are allocated in an area, element references are stored in fixed-size pages of 256 entries, and -// an index of pages is maintained in a growable list. +// Elements are allocated in an arena, and compressed indexes are stored in fixed-size pages of 256 entries. +// The pages contain no Go pointers, halving their size on 64-bit hosts and avoiding GC scans of each entry. type PagedLinkStore[V any] struct { - pages []*[LinkPageSize]*V - arena Arena[V] + pages []*[LinkPageSize]uint32 + pageArena Arena[[LinkPageSize]uint32] + arena IndexedArena[V] } func (s *PagedLinkStore[V]) Get(key uint64) *V { + if pageIndex := int(key >> LinkPageShift); pageIndex < len(s.pages) { + if page := s.pages[pageIndex]; page != nil { + if index := page[key&LinkPageMask]; index != 0 { + index-- + return &s.arena.blocks[index>>indexedArenaPageShift][index&((1<> LinkPageShift if int(pageIndex) >= len(s.pages) { - // Grow the length of the list to pageIndex+1 - s.pages = slices.Grow(s.pages, int(pageIndex)-len(s.pages)+1)[:pageIndex+1] + needed := int(pageIndex) + 1 + if needed <= cap(s.pages) { + s.pages = s.pages[:needed] + } else { + newCap := max(needed, cap(s.pages)*2, 8) + next := make([]*[LinkPageSize]uint32, needed, newCap) + copy(next, s.pages) + s.pages = next + } } page := s.pages[pageIndex] if page == nil { - page = new([LinkPageSize]*V) + page = s.pageArena.New() s.pages[pageIndex] = page } - link := page[key&LinkPageMask] - if link == nil { - link = s.arena.New() - page[key&LinkPageMask] = link + index := page[key&LinkPageMask] + if index == 0 { + id, value := s.arena.New() + page[key&LinkPageMask] = id + return value } - return link + return s.arena.Get(index) } func (s *PagedLinkStore[V]) Has(key uint64) bool { - return s.TryGet(key) != nil + if pageIndex := int(key >> LinkPageShift); pageIndex < len(s.pages) { + if page := s.pages[pageIndex]; page != nil { + return page[key&LinkPageMask] != 0 + } + } + return false } func (s *PagedLinkStore[V]) TryGet(key uint64) *V { + if pageIndex := int(key >> LinkPageShift); pageIndex < len(s.pages) { + if page := s.pages[pageIndex]; page != nil { + if index := page[key&LinkPageMask]; index != 0 { + index-- + return &s.arena.blocks[index>>indexedArenaPageShift][index&((1<> LinkPageShift - if int(pageIndex) < len(s.pages) { + if int(pageIndex) >= len(s.pages) { + needed := int(pageIndex) + 1 + if needed <= cap(s.pages) { + s.pages = s.pages[:needed] + } else { + newCap := max(needed, cap(s.pages)*2, 8) + next := make([]*[LinkPageSize]V, needed, newCap) + copy(next, s.pages) + s.pages = next + } + } + page := s.pages[pageIndex] + if page == nil { + page = s.pageArena.New() + s.pages[pageIndex] = page + } + return &page[key&LinkPageMask] +} + +func (s *PagedValueLinkStore[V]) TryGet(key uint64) *V { + if pageIndex := int(key >> LinkPageShift); pageIndex < len(s.pages) { if page := s.pages[pageIndex]; page != nil { - return page[key&LinkPageMask] + return &page[key&LinkPageMask] } } return nil diff --git a/tsc/internal/core/linkstore_test.go b/tsc/internal/core/linkstore_test.go new file mode 100644 index 0000000000000..af785a980a7f8 --- /dev/null +++ b/tsc/internal/core/linkstore_test.go @@ -0,0 +1,145 @@ +package core_test + +import ( + "runtime" + "strconv" + "testing" + + "github.com/microsoft/TypeScript/tsc/internal/core" + "gotest.tools/v3/assert" +) + +func TestLinkStore(t *testing.T) { + t.Parallel() + type value struct { + number int + text *string + } + var store core.LinkStore[int, value] + assert.Assert(t, !store.Has(0)) + assert.Assert(t, store.TryGet(0) == nil) + + values := make([]*value, 4096) + for i := range values { + v := store.Get(i) + assert.Equal(t, *v, value{}) + v.number = i + v.text = new("retained") + values[i] = v + } + runtime.GC() + for i, v := range values { + assert.Assert(t, store.Has(i)) + assert.Equal(t, store.Get(i), v) + assert.Equal(t, store.TryGet(i), v) + assert.Equal(t, v.number, i) + assert.Equal(t, *v.text, "retained") + } + assert.Assert(t, !store.Has(len(values))) + assert.Assert(t, store.TryGet(len(values)) == nil) +} + +func TestPagedLinkStore(t *testing.T) { + t.Parallel() + var store core.PagedLinkStore[int] + keys := []uint64{0, 255, 256, 65535, 16777215} + values := make([]*int, len(keys)) + for _, key := range keys { + assert.Assert(t, !store.Has(key)) + } + for i, key := range keys { + v := store.Get(key) + assert.Equal(t, *v, 0) + *v = i + 1 + values[i] = v + } + runtime.GC() + for i, key := range keys { + assert.Assert(t, store.Has(key)) + assert.Equal(t, store.Get(key), values[i]) + assert.Equal(t, *store.TryGet(key), i+1) + } + assert.Assert(t, !store.Has(1)) + assert.Assert(t, store.TryGet(1) == nil) +} + +func BenchmarkLinkStore(b *testing.B) { + type value struct { + typ *int + flags uint32 + } + b.ReportAllocs() + for b.Loop() { + var store core.LinkStore[int, value] + for i := range 10000 { + store.Get(i).flags = uint32(i) + } + for i := range 10000 { + if store.Get(i).flags != uint32(i) { + b.Fatal("link changed during arena growth") + } + } + + } +} + +func TestIndexedArena(t *testing.T) { + t.Parallel() + type value struct { + number int + text *string + } + var arena core.IndexedArena[value] + assert.Assert(t, arena.Get(0) == nil) + values := make([]*value, 4096) + for i := range values { + index, v := arena.New() + assert.Equal(t, index, uint32(i+1)) + assert.Equal(t, *v, value{}) + v.number = i + v.text = new("retained") + values[i] = v + } + runtime.GC() + for i, v := range values { + assert.Equal(t, arena.Get(uint32(i+1)), v) + assert.Equal(t, v.number, i) + assert.Equal(t, *v.text, "retained") + } +} + +func TestIndexedArenaReachability(t *testing.T) { + t.Parallel() + var arena core.IndexedArena[*string] + for i := range 4096 { + _, value := arena.New() + *value = new(strconv.Itoa(i)) + } + runtime.GC() + for i := range 4096 { + assert.Equal(t, **arena.Get(uint32(i + 1)), strconv.Itoa(i)) + } +} + +func BenchmarkPagedLinkStore(b *testing.B) { + for _, stride := range []uint64{1, 16, 256} { + b.Run(strconv.FormatUint(stride, 10), func(b *testing.B) { + type value struct { + typ *int + flags uint32 + } + b.ReportAllocs() + for b.Loop() { + var store core.PagedLinkStore[value] + for i := range 10000 { + store.Get(uint64(i) * stride).flags = uint32(i) + } + for i := range 10000 { + if store.Get(uint64(i)*stride).flags != uint32(i) { + b.Fatal("link changed during arena growth") + } + } + } + }) + } +} diff --git a/tsc/internal/core/options_generated.go b/tsc/internal/core/options_generated.go index 911b2d6af13d6..04ff45f076a39 100644 --- a/tsc/internal/core/options_generated.go +++ b/tsc/internal/core/options_generated.go @@ -160,6 +160,7 @@ type CompilerOptions struct { SingleThreaded Tristate `json:"singleThreaded,omitzero"` Quiet Tristate `json:"quiet,omitzero"` Checkers *int `json:"checkers,omitzero"` + MaxMemory *int `json:"maxMemory,omitzero"` } // Clone creates a shallow copy of the CompilerOptions. @@ -297,6 +298,7 @@ func (options *CompilerOptions) Clone() *CompilerOptions { SingleThreaded: options.SingleThreaded, Quiet: options.Quiet, Checkers: options.Checkers, + MaxMemory: options.MaxMemory, } } @@ -707,6 +709,9 @@ func (options *CompilerOptions) Equals(other *CompilerOptions) bool { if options.Checkers != other.Checkers && (options.Checkers == nil || other.Checkers == nil || *options.Checkers != *other.Checkers) { return false } + if options.MaxMemory != other.MaxMemory && (options.MaxMemory == nil || other.MaxMemory == nil || *options.MaxMemory != *other.MaxMemory) { + return false + } return true } diff --git a/tsc/internal/core/workgroup.go b/tsc/internal/core/workgroup.go index 73618a1a19fc1..03568da938dc1 100644 --- a/tsc/internal/core/workgroup.go +++ b/tsc/internal/core/workgroup.go @@ -2,6 +2,7 @@ package core import ( "context" + "runtime" "sync" "sync/atomic" @@ -18,7 +19,7 @@ type WorkGroup interface { } func NewWorkGroup(singleThreaded bool) WorkGroup { - if singleThreaded { + if singleThreaded || runtime.GOARCH == "wasm" { return &singleThreadedWorkGroup{} } return ¶llelWorkGroup{} @@ -35,6 +36,10 @@ func (w *parallelWorkGroup) Queue(fn func()) { if w.done.Load() { panic("Queue called after RunAndWait returned") } + if runtime.GOARCH == "wasm" { + fn() + return + } w.wg.Go(func() { fn() @@ -106,6 +111,10 @@ func NewThrottleGroup(ctx context.Context, semaphore chan struct{}) *ThrottleGro // Go runs the given function in a new goroutine, but first acquires a slot from the semaphore. // The semaphore slot is released when the function completes. func (tg *ThrottleGroup) Go(fn func() error) { + if runtime.GOARCH == "wasm" { + _ = fn() + return + } tg.group.Go(func() error { // Acquire semaphore slot - this will block until a slot is available tg.semaphore <- struct{}{} diff --git a/tsc/internal/diagnostics/diagnosticMessages.generated.json b/tsc/internal/diagnostics/diagnosticMessages.generated.json index ae749e01b85b5..14f0a7f8e9ae7 100644 --- a/tsc/internal/diagnostics/diagnosticMessages.generated.json +++ b/tsc/internal/diagnostics/diagnosticMessages.generated.json @@ -1696,6 +1696,7 @@ "Set_the_number_of_projects_to_build_concurrently_6938": "Set the number of projects to build concurrently.", "Deduplicate_packages_with_the_same_name_and_version_6939": "Deduplicate packages with the same name and version.", "Allow_loading_external_content_mapper_plugins_that_execute_code_during_compilation_6940": "Allow loading external content mapper plugins that execute code during compilation.", + "Set_the_memory_limit_in_MiB_for_recycling_checkers_during_no_emit_type_checking_6941": "Set the memory limit in MiB for recycling checkers during no-emit type checking.", "Variable_0_implicitly_has_an_1_type_7005": "Variable '{0}' implicitly has an '{1}' type.", "Parameter_0_implicitly_has_an_1_type_7006": "Parameter '{0}' implicitly has an '{1}' type.", "Member_0_implicitly_has_an_1_type_7008": "Member '{0}' implicitly has an '{1}' type.", diff --git a/tsc/internal/diagnostics/diagnosticMessages.json b/tsc/internal/diagnostics/diagnosticMessages.json index d4bf59ab839de..8b623ebf1ea14 100644 --- a/tsc/internal/diagnostics/diagnosticMessages.json +++ b/tsc/internal/diagnostics/diagnosticMessages.json @@ -6819,6 +6819,10 @@ "category": "Message", "code": 6940 }, + "Set the memory limit in MiB for recycling checkers during no-emit type checking.": { + "category": "Message", + "code": 6941 + }, "Variable '{0}' implicitly has an '{1}' type.": { "category": "Error", diff --git a/tsc/internal/diagnostics/diagnostics_generated.go b/tsc/internal/diagnostics/diagnostics_generated.go index 0e08cf23701b7..1868ce2ae120a 100644 --- a/tsc/internal/diagnostics/diagnostics_generated.go +++ b/tsc/internal/diagnostics/diagnostics_generated.go @@ -3396,6 +3396,8 @@ var Deduplicate_packages_with_the_same_name_and_version = &Message{code: 6939, c var Allow_loading_external_content_mapper_plugins_that_execute_code_during_compilation = &Message{code: 6940, category: CategoryMessage, key: "Allow_loading_external_content_mapper_plugins_that_execute_code_during_compilation_6940", text: "Allow loading external content mapper plugins that execute code during compilation."} +var Set_the_memory_limit_in_MiB_for_recycling_checkers_during_no_emit_type_checking = &Message{code: 6941, category: CategoryMessage, key: "Set_the_memory_limit_in_MiB_for_recycling_checkers_during_no_emit_type_checking_6941", text: "Set the memory limit in MiB for recycling checkers during no-emit type checking."} + var Variable_0_implicitly_has_an_1_type = &Message{code: 7005, category: CategoryError, key: "Variable_0_implicitly_has_an_1_type_7005", text: "Variable '{0}' implicitly has an '{1}' type."} var Parameter_0_implicitly_has_an_1_type = &Message{code: 7006, category: CategoryError, key: "Parameter_0_implicitly_has_an_1_type_7006", text: "Parameter '{0}' implicitly has an '{1}' type."} @@ -6160,6 +6162,7 @@ var allMessages = [...]**Message{ &Set_the_number_of_projects_to_build_concurrently, &Deduplicate_packages_with_the_same_name_and_version, &Allow_loading_external_content_mapper_plugins_that_execute_code_during_compilation, + &Set_the_memory_limit_in_MiB_for_recycling_checkers_during_no_emit_type_checking, &Variable_0_implicitly_has_an_1_type, &Parameter_0_implicitly_has_an_1_type, &Member_0_implicitly_has_an_1_type, diff --git a/tsc/internal/tsoptions/declarations_generated.go b/tsc/internal/tsoptions/declarations_generated.go index 7f3b37706c4b9..097384b5fd5cf 100644 --- a/tsc/internal/tsoptions/declarations_generated.go +++ b/tsc/internal/tsoptions/declarations_generated.go @@ -234,6 +234,13 @@ var commonOptionsWithBuild = []*CommandLineOption{ DefaultValueDescription: diagnostics.X_4_unless_singleThreaded_is_passed, minValue: 1, }, + { + Name: "maxMemory", + Kind: CommandLineOptionTypeNumber, + Category: diagnostics.Command_line_Options, + Description: diagnostics.Set_the_memory_limit_in_MiB_for_recycling_checkers_during_no_emit_type_checking, + minValue: 1, + }, { Name: "runExternalCode", Kind: CommandLineOptionTypeBoolean, diff --git a/tsc/internal/tsoptions/options_generated.go b/tsc/internal/tsoptions/options_generated.go index 975913279a80c..f39267dc03913 100644 --- a/tsc/internal/tsoptions/options_generated.go +++ b/tsc/internal/tsoptions/options_generated.go @@ -288,6 +288,8 @@ func parseCompilerOptions(key string, value any, allOptions *core.CompilerOption allOptions.Quiet = ParseTristate(value) case "checkers": allOptions.Checkers = parseNumber(value) + case "maxMemory": + allOptions.MaxMemory = parseNumber(value) default: return false } @@ -1327,6 +1329,11 @@ func mergeCompilerOptionFields(targetOptions, sourceOptions *core.CompilerOption } else if sourceOptions.Checkers != nil { targetOptions.Checkers = sourceOptions.Checkers } + if explicitNullFields.Has("maxMemory") { + targetOptions.MaxMemory = nil + } else if sourceOptions.MaxMemory != nil { + targetOptions.MaxMemory = sourceOptions.MaxMemory + } } func handleOptionConfigDirTemplateSubstitution(compilerOptions *parsedCompilerOptions, basePath tspath.RootedDirectoryPath) { diff --git a/tsc/testdata/baselines/reference/tsbuild/commandLine/help.js b/tsc/testdata/baselines/reference/tsbuild/commandLine/help.js index 378781eb3dd57..216d8b921ff3f 100644 --- a/tsc/testdata/baselines/reference/tsbuild/commandLine/help.js +++ b/tsc/testdata/baselines/reference/tsbuild/commandLine/help.js @@ -134,6 +134,9 @@ Generate pprof CPU/memory profiles to the given directory. --checkers Set the number of checkers per project. +--maxMemory +Set the memory limit in MiB for recycling checkers during no-emit type checking. + --runExternalCode Allow loading external content mapper plugins that execute code during compilation. diff --git a/tsc/testdata/baselines/reference/tsbuild/commandLine/locale.js b/tsc/testdata/baselines/reference/tsbuild/commandLine/locale.js index c797a521ce26f..d295c23aa9d6e 100644 --- a/tsc/testdata/baselines/reference/tsbuild/commandLine/locale.js +++ b/tsc/testdata/baselines/reference/tsbuild/commandLine/locale.js @@ -134,6 +134,9 @@ Generate pprof CPU/memory profiles to the given directory. --checkers Set the number of checkers per project. +--maxMemory +Set the memory limit in MiB for recycling checkers during no-emit type checking. + --runExternalCode Allow loading external content mapper plugins that execute code during compilation. diff --git a/tsc/testdata/baselines/reference/tsc/commandLine/help-all.js b/tsc/testdata/baselines/reference/tsc/commandLine/help-all.js index e63595b43e09c..a1556bb789ce3 100644 --- a/tsc/testdata/baselines/reference/tsc/commandLine/help-all.js +++ b/tsc/testdata/baselines/reference/tsc/commandLine/help-all.js @@ -38,6 +38,9 @@ Print names of files that are part of the compilation and then stop processing. --locale Set the language of the messaging from TypeScript. This does not affect emit. +--maxMemory +Set the memory limit in MiB for recycling checkers during no-emit type checking. + --pprofDir Generate pprof CPU/memory profiles to the given directory. From 515cb753e0b963f2ff490377779e833f967e2ce2 Mon Sep 17 00:00:00 2001 From: Steven Chong <25894545+teamchong@users.noreply.github.com> Date: Mon, 26 Oct 2026 14:19:06 -0400 Subject: [PATCH 02/10] perf(scanner): replace keyword map and identifier closure with direct lookup tables - Replace textToKeyword map[string]ast.Kind lookups in GetIdentifierToken and IdentifierToKeywordKind with a 0-collision 512-entry direct-mapped keywordTable keyed by (len, c0, c1, cLast). - Replace the per-byte scanASCIIWhile closure in scanIdentifier with a 256-byte isASCIIIdentPart lookup table and inline space/tab skipping in Scan. --- tsc/internal/scanner/scanner.go | 34 ++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/tsc/internal/scanner/scanner.go b/tsc/internal/scanner/scanner.go index 17d5be310925f..8d5790636e1fc 100644 --- a/tsc/internal/scanner/scanner.go +++ b/tsc/internal/scanner/scanner.go @@ -243,6 +243,22 @@ var textToToken = func() map[string]ast.Kind { return m }() +var isASCIIIdentPart = func() [256]bool { + var table [256]bool + for b := byte('a'); b <= 'z'; b++ { + table[b] = true + } + for b := byte('A'); b <= 'Z'; b++ { + table[b] = true + } + for b := byte('0'); b <= '9'; b++ { + table[b] = true + } + table['_'] = true + table['$'] = true + return table +}() + type ScannerState struct { pos int // Current position in text (and ending position of current token) fullStartPos int // Starting position of current token including preceding whitespace @@ -531,6 +547,12 @@ func (s *Scanner) Scan() ast.Kind { case '\t', '\v', '\f', ' ': s.pos++ if s.skipTrivia { + text := s.text + pos := s.pos + for pos < s.end && (text[pos] == ' ' || text[pos] == '\t') { + pos++ + } + s.pos = pos continue } for { @@ -1534,13 +1556,15 @@ func (s *Scanner) scanIdentifier(prefixLength int, variant identifierVariant) bo ch := s.char() // Fast path for simple ASCII identifiers if variant != identifierVariantJSX && (stringutil.IsASCIILetter(ch) || ch == '_' || ch == '$') { - s.pos++ - s.scanASCIIWhile(func(b byte) bool { - return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9') || b == '_' || b == '$' - }) + text := s.text + pos := s.pos + 1 + for pos < s.end && isASCIIIdentPart[text[pos]] { + pos++ + } + s.pos = pos ch = s.char() if ch < utf8.RuneSelf && ch != '\\' { - s.tokenValue = s.text[start:s.pos] + s.tokenValue = text[start:pos] return true } s.pos = identifierStart From fdead5309e5c371978df3c85d894a6c4695e3a3b Mon Sep 17 00:00:00 2001 From: Steven Chong <25894545+teamchong@users.noreply.github.com> Date: Mon, 26 Oct 2026 14:19:06 -0400 Subject: [PATCH 03/10] perf(declarations): replace per-node diagnostic closures with state fields - Store diagnosticNode and diagnosticForNodeName directly on SymbolTrackerSharedState and evaluate accessibility diagnostics lazily via evalSymbolAccessibilityDiagnostic only when an error is reported, eliminating per-node closure allocations during declaration emit. - Construct a fresh ImportDeclaration via NewImportDeclaration instead of mutating Kind on a cloned node. --- .../transformers/declarations/tracker.go | 23 +++- .../transformers/declarations/transform.go | 112 +++++++++++++----- 2 files changed, 102 insertions(+), 33 deletions(-) diff --git a/tsc/internal/transformers/declarations/tracker.go b/tsc/internal/transformers/declarations/tracker.go index 14d0fcd5e5032..404c2ab9661c8 100644 --- a/tsc/internal/transformers/declarations/tracker.go +++ b/tsc/internal/transformers/declarations/tracker.go @@ -214,7 +214,7 @@ func (s *SymbolTrackerImpl) handleSymbolAccessibilityError(symbolAccessibilityRe // The checker should issue errors on unresolvable names, skip the declaration emit error for using a private/unreachable name for those } else if symbolAccessibilityResult.Accessibility != printer.SymbolAccessibilityNotResolved { // Report error - errorInfo := s.state.getSymbolAccessibilityDiagnostic(symbolAccessibilityResult) + errorInfo := s.state.evalSymbolAccessibilityDiagnostic(symbolAccessibilityResult) if errorInfo != nil { info := *errorInfo diagNode := symbolAccessibilityResult.ErrorNode @@ -240,6 +240,8 @@ type SymbolTrackerSharedState struct { lateMarkedStatements []*ast.Node diagnostics []*ast.Diagnostic getSymbolAccessibilityDiagnostic GetSymbolAccessibilityDiagnostic + diagnosticNode *ast.Node + diagnosticForNodeName bool errorNameNode *ast.Node isolatedDeclarations bool stripInternal bool @@ -248,6 +250,25 @@ type SymbolTrackerSharedState struct { reportExpandoFunctionErrors func(node *ast.Node) } +func (s *SymbolTrackerSharedState) setDiagnosticNode(node *ast.Node, forNodeName bool) { + s.getSymbolAccessibilityDiagnostic = nil + s.diagnosticNode = node + s.diagnosticForNodeName = forNodeName +} + +func (s *SymbolTrackerSharedState) evalSymbolAccessibilityDiagnostic(res printer.SymbolAccessibilityResult) *SymbolAccessibilityDiagnostic { + if s.getSymbolAccessibilityDiagnostic != nil { + return s.getSymbolAccessibilityDiagnostic(res) + } + if s.diagnosticNode != nil { + if s.diagnosticForNodeName { + return createGetSymbolAccessibilityDiagnosticForNodeName(s.diagnosticNode)(res) + } + return createGetSymbolAccessibilityDiagnosticForNode(s.diagnosticNode)(res) + } + return throwDiagnostic(res) +} + func (s *SymbolTrackerSharedState) addDiagnostic(diag *ast.Diagnostic) { s.diagnostics = append(s.diagnostics, diag) } diff --git a/tsc/internal/transformers/declarations/transform.go b/tsc/internal/transformers/declarations/transform.go index 8b5b449ec87f5..dde7c11a10a8c 100644 --- a/tsc/internal/transformers/declarations/transform.go +++ b/tsc/internal/transformers/declarations/transform.go @@ -543,26 +543,39 @@ func (tx *DeclarationTransformer) getTypeReferences() (result []*ast.FileReferen return result } -func (tx *DeclarationTransformer) setupDiagnosticContext(input *ast.Node) (bool, func()) { +type savedDiagnosticContext struct { + oldDiag GetSymbolAccessibilityDiagnostic + oldDiagNode *ast.Node + oldDiagForName bool + oldName *ast.Node + oldWithinObjectLiteralType bool +} + +func (tx *DeclarationTransformer) setupDiagnosticContext(input *ast.Node) (bool, savedDiagnosticContext) { canProduceDiagnostic := canProduceDiagnostics(input) - oldWithinObjectLiteralType := tx.suppressNewDiagnosticContexts + saved := savedDiagnosticContext{ + oldDiag: tx.state.getSymbolAccessibilityDiagnostic, + oldDiagNode: tx.state.diagnosticNode, + oldDiagForName: tx.state.diagnosticForNodeName, + oldName: tx.state.errorNameNode, + oldWithinObjectLiteralType: tx.suppressNewDiagnosticContexts, + } shouldEnterSuppressNewDiagnosticsContextContext := (input.Kind == ast.KindTypeLiteral || input.Kind == ast.KindMappedType) && !(input.Parent.Kind == ast.KindTypeAliasDeclaration || input.Parent.Kind == ast.KindJSTypeAliasDeclaration) - - oldDiag := tx.state.getSymbolAccessibilityDiagnostic if canProduceDiagnostic && !tx.suppressNewDiagnosticContexts { - tx.state.getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(input) + tx.state.setDiagnosticNode(input, false) } - oldName := tx.state.errorNameNode - if shouldEnterSuppressNewDiagnosticsContextContext { tx.suppressNewDiagnosticContexts = true } + return canProduceDiagnostic, saved +} - return canProduceDiagnostic, func() { - tx.state.getSymbolAccessibilityDiagnostic = oldDiag - tx.state.errorNameNode = oldName - tx.suppressNewDiagnosticContexts = oldWithinObjectLiteralType - } +func (tx *DeclarationTransformer) restoreDiagnosticContext(saved savedDiagnosticContext) { + tx.state.getSymbolAccessibilityDiagnostic = saved.oldDiag + tx.state.diagnosticNode = saved.oldDiagNode + tx.state.diagnosticForNodeName = saved.oldDiagForName + tx.state.errorNameNode = saved.oldName + tx.suppressNewDiagnosticContexts = saved.oldWithinObjectLiteralType } func (tx *DeclarationTransformer) visitDeclarationSubtree(input *ast.Node) *ast.Node { @@ -611,9 +624,19 @@ func (tx *DeclarationTransformer) visitDeclarationSubtree(input *ast.Node) *ast. tx.enclosingDeclaration = input } - canProduceDiagnostic, cleanupDiagnosticContext := tx.setupDiagnosticContext(input) - defer cleanupDiagnosticContext() - + canProduceDiagnostic := canProduceDiagnostics(input) + oldWithinObjectLiteralType := tx.suppressNewDiagnosticContexts + shouldEnterSuppressNewDiagnosticsContextContext := (input.Kind == ast.KindTypeLiteral || input.Kind == ast.KindMappedType) && !(input.Parent.Kind == ast.KindTypeAliasDeclaration || input.Parent.Kind == ast.KindJSTypeAliasDeclaration) + oldDiag := tx.state.getSymbolAccessibilityDiagnostic + oldDiagNode := tx.state.diagnosticNode + oldDiagForName := tx.state.diagnosticForNodeName + if canProduceDiagnostic && !tx.suppressNewDiagnosticContexts { + tx.state.setDiagnosticNode(input, false) + } + oldName := tx.state.errorNameNode + if shouldEnterSuppressNewDiagnosticsContextContext { + tx.suppressNewDiagnosticContexts = true + } var result *ast.Node switch input.Kind { @@ -697,13 +720,20 @@ func (tx *DeclarationTransformer) visitDeclarationSubtree(input *ast.Node) *ast. } tx.enclosingDeclaration = previousEnclosingDeclaration + tx.state.getSymbolAccessibilityDiagnostic = oldDiag + tx.state.diagnosticNode = oldDiagNode + tx.state.diagnosticForNodeName = oldDiagForName + tx.state.errorNameNode = oldName + tx.suppressNewDiagnosticContexts = oldWithinObjectLiteralType return result } func (tx *DeclarationTransformer) checkName(node *ast.Node) { oldDiag := tx.state.getSymbolAccessibilityDiagnostic + oldDiagNode := tx.state.diagnosticNode + oldDiagForName := tx.state.diagnosticForNodeName if !tx.suppressNewDiagnosticContexts { - tx.state.getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNodeName(node) + tx.state.setDiagnosticNode(node, true) } tx.state.errorNameNode = node.Name() debug.Assert(ast.HasDynamicName(node)) // Should only be called with dynamic names @@ -711,6 +741,8 @@ func (tx *DeclarationTransformer) checkName(node *ast.Node) { tx.checkEntityNameVisibility(entityName, tx.enclosingDeclaration) if !tx.suppressNewDiagnosticContexts { tx.state.getSymbolAccessibilityDiagnostic = oldDiag + tx.state.diagnosticNode = oldDiagNode + tx.state.diagnosticForNodeName = oldDiagForName } tx.state.errorNameNode = nil } @@ -1684,10 +1716,14 @@ func (tx *DeclarationTransformer) ensureType(node *ast.Node, ignorePrivate bool) oldErrorNameNode := tx.state.errorNameNode tx.state.errorNameNode = node.Name() var oldDiag GetSymbolAccessibilityDiagnostic + var oldDiagNode *ast.Node + var oldDiagForName bool if !tx.suppressNewDiagnosticContexts { oldDiag = tx.state.getSymbolAccessibilityDiagnostic + oldDiagNode = tx.state.diagnosticNode + oldDiagForName = tx.state.diagnosticForNodeName if canProduceDiagnostics(node) { - tx.state.getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(node) + tx.state.setDiagnosticNode(node, false) } } var typeNode *ast.Node @@ -1707,6 +1743,8 @@ func (tx *DeclarationTransformer) ensureType(node *ast.Node, ignorePrivate bool) tx.state.errorNameNode = oldErrorNameNode if !tx.suppressNewDiagnosticContexts { tx.state.getSymbolAccessibilityDiagnostic = oldDiag + tx.state.diagnosticNode = oldDiagNode + tx.state.diagnosticForNodeName = oldDiagForName } if typeNode == nil { return tx.Factory().NewKeywordTypeNode(ast.KindAnyKeyword) @@ -1773,9 +1811,11 @@ func (tx *DeclarationTransformer) transformTopLevelDeclaration(input *ast.Node) canProduceDiagnostic := canProduceDiagnostics(input) oldDiag := tx.state.getSymbolAccessibilityDiagnostic + oldDiagNode := tx.state.diagnosticNode + oldDiagForName := tx.state.diagnosticForNodeName oldName := tx.state.errorNameNode if canProduceDiagnostic { - tx.state.getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(input) + tx.state.setDiagnosticNode(input, false) } saveNeedsDeclare := tx.needsDeclare @@ -1802,6 +1842,8 @@ func (tx *DeclarationTransformer) transformTopLevelDeclaration(input *ast.Node) tx.enclosingDeclaration = previousEnclosingDeclaration tx.state.getSymbolAccessibilityDiagnostic = oldDiag + tx.state.diagnosticNode = oldDiagNode + tx.state.diagnosticForNodeName = oldDiagForName tx.needsDeclare = saveNeedsDeclare tx.state.errorNameNode = oldName return result @@ -1956,11 +1998,13 @@ func (tx *DeclarationTransformer) buildClassMembers(classNode *ast.Node, extraMe var parameterProperties []*ast.Node if ctor != nil { oldDiag := tx.state.getSymbolAccessibilityDiagnostic + oldDiagNode := tx.state.diagnosticNode + oldDiagForName := tx.state.diagnosticForNodeName for _, param := range ctor.AsConstructorDeclaration().Parameters.Nodes { if !ast.HasSyntacticModifier(param, ast.ModifierFlagsParameterPropertyModifier) || tx.shouldStripInternal(param) { continue } - tx.state.getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(param) + tx.state.setDiagnosticNode(param, false) if param.Name().Kind == ast.KindIdentifier { updated := tx.Factory().NewPropertyDeclaration( tx.ensureModifiers(param), @@ -1977,6 +2021,8 @@ func (tx *DeclarationTransformer) buildClassMembers(classNode *ast.Node, extraMe } } tx.state.getSymbolAccessibilityDiagnostic = oldDiag + tx.state.diagnosticNode = oldDiagNode + tx.state.diagnosticForNodeName = oldDiagForName } // When the class has at least one private identifier, create a unique constant identifier to retain the nominal typing behavior @@ -2706,8 +2752,7 @@ func (tx *DeclarationTransformer) stripDeclareModifiers(node *ast.Node) *ast.Nod func (tx *DeclarationTransformer) visitCJSExportAssignments(expression *ast.Node) *ast.Node { if expression != nil { - _, cleanupDiagnosticContext := tx.setupDiagnosticContext(expression) - defer cleanupDiagnosticContext() + _, savedDiag := tx.setupDiagnosticContext(expression) switch ast.GetAssignmentDeclarationKind(expression) { case ast.JSDeclarationKindModuleExports: if tx.state.currentSourceFile.CommonJSModuleIndicator != nil { @@ -2719,15 +2764,16 @@ func (tx *DeclarationTransformer) visitCJSExportAssignments(expression *ast.Node } } } - return tx.cjsExportAssignmentVisitor.VisitEachChild(expression) // recur through the whole tree, looking for module.exports= + res := tx.cjsExportAssignmentVisitor.VisitEachChild(expression) // recur through the whole tree, looking for module.exports= + tx.restoreDiagnosticContext(savedDiag) + return res } return nil } func (tx *DeclarationTransformer) visitNestedExpression(expression *ast.Node) *ast.Node { if expression != nil { - _, cleanupDiagnosticContext := tx.setupDiagnosticContext(expression) - defer cleanupDiagnosticContext() + _, savedDiag := tx.setupDiagnosticContext(expression) switch ast.GetAssignmentDeclarationKind(expression) { case ast.JSDeclarationKindProperty: tx.transformExpandoAssignment(expression.AsBinaryExpression()) @@ -2746,7 +2792,9 @@ func (tx *DeclarationTransformer) visitNestedExpression(expression *ast.Node) *a } } } - return tx.expressionVisitor.VisitEachChild(expression) // recur through the whole tree, looking for special assignments + res := tx.expressionVisitor.VisitEachChild(expression) // recur through the whole tree, looking for special assignments + tx.restoreDiagnosticContext(savedDiag) + return res } return nil } @@ -2823,8 +2871,7 @@ func (tx *DeclarationTransformer) transformExpandoAssignment(node *ast.BinaryExp localName = tx.Factory().NewGeneratedNameForNode(node.AsNode()) } - _, cleanupDiagnosticContext := tx.setupDiagnosticContext(node.AsNode()) - defer cleanupDiagnosticContext() + _, savedDiag := tx.setupDiagnosticContext(node.AsNode()) preexistingExpandoHasExport := core.Some(tx.expandoMembers[hostId], ast.IsExportDeclaration) @@ -2835,6 +2882,7 @@ func (tx *DeclarationTransformer) transformExpandoAssignment(node *ast.BinaryExp // alias-like, emit an `export {name}` or `export {name as alias}` result := tx.transformBinaryExpressionToExportDeclaration(node.AsNode(), exportName) tx.expandoMembers[hostId] = append(tx.expandoMembers[hostId], result) + tx.restoreDiagnosticContext(savedDiag) return } @@ -2854,9 +2902,6 @@ func (tx *DeclarationTransformer) transformExpandoAssignment(node *ast.BinaryExp oldEnclosing := tx.enclosingDeclaration tx.enclosingDeclaration = synthesizedNamespace - defer func() { - tx.enclosingDeclaration = oldEnclosing - }() statements := []*ast.Statement{ tx.Factory().NewVariableStatement( @@ -2883,7 +2928,9 @@ func (tx *DeclarationTransformer) transformExpandoAssignment(node *ast.BinaryExp } } + tx.enclosingDeclaration = oldEnclosing tx.expandoMembers[hostId] = append(tx.expandoMembers[hostId], statements...) + tx.restoreDiagnosticContext(savedDiag) } func (tx *DeclarationTransformer) addExportModifierToExpandoMembers(hostId ast.NodeId) { @@ -2923,8 +2970,7 @@ func (tx *DeclarationTransformer) transformExpandoHost(name *ast.Node, declarati modifierFlags ^= ast.ModifierFlagsExport } - _, cleanupDiagnosticContext := tx.setupDiagnosticContext(declaration) - defer cleanupDiagnosticContext() + _, savedDiag := tx.setupDiagnosticContext(declaration) modifiers := tx.Factory().NewModifierList(ast.CreateModifiersFromModifierFlags(modifierFlags, tx.Factory().NewModifier)) replacement := make([]*ast.Node, 0) @@ -2938,6 +2984,7 @@ func (tx *DeclarationTransformer) transformExpandoHost(name *ast.Node, declarati replacement = append(replacement, tx.Factory().NewFunctionDeclaration(modifiers, asteriskToken, tx.Factory().NewIdentifier(name.Text()), tx.ensureTypeParams(fn, typeParameters), tx.updateParamList(fn, parameters), tx.ensureType(fn, false), nil /*fullSignature*/, nil /*body*/)) } else { tx.expandoHosts[id] = tx.transformTopLevelDeclaration(declaration) + tx.restoreDiagnosticContext(savedDiag) return } @@ -2956,6 +3003,7 @@ func (tx *DeclarationTransformer) transformExpandoHost(name *ast.Node, declarati if _, ok := tx.lateStatementReplacementMap[id]; ok { tx.lateStatementReplacementMap[id] = tx.createFullExpandoBlock(id) } + tx.restoreDiagnosticContext(savedDiag) } func (tx *DeclarationTransformer) createFullExpandoBlock(id ast.NodeId) *ast.Node { From c614eecdf4fa62aae91f4eee811a0b330f1e0b12 Mon Sep 17 00:00:00 2001 From: Steven Chong <25894545+teamchong@users.noreply.github.com> Date: Mon, 26 Oct 2026 14:23:24 -0400 Subject: [PATCH 04/10] perf(ast,parser,binder): compact AST/Symbol layouts, lazy .d.ts member lists, and fast NameResolver - Replace multi-case Kind switches in generated and handwritten Node accessors (Data, DeclarationData, ExportableData, LocalsContainerData, FunctionLikeData, ClassLikeData, BodyData, LiteralLikeData, TemplateLiteralLikeData, Name, Modifiers, SetModifiers, Text, Expression, TypeArgumentList, TypeParameterList, MemberList, StatementList, Type, SetType, Initializer, SetInitializer) with [kindFlowReduceLabelData + 1]uint8 byte-offset tables, keeping hot accessors under Go's 80-node inline budget. - Make CompositeBase a zero-sized struct{} with a 64-shard subtreeFactsShards table (shrinking 46 AST node types by 8B), mark 28 hot AST node kinds arena: true, and merge FlowNode.Antecedent and FlowNode.Antecedents into a single pointer field (shrinking FlowNode from 32B to 24B). - Pack declLen (15 bits), declCap (15 bits), and LazyState (2 bits) into Symbol.declHeader, merge Symbol.data and SymbolData.Parent into Symbol.parentOrData (shrinking SymbolWithData from 80B to 64B), add Symbol.FirstDeclaration() and checkFlagsMerged, and inline (*Symbol).Members() and (*Symbol).Exports(). - Add LazyNodeList for on-demand .d.ts interface, class, and type-literal member reparsing/binding. - Add a tight inner ancestor loop for non-active scopes in NameResolver.Resolve, skip getIsDeferredContext on non-value lookups, and pre-size SymbolTable maps in getMembersSlow, getExportsSlow, and GetLocals. --- packages/typescript/enums/nodeFlags.d.ts | 1 + packages/typescript/enums/nodeFlags.js | 1 + tools/scripts/tsc/ast.json | 90 +- tools/scripts/tsc/generate-go-ast.ts | 77 +- tsc/internal/ast/ast.go | 649 ++++----- tsc/internal/ast/ast_generated.go | 1692 ++++++---------------- tsc/internal/ast/flow.go | 17 +- tsc/internal/ast/lazylist.go | 321 ++++ tsc/internal/ast/nodeflags.go | 3 + tsc/internal/ast/subtreefacts.go | 2 +- tsc/internal/ast/symbol.go | 252 +++- tsc/internal/ast/symbol_test.go | 4 +- tsc/internal/ast/utilities.go | 124 +- tsc/internal/ast/visitor.go | 2 +- tsc/internal/binder/binder.go | 108 +- tsc/internal/binder/nameresolver.go | 153 +- tsc/internal/checker/flow.go | 14 +- tsc/internal/parser/parser.go | 368 ++++- tsc/internal/printer/printer.go | 6 +- 19 files changed, 1995 insertions(+), 1889 deletions(-) create mode 100644 tsc/internal/ast/lazylist.go diff --git a/packages/typescript/enums/nodeFlags.d.ts b/packages/typescript/enums/nodeFlags.d.ts index b9c3335f7603f..f03f6c24ccc76 100644 --- a/packages/typescript/enums/nodeFlags.d.ts +++ b/packages/typescript/enums/nodeFlags.d.ts @@ -31,6 +31,7 @@ export declare enum NodeFlags { PossiblyContainsDeprecatedTag = 1 << 26, Unreachable = 1 << 27, ReparserTransformedLiteral = 1 << 28, + CondFlowClean = 1 << 29, BlockScoped = Let | Const | Using, Constant = Const | Using, AwaitUsing = Const | Using, diff --git a/packages/typescript/enums/nodeFlags.js b/packages/typescript/enums/nodeFlags.js index 14fc1251943d3..012347c6a601d 100644 --- a/packages/typescript/enums/nodeFlags.js +++ b/packages/typescript/enums/nodeFlags.js @@ -31,6 +31,7 @@ export var NodeFlags; NodeFlags[NodeFlags["PossiblyContainsDeprecatedTag"] = 67108864] = "PossiblyContainsDeprecatedTag"; NodeFlags[NodeFlags["Unreachable"] = 134217728] = "Unreachable"; NodeFlags[NodeFlags["ReparserTransformedLiteral"] = 268435456] = "ReparserTransformedLiteral"; + NodeFlags[NodeFlags["CondFlowClean"] = 536870912] = "CondFlowClean"; NodeFlags[NodeFlags["BlockScoped"] = 7] = "BlockScoped"; NodeFlags[NodeFlags["Constant"] = 6] = "Constant"; NodeFlags[NodeFlags["AwaitUsing"] = 6] = "AwaitUsing"; diff --git a/tools/scripts/tsc/ast.json b/tools/scripts/tsc/ast.json index a133915a10665..ba328f9bff3c3 100644 --- a/tools/scripts/tsc/ast.json +++ b/tools/scripts/tsc/ast.json @@ -945,12 +945,6 @@ "extends": [] }, "CompositeBase": { - "fields": { - "facts": { - "type": "atomic.Uint32", - "goOnly": true - } - }, "extends": [] }, "TypeSyntaxBase": { @@ -1743,7 +1737,8 @@ "type": "BindingElement", "list": "NodeList" } - ] + ], + "arena": true }, "ParameterDeclaration": { "kind": "Parameter", @@ -1816,7 +1811,8 @@ "type": "Expression", "optional": true } - ] + ], + "arena": true }, "MissingDeclaration": { "extends": [ @@ -2141,7 +2137,8 @@ "type": "ImportAttributes", "optional": true } - ] + ], + "arena": true }, "ExternalModuleReference": { "generateSubtreeFacts": true, @@ -2182,7 +2179,8 @@ "type": "ImportSpecifier", "list": "NodeList" } - ] + ], + "arena": true }, "ExportAssignment": { "extends": [ @@ -2577,7 +2575,8 @@ "optional": true, "visit": "functionBody" } - ] + ], + "arena": true }, "PropertySignatureDeclaration": { "kind": "PropertySignature", @@ -2643,7 +2642,8 @@ "type": "Expression", "optional": true } - ] + ], + "arena": true }, "SemicolonClassElement": { "extends": [ @@ -2930,7 +2930,8 @@ "type": "ConciseBody", "visit": "functionBody" } - ] + ], + "arena": true }, "FunctionExpression": { "extends": [ @@ -3001,7 +3002,8 @@ "name": "Type", "type": "TypeNode" } - ] + ], + "arena": true }, "SatisfiesExpression": { "extends": [ @@ -3229,7 +3231,8 @@ "type": "TemplateSpan", "list": "NodeList" } - ] + ], + "arena": true }, "TemplateSpan": { "generateSubtreeFacts": true, @@ -3245,7 +3248,8 @@ "name": "Literal", "type": "TemplateMiddleOrTail" } - ] + ], + "arena": true }, "TaggedTemplateExpression": { "extends": [ @@ -3307,7 +3311,8 @@ "name": "MultiLine", "type": "bool" } - ] + ], + "arena": true }, "ObjectLiteralExpression": { "generateSubtreeFacts": true, @@ -3326,7 +3331,8 @@ "name": "MultiLine", "type": "bool" } - ] + ], + "arena": true }, "SpreadAssignment": { "extends": [ @@ -3408,7 +3414,8 @@ "type": "Expression", "optional": true } - ] + ], + "arena": true }, "DeleteExpression": { "generateSubtreeFacts": true, @@ -3432,7 +3439,8 @@ "name": "Expression", "type": "Expression" } - ] + ], + "arena": true }, "VoidExpression": { "generateSubtreeFacts": true, @@ -3455,7 +3463,8 @@ "name": "Expression", "type": "Expression" } - ] + ], + "arena": true }, "TypeAssertion": { "kind": "TypeAssertionExpression", @@ -3542,7 +3551,8 @@ "name": "FalseType", "type": "TypeNode" } - ] + ], + "arena": true }, "TypeOperatorNode": { "kind": "TypeOperator", @@ -3575,7 +3585,8 @@ "name": "TypeParameter", "type": "TypeParameterDeclaration" } - ] + ], + "arena": true }, "ArrayTypeNode": { "kind": "ArrayType", @@ -3660,7 +3671,8 @@ "kind": "ThisType", "extends": [ "TypeNodeBase" - ] + ], + "arena": true }, "TypePredicateNode": { "kind": "TypePredicate", @@ -3741,7 +3753,8 @@ "name": "TypeArguments", "inherited": true } - ] + ], + "arena": true }, "MappedTypeNode": { "kind": "MappedType", @@ -3819,7 +3832,8 @@ "type": "TypeNode", "list": "NodeList" } - ] + ], + "arena": true }, "NamedTupleMember": { "extends": [ @@ -3870,7 +3884,8 @@ "name": "Type", "type": "TypeNode" } - ] + ], + "arena": true }, "ParenthesizedTypeNode": { "kind": "ParenthesizedType", @@ -3949,7 +3964,8 @@ "inherited": true, "bitmask": "TokenFlagsTemplateLiteralLikeFlags" } - ] + ], + "arena": true }, "TemplateMiddle": { "extends": [ @@ -3970,7 +3986,8 @@ "inherited": true, "bitmask": "TokenFlagsTemplateLiteralLikeFlags" } - ] + ], + "arena": true }, "TemplateTail": { "extends": [ @@ -3991,7 +4008,8 @@ "inherited": true, "bitmask": "TokenFlagsTemplateLiteralLikeFlags" } - ] + ], + "arena": true }, "TemplateLiteralTypeNode": { "kind": "TemplateLiteralType", @@ -4008,7 +4026,8 @@ "type": "TemplateLiteralTypeSpan", "list": "NodeList" } - ] + ], + "arena": true }, "TemplateLiteralTypeSpan": { "extends": [ @@ -4023,7 +4042,8 @@ "name": "Literal", "type": "TemplateMiddleOrTail" } - ] + ], + "arena": true }, "SyntheticExpression": { "extends": [ @@ -4916,7 +4936,8 @@ "name": "TypeArguments", "inherited": true } - ] + ], + "arena": true }, "ImportClause": { "extends": [ @@ -4942,7 +4963,8 @@ "type": "NamedImportBindings", "optional": true } - ] + ], + "arena": true }, "ImportSpecifier": { "extends": [ diff --git a/tools/scripts/tsc/generate-go-ast.ts b/tools/scripts/tsc/generate-go-ast.ts index 6724adf71bbcb..73a1a5cdc895f 100644 --- a/tools/scripts/tsc/generate-go-ast.ts +++ b/tools/scripts/tsc/generate-go-ast.ts @@ -867,7 +867,7 @@ function transitiveBaseKeys(node: NodeType): Set { return seen; } -function generateNodeAccessorDispatch(w: CodeWriter, method: string, ret: string, hasAccessor: (node: NodeType) => boolean) { +function generateNodeAccessorDispatch(w: CodeWriter, method: string, ret: string, field: string, deref: boolean, hasAccessor: (node: NodeType) => boolean) { const cases: { node: NodeType; kinds: string[]; }[] = []; for (const node of api.nodes()) { if (!hasAccessor(node)) continue; @@ -875,46 +875,33 @@ function generateNodeAccessorDispatch(w: CodeWriter, method: string, ret: string if (kinds.length !== 0) cases.push({ node, kinds }); } - // Large sparse Kind switches become comparison trees. Dense indices let Go - // emit jump tables; zero is reserved for kinds without the accessor. - const useDispatchTable = cases.length >= 8; const table = `node${method}Dispatch`; - if (useDispatchTable) { - if (cases.length > 255) { - throw new Error(`${method} dispatch exceeds the uint8 index range`); - } - w.write(`var ${table} = [kindFlowReduceLabelData + 1]uint8{`); - w.push(); - for (const [index, { kinds }] of cases.entries()) { - for (const kind of kinds) { - w.write(`${kind}: ${index + 1},`); - } + w.write(`var ${table} = [kindFlowReduceLabelData + 1]uint8{`); + w.push(); + for (const { node, kinds } of cases) { + for (const kind of kinds) { + w.write(`${kind}: uint8(unsafe.Offsetof(${node.name}{}.${field})),`); } - w.pop(); - w.write("}"); - w.write(""); } + w.pop(); + w.write("}"); + w.write(""); w.write(`func (n *Node) ${method}() ${ret} {`); w.push(); - if (useDispatchTable) { - w.write(`switch ${table}[n.Kind] {`); + w.write(`if offset := ${table}[n.Kind]; offset != 0 {`); + w.push(); + w.write(`// SAFETY: ${table} stores the byte offset of ${field} within the concrete node struct for n.Kind.`); + if (deref) { + w.write(`return *(*${ret})(unsafe.Add(unsafe.Pointer(n), offset))`); } else { - w.write("switch n.Kind {"); + w.write(`return (${ret})(unsafe.Add(unsafe.Pointer(n), offset))`); } - for (const [index, { node, kinds }] of cases.entries()) { - w.write(`case ${useDispatchTable ? index + 1 : kinds.join(", ")}:`); - w.push(); - writeNodeCastSafety(w, node); - w.write(`return (*${node.name})(unsafe.Pointer(n)).${method}()`); - w.pop(); - } - w.write("default:"); - w.push(); - w.write("return nil"); + w.pop(); w.write("}"); + w.write("return nil"); w.pop(); w.write("}"); w.write(""); @@ -922,7 +909,7 @@ function generateNodeAccessorDispatch(w: CodeWriter, method: string, ret: string function generateNodeAccessors(w: CodeWriter) { for (const { method, ret, base } of NODE_ACCESSORS) { - generateNodeAccessorDispatch(w, method, ret, node => transitiveBaseKeys(node).has(base)); + generateNodeAccessorDispatch(w, method, ret, base, false, node => transitiveBaseKeys(node).has(base)); } } @@ -930,28 +917,14 @@ function hasMember(node: NodeType, name: string): boolean { return schemaMembers(node).some(member => member.name === name); } -function generateNameDispatch(w: CodeWriter) { - generateNodeAccessorDispatch(w, "Name", "*DeclarationName", node => hasMember(node, "name")); -} - -function generateModifiersDispatch(w: CodeWriter) { - generateNodeAccessorDispatch(w, "Modifiers", "*ModifierList", node => hasMember(node, "modifiers")); -} - function generateSetModifiersDispatch(w: CodeWriter) { w.write("func (n *MutableNode) SetModifiers(modifiers *ModifierList) {"); w.push(); - w.write("switch n.Kind {"); - for (const node of api.nodes()) { - if (!hasMember(node, "modifiers")) continue; - const kinds = node.allKinds().map(kind => kind.formatGoConstant()); - if (kinds.length === 0) continue; - w.write(`case ${kinds.join(", ")}:`); - w.push(); - writeNodeCastSafety(w, node); - w.write(`(*${node.name})(unsafe.Pointer(n)).setModifiers(modifiers)`); - w.pop(); - } + w.write("if offset := nodeModifiersDispatch[n.Kind]; offset != 0 {"); + w.push(); + w.write("// SAFETY: nodeModifiersDispatch stores the byte offset of modifiers within the concrete node struct for n.Kind."); + w.write("*(**ModifierList)(unsafe.Add(unsafe.Pointer(n), offset)) = modifiers"); + w.pop(); w.write("}"); w.pop(); w.write("}"); @@ -1341,8 +1314,8 @@ function generate(): string { w.write("// Common node accessor dispatch"); w.write("// ──────────────────────────────────────────────────────────────────────"); w.write(""); - generateNameDispatch(w); - generateModifiersDispatch(w); + generateNodeAccessorDispatch(w, "Name", "*DeclarationName", "name", true, node => hasMember(node, "name")); + generateNodeAccessorDispatch(w, "Modifiers", "*ModifierList", "modifiers", true, node => hasMember(node, "modifiers")); generateSetModifiersDispatch(w); // As*() casts diff --git a/tsc/internal/ast/ast.go b/tsc/internal/ast/ast.go index 80089ab333bf3..3558c98c9cbf7 100644 --- a/tsc/internal/ast/ast.go +++ b/tsc/internal/ast/ast.go @@ -47,6 +47,10 @@ func visitNodes(v Visitor, nodes []*Node) bool { func visitNodeList(v Visitor, nodeList *NodeList) bool { if nodeList != nil { + // SAFETY: SliceData only inspects the backing-array pointer of nodeList.Nodes. + if unsafe.SliceData(nodeList.Nodes) == &lazySentinelElem { + return visitNodes(v, nodeList.forceLazy()) + } return visitNodes(v, nodeList.Nodes) } return false @@ -95,6 +99,11 @@ func (f *NodeFactory) TextCount() int { return f.textCount } +func (f *NodeFactory) ResetCounts() { + f.nodeCount = 0 + f.textCount = 0 +} + func (f *NodeFactory) AsNodeFactory() *NodeFactory { return f } @@ -136,15 +145,16 @@ func (list *NodeList) Pos() int { return list.Loc.Pos() } func (list *NodeList) End() int { return list.Loc.End() } func (list *NodeList) HasTrailingComma() bool { - if len(list.Nodes) == 0 { + nodes := list.Slice() + if len(nodes) == 0 { return false } - last := list.Nodes[len(list.Nodes)-1] + last := nodes[len(nodes)-1] return last.End() < list.End() } func (list *NodeList) Clone(f NodeFactoryCoercible) *NodeList { - result := f.AsNodeFactory().NewNodeList(list.Nodes) + result := f.AsNodeFactory().NewNodeList(list.Slice()) result.Loc = list.Loc return result } @@ -180,7 +190,7 @@ type Node struct { Kind Kind `customlint:"protected"` Flags NodeFlags Loc core.TextRange - id atomic.Uint64 + id atomic.Uint32 Parent *Node } @@ -245,32 +255,36 @@ func (n *Node) Body() *Node { return nil } +const nodeFlagsOffset = uint8(unsafe.Offsetof(Node{}.Flags)) + +var nodeTextOffset = [kindFlowReduceLabelData + 1]uint8{ + KindIdentifier: uint8(unsafe.Offsetof(Identifier{}.Text)) - nodeFlagsOffset, + KindPrivateIdentifier: uint8(unsafe.Offsetof(PrivateIdentifier{}.Text)) - nodeFlagsOffset, + KindStringLiteral: uint8(unsafe.Offsetof(StringLiteral{}.Text)) - nodeFlagsOffset, + KindNumericLiteral: uint8(unsafe.Offsetof(NumericLiteral{}.Text)) - nodeFlagsOffset, + KindBigIntLiteral: uint8(unsafe.Offsetof(BigIntLiteral{}.Text)) - nodeFlagsOffset, + KindNoSubstitutionTemplateLiteral: uint8(unsafe.Offsetof(NoSubstitutionTemplateLiteral{}.Text)) - nodeFlagsOffset, + KindTemplateHead: uint8(unsafe.Offsetof(TemplateHead{}.Text)) - nodeFlagsOffset, + KindTemplateMiddle: uint8(unsafe.Offsetof(TemplateMiddle{}.Text)) - nodeFlagsOffset, + KindTemplateTail: uint8(unsafe.Offsetof(TemplateTail{}.Text)) - nodeFlagsOffset, + KindJsxText: uint8(unsafe.Offsetof(JsxText{}.Text)) - nodeFlagsOffset, + KindRegularExpressionLiteral: uint8(unsafe.Offsetof(RegularExpressionLiteral{}.Text)) - nodeFlagsOffset, +} + func (n *Node) Text() string { + if offset := nodeTextOffset[n.Kind]; offset != 0 { + // SAFETY: nodeTextOffset stores the byte offset of Text from n.Flags for n.Kind. + return *(*string)(unsafe.Add(unsafe.Pointer(&n.Flags), offset)) + } + return n.textSlow() +} + +func (n *Node) textSlow() string { switch n.Kind { - case KindIdentifier: - return n.AsIdentifier().Text - case KindPrivateIdentifier: - return n.AsPrivateIdentifier().Text - case KindStringLiteral: - return n.AsStringLiteral().Text - case KindNumericLiteral: - return n.AsNumericLiteral().Text - case KindBigIntLiteral: - return n.AsBigIntLiteral().Text case KindMetaProperty: return n.AsMetaProperty().Name().Text() - case KindNoSubstitutionTemplateLiteral: - return n.AsNoSubstitutionTemplateLiteral().Text - case KindTemplateHead: - return n.AsTemplateHead().Text - case KindTemplateMiddle: - return n.AsTemplateMiddle().Text - case KindTemplateTail: - return n.AsTemplateTail().Text case KindJsxNamespacedName: return n.AsJsxNamespacedName().Namespace.Text() + ":" + n.AsJsxNamespacedName().name.Text() - case KindRegularExpressionLiteral: - return n.AsRegularExpressionLiteral().Text case KindJSDocText: return strings.Join(n.AsJSDocText().text, "") case KindJSDocLink: @@ -283,80 +297,51 @@ func (n *Node) Text() string { panic(fmt.Sprintf("Unhandled case in Node.Text: %s", n.Kind)) } +var nodeExpressionOffset = [kindFlowReduceLabelData + 1]uint8{ + KindPropertyAccessExpression: uint8(unsafe.Offsetof(PropertyAccessExpression{}.Expression)), + KindElementAccessExpression: uint8(unsafe.Offsetof(ElementAccessExpression{}.Expression)), + KindParenthesizedExpression: uint8(unsafe.Offsetof(ParenthesizedExpression{}.Expression)), + KindCallExpression: uint8(unsafe.Offsetof(CallExpression{}.Expression)), + KindNewExpression: uint8(unsafe.Offsetof(NewExpression{}.Expression)), + KindExpressionWithTypeArguments: uint8(unsafe.Offsetof(ExpressionWithTypeArguments{}.Expression)), + KindComputedPropertyName: uint8(unsafe.Offsetof(ComputedPropertyName{}.Expression)), + KindNonNullExpression: uint8(unsafe.Offsetof(NonNullExpression{}.Expression)), + KindTypeAssertionExpression: uint8(unsafe.Offsetof(TypeAssertion{}.Expression)), + KindAsExpression: uint8(unsafe.Offsetof(AsExpression{}.Expression)), + KindSatisfiesExpression: uint8(unsafe.Offsetof(SatisfiesExpression{}.Expression)), + KindTypeOfExpression: uint8(unsafe.Offsetof(TypeOfExpression{}.Expression)), + KindSpreadAssignment: uint8(unsafe.Offsetof(SpreadAssignment{}.Expression)), + KindSpreadElement: uint8(unsafe.Offsetof(SpreadElement{}.Expression)), + KindTemplateSpan: uint8(unsafe.Offsetof(TemplateSpan{}.Expression)), + KindDeleteExpression: uint8(unsafe.Offsetof(DeleteExpression{}.Expression)), + KindVoidExpression: uint8(unsafe.Offsetof(VoidExpression{}.Expression)), + KindAwaitExpression: uint8(unsafe.Offsetof(AwaitExpression{}.Expression)), + KindYieldExpression: uint8(unsafe.Offsetof(YieldExpression{}.Expression)), + KindPartiallyEmittedExpression: uint8(unsafe.Offsetof(PartiallyEmittedExpression{}.Expression)), + KindIfStatement: uint8(unsafe.Offsetof(IfStatement{}.Expression)), + KindDoStatement: uint8(unsafe.Offsetof(DoStatement{}.Expression)), + KindWhileStatement: uint8(unsafe.Offsetof(WhileStatement{}.Expression)), + KindWithStatement: uint8(unsafe.Offsetof(WithStatement{}.Expression)), + KindForInStatement: uint8(unsafe.Offsetof(ForInOrOfStatement{}.Expression)), + KindForOfStatement: uint8(unsafe.Offsetof(ForInOrOfStatement{}.Expression)), + KindSwitchStatement: uint8(unsafe.Offsetof(SwitchStatement{}.Expression)), + KindCaseClause: uint8(unsafe.Offsetof(CaseOrDefaultClause{}.Expression)), + KindExpressionStatement: uint8(unsafe.Offsetof(ExpressionStatement{}.Expression)), + KindReturnStatement: uint8(unsafe.Offsetof(ReturnStatement{}.Expression)), + KindThrowStatement: uint8(unsafe.Offsetof(ThrowStatement{}.Expression)), + KindExternalModuleReference: uint8(unsafe.Offsetof(ExternalModuleReference{}.Expression)), + KindExportAssignment: uint8(unsafe.Offsetof(ExportAssignment{}.Expression)), + KindDecorator: uint8(unsafe.Offsetof(Decorator{}.Expression)), + KindJsxExpression: uint8(unsafe.Offsetof(JsxExpression{}.Expression)), + KindJsxSpreadAttribute: uint8(unsafe.Offsetof(JsxSpreadAttribute{}.Expression)), +} + func (n *Node) Expression() *Node { - switch n.Kind { - case KindPropertyAccessExpression: - return n.AsPropertyAccessExpression().Expression - case KindElementAccessExpression: - return n.AsElementAccessExpression().Expression - case KindParenthesizedExpression: - return n.AsParenthesizedExpression().Expression - case KindCallExpression: - return n.AsCallExpression().Expression - case KindNewExpression: - return n.AsNewExpression().Expression - case KindExpressionWithTypeArguments: - return n.AsExpressionWithTypeArguments().Expression - case KindComputedPropertyName: - return n.AsComputedPropertyName().Expression - case KindNonNullExpression: - return n.AsNonNullExpression().Expression - case KindTypeAssertionExpression: - return n.AsTypeAssertion().Expression - case KindAsExpression: - return n.AsAsExpression().Expression - case KindSatisfiesExpression: - return n.AsSatisfiesExpression().Expression - case KindTypeOfExpression: - return n.AsTypeOfExpression().Expression - case KindSpreadAssignment: - return n.AsSpreadAssignment().Expression - case KindSpreadElement: - return n.AsSpreadElement().Expression - case KindTemplateSpan: - return n.AsTemplateSpan().Expression - case KindDeleteExpression: - return n.AsDeleteExpression().Expression - case KindVoidExpression: - return n.AsVoidExpression().Expression - case KindAwaitExpression: - return n.AsAwaitExpression().Expression - case KindYieldExpression: - return n.AsYieldExpression().Expression - case KindPartiallyEmittedExpression: - return n.AsPartiallyEmittedExpression().Expression - case KindIfStatement: - return n.AsIfStatement().Expression - case KindDoStatement: - return n.AsDoStatement().Expression - case KindWhileStatement: - return n.AsWhileStatement().Expression - case KindWithStatement: - return n.AsWithStatement().Expression - case KindForInStatement, KindForOfStatement: - return n.AsForInOrOfStatement().Expression - case KindSwitchStatement: - return n.AsSwitchStatement().Expression - case KindCaseClause: - return n.AsCaseOrDefaultClause().Expression - case KindExpressionStatement: - return n.AsExpressionStatement().Expression - case KindReturnStatement: - return n.AsReturnStatement().Expression - case KindThrowStatement: - return n.AsThrowStatement().Expression - case KindExternalModuleReference: - return n.AsExternalModuleReference().Expression - case KindExportAssignment: - return n.AsExportAssignment().Expression - case KindDecorator: - return n.AsDecorator().Expression - case KindJsxExpression: - return n.AsJsxExpression().Expression - case KindJsxSpreadAttribute: - return n.AsJsxSpreadAttribute().Expression - } - panic("Unhandled case in Node.Expression: " + n.Kind.String()) + if offset := nodeExpressionOffset[n.Kind]; offset != 0 { + // SAFETY: nodeExpressionOffset stores the byte offset of Expression within the concrete node struct for n.Kind. + return *(**Node)(unsafe.Add(unsafe.Pointer(&n.Flags), offset-nodeFlagsOffset)) + } + panic("Unhandled case in Node.Expression") } func (n *Node) RawText() string { @@ -373,80 +358,12 @@ func (n *Node) RawText() string { func (m *MutableNode) SetExpression(expr *Node) { n := (*Node)(m) - switch n.Kind { - case KindPropertyAccessExpression: - n.AsPropertyAccessExpression().Expression = expr - case KindElementAccessExpression: - n.AsElementAccessExpression().Expression = expr - case KindParenthesizedExpression: - n.AsParenthesizedExpression().Expression = expr - case KindCallExpression: - n.AsCallExpression().Expression = expr - case KindNewExpression: - n.AsNewExpression().Expression = expr - case KindExpressionWithTypeArguments: - n.AsExpressionWithTypeArguments().Expression = expr - case KindComputedPropertyName: - n.AsComputedPropertyName().Expression = expr - case KindNonNullExpression: - n.AsNonNullExpression().Expression = expr - case KindTypeAssertionExpression: - n.AsTypeAssertion().Expression = expr - case KindAsExpression: - n.AsAsExpression().Expression = expr - case KindSatisfiesExpression: - n.AsSatisfiesExpression().Expression = expr - case KindTypeOfExpression: - n.AsTypeOfExpression().Expression = expr - case KindSpreadAssignment: - n.AsSpreadAssignment().Expression = expr - case KindSpreadElement: - n.AsSpreadElement().Expression = expr - case KindTemplateSpan: - n.AsTemplateSpan().Expression = expr - case KindDeleteExpression: - n.AsDeleteExpression().Expression = expr - case KindVoidExpression: - n.AsVoidExpression().Expression = expr - case KindAwaitExpression: - n.AsAwaitExpression().Expression = expr - case KindYieldExpression: - n.AsYieldExpression().Expression = expr - case KindPartiallyEmittedExpression: - n.AsPartiallyEmittedExpression().Expression = expr - case KindIfStatement: - n.AsIfStatement().Expression = expr - case KindDoStatement: - n.AsDoStatement().Expression = expr - case KindWhileStatement: - n.AsWhileStatement().Expression = expr - case KindWithStatement: - n.AsWithStatement().Expression = expr - case KindForInStatement, KindForOfStatement: - n.AsForInOrOfStatement().Expression = expr - case KindSwitchStatement: - n.AsSwitchStatement().Expression = expr - case KindCaseClause: - n.AsCaseOrDefaultClause().Expression = expr - case KindExpressionStatement: - n.AsExpressionStatement().Expression = expr - case KindReturnStatement: - n.AsReturnStatement().Expression = expr - case KindThrowStatement: - n.AsThrowStatement().Expression = expr - case KindExternalModuleReference: - n.AsExternalModuleReference().Expression = expr - case KindExportAssignment: - n.AsExportAssignment().Expression = expr - case KindDecorator: - n.AsDecorator().Expression = expr - case KindJsxExpression: - n.AsJsxExpression().Expression = expr - case KindJsxSpreadAttribute: - n.AsJsxSpreadAttribute().Expression = expr - default: - panic("Unhandled case in mutableNode.SetExpression: " + n.Kind.String()) + if offset := nodeExpressionOffset[n.Kind]; offset != 0 { + // SAFETY: nodeExpressionOffset stores the byte offset of Expression within the concrete node struct for n.Kind. + *(**Node)(unsafe.Add(unsafe.Pointer(&n.Flags), offset-nodeFlagsOffset)) = expr + return } + panic("Unhandled case in mutableNode.SetExpression") } func (n *Node) ArgumentList() *NodeList { @@ -467,26 +384,22 @@ func (n *Node) Arguments() []*Node { return nil } +var nodeTypeArgumentsOffset = [kindFlowReduceLabelData + 1]uint8{ + KindCallExpression: uint8(unsafe.Offsetof(CallExpression{}.TypeArguments)), + KindNewExpression: uint8(unsafe.Offsetof(NewExpression{}.TypeArguments)), + KindTaggedTemplateExpression: uint8(unsafe.Offsetof(TaggedTemplateExpression{}.TypeArguments)), + KindTypeReference: uint8(unsafe.Offsetof(TypeReferenceNode{}.TypeArguments)), + KindExpressionWithTypeArguments: uint8(unsafe.Offsetof(ExpressionWithTypeArguments{}.TypeArguments)), + KindImportType: uint8(unsafe.Offsetof(ImportTypeNode{}.TypeArguments)), + KindTypeQuery: uint8(unsafe.Offsetof(TypeQueryNode{}.TypeArguments)), + KindJsxOpeningElement: uint8(unsafe.Offsetof(JsxOpeningElement{}.TypeArguments)), + KindJsxSelfClosingElement: uint8(unsafe.Offsetof(JsxSelfClosingElement{}.TypeArguments)), +} + func (n *Node) TypeArgumentList() *NodeList { - switch n.Kind { - case KindCallExpression: - return n.AsCallExpression().TypeArguments - case KindNewExpression: - return n.AsNewExpression().TypeArguments - case KindTaggedTemplateExpression: - return n.AsTaggedTemplateExpression().TypeArguments - case KindTypeReference: - return n.AsTypeReferenceNode().TypeArguments - case KindExpressionWithTypeArguments: - return n.AsExpressionWithTypeArguments().TypeArguments - case KindImportType: - return n.AsImportTypeNode().TypeArguments - case KindTypeQuery: - return n.AsTypeQueryNode().TypeArguments - case KindJsxOpeningElement: - return n.AsJsxOpeningElement().TypeArguments - case KindJsxSelfClosingElement: - return n.AsJsxSelfClosingElement().TypeArguments + if offset := nodeTypeArgumentsOffset[n.Kind]; offset != 0 { + // SAFETY: nodeTypeArgumentsOffset stores the byte offset of TypeArguments within the concrete node struct for n.Kind. + return *(**NodeList)(unsafe.Add(unsafe.Pointer(&n.Flags), offset-nodeFlagsOffset)) } panic("Unhandled case in Node.TypeArguments") } @@ -499,23 +412,22 @@ func (n *Node) TypeArguments() []*Node { return nil } +var nodeTypeParametersOffset = [kindFlowReduceLabelData + 1]uint8{ + KindClassDeclaration: uint8(unsafe.Offsetof(ClassDeclaration{}.TypeParameters)), + KindClassExpression: uint8(unsafe.Offsetof(ClassExpression{}.TypeParameters)), + KindInterfaceDeclaration: uint8(unsafe.Offsetof(InterfaceDeclaration{}.TypeParameters)), + KindTypeAliasDeclaration: uint8(unsafe.Offsetof(TypeAliasDeclaration{}.TypeParameters)), + KindJSTypeAliasDeclaration: uint8(unsafe.Offsetof(TypeAliasDeclaration{}.TypeParameters)), + KindJSDocTemplateTag: uint8(unsafe.Offsetof(JSDocTemplateTag{}.TypeParameters)), +} + func (n *Node) TypeParameterList() *NodeList { - switch n.Kind { - case KindClassDeclaration: - return n.AsClassDeclaration().TypeParameters - case KindClassExpression: - return n.AsClassExpression().TypeParameters - case KindInterfaceDeclaration: - return n.AsInterfaceDeclaration().TypeParameters - case KindTypeAliasDeclaration, KindJSTypeAliasDeclaration: - return n.AsTypeAliasDeclaration().TypeParameters - case KindJSDocTemplateTag: - return n.AsJSDocTemplateTag().TypeParameters - default: - funcLike := n.FunctionLikeData() - if funcLike != nil { - return funcLike.TypeParameters - } + if offset := nodeTypeParametersOffset[n.Kind]; offset != 0 { + // SAFETY: nodeTypeParametersOffset stores the byte offset of TypeParameters within the concrete node struct for n.Kind. + return *(**NodeList)(unsafe.Add(unsafe.Pointer(&n.Flags), offset-nodeFlagsOffset)) + } + if funcLike := n.FunctionLikeData(); funcLike != nil { + return funcLike.TypeParameters } panic("Unhandled case in Node.TypeParameterList") } @@ -528,44 +440,49 @@ func (n *Node) TypeParameters() []*Node { return nil } +var nodeMembersOffset = [kindFlowReduceLabelData + 1]uint8{ + KindClassDeclaration: uint8(unsafe.Offsetof(ClassDeclaration{}.Members)), + KindClassExpression: uint8(unsafe.Offsetof(ClassExpression{}.Members)), + KindInterfaceDeclaration: uint8(unsafe.Offsetof(InterfaceDeclaration{}.Members)), + KindEnumDeclaration: uint8(unsafe.Offsetof(EnumDeclaration{}.Members)), + KindTypeLiteral: uint8(unsafe.Offsetof(TypeLiteralNode{}.Members)), + KindMappedType: uint8(unsafe.Offsetof(MappedTypeNode{}.Members)), +} + func (n *Node) MemberList() *NodeList { - switch n.Kind { - case KindClassDeclaration: - return n.AsClassDeclaration().Members - case KindClassExpression: - return n.AsClassExpression().Members - case KindInterfaceDeclaration: - return n.AsInterfaceDeclaration().Members - case KindEnumDeclaration: - return n.AsEnumDeclaration().Members - case KindTypeLiteral: - return n.AsTypeLiteralNode().Members - case KindMappedType: - return n.AsMappedTypeNode().Members + if offset := nodeMembersOffset[n.Kind]; offset != 0 { + // SAFETY: nodeMembersOffset stores the byte offset of Members within the concrete node struct for n.Kind. + return *(**NodeList)(unsafe.Add(unsafe.Pointer(&n.Flags), offset-nodeFlagsOffset)) } - panic("Unhandled case in Node.MemberList: " + n.Kind.String()) + panic("Unhandled case in Node.MemberList") } func (n *Node) Members() []*Node { list := n.MemberList() if list != nil { + // SAFETY: SliceData only compares the backing-array pointer against lazySentinelElem. + if unsafe.SliceData(list.Nodes) == &lazySentinelElem { + return list.forceLazy() + } return list.Nodes } return nil } +var nodeStatementsOffset = [kindFlowReduceLabelData + 1]uint8{ + KindSourceFile: uint8(unsafe.Offsetof(SourceFile{}.Statements)), + KindBlock: uint8(unsafe.Offsetof(Block{}.Statements)), + KindModuleBlock: uint8(unsafe.Offsetof(ModuleBlock{}.Statements)), + KindCaseClause: uint8(unsafe.Offsetof(CaseOrDefaultClause{}.Statements)), + KindDefaultClause: uint8(unsafe.Offsetof(CaseOrDefaultClause{}.Statements)), +} + func (n *Node) StatementList() *NodeList { - switch n.Kind { - case KindSourceFile: - return n.AsSourceFile().Statements - case KindBlock: - return n.AsBlock().Statements - case KindModuleBlock: - return n.AsModuleBlock().Statements - case KindCaseClause, KindDefaultClause: - return n.AsCaseOrDefaultClause().Statements + if offset := nodeStatementsOffset[n.Kind]; offset != 0 { + // SAFETY: nodeStatementsOffset stores the byte offset of Statements within the concrete node struct for n.Kind. + return *(**NodeList)(unsafe.Add(unsafe.Pointer(&n.Flags), offset-nodeFlagsOffset)) } - panic("Unhandled case in Node.StatementList: " + n.Kind.String()) + panic("Unhandled case in Node.StatementList") } func (n *Node) Statements() []*Node { @@ -601,180 +518,91 @@ func (n *Node) ModifierNodes() []*Node { return nil } +var nodeTypeOffset = [kindFlowReduceLabelData + 1]uint8{ + KindVariableDeclaration: uint8(unsafe.Offsetof(VariableDeclaration{}.Type)), + KindParameter: uint8(unsafe.Offsetof(ParameterDeclaration{}.Type)), + KindPropertySignature: uint8(unsafe.Offsetof(PropertySignatureDeclaration{}.Type)), + KindPropertyDeclaration: uint8(unsafe.Offsetof(PropertyDeclaration{}.Type)), + KindPropertyAssignment: uint8(unsafe.Offsetof(PropertyAssignment{}.Type)), + KindShorthandPropertyAssignment: uint8(unsafe.Offsetof(ShorthandPropertyAssignment{}.Type)), + KindTypePredicate: uint8(unsafe.Offsetof(TypePredicateNode{}.Type)), + KindParenthesizedType: uint8(unsafe.Offsetof(ParenthesizedTypeNode{}.Type)), + KindTypeOperator: uint8(unsafe.Offsetof(TypeOperatorNode{}.Type)), + KindMappedType: uint8(unsafe.Offsetof(MappedTypeNode{}.Type)), + KindTypeAssertionExpression: uint8(unsafe.Offsetof(TypeAssertion{}.Type)), + KindAsExpression: uint8(unsafe.Offsetof(AsExpression{}.Type)), + KindSatisfiesExpression: uint8(unsafe.Offsetof(SatisfiesExpression{}.Type)), + KindTypeAliasDeclaration: uint8(unsafe.Offsetof(TypeAliasDeclaration{}.Type)), + KindJSTypeAliasDeclaration: uint8(unsafe.Offsetof(TypeAliasDeclaration{}.Type)), + KindNamedTupleMember: uint8(unsafe.Offsetof(NamedTupleMember{}.Type)), + KindOptionalType: uint8(unsafe.Offsetof(OptionalTypeNode{}.Type)), + KindRestType: uint8(unsafe.Offsetof(RestTypeNode{}.Type)), + KindTemplateLiteralTypeSpan: uint8(unsafe.Offsetof(TemplateLiteralTypeSpan{}.Type)), + KindJSDocTypeExpression: uint8(unsafe.Offsetof(JSDocTypeExpression{}.Type)), + KindJSDocParameterTag: uint8(unsafe.Offsetof(JSDocParameterOrPropertyTag{}.TypeExpression)), + KindJSDocPropertyTag: uint8(unsafe.Offsetof(JSDocParameterOrPropertyTag{}.TypeExpression)), + KindJSDocNullableType: uint8(unsafe.Offsetof(JSDocNullableType{}.Type)), + KindJSDocNonNullableType: uint8(unsafe.Offsetof(JSDocNonNullableType{}.Type)), + KindJSDocOptionalType: uint8(unsafe.Offsetof(JSDocOptionalType{}.Type)), + KindExportAssignment: uint8(unsafe.Offsetof(ExportAssignment{}.Type)), + KindBinaryExpression: uint8(unsafe.Offsetof(BinaryExpression{}.Type)), +} + func (n *Node) Type() *Node { - switch n.Kind { - case KindVariableDeclaration: - return n.AsVariableDeclaration().Type - case KindParameter: - return n.AsParameterDeclaration().Type - case KindPropertySignature: - return n.AsPropertySignatureDeclaration().Type - case KindPropertyDeclaration: - return n.AsPropertyDeclaration().Type - case KindPropertyAssignment: - return n.AsPropertyAssignment().Type - case KindShorthandPropertyAssignment: - return n.AsShorthandPropertyAssignment().Type - case KindTypePredicate: - return n.AsTypePredicateNode().Type - case KindParenthesizedType: - return n.AsParenthesizedTypeNode().Type - case KindTypeOperator: - return n.AsTypeOperatorNode().Type - case KindMappedType: - return n.AsMappedTypeNode().Type - case KindTypeAssertionExpression: - return n.AsTypeAssertion().Type - case KindAsExpression: - return n.AsAsExpression().Type - case KindSatisfiesExpression: - return n.AsSatisfiesExpression().Type - case KindTypeAliasDeclaration, KindJSTypeAliasDeclaration: - return n.AsTypeAliasDeclaration().Type - case KindNamedTupleMember: - return n.AsNamedTupleMember().Type - case KindOptionalType: - return n.AsOptionalTypeNode().Type - case KindRestType: - return n.AsRestTypeNode().Type - case KindTemplateLiteralTypeSpan: - return n.AsTemplateLiteralTypeSpan().Type - case KindJSDocTypeExpression: - return n.AsJSDocTypeExpression().Type - case KindJSDocParameterTag, KindJSDocPropertyTag: - return n.AsJSDocParameterOrPropertyTag().TypeExpression - case KindJSDocNullableType: - return n.AsJSDocNullableType().Type - case KindJSDocNonNullableType: - return n.AsJSDocNonNullableType().Type - case KindJSDocOptionalType: - return n.AsJSDocOptionalType().Type - case KindExportAssignment: - return n.AsExportAssignment().Type - case KindBinaryExpression: - return n.AsBinaryExpression().Type - default: - if funcLike := n.FunctionLikeData(); funcLike != nil { - return funcLike.Type - } + if offset := nodeTypeOffset[n.Kind]; offset != 0 { + // SAFETY: nodeTypeOffset stores the byte offset of Type/TypeExpression within the concrete node struct for n.Kind. + return *(**Node)(unsafe.Add(unsafe.Pointer(&n.Flags), offset-nodeFlagsOffset)) + } + if funcLike := n.FunctionLikeData(); funcLike != nil { + return funcLike.Type } return nil } func (m *MutableNode) SetType(t *Node) { n := (*Node)(m) - switch m.Kind { - case KindVariableDeclaration: - n.AsVariableDeclaration().Type = t - case KindParameter: - n.AsParameterDeclaration().Type = t - case KindPropertySignature: - n.AsPropertySignatureDeclaration().Type = t - case KindPropertyDeclaration: - n.AsPropertyDeclaration().Type = t - case KindPropertyAssignment: - n.AsPropertyAssignment().Type = t - case KindShorthandPropertyAssignment: - n.AsShorthandPropertyAssignment().Type = t - case KindTypePredicate: - n.AsTypePredicateNode().Type = t - case KindParenthesizedType: - n.AsParenthesizedTypeNode().Type = t - case KindTypeOperator: - n.AsTypeOperatorNode().Type = t - case KindMappedType: - n.AsMappedTypeNode().Type = t - case KindTypeAssertionExpression: - n.AsTypeAssertion().Type = t - case KindAsExpression: - n.AsAsExpression().Type = t - case KindSatisfiesExpression: - n.AsSatisfiesExpression().Type = t - case KindTypeAliasDeclaration, KindJSTypeAliasDeclaration: - n.AsTypeAliasDeclaration().Type = t - case KindNamedTupleMember: - n.AsNamedTupleMember().Type = t - case KindOptionalType: - n.AsOptionalTypeNode().Type = t - case KindRestType: - n.AsRestTypeNode().Type = t - case KindTemplateLiteralTypeSpan: - n.AsTemplateLiteralTypeSpan().Type = t - case KindJSDocTypeExpression: - n.AsJSDocTypeExpression().Type = t - case KindJSDocParameterTag, KindJSDocPropertyTag: - n.AsJSDocParameterOrPropertyTag().TypeExpression = t - case KindJSDocNullableType: - n.AsJSDocNullableType().Type = t - case KindJSDocNonNullableType: - n.AsJSDocNonNullableType().Type = t - case KindJSDocOptionalType: - n.AsJSDocOptionalType().Type = t - case KindExportAssignment: - n.AsExportAssignment().Type = t - case KindBinaryExpression: - n.AsBinaryExpression().Type = t - default: - if funcLike := n.FunctionLikeData(); funcLike != nil { - funcLike.Type = t - } else { - panic("Unhandled case in mutableNode.SetType: " + n.Kind.String()) - } + if offset := nodeTypeOffset[n.Kind]; offset != 0 { + // SAFETY: nodeTypeOffset stores the byte offset of Type/TypeExpression within the concrete node struct for n.Kind. + *(**Node)(unsafe.Add(unsafe.Pointer(&n.Flags), offset-nodeFlagsOffset)) = t + return + } + if funcLike := n.FunctionLikeData(); funcLike != nil { + funcLike.Type = t + return } + panic("Unhandled case in mutableNode.SetType") +} + +var nodeInitializerOffset = [kindFlowReduceLabelData + 1]uint8{ + KindVariableDeclaration: uint8(unsafe.Offsetof(VariableDeclaration{}.Initializer)), + KindParameter: uint8(unsafe.Offsetof(ParameterDeclaration{}.Initializer)), + KindBindingElement: uint8(unsafe.Offsetof(BindingElement{}.Initializer)), + KindPropertyDeclaration: uint8(unsafe.Offsetof(PropertyDeclaration{}.Initializer)), + KindPropertySignature: uint8(unsafe.Offsetof(PropertySignatureDeclaration{}.Initializer)), + KindPropertyAssignment: uint8(unsafe.Offsetof(PropertyAssignment{}.Initializer)), + KindEnumMember: uint8(unsafe.Offsetof(EnumMember{}.Initializer)), + KindForStatement: uint8(unsafe.Offsetof(ForStatement{}.Initializer)), + KindForInStatement: uint8(unsafe.Offsetof(ForInOrOfStatement{}.Initializer)), + KindForOfStatement: uint8(unsafe.Offsetof(ForInOrOfStatement{}.Initializer)), + KindJsxAttribute: uint8(unsafe.Offsetof(JsxAttribute{}.Initializer)), } func (n *Node) Initializer() *Node { - switch n.Kind { - case KindVariableDeclaration: - return n.AsVariableDeclaration().Initializer - case KindParameter: - return n.AsParameterDeclaration().Initializer - case KindBindingElement: - return n.AsBindingElement().Initializer - case KindPropertyDeclaration: - return n.AsPropertyDeclaration().Initializer - case KindPropertySignature: - return n.AsPropertySignatureDeclaration().Initializer - case KindPropertyAssignment: - return n.AsPropertyAssignment().Initializer - case KindEnumMember: - return n.AsEnumMember().Initializer - case KindForStatement: - return n.AsForStatement().Initializer - case KindForInStatement, KindForOfStatement: - return n.AsForInOrOfStatement().Initializer - case KindJsxAttribute: - return n.AsJsxAttribute().Initializer + if offset := nodeInitializerOffset[n.Kind]; offset != 0 { + // SAFETY: nodeInitializerOffset stores the byte offset of Initializer within the concrete node struct for n.Kind. + return *(**Node)(unsafe.Add(unsafe.Pointer(&n.Flags), offset-nodeFlagsOffset)) } panic("Unhandled case in Node.Initializer") } func (m *MutableNode) SetInitializer(initializer *Node) { n := (*Node)(m) - switch n.Kind { - case KindVariableDeclaration: - n.AsVariableDeclaration().Initializer = initializer - case KindParameter: - n.AsParameterDeclaration().Initializer = initializer - case KindBindingElement: - n.AsBindingElement().Initializer = initializer - case KindPropertyDeclaration: - n.AsPropertyDeclaration().Initializer = initializer - case KindPropertySignature: - n.AsPropertySignatureDeclaration().Initializer = initializer - case KindPropertyAssignment: - n.AsPropertyAssignment().Initializer = initializer - case KindEnumMember: - n.AsEnumMember().Initializer = initializer - case KindForStatement: - n.AsForStatement().Initializer = initializer - case KindForInStatement, KindForOfStatement: - n.AsForInOrOfStatement().Initializer = initializer - case KindJsxAttribute: - n.AsJsxAttribute().Initializer = initializer - default: - panic("Unhandled case in mutableNode.SetInitializer") + if offset := nodeInitializerOffset[n.Kind]; offset != 0 { + // SAFETY: nodeInitializerOffset stores the byte offset of Initializer within the concrete node struct for n.Kind. + *(**Node)(unsafe.Add(unsafe.Pointer(&n.Flags), offset-nodeFlagsOffset)) = initializer + return } + panic("Unhandled case in mutableNode.SetInitializer") } func (n *Node) TagName() *Node { @@ -1529,12 +1357,26 @@ func (node *Node) EagerJSDoc(file *SourceFile) []*Node { // CompositeBase +var subtreeFactsShards [64]struct { + mu sync.RWMutex + m map[*Node]SubtreeFacts +} + func (node *CompositeBase) subtreeFactsWorker(self *Node) SubtreeFacts { // computeSubtreeFacts() is expected to be idempotent, so races will only impact time, not correctness. - facts := SubtreeFacts(node.facts.Load()) + // SAFETY: Converts the node address to uintptr solely to select a lock shard. + shard := &subtreeFactsShards[(uintptr(unsafe.Pointer(&self.Flags))>>4)&63] + shard.mu.RLock() + facts := shard.m[self] + shard.mu.RUnlock() if facts&SubtreeFactsComputed == 0 { - facts |= self.computeSubtreeFacts() | SubtreeFactsComputed - node.facts.Store(uint32(facts)) + facts = self.computeSubtreeFacts() | SubtreeFactsComputed + shard.mu.Lock() + if shard.m == nil { + shard.m = make(map[*Node]SubtreeFacts) + } + shard.m[self] = facts + shard.mu.Unlock() } return facts &^ SubtreeFactsComputed } @@ -2322,9 +2164,9 @@ type sourceFileDataKey uint64 var sourceFileDataKeyCounter atomic.Uint64 -type sourceFileDataCell[T any] struct { +type sourceFileDataCell struct { once sync.Once - value T + value unsafe.Pointer } func NewSourceFileDataKey[T any]() *SourceFileDataKey[T] { @@ -2332,15 +2174,18 @@ func NewSourceFileDataKey[T any]() *SourceFileDataKey[T] { } func (file *SourceFile) GetOrComputeData[T any](key *SourceFileDataKey[T], compute func(*SourceFile) T) T { - cell := file.getDataCell(key) + cell := file.getDataCell(key.key) cell.once.Do(func() { - cell.value = compute(file) + v := compute(file) + // SAFETY: cell.value is only written and read with the type T associated with this unique SourceFileDataKey[T]. + cell.value = unsafe.Pointer(&v) }) - return cell.value + // SAFETY: cell.once guarantees cell.value holds a *T stored above for this SourceFileDataKey[T]. + return *(*T)(cell.value) } -func (file *SourceFile) getDataCell[T any](key *SourceFileDataKey[T]) *sourceFileDataCell[T] { - if key == nil || key.key == 0 { +func (file *SourceFile) getDataCell(key sourceFileDataKey) *sourceFileDataCell { + if key == 0 { panic("invalid SourceFileDataKey; use NewSourceFileDataKey") } @@ -2348,13 +2193,13 @@ func (file *SourceFile) getDataCell[T any](key *SourceFileDataKey[T]) *sourceFil defer file.dataMu.Unlock() if file.data == nil { - file.data = make(map[sourceFileDataKey]any) + file.data = make(map[sourceFileDataKey]*sourceFileDataCell) } - if cell, ok := file.data[key.key]; ok { - return cell.(*sourceFileDataCell[T]) + if cell, ok := file.data[key]; ok { + return cell } - cell := &sourceFileDataCell[T]{} - file.data[key.key] = cell + cell := &sourceFileDataCell{} + file.data[key] = cell return cell } @@ -2389,7 +2234,7 @@ type SourceFile struct { // Fields for lazily-computed data owned by packages outside ast. dataMu sync.Mutex - data map[sourceFileDataKey]any + data map[sourceFileDataKey]*sourceFileDataCell // Fields set by parser diagnostics []*Diagnostic @@ -2430,7 +2275,7 @@ type SourceFile struct { SymbolCount int PatternAmbientModules []*PatternAmbientModule GlobalExports SymbolTable - + LazyLists []*LazyNodeList // Fields set by ECMALineMap ecmaLineMapMu sync.RWMutex diff --git a/tsc/internal/ast/ast_generated.go b/tsc/internal/ast/ast_generated.go index 84e61671b22df..621af08d5cf86 100644 --- a/tsc/internal/ast/ast_generated.go +++ b/tsc/internal/ast/ast_generated.go @@ -20,11 +20,18 @@ var ( type NodeFactory struct { hooks NodeFactoryHooks + arrayLiteralExpressionArena core.Arena[ArrayLiteralExpression] arrayTypeNodeArena core.Arena[ArrayTypeNode] + arrowFunctionArena core.Arena[ArrowFunction] + asExpressionArena core.Arena[AsExpression] + awaitExpressionArena core.Arena[AwaitExpression] binaryExpressionArena core.Arena[BinaryExpression] + bindingElementArena core.Arena[BindingElement] + bindingPatternArena core.Arena[BindingPattern] blockArena core.Arena[Block] callExpressionArena core.Arena[CallExpression] conditionalExpressionArena core.Arena[ConditionalExpression] + conditionalTypeNodeArena core.Arena[ConditionalTypeNode] constructSignatureDeclarationArena core.Arena[ConstructSignatureDeclaration] elementAccessExpressionArena core.Arena[ElementAccessExpression] expressionStatementArena core.Arena[ExpressionStatement] @@ -34,8 +41,12 @@ type NodeFactory struct { heritageClauseArena core.Arena[HeritageClause] identifierArena core.Arena[Identifier] ifStatementArena core.Arena[IfStatement] + importClauseArena core.Arena[ImportClause] + importDeclarationArena core.Arena[ImportDeclaration] importSpecifierArena core.Arena[ImportSpecifier] + importTypeNodeArena core.Arena[ImportTypeNode] indexedAccessTypeNodeArena core.Arena[IndexedAccessTypeNode] + inferTypeNodeArena core.Arena[InferTypeNode] interfaceDeclarationArena core.Arena[InterfaceDeclaration] intersectionTypeNodeArena core.Arena[IntersectionTypeNode] jsdocArena core.Arena[JSDoc] @@ -45,24 +56,41 @@ type NodeFactory struct { keywordExpressionArena core.Arena[KeywordExpression] keywordTypeNodeArena core.Arena[KeywordTypeNode] literalTypeNodeArena core.Arena[LiteralTypeNode] + methodDeclarationArena core.Arena[MethodDeclaration] methodSignatureDeclarationArena core.Arena[MethodSignatureDeclaration] modifierListArena core.Arena[ModifierList] + namedImportsArena core.Arena[NamedImports] nodeListArena core.Arena[NodeList] numericLiteralArena core.Arena[NumericLiteral] + objectLiteralExpressionArena core.Arena[ObjectLiteralExpression] parameterDeclarationArena core.Arena[ParameterDeclaration] parenthesizedExpressionArena core.Arena[ParenthesizedExpression] parenthesizedTypeNodeArena core.Arena[ParenthesizedTypeNode] prefixUnaryExpressionArena core.Arena[PrefixUnaryExpression] propertyAccessExpressionArena core.Arena[PropertyAccessExpression] propertyAssignmentArena core.Arena[PropertyAssignment] + propertyDeclarationArena core.Arena[PropertyDeclaration] propertySignatureDeclarationArena core.Arena[PropertySignatureDeclaration] + restTypeNodeArena core.Arena[RestTypeNode] returnStatementArena core.Arena[ReturnStatement] + shorthandPropertyAssignmentArena core.Arena[ShorthandPropertyAssignment] stringLiteralArena core.Arena[StringLiteral] + templateExpressionArena core.Arena[TemplateExpression] + templateHeadArena core.Arena[TemplateHead] + templateLiteralTypeNodeArena core.Arena[TemplateLiteralTypeNode] + templateLiteralTypeSpanArena core.Arena[TemplateLiteralTypeSpan] + templateMiddleArena core.Arena[TemplateMiddle] + templateSpanArena core.Arena[TemplateSpan] + templateTailArena core.Arena[TemplateTail] + thisTypeNodeArena core.Arena[ThisTypeNode] tokenArena core.Arena[Token] + tupleTypeNodeArena core.Arena[TupleTypeNode] typeAliasDeclarationArena core.Arena[TypeAliasDeclaration] typeLiteralNodeArena core.Arena[TypeLiteralNode] + typeOfExpressionArena core.Arena[TypeOfExpression] typeOperatorNodeArena core.Arena[TypeOperatorNode] typeParameterDeclarationArena core.Arena[TypeParameterDeclaration] + typeQueryNodeArena core.Arena[TypeQueryNode] typeReferenceNodeArena core.Arena[TypeReferenceNode] unionTypeNodeArena core.Arena[UnionTypeNode] variableDeclarationArena core.Arena[VariableDeclaration] @@ -146,9 +174,7 @@ type FlowNodeBase struct { FlowNode *FlowNode } -type CompositeBase struct { - facts atomic.Uint32 -} +type CompositeBase struct{} type TypeSyntaxBase struct{} @@ -172,10 +198,10 @@ type FunctionLikeWithBodyBase struct { } type ClassLikeBase struct { + CompositeBase ExportableBase ModifiersBase LocalsContainerBase - CompositeBase name *IdentifierNode // Optional TypeParameters *TypeParameterList // Optional HeritageClauses *HeritageClauseList // Optional @@ -214,12 +240,12 @@ type AccessorDeclarationBase struct { TypeElementBase ClassElementBase ObjectLiteralElementBase + CompositeBase NodeBase DeclarationBase NamedMemberBase FunctionLikeWithBodyBase FlowNodeBase - CompositeBase } type FunctionOrConstructorTypeNodeBase struct { @@ -622,13 +648,13 @@ func (f *NodeFactory) NewToken(kind TokenSyntaxKind) *Node { data := &NoSubstitutionTemplateLiteral{} return f.newNode(kind, data.AsNode()) case KindTemplateHead: - data := &TemplateHead{} + data := f.templateHeadArena.New() return f.newNode(kind, data.AsNode()) case KindTemplateMiddle: - data := &TemplateMiddle{} + data := f.templateMiddleArena.New() return f.newNode(kind, data.AsNode()) case KindTemplateTail: - data := &TemplateTail{} + data := f.templateTailArena.New() return f.newNode(kind, data.AsNode()) case KindIdentifier: data := f.identifierArena.New() @@ -714,9 +740,9 @@ func IsPrivateIdentifier(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type QualifiedName struct { + CompositeBase NodeBase FlowNodeBase - CompositeBase Left *EntityName Right *MemberName } @@ -761,8 +787,8 @@ func IsQualifiedName(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type ComputedPropertyName struct { - NodeBase CompositeBase + NodeBase Expression *Expression } @@ -804,8 +830,8 @@ func IsComputedPropertyName(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type Decorator struct { - NodeBase CompositeBase + NodeBase Expression *LeftHandSideExpression } @@ -864,8 +890,8 @@ func IsEmptyStatement(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type IfStatement struct { - StatementBase CompositeBase + StatementBase Expression *Expression ThenStatement *Statement ElseStatement *Statement // Optional @@ -913,8 +939,8 @@ func IsIfStatement(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type DoStatement struct { - IterationStatementBase CompositeBase + IterationStatementBase Expression *Expression } @@ -958,8 +984,8 @@ func IsDoStatement(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type WhileStatement struct { - IterationStatementBase CompositeBase + IterationStatementBase Expression *Expression } @@ -1003,9 +1029,9 @@ func IsWhileStatement(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type ForStatement struct { + CompositeBase IterationStatementBase LocalsContainerBase - CompositeBase Initializer *ForInitializer // Optional Condition *Expression // Optional Incrementor *Expression // Optional @@ -1058,9 +1084,9 @@ func IsForStatement(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type ForInOrOfStatement struct { + CompositeBase StatementBase LocalsContainerBase - CompositeBase AwaitModifier *AwaitKeyword // Optional Initializer *ForInitializer Expression *Expression @@ -1192,8 +1218,8 @@ func IsContinueStatement(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type ReturnStatement struct { - StatementBase CompositeBase + StatementBase Expression *Expression // Optional } @@ -1231,8 +1257,8 @@ func IsReturnStatement(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type WithStatement struct { - StatementBase CompositeBase + StatementBase Expression *Expression Statement *Statement } @@ -1277,8 +1303,8 @@ func IsWithStatement(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type SwitchStatement struct { - StatementBase CompositeBase + StatementBase Expression *Expression CaseBlock *CaseBlockNode } @@ -1323,9 +1349,9 @@ func IsSwitchStatement(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type CaseBlock struct { + CompositeBase NodeBase LocalsContainerBase - CompositeBase Clauses *CaseClausesList } @@ -1367,8 +1393,8 @@ func IsCaseBlock(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type CaseOrDefaultClause struct { - NodeBase CompositeBase + NodeBase Expression *Expression Statements *StatementList FallthroughFlowNode *FlowNode @@ -1423,8 +1449,8 @@ func IsDefaultClause(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type ThrowStatement struct { - StatementBase CompositeBase + StatementBase Expression *Expression } @@ -1466,8 +1492,8 @@ func IsThrowStatement(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type TryStatement struct { - StatementBase CompositeBase + StatementBase TryBlock *BlockNode CatchClause *CatchClauseNode // Optional FinallyBlock *BlockNode // Optional @@ -1515,9 +1541,9 @@ func IsTryStatement(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type CatchClause struct { + CompositeBase NodeBase LocalsContainerBase - CompositeBase VariableDeclaration *VariableDeclarationNode // Optional Block *BlockNode } @@ -1665,9 +1691,9 @@ func IsExpressionStatement(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type Block struct { + CompositeBase StatementBase LocalsContainerBase - CompositeBase Statements *StatementList MultiLine bool } @@ -1711,9 +1737,9 @@ func IsBlock(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type VariableStatement struct { + CompositeBase StatementBase ModifiersBase - CompositeBase DeclarationList *VariableDeclarationListNode } @@ -1752,10 +1778,10 @@ func IsVariableStatement(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type VariableDeclaration struct { + CompositeBase NodeBase DeclarationBase ExportableBase - CompositeBase name *BindingName ExclamationToken *ExclamationToken // Optional Type *TypeNode // Optional @@ -1806,8 +1832,8 @@ func IsVariableDeclaration(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type VariableDeclarationList struct { - NodeBase CompositeBase + NodeBase Declarations *VariableDeclarationNodeList } @@ -1847,8 +1873,8 @@ func IsVariableDeclarationList(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type BindingPattern struct { - NodeBase CompositeBase + NodeBase Elements *BindingElementList } @@ -1858,7 +1884,7 @@ func (f *NodeFactory) NewBindingPattern(kind Kind, elements *BindingElementList) default: panic("Invalid node kind for BindingPattern") } - data := &BindingPattern{} + data := f.bindingPatternArena.New() data.Elements = elements return f.newNode(kind, data.AsNode()) } @@ -1895,10 +1921,10 @@ func IsArrayBindingPattern(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type ParameterDeclaration struct { + CompositeBase NodeBase DeclarationBase ModifiersBase - CompositeBase DotDotDotToken *DotDotDotToken // Optional name *BindingName QuestionToken *QuestionToken // Optional @@ -1954,11 +1980,11 @@ func IsParameterDeclaration(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type BindingElement struct { + CompositeBase NodeBase DeclarationBase ExportableBase FlowNodeBase - CompositeBase DotDotDotToken *DotDotDotToken // Optional PropertyName *PropertyName // Optional name *BindingName // Optional @@ -1966,7 +1992,7 @@ type BindingElement struct { } func (f *NodeFactory) NewBindingElement(dotDotDotToken *DotDotDotToken, propertyName *PropertyName, name *BindingName, initializer *Expression) *Node { - data := &BindingElement{} + data := f.bindingElementArena.New() data.DotDotDotToken = dotDotDotToken data.PropertyName = propertyName data.name = name @@ -2048,12 +2074,12 @@ func IsMissingDeclaration(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type FunctionDeclaration struct { + CompositeBase StatementBase DeclarationBase ExportableBase ModifiersBase FunctionLikeWithBodyBase - CompositeBase name *IdentifierNode // Optional ReturnFlowNode *FlowNode } @@ -2212,8 +2238,8 @@ func IsClassExpression(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type HeritageClause struct { - NodeBase CompositeBase + NodeBase Token Kind Types *HeritageClauseElementList } @@ -2392,10 +2418,10 @@ func IsJSTypeAliasDeclaration(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type EnumMember struct { + CompositeBase NodeBase DeclarationBase NamedMemberBase - CompositeBase Initializer *Expression // Optional } @@ -2438,11 +2464,11 @@ func IsEnumMember(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type EnumDeclaration struct { + CompositeBase StatementBase DeclarationBase ExportableBase ModifiersBase - CompositeBase name *IdentifierNode Members *EnumMemberList } @@ -2487,8 +2513,8 @@ func IsEnumDeclaration(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type ModuleBlock struct { - StatementBase CompositeBase + StatementBase Statements *StatementList } @@ -2574,9 +2600,9 @@ func IsNotEmittedTypeElement(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type ImportDeclaration struct { + CompositeBase StatementBase ModifiersBase - CompositeBase DeclarationBase ImportClause *ImportClauseNode // Optional ModuleSpecifier *Expression @@ -2584,7 +2610,7 @@ type ImportDeclaration struct { } func (f *NodeFactory) NewImportDeclaration(modifiers *ModifierList, importClause *ImportClauseNode, moduleSpecifier *Expression, attributes *ImportAttributesNode) *Node { - data := &ImportDeclaration{} + data := f.importDeclarationArena.New() data.modifiers = modifiers data.ImportClause = importClause data.ModuleSpecifier = moduleSpecifier @@ -2593,7 +2619,7 @@ func (f *NodeFactory) NewImportDeclaration(modifiers *ModifierList, importClause } func (f *NodeFactory) NewJSImportDeclaration(modifiers *ModifierList, importClause *ImportClauseNode, moduleSpecifier *Expression, attributes *ImportAttributesNode) *Node { - data := &ImportDeclaration{} + data := f.importDeclarationArena.New() data.modifiers = modifiers data.ImportClause = importClause data.ModuleSpecifier = moduleSpecifier @@ -2747,13 +2773,13 @@ func IsNamespaceImport(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type NamedImports struct { - NodeBase CompositeBase + NodeBase Elements *ImportSpecifierList } func (f *NodeFactory) NewNamedImports(elements *ImportSpecifierList) *Node { - data := &NamedImports{} + data := f.namedImportsArena.New() data.Elements = elements return f.newNode(KindNamedImports, data.AsNode()) } @@ -2790,10 +2816,10 @@ func IsNamedImports(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type ExportAssignment struct { + CompositeBase StatementBase DeclarationBase ModifiersBase - CompositeBase IsExportEquals bool Type *TypeNode Expression *Expression @@ -2929,8 +2955,8 @@ func IsNamespaceExport(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type NamedExports struct { - NodeBase CompositeBase + NodeBase Elements *ExportSpecifierList } @@ -2972,10 +2998,10 @@ func IsNamedExports(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type ExportSpecifier struct { + CompositeBase NodeBase DeclarationBase ExportableBase - CompositeBase IsTypeOnly bool PropertyName *ModuleExportName // Optional name *ModuleExportName @@ -3108,11 +3134,11 @@ func IsConstructSignatureDeclaration(node *Node) bool { type ConstructorDeclaration struct { ClassElementBase + CompositeBase NodeBase DeclarationBase ModifiersBase FunctionLikeWithBodyBase - CompositeBase ReturnFlowNode *FlowNode } @@ -3369,16 +3395,16 @@ func IsMethodSignatureDeclaration(node *Node) bool { type MethodDeclaration struct { ClassElementBase ObjectLiteralElementBase + CompositeBase NodeBase DeclarationBase NamedMemberBase FunctionLikeWithBodyBase FlowNodeBase - CompositeBase } func (f *NodeFactory) NewMethodDeclaration(modifiers *ModifierList, asteriskToken *AsteriskToken, name *PropertyName, postfixToken *TokenNode, typeParameters *TypeParameterList, parameters *ParameterList, typeNode *TypeNode, fullSignature *TypeNode, body *FunctionBody) *Node { - data := &MethodDeclaration{} + data := f.methodDeclarationArena.New() data.modifiers = modifiers data.AsteriskToken = asteriskToken data.name = name @@ -3487,16 +3513,16 @@ func IsPropertySignatureDeclaration(node *Node) bool { type PropertyDeclaration struct { ClassElementBase + CompositeBase NodeBase DeclarationBase NamedMemberBase - CompositeBase Type *TypeNode // Optional Initializer *Expression // Optional } func (f *NodeFactory) NewPropertyDeclaration(modifiers *ModifierList, name *PropertyName, postfixToken *TokenNode, typeNode *TypeNode, initializer *Expression) *Node { - data := &PropertyDeclaration{} + data := f.propertyDeclarationArena.New() data.modifiers = modifiers data.name = name data.PostfixToken = postfixToken @@ -3565,11 +3591,11 @@ func IsSemicolonClassElement(node *Node) bool { type ClassStaticBlockDeclaration struct { ClassElementBase + CompositeBase NodeBase DeclarationBase ModifiersBase LocalsContainerBase - CompositeBase Body *BlockNode ReturnFlowNode *FlowNode } @@ -3783,10 +3809,10 @@ func IsNoSubstitutionTemplateLiteral(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type BinaryExpression struct { + CompositeBase ExpressionBase DeclarationBase ModifiersBase - CompositeBase Left *Expression Type *TypeNode // Optional OperatorToken *BinaryOperatorToken @@ -3963,17 +3989,17 @@ func IsYieldExpression(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type ArrowFunction struct { + CompositeBase ExpressionBase DeclarationBase ModifiersBase FunctionLikeWithBodyBase FlowNodeBase - CompositeBase EqualsGreaterThanToken *EqualsGreaterThanToken } func (f *NodeFactory) NewArrowFunction(modifiers *ModifierList, typeParameters *TypeParameterList, parameters *ParameterList, typeNode *TypeNode, fullSignature *TypeNode, equalsGreaterThanToken *EqualsGreaterThanToken, body *ConciseBody) *Node { - data := &ArrowFunction{} + data := f.arrowFunctionArena.New() data.modifiers = modifiers data.TypeParameters = typeParameters data.Parameters = parameters @@ -4018,12 +4044,12 @@ func IsArrowFunction(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type FunctionExpression struct { + CompositeBase PrimaryExpressionBase DeclarationBase ModifiersBase FunctionLikeWithBodyBase FlowNodeBase - CompositeBase name *IdentifierNode // Optional ReturnFlowNode *FlowNode } @@ -4086,7 +4112,7 @@ type AsExpression struct { } func (f *NodeFactory) NewAsExpression(expression *Expression, typeNode *TypeNode) *Node { - data := &AsExpression{} + data := f.asExpressionArena.New() data.Expression = expression data.Type = typeNode return f.newNode(KindAsExpression, data.AsNode()) @@ -4160,8 +4186,8 @@ func IsSatisfiesExpression(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type ConditionalExpression struct { - ExpressionBase CompositeBase + ExpressionBase Condition *Expression QuestionToken *QuestionToken WhenTrue *Expression @@ -4219,9 +4245,9 @@ func IsConditionalExpression(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type PropertyAccessExpression struct { + CompositeBase MemberExpressionBase FlowNodeBase - CompositeBase Expression *Expression QuestionDotToken *QuestionDotToken // Optional name *MemberName @@ -4269,9 +4295,9 @@ func IsPropertyAccessExpression(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type ElementAccessExpression struct { + CompositeBase MemberExpressionBase FlowNodeBase - CompositeBase Expression *Expression QuestionDotToken *QuestionDotToken // Optional ArgumentExpression *Expression @@ -4321,9 +4347,9 @@ func IsElementAccessExpression(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type CallExpression struct { + CompositeBase LeftHandSideExpressionBase DeclarationBase - CompositeBase Expression *Expression QuestionDotToken *QuestionDotToken // Optional TypeArguments *TypeList // Optional @@ -4372,8 +4398,8 @@ func IsCallExpression(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type NewExpression struct { - PrimaryExpressionBase CompositeBase + PrimaryExpressionBase Expression *Expression TypeArguments *TypeList // Optional Arguments *ElementList // Optional @@ -4415,9 +4441,9 @@ func IsNewExpression(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type MetaProperty struct { + CompositeBase PrimaryExpressionBase FlowNodeBase - CompositeBase KeywordToken Kind name *IdentifierNode } @@ -4539,14 +4565,14 @@ func IsSpreadElement(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type TemplateExpression struct { - PrimaryExpressionBase CompositeBase + PrimaryExpressionBase Head *TemplateHeadNode TemplateSpans *TemplateSpanList } func (f *NodeFactory) NewTemplateExpression(head *TemplateHeadNode, templateSpans *TemplateSpanList) *Node { - data := &TemplateExpression{} + data := f.templateExpressionArena.New() data.Head = head data.TemplateSpans = templateSpans return f.newNode(KindTemplateExpression, data.AsNode()) @@ -4591,7 +4617,7 @@ type TemplateSpan struct { } func (f *NodeFactory) NewTemplateSpan(expression *Expression, literal *TemplateMiddleOrTail) *Node { - data := &TemplateSpan{} + data := f.templateSpanArena.New() data.Expression = expression data.Literal = literal return f.newNode(KindTemplateSpan, data.AsNode()) @@ -4630,8 +4656,8 @@ func IsTemplateSpan(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type TaggedTemplateExpression struct { - MemberExpressionBase CompositeBase + MemberExpressionBase Tag *Expression QuestionDotToken *QuestionDotToken TypeArguments *TypeList // Optional @@ -4722,14 +4748,14 @@ func IsParenthesizedExpression(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type ArrayLiteralExpression struct { - PrimaryExpressionBase CompositeBase + PrimaryExpressionBase Elements *ElementList MultiLine bool } func (f *NodeFactory) NewArrayLiteralExpression(elements *ElementList, multiLine bool) *Node { - data := &ArrayLiteralExpression{} + data := f.arrayLiteralExpressionArena.New() data.Elements = elements data.MultiLine = multiLine return f.newNode(KindArrayLiteralExpression, data.AsNode()) @@ -4767,15 +4793,15 @@ func IsArrayLiteralExpression(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type ObjectLiteralExpression struct { + CompositeBase PrimaryExpressionBase DeclarationBase - CompositeBase Properties *NodeList MultiLine bool } func (f *NodeFactory) NewObjectLiteralExpression(properties *NodeList, multiLine bool) *Node { - data := &ObjectLiteralExpression{} + data := f.objectLiteralExpressionArena.New() data.Properties = properties data.MultiLine = multiLine return f.newNode(KindObjectLiteralExpression, data.AsNode()) @@ -4854,10 +4880,10 @@ func IsSpreadAssignment(node *Node) bool { type PropertyAssignment struct { ObjectLiteralElementBase + CompositeBase NodeBase DeclarationBase NamedMemberBase - CompositeBase Type *TypeNode Initializer *Expression } @@ -4909,17 +4935,17 @@ func IsPropertyAssignment(node *Node) bool { type ShorthandPropertyAssignment struct { ObjectLiteralElementBase + CompositeBase NodeBase DeclarationBase NamedMemberBase - CompositeBase Type *TypeNode EqualsToken *EqualsToken // Optional ObjectAssignmentInitializer *Expression // Optional } func (f *NodeFactory) NewShorthandPropertyAssignment(modifiers *ModifierList, name *PropertyName, postfixToken *TokenNode, typeNode *TypeNode, equalsToken *EqualsToken, objectAssignmentInitializer *Expression) *Node { - data := &ShorthandPropertyAssignment{} + data := f.shorthandPropertyAssignmentArena.New() data.modifiers = modifiers data.name = name data.PostfixToken = postfixToken @@ -5013,7 +5039,7 @@ type TypeOfExpression struct { } func (f *NodeFactory) NewTypeOfExpression(expression *Expression) *Node { - data := &TypeOfExpression{} + data := f.typeOfExpressionArena.New() data.Expression = expression return f.newNode(KindTypeOfExpression, data.AsNode()) } @@ -5097,7 +5123,7 @@ type AwaitExpression struct { } func (f *NodeFactory) NewAwaitExpression(expression *Expression) *Node { - data := &AwaitExpression{} + data := f.awaitExpressionArena.New() data.Expression = expression return f.newNode(KindAwaitExpression, data.AsNode()) } @@ -5283,7 +5309,7 @@ type ConditionalTypeNode struct { } func (f *NodeFactory) NewConditionalTypeNode(checkType *TypeNode, extendsType *TypeNode, trueType *TypeNode, falseType *TypeNode) *Node { - data := &ConditionalTypeNode{} + data := f.conditionalTypeNodeArena.New() data.CheckType = checkType data.ExtendsType = extendsType data.TrueType = trueType @@ -5367,7 +5393,7 @@ type InferTypeNode struct { } func (f *NodeFactory) NewInferTypeNode(typeParameter *TypeParameterDeclarationNode) *Node { - data := &InferTypeNode{} + data := f.inferTypeNodeArena.New() data.TypeParameter = typeParameter return f.newNode(KindInferType, data.AsNode()) } @@ -5517,8 +5543,8 @@ func IsTypeReferenceNode(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type ExpressionWithTypeArguments struct { - MemberExpressionBase CompositeBase + MemberExpressionBase Expression *Expression TypeArguments *TypeList // Optional } @@ -5600,7 +5626,7 @@ type ThisTypeNode struct { } func (f *NodeFactory) NewThisTypeNode() *Node { - data := &ThisTypeNode{} + data := f.thisTypeNodeArena.New() return f.newNode(KindThisType, data.AsNode()) } @@ -5659,8 +5685,8 @@ func IsTypePredicateNode(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type ImportAttribute struct { - NodeBase CompositeBase + NodeBase name *ImportAttributeName Value *Expression } @@ -5709,8 +5735,8 @@ func IsImportAttribute(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type ImportAttributes struct { - NodeBase CompositeBase + NodeBase Token Kind Attributes *ImportAttributeList MultiLine bool @@ -5761,7 +5787,7 @@ type TypeQueryNode struct { } func (f *NodeFactory) NewTypeQueryNode(exprName *EntityName, typeArguments *TypeList) *Node { - data := &TypeQueryNode{} + data := f.typeQueryNodeArena.New() data.ExprName = exprName data.TypeArguments = typeArguments return f.newNode(KindTypeQuery, data.AsNode()) @@ -5894,7 +5920,7 @@ type TupleTypeNode struct { } func (f *NodeFactory) NewTupleTypeNode(elements *TypeList) *Node { - data := &TupleTypeNode{} + data := f.tupleTypeNodeArena.New() data.Elements = elements return f.newNode(KindTupleType, data.AsNode()) } @@ -6022,7 +6048,7 @@ type RestTypeNode struct { } func (f *NodeFactory) NewRestTypeNode(typeNode *TypeNode) *Node { - data := &RestTypeNode{} + data := f.restTypeNodeArena.New() data.Type = typeNode return f.newNode(KindRestType, data.AsNode()) } @@ -6180,7 +6206,7 @@ type TemplateHead struct { } func (f *NodeFactory) NewTemplateHead(text string, rawText string, templateFlags TokenFlags) *Node { - data := &TemplateHead{} + data := f.templateHeadArena.New() data.Text = text data.RawText = rawText data.TemplateFlags = templateFlags & TokenFlagsTemplateLiteralLikeFlags @@ -6206,7 +6232,7 @@ type TemplateMiddle struct { } func (f *NodeFactory) NewTemplateMiddle(text string, rawText string, templateFlags TokenFlags) *Node { - data := &TemplateMiddle{} + data := f.templateMiddleArena.New() data.Text = text data.RawText = rawText data.TemplateFlags = templateFlags & TokenFlagsTemplateLiteralLikeFlags @@ -6232,7 +6258,7 @@ type TemplateTail struct { } func (f *NodeFactory) NewTemplateTail(text string, rawText string, templateFlags TokenFlags) *Node { - data := &TemplateTail{} + data := f.templateTailArena.New() data.Text = text data.RawText = rawText data.TemplateFlags = templateFlags & TokenFlagsTemplateLiteralLikeFlags @@ -6259,7 +6285,7 @@ type TemplateLiteralTypeNode struct { } func (f *NodeFactory) NewTemplateLiteralTypeNode(head *TemplateHeadNode, templateSpans *TemplateLiteralTypeSpanList) *Node { - data := &TemplateLiteralTypeNode{} + data := f.templateLiteralTypeNodeArena.New() data.Head = head data.TemplateSpans = templateSpans return f.newNode(KindTemplateLiteralType, data.AsNode()) @@ -6299,7 +6325,7 @@ type TemplateLiteralTypeSpan struct { } func (f *NodeFactory) NewTemplateLiteralTypeSpan(typeNode *TypeNode, literal *TemplateMiddleOrTail) *Node { - data := &TemplateLiteralTypeSpan{} + data := f.templateLiteralTypeSpanArena.New() data.Type = typeNode data.Literal = literal return f.newNode(KindTemplateLiteralTypeSpan, data.AsNode()) @@ -6417,8 +6443,8 @@ func IsPartiallyEmittedExpression(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type JsxElement struct { - PrimaryExpressionBase CompositeBase + PrimaryExpressionBase OpeningElement *JsxOpeningElementNode Children *JsxChildList ClosingElement *JsxClosingElementNode @@ -6460,9 +6486,9 @@ func IsJsxElement(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type JsxAttributes struct { + CompositeBase PrimaryExpressionBase DeclarationBase - CompositeBase Properties *JsxAttributeList } @@ -6500,8 +6526,8 @@ func IsJsxAttributes(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type JsxNamespacedName struct { - ExpressionBase CompositeBase + ExpressionBase Namespace *IdentifierNode name *IdentifierNode } @@ -6545,8 +6571,8 @@ func IsJsxNamespacedName(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type JsxOpeningElement struct { - ExpressionBase CompositeBase + ExpressionBase TagName *JsxTagNameExpression TypeArguments *TypeList // Optional Attributes *JsxAttributesNode @@ -6588,8 +6614,8 @@ func IsJsxOpeningElement(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type JsxSelfClosingElement struct { - PrimaryExpressionBase CompositeBase + PrimaryExpressionBase TagName *JsxTagNameExpression TypeArguments *TypeList // Optional Attributes *JsxAttributesNode @@ -6631,8 +6657,8 @@ func IsJsxSelfClosingElement(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type JsxFragment struct { - PrimaryExpressionBase CompositeBase + PrimaryExpressionBase OpeningFragment *JsxOpeningFragmentNode Children *JsxChildList ClosingFragment *JsxClosingFragmentNode @@ -6716,9 +6742,9 @@ func IsJsxClosingFragment(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type JsxAttribute struct { + CompositeBase NodeBase DeclarationBase - CompositeBase name *JsxAttributeName Initializer *JsxAttributeValue // Optional } @@ -8105,13 +8131,13 @@ func IsSourceFile(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type ModuleDeclaration struct { + CompositeBase StatementBase DeclarationBase ExportableBase ModifiersBase LocalsContainerBase BodyBase - CompositeBase Keyword Kind name *ModuleName Attributes *TypeLiteralNodeNode // Optional @@ -8162,11 +8188,11 @@ func IsModuleDeclaration(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type ImportEqualsDeclaration struct { + CompositeBase StatementBase DeclarationBase ExportableBase ModifiersBase - CompositeBase IsTypeOnly bool name *IdentifierNode ModuleReference *ModuleReference @@ -8213,10 +8239,10 @@ func IsImportEqualsDeclaration(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type ExportDeclaration struct { + CompositeBase StatementBase DeclarationBase ModifiersBase - CompositeBase IsTypeOnly bool ExportClause *NamedExportBindings // Optional ModuleSpecifier *Expression // Optional @@ -8272,7 +8298,7 @@ type ImportTypeNode struct { } func (f *NodeFactory) NewImportTypeNode(isTypeOf bool, argument *TypeNode, attributes *ImportAttributesNode, qualifier *EntityName, typeArguments *TypeList) *Node { - data := &ImportTypeNode{} + data := f.importTypeNodeArena.New() data.IsTypeOf = isTypeOf data.Argument = argument data.Attributes = attributes @@ -8312,17 +8338,17 @@ func IsImportTypeNode(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type ImportClause struct { + CompositeBase NodeBase DeclarationBase ExportableBase - CompositeBase PhaseModifier ImportPhaseModifierSyntaxKind // Optional name *IdentifierNode // Optional NamedBindings *NamedImportBindings // Optional } func (f *NodeFactory) NewImportClause(phaseModifier ImportPhaseModifierSyntaxKind, name *IdentifierNode, namedBindings *NamedImportBindings) *Node { - data := &ImportClause{} + data := f.importClauseArena.New() data.PhaseModifier = phaseModifier data.name = name data.NamedBindings = namedBindings @@ -8361,10 +8387,10 @@ func IsImportClause(node *Node) bool { // ────────────────────────────────────────────────────────────────────── type ImportSpecifier struct { + CompositeBase NodeBase DeclarationBase ExportableBase - CompositeBase IsTypeOnly bool PropertyName *ModuleExportName // Optional name *IdentifierNode @@ -12015,806 +12041,293 @@ func (n *Node) propagateSubtreeFacts() SubtreeFacts { // ────────────────────────────────────────────────────────────────────── var nodeFlowNodeDataDispatch = [kindFlowReduceLabelData + 1]uint8{ - KindIdentifier: 1, - KindQualifiedName: 2, - KindEmptyStatement: 3, - KindIfStatement: 4, - KindDoStatement: 5, - KindWhileStatement: 6, - KindForStatement: 7, - KindForInStatement: 8, - KindForOfStatement: 8, - KindBreakStatement: 9, - KindContinueStatement: 10, - KindReturnStatement: 11, - KindWithStatement: 12, - KindSwitchStatement: 13, - KindThrowStatement: 14, - KindTryStatement: 15, - KindDebuggerStatement: 16, - KindLabeledStatement: 17, - KindExpressionStatement: 18, - KindBlock: 19, - KindVariableStatement: 20, - KindBindingElement: 21, - KindMissingDeclaration: 22, - KindFunctionDeclaration: 23, - KindClassDeclaration: 24, - KindInterfaceDeclaration: 25, - KindTypeAliasDeclaration: 26, - KindJSTypeAliasDeclaration: 26, - KindEnumDeclaration: 27, - KindModuleBlock: 28, - KindNotEmittedStatement: 29, - KindImportDeclaration: 30, - KindJSImportDeclaration: 30, - KindExportAssignment: 31, - KindNamespaceExportDeclaration: 32, - KindGetAccessor: 33, - KindSetAccessor: 34, - KindMethodDeclaration: 35, - KindNullKeyword: 36, - KindTrueKeyword: 36, - KindFalseKeyword: 36, - KindThisKeyword: 36, - KindSuperKeyword: 36, - KindImportKeyword: 36, - KindArrowFunction: 37, - KindFunctionExpression: 38, - KindPropertyAccessExpression: 39, - KindElementAccessExpression: 40, - KindMetaProperty: 41, - KindModuleDeclaration: 42, - KindImportEqualsDeclaration: 43, - KindExportDeclaration: 44, + KindIdentifier: uint8(unsafe.Offsetof(Identifier{}.FlowNodeBase)), + KindQualifiedName: uint8(unsafe.Offsetof(QualifiedName{}.FlowNodeBase)), + KindEmptyStatement: uint8(unsafe.Offsetof(EmptyStatement{}.FlowNodeBase)), + KindIfStatement: uint8(unsafe.Offsetof(IfStatement{}.FlowNodeBase)), + KindDoStatement: uint8(unsafe.Offsetof(DoStatement{}.FlowNodeBase)), + KindWhileStatement: uint8(unsafe.Offsetof(WhileStatement{}.FlowNodeBase)), + KindForStatement: uint8(unsafe.Offsetof(ForStatement{}.FlowNodeBase)), + KindForInStatement: uint8(unsafe.Offsetof(ForInOrOfStatement{}.FlowNodeBase)), + KindForOfStatement: uint8(unsafe.Offsetof(ForInOrOfStatement{}.FlowNodeBase)), + KindBreakStatement: uint8(unsafe.Offsetof(BreakStatement{}.FlowNodeBase)), + KindContinueStatement: uint8(unsafe.Offsetof(ContinueStatement{}.FlowNodeBase)), + KindReturnStatement: uint8(unsafe.Offsetof(ReturnStatement{}.FlowNodeBase)), + KindWithStatement: uint8(unsafe.Offsetof(WithStatement{}.FlowNodeBase)), + KindSwitchStatement: uint8(unsafe.Offsetof(SwitchStatement{}.FlowNodeBase)), + KindThrowStatement: uint8(unsafe.Offsetof(ThrowStatement{}.FlowNodeBase)), + KindTryStatement: uint8(unsafe.Offsetof(TryStatement{}.FlowNodeBase)), + KindDebuggerStatement: uint8(unsafe.Offsetof(DebuggerStatement{}.FlowNodeBase)), + KindLabeledStatement: uint8(unsafe.Offsetof(LabeledStatement{}.FlowNodeBase)), + KindExpressionStatement: uint8(unsafe.Offsetof(ExpressionStatement{}.FlowNodeBase)), + KindBlock: uint8(unsafe.Offsetof(Block{}.FlowNodeBase)), + KindVariableStatement: uint8(unsafe.Offsetof(VariableStatement{}.FlowNodeBase)), + KindBindingElement: uint8(unsafe.Offsetof(BindingElement{}.FlowNodeBase)), + KindMissingDeclaration: uint8(unsafe.Offsetof(MissingDeclaration{}.FlowNodeBase)), + KindFunctionDeclaration: uint8(unsafe.Offsetof(FunctionDeclaration{}.FlowNodeBase)), + KindClassDeclaration: uint8(unsafe.Offsetof(ClassDeclaration{}.FlowNodeBase)), + KindInterfaceDeclaration: uint8(unsafe.Offsetof(InterfaceDeclaration{}.FlowNodeBase)), + KindTypeAliasDeclaration: uint8(unsafe.Offsetof(TypeAliasDeclaration{}.FlowNodeBase)), + KindJSTypeAliasDeclaration: uint8(unsafe.Offsetof(TypeAliasDeclaration{}.FlowNodeBase)), + KindEnumDeclaration: uint8(unsafe.Offsetof(EnumDeclaration{}.FlowNodeBase)), + KindModuleBlock: uint8(unsafe.Offsetof(ModuleBlock{}.FlowNodeBase)), + KindNotEmittedStatement: uint8(unsafe.Offsetof(NotEmittedStatement{}.FlowNodeBase)), + KindImportDeclaration: uint8(unsafe.Offsetof(ImportDeclaration{}.FlowNodeBase)), + KindJSImportDeclaration: uint8(unsafe.Offsetof(ImportDeclaration{}.FlowNodeBase)), + KindExportAssignment: uint8(unsafe.Offsetof(ExportAssignment{}.FlowNodeBase)), + KindNamespaceExportDeclaration: uint8(unsafe.Offsetof(NamespaceExportDeclaration{}.FlowNodeBase)), + KindGetAccessor: uint8(unsafe.Offsetof(GetAccessorDeclaration{}.FlowNodeBase)), + KindSetAccessor: uint8(unsafe.Offsetof(SetAccessorDeclaration{}.FlowNodeBase)), + KindMethodDeclaration: uint8(unsafe.Offsetof(MethodDeclaration{}.FlowNodeBase)), + KindNullKeyword: uint8(unsafe.Offsetof(KeywordExpression{}.FlowNodeBase)), + KindTrueKeyword: uint8(unsafe.Offsetof(KeywordExpression{}.FlowNodeBase)), + KindFalseKeyword: uint8(unsafe.Offsetof(KeywordExpression{}.FlowNodeBase)), + KindThisKeyword: uint8(unsafe.Offsetof(KeywordExpression{}.FlowNodeBase)), + KindSuperKeyword: uint8(unsafe.Offsetof(KeywordExpression{}.FlowNodeBase)), + KindImportKeyword: uint8(unsafe.Offsetof(KeywordExpression{}.FlowNodeBase)), + KindArrowFunction: uint8(unsafe.Offsetof(ArrowFunction{}.FlowNodeBase)), + KindFunctionExpression: uint8(unsafe.Offsetof(FunctionExpression{}.FlowNodeBase)), + KindPropertyAccessExpression: uint8(unsafe.Offsetof(PropertyAccessExpression{}.FlowNodeBase)), + KindElementAccessExpression: uint8(unsafe.Offsetof(ElementAccessExpression{}.FlowNodeBase)), + KindMetaProperty: uint8(unsafe.Offsetof(MetaProperty{}.FlowNodeBase)), + KindModuleDeclaration: uint8(unsafe.Offsetof(ModuleDeclaration{}.FlowNodeBase)), + KindImportEqualsDeclaration: uint8(unsafe.Offsetof(ImportEqualsDeclaration{}.FlowNodeBase)), + KindExportDeclaration: uint8(unsafe.Offsetof(ExportDeclaration{}.FlowNodeBase)), } func (n *Node) FlowNodeData() *FlowNodeBase { - switch nodeFlowNodeDataDispatch[n.Kind] { - case 1: - // SAFETY: This case only matches kinds allocated as a Identifier, which embeds its Node header at offset zero. - return (*Identifier)(unsafe.Pointer(n)).FlowNodeData() - case 2: - // SAFETY: This case only matches kinds allocated as a QualifiedName, which embeds its Node header at offset zero. - return (*QualifiedName)(unsafe.Pointer(n)).FlowNodeData() - case 3: - // SAFETY: This case only matches kinds allocated as a EmptyStatement, which embeds its Node header at offset zero. - return (*EmptyStatement)(unsafe.Pointer(n)).FlowNodeData() - case 4: - // SAFETY: This case only matches kinds allocated as a IfStatement, which embeds its Node header at offset zero. - return (*IfStatement)(unsafe.Pointer(n)).FlowNodeData() - case 5: - // SAFETY: This case only matches kinds allocated as a DoStatement, which embeds its Node header at offset zero. - return (*DoStatement)(unsafe.Pointer(n)).FlowNodeData() - case 6: - // SAFETY: This case only matches kinds allocated as a WhileStatement, which embeds its Node header at offset zero. - return (*WhileStatement)(unsafe.Pointer(n)).FlowNodeData() - case 7: - // SAFETY: This case only matches kinds allocated as a ForStatement, which embeds its Node header at offset zero. - return (*ForStatement)(unsafe.Pointer(n)).FlowNodeData() - case 8: - // SAFETY: This case only matches kinds allocated as a ForInOrOfStatement, which embeds its Node header at offset zero. - return (*ForInOrOfStatement)(unsafe.Pointer(n)).FlowNodeData() - case 9: - // SAFETY: This case only matches kinds allocated as a BreakStatement, which embeds its Node header at offset zero. - return (*BreakStatement)(unsafe.Pointer(n)).FlowNodeData() - case 10: - // SAFETY: This case only matches kinds allocated as a ContinueStatement, which embeds its Node header at offset zero. - return (*ContinueStatement)(unsafe.Pointer(n)).FlowNodeData() - case 11: - // SAFETY: This case only matches kinds allocated as a ReturnStatement, which embeds its Node header at offset zero. - return (*ReturnStatement)(unsafe.Pointer(n)).FlowNodeData() - case 12: - // SAFETY: This case only matches kinds allocated as a WithStatement, which embeds its Node header at offset zero. - return (*WithStatement)(unsafe.Pointer(n)).FlowNodeData() - case 13: - // SAFETY: This case only matches kinds allocated as a SwitchStatement, which embeds its Node header at offset zero. - return (*SwitchStatement)(unsafe.Pointer(n)).FlowNodeData() - case 14: - // SAFETY: This case only matches kinds allocated as a ThrowStatement, which embeds its Node header at offset zero. - return (*ThrowStatement)(unsafe.Pointer(n)).FlowNodeData() - case 15: - // SAFETY: This case only matches kinds allocated as a TryStatement, which embeds its Node header at offset zero. - return (*TryStatement)(unsafe.Pointer(n)).FlowNodeData() - case 16: - // SAFETY: This case only matches kinds allocated as a DebuggerStatement, which embeds its Node header at offset zero. - return (*DebuggerStatement)(unsafe.Pointer(n)).FlowNodeData() - case 17: - // SAFETY: This case only matches kinds allocated as a LabeledStatement, which embeds its Node header at offset zero. - return (*LabeledStatement)(unsafe.Pointer(n)).FlowNodeData() - case 18: - // SAFETY: This case only matches kinds allocated as a ExpressionStatement, which embeds its Node header at offset zero. - return (*ExpressionStatement)(unsafe.Pointer(n)).FlowNodeData() - case 19: - // SAFETY: This case only matches kinds allocated as a Block, which embeds its Node header at offset zero. - return (*Block)(unsafe.Pointer(n)).FlowNodeData() - case 20: - // SAFETY: This case only matches kinds allocated as a VariableStatement, which embeds its Node header at offset zero. - return (*VariableStatement)(unsafe.Pointer(n)).FlowNodeData() - case 21: - // SAFETY: This case only matches kinds allocated as a BindingElement, which embeds its Node header at offset zero. - return (*BindingElement)(unsafe.Pointer(n)).FlowNodeData() - case 22: - // SAFETY: This case only matches kinds allocated as a MissingDeclaration, which embeds its Node header at offset zero. - return (*MissingDeclaration)(unsafe.Pointer(n)).FlowNodeData() - case 23: - // SAFETY: This case only matches kinds allocated as a FunctionDeclaration, which embeds its Node header at offset zero. - return (*FunctionDeclaration)(unsafe.Pointer(n)).FlowNodeData() - case 24: - // SAFETY: This case only matches kinds allocated as a ClassDeclaration, which embeds its Node header at offset zero. - return (*ClassDeclaration)(unsafe.Pointer(n)).FlowNodeData() - case 25: - // SAFETY: This case only matches kinds allocated as a InterfaceDeclaration, which embeds its Node header at offset zero. - return (*InterfaceDeclaration)(unsafe.Pointer(n)).FlowNodeData() - case 26: - // SAFETY: This case only matches kinds allocated as a TypeAliasDeclaration, which embeds its Node header at offset zero. - return (*TypeAliasDeclaration)(unsafe.Pointer(n)).FlowNodeData() - case 27: - // SAFETY: This case only matches kinds allocated as a EnumDeclaration, which embeds its Node header at offset zero. - return (*EnumDeclaration)(unsafe.Pointer(n)).FlowNodeData() - case 28: - // SAFETY: This case only matches kinds allocated as a ModuleBlock, which embeds its Node header at offset zero. - return (*ModuleBlock)(unsafe.Pointer(n)).FlowNodeData() - case 29: - // SAFETY: This case only matches kinds allocated as a NotEmittedStatement, which embeds its Node header at offset zero. - return (*NotEmittedStatement)(unsafe.Pointer(n)).FlowNodeData() - case 30: - // SAFETY: This case only matches kinds allocated as a ImportDeclaration, which embeds its Node header at offset zero. - return (*ImportDeclaration)(unsafe.Pointer(n)).FlowNodeData() - case 31: - // SAFETY: This case only matches kinds allocated as a ExportAssignment, which embeds its Node header at offset zero. - return (*ExportAssignment)(unsafe.Pointer(n)).FlowNodeData() - case 32: - // SAFETY: This case only matches kinds allocated as a NamespaceExportDeclaration, which embeds its Node header at offset zero. - return (*NamespaceExportDeclaration)(unsafe.Pointer(n)).FlowNodeData() - case 33: - // SAFETY: This case only matches kinds allocated as a GetAccessorDeclaration, which embeds its Node header at offset zero. - return (*GetAccessorDeclaration)(unsafe.Pointer(n)).FlowNodeData() - case 34: - // SAFETY: This case only matches kinds allocated as a SetAccessorDeclaration, which embeds its Node header at offset zero. - return (*SetAccessorDeclaration)(unsafe.Pointer(n)).FlowNodeData() - case 35: - // SAFETY: This case only matches kinds allocated as a MethodDeclaration, which embeds its Node header at offset zero. - return (*MethodDeclaration)(unsafe.Pointer(n)).FlowNodeData() - case 36: - // SAFETY: This case only matches kinds allocated as a KeywordExpression, which embeds its Node header at offset zero. - return (*KeywordExpression)(unsafe.Pointer(n)).FlowNodeData() - case 37: - // SAFETY: This case only matches kinds allocated as a ArrowFunction, which embeds its Node header at offset zero. - return (*ArrowFunction)(unsafe.Pointer(n)).FlowNodeData() - case 38: - // SAFETY: This case only matches kinds allocated as a FunctionExpression, which embeds its Node header at offset zero. - return (*FunctionExpression)(unsafe.Pointer(n)).FlowNodeData() - case 39: - // SAFETY: This case only matches kinds allocated as a PropertyAccessExpression, which embeds its Node header at offset zero. - return (*PropertyAccessExpression)(unsafe.Pointer(n)).FlowNodeData() - case 40: - // SAFETY: This case only matches kinds allocated as a ElementAccessExpression, which embeds its Node header at offset zero. - return (*ElementAccessExpression)(unsafe.Pointer(n)).FlowNodeData() - case 41: - // SAFETY: This case only matches kinds allocated as a MetaProperty, which embeds its Node header at offset zero. - return (*MetaProperty)(unsafe.Pointer(n)).FlowNodeData() - case 42: - // SAFETY: This case only matches kinds allocated as a ModuleDeclaration, which embeds its Node header at offset zero. - return (*ModuleDeclaration)(unsafe.Pointer(n)).FlowNodeData() - case 43: - // SAFETY: This case only matches kinds allocated as a ImportEqualsDeclaration, which embeds its Node header at offset zero. - return (*ImportEqualsDeclaration)(unsafe.Pointer(n)).FlowNodeData() - case 44: - // SAFETY: This case only matches kinds allocated as a ExportDeclaration, which embeds its Node header at offset zero. - return (*ExportDeclaration)(unsafe.Pointer(n)).FlowNodeData() - default: - return nil + if offset := nodeFlowNodeDataDispatch[n.Kind]; offset != 0 { + // SAFETY: nodeFlowNodeDataDispatch stores the byte offset of FlowNodeBase within the concrete node struct for n.Kind. + return (*FlowNodeBase)(unsafe.Add(unsafe.Pointer(n), offset)) } + return nil } var nodeDeclarationDataDispatch = [kindFlowReduceLabelData + 1]uint8{ - KindVariableDeclaration: 1, - KindParameter: 2, - KindBindingElement: 3, - KindMissingDeclaration: 4, - KindFunctionDeclaration: 5, - KindClassDeclaration: 6, - KindClassExpression: 7, - KindInterfaceDeclaration: 8, - KindTypeAliasDeclaration: 9, - KindJSTypeAliasDeclaration: 9, - KindEnumMember: 10, - KindEnumDeclaration: 11, - KindNotEmittedTypeElement: 12, - KindImportDeclaration: 13, - KindJSImportDeclaration: 13, - KindNamespaceImport: 14, - KindExportAssignment: 15, - KindNamespaceExportDeclaration: 16, - KindNamespaceExport: 17, - KindExportSpecifier: 18, - KindCallSignature: 19, - KindConstructSignature: 20, - KindConstructor: 21, - KindGetAccessor: 22, - KindSetAccessor: 23, - KindIndexSignature: 24, - KindMethodSignature: 25, - KindMethodDeclaration: 26, - KindPropertySignature: 27, - KindPropertyDeclaration: 28, - KindSemicolonClassElement: 29, - KindClassStaticBlockDeclaration: 30, - KindNoSubstitutionTemplateLiteral: 31, - KindBinaryExpression: 32, - KindArrowFunction: 33, - KindFunctionExpression: 34, - KindCallExpression: 35, - KindObjectLiteralExpression: 36, - KindSpreadAssignment: 37, - KindPropertyAssignment: 38, - KindShorthandPropertyAssignment: 39, - KindMappedType: 40, - KindTypeLiteral: 41, - KindNamedTupleMember: 42, - KindFunctionType: 43, - KindConstructorType: 44, - KindJsxAttributes: 45, - KindJsxAttribute: 46, - KindJsxSpreadAttribute: 47, - KindJSDocSignature: 48, - KindSourceFile: 49, - KindModuleDeclaration: 50, - KindImportEqualsDeclaration: 51, - KindExportDeclaration: 52, - KindImportClause: 53, - KindImportSpecifier: 54, - KindTypeParameter: 55, - KindJSDocTypeLiteral: 56, + KindVariableDeclaration: uint8(unsafe.Offsetof(VariableDeclaration{}.DeclarationBase)), + KindParameter: uint8(unsafe.Offsetof(ParameterDeclaration{}.DeclarationBase)), + KindBindingElement: uint8(unsafe.Offsetof(BindingElement{}.DeclarationBase)), + KindMissingDeclaration: uint8(unsafe.Offsetof(MissingDeclaration{}.DeclarationBase)), + KindFunctionDeclaration: uint8(unsafe.Offsetof(FunctionDeclaration{}.DeclarationBase)), + KindClassDeclaration: uint8(unsafe.Offsetof(ClassDeclaration{}.DeclarationBase)), + KindClassExpression: uint8(unsafe.Offsetof(ClassExpression{}.DeclarationBase)), + KindInterfaceDeclaration: uint8(unsafe.Offsetof(InterfaceDeclaration{}.DeclarationBase)), + KindTypeAliasDeclaration: uint8(unsafe.Offsetof(TypeAliasDeclaration{}.DeclarationBase)), + KindJSTypeAliasDeclaration: uint8(unsafe.Offsetof(TypeAliasDeclaration{}.DeclarationBase)), + KindEnumMember: uint8(unsafe.Offsetof(EnumMember{}.DeclarationBase)), + KindEnumDeclaration: uint8(unsafe.Offsetof(EnumDeclaration{}.DeclarationBase)), + KindNotEmittedTypeElement: uint8(unsafe.Offsetof(NotEmittedTypeElement{}.DeclarationBase)), + KindImportDeclaration: uint8(unsafe.Offsetof(ImportDeclaration{}.DeclarationBase)), + KindJSImportDeclaration: uint8(unsafe.Offsetof(ImportDeclaration{}.DeclarationBase)), + KindNamespaceImport: uint8(unsafe.Offsetof(NamespaceImport{}.DeclarationBase)), + KindExportAssignment: uint8(unsafe.Offsetof(ExportAssignment{}.DeclarationBase)), + KindNamespaceExportDeclaration: uint8(unsafe.Offsetof(NamespaceExportDeclaration{}.DeclarationBase)), + KindNamespaceExport: uint8(unsafe.Offsetof(NamespaceExport{}.DeclarationBase)), + KindExportSpecifier: uint8(unsafe.Offsetof(ExportSpecifier{}.DeclarationBase)), + KindCallSignature: uint8(unsafe.Offsetof(CallSignatureDeclaration{}.DeclarationBase)), + KindConstructSignature: uint8(unsafe.Offsetof(ConstructSignatureDeclaration{}.DeclarationBase)), + KindConstructor: uint8(unsafe.Offsetof(ConstructorDeclaration{}.DeclarationBase)), + KindGetAccessor: uint8(unsafe.Offsetof(GetAccessorDeclaration{}.DeclarationBase)), + KindSetAccessor: uint8(unsafe.Offsetof(SetAccessorDeclaration{}.DeclarationBase)), + KindIndexSignature: uint8(unsafe.Offsetof(IndexSignatureDeclaration{}.DeclarationBase)), + KindMethodSignature: uint8(unsafe.Offsetof(MethodSignatureDeclaration{}.DeclarationBase)), + KindMethodDeclaration: uint8(unsafe.Offsetof(MethodDeclaration{}.DeclarationBase)), + KindPropertySignature: uint8(unsafe.Offsetof(PropertySignatureDeclaration{}.DeclarationBase)), + KindPropertyDeclaration: uint8(unsafe.Offsetof(PropertyDeclaration{}.DeclarationBase)), + KindSemicolonClassElement: uint8(unsafe.Offsetof(SemicolonClassElement{}.DeclarationBase)), + KindClassStaticBlockDeclaration: uint8(unsafe.Offsetof(ClassStaticBlockDeclaration{}.DeclarationBase)), + KindNoSubstitutionTemplateLiteral: uint8(unsafe.Offsetof(NoSubstitutionTemplateLiteral{}.DeclarationBase)), + KindBinaryExpression: uint8(unsafe.Offsetof(BinaryExpression{}.DeclarationBase)), + KindArrowFunction: uint8(unsafe.Offsetof(ArrowFunction{}.DeclarationBase)), + KindFunctionExpression: uint8(unsafe.Offsetof(FunctionExpression{}.DeclarationBase)), + KindCallExpression: uint8(unsafe.Offsetof(CallExpression{}.DeclarationBase)), + KindObjectLiteralExpression: uint8(unsafe.Offsetof(ObjectLiteralExpression{}.DeclarationBase)), + KindSpreadAssignment: uint8(unsafe.Offsetof(SpreadAssignment{}.DeclarationBase)), + KindPropertyAssignment: uint8(unsafe.Offsetof(PropertyAssignment{}.DeclarationBase)), + KindShorthandPropertyAssignment: uint8(unsafe.Offsetof(ShorthandPropertyAssignment{}.DeclarationBase)), + KindMappedType: uint8(unsafe.Offsetof(MappedTypeNode{}.DeclarationBase)), + KindTypeLiteral: uint8(unsafe.Offsetof(TypeLiteralNode{}.DeclarationBase)), + KindNamedTupleMember: uint8(unsafe.Offsetof(NamedTupleMember{}.DeclarationBase)), + KindFunctionType: uint8(unsafe.Offsetof(FunctionTypeNode{}.DeclarationBase)), + KindConstructorType: uint8(unsafe.Offsetof(ConstructorTypeNode{}.DeclarationBase)), + KindJsxAttributes: uint8(unsafe.Offsetof(JsxAttributes{}.DeclarationBase)), + KindJsxAttribute: uint8(unsafe.Offsetof(JsxAttribute{}.DeclarationBase)), + KindJsxSpreadAttribute: uint8(unsafe.Offsetof(JsxSpreadAttribute{}.DeclarationBase)), + KindJSDocSignature: uint8(unsafe.Offsetof(JSDocSignature{}.DeclarationBase)), + KindSourceFile: uint8(unsafe.Offsetof(SourceFile{}.DeclarationBase)), + KindModuleDeclaration: uint8(unsafe.Offsetof(ModuleDeclaration{}.DeclarationBase)), + KindImportEqualsDeclaration: uint8(unsafe.Offsetof(ImportEqualsDeclaration{}.DeclarationBase)), + KindExportDeclaration: uint8(unsafe.Offsetof(ExportDeclaration{}.DeclarationBase)), + KindImportClause: uint8(unsafe.Offsetof(ImportClause{}.DeclarationBase)), + KindImportSpecifier: uint8(unsafe.Offsetof(ImportSpecifier{}.DeclarationBase)), + KindTypeParameter: uint8(unsafe.Offsetof(TypeParameterDeclaration{}.DeclarationBase)), + KindJSDocTypeLiteral: uint8(unsafe.Offsetof(JSDocTypeLiteral{}.DeclarationBase)), } func (n *Node) DeclarationData() *DeclarationBase { - switch nodeDeclarationDataDispatch[n.Kind] { - case 1: - // SAFETY: This case only matches kinds allocated as a VariableDeclaration, which embeds its Node header at offset zero. - return (*VariableDeclaration)(unsafe.Pointer(n)).DeclarationData() - case 2: - // SAFETY: This case only matches kinds allocated as a ParameterDeclaration, which embeds its Node header at offset zero. - return (*ParameterDeclaration)(unsafe.Pointer(n)).DeclarationData() - case 3: - // SAFETY: This case only matches kinds allocated as a BindingElement, which embeds its Node header at offset zero. - return (*BindingElement)(unsafe.Pointer(n)).DeclarationData() - case 4: - // SAFETY: This case only matches kinds allocated as a MissingDeclaration, which embeds its Node header at offset zero. - return (*MissingDeclaration)(unsafe.Pointer(n)).DeclarationData() - case 5: - // SAFETY: This case only matches kinds allocated as a FunctionDeclaration, which embeds its Node header at offset zero. - return (*FunctionDeclaration)(unsafe.Pointer(n)).DeclarationData() - case 6: - // SAFETY: This case only matches kinds allocated as a ClassDeclaration, which embeds its Node header at offset zero. - return (*ClassDeclaration)(unsafe.Pointer(n)).DeclarationData() - case 7: - // SAFETY: This case only matches kinds allocated as a ClassExpression, which embeds its Node header at offset zero. - return (*ClassExpression)(unsafe.Pointer(n)).DeclarationData() - case 8: - // SAFETY: This case only matches kinds allocated as a InterfaceDeclaration, which embeds its Node header at offset zero. - return (*InterfaceDeclaration)(unsafe.Pointer(n)).DeclarationData() - case 9: - // SAFETY: This case only matches kinds allocated as a TypeAliasDeclaration, which embeds its Node header at offset zero. - return (*TypeAliasDeclaration)(unsafe.Pointer(n)).DeclarationData() - case 10: - // SAFETY: This case only matches kinds allocated as a EnumMember, which embeds its Node header at offset zero. - return (*EnumMember)(unsafe.Pointer(n)).DeclarationData() - case 11: - // SAFETY: This case only matches kinds allocated as a EnumDeclaration, which embeds its Node header at offset zero. - return (*EnumDeclaration)(unsafe.Pointer(n)).DeclarationData() - case 12: - // SAFETY: This case only matches kinds allocated as a NotEmittedTypeElement, which embeds its Node header at offset zero. - return (*NotEmittedTypeElement)(unsafe.Pointer(n)).DeclarationData() - case 13: - // SAFETY: This case only matches kinds allocated as a ImportDeclaration, which embeds its Node header at offset zero. - return (*ImportDeclaration)(unsafe.Pointer(n)).DeclarationData() - case 14: - // SAFETY: This case only matches kinds allocated as a NamespaceImport, which embeds its Node header at offset zero. - return (*NamespaceImport)(unsafe.Pointer(n)).DeclarationData() - case 15: - // SAFETY: This case only matches kinds allocated as a ExportAssignment, which embeds its Node header at offset zero. - return (*ExportAssignment)(unsafe.Pointer(n)).DeclarationData() - case 16: - // SAFETY: This case only matches kinds allocated as a NamespaceExportDeclaration, which embeds its Node header at offset zero. - return (*NamespaceExportDeclaration)(unsafe.Pointer(n)).DeclarationData() - case 17: - // SAFETY: This case only matches kinds allocated as a NamespaceExport, which embeds its Node header at offset zero. - return (*NamespaceExport)(unsafe.Pointer(n)).DeclarationData() - case 18: - // SAFETY: This case only matches kinds allocated as a ExportSpecifier, which embeds its Node header at offset zero. - return (*ExportSpecifier)(unsafe.Pointer(n)).DeclarationData() - case 19: - // SAFETY: This case only matches kinds allocated as a CallSignatureDeclaration, which embeds its Node header at offset zero. - return (*CallSignatureDeclaration)(unsafe.Pointer(n)).DeclarationData() - case 20: - // SAFETY: This case only matches kinds allocated as a ConstructSignatureDeclaration, which embeds its Node header at offset zero. - return (*ConstructSignatureDeclaration)(unsafe.Pointer(n)).DeclarationData() - case 21: - // SAFETY: This case only matches kinds allocated as a ConstructorDeclaration, which embeds its Node header at offset zero. - return (*ConstructorDeclaration)(unsafe.Pointer(n)).DeclarationData() - case 22: - // SAFETY: This case only matches kinds allocated as a GetAccessorDeclaration, which embeds its Node header at offset zero. - return (*GetAccessorDeclaration)(unsafe.Pointer(n)).DeclarationData() - case 23: - // SAFETY: This case only matches kinds allocated as a SetAccessorDeclaration, which embeds its Node header at offset zero. - return (*SetAccessorDeclaration)(unsafe.Pointer(n)).DeclarationData() - case 24: - // SAFETY: This case only matches kinds allocated as a IndexSignatureDeclaration, which embeds its Node header at offset zero. - return (*IndexSignatureDeclaration)(unsafe.Pointer(n)).DeclarationData() - case 25: - // SAFETY: This case only matches kinds allocated as a MethodSignatureDeclaration, which embeds its Node header at offset zero. - return (*MethodSignatureDeclaration)(unsafe.Pointer(n)).DeclarationData() - case 26: - // SAFETY: This case only matches kinds allocated as a MethodDeclaration, which embeds its Node header at offset zero. - return (*MethodDeclaration)(unsafe.Pointer(n)).DeclarationData() - case 27: - // SAFETY: This case only matches kinds allocated as a PropertySignatureDeclaration, which embeds its Node header at offset zero. - return (*PropertySignatureDeclaration)(unsafe.Pointer(n)).DeclarationData() - case 28: - // SAFETY: This case only matches kinds allocated as a PropertyDeclaration, which embeds its Node header at offset zero. - return (*PropertyDeclaration)(unsafe.Pointer(n)).DeclarationData() - case 29: - // SAFETY: This case only matches kinds allocated as a SemicolonClassElement, which embeds its Node header at offset zero. - return (*SemicolonClassElement)(unsafe.Pointer(n)).DeclarationData() - case 30: - // SAFETY: This case only matches kinds allocated as a ClassStaticBlockDeclaration, which embeds its Node header at offset zero. - return (*ClassStaticBlockDeclaration)(unsafe.Pointer(n)).DeclarationData() - case 31: - // SAFETY: This case only matches kinds allocated as a NoSubstitutionTemplateLiteral, which embeds its Node header at offset zero. - return (*NoSubstitutionTemplateLiteral)(unsafe.Pointer(n)).DeclarationData() - case 32: - // SAFETY: This case only matches kinds allocated as a BinaryExpression, which embeds its Node header at offset zero. - return (*BinaryExpression)(unsafe.Pointer(n)).DeclarationData() - case 33: - // SAFETY: This case only matches kinds allocated as a ArrowFunction, which embeds its Node header at offset zero. - return (*ArrowFunction)(unsafe.Pointer(n)).DeclarationData() - case 34: - // SAFETY: This case only matches kinds allocated as a FunctionExpression, which embeds its Node header at offset zero. - return (*FunctionExpression)(unsafe.Pointer(n)).DeclarationData() - case 35: - // SAFETY: This case only matches kinds allocated as a CallExpression, which embeds its Node header at offset zero. - return (*CallExpression)(unsafe.Pointer(n)).DeclarationData() - case 36: - // SAFETY: This case only matches kinds allocated as a ObjectLiteralExpression, which embeds its Node header at offset zero. - return (*ObjectLiteralExpression)(unsafe.Pointer(n)).DeclarationData() - case 37: - // SAFETY: This case only matches kinds allocated as a SpreadAssignment, which embeds its Node header at offset zero. - return (*SpreadAssignment)(unsafe.Pointer(n)).DeclarationData() - case 38: - // SAFETY: This case only matches kinds allocated as a PropertyAssignment, which embeds its Node header at offset zero. - return (*PropertyAssignment)(unsafe.Pointer(n)).DeclarationData() - case 39: - // SAFETY: This case only matches kinds allocated as a ShorthandPropertyAssignment, which embeds its Node header at offset zero. - return (*ShorthandPropertyAssignment)(unsafe.Pointer(n)).DeclarationData() - case 40: - // SAFETY: This case only matches kinds allocated as a MappedTypeNode, which embeds its Node header at offset zero. - return (*MappedTypeNode)(unsafe.Pointer(n)).DeclarationData() - case 41: - // SAFETY: This case only matches kinds allocated as a TypeLiteralNode, which embeds its Node header at offset zero. - return (*TypeLiteralNode)(unsafe.Pointer(n)).DeclarationData() - case 42: - // SAFETY: This case only matches kinds allocated as a NamedTupleMember, which embeds its Node header at offset zero. - return (*NamedTupleMember)(unsafe.Pointer(n)).DeclarationData() - case 43: - // SAFETY: This case only matches kinds allocated as a FunctionTypeNode, which embeds its Node header at offset zero. - return (*FunctionTypeNode)(unsafe.Pointer(n)).DeclarationData() - case 44: - // SAFETY: This case only matches kinds allocated as a ConstructorTypeNode, which embeds its Node header at offset zero. - return (*ConstructorTypeNode)(unsafe.Pointer(n)).DeclarationData() - case 45: - // SAFETY: This case only matches kinds allocated as a JsxAttributes, which embeds its Node header at offset zero. - return (*JsxAttributes)(unsafe.Pointer(n)).DeclarationData() - case 46: - // SAFETY: This case only matches kinds allocated as a JsxAttribute, which embeds its Node header at offset zero. - return (*JsxAttribute)(unsafe.Pointer(n)).DeclarationData() - case 47: - // SAFETY: This case only matches kinds allocated as a JsxSpreadAttribute, which embeds its Node header at offset zero. - return (*JsxSpreadAttribute)(unsafe.Pointer(n)).DeclarationData() - case 48: - // SAFETY: This case only matches kinds allocated as a JSDocSignature, which embeds its Node header at offset zero. - return (*JSDocSignature)(unsafe.Pointer(n)).DeclarationData() - case 49: - // SAFETY: This case only matches kinds allocated as a SourceFile, which embeds its Node header at offset zero. - return (*SourceFile)(unsafe.Pointer(n)).DeclarationData() - case 50: - // SAFETY: This case only matches kinds allocated as a ModuleDeclaration, which embeds its Node header at offset zero. - return (*ModuleDeclaration)(unsafe.Pointer(n)).DeclarationData() - case 51: - // SAFETY: This case only matches kinds allocated as a ImportEqualsDeclaration, which embeds its Node header at offset zero. - return (*ImportEqualsDeclaration)(unsafe.Pointer(n)).DeclarationData() - case 52: - // SAFETY: This case only matches kinds allocated as a ExportDeclaration, which embeds its Node header at offset zero. - return (*ExportDeclaration)(unsafe.Pointer(n)).DeclarationData() - case 53: - // SAFETY: This case only matches kinds allocated as a ImportClause, which embeds its Node header at offset zero. - return (*ImportClause)(unsafe.Pointer(n)).DeclarationData() - case 54: - // SAFETY: This case only matches kinds allocated as a ImportSpecifier, which embeds its Node header at offset zero. - return (*ImportSpecifier)(unsafe.Pointer(n)).DeclarationData() - case 55: - // SAFETY: This case only matches kinds allocated as a TypeParameterDeclaration, which embeds its Node header at offset zero. - return (*TypeParameterDeclaration)(unsafe.Pointer(n)).DeclarationData() - case 56: - // SAFETY: This case only matches kinds allocated as a JSDocTypeLiteral, which embeds its Node header at offset zero. - return (*JSDocTypeLiteral)(unsafe.Pointer(n)).DeclarationData() - default: - return nil + if offset := nodeDeclarationDataDispatch[n.Kind]; offset != 0 { + // SAFETY: nodeDeclarationDataDispatch stores the byte offset of DeclarationBase within the concrete node struct for n.Kind. + return (*DeclarationBase)(unsafe.Add(unsafe.Pointer(n), offset)) } + return nil } var nodeExportableDataDispatch = [kindFlowReduceLabelData + 1]uint8{ - KindVariableDeclaration: 1, - KindBindingElement: 2, - KindFunctionDeclaration: 3, - KindClassDeclaration: 4, - KindClassExpression: 5, - KindInterfaceDeclaration: 6, - KindTypeAliasDeclaration: 7, - KindJSTypeAliasDeclaration: 7, - KindEnumDeclaration: 8, - KindNamespaceImport: 9, - KindExportSpecifier: 10, - KindModuleDeclaration: 11, - KindImportEqualsDeclaration: 12, - KindImportClause: 13, - KindImportSpecifier: 14, + KindVariableDeclaration: uint8(unsafe.Offsetof(VariableDeclaration{}.ExportableBase)), + KindBindingElement: uint8(unsafe.Offsetof(BindingElement{}.ExportableBase)), + KindFunctionDeclaration: uint8(unsafe.Offsetof(FunctionDeclaration{}.ExportableBase)), + KindClassDeclaration: uint8(unsafe.Offsetof(ClassDeclaration{}.ExportableBase)), + KindClassExpression: uint8(unsafe.Offsetof(ClassExpression{}.ExportableBase)), + KindInterfaceDeclaration: uint8(unsafe.Offsetof(InterfaceDeclaration{}.ExportableBase)), + KindTypeAliasDeclaration: uint8(unsafe.Offsetof(TypeAliasDeclaration{}.ExportableBase)), + KindJSTypeAliasDeclaration: uint8(unsafe.Offsetof(TypeAliasDeclaration{}.ExportableBase)), + KindEnumDeclaration: uint8(unsafe.Offsetof(EnumDeclaration{}.ExportableBase)), + KindNamespaceImport: uint8(unsafe.Offsetof(NamespaceImport{}.ExportableBase)), + KindExportSpecifier: uint8(unsafe.Offsetof(ExportSpecifier{}.ExportableBase)), + KindModuleDeclaration: uint8(unsafe.Offsetof(ModuleDeclaration{}.ExportableBase)), + KindImportEqualsDeclaration: uint8(unsafe.Offsetof(ImportEqualsDeclaration{}.ExportableBase)), + KindImportClause: uint8(unsafe.Offsetof(ImportClause{}.ExportableBase)), + KindImportSpecifier: uint8(unsafe.Offsetof(ImportSpecifier{}.ExportableBase)), } func (n *Node) ExportableData() *ExportableBase { - switch nodeExportableDataDispatch[n.Kind] { - case 1: - // SAFETY: This case only matches kinds allocated as a VariableDeclaration, which embeds its Node header at offset zero. - return (*VariableDeclaration)(unsafe.Pointer(n)).ExportableData() - case 2: - // SAFETY: This case only matches kinds allocated as a BindingElement, which embeds its Node header at offset zero. - return (*BindingElement)(unsafe.Pointer(n)).ExportableData() - case 3: - // SAFETY: This case only matches kinds allocated as a FunctionDeclaration, which embeds its Node header at offset zero. - return (*FunctionDeclaration)(unsafe.Pointer(n)).ExportableData() - case 4: - // SAFETY: This case only matches kinds allocated as a ClassDeclaration, which embeds its Node header at offset zero. - return (*ClassDeclaration)(unsafe.Pointer(n)).ExportableData() - case 5: - // SAFETY: This case only matches kinds allocated as a ClassExpression, which embeds its Node header at offset zero. - return (*ClassExpression)(unsafe.Pointer(n)).ExportableData() - case 6: - // SAFETY: This case only matches kinds allocated as a InterfaceDeclaration, which embeds its Node header at offset zero. - return (*InterfaceDeclaration)(unsafe.Pointer(n)).ExportableData() - case 7: - // SAFETY: This case only matches kinds allocated as a TypeAliasDeclaration, which embeds its Node header at offset zero. - return (*TypeAliasDeclaration)(unsafe.Pointer(n)).ExportableData() - case 8: - // SAFETY: This case only matches kinds allocated as a EnumDeclaration, which embeds its Node header at offset zero. - return (*EnumDeclaration)(unsafe.Pointer(n)).ExportableData() - case 9: - // SAFETY: This case only matches kinds allocated as a NamespaceImport, which embeds its Node header at offset zero. - return (*NamespaceImport)(unsafe.Pointer(n)).ExportableData() - case 10: - // SAFETY: This case only matches kinds allocated as a ExportSpecifier, which embeds its Node header at offset zero. - return (*ExportSpecifier)(unsafe.Pointer(n)).ExportableData() - case 11: - // SAFETY: This case only matches kinds allocated as a ModuleDeclaration, which embeds its Node header at offset zero. - return (*ModuleDeclaration)(unsafe.Pointer(n)).ExportableData() - case 12: - // SAFETY: This case only matches kinds allocated as a ImportEqualsDeclaration, which embeds its Node header at offset zero. - return (*ImportEqualsDeclaration)(unsafe.Pointer(n)).ExportableData() - case 13: - // SAFETY: This case only matches kinds allocated as a ImportClause, which embeds its Node header at offset zero. - return (*ImportClause)(unsafe.Pointer(n)).ExportableData() - case 14: - // SAFETY: This case only matches kinds allocated as a ImportSpecifier, which embeds its Node header at offset zero. - return (*ImportSpecifier)(unsafe.Pointer(n)).ExportableData() - default: - return nil + if offset := nodeExportableDataDispatch[n.Kind]; offset != 0 { + // SAFETY: nodeExportableDataDispatch stores the byte offset of ExportableBase within the concrete node struct for n.Kind. + return (*ExportableBase)(unsafe.Add(unsafe.Pointer(n), offset)) } + return nil } var nodeLocalsContainerDataDispatch = [kindFlowReduceLabelData + 1]uint8{ - KindForStatement: 1, - KindForInStatement: 2, - KindForOfStatement: 2, - KindCaseBlock: 3, - KindCatchClause: 4, - KindBlock: 5, - KindFunctionDeclaration: 6, - KindClassDeclaration: 7, - KindClassExpression: 8, - KindTypeAliasDeclaration: 9, - KindJSTypeAliasDeclaration: 9, - KindCallSignature: 10, - KindConstructSignature: 11, - KindConstructor: 12, - KindGetAccessor: 13, - KindSetAccessor: 14, - KindIndexSignature: 15, - KindMethodSignature: 16, - KindMethodDeclaration: 17, - KindClassStaticBlockDeclaration: 18, - KindArrowFunction: 19, - KindFunctionExpression: 20, - KindConditionalType: 21, - KindMappedType: 22, - KindFunctionType: 23, - KindConstructorType: 24, - KindJSDocSignature: 25, - KindSourceFile: 26, - KindModuleDeclaration: 27, + KindForStatement: uint8(unsafe.Offsetof(ForStatement{}.LocalsContainerBase)), + KindForInStatement: uint8(unsafe.Offsetof(ForInOrOfStatement{}.LocalsContainerBase)), + KindForOfStatement: uint8(unsafe.Offsetof(ForInOrOfStatement{}.LocalsContainerBase)), + KindCaseBlock: uint8(unsafe.Offsetof(CaseBlock{}.LocalsContainerBase)), + KindCatchClause: uint8(unsafe.Offsetof(CatchClause{}.LocalsContainerBase)), + KindBlock: uint8(unsafe.Offsetof(Block{}.LocalsContainerBase)), + KindFunctionDeclaration: uint8(unsafe.Offsetof(FunctionDeclaration{}.LocalsContainerBase)), + KindClassDeclaration: uint8(unsafe.Offsetof(ClassDeclaration{}.LocalsContainerBase)), + KindClassExpression: uint8(unsafe.Offsetof(ClassExpression{}.LocalsContainerBase)), + KindTypeAliasDeclaration: uint8(unsafe.Offsetof(TypeAliasDeclaration{}.LocalsContainerBase)), + KindJSTypeAliasDeclaration: uint8(unsafe.Offsetof(TypeAliasDeclaration{}.LocalsContainerBase)), + KindCallSignature: uint8(unsafe.Offsetof(CallSignatureDeclaration{}.LocalsContainerBase)), + KindConstructSignature: uint8(unsafe.Offsetof(ConstructSignatureDeclaration{}.LocalsContainerBase)), + KindConstructor: uint8(unsafe.Offsetof(ConstructorDeclaration{}.LocalsContainerBase)), + KindGetAccessor: uint8(unsafe.Offsetof(GetAccessorDeclaration{}.LocalsContainerBase)), + KindSetAccessor: uint8(unsafe.Offsetof(SetAccessorDeclaration{}.LocalsContainerBase)), + KindIndexSignature: uint8(unsafe.Offsetof(IndexSignatureDeclaration{}.LocalsContainerBase)), + KindMethodSignature: uint8(unsafe.Offsetof(MethodSignatureDeclaration{}.LocalsContainerBase)), + KindMethodDeclaration: uint8(unsafe.Offsetof(MethodDeclaration{}.LocalsContainerBase)), + KindClassStaticBlockDeclaration: uint8(unsafe.Offsetof(ClassStaticBlockDeclaration{}.LocalsContainerBase)), + KindArrowFunction: uint8(unsafe.Offsetof(ArrowFunction{}.LocalsContainerBase)), + KindFunctionExpression: uint8(unsafe.Offsetof(FunctionExpression{}.LocalsContainerBase)), + KindConditionalType: uint8(unsafe.Offsetof(ConditionalTypeNode{}.LocalsContainerBase)), + KindMappedType: uint8(unsafe.Offsetof(MappedTypeNode{}.LocalsContainerBase)), + KindFunctionType: uint8(unsafe.Offsetof(FunctionTypeNode{}.LocalsContainerBase)), + KindConstructorType: uint8(unsafe.Offsetof(ConstructorTypeNode{}.LocalsContainerBase)), + KindJSDocSignature: uint8(unsafe.Offsetof(JSDocSignature{}.LocalsContainerBase)), + KindSourceFile: uint8(unsafe.Offsetof(SourceFile{}.LocalsContainerBase)), + KindModuleDeclaration: uint8(unsafe.Offsetof(ModuleDeclaration{}.LocalsContainerBase)), } func (n *Node) LocalsContainerData() *LocalsContainerBase { - switch nodeLocalsContainerDataDispatch[n.Kind] { - case 1: - // SAFETY: This case only matches kinds allocated as a ForStatement, which embeds its Node header at offset zero. - return (*ForStatement)(unsafe.Pointer(n)).LocalsContainerData() - case 2: - // SAFETY: This case only matches kinds allocated as a ForInOrOfStatement, which embeds its Node header at offset zero. - return (*ForInOrOfStatement)(unsafe.Pointer(n)).LocalsContainerData() - case 3: - // SAFETY: This case only matches kinds allocated as a CaseBlock, which embeds its Node header at offset zero. - return (*CaseBlock)(unsafe.Pointer(n)).LocalsContainerData() - case 4: - // SAFETY: This case only matches kinds allocated as a CatchClause, which embeds its Node header at offset zero. - return (*CatchClause)(unsafe.Pointer(n)).LocalsContainerData() - case 5: - // SAFETY: This case only matches kinds allocated as a Block, which embeds its Node header at offset zero. - return (*Block)(unsafe.Pointer(n)).LocalsContainerData() - case 6: - // SAFETY: This case only matches kinds allocated as a FunctionDeclaration, which embeds its Node header at offset zero. - return (*FunctionDeclaration)(unsafe.Pointer(n)).LocalsContainerData() - case 7: - // SAFETY: This case only matches kinds allocated as a ClassDeclaration, which embeds its Node header at offset zero. - return (*ClassDeclaration)(unsafe.Pointer(n)).LocalsContainerData() - case 8: - // SAFETY: This case only matches kinds allocated as a ClassExpression, which embeds its Node header at offset zero. - return (*ClassExpression)(unsafe.Pointer(n)).LocalsContainerData() - case 9: - // SAFETY: This case only matches kinds allocated as a TypeAliasDeclaration, which embeds its Node header at offset zero. - return (*TypeAliasDeclaration)(unsafe.Pointer(n)).LocalsContainerData() - case 10: - // SAFETY: This case only matches kinds allocated as a CallSignatureDeclaration, which embeds its Node header at offset zero. - return (*CallSignatureDeclaration)(unsafe.Pointer(n)).LocalsContainerData() - case 11: - // SAFETY: This case only matches kinds allocated as a ConstructSignatureDeclaration, which embeds its Node header at offset zero. - return (*ConstructSignatureDeclaration)(unsafe.Pointer(n)).LocalsContainerData() - case 12: - // SAFETY: This case only matches kinds allocated as a ConstructorDeclaration, which embeds its Node header at offset zero. - return (*ConstructorDeclaration)(unsafe.Pointer(n)).LocalsContainerData() - case 13: - // SAFETY: This case only matches kinds allocated as a GetAccessorDeclaration, which embeds its Node header at offset zero. - return (*GetAccessorDeclaration)(unsafe.Pointer(n)).LocalsContainerData() - case 14: - // SAFETY: This case only matches kinds allocated as a SetAccessorDeclaration, which embeds its Node header at offset zero. - return (*SetAccessorDeclaration)(unsafe.Pointer(n)).LocalsContainerData() - case 15: - // SAFETY: This case only matches kinds allocated as a IndexSignatureDeclaration, which embeds its Node header at offset zero. - return (*IndexSignatureDeclaration)(unsafe.Pointer(n)).LocalsContainerData() - case 16: - // SAFETY: This case only matches kinds allocated as a MethodSignatureDeclaration, which embeds its Node header at offset zero. - return (*MethodSignatureDeclaration)(unsafe.Pointer(n)).LocalsContainerData() - case 17: - // SAFETY: This case only matches kinds allocated as a MethodDeclaration, which embeds its Node header at offset zero. - return (*MethodDeclaration)(unsafe.Pointer(n)).LocalsContainerData() - case 18: - // SAFETY: This case only matches kinds allocated as a ClassStaticBlockDeclaration, which embeds its Node header at offset zero. - return (*ClassStaticBlockDeclaration)(unsafe.Pointer(n)).LocalsContainerData() - case 19: - // SAFETY: This case only matches kinds allocated as a ArrowFunction, which embeds its Node header at offset zero. - return (*ArrowFunction)(unsafe.Pointer(n)).LocalsContainerData() - case 20: - // SAFETY: This case only matches kinds allocated as a FunctionExpression, which embeds its Node header at offset zero. - return (*FunctionExpression)(unsafe.Pointer(n)).LocalsContainerData() - case 21: - // SAFETY: This case only matches kinds allocated as a ConditionalTypeNode, which embeds its Node header at offset zero. - return (*ConditionalTypeNode)(unsafe.Pointer(n)).LocalsContainerData() - case 22: - // SAFETY: This case only matches kinds allocated as a MappedTypeNode, which embeds its Node header at offset zero. - return (*MappedTypeNode)(unsafe.Pointer(n)).LocalsContainerData() - case 23: - // SAFETY: This case only matches kinds allocated as a FunctionTypeNode, which embeds its Node header at offset zero. - return (*FunctionTypeNode)(unsafe.Pointer(n)).LocalsContainerData() - case 24: - // SAFETY: This case only matches kinds allocated as a ConstructorTypeNode, which embeds its Node header at offset zero. - return (*ConstructorTypeNode)(unsafe.Pointer(n)).LocalsContainerData() - case 25: - // SAFETY: This case only matches kinds allocated as a JSDocSignature, which embeds its Node header at offset zero. - return (*JSDocSignature)(unsafe.Pointer(n)).LocalsContainerData() - case 26: - // SAFETY: This case only matches kinds allocated as a SourceFile, which embeds its Node header at offset zero. - return (*SourceFile)(unsafe.Pointer(n)).LocalsContainerData() - case 27: - // SAFETY: This case only matches kinds allocated as a ModuleDeclaration, which embeds its Node header at offset zero. - return (*ModuleDeclaration)(unsafe.Pointer(n)).LocalsContainerData() - default: - return nil + if offset := nodeLocalsContainerDataDispatch[n.Kind]; offset != 0 { + // SAFETY: nodeLocalsContainerDataDispatch stores the byte offset of LocalsContainerBase within the concrete node struct for n.Kind. + return (*LocalsContainerBase)(unsafe.Add(unsafe.Pointer(n), offset)) } + return nil } var nodeFunctionLikeDataDispatch = [kindFlowReduceLabelData + 1]uint8{ - KindFunctionDeclaration: 1, - KindCallSignature: 2, - KindConstructSignature: 3, - KindConstructor: 4, - KindGetAccessor: 5, - KindSetAccessor: 6, - KindIndexSignature: 7, - KindMethodSignature: 8, - KindMethodDeclaration: 9, - KindArrowFunction: 10, - KindFunctionExpression: 11, - KindFunctionType: 12, - KindConstructorType: 13, - KindJSDocSignature: 14, + KindFunctionDeclaration: uint8(unsafe.Offsetof(FunctionDeclaration{}.FunctionLikeBase)), + KindCallSignature: uint8(unsafe.Offsetof(CallSignatureDeclaration{}.FunctionLikeBase)), + KindConstructSignature: uint8(unsafe.Offsetof(ConstructSignatureDeclaration{}.FunctionLikeBase)), + KindConstructor: uint8(unsafe.Offsetof(ConstructorDeclaration{}.FunctionLikeBase)), + KindGetAccessor: uint8(unsafe.Offsetof(GetAccessorDeclaration{}.FunctionLikeBase)), + KindSetAccessor: uint8(unsafe.Offsetof(SetAccessorDeclaration{}.FunctionLikeBase)), + KindIndexSignature: uint8(unsafe.Offsetof(IndexSignatureDeclaration{}.FunctionLikeBase)), + KindMethodSignature: uint8(unsafe.Offsetof(MethodSignatureDeclaration{}.FunctionLikeBase)), + KindMethodDeclaration: uint8(unsafe.Offsetof(MethodDeclaration{}.FunctionLikeBase)), + KindArrowFunction: uint8(unsafe.Offsetof(ArrowFunction{}.FunctionLikeBase)), + KindFunctionExpression: uint8(unsafe.Offsetof(FunctionExpression{}.FunctionLikeBase)), + KindFunctionType: uint8(unsafe.Offsetof(FunctionTypeNode{}.FunctionLikeBase)), + KindConstructorType: uint8(unsafe.Offsetof(ConstructorTypeNode{}.FunctionLikeBase)), + KindJSDocSignature: uint8(unsafe.Offsetof(JSDocSignature{}.FunctionLikeBase)), } func (n *Node) FunctionLikeData() *FunctionLikeBase { - switch nodeFunctionLikeDataDispatch[n.Kind] { - case 1: - // SAFETY: This case only matches kinds allocated as a FunctionDeclaration, which embeds its Node header at offset zero. - return (*FunctionDeclaration)(unsafe.Pointer(n)).FunctionLikeData() - case 2: - // SAFETY: This case only matches kinds allocated as a CallSignatureDeclaration, which embeds its Node header at offset zero. - return (*CallSignatureDeclaration)(unsafe.Pointer(n)).FunctionLikeData() - case 3: - // SAFETY: This case only matches kinds allocated as a ConstructSignatureDeclaration, which embeds its Node header at offset zero. - return (*ConstructSignatureDeclaration)(unsafe.Pointer(n)).FunctionLikeData() - case 4: - // SAFETY: This case only matches kinds allocated as a ConstructorDeclaration, which embeds its Node header at offset zero. - return (*ConstructorDeclaration)(unsafe.Pointer(n)).FunctionLikeData() - case 5: - // SAFETY: This case only matches kinds allocated as a GetAccessorDeclaration, which embeds its Node header at offset zero. - return (*GetAccessorDeclaration)(unsafe.Pointer(n)).FunctionLikeData() - case 6: - // SAFETY: This case only matches kinds allocated as a SetAccessorDeclaration, which embeds its Node header at offset zero. - return (*SetAccessorDeclaration)(unsafe.Pointer(n)).FunctionLikeData() - case 7: - // SAFETY: This case only matches kinds allocated as a IndexSignatureDeclaration, which embeds its Node header at offset zero. - return (*IndexSignatureDeclaration)(unsafe.Pointer(n)).FunctionLikeData() - case 8: - // SAFETY: This case only matches kinds allocated as a MethodSignatureDeclaration, which embeds its Node header at offset zero. - return (*MethodSignatureDeclaration)(unsafe.Pointer(n)).FunctionLikeData() - case 9: - // SAFETY: This case only matches kinds allocated as a MethodDeclaration, which embeds its Node header at offset zero. - return (*MethodDeclaration)(unsafe.Pointer(n)).FunctionLikeData() - case 10: - // SAFETY: This case only matches kinds allocated as a ArrowFunction, which embeds its Node header at offset zero. - return (*ArrowFunction)(unsafe.Pointer(n)).FunctionLikeData() - case 11: - // SAFETY: This case only matches kinds allocated as a FunctionExpression, which embeds its Node header at offset zero. - return (*FunctionExpression)(unsafe.Pointer(n)).FunctionLikeData() - case 12: - // SAFETY: This case only matches kinds allocated as a FunctionTypeNode, which embeds its Node header at offset zero. - return (*FunctionTypeNode)(unsafe.Pointer(n)).FunctionLikeData() - case 13: - // SAFETY: This case only matches kinds allocated as a ConstructorTypeNode, which embeds its Node header at offset zero. - return (*ConstructorTypeNode)(unsafe.Pointer(n)).FunctionLikeData() - case 14: - // SAFETY: This case only matches kinds allocated as a JSDocSignature, which embeds its Node header at offset zero. - return (*JSDocSignature)(unsafe.Pointer(n)).FunctionLikeData() - default: - return nil + if offset := nodeFunctionLikeDataDispatch[n.Kind]; offset != 0 { + // SAFETY: nodeFunctionLikeDataDispatch stores the byte offset of FunctionLikeBase within the concrete node struct for n.Kind. + return (*FunctionLikeBase)(unsafe.Add(unsafe.Pointer(n), offset)) } + return nil +} + +var nodeClassLikeDataDispatch = [kindFlowReduceLabelData + 1]uint8{ + KindClassDeclaration: uint8(unsafe.Offsetof(ClassDeclaration{}.ClassLikeBase)), + KindClassExpression: uint8(unsafe.Offsetof(ClassExpression{}.ClassLikeBase)), } func (n *Node) ClassLikeData() *ClassLikeBase { - switch n.Kind { - case KindClassDeclaration: - // SAFETY: This case only matches kinds allocated as a ClassDeclaration, which embeds its Node header at offset zero. - return (*ClassDeclaration)(unsafe.Pointer(n)).ClassLikeData() - case KindClassExpression: - // SAFETY: This case only matches kinds allocated as a ClassExpression, which embeds its Node header at offset zero. - return (*ClassExpression)(unsafe.Pointer(n)).ClassLikeData() - default: - return nil + if offset := nodeClassLikeDataDispatch[n.Kind]; offset != 0 { + // SAFETY: nodeClassLikeDataDispatch stores the byte offset of ClassLikeBase within the concrete node struct for n.Kind. + return (*ClassLikeBase)(unsafe.Add(unsafe.Pointer(n), offset)) } + return nil } var nodeBodyDataDispatch = [kindFlowReduceLabelData + 1]uint8{ - KindFunctionDeclaration: 1, - KindConstructor: 2, - KindGetAccessor: 3, - KindSetAccessor: 4, - KindMethodDeclaration: 5, - KindArrowFunction: 6, - KindFunctionExpression: 7, - KindModuleDeclaration: 8, + KindFunctionDeclaration: uint8(unsafe.Offsetof(FunctionDeclaration{}.BodyBase)), + KindConstructor: uint8(unsafe.Offsetof(ConstructorDeclaration{}.BodyBase)), + KindGetAccessor: uint8(unsafe.Offsetof(GetAccessorDeclaration{}.BodyBase)), + KindSetAccessor: uint8(unsafe.Offsetof(SetAccessorDeclaration{}.BodyBase)), + KindMethodDeclaration: uint8(unsafe.Offsetof(MethodDeclaration{}.BodyBase)), + KindArrowFunction: uint8(unsafe.Offsetof(ArrowFunction{}.BodyBase)), + KindFunctionExpression: uint8(unsafe.Offsetof(FunctionExpression{}.BodyBase)), + KindModuleDeclaration: uint8(unsafe.Offsetof(ModuleDeclaration{}.BodyBase)), } func (n *Node) BodyData() *BodyBase { - switch nodeBodyDataDispatch[n.Kind] { - case 1: - // SAFETY: This case only matches kinds allocated as a FunctionDeclaration, which embeds its Node header at offset zero. - return (*FunctionDeclaration)(unsafe.Pointer(n)).BodyData() - case 2: - // SAFETY: This case only matches kinds allocated as a ConstructorDeclaration, which embeds its Node header at offset zero. - return (*ConstructorDeclaration)(unsafe.Pointer(n)).BodyData() - case 3: - // SAFETY: This case only matches kinds allocated as a GetAccessorDeclaration, which embeds its Node header at offset zero. - return (*GetAccessorDeclaration)(unsafe.Pointer(n)).BodyData() - case 4: - // SAFETY: This case only matches kinds allocated as a SetAccessorDeclaration, which embeds its Node header at offset zero. - return (*SetAccessorDeclaration)(unsafe.Pointer(n)).BodyData() - case 5: - // SAFETY: This case only matches kinds allocated as a MethodDeclaration, which embeds its Node header at offset zero. - return (*MethodDeclaration)(unsafe.Pointer(n)).BodyData() - case 6: - // SAFETY: This case only matches kinds allocated as a ArrowFunction, which embeds its Node header at offset zero. - return (*ArrowFunction)(unsafe.Pointer(n)).BodyData() - case 7: - // SAFETY: This case only matches kinds allocated as a FunctionExpression, which embeds its Node header at offset zero. - return (*FunctionExpression)(unsafe.Pointer(n)).BodyData() - case 8: - // SAFETY: This case only matches kinds allocated as a ModuleDeclaration, which embeds its Node header at offset zero. - return (*ModuleDeclaration)(unsafe.Pointer(n)).BodyData() - default: - return nil + if offset := nodeBodyDataDispatch[n.Kind]; offset != 0 { + // SAFETY: nodeBodyDataDispatch stores the byte offset of BodyBase within the concrete node struct for n.Kind. + return (*BodyBase)(unsafe.Add(unsafe.Pointer(n), offset)) } + return nil } var nodeLiteralLikeDataDispatch = [kindFlowReduceLabelData + 1]uint8{ - KindStringLiteral: 1, - KindNumericLiteral: 2, - KindBigIntLiteral: 3, - KindRegularExpressionLiteral: 4, - KindNoSubstitutionTemplateLiteral: 5, - KindTemplateHead: 6, - KindTemplateMiddle: 7, - KindTemplateTail: 8, - KindJsxText: 9, + KindStringLiteral: uint8(unsafe.Offsetof(StringLiteral{}.LiteralLikeNodeBase)), + KindNumericLiteral: uint8(unsafe.Offsetof(NumericLiteral{}.LiteralLikeNodeBase)), + KindBigIntLiteral: uint8(unsafe.Offsetof(BigIntLiteral{}.LiteralLikeNodeBase)), + KindRegularExpressionLiteral: uint8(unsafe.Offsetof(RegularExpressionLiteral{}.LiteralLikeNodeBase)), + KindNoSubstitutionTemplateLiteral: uint8(unsafe.Offsetof(NoSubstitutionTemplateLiteral{}.LiteralLikeNodeBase)), + KindTemplateHead: uint8(unsafe.Offsetof(TemplateHead{}.LiteralLikeNodeBase)), + KindTemplateMiddle: uint8(unsafe.Offsetof(TemplateMiddle{}.LiteralLikeNodeBase)), + KindTemplateTail: uint8(unsafe.Offsetof(TemplateTail{}.LiteralLikeNodeBase)), + KindJsxText: uint8(unsafe.Offsetof(JsxText{}.LiteralLikeNodeBase)), } func (n *Node) LiteralLikeData() *LiteralLikeNodeBase { - switch nodeLiteralLikeDataDispatch[n.Kind] { - case 1: - // SAFETY: This case only matches kinds allocated as a StringLiteral, which embeds its Node header at offset zero. - return (*StringLiteral)(unsafe.Pointer(n)).LiteralLikeData() - case 2: - // SAFETY: This case only matches kinds allocated as a NumericLiteral, which embeds its Node header at offset zero. - return (*NumericLiteral)(unsafe.Pointer(n)).LiteralLikeData() - case 3: - // SAFETY: This case only matches kinds allocated as a BigIntLiteral, which embeds its Node header at offset zero. - return (*BigIntLiteral)(unsafe.Pointer(n)).LiteralLikeData() - case 4: - // SAFETY: This case only matches kinds allocated as a RegularExpressionLiteral, which embeds its Node header at offset zero. - return (*RegularExpressionLiteral)(unsafe.Pointer(n)).LiteralLikeData() - case 5: - // SAFETY: This case only matches kinds allocated as a NoSubstitutionTemplateLiteral, which embeds its Node header at offset zero. - return (*NoSubstitutionTemplateLiteral)(unsafe.Pointer(n)).LiteralLikeData() - case 6: - // SAFETY: This case only matches kinds allocated as a TemplateHead, which embeds its Node header at offset zero. - return (*TemplateHead)(unsafe.Pointer(n)).LiteralLikeData() - case 7: - // SAFETY: This case only matches kinds allocated as a TemplateMiddle, which embeds its Node header at offset zero. - return (*TemplateMiddle)(unsafe.Pointer(n)).LiteralLikeData() - case 8: - // SAFETY: This case only matches kinds allocated as a TemplateTail, which embeds its Node header at offset zero. - return (*TemplateTail)(unsafe.Pointer(n)).LiteralLikeData() - case 9: - // SAFETY: This case only matches kinds allocated as a JsxText, which embeds its Node header at offset zero. - return (*JsxText)(unsafe.Pointer(n)).LiteralLikeData() - default: - return nil + if offset := nodeLiteralLikeDataDispatch[n.Kind]; offset != 0 { + // SAFETY: nodeLiteralLikeDataDispatch stores the byte offset of LiteralLikeNodeBase within the concrete node struct for n.Kind. + return (*LiteralLikeNodeBase)(unsafe.Add(unsafe.Pointer(n), offset)) } + return nil +} + +var nodeTemplateLiteralLikeDataDispatch = [kindFlowReduceLabelData + 1]uint8{ + KindNoSubstitutionTemplateLiteral: uint8(unsafe.Offsetof(NoSubstitutionTemplateLiteral{}.TemplateLiteralLikeNodeBase)), + KindTemplateHead: uint8(unsafe.Offsetof(TemplateHead{}.TemplateLiteralLikeNodeBase)), + KindTemplateMiddle: uint8(unsafe.Offsetof(TemplateMiddle{}.TemplateLiteralLikeNodeBase)), + KindTemplateTail: uint8(unsafe.Offsetof(TemplateTail{}.TemplateLiteralLikeNodeBase)), } func (n *Node) TemplateLiteralLikeData() *TemplateLiteralLikeNodeBase { - switch n.Kind { - case KindNoSubstitutionTemplateLiteral: - // SAFETY: This case only matches kinds allocated as a NoSubstitutionTemplateLiteral, which embeds its Node header at offset zero. - return (*NoSubstitutionTemplateLiteral)(unsafe.Pointer(n)).TemplateLiteralLikeData() - case KindTemplateHead: - // SAFETY: This case only matches kinds allocated as a TemplateHead, which embeds its Node header at offset zero. - return (*TemplateHead)(unsafe.Pointer(n)).TemplateLiteralLikeData() - case KindTemplateMiddle: - // SAFETY: This case only matches kinds allocated as a TemplateMiddle, which embeds its Node header at offset zero. - return (*TemplateMiddle)(unsafe.Pointer(n)).TemplateLiteralLikeData() - case KindTemplateTail: - // SAFETY: This case only matches kinds allocated as a TemplateTail, which embeds its Node header at offset zero. - return (*TemplateTail)(unsafe.Pointer(n)).TemplateLiteralLikeData() - default: - return nil + if offset := nodeTemplateLiteralLikeDataDispatch[n.Kind]; offset != 0 { + // SAFETY: nodeTemplateLiteralLikeDataDispatch stores the byte offset of TemplateLiteralLikeNodeBase within the concrete node struct for n.Kind. + return (*TemplateLiteralLikeNodeBase)(unsafe.Add(unsafe.Pointer(n), offset)) } + return nil } // ────────────────────────────────────────────────────────────────────── @@ -12822,412 +12335,107 @@ func (n *Node) TemplateLiteralLikeData() *TemplateLiteralLikeNodeBase { // ────────────────────────────────────────────────────────────────────── var nodeNameDispatch = [kindFlowReduceLabelData + 1]uint8{ - KindVariableDeclaration: 1, - KindParameter: 2, - KindBindingElement: 3, - KindFunctionDeclaration: 4, - KindClassDeclaration: 5, - KindClassExpression: 6, - KindInterfaceDeclaration: 7, - KindTypeAliasDeclaration: 8, - KindJSTypeAliasDeclaration: 8, - KindEnumMember: 9, - KindEnumDeclaration: 10, - KindNamespaceImport: 11, - KindNamespaceExportDeclaration: 12, - KindNamespaceExport: 13, - KindExportSpecifier: 14, - KindGetAccessor: 15, - KindSetAccessor: 16, - KindMethodSignature: 17, - KindMethodDeclaration: 18, - KindPropertySignature: 19, - KindPropertyDeclaration: 20, - KindFunctionExpression: 21, - KindPropertyAccessExpression: 22, - KindMetaProperty: 23, - KindPropertyAssignment: 24, - KindShorthandPropertyAssignment: 25, - KindImportAttribute: 26, - KindNamedTupleMember: 27, - KindJsxNamespacedName: 28, - KindJsxAttribute: 29, - KindJSDocCallbackTag: 30, - KindJSDocTypedefTag: 31, - KindJSDocNameReference: 32, - KindModuleDeclaration: 33, - KindImportEqualsDeclaration: 34, - KindImportClause: 35, - KindImportSpecifier: 36, - KindJSDocLink: 37, - KindJSDocLinkPlain: 38, - KindJSDocLinkCode: 39, - KindTypeParameter: 40, - KindJSDocParameterTag: 41, - KindJSDocPropertyTag: 41, + KindVariableDeclaration: uint8(unsafe.Offsetof(VariableDeclaration{}.name)), + KindParameter: uint8(unsafe.Offsetof(ParameterDeclaration{}.name)), + KindBindingElement: uint8(unsafe.Offsetof(BindingElement{}.name)), + KindFunctionDeclaration: uint8(unsafe.Offsetof(FunctionDeclaration{}.name)), + KindClassDeclaration: uint8(unsafe.Offsetof(ClassDeclaration{}.name)), + KindClassExpression: uint8(unsafe.Offsetof(ClassExpression{}.name)), + KindInterfaceDeclaration: uint8(unsafe.Offsetof(InterfaceDeclaration{}.name)), + KindTypeAliasDeclaration: uint8(unsafe.Offsetof(TypeAliasDeclaration{}.name)), + KindJSTypeAliasDeclaration: uint8(unsafe.Offsetof(TypeAliasDeclaration{}.name)), + KindEnumMember: uint8(unsafe.Offsetof(EnumMember{}.name)), + KindEnumDeclaration: uint8(unsafe.Offsetof(EnumDeclaration{}.name)), + KindNamespaceImport: uint8(unsafe.Offsetof(NamespaceImport{}.name)), + KindNamespaceExportDeclaration: uint8(unsafe.Offsetof(NamespaceExportDeclaration{}.name)), + KindNamespaceExport: uint8(unsafe.Offsetof(NamespaceExport{}.name)), + KindExportSpecifier: uint8(unsafe.Offsetof(ExportSpecifier{}.name)), + KindGetAccessor: uint8(unsafe.Offsetof(GetAccessorDeclaration{}.name)), + KindSetAccessor: uint8(unsafe.Offsetof(SetAccessorDeclaration{}.name)), + KindMethodSignature: uint8(unsafe.Offsetof(MethodSignatureDeclaration{}.name)), + KindMethodDeclaration: uint8(unsafe.Offsetof(MethodDeclaration{}.name)), + KindPropertySignature: uint8(unsafe.Offsetof(PropertySignatureDeclaration{}.name)), + KindPropertyDeclaration: uint8(unsafe.Offsetof(PropertyDeclaration{}.name)), + KindFunctionExpression: uint8(unsafe.Offsetof(FunctionExpression{}.name)), + KindPropertyAccessExpression: uint8(unsafe.Offsetof(PropertyAccessExpression{}.name)), + KindMetaProperty: uint8(unsafe.Offsetof(MetaProperty{}.name)), + KindPropertyAssignment: uint8(unsafe.Offsetof(PropertyAssignment{}.name)), + KindShorthandPropertyAssignment: uint8(unsafe.Offsetof(ShorthandPropertyAssignment{}.name)), + KindImportAttribute: uint8(unsafe.Offsetof(ImportAttribute{}.name)), + KindNamedTupleMember: uint8(unsafe.Offsetof(NamedTupleMember{}.name)), + KindJsxNamespacedName: uint8(unsafe.Offsetof(JsxNamespacedName{}.name)), + KindJsxAttribute: uint8(unsafe.Offsetof(JsxAttribute{}.name)), + KindJSDocCallbackTag: uint8(unsafe.Offsetof(JSDocCallbackTag{}.name)), + KindJSDocTypedefTag: uint8(unsafe.Offsetof(JSDocTypedefTag{}.name)), + KindJSDocNameReference: uint8(unsafe.Offsetof(JSDocNameReference{}.name)), + KindModuleDeclaration: uint8(unsafe.Offsetof(ModuleDeclaration{}.name)), + KindImportEqualsDeclaration: uint8(unsafe.Offsetof(ImportEqualsDeclaration{}.name)), + KindImportClause: uint8(unsafe.Offsetof(ImportClause{}.name)), + KindImportSpecifier: uint8(unsafe.Offsetof(ImportSpecifier{}.name)), + KindJSDocLink: uint8(unsafe.Offsetof(JSDocLink{}.name)), + KindJSDocLinkPlain: uint8(unsafe.Offsetof(JSDocLinkPlain{}.name)), + KindJSDocLinkCode: uint8(unsafe.Offsetof(JSDocLinkCode{}.name)), + KindTypeParameter: uint8(unsafe.Offsetof(TypeParameterDeclaration{}.name)), + KindJSDocParameterTag: uint8(unsafe.Offsetof(JSDocParameterOrPropertyTag{}.name)), + KindJSDocPropertyTag: uint8(unsafe.Offsetof(JSDocParameterOrPropertyTag{}.name)), } func (n *Node) Name() *DeclarationName { - switch nodeNameDispatch[n.Kind] { - case 1: - // SAFETY: This case only matches kinds allocated as a VariableDeclaration, which embeds its Node header at offset zero. - return (*VariableDeclaration)(unsafe.Pointer(n)).Name() - case 2: - // SAFETY: This case only matches kinds allocated as a ParameterDeclaration, which embeds its Node header at offset zero. - return (*ParameterDeclaration)(unsafe.Pointer(n)).Name() - case 3: - // SAFETY: This case only matches kinds allocated as a BindingElement, which embeds its Node header at offset zero. - return (*BindingElement)(unsafe.Pointer(n)).Name() - case 4: - // SAFETY: This case only matches kinds allocated as a FunctionDeclaration, which embeds its Node header at offset zero. - return (*FunctionDeclaration)(unsafe.Pointer(n)).Name() - case 5: - // SAFETY: This case only matches kinds allocated as a ClassDeclaration, which embeds its Node header at offset zero. - return (*ClassDeclaration)(unsafe.Pointer(n)).Name() - case 6: - // SAFETY: This case only matches kinds allocated as a ClassExpression, which embeds its Node header at offset zero. - return (*ClassExpression)(unsafe.Pointer(n)).Name() - case 7: - // SAFETY: This case only matches kinds allocated as a InterfaceDeclaration, which embeds its Node header at offset zero. - return (*InterfaceDeclaration)(unsafe.Pointer(n)).Name() - case 8: - // SAFETY: This case only matches kinds allocated as a TypeAliasDeclaration, which embeds its Node header at offset zero. - return (*TypeAliasDeclaration)(unsafe.Pointer(n)).Name() - case 9: - // SAFETY: This case only matches kinds allocated as a EnumMember, which embeds its Node header at offset zero. - return (*EnumMember)(unsafe.Pointer(n)).Name() - case 10: - // SAFETY: This case only matches kinds allocated as a EnumDeclaration, which embeds its Node header at offset zero. - return (*EnumDeclaration)(unsafe.Pointer(n)).Name() - case 11: - // SAFETY: This case only matches kinds allocated as a NamespaceImport, which embeds its Node header at offset zero. - return (*NamespaceImport)(unsafe.Pointer(n)).Name() - case 12: - // SAFETY: This case only matches kinds allocated as a NamespaceExportDeclaration, which embeds its Node header at offset zero. - return (*NamespaceExportDeclaration)(unsafe.Pointer(n)).Name() - case 13: - // SAFETY: This case only matches kinds allocated as a NamespaceExport, which embeds its Node header at offset zero. - return (*NamespaceExport)(unsafe.Pointer(n)).Name() - case 14: - // SAFETY: This case only matches kinds allocated as a ExportSpecifier, which embeds its Node header at offset zero. - return (*ExportSpecifier)(unsafe.Pointer(n)).Name() - case 15: - // SAFETY: This case only matches kinds allocated as a GetAccessorDeclaration, which embeds its Node header at offset zero. - return (*GetAccessorDeclaration)(unsafe.Pointer(n)).Name() - case 16: - // SAFETY: This case only matches kinds allocated as a SetAccessorDeclaration, which embeds its Node header at offset zero. - return (*SetAccessorDeclaration)(unsafe.Pointer(n)).Name() - case 17: - // SAFETY: This case only matches kinds allocated as a MethodSignatureDeclaration, which embeds its Node header at offset zero. - return (*MethodSignatureDeclaration)(unsafe.Pointer(n)).Name() - case 18: - // SAFETY: This case only matches kinds allocated as a MethodDeclaration, which embeds its Node header at offset zero. - return (*MethodDeclaration)(unsafe.Pointer(n)).Name() - case 19: - // SAFETY: This case only matches kinds allocated as a PropertySignatureDeclaration, which embeds its Node header at offset zero. - return (*PropertySignatureDeclaration)(unsafe.Pointer(n)).Name() - case 20: - // SAFETY: This case only matches kinds allocated as a PropertyDeclaration, which embeds its Node header at offset zero. - return (*PropertyDeclaration)(unsafe.Pointer(n)).Name() - case 21: - // SAFETY: This case only matches kinds allocated as a FunctionExpression, which embeds its Node header at offset zero. - return (*FunctionExpression)(unsafe.Pointer(n)).Name() - case 22: - // SAFETY: This case only matches kinds allocated as a PropertyAccessExpression, which embeds its Node header at offset zero. - return (*PropertyAccessExpression)(unsafe.Pointer(n)).Name() - case 23: - // SAFETY: This case only matches kinds allocated as a MetaProperty, which embeds its Node header at offset zero. - return (*MetaProperty)(unsafe.Pointer(n)).Name() - case 24: - // SAFETY: This case only matches kinds allocated as a PropertyAssignment, which embeds its Node header at offset zero. - return (*PropertyAssignment)(unsafe.Pointer(n)).Name() - case 25: - // SAFETY: This case only matches kinds allocated as a ShorthandPropertyAssignment, which embeds its Node header at offset zero. - return (*ShorthandPropertyAssignment)(unsafe.Pointer(n)).Name() - case 26: - // SAFETY: This case only matches kinds allocated as a ImportAttribute, which embeds its Node header at offset zero. - return (*ImportAttribute)(unsafe.Pointer(n)).Name() - case 27: - // SAFETY: This case only matches kinds allocated as a NamedTupleMember, which embeds its Node header at offset zero. - return (*NamedTupleMember)(unsafe.Pointer(n)).Name() - case 28: - // SAFETY: This case only matches kinds allocated as a JsxNamespacedName, which embeds its Node header at offset zero. - return (*JsxNamespacedName)(unsafe.Pointer(n)).Name() - case 29: - // SAFETY: This case only matches kinds allocated as a JsxAttribute, which embeds its Node header at offset zero. - return (*JsxAttribute)(unsafe.Pointer(n)).Name() - case 30: - // SAFETY: This case only matches kinds allocated as a JSDocCallbackTag, which embeds its Node header at offset zero. - return (*JSDocCallbackTag)(unsafe.Pointer(n)).Name() - case 31: - // SAFETY: This case only matches kinds allocated as a JSDocTypedefTag, which embeds its Node header at offset zero. - return (*JSDocTypedefTag)(unsafe.Pointer(n)).Name() - case 32: - // SAFETY: This case only matches kinds allocated as a JSDocNameReference, which embeds its Node header at offset zero. - return (*JSDocNameReference)(unsafe.Pointer(n)).Name() - case 33: - // SAFETY: This case only matches kinds allocated as a ModuleDeclaration, which embeds its Node header at offset zero. - return (*ModuleDeclaration)(unsafe.Pointer(n)).Name() - case 34: - // SAFETY: This case only matches kinds allocated as a ImportEqualsDeclaration, which embeds its Node header at offset zero. - return (*ImportEqualsDeclaration)(unsafe.Pointer(n)).Name() - case 35: - // SAFETY: This case only matches kinds allocated as a ImportClause, which embeds its Node header at offset zero. - return (*ImportClause)(unsafe.Pointer(n)).Name() - case 36: - // SAFETY: This case only matches kinds allocated as a ImportSpecifier, which embeds its Node header at offset zero. - return (*ImportSpecifier)(unsafe.Pointer(n)).Name() - case 37: - // SAFETY: This case only matches kinds allocated as a JSDocLink, which embeds its Node header at offset zero. - return (*JSDocLink)(unsafe.Pointer(n)).Name() - case 38: - // SAFETY: This case only matches kinds allocated as a JSDocLinkPlain, which embeds its Node header at offset zero. - return (*JSDocLinkPlain)(unsafe.Pointer(n)).Name() - case 39: - // SAFETY: This case only matches kinds allocated as a JSDocLinkCode, which embeds its Node header at offset zero. - return (*JSDocLinkCode)(unsafe.Pointer(n)).Name() - case 40: - // SAFETY: This case only matches kinds allocated as a TypeParameterDeclaration, which embeds its Node header at offset zero. - return (*TypeParameterDeclaration)(unsafe.Pointer(n)).Name() - case 41: - // SAFETY: This case only matches kinds allocated as a JSDocParameterOrPropertyTag, which embeds its Node header at offset zero. - return (*JSDocParameterOrPropertyTag)(unsafe.Pointer(n)).Name() - default: - return nil + if offset := nodeNameDispatch[n.Kind]; offset != 0 { + // SAFETY: nodeNameDispatch stores the byte offset of name within the concrete node struct for n.Kind. + return *(**DeclarationName)(unsafe.Add(unsafe.Pointer(n), offset)) } + return nil } var nodeModifiersDispatch = [kindFlowReduceLabelData + 1]uint8{ - KindVariableStatement: 1, - KindParameter: 2, - KindMissingDeclaration: 3, - KindFunctionDeclaration: 4, - KindClassDeclaration: 5, - KindClassExpression: 6, - KindInterfaceDeclaration: 7, - KindTypeAliasDeclaration: 8, - KindJSTypeAliasDeclaration: 8, - KindEnumDeclaration: 9, - KindImportDeclaration: 10, - KindJSImportDeclaration: 10, - KindExportAssignment: 11, - KindNamespaceExportDeclaration: 12, - KindConstructor: 13, - KindGetAccessor: 14, - KindSetAccessor: 15, - KindIndexSignature: 16, - KindMethodSignature: 17, - KindMethodDeclaration: 18, - KindPropertySignature: 19, - KindPropertyDeclaration: 20, - KindClassStaticBlockDeclaration: 21, - KindBinaryExpression: 22, - KindArrowFunction: 23, - KindFunctionExpression: 24, - KindPropertyAssignment: 25, - KindShorthandPropertyAssignment: 26, - KindConstructorType: 27, - KindModuleDeclaration: 28, - KindImportEqualsDeclaration: 29, - KindExportDeclaration: 30, - KindTypeParameter: 31, + KindVariableStatement: uint8(unsafe.Offsetof(VariableStatement{}.modifiers)), + KindParameter: uint8(unsafe.Offsetof(ParameterDeclaration{}.modifiers)), + KindMissingDeclaration: uint8(unsafe.Offsetof(MissingDeclaration{}.modifiers)), + KindFunctionDeclaration: uint8(unsafe.Offsetof(FunctionDeclaration{}.modifiers)), + KindClassDeclaration: uint8(unsafe.Offsetof(ClassDeclaration{}.modifiers)), + KindClassExpression: uint8(unsafe.Offsetof(ClassExpression{}.modifiers)), + KindInterfaceDeclaration: uint8(unsafe.Offsetof(InterfaceDeclaration{}.modifiers)), + KindTypeAliasDeclaration: uint8(unsafe.Offsetof(TypeAliasDeclaration{}.modifiers)), + KindJSTypeAliasDeclaration: uint8(unsafe.Offsetof(TypeAliasDeclaration{}.modifiers)), + KindEnumDeclaration: uint8(unsafe.Offsetof(EnumDeclaration{}.modifiers)), + KindImportDeclaration: uint8(unsafe.Offsetof(ImportDeclaration{}.modifiers)), + KindJSImportDeclaration: uint8(unsafe.Offsetof(ImportDeclaration{}.modifiers)), + KindExportAssignment: uint8(unsafe.Offsetof(ExportAssignment{}.modifiers)), + KindNamespaceExportDeclaration: uint8(unsafe.Offsetof(NamespaceExportDeclaration{}.modifiers)), + KindConstructor: uint8(unsafe.Offsetof(ConstructorDeclaration{}.modifiers)), + KindGetAccessor: uint8(unsafe.Offsetof(GetAccessorDeclaration{}.modifiers)), + KindSetAccessor: uint8(unsafe.Offsetof(SetAccessorDeclaration{}.modifiers)), + KindIndexSignature: uint8(unsafe.Offsetof(IndexSignatureDeclaration{}.modifiers)), + KindMethodSignature: uint8(unsafe.Offsetof(MethodSignatureDeclaration{}.modifiers)), + KindMethodDeclaration: uint8(unsafe.Offsetof(MethodDeclaration{}.modifiers)), + KindPropertySignature: uint8(unsafe.Offsetof(PropertySignatureDeclaration{}.modifiers)), + KindPropertyDeclaration: uint8(unsafe.Offsetof(PropertyDeclaration{}.modifiers)), + KindClassStaticBlockDeclaration: uint8(unsafe.Offsetof(ClassStaticBlockDeclaration{}.modifiers)), + KindBinaryExpression: uint8(unsafe.Offsetof(BinaryExpression{}.modifiers)), + KindArrowFunction: uint8(unsafe.Offsetof(ArrowFunction{}.modifiers)), + KindFunctionExpression: uint8(unsafe.Offsetof(FunctionExpression{}.modifiers)), + KindPropertyAssignment: uint8(unsafe.Offsetof(PropertyAssignment{}.modifiers)), + KindShorthandPropertyAssignment: uint8(unsafe.Offsetof(ShorthandPropertyAssignment{}.modifiers)), + KindConstructorType: uint8(unsafe.Offsetof(ConstructorTypeNode{}.modifiers)), + KindModuleDeclaration: uint8(unsafe.Offsetof(ModuleDeclaration{}.modifiers)), + KindImportEqualsDeclaration: uint8(unsafe.Offsetof(ImportEqualsDeclaration{}.modifiers)), + KindExportDeclaration: uint8(unsafe.Offsetof(ExportDeclaration{}.modifiers)), + KindTypeParameter: uint8(unsafe.Offsetof(TypeParameterDeclaration{}.modifiers)), } func (n *Node) Modifiers() *ModifierList { - switch nodeModifiersDispatch[n.Kind] { - case 1: - // SAFETY: This case only matches kinds allocated as a VariableStatement, which embeds its Node header at offset zero. - return (*VariableStatement)(unsafe.Pointer(n)).Modifiers() - case 2: - // SAFETY: This case only matches kinds allocated as a ParameterDeclaration, which embeds its Node header at offset zero. - return (*ParameterDeclaration)(unsafe.Pointer(n)).Modifiers() - case 3: - // SAFETY: This case only matches kinds allocated as a MissingDeclaration, which embeds its Node header at offset zero. - return (*MissingDeclaration)(unsafe.Pointer(n)).Modifiers() - case 4: - // SAFETY: This case only matches kinds allocated as a FunctionDeclaration, which embeds its Node header at offset zero. - return (*FunctionDeclaration)(unsafe.Pointer(n)).Modifiers() - case 5: - // SAFETY: This case only matches kinds allocated as a ClassDeclaration, which embeds its Node header at offset zero. - return (*ClassDeclaration)(unsafe.Pointer(n)).Modifiers() - case 6: - // SAFETY: This case only matches kinds allocated as a ClassExpression, which embeds its Node header at offset zero. - return (*ClassExpression)(unsafe.Pointer(n)).Modifiers() - case 7: - // SAFETY: This case only matches kinds allocated as a InterfaceDeclaration, which embeds its Node header at offset zero. - return (*InterfaceDeclaration)(unsafe.Pointer(n)).Modifiers() - case 8: - // SAFETY: This case only matches kinds allocated as a TypeAliasDeclaration, which embeds its Node header at offset zero. - return (*TypeAliasDeclaration)(unsafe.Pointer(n)).Modifiers() - case 9: - // SAFETY: This case only matches kinds allocated as a EnumDeclaration, which embeds its Node header at offset zero. - return (*EnumDeclaration)(unsafe.Pointer(n)).Modifiers() - case 10: - // SAFETY: This case only matches kinds allocated as a ImportDeclaration, which embeds its Node header at offset zero. - return (*ImportDeclaration)(unsafe.Pointer(n)).Modifiers() - case 11: - // SAFETY: This case only matches kinds allocated as a ExportAssignment, which embeds its Node header at offset zero. - return (*ExportAssignment)(unsafe.Pointer(n)).Modifiers() - case 12: - // SAFETY: This case only matches kinds allocated as a NamespaceExportDeclaration, which embeds its Node header at offset zero. - return (*NamespaceExportDeclaration)(unsafe.Pointer(n)).Modifiers() - case 13: - // SAFETY: This case only matches kinds allocated as a ConstructorDeclaration, which embeds its Node header at offset zero. - return (*ConstructorDeclaration)(unsafe.Pointer(n)).Modifiers() - case 14: - // SAFETY: This case only matches kinds allocated as a GetAccessorDeclaration, which embeds its Node header at offset zero. - return (*GetAccessorDeclaration)(unsafe.Pointer(n)).Modifiers() - case 15: - // SAFETY: This case only matches kinds allocated as a SetAccessorDeclaration, which embeds its Node header at offset zero. - return (*SetAccessorDeclaration)(unsafe.Pointer(n)).Modifiers() - case 16: - // SAFETY: This case only matches kinds allocated as a IndexSignatureDeclaration, which embeds its Node header at offset zero. - return (*IndexSignatureDeclaration)(unsafe.Pointer(n)).Modifiers() - case 17: - // SAFETY: This case only matches kinds allocated as a MethodSignatureDeclaration, which embeds its Node header at offset zero. - return (*MethodSignatureDeclaration)(unsafe.Pointer(n)).Modifiers() - case 18: - // SAFETY: This case only matches kinds allocated as a MethodDeclaration, which embeds its Node header at offset zero. - return (*MethodDeclaration)(unsafe.Pointer(n)).Modifiers() - case 19: - // SAFETY: This case only matches kinds allocated as a PropertySignatureDeclaration, which embeds its Node header at offset zero. - return (*PropertySignatureDeclaration)(unsafe.Pointer(n)).Modifiers() - case 20: - // SAFETY: This case only matches kinds allocated as a PropertyDeclaration, which embeds its Node header at offset zero. - return (*PropertyDeclaration)(unsafe.Pointer(n)).Modifiers() - case 21: - // SAFETY: This case only matches kinds allocated as a ClassStaticBlockDeclaration, which embeds its Node header at offset zero. - return (*ClassStaticBlockDeclaration)(unsafe.Pointer(n)).Modifiers() - case 22: - // SAFETY: This case only matches kinds allocated as a BinaryExpression, which embeds its Node header at offset zero. - return (*BinaryExpression)(unsafe.Pointer(n)).Modifiers() - case 23: - // SAFETY: This case only matches kinds allocated as a ArrowFunction, which embeds its Node header at offset zero. - return (*ArrowFunction)(unsafe.Pointer(n)).Modifiers() - case 24: - // SAFETY: This case only matches kinds allocated as a FunctionExpression, which embeds its Node header at offset zero. - return (*FunctionExpression)(unsafe.Pointer(n)).Modifiers() - case 25: - // SAFETY: This case only matches kinds allocated as a PropertyAssignment, which embeds its Node header at offset zero. - return (*PropertyAssignment)(unsafe.Pointer(n)).Modifiers() - case 26: - // SAFETY: This case only matches kinds allocated as a ShorthandPropertyAssignment, which embeds its Node header at offset zero. - return (*ShorthandPropertyAssignment)(unsafe.Pointer(n)).Modifiers() - case 27: - // SAFETY: This case only matches kinds allocated as a ConstructorTypeNode, which embeds its Node header at offset zero. - return (*ConstructorTypeNode)(unsafe.Pointer(n)).Modifiers() - case 28: - // SAFETY: This case only matches kinds allocated as a ModuleDeclaration, which embeds its Node header at offset zero. - return (*ModuleDeclaration)(unsafe.Pointer(n)).Modifiers() - case 29: - // SAFETY: This case only matches kinds allocated as a ImportEqualsDeclaration, which embeds its Node header at offset zero. - return (*ImportEqualsDeclaration)(unsafe.Pointer(n)).Modifiers() - case 30: - // SAFETY: This case only matches kinds allocated as a ExportDeclaration, which embeds its Node header at offset zero. - return (*ExportDeclaration)(unsafe.Pointer(n)).Modifiers() - case 31: - // SAFETY: This case only matches kinds allocated as a TypeParameterDeclaration, which embeds its Node header at offset zero. - return (*TypeParameterDeclaration)(unsafe.Pointer(n)).Modifiers() - default: - return nil + if offset := nodeModifiersDispatch[n.Kind]; offset != 0 { + // SAFETY: nodeModifiersDispatch stores the byte offset of modifiers within the concrete node struct for n.Kind. + return *(**ModifierList)(unsafe.Add(unsafe.Pointer(n), offset)) } + return nil } func (n *MutableNode) SetModifiers(modifiers *ModifierList) { - switch n.Kind { - case KindVariableStatement: - // SAFETY: This case only matches kinds allocated as a VariableStatement, which embeds its Node header at offset zero. - (*VariableStatement)(unsafe.Pointer(n)).setModifiers(modifiers) - case KindParameter: - // SAFETY: This case only matches kinds allocated as a ParameterDeclaration, which embeds its Node header at offset zero. - (*ParameterDeclaration)(unsafe.Pointer(n)).setModifiers(modifiers) - case KindMissingDeclaration: - // SAFETY: This case only matches kinds allocated as a MissingDeclaration, which embeds its Node header at offset zero. - (*MissingDeclaration)(unsafe.Pointer(n)).setModifiers(modifiers) - case KindFunctionDeclaration: - // SAFETY: This case only matches kinds allocated as a FunctionDeclaration, which embeds its Node header at offset zero. - (*FunctionDeclaration)(unsafe.Pointer(n)).setModifiers(modifiers) - case KindClassDeclaration: - // SAFETY: This case only matches kinds allocated as a ClassDeclaration, which embeds its Node header at offset zero. - (*ClassDeclaration)(unsafe.Pointer(n)).setModifiers(modifiers) - case KindClassExpression: - // SAFETY: This case only matches kinds allocated as a ClassExpression, which embeds its Node header at offset zero. - (*ClassExpression)(unsafe.Pointer(n)).setModifiers(modifiers) - case KindInterfaceDeclaration: - // SAFETY: This case only matches kinds allocated as a InterfaceDeclaration, which embeds its Node header at offset zero. - (*InterfaceDeclaration)(unsafe.Pointer(n)).setModifiers(modifiers) - case KindTypeAliasDeclaration, KindJSTypeAliasDeclaration: - // SAFETY: This case only matches kinds allocated as a TypeAliasDeclaration, which embeds its Node header at offset zero. - (*TypeAliasDeclaration)(unsafe.Pointer(n)).setModifiers(modifiers) - case KindEnumDeclaration: - // SAFETY: This case only matches kinds allocated as a EnumDeclaration, which embeds its Node header at offset zero. - (*EnumDeclaration)(unsafe.Pointer(n)).setModifiers(modifiers) - case KindImportDeclaration, KindJSImportDeclaration: - // SAFETY: This case only matches kinds allocated as a ImportDeclaration, which embeds its Node header at offset zero. - (*ImportDeclaration)(unsafe.Pointer(n)).setModifiers(modifiers) - case KindExportAssignment: - // SAFETY: This case only matches kinds allocated as a ExportAssignment, which embeds its Node header at offset zero. - (*ExportAssignment)(unsafe.Pointer(n)).setModifiers(modifiers) - case KindNamespaceExportDeclaration: - // SAFETY: This case only matches kinds allocated as a NamespaceExportDeclaration, which embeds its Node header at offset zero. - (*NamespaceExportDeclaration)(unsafe.Pointer(n)).setModifiers(modifiers) - case KindConstructor: - // SAFETY: This case only matches kinds allocated as a ConstructorDeclaration, which embeds its Node header at offset zero. - (*ConstructorDeclaration)(unsafe.Pointer(n)).setModifiers(modifiers) - case KindGetAccessor: - // SAFETY: This case only matches kinds allocated as a GetAccessorDeclaration, which embeds its Node header at offset zero. - (*GetAccessorDeclaration)(unsafe.Pointer(n)).setModifiers(modifiers) - case KindSetAccessor: - // SAFETY: This case only matches kinds allocated as a SetAccessorDeclaration, which embeds its Node header at offset zero. - (*SetAccessorDeclaration)(unsafe.Pointer(n)).setModifiers(modifiers) - case KindIndexSignature: - // SAFETY: This case only matches kinds allocated as a IndexSignatureDeclaration, which embeds its Node header at offset zero. - (*IndexSignatureDeclaration)(unsafe.Pointer(n)).setModifiers(modifiers) - case KindMethodSignature: - // SAFETY: This case only matches kinds allocated as a MethodSignatureDeclaration, which embeds its Node header at offset zero. - (*MethodSignatureDeclaration)(unsafe.Pointer(n)).setModifiers(modifiers) - case KindMethodDeclaration: - // SAFETY: This case only matches kinds allocated as a MethodDeclaration, which embeds its Node header at offset zero. - (*MethodDeclaration)(unsafe.Pointer(n)).setModifiers(modifiers) - case KindPropertySignature: - // SAFETY: This case only matches kinds allocated as a PropertySignatureDeclaration, which embeds its Node header at offset zero. - (*PropertySignatureDeclaration)(unsafe.Pointer(n)).setModifiers(modifiers) - case KindPropertyDeclaration: - // SAFETY: This case only matches kinds allocated as a PropertyDeclaration, which embeds its Node header at offset zero. - (*PropertyDeclaration)(unsafe.Pointer(n)).setModifiers(modifiers) - case KindClassStaticBlockDeclaration: - // SAFETY: This case only matches kinds allocated as a ClassStaticBlockDeclaration, which embeds its Node header at offset zero. - (*ClassStaticBlockDeclaration)(unsafe.Pointer(n)).setModifiers(modifiers) - case KindBinaryExpression: - // SAFETY: This case only matches kinds allocated as a BinaryExpression, which embeds its Node header at offset zero. - (*BinaryExpression)(unsafe.Pointer(n)).setModifiers(modifiers) - case KindArrowFunction: - // SAFETY: This case only matches kinds allocated as a ArrowFunction, which embeds its Node header at offset zero. - (*ArrowFunction)(unsafe.Pointer(n)).setModifiers(modifiers) - case KindFunctionExpression: - // SAFETY: This case only matches kinds allocated as a FunctionExpression, which embeds its Node header at offset zero. - (*FunctionExpression)(unsafe.Pointer(n)).setModifiers(modifiers) - case KindPropertyAssignment: - // SAFETY: This case only matches kinds allocated as a PropertyAssignment, which embeds its Node header at offset zero. - (*PropertyAssignment)(unsafe.Pointer(n)).setModifiers(modifiers) - case KindShorthandPropertyAssignment: - // SAFETY: This case only matches kinds allocated as a ShorthandPropertyAssignment, which embeds its Node header at offset zero. - (*ShorthandPropertyAssignment)(unsafe.Pointer(n)).setModifiers(modifiers) - case KindConstructorType: - // SAFETY: This case only matches kinds allocated as a ConstructorTypeNode, which embeds its Node header at offset zero. - (*ConstructorTypeNode)(unsafe.Pointer(n)).setModifiers(modifiers) - case KindModuleDeclaration: - // SAFETY: This case only matches kinds allocated as a ModuleDeclaration, which embeds its Node header at offset zero. - (*ModuleDeclaration)(unsafe.Pointer(n)).setModifiers(modifiers) - case KindImportEqualsDeclaration: - // SAFETY: This case only matches kinds allocated as a ImportEqualsDeclaration, which embeds its Node header at offset zero. - (*ImportEqualsDeclaration)(unsafe.Pointer(n)).setModifiers(modifiers) - case KindExportDeclaration: - // SAFETY: This case only matches kinds allocated as a ExportDeclaration, which embeds its Node header at offset zero. - (*ExportDeclaration)(unsafe.Pointer(n)).setModifiers(modifiers) - case KindTypeParameter: - // SAFETY: This case only matches kinds allocated as a TypeParameterDeclaration, which embeds its Node header at offset zero. - (*TypeParameterDeclaration)(unsafe.Pointer(n)).setModifiers(modifiers) + if offset := nodeModifiersDispatch[n.Kind]; offset != 0 { + // SAFETY: nodeModifiersDispatch stores the byte offset of modifiers within the concrete node struct for n.Kind. + *(**ModifierList)(unsafe.Add(unsafe.Pointer(n), offset)) = modifiers } } diff --git a/tsc/internal/ast/flow.go b/tsc/internal/ast/flow.go index 43a4bc2f0dfea..a37a4c1160773 100644 --- a/tsc/internal/ast/flow.go +++ b/tsc/internal/ast/flow.go @@ -27,10 +27,19 @@ const ( // FlowNode type FlowNode struct { - Flags FlowFlags - Node *Node // Associated AST node - Antecedent *FlowNode // Antecedent for all but FlowLabel - Antecedents *FlowList // Linked list of antecedents for FlowLabel + Flags FlowFlags + Node *Node // Associated AST node + Antecedent *FlowNode // Antecedent for all but FlowLabel; for FlowLabel, stores *FlowList +} + +func (f *FlowNode) Antecedents() *FlowList { + // SAFETY: On FlowLabel nodes, Antecedent stores a *FlowList pointer. + return (*FlowList)(unsafe.Pointer(f.Antecedent)) +} + +func (f *FlowNode) SetAntecedents(list *FlowList) { + // SAFETY: On FlowLabel nodes, Antecedent stores a *FlowList pointer. + f.Antecedent = (*FlowNode)(unsafe.Pointer(list)) } type FlowList struct { diff --git a/tsc/internal/ast/lazylist.go b/tsc/internal/ast/lazylist.go new file mode 100644 index 0000000000000..d6e9a7cfaf6e5 --- /dev/null +++ b/tsc/internal/ast/lazylist.go @@ -0,0 +1,321 @@ +package ast + +import ( + "sync" + "sync/atomic" + "unsafe" + + "github.com/microsoft/TypeScript/tsc/internal/core" +) + +const ( + LazyListPending uint32 = 0 // Parsed lazily; binder has not visited owner yet + LazyListDeferred uint32 = 1 // Binder visited owner and deferred binding + LazyListUnbound uint32 = 2 // File finished binding without visiting owner + LazyListDone uint32 = 3 // Forced (parsed and bound if deferred) +) + +var ( + lazySentinelElem *Node + // SAFETY: Creates a 0-length slice backed by the address of lazySentinelElem as a sentinel tag. + lazySentinelNodes = unsafe.Slice(&lazySentinelElem, 1)[:0] +) + +type LazyBindContext struct { + Container *Node + ThisContainer *Node + BlockScopeContainer *Node + CurrentFlow *FlowNode + UnreachableFlow *FlowNode +} + +type LazyNodeList struct { + NodeList // Must be at offset 0 so (*LazyNodeList)(unsafe.Pointer(list)) is valid + state atomic.Uint32 + ParsingContext uint8 + ContextFlags NodeFlags + ParsingContexts uint32 + Owner *Node + File *SourceFile + BindCtx *LazyBindContext + forcedNodes []*Node + nestedLazyLists []*LazyNodeList + mu sync.Mutex +} + +var ( + lazyReparseHook func(lazy *LazyNodeList, file *SourceFile, allowNestedLazy bool) []*Node + lazyBindHook func(lazy *LazyNodeList, file *SourceFile, nodes []*Node) + lazySymbolLocks [256]sync.Mutex +) + +func SetLazyParseHook(fn func(lazy *LazyNodeList, file *SourceFile, allowNestedLazy bool) []*Node) { + lazyReparseHook = fn +} + +func SetLazyBindHook(fn func(lazy *LazyNodeList, file *SourceFile, nodes []*Node)) { + lazyBindHook = fn +} + +func NewLazyNodeList(loc core.TextRange, parsingContext uint8, contextFlags NodeFlags, parsingContexts uint32) *LazyNodeList { + var list LazyNodeList + list.Loc = loc + list.Nodes = lazySentinelNodes + list.ParsingContext = parsingContext + list.ContextFlags = contextFlags + list.ParsingContexts = parsingContexts + return &list +} + +func (list *NodeList) IsLazy() bool { + // SAFETY: SliceData only inspects the backing-array pointer of list.Nodes. + return list != nil && unsafe.SliceData(list.Nodes) == &lazySentinelElem +} + +func (list *NodeList) AsLazy() *LazyNodeList { + // SAFETY: IsLazy callers guarantee list was allocated as a LazyNodeList, which embeds NodeList at offset zero. + return (*LazyNodeList)(unsafe.Pointer(list)) +} + +func (list *NodeList) Slice() []*Node { + if list == nil { + return nil + } + // SAFETY: SliceData only inspects the backing-array pointer of list.Nodes. + if unsafe.SliceData(list.Nodes) == &lazySentinelElem { + return list.forceLazy() + } + return list.Nodes +} + +//go:noinline +func (list *NodeList) forceLazy() []*Node { + // SAFETY: forceLazy is only called when list.Nodes has the lazySentinelElem backing pointer of a LazyNodeList. + return (*LazyNodeList)(unsafe.Pointer(list)).Force() +} + +func SetLazyOwner(list *NodeList, owner *Node) { + // SAFETY: SliceData checks the LazyNodeList sentinel before casting list to its enclosing LazyNodeList at offset zero. + if list != nil && unsafe.SliceData(list.Nodes) == &lazySentinelElem { + (*LazyNodeList)(unsafe.Pointer(list)).Owner = owner + } +} + +func (lazy *LazyNodeList) State() uint32 { + return lazy.state.Load() +} + +func (lazy *LazyNodeList) SetNestedLazyLists(lists []*LazyNodeList) { + lazy.nestedLazyLists = lists +} + +func (lazy *LazyNodeList) NestedLazyLists() []*LazyNodeList { + return lazy.nestedLazyLists +} + +func (lazy *LazyNodeList) Defer(file *SourceFile, ctx *LazyBindContext) { + lazy.File = file + lazy.BindCtx = ctx + lazy.state.Store(LazyListDeferred) +} + +func (lazy *LazyNodeList) MarkUnbound(file *SourceFile) { + if lazy.state.Load() == LazyListPending { + lazy.File = file + lazy.state.Store(LazyListUnbound) + } +} + +func (lazy *LazyNodeList) ParseNow(file *SourceFile) []*Node { + lazy.File = file + nodes := lazyReparseHook(lazy, file, true /*allowNestedLazy*/) + lazy.forcedNodes = nodes + lazy.state.Store(LazyListDone) + return nodes +} + +func (lazy *LazyNodeList) Force() []*Node { + switch lazy.state.Load() { + case LazyListDone: + return lazy.forcedNodes + case LazyListPending: + return nil + } + if lazy.Owner != nil { + if sym := lazy.Owner.Symbol(); sym != nil && atomic.LoadUint32(&sym.owner().declHeader)&lazyStateMask != 0 { + sym.forceLazyMembers() + if lazy.state.Load() == LazyListDone { + return lazy.forcedNodes + } + } + } + lazy.mu.Lock() + defer lazy.mu.Unlock() + if lazy.state.Load() == LazyListDone { + return lazy.forcedNodes + } + if lazy.state.Load() == LazyListPending { + return nil + } + wasDeferred := lazy.state.Load() == LazyListDeferred + nodes := lazyReparseHook(lazy, lazy.File, wasDeferred) + lazy.forcedNodes = nodes + if wasDeferred && lazy.BindCtx != nil { + lazyBindHook(lazy, lazy.File, nodes) + for _, nested := range lazy.nestedLazyLists { + nested.MarkUnbound(lazy.File) + } + lazy.nestedLazyLists = nil + lazy.BindCtx = nil + } + lazy.state.Store(LazyListDone) + return nodes +} + +func (s *Symbol) SetHasLazyMembers() { + owner := s.owner() + for { + old := atomic.LoadUint32(&owner.declHeader) + next := (old &^ lazyStateMask) | lazyStatePending + if atomic.CompareAndSwapUint32(&owner.declHeader, old, next) { + break + } + } +} + +func (s *Symbol) HasLazyMembers() bool { + return s != nil && atomic.LoadUint32(&s.owner().declHeader)&lazyStateMask == lazyStatePending +} + +//go:noinline +func (s *Symbol) membersSlow() SymbolTable { + if atomic.LoadUint32(&s.owner().declHeader)&lazyStateMask != 0 { + s.forceLazyMembers() + } + d := s.data() + if d.extra == nil { + return nil + } + return d.extra.members +} + +//go:noinline +func (s *Symbol) exportsSlow() SymbolTable { + if atomic.LoadUint32(&s.owner().declHeader)&lazyStateMask != 0 { + s.forceLazyMembers() + } + d := s.data() + if d.extra == nil { + return nil + } + return d.extra.exports +} + +//go:noinline +func memberCountHint(decl *Node) int { + list := decl.MemberList() + if list == nil { + return 0 + } + if list.IsLazy() { + return len(list.AsLazy().forcedNodes) + } + return len(list.Nodes) +} + +func (s *Symbol) getMembersSlow(arena *SymbolExtraArena) SymbolTable { + if atomic.LoadUint32(&s.owner().declHeader)&lazyStateMask == lazyStatePending { + s.forceLazyMembers() + } + m := &s.ensureExtra(arena).members + if *m == nil { + var capHint int + if decls := s.Declarations(); len(decls) > 0 && decls[0] != nil { + switch decls[0].Kind { + case KindInterfaceDeclaration, KindClassDeclaration, KindClassExpression, KindTypeLiteral: + capHint = memberCountHint(decls[0]) + case KindObjectLiteralExpression, KindJsxAttributes: + capHint = len(decls[0].Properties()) + } + } + *m = NewSymbolTableWithCapacity(capHint) + } + return *m +} + +//go:noinline +func (s *Symbol) getExportsSlow(arena *SymbolExtraArena) SymbolTable { + if atomic.LoadUint32(&s.owner().declHeader)&lazyStateMask == lazyStatePending { + s.forceLazyMembers() + } + m := &s.ensureExtra(arena).exports + if *m == nil { + var capHint int + if decls := s.Declarations(); len(decls) > 0 && decls[0] != nil { + switch decls[0].Kind { + case KindEnumDeclaration: + capHint = memberCountHint(decls[0]) + case KindSourceFile: + capHint = len(decls[0].Statements()) + case KindModuleDeclaration: + if body := decls[0].Body(); body != nil && body.Kind == KindModuleBlock { + capHint = len(body.Statements()) + } + } + } + *m = NewSymbolTableWithCapacity(capHint) + } + return *m +} + +func (s *Symbol) forceLazyMembers() { + if s == nil { + return + } + owner := s.owner() + if atomic.LoadUint32(&owner.declHeader)&lazyStateMask == 0 { + return + } + // SAFETY: Converts the owner Symbol address to uintptr solely to select a lock shard. + mu := &lazySymbolLocks[(uintptr(unsafe.Pointer(owner))>>6)&255] + mu.Lock() + defer mu.Unlock() + if atomic.LoadUint32(&owner.declHeader)&lazyStateMask != lazyStatePending { + return + } + for { + old := atomic.LoadUint32(&owner.declHeader) + if atomic.CompareAndSwapUint32(&owner.declHeader, old, (old&^lazyStateMask)|lazyStateActive) { + break + } + } + for _, decl := range s.Declarations() { + if decl == nil { + continue + } + switch decl.Kind { + case KindInterfaceDeclaration, KindClassDeclaration, KindClassExpression, KindTypeLiteral: + list := decl.MemberList() + if list.IsLazy() { + l := list.AsLazy() + if l.state.Load() == LazyListDeferred { + nodes := lazyReparseHook(l, l.File, true /*allowNestedLazy*/) + l.forcedNodes = nodes + lazyBindHook(l, l.File, nodes) + for _, nested := range l.nestedLazyLists { + nested.MarkUnbound(l.File) + } + l.nestedLazyLists = nil + l.BindCtx = nil + l.state.Store(LazyListDone) + } + } + } + } + for { + old := atomic.LoadUint32(&owner.declHeader) + if atomic.CompareAndSwapUint32(&owner.declHeader, old, old&^lazyStateMask) { + break + } + } +} diff --git a/tsc/internal/ast/nodeflags.go b/tsc/internal/ast/nodeflags.go index 2c808601a2260..cd86f5679a440 100644 --- a/tsc/internal/ast/nodeflags.go +++ b/tsc/internal/ast/nodeflags.go @@ -45,6 +45,9 @@ const ( NodeFlagsPossiblyContainsDeprecatedTag NodeFlags = 1 << 26 // Set during parse if comment text contains '@deprecated'; must confirm via JSDoc lookup NodeFlagsUnreachable NodeFlags = 1 << 27 // If node is unreachable according to the binder NodeFlagsReparserTransformedLiteral NodeFlags = 1 << 28 // If node was transformed during parsing, making its' naive text source not match the AST + // Set by the checker (getConditionalFlowTypeOfType) to memoize that no ancestor of this node, + // up to the enclosing statement, can contribute an implied constraint. + NodeFlagsCondFlowClean NodeFlags = 1 << 29 NodeFlagsBlockScoped = NodeFlagsLet | NodeFlagsConst | NodeFlagsUsing NodeFlagsConstant = NodeFlagsConst | NodeFlagsUsing diff --git a/tsc/internal/ast/subtreefacts.go b/tsc/internal/ast/subtreefacts.go index 7c6a2a5cc113a..ff3efe41625fc 100644 --- a/tsc/internal/ast/subtreefacts.go +++ b/tsc/internal/ast/subtreefacts.go @@ -119,7 +119,7 @@ func propagateNodeListSubtreeFacts(children *NodeList, propagate func(*Node) Sub return SubtreeFactsNone } facts := SubtreeFactsNone - for _, child := range children.Nodes { + for _, child := range children.Slice() { facts |= propagate(child) } return facts diff --git a/tsc/internal/ast/symbol.go b/tsc/internal/ast/symbol.go index 247a4e0ca81f7..76f1d187f25c9 100644 --- a/tsc/internal/ast/symbol.go +++ b/tsc/internal/ast/symbol.go @@ -6,31 +6,31 @@ import ( "slices" "strings" "sync/atomic" + "unsafe" "github.com/microsoft/TypeScript/tsc/internal/core" "github.com/microsoft/TypeScript/tsc/internal/debug" ) -// Symbol stores flags, checkFlags, and id uniquely for every symbol instance, but may share -// symbolData with other symbols. This is done so that instantiated symbols in the checker can -// all share the same underlying symbolData. Every Symbol needs its data field initialized either -// through SymbolWithData.Initialize or by explicitly setting it with Symbol.SetSymbolData. +// Symbol stores flags, checkFlags, id, and declaration header uniquely for every symbol instance. +// Ordinary symbols are embedded at offset 0 of SymbolWithData and store their parent in parentOrData; +// transient symbols created with SetSymbolData set checkFlagsSharedData and store their owner Symbol +// in parentOrData. type Symbol struct { - flags SymbolFlags - checkFlags CheckFlags // Non-zero only in transient symbols created by Checker - id atomic.Uint64 - data *symbolData + flags SymbolFlags + checkFlags CheckFlags // Non-zero only in transient symbols created by Checker + id atomic.Uint32 + declHeader uint32 // bits 0..14: declLen, bits 15..29: declCap, bits 30..31: lazyState + parentOrData *Symbol } // Every symbol references a symbolData structure that may be shared with other symbols. type symbolData struct { name string - declarations []*Node + declPtr unsafe.Pointer valueDeclaration *Node - parent *Symbol extra *symbolExtra } - type symbolExtra struct { members SymbolTable exports SymbolTable @@ -43,62 +43,228 @@ type SymbolExtraArena struct { arena core.Arena[symbolExtra] } -func (s *Symbol) Flags() SymbolFlags { return s.flags } -func (s *Symbol) CheckFlags() CheckFlags { return s.checkFlags } -func (s *Symbol) Name() string { return s.data.name } -func (s *Symbol) Declarations() []*Node { return s.data.declarations } -func (s *Symbol) ValueDeclaration() *Node { return s.data.valueDeclaration } -func (s *Symbol) Parent() *Symbol { return s.data.parent } +const ( + checkFlagsMerged CheckFlags = 1 << 27 // Symbol is a key in Checker.mergedSymbols + checkFlagsReferencedAny CheckFlags = 1 << 28 + checkFlagsReferencedVariable CheckFlags = 1 << 29 + checkFlagsReferencedTypeParameter CheckFlags = 1 << 30 + checkFlagsSharedData CheckFlags = 1 << 31 + checkFlagsReferencedMask CheckFlags = checkFlagsReferencedAny | checkFlagsReferencedVariable | checkFlagsReferencedTypeParameter + checkFlagsInternalMask CheckFlags = checkFlagsMerged | checkFlagsReferencedMask | checkFlagsSharedData + declLenCapMask uint32 = 0x7fff + declCapShift uint32 = 15 + lazyStateShift uint32 = 30 + lazyStateMask uint32 = 3 << lazyStateShift + lazyStatePending uint32 = 1 << lazyStateShift + lazyStateActive uint32 = 2 << lazyStateShift +) -func (s *Symbol) Members() SymbolTable { - if s.data.extra == nil { +func (s *Symbol) owner() *Symbol { + if s.checkFlags&checkFlagsSharedData != 0 { + return s.parentOrData + } + return s +} + +func (s *Symbol) data() *symbolData { + if s.checkFlags&checkFlagsSharedData != 0 { + // SAFETY: When checkFlagsSharedData is set, parentOrData points to an owner Symbol embedded at offset zero of SymbolWithData. + return &(*SymbolWithData)(unsafe.Pointer(s.parentOrData)).d + } + // SAFETY: Non-shared Symbols are embedded at offset zero of SymbolWithData. + return &(*SymbolWithData)(unsafe.Pointer(s)).d +} + +func (s *Symbol) Flags() SymbolFlags { return s.flags } +func (s *Symbol) CheckFlags() CheckFlags { + return CheckFlags(atomic.LoadUint32((*uint32)(&s.checkFlags))) &^ checkFlagsInternalMask +} + +func (s *Symbol) AddReference(meaning SymbolFlags) { + if s == nil { + return + } + flags := uint32(checkFlagsReferencedAny) + if meaning&SymbolFlagsVariable != 0 { + flags |= uint32(checkFlagsReferencedVariable) + } + if meaning&SymbolFlagsTypeParameter != 0 { + flags |= uint32(checkFlagsReferencedTypeParameter) + } + for { + old := atomic.LoadUint32((*uint32)(&s.checkFlags)) + if old&flags == flags || atomic.CompareAndSwapUint32((*uint32)(&s.checkFlags), old, old|flags) { + break + } + } +} + +func (s *Symbol) ReferenceKinds() SymbolFlags { + if s == nil { + return 0 + } + cf := CheckFlags(atomic.LoadUint32((*uint32)(&s.checkFlags))) + if cf&checkFlagsReferencedMask == 0 { + return 0 + } + res := SymbolFlagsAll &^ (SymbolFlagsVariable | SymbolFlagsTypeParameter) + if cf&checkFlagsReferencedVariable != 0 { + res |= SymbolFlagsVariable + } + if cf&checkFlagsReferencedTypeParameter != 0 { + res |= SymbolFlagsTypeParameter + } + return res +} + +func (s *Symbol) IsMerged() bool { + return CheckFlags(atomic.LoadUint32((*uint32)(&s.checkFlags)))&checkFlagsMerged != 0 +} + +func (s *Symbol) MarkMerged() { + for { + old := atomic.LoadUint32((*uint32)(&s.checkFlags)) + if CheckFlags(old)&checkFlagsMerged != 0 { + return + } + if atomic.CompareAndSwapUint32((*uint32)(&s.checkFlags), old, old|uint32(checkFlagsMerged)) { + return + } + } +} +func (s *Symbol) Name() string { return s.data().name } +func (s *Symbol) Declarations() []*Node { + owner := s.owner() + hdr := owner.declHeader + declLen := hdr & declLenCapMask + if declLen == 0 { return nil } - return s.data.extra.members + // SAFETY: owner() returns a Symbol embedded at offset zero of SymbolWithData. + d := &(*SymbolWithData)(unsafe.Pointer(owner)).d + declCap := (hdr >> declCapShift) & declLenCapMask + if declCap == 0 { + // SAFETY: When declCap == 0 and declLen == 1, d.declPtr stores a single *Node inline. + return unsafe.Slice((**Node)(unsafe.Pointer(&d.declPtr)), 1) + } + // SAFETY: When declCap > 0, d.declPtr points to a []*Node backing array of length at least declCap >= declLen. + return unsafe.Slice((**Node)(d.declPtr), declCap)[:declLen] } -func (s *Symbol) Exports() SymbolTable { - if s.data.extra == nil { +func (s *Symbol) FirstDeclaration() *Node { + owner := s.owner() + hdr := owner.declHeader + if hdr&declLenCapMask == 0 { return nil } - return s.data.extra.exports + // SAFETY: owner() returns a Symbol embedded at offset zero of SymbolWithData. + d := &(*SymbolWithData)(unsafe.Pointer(owner)).d + if (hdr>>declCapShift)&declLenCapMask == 0 { + // SAFETY: When declCap == 0 and declLen == 1, d.declPtr stores a single *Node inline. + return *(**Node)(unsafe.Pointer(&d.declPtr)) + } + // SAFETY: When declCap > 0, d.declPtr points to the first element of a non-empty []*Node backing array. + return *(**Node)(d.declPtr) +} +func (s *Symbol) ValueDeclaration() *Node { return s.data().valueDeclaration } +func (s *Symbol) Parent() *Symbol { return s.owner().parentOrData } + +func (s *Symbol) Members() SymbolTable { + if s.declHeader < lazyStatePending { + // SAFETY: Shared-data symbols set declHeader = lazyStateMask, so declHeader < lazyStatePending guarantees s is a SymbolWithData. + if extra := (*SymbolWithData)(unsafe.Pointer(s)).d.extra; extra != nil { + return extra.members + } + } + return s.membersSlow() +} + +func (s *Symbol) Exports() SymbolTable { + if s.declHeader < lazyStatePending { + // SAFETY: Shared-data symbols set declHeader = lazyStateMask, so declHeader < lazyStatePending guarantees s is a SymbolWithData. + if extra := (*SymbolWithData)(unsafe.Pointer(s)).d.extra; extra != nil { + return extra.exports + } + } + return s.exportsSlow() } func (s *Symbol) ExportSymbol() *Symbol { - if s.data.extra == nil { + d := s.data() + if d.extra == nil { return nil } - return s.data.extra.exportSymbol + return d.extra.exportSymbol } -func (s *Symbol) SetFlags(value SymbolFlags) { s.flags = value } -func (s *Symbol) SetCheckFlags(value CheckFlags) { s.checkFlags = value } -func (s *Symbol) SetName(value string) { s.data.name = value } -func (s *Symbol) SetDeclarations(value []*Node) { s.data.declarations = value } -func (s *Symbol) SetValueDeclaration(value *Node) { s.data.valueDeclaration = value } -func (s *Symbol) SetParent(value *Symbol) { s.data.parent = value } +func (s *Symbol) SetFlags(value SymbolFlags) { s.flags = value } +func (s *Symbol) SetCheckFlags(value CheckFlags) { + for { + old := atomic.LoadUint32((*uint32)(&s.checkFlags)) + next := (old & uint32(checkFlagsInternalMask)) | (uint32(value) &^ uint32(checkFlagsInternalMask)) + if atomic.CompareAndSwapUint32((*uint32)(&s.checkFlags), old, next) { + break + } + } +} +func (s *Symbol) SetName(value string) { s.data().name = value } +func (s *Symbol) SetSingleDeclaration(node *Node) { + owner := s.owner() + // SAFETY: owner() returns a Symbol embedded at offset zero of SymbolWithData. + d := &(*SymbolWithData)(unsafe.Pointer(owner)).d + if node != nil { + // SAFETY: Recovers the *Node pointer from &node.Flags. + d.declPtr = unsafe.Add(unsafe.Pointer(&node.Flags), -int(unsafe.Offsetof(Node{}.Flags))) + } else { + d.declPtr = nil + } + owner.declHeader = (owner.declHeader & lazyStateMask) | 1 +} + +func (s *Symbol) SetDeclarations(value []*Node) { + owner := s.owner() + // SAFETY: owner() returns a Symbol embedded at offset zero of SymbolWithData. + d := &(*SymbolWithData)(unsafe.Pointer(owner)).d + lazy := owner.declHeader & lazyStateMask + if len(value) == 0 { + d.declPtr = nil + owner.declHeader = lazy + return + } + // SAFETY: Stores the backing array pointer of the non-empty value slice alongside its length and capacity. + d.declPtr = unsafe.Pointer(unsafe.SliceData(value)) + declLen := uint32(len(value)) & declLenCapMask + declCap := uint32(max(cap(value), len(value))) & declLenCapMask + owner.declHeader = lazy | declLen | (declCap << declCapShift) +} +func (s *Symbol) SetValueDeclaration(value *Node) { s.data().valueDeclaration = value } +func (s *Symbol) SetParent(value *Symbol) { s.owner().parentOrData = value } func (s *Symbol) ensureExtra(arena *SymbolExtraArena) *symbolExtra { - if s.data.extra == nil { - s.data.extra = arena.arena.New() + d := s.data() + if d.extra == nil { + d.extra = arena.arena.New() } - return s.data.extra + return d.extra } func (s *Symbol) SetMembers(value SymbolTable, arena *SymbolExtraArena) { - if value != nil || s.data.extra != nil { + d := s.data() + if value != nil || d.extra != nil { s.ensureExtra(arena).members = value } } func (s *Symbol) SetExports(value SymbolTable, arena *SymbolExtraArena) { - if value != nil || s.data.extra != nil { + d := s.data() + if value != nil || d.extra != nil { s.ensureExtra(arena).exports = value } } func (s *Symbol) SetExportSymbol(value *Symbol, arena *SymbolExtraArena) { - if value != nil || s.data.extra != nil { + d := s.data() + if value != nil || d.extra != nil { s.ensureExtra(arena).exportSymbol = value } } @@ -111,7 +277,6 @@ type SymbolWithData struct { // Initializes a SymbolWithData instance and returns the embedded Symbol. func (sd *SymbolWithData) Initialize() *Symbol { - sd.s.data = &sd.d return &sd.s } @@ -120,9 +285,10 @@ func NewSymbol() *Symbol { } // Initializes a Symbol's data field with the data from another Symbol. This allows multiple -// symbols to share the same underlying symbolData. func (s *Symbol) SetSymbolData(other *Symbol) { - s.data = other.data + s.parentOrData = other.owner() + s.checkFlags |= checkFlagsSharedData + s.declHeader = lazyStateMask } // GetSourceFileOfSymbol returns the owning file of a published binder symbol, or @@ -218,7 +384,7 @@ func (m *SymbolMap) keyAt(i int) string { func (m *SymbolMap) symbolAt(i int) *Symbol { symbol := m.entries[i] if symbol.flags == SymbolFlagsNameProxy { - symbol = symbol.data.parent + symbol = symbol.parentOrData } return symbol } @@ -273,8 +439,8 @@ func (m *SymbolMap) Set(key string, symbol *Symbol) { if key != symbol.Name() { proxy := NewSymbol() proxy.flags = SymbolFlagsNameProxy - proxy.data.name = key - proxy.data.parent = symbol + proxy.data().name = key + proxy.parentOrData = symbol symbol = proxy } if i := m.find(key); i >= 0 { diff --git a/tsc/internal/ast/symbol_test.go b/tsc/internal/ast/symbol_test.go index d43d726613e46..e53922bec62b6 100644 --- a/tsc/internal/ast/symbol_test.go +++ b/tsc/internal/ast/symbol_test.go @@ -118,7 +118,7 @@ func TestSymbolTableInitialization(t *testing.T) { func TestSymbolHotRecordLayout(t *testing.T) { t.Parallel() - assert.Equal(t, unsafe.Sizeof(ast.SymbolWithData{}), unsafe.Sizeof(ast.Symbol{})+8*unsafe.Sizeof((*ast.Node)(nil))) + assert.Equal(t, unsafe.Sizeof(ast.SymbolWithData{}), unsafe.Sizeof(ast.Symbol{})+5*unsafe.Sizeof((*ast.Node)(nil))) } func TestSymbolNilExtraFieldsDoNotAllocate(t *testing.T) { //nolint:paralleltest @@ -130,7 +130,7 @@ func TestSymbolNilExtraFieldsDoNotAllocate(t *testing.T) { //nolint:paralleltest symbol.SetExportSymbol(nil, &arena) }) assert.Equal(t, allocs, float64(0)) - assert.Assert(t, reflect.ValueOf(symbol).Elem().FieldByName("data").Elem().FieldByName("extra").IsNil()) + assert.Assert(t, symbol.Members() == nil && symbol.Exports() == nil && symbol.ExportSymbol() == nil) } func TestSymbolSharedExtraData(t *testing.T) { diff --git a/tsc/internal/ast/utilities.go b/tsc/internal/ast/utilities.go index f178903372dc2..ad3a51368e860 100644 --- a/tsc/internal/ast/utilities.go +++ b/tsc/internal/ast/utilities.go @@ -15,10 +15,10 @@ import ( // Atomic ids var ( - nextNodeId atomic.Uint64 - nextSymbolId atomic.Uint64 - nextNodeBlockId atomic.Uint64 - nextSymbolBlockId atomic.Uint64 + nextNodeId atomic.Uint32 + nextSymbolId atomic.Uint32 + nextNodeBlockId atomic.Uint32 + nextSymbolBlockId atomic.Uint32 ) func GetNodeId(node *Node) NodeId { @@ -45,24 +45,32 @@ func GetSymbolId(symbol *Symbol) SymbolId { return SymbolId(id) } +func PeekNodeId(node *Node) NodeId { + return NodeId(node.id.Load()) +} + +func PeekSymbolId(symbol *Symbol) SymbolId { + return SymbolId(symbol.id.Load()) +} + const ( - BlockIdOffset = 0x1_0000_0000_0000 - BlockIdSize = core.LinkPageSize + BlockIdOffset = 0x4000_0000 + BlockIdSize = 1024 ) type NodeIdGenerator struct { - nextId uint64 - lastId uint64 + nextId uint32 + lastId uint32 } func (a *NodeIdGenerator) GetNodeId(node *Node) NodeId { id := node.id.Load() if id == 0 { if a.nextId == a.lastId { - a.nextId = nextNodeBlockId.Add(BlockIdSize) - BlockIdSize + a.nextId = nextNodeBlockId.Add(BlockIdSize) + (BlockIdOffset - BlockIdSize) a.lastId = a.nextId + BlockIdSize } - id = a.nextId + BlockIdOffset + id = a.nextId a.nextId++ if !node.id.CompareAndSwap(0, id) { id = node.id.Load() @@ -72,18 +80,18 @@ func (a *NodeIdGenerator) GetNodeId(node *Node) NodeId { } type SymbolIdGenerator struct { - nextId uint64 - lastId uint64 + nextId uint32 + lastId uint32 } func (a *SymbolIdGenerator) GetSymbolId(symbol *Symbol) SymbolId { id := symbol.id.Load() if id == 0 { if a.nextId == a.lastId { - a.nextId = nextSymbolBlockId.Add(BlockIdSize) - BlockIdSize + a.nextId = nextSymbolBlockId.Add(BlockIdSize) + (BlockIdOffset - BlockIdSize) a.lastId = a.nextId + BlockIdSize } - id = a.nextId + BlockIdOffset + id = a.nextId a.nextId++ if !symbol.id.CompareAndSwap(0, id) { id = symbol.id.Load() @@ -100,15 +108,47 @@ func GetSymbolTable(data *SymbolTable) SymbolTable { } func GetMembers(symbol *Symbol, arena *SymbolExtraArena) SymbolTable { - return GetSymbolTable(&symbol.ensureExtra(arena).members) + owner := symbol.owner() + d := owner.data() + if owner.declHeader&lazyStateMask == 0 && d.extra != nil && d.extra.members != nil { + return d.extra.members + } + return symbol.getMembersSlow(arena) } func GetExports(symbol *Symbol, arena *SymbolExtraArena) SymbolTable { - return GetSymbolTable(&symbol.ensureExtra(arena).exports) + owner := symbol.owner() + d := owner.data() + if owner.declHeader&lazyStateMask == 0 && d.extra != nil && d.extra.exports != nil { + return d.extra.exports + } + return symbol.getExportsSlow(arena) } func GetLocals(container *Node) SymbolTable { - return GetSymbolTable(&container.LocalsContainerData().Locals) + data := container.LocalsContainerData() + if data.Locals == nil { + var capHint int + switch container.Kind { + case KindSourceFile: + capHint = len(container.Statements()) + case KindModuleDeclaration: + if body := container.Body(); body != nil && body.Kind == KindModuleBlock { + capHint = len(body.Statements()) + } + case KindFunctionDeclaration, KindMethodDeclaration, KindMethodSignature, KindConstructor, + KindGetAccessor, KindSetAccessor, KindFunctionExpression, KindArrowFunction, + KindCallSignature, KindConstructSignature, KindFunctionType, KindConstructorType: + capHint = len(container.Parameters()) + if tps := container.TypeParameters(); len(tps) > 0 { + capHint += len(tps) + } + case KindTypeAliasDeclaration, KindJSTypeAliasDeclaration: + capHint = len(container.TypeParameters()) + } + data.Locals = NewSymbolTableWithCapacity(capHint) + } + return data.Locals } // Determines if a node is missing (either `nil` or empty) @@ -1406,15 +1446,15 @@ func IsVoidZero(node *Node) bool { } func IsExportsIdentifier(node *Node) bool { - return IsIdentifier(node) && node.Text() == "exports" + return node != nil && node.Kind == KindIdentifier && node.AsIdentifier().Text == "exports" } func IsModuleIdentifier(node *Node) bool { - return IsIdentifier(node) && node.Text() == "module" + return node != nil && node.Kind == KindIdentifier && node.AsIdentifier().Text == "module" } func IsThisIdentifier(node *Node) bool { - return IsIdentifier(node) && node.Text() == "this" + return node != nil && node.Kind == KindIdentifier && node.AsIdentifier().Text == "this" } func IsThisParameter(node *Node) bool { @@ -2814,30 +2854,26 @@ func nodeContainsPosition(node *Node, position int) bool { return node.Kind >= KindFirstNode && node.Pos() <= position && (position < node.End() || position == node.End() && node.Kind == KindEndOfFile) } -func findImportOrRequire(text string, start int) (index int, size int) { - index = max(start, 0) - n := len(text) - for index < n { - next := strings.IndexAny(text[index:], "ir") - if next < 0 { - break - } - index += next - - var expected string - if text[index] == 'i' { - size = 6 - expected = "import" - } else { - size = 7 - expected = "require" - } - if index+size <= n && text[index:index+size] == expected { - return index, size +func findImportOrRequire(text string, start int, isJavaScriptFile bool) (index int, size int) { + start = max(start, 0) + if start >= len(text) { + return -1, 0 + } + sub := text[start:] + imp := strings.Index(sub, "import") + if !isJavaScriptFile { + if imp < 0 { + return -1, 0 } - index++ + return start + imp, 6 + } + req := strings.Index(sub, "require") + if imp >= 0 && (req < 0 || imp < req) { + return start + imp, 6 + } + if req >= 0 { + return start + req, 7 } - return -1, 0 } @@ -2848,7 +2884,7 @@ func ForEachDynamicImportOrRequireCall( cb func(node *Node, argument *Expression) bool, ) bool { isJavaScriptFile := IsInJSFile(file.AsNode()) - lastIndex, size := findImportOrRequire(file.Text(), 0) + lastIndex, size := findImportOrRequire(file.Text(), 0, isJavaScriptFile) for lastIndex >= 0 { node := GetNodeAtPosition(file, lastIndex, isJavaScriptFile && includeTypeSpaceImports) if isJavaScriptFile && IsRequireCall(node, requireStringLiteralLikeArgument) { @@ -2866,7 +2902,7 @@ func ForEachDynamicImportOrRequireCall( } // skip past import/require lastIndex += size - lastIndex, size = findImportOrRequire(file.Text(), lastIndex) + lastIndex, size = findImportOrRequire(file.Text(), lastIndex, isJavaScriptFile) } return false } diff --git a/tsc/internal/ast/visitor.go b/tsc/internal/ast/visitor.go index bedcc8b729eea..33694f0203764 100644 --- a/tsc/internal/ast/visitor.go +++ b/tsc/internal/ast/visitor.go @@ -96,7 +96,7 @@ func (v *NodeVisitor) VisitNodes(nodes *NodeList) *NodeList { return nodes } - if result, changed := v.VisitSlice(nodes.Nodes); changed { + if result, changed := v.VisitSlice(nodes.Slice()); changed { list := v.Factory.NewNodeList(result) list.Loc = nodes.Loc return list diff --git a/tsc/internal/binder/binder.go b/tsc/internal/binder/binder.go index bc462e49a7f95..4a843f14ace80 100644 --- a/tsc/internal/binder/binder.go +++ b/tsc/internal/binder/binder.go @@ -72,6 +72,7 @@ type Binder struct { inAssignmentPattern bool seenParseError bool symbolCount int + isLazy bool notConstEnumOnlyModules collections.Set[*ast.Symbol] symbolWithDataArena core.Arena[ast.SymbolWithData] symbolExtraArena ast.SymbolExtraArena @@ -100,6 +101,10 @@ func BindSourceFile(file *ast.SourceFile) { } } +func init() { + ast.SetLazyBindHook(bindLazyList) +} + var binderPool = sync.Pool{ New: func() any { b := &Binder{} @@ -113,7 +118,14 @@ func getBinder() *Binder { } func putBinder(b *Binder) { - *b = Binder{bindFunc: b.bindFunc} + *b = Binder{ + bindFunc: b.bindFunc, + symbolWithDataArena: b.symbolWithDataArena, + symbolExtraArena: b.symbolExtraArena, + flowNodeArena: b.flowNodeArena, + flowListArena: b.flowListArena, + singleDeclarationsArena: b.singleDeclarationsArena, + } binderPool.Put(b) } @@ -126,6 +138,9 @@ func bindSourceFile(file *ast.SourceFile) { b.bind(file.AsNode()) b.bindDeferredExpandoAssignments() file.SymbolCount = b.symbolCount + for _, lazy := range file.LazyLists { + lazy.MarkUnbound(file) + } }) } @@ -550,14 +565,14 @@ func (b *Binder) addAntecedent(label *ast.FlowLabel, antecedent *ast.FlowNode) { } // If antecedent isn't already on the Antecedents list, add it to the end of the list var last *ast.FlowList - for list := label.Antecedents; list != nil; list = list.Next { + for list := label.Antecedents(); list != nil; list = list.Next { if list.Flow == antecedent { return } last = list } if last == nil { - label.Antecedents = b.newFlowList(antecedent, nil) + label.SetAntecedents(b.newFlowList(antecedent, nil)) } else { last.Next = b.newFlowList(antecedent, nil) } @@ -565,11 +580,12 @@ func (b *Binder) addAntecedent(label *ast.FlowLabel, antecedent *ast.FlowNode) { } func (b *Binder) finishFlowLabel(label *ast.FlowLabel) *ast.FlowNode { - if label.Antecedents == nil { + antecedents := label.Antecedents() + if antecedents == nil { return b.unreachableFlow } - if label.Antecedents.Next == nil { - return label.Antecedents.Flow + if antecedents.Next == nil { + return antecedents.Flow } return label } @@ -1736,6 +1752,9 @@ func (b *Binder) bindChildren(node *ast.Node) { case ast.KindObjectLiteralExpression, ast.KindArrayLiteralExpression, ast.KindPropertyAssignment, ast.KindSpreadElement: b.inAssignmentPattern = saveInAssignmentPattern b.bindEachChild(node) + case ast.KindInterfaceDeclaration, ast.KindClassDeclaration, ast.KindClassExpression, ast.KindTypeLiteral: + b.bindEachChild(node) + b.bindLazyMemberList(node) default: b.bindEachChild(node) } @@ -1752,6 +1771,63 @@ func (b *Binder) bindEach(nodes []*ast.Node) { } } +func (b *Binder) bindLazyMemberList(node *ast.Node) { + list := node.MemberList() + if !list.IsLazy() { + return + } + lazy := list.AsLazy() + if lazy.State() != ast.LazyListPending { + return + } + symbol := node.Symbol() + plain := symbol != nil && + b.currentFlow != b.unreachableFlow && + b.currentBreakTarget == nil && + b.currentContinueTarget == nil && + b.currentReturnTarget == nil && + b.currentTrueTarget == nil && + b.currentFalseTarget == nil && + b.currentExceptionTarget == nil && + b.preSwitchCaseFlow == nil && + b.activeLabelList == nil && + !b.inAssignmentPattern + if !plain { + nodes := lazy.ParseNow(b.file) + b.bindEach(nodes) + for _, nested := range lazy.NestedLazyLists() { + nested.MarkUnbound(b.file) + } + lazy.SetNestedLazyLists(nil) + return + } + lazy.Defer(b.file, &ast.LazyBindContext{ + Container: b.container, + ThisContainer: b.thisContainer, + BlockScopeContainer: b.blockScopeContainer, + CurrentFlow: b.currentFlow, + UnreachableFlow: b.unreachableFlow, + }) + symbol.SetHasLazyMembers() +} + +func bindLazyList(lazy *ast.LazyNodeList, file *ast.SourceFile, nodes []*ast.Node) { + ctx := lazy.BindCtx + if ctx == nil { + return + } + b := getBinder() + defer putBinder(b) + b.file = file + b.isLazy = true + b.container = ctx.Container + b.thisContainer = ctx.ThisContainer + b.blockScopeContainer = ctx.BlockScopeContainer + b.currentFlow = ctx.CurrentFlow + b.unreachableFlow = ctx.UnreachableFlow + b.bindEach(nodes) +} + func (b *Binder) bindNodeList(nodeList *ast.NodeList) { if nodeList != nil { b.bindEach(nodeList.Nodes) @@ -2062,7 +2138,7 @@ func (b *Binder) bindTryStatement(node *ast.Node) { // set of antecedents for the pre-finally label. As control flow analysis passes by a ReduceLabel // node, the pre-finally label is temporarily switched to the reduced antecedent set. finallyLabel := b.createBranchLabel() - finallyLabel.Antecedents = b.combineFlowLists(normalExitLabel.Antecedents, b.combineFlowLists(exceptionLabel.Antecedents, returnLabel.Antecedents)) + finallyLabel.SetAntecedents(b.combineFlowLists(normalExitLabel.Antecedents(), b.combineFlowLists(exceptionLabel.Antecedents(), returnLabel.Antecedents()))) b.currentFlow = finallyLabel b.bind(stmt.FinallyBlock) if b.currentFlow.Flags&ast.FlowFlagsUnreachable != 0 { @@ -2071,19 +2147,19 @@ func (b *Binder) bindTryStatement(node *ast.Node) { } else { // If we have an IIFE return target and return statements in the try or catch blocks, add a control // flow that goes back through the finally block and back through only the return statements. - if b.currentReturnTarget != nil && returnLabel.Antecedents != nil { - b.addAntecedent(b.currentReturnTarget, b.createReduceLabel(finallyLabel, returnLabel.Antecedents, b.currentFlow)) + if b.currentReturnTarget != nil && returnLabel.Antecedents() != nil { + b.addAntecedent(b.currentReturnTarget, b.createReduceLabel(finallyLabel, returnLabel.Antecedents(), b.currentFlow)) } // If we have an outer exception target (i.e. a containing try-finally or try-catch-finally), add a // control flow that goes back through the finally block and back through each possible exception source. - if b.currentExceptionTarget != nil && exceptionLabel.Antecedents != nil { - b.addAntecedent(b.currentExceptionTarget, b.createReduceLabel(finallyLabel, exceptionLabel.Antecedents, b.currentFlow)) + if b.currentExceptionTarget != nil && exceptionLabel.Antecedents() != nil { + b.addAntecedent(b.currentExceptionTarget, b.createReduceLabel(finallyLabel, exceptionLabel.Antecedents(), b.currentFlow)) } // If the end of the finally block is reachable, but the end of the try and catch blocks are not, // convert the current flow to unreachable. For example, 'try { return 1; } finally { ... }' should // result in an unreachable current control flow. - if normalExitLabel.Antecedents != nil { - b.currentFlow = b.createReduceLabel(finallyLabel, normalExitLabel.Antecedents, b.currentFlow) + if normalExitLabel.Antecedents() != nil { + b.currentFlow = b.createReduceLabel(finallyLabel, normalExitLabel.Antecedents(), b.currentFlow) } else { b.currentFlow = b.unreachableFlow } @@ -2543,7 +2619,7 @@ func (b *Binder) addDeclarationToSymbol(symbol *ast.Symbol, node *ast.Node, symb symbol.SetFlags(symbol.Flags() | symbolFlags) node.DeclarationData().Symbol = symbol if symbol.Declarations() == nil { - symbol.SetDeclarations(b.newSingleDeclaration(node)) + symbol.SetSingleDeclaration(node) } else { symbol.SetDeclarations(core.AppendIfUnique(symbol.Declarations(), node)) } @@ -2742,7 +2818,9 @@ func (b *Binder) createDiagnosticForNode(node *ast.Node, message *diagnostics.Me } func (b *Binder) addDiagnostic(diagnostic *ast.Diagnostic) { - b.file.SetBindDiagnostics(append(b.file.BindDiagnostics(), diagnostic)) + if !b.isLazy { + b.file.SetBindDiagnostics(append(b.file.BindDiagnostics(), diagnostic)) + } } func isSignedNumericLiteral(node *ast.Node) bool { diff --git a/tsc/internal/binder/nameresolver.go b/tsc/internal/binder/nameresolver.go index 4d0db524946cb..93272224cf81a 100644 --- a/tsc/internal/binder/nameresolver.go +++ b/tsc/internal/binder/nameresolver.go @@ -14,6 +14,7 @@ type NameResolver struct { ArgumentsSymbol *ast.Symbol RequireSymbol *ast.Symbol Lookup func(symbols ast.SymbolTable, name string, meaning ast.SymbolFlags) *ast.Symbol + LookupSymbol func(symbol *ast.Symbol, meaning ast.SymbolFlags) *ast.Symbol SymbolReferenced func(symbol *ast.Symbol, meaning ast.SymbolFlags) SetRequiresScopeChangeCache func(node *ast.Node, value core.Tristate) GetRequiresScopeChangeCache func(node *ast.Node) core.Tristate @@ -22,6 +23,53 @@ type NameResolver struct { OnSuccessfullyResolvedSymbol func(location *ast.Node, result *ast.Symbol, meaning ast.SymbolFlags, lastLocation *ast.Node, associatedDeclarationForContainingInitializerOrBindingName *ast.Node, withinDeferredContext bool) } +var isActiveScopeKind = [ast.KindCount + 4]bool{ + ast.KindForStatement: true, + ast.KindForInStatement: true, + ast.KindForOfStatement: true, + ast.KindCaseBlock: true, + ast.KindCatchClause: true, + ast.KindBlock: true, + ast.KindFunctionDeclaration: true, + ast.KindClassDeclaration: true, + ast.KindClassExpression: true, + ast.KindTypeAliasDeclaration: true, + ast.KindJSTypeAliasDeclaration: true, + ast.KindCallSignature: true, + ast.KindConstructSignature: true, + ast.KindConstructor: true, + ast.KindGetAccessor: true, + ast.KindSetAccessor: true, + ast.KindIndexSignature: true, + ast.KindMethodSignature: true, + ast.KindMethodDeclaration: true, + ast.KindClassStaticBlockDeclaration: true, + ast.KindArrowFunction: true, + ast.KindFunctionExpression: true, + ast.KindConditionalType: true, + ast.KindMappedType: true, + ast.KindFunctionType: true, + ast.KindConstructorType: true, + ast.KindJSDocSignature: true, + ast.KindSourceFile: true, + ast.KindModuleDeclaration: true, + ast.KindImportAttributes: true, + ast.KindEnumDeclaration: true, + ast.KindPropertyDeclaration: true, + ast.KindInterfaceDeclaration: true, + ast.KindExpressionWithTypeArguments: true, + ast.KindComputedPropertyName: true, + ast.KindDecorator: true, + ast.KindParameter: true, + ast.KindBindingElement: true, + ast.KindInferType: true, + ast.KindExportSpecifier: true, + ast.KindTypeQuery: true, + ast.KindJSDocParameterTag: true, + ast.KindJSDocReturnTag: true, + ast.KindJSDocTemplateTag: true, +} + func (r *NameResolver) Resolve(location *ast.Node, name string, meaning ast.SymbolFlags, nameNotFoundMessage *diagnostics.Message, isUse bool, excludeGlobals bool) *ast.Symbol { var result *ast.Symbol var lastLocation *ast.Node @@ -34,10 +82,34 @@ func (r *NameResolver) Resolve(location *ast.Node, name string, meaning ast.Symb nameIsConst := name == "const" loop: for location != nil { - if nameIsConst && ast.IsConstAssertion(location) { - // `const` in an `as const` has no symbol, but issues no error because there is no *actual* lookup of the type - // (it refers to the constant type of the expression instead) - return nil + if !nameIsConst { + for { + if location.Kind == ast.KindBlock { + if locals := location.AsBlock().Locals; locals.Len() != 0 { + if result = r.lookup(locals, name, meaning); result != nil { + break loop + } + } + } else if isActiveScopeKind[location.Kind] { + break + } + lastLocation = location + location = location.Parent + if location == nil { + break loop + } + } + } else { + if ast.IsConstAssertion(location) { + // `const` in an `as const` has no symbol, but issues no error because there is no *actual* lookup of the type + // (it refers to the constant type of the expression instead) + return nil + } + if !isActiveScopeKind[location.Kind] { + lastLocation = location + location = location.Parent + continue + } } if ast.IsModuleOrEnumDeclaration(location) && lastLocation != nil && location.Name() == lastLocation { // If lastLocation is the name of a namespace or enum, skip the parent since it will have is own locals that could @@ -50,7 +122,7 @@ loop: lastLocation == location.AsModuleDeclaration().Attributes locals := location.Locals() // Locals of a source file are not in scope (because they get merged into the global symbol table) - if locals != nil && !ast.IsGlobalSourceFile(location) { + if locals.Len() != 0 && !ast.IsGlobalSourceFile(location) { result = r.lookup(locals, name, meaning) if result != nil { useResult := true @@ -99,7 +171,7 @@ loop: result = nil } } - withinDeferredContext = withinDeferredContext || getIsDeferredContext(location, lastLocation) + withinDeferredContext = withinDeferredContext || (nameNotFoundMessage != nil && meaning&ast.SymbolFlagsValue == ast.SymbolFlagsValue && getIsDeferredContext(location, lastLocation)) switch location.Kind { case ast.KindSourceFile: if !ast.IsExternalOrCommonJSModule(location.AsSourceFile()) { @@ -115,6 +187,9 @@ loop: break } moduleExports := moduleSymbol.Exports() + if moduleExports.Len() == 0 { + break + } if ast.IsSourceFile(location) || (ast.IsModuleDeclaration(location) && location.Flags&ast.NodeFlagsAmbient != 0 && !ast.IsGlobalScopeAugmentation(location)) { // It's an external module. First see if the module has an export default and if the local // name of that export default matches. @@ -141,6 +216,26 @@ loop: if moduleExport != nil && moduleExport.Flags() == ast.SymbolFlagsAlias && (ast.GetDeclarationOfKind(moduleExport, ast.KindExportSpecifier) != nil || ast.GetDeclarationOfKind(moduleExport, ast.KindNamespaceExport) != nil) { break } + if r.Lookup == nil { + if moduleExport == nil { + break + } + if name != ast.InternalSymbolNameDefault { + if r.LookupSymbol != nil { + result = r.LookupSymbol(moduleExport, meaning&ast.SymbolFlagsModuleMember) + } else if moduleExport.Flags()&(meaning&ast.SymbolFlagsModuleMember) != 0 { + result = moduleExport + } + if result != nil { + if ast.IsSourceFile(location) && location.AsSourceFile().CommonJSModuleIndicator != nil && result.Flags()&ast.SymbolFlagsType == 0 { + result = nil + } else { + break loop + } + } + } + break + } } if name != ast.InternalSymbolNameDefault { if result = r.lookup(moduleExports, name, meaning&ast.SymbolFlagsModuleMember); result != nil { @@ -176,23 +271,25 @@ loop: } } case ast.KindClassDeclaration, ast.KindClassExpression, ast.KindInterfaceDeclaration: - result = r.lookup(r.getSymbolOfDeclaration(location).Members(), name, meaning&ast.SymbolFlagsType) - if result != nil { - if !isTypeParameterSymbolDeclaredInContainer(result, location) { - // ignore type parameters not declared in this container - result = nil - break - } - if lastLocation != nil && ast.IsStatic(lastLocation) { - // TypeScript 1.0 spec (April 2014): 3.4.1 - // The scope of a type parameter extends over the entire declaration with which the type - // parameter list is associated, with the exception of static member declarations in classes. - if nameNotFoundMessage != nil { - r.error(originalLocation, diagnostics.Static_members_cannot_reference_class_type_parameters) + if meaning&ast.SymbolFlagsTypeParameter != 0 && (len(location.TypeParameters()) != 0 || ast.IsInJSFile(location)) { + result = r.lookup(r.getSymbolOfDeclaration(location).Members(), name, meaning&ast.SymbolFlagsType) + if result != nil { + if !isTypeParameterSymbolDeclaredInContainer(result, location) { + // ignore type parameters not declared in this container + result = nil + break } - return nil + if lastLocation != nil && ast.IsStatic(lastLocation) { + // TypeScript 1.0 spec (April 2014): 3.4.1 + // The scope of a type parameter extends over the entire declaration with which the type + // parameter list is associated, with the exception of static member declarations in classes. + if nameNotFoundMessage != nil { + r.error(originalLocation, diagnostics.Static_members_cannot_reference_class_type_parameters) + } + return nil + } + break loop } - break loop } if ast.IsClassExpression(location) && meaning&ast.SymbolFlagsClass != 0 { className := location.Name() @@ -204,7 +301,7 @@ loop: case ast.KindExpressionWithTypeArguments: if lastLocation == location.Expression() && ast.IsHeritageClause(location.Parent) && location.Parent.AsHeritageClause().Token == ast.KindExtendsKeyword { container := location.Parent.Parent - if ast.IsClassLike(container) { + if ast.IsClassLike(container) && meaning&ast.SymbolFlagsTypeParameter != 0 && (len(container.TypeParameters()) != 0 || ast.IsInJSFile(container)) { result = r.lookup(r.getSymbolOfDeclaration(container).Members(), name, meaning&ast.SymbolFlagsType) if result != nil { if nameNotFoundMessage != nil { @@ -223,8 +320,7 @@ loop: // } case ast.KindComputedPropertyName: grandparent = location.Parent.Parent - if ast.IsClassLike(grandparent) || ast.IsInterfaceDeclaration(grandparent) { - // A reference to this grandparent's type parameters would be an error + if (ast.IsClassLike(grandparent) || ast.IsInterfaceDeclaration(grandparent)) && meaning&ast.SymbolFlagsTypeParameter != 0 && (len(grandparent.TypeParameters()) != 0 || ast.IsInJSFile(grandparent)) { result = r.lookup(r.getSymbolOfDeclaration(grandparent).Members(), name, meaning&ast.SymbolFlagsType) if result != nil { if nameNotFoundMessage != nil { @@ -424,6 +520,15 @@ func (r *NameResolver) getSymbolOfDeclaration(node *ast.Node) *ast.Symbol { } func (r *NameResolver) lookup(symbols ast.SymbolTable, name string, meaning ast.SymbolFlags) *ast.Symbol { + if meaning == 0 || symbols.Len() == 0 { + return nil + } + if r.LookupSymbol != nil { + if symbol := symbols.Get(name); symbol != nil { + return r.LookupSymbol(symbol, meaning) + } + return nil + } if r.Lookup != nil { return r.Lookup(symbols, name, meaning) } diff --git a/tsc/internal/checker/flow.go b/tsc/internal/checker/flow.go index 05daef984819d..d9d4ac3737b7b 100644 --- a/tsc/internal/checker/flow.go +++ b/tsc/internal/checker/flow.go @@ -167,8 +167,8 @@ func (c *Checker) getTypeAtFlowNode(f *FlowState, flow *ast.FlowNode) FlowType { } t = c.getTypeAtFlowBranchLabel(f, flow, antecedents) case flags&ast.FlowFlagsLoopLabel != 0: - if flow.Antecedents.Next == nil { - flow = flow.Antecedents.Flow + if flow.Antecedents().Next == nil { + flow = flow.Antecedents().Flow continue } t = c.getTypeAtFlowLoopLabel(f, flow) @@ -214,7 +214,7 @@ func getBranchLabelAntecedents(flow *ast.FlowNode, reduceLabels []*ast.FlowReduc return data.Antecedents } } - return flow.Antecedents + return flow.Antecedents() } func (c *Checker) getTypeAtFlowAssignment(f *FlowState, flow *ast.FlowNode) FlowType { @@ -1354,7 +1354,7 @@ func (c *Checker) getTypeAtFlowLoopLabel(f *FlowState, flow *ast.FlowNode) FlowT antecedentTypes := make([]*Type, 0, 4) subtypeReduction := false var firstAntecedentType FlowType - for list := flow.Antecedents; list != nil; list = list.Next { + for list := flow.Antecedents(); list != nil; list = list.Next { var flowType FlowType if firstAntecedentType.isNil() { // The first antecedent of a loop junction is always the non-looping control @@ -2572,11 +2572,11 @@ func (c *Checker) isReachableFlowNodeWorker(f *FlowState, flow *ast.FlowNode, no } return false case flags&ast.FlowFlagsLoopLabel != 0: - if flow.Antecedents == nil { + if flow.Antecedents() == nil { return false } // A loop is reachable if the control flow path that leads to the top is reachable. - flow = flow.Antecedents.Flow + flow = flow.Antecedents().Flow case flags&ast.FlowFlagsSwitchClause != 0: // The control flow path representing an unmatched value in a switch statement with // no default clause is unreachable if the switch statement is exhaustive. @@ -2650,7 +2650,7 @@ func (c *Checker) isPostSuperFlowNodeWorker(f *FlowState, flow *ast.FlowNode, no return true case flags&ast.FlowFlagsLoopLabel != 0: // A loop is post-super if the control flow path that leads to the top is post-super. - flow = flow.Antecedents.Flow + flow = flow.Antecedents().Flow case flags&ast.FlowFlagsReduceLabel != 0: f.reduceLabels = append(f.reduceLabels, flow.Node.AsFlowReduceLabelData()) result := c.isPostSuperFlowNodeWorker(f, flow.Antecedent, false /*noCacheCheck*/) diff --git a/tsc/internal/parser/parser.go b/tsc/internal/parser/parser.go index c670d64ad21c2..8d1eaf2c41aab 100644 --- a/tsc/internal/parser/parser.go +++ b/tsc/internal/parser/parser.go @@ -4,6 +4,7 @@ import ( "slices" "strings" "sync" + "sync/atomic" "unicode/utf8" "github.com/microsoft/TypeScript/tsc/internal/ast" @@ -98,6 +99,9 @@ type Parser struct { currentParent *ast.Node setParentFromContext ast.Visitor reparsedClones []*ast.Node + freeNodeLists [][]*ast.Node + lazyDts bool + lazyLists []*ast.LazyNodeList } func newParser() *Parser { @@ -122,15 +126,49 @@ var parserPool = sync.Pool{ }, } +var lazyDtsEnabled atomic.Bool + +func EnableLazyDts(enabled bool) { + lazyDtsEnabled.Store(enabled) +} + +func init() { + ast.SetLazyParseHook(reparseLazyList) +} + func getParser() *Parser { return parserPool.Get().(*Parser) } func putParser(p *Parser) { - *p = Parser{scanner: p.scanner, setParentFromContext: p.setParentFromContext} + p.factory.ResetCounts() + *p = Parser{ + scanner: p.scanner, + factory: p.factory, + nodeSliceArena: p.nodeSliceArena, + stringSliceArena: p.stringSliceArena, + setParentFromContext: p.setParentFromContext, + freeNodeLists: p.freeNodeLists, + } parserPool.Put(p) } +func (p *Parser) allocNodeList() []*ast.Node { + if n := len(p.freeNodeLists); n > 0 { + list := p.freeNodeLists[n-1] + p.freeNodeLists = p.freeNodeLists[:n-1] + return list[:0] + } + return make([]*ast.Node, 0, 16) +} + +func (p *Parser) freeNodeList(list []*ast.Node) { + if list != nil { + clear(list) + p.freeNodeLists = append(p.freeNodeLists, list[:0]) + } +} + func ParseSourceFile(opts ast.SourceFileParseOptions, sourceText string, scriptKind core.ScriptKind) *ast.SourceFile { p := getParser() defer putParser(p) @@ -431,6 +469,7 @@ func (p *Parser) parseSourceFileWorker() *ast.SourceFile { isDeclarationFile := p.opts.FileName.IsDeclarationFile() if isDeclarationFile { p.contextFlags |= ast.NodeFlagsAmbient + p.lazyDts = lazyDtsEnabled.Load() } pos := p.nodePos() statements := p.parseListIndex(PCSourceElements, (*Parser).parseToplevelStatement) @@ -483,6 +522,10 @@ func (p *Parser) finishSourceFile(result *ast.SourceFile, isDeclarationFile bool slices.SortFunc(p.reparsedClones, ast.CompareNodePositions) result.ReparsedClones = slices.Clone(p.reparsedClones) ast.SetExternalModuleIndicator(result, p.opts.ExternalModuleIndicatorOptions) + if len(p.lazyLists) > 0 { + result.LazyLists = slices.Clone(p.lazyLists) + p.lazyLists = nil + } } func (p *Parser) createJSDocCache() map[*ast.Node][]*ast.Node { @@ -610,14 +653,23 @@ func (p *Parser) reparseTopLevelAwait(sourceFile *ast.SourceFile) *ast.Node { } func (p *Parser) parseListIndex(kind ParsingContext, parseElement func(p *Parser, index int) *ast.Node) []*ast.Node { + return p.parseListWorker(kind, nil, parseElement) +} + +func (p *Parser) parseListWorker(kind ParsingContext, parseElement func(p *Parser) *ast.Node, parseElementIndex func(p *Parser, index int) *ast.Node) []*ast.Node { saveParsingContexts := p.parsingContexts p.parsingContexts |= 1 << kind outerReparseList := p.reparseList p.reparseList = nil - list := make([]*ast.Node, 0, 16) + list := p.allocNodeList() for i := 0; !p.isListTerminator(kind); i++ { if p.isListElement(kind, false /*inErrorRecovery*/) { - elt := parseElement(p, len(list)) + var elt *ast.Node + if parseElement != nil { + elt = parseElement(p) + } else { + elt = parseElementIndex(p, len(list)) + } if len(p.reparseList) != 0 { for _, e := range p.reparseList { // Propagate @typedef type alias declarations outwards to a context that permits them. @@ -638,27 +690,296 @@ func (p *Parser) parseListIndex(kind ParsingContext, parseElement func(p *Parser } p.reparseList = outerReparseList p.parsingContexts = saveParsingContexts - return p.nodeSliceArena.Clone(list) + res := p.nodeSliceArena.Clone(list) + p.freeNodeList(list) + return res } func (p *Parser) parseList(kind ParsingContext, parseElement func(p *Parser) *ast.Node) *ast.NodeList { pos := p.nodePos() - nodes := p.parseListIndex(kind, func(p *Parser, _ int) *ast.Node { return parseElement(p) }) + nodes := p.parseListWorker(kind, parseElement, nil) return p.newNodeList(core.NewTextRange(pos, p.nodePos()), nodes) } +const lazyListMaxText = 65536 + +func (p *Parser) parseMemberList(kind ParsingContext, parseElement func(p *Parser) *ast.Node) *ast.NodeList { + if p.lazyDts && p.contextFlags&ast.NodeFlagsJSDoc == 0 && p.token != ast.KindCloseBraceToken { + return p.parseMemberListLazily(kind, parseElement) + } + return p.parseList(kind, parseElement) +} + +func (p *Parser) parseMemberListLazily(kind ParsingContext, parseElement func(p *Parser) *ast.Node) *ast.NodeList { + if !p.hasParseError && + !p.scanner.HasPrecedingJSDocWithSeeOrLink() && + !p.scanner.HasPrecedingJSDocWithDeprecatedTag() { + if lastTokenEnd, ok := scanBalancedBraces(p.sourceText, p.scanner.TokenStart(), kind == PCClassMembers); ok { + startPos := p.nodePos() + contextFlags := p.contextFlags + parsingContexts := p.parsingContexts + beforeCommentDirectives := len(p.scanner.CommentDirectives()) + markState := p.mark() + p.scanner.ResetTokenState(lastTokenEnd) + p.nextToken() + if p.token == ast.KindCloseBraceToken && + !p.hasParseError && + len(p.scanner.CommentDirectives()) == beforeCommentDirectives && + !p.scanner.HasPrecedingJSDocWithSeeOrLink() && + !p.scanner.HasPrecedingJSDocWithDeprecatedTag() { + loc := core.NewTextRange(startPos, p.nodePos()) + lazy := ast.NewLazyNodeList(loc, uint8(kind), contextFlags, uint32(parsingContexts)) + p.lazyLists = append(p.lazyLists, lazy) + return &lazy.NodeList + } + p.rewind(markState) + } + } + return p.parseList(kind, parseElement) +} + +func scanBalancedBraces(text string, startPos int, isClass bool) (int, bool) { + maxEnd := min(len(text), startPos+lazyListMaxText) + i := startPos + depth := 0 + parenDepth := 0 + bracketDepth := 0 + lastTokenEnd := startPos + for i < maxEnd { + c := text[i] + switch c { + case ' ', '\t', '\r', '\n': + i++ + case '}': + if depth == 0 { + if parenDepth != 0 || bracketDepth != 0 || lastTokenEnd == startPos { + return 0, false + } + return lastTokenEnd, true + } + depth-- + i++ + lastTokenEnd = i + case '{': + if isClass && depth == 0 && parenDepth == 0 && bracketDepth == 0 { + return 0, false + } + depth++ + i++ + lastTokenEnd = i + case '(': + parenDepth++ + i++ + lastTokenEnd = i + case ')': + if parenDepth == 0 { + return 0, false + } + parenDepth-- + i++ + lastTokenEnd = i + case '[': + bracketDepth++ + i++ + lastTokenEnd = i + case ']': + if bracketDepth == 0 { + return 0, false + } + bracketDepth-- + i++ + lastTokenEnd = i + case '"', '\'': + quote := c + i++ + for i < maxEnd { + ch := text[i] + if ch == '\\' { + i += 2 + continue + } + if ch == '\n' || ch == '\r' { + return 0, false + } + i++ + if ch == quote { + break + } + } + if i > maxEnd || text[i-1] != quote { + return 0, false + } + lastTokenEnd = i + case '`': + i++ + for i < maxEnd { + ch := text[i] + if ch == '\\' { + i += 2 + continue + } + if ch == '$' && i+1 < maxEnd && text[i+1] == '{' { + return 0, false + } + i++ + if ch == '`' { + break + } + } + if i > maxEnd || text[i-1] != '`' { + return 0, false + } + lastTokenEnd = i + case '/': + if i+1 >= maxEnd { + return 0, false + } + next := text[i+1] + if next == '/' { + i += 2 + for i < maxEnd && text[i] != '\n' { + if text[i] == '@' || text[i] == '/' { + return 0, false + } + i++ + } + } else if next == '*' { + i += 2 + closed := false + for i+1 < maxEnd { + if text[i] == '@' { + return 0, false + } + if text[i] == '*' && text[i+1] == '/' { + i += 2 + closed = true + break + } + i++ + } + if !closed { + return 0, false + } + } else { + return 0, false + } + case '@', '#': + return 0, false + case '=': + if i+1 < maxEnd && text[i+1] == '>' { + i += 2 + lastTokenEnd = i + continue + } + if isClass && depth == 0 { + return 0, false + } + i++ + lastTokenEnd = i + default: + if c >= 0x80 { + return 0, false + } + if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == '$' { + wordStart := i + i++ + for i < maxEnd { + ch := text[i] + if (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_' || ch == '$' { + i++ + } else if ch >= 0x80 { + return 0, false + } else { + break + } + } + word := text[wordStart:i] + switch len(word) { + case 4: + if word == "this" || word == "void" { + return 0, false + } + case 5: + if word == "super" || word == "infer" || word == "async" || word == "await" || word == "yield" || word == "class" { + return 0, false + } + case 6: + if word == "import" || word == "delete" { + return 0, false + } + case 8: + if word == "function" { + return 0, false + } + case 9: + if word == "satisfies" { + return 0, false + } + } + lastTokenEnd = i + } else if c >= '0' && c <= '9' { + i++ + for i < maxEnd { + ch := text[i] + if (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_' || ch == '.' { + i++ + } else { + break + } + } + lastTokenEnd = i + } else { + i++ + lastTokenEnd = i + } + } + } + return 0, false +} + +func reparseLazyList(lazy *ast.LazyNodeList, file *ast.SourceFile, allowNestedLazy bool) []*ast.Node { + p := getParser() + defer putParser(p) + p.initializeState(file.ParseOptions(), file.Text(), file.ScriptKind) + p.lazyDts = allowNestedLazy && lazyDtsEnabled.Load() + p.scanner.ResetTokenState(lazy.Pos()) + p.nextToken() + p.contextFlags = lazy.ContextFlags + p.parsingContexts = ParsingContexts(lazy.ParsingContexts) + var list *ast.NodeList + if ParsingContext(lazy.ParsingContext) == PCClassMembers { + list = p.parseList(PCClassMembers, (*Parser).parseClassElement) + } else { + list = p.parseList(PCTypeMembers, (*Parser).parseTypeMember) + } + owner := lazy.Owner + nodes := list.Nodes + for _, m := range nodes { + m.Parent = owner + } + if len(p.lazyLists) > 0 { + for _, nested := range p.lazyLists { + nested.File = file + } + lazy.SetNestedLazyLists(slices.Clone(p.lazyLists)) + p.lazyLists = nil + } + return nodes +} + // Return a non-nil (but possibly empty) slice if parsing was successful, or nil if parseElement returned nil func (p *Parser) parseDelimitedList(kind ParsingContext, parseElement func(p *Parser) *ast.Node) *ast.NodeList { pos := p.nodePos() saveParsingContexts := p.parsingContexts p.parsingContexts |= 1 << kind - list := make([]*ast.Node, 0, 16) + list := p.allocNodeList() for { if p.isListElement(kind, false /*inErrorRecovery*/) { startPos := p.nodePos() element := parseElement(p) if element == nil { p.parsingContexts = saveParsingContexts + p.freeNodeList(list) // Return nil to indicate parseElement failed return nil } @@ -702,7 +1023,9 @@ func (p *Parser) parseDelimitedList(kind ParsingContext, parseElement func(p *Pa } } p.parsingContexts = saveParsingContexts - return p.newNodeList(core.NewTextRange(pos, p.nodePos()), p.nodeSliceArena.Clone(list)) + res := p.newNodeList(core.NewTextRange(pos, p.nodePos()), p.nodeSliceArena.Clone(list)) + p.freeNodeList(list) + return res } // Return a non-nil (but possibly empty) NodeList if parsing was successful, a missing NodeList if the opening @@ -1761,7 +2084,7 @@ func (p *Parser) parseClassDeclarationOrExpression(pos int, jsdoc jsdocScannerIn if p.parseExpected(ast.KindOpenBraceToken) { // ClassTail[Yield,Await] : (Modified) See 14.5 // ClassHeritage[?Yield,?Await]opt { ClassBody[?Yield,?Await]opt } - members = p.parseList(PCClassMembers, (*Parser).parseClassElement) + members = p.parseMemberList(PCClassMembers, (*Parser).parseClassElement) p.parseExpected(ast.KindCloseBraceToken) } else { members = p.createMissingList() @@ -1776,7 +2099,8 @@ func (p *Parser) parseClassDeclarationOrExpression(pos int, jsdoc jsdocScannerIn } else { result = p.factory.NewClassExpression(modifiers, name, typeParameters, heritageClauses, members) } - p.finishNode(result, pos) + ast.SetLazyOwner(members, result) + result = p.finishNode(result, pos) p.withJSDoc(result, jsdoc) if result.Flags&ast.NodeFlagsJavaScriptFile != 0 { p.checkJSSyntax(result) @@ -2133,6 +2457,7 @@ func (p *Parser) parseInterfaceDeclaration(pos int, jsdoc jsdocScannerInfo, modi heritageClauses := p.parseHeritageClauses(true /*isInterface*/) members := p.parseObjectTypeMembers() result := p.finishNode(p.factory.NewInterfaceDeclaration(modifiers, name, typeParameters, heritageClauses, members), pos) + ast.SetLazyOwner(members, result) p.withJSDoc(result, jsdoc) p.checkJSSyntax(result) return result @@ -2721,12 +3046,13 @@ func (p *Parser) parseUnionOrIntersectionType(operator ast.Kind, parseConstituen typeNode = parseConstituentType(p) } if p.token == operator || hasLeadingOperator { - types := make([]*ast.Node, 1, 8) - types[0] = typeNode + types := append(p.allocNodeList(), typeNode) for p.parseOptional(operator) { types = append(types, p.parseFunctionOrConstructorTypeToError(isUnionType, parseConstituentType)) } - typeNode = p.createUnionOrIntersectionTypeNode(operator, p.newNodeList(core.NewTextRange(pos, p.nodePos()), p.nodeSliceArena.Clone(types))) + cloned := p.nodeSliceArena.Clone(types) + p.freeNodeList(types) + typeNode = p.createUnionOrIntersectionTypeNode(operator, p.newNodeList(core.NewTextRange(pos, p.nodePos()), cloned)) p.finishNode(typeNode, pos) } return typeNode @@ -3671,13 +3997,15 @@ func (p *Parser) parsePropertyOrMethodSignature(pos int, jsdoc jsdocScannerInfo, func (p *Parser) parseTypeLiteral() *ast.Node { pos := p.nodePos() - result := p.finishNode(p.factory.NewTypeLiteralNode(p.parseObjectTypeMembers()), pos) + members := p.parseObjectTypeMembers() + result := p.finishNode(p.factory.NewTypeLiteralNode(members), pos) + ast.SetLazyOwner(members, result) return result } func (p *Parser) parseObjectTypeMembers() *ast.NodeList { if p.parseExpected(ast.KindOpenBraceToken) { - members := p.parseList(PCTypeMembers, (*Parser).parseTypeMember) + members := p.parseMemberList(PCTypeMembers, (*Parser).parseTypeMember) p.parseExpected(ast.KindCloseBraceToken) return members } @@ -3940,10 +4268,13 @@ func (p *Parser) parseModifiersEx(allowDecorators bool, permitConstAsModifier bo // It is illegal to have both leadingDecorators and trailingDecorators, but we will report that as a grammar check in the checker. // parse leading decorators pos := p.nodePos() - list := make([]*ast.Node, 0, 16) + var list []*ast.Node for { if allowDecorators && p.token == ast.KindAtToken && !hasTrailingModifier { decorator := p.parseDecorator() + if list == nil { + list = p.allocNodeList() + } list = append(list, decorator) if hasLeadingModifier { hasTrailingDecorator = true @@ -3956,6 +4287,9 @@ func (p *Parser) parseModifiersEx(allowDecorators bool, permitConstAsModifier bo if modifier.Kind == ast.KindStaticKeyword { hasStaticModifier = true } + if list == nil { + list = p.allocNodeList() + } list = append(list, modifier) if hasTrailingDecorator { hasTrailingModifier = true @@ -3965,7 +4299,9 @@ func (p *Parser) parseModifiersEx(allowDecorators bool, permitConstAsModifier bo } } if len(list) != 0 { - return p.newModifierList(core.NewTextRange(pos, p.nodePos()), p.nodeSliceArena.Clone(list)) + res := p.newModifierList(core.NewTextRange(pos, p.nodePos()), p.nodeSliceArena.Clone(list)) + p.freeNodeList(list) + return res } return nil } diff --git a/tsc/internal/printer/printer.go b/tsc/internal/printer/printer.go index 28e6dbf572fc1..3c0731aeaf5eb 100644 --- a/tsc/internal/printer/printer.go +++ b/tsc/internal/printer/printer.go @@ -4736,8 +4736,10 @@ func (p *Printer) emitListRange(emit func(p *Printer, node *ast.Node), parentNod isNil := children == nil length := 0 + var childrenNodes []*ast.Node if !isNil { - length = len(children.Nodes) + childrenNodes = children.Slice() + length = len(childrenNodes) } if start < 0 { @@ -4784,7 +4786,7 @@ func (p *Printer) emitListRange(emit func(p *Printer, node *ast.Node), parentNod } else { end := min(start+count, length) - p.emitListItems(emit, parentNode, children.Nodes[start:end], format, p.hasTrailingComma(parentNode, children), children.Loc) + p.emitListItems(emit, parentNode, childrenNodes[start:end], format, p.hasTrailingComma(parentNode, children), children.Loc) } if p.OnAfterEmitNodeList != nil { From 3cdd9d1a4b22e4655e51e80a359d0a9e6f4ed7b7 Mon Sep 17 00:00:00 2001 From: Steven Chong <25894545+teamchong@users.noreply.github.com> Date: Mon, 26 Oct 2026 14:46:04 -0400 Subject: [PATCH 05/10] perf(checker): compact Type/Signature/MemberIndex layouts and inline hot paths - Replace interface-dispatched Type, Signature, IndexInfo, and TypeMapper variants with concrete struct hierarchies and lazy extra allocations in Checker arenas. - Replace pointer-keyed NodeLinks/SymbolLinks stores with 32-bit node/symbol ID indexed PagedLinkStore and PagedValueLinkStore tables. - Precompute ObjectFlagsHasGenericArgumentsComputed, ObjectFlagsCouldContainTypeVariablesComputed, and ObjectFlagsIdenticalBaseTypeExists, and add unified ValueSymbolLinks.resolvedType and merged-symbol lookup fast paths. --- packages/typescript/enums/objectFlags.d.ts | 2 + packages/typescript/enums/objectFlags.js | 2 + .../typescript/src/api/proto.generated.ts | 1 + tsc/internal/api/enum_values_generated.go | 3 + tsc/internal/ast/symbol.go | 36 + tsc/internal/checker/checker.go | 4954 +++++++++++++---- tsc/internal/checker/checker_test.go | 46 + tsc/internal/checker/emitsupport.go | 1 - tsc/internal/checker/exports.go | 4 +- tsc/internal/checker/flow.go | 6 +- tsc/internal/checker/grammarchecks.go | 26 +- tsc/internal/checker/jsx.go | 25 +- tsc/internal/checker/links.go | 136 +- tsc/internal/checker/mapper.go | 145 +- tsc/internal/checker/nodebuilder_hover.go | 2 +- tsc/internal/checker/nodebuilderimpl.go | 21 +- tsc/internal/checker/nodebuilderscopes.go | 2 +- tsc/internal/checker/pseudotypenodebuilder.go | 8 +- tsc/internal/checker/relater.go | 21 +- tsc/internal/checker/services.go | 26 +- tsc/internal/checker/tracer.go | 10 +- tsc/internal/checker/types.go | 514 +- tsc/internal/checker/types_test.go | 101 +- tsc/internal/checker/utilities.go | 188 +- 24 files changed, 5029 insertions(+), 1251 deletions(-) diff --git a/packages/typescript/enums/objectFlags.d.ts b/packages/typescript/enums/objectFlags.d.ts index fd17f37c6fc0b..e51db9eca2ca7 100644 --- a/packages/typescript/enums/objectFlags.d.ts +++ b/packages/typescript/enums/objectFlags.d.ts @@ -37,6 +37,8 @@ export declare enum ObjectFlags { IdenticalBaseTypeCalculated = 1 << 27, IdenticalBaseTypeExists = 1 << 28, FromTypeNode = 1 << 29, + HasGenericArgumentsComputed = 1 << 30, + HasGenericArguments = 1 << 31, IsGenericTypeComputed = 1 << 22, IsGenericObjectType = 1 << 23, IsGenericIndexType = 1 << 24, diff --git a/packages/typescript/enums/objectFlags.js b/packages/typescript/enums/objectFlags.js index 013bee411f001..7470856e3abcb 100644 --- a/packages/typescript/enums/objectFlags.js +++ b/packages/typescript/enums/objectFlags.js @@ -37,6 +37,8 @@ export var ObjectFlags; ObjectFlags[ObjectFlags["IdenticalBaseTypeCalculated"] = 134217728] = "IdenticalBaseTypeCalculated"; ObjectFlags[ObjectFlags["IdenticalBaseTypeExists"] = 268435456] = "IdenticalBaseTypeExists"; ObjectFlags[ObjectFlags["FromTypeNode"] = 536870912] = "FromTypeNode"; + ObjectFlags[ObjectFlags["HasGenericArgumentsComputed"] = 1073741824] = "HasGenericArgumentsComputed"; + ObjectFlags[ObjectFlags["HasGenericArguments"] = -2147483648] = "HasGenericArguments"; ObjectFlags[ObjectFlags["IsGenericTypeComputed"] = 4194304] = "IsGenericTypeComputed"; ObjectFlags[ObjectFlags["IsGenericObjectType"] = 8388608] = "IsGenericObjectType"; ObjectFlags[ObjectFlags["IsGenericIndexType"] = 16777216] = "IsGenericIndexType"; diff --git a/packages/typescript/src/api/proto.generated.ts b/packages/typescript/src/api/proto.generated.ts index 51b1123b15634..c9ae4908adf5a 100644 --- a/packages/typescript/src/api/proto.generated.ts +++ b/packages/typescript/src/api/proto.generated.ts @@ -1935,4 +1935,5 @@ export interface RawCompilerOptions { singleThreaded?: boolean | undefined; quiet?: boolean | undefined; checkers?: number | undefined; + maxMemory?: number | undefined; } diff --git a/tsc/internal/api/enum_values_generated.go b/tsc/internal/api/enum_values_generated.go index 1697a479539a5..299ccc05fc049 100644 --- a/tsc/internal/api/enum_values_generated.go +++ b/tsc/internal/api/enum_values_generated.go @@ -245,6 +245,8 @@ func main() { "IdenticalBaseTypeCalculated": toInt32(checker.ObjectFlagsIdenticalBaseTypeCalculated), "IdenticalBaseTypeExists": toInt32(checker.ObjectFlagsIdenticalBaseTypeExists), "FromTypeNode": toInt32(checker.ObjectFlagsFromTypeNode), + "HasGenericArgumentsComputed": toInt32(checker.ObjectFlagsHasGenericArgumentsComputed), + "HasGenericArguments": toInt32(checker.ObjectFlagsHasGenericArguments), "IsGenericTypeComputed": toInt32(checker.ObjectFlagsIsGenericTypeComputed), "IsGenericObjectType": toInt32(checker.ObjectFlagsIsGenericObjectType), "IsGenericIndexType": toInt32(checker.ObjectFlagsIsGenericIndexType), @@ -751,6 +753,7 @@ func main() { "PossiblyContainsDeprecatedTag": toInt32(ast.NodeFlagsPossiblyContainsDeprecatedTag), "Unreachable": toInt32(ast.NodeFlagsUnreachable), "ReparserTransformedLiteral": toInt32(ast.NodeFlagsReparserTransformedLiteral), + "CondFlowClean": toInt32(ast.NodeFlagsCondFlowClean), "BlockScoped": toInt32(ast.NodeFlagsBlockScoped), "Constant": toInt32(ast.NodeFlagsConstant), "AwaitUsing": toInt32(ast.NodeFlagsAwaitUsing), diff --git a/tsc/internal/ast/symbol.go b/tsc/internal/ast/symbol.go index 76f1d187f25c9..54d10a96b9d3c 100644 --- a/tsc/internal/ast/symbol.go +++ b/tsc/internal/ast/symbol.go @@ -539,6 +539,42 @@ func (m *SymbolMap) Clone() SymbolTable { return clone } +func (m *SymbolMap) Clear() { + if m == nil { + return + } + clear(m.entries) + m.entries = m.entries[:0] + m.index = nil +} + +func (m *SymbolMap) CopyFrom(src SymbolTable) { + if src == nil || len(src.entries) == 0 { + return + } + if len(m.entries) == 0 { + m.entries = append(m.entries, src.entries...) + if src.index != nil { + m.index = slices.Clone(src.index) + } + return + } + for i := range src.entries { + m.Set(src.keyAt(i), src.symbolAt(i)) + } +} + +func (m *SymbolMap) HasNameProxies() bool { + if m == nil { + return false + } + for _, entry := range m.entries { + if entry.flags == SymbolFlagsNameProxy { + return true + } + } + return false +} const InternalSymbolNamePrefix = "\xFE" // Invalid UTF8 sequence, will never occur as IdentifierName const ( diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 5a5a155797c24..f650ccb2666ed 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -7,6 +7,8 @@ import ( "iter" "maps" "math" + "math/bits" + "runtime" "slices" "strconv" "strings" @@ -71,6 +73,7 @@ type TypeResolution struct { target TypeSystemEntity propertyName TypeSystemPropertyName result bool + serial uint32 } // ContextualInfo @@ -258,8 +261,9 @@ type FlowLoopKey struct { } type FlowLoopInfo struct { - key FlowLoopKey - types []*Type + key FlowLoopKey + types []*Type + serial uint32 } // InferenceFlags @@ -288,6 +292,7 @@ type InferenceContext struct { inferredTypeParameters []*Type // Inferred type parameters for function result intraExpressionInferenceSites []IntraExpressionInferenceSite mapperStorage [2]InferenceTypeMapper + escaped bool } type InferenceInfo struct { @@ -299,6 +304,7 @@ type InferenceInfo struct { topLevel bool // True if all inferences are to top level occurrences isFixed bool // True if inferences are fixed impliedArity int // Implied arity (or -1) + candidateStorage [2]*Type } type InferencePriority int32 @@ -631,8 +637,8 @@ type Checker struct { bigintLiteralTypes map[jsnum.PseudoBigInt]*Type enumLiteralTypes map[EnumLiteralKey]*Type enumNaNLiteralTypes map[*ast.Symbol]*Type - indexedAccessTypes map[CacheHashKey]*Type - templateLiteralTypes map[CacheHashKey]*Type + indexedAccessTypes typeCacheTable + templateLiteralTypes typeCacheTable stringMappingTypes map[StringMappingKey]*Type uniqueESSymbolTypes map[*ast.Symbol]*Type thisExpandoKinds map[*ast.Symbol]thisAssignmentDeclarationKind @@ -658,17 +664,18 @@ type Checker struct { requireSymbol *ast.Symbol unknownSymbol *ast.Symbol unresolvedSymbols map[string]*ast.Symbol - errorTypes map[CacheHashKey]*Type + errorTypes typeCacheTable 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 + tupleTypes typeCacheTable + unionTypes typeCacheTable unionOfUnionTypes map[UnionOfUnionKey]*Type - intersectionTypes map[CacheHashKey]*Type + unionDiscriminants map[*UnionType]unionDiscriminant + intersectionTypes typeCacheTable propertiesTypes map[PropertiesTypesKey]*Type diagnostics ast.DiagnosticsCollection suggestionDiagnostics ast.DiagnosticsCollection @@ -688,21 +695,63 @@ type Checker struct { typeParameterArena core.Arena[TypeParameter] indexedAccessTypeArena core.Arena[IndexedAccessType] conditionalTypeArena core.Arena[ConditionalType] + conditionalTypeExtraArena core.Arena[conditionalTypeExtra] typeListArena core.Arena[*Type] + signatureListArena core.Arena[*Signature] + indexInfoListArena core.Arena[*IndexInfo] outerTypeParameterListArena core.Arena[[]*Type] valueSymbolExtraArena core.Arena[valueSymbolExtra] structuredTypeExtraArena core.Arena[structuredTypeExtra] + typeCacheTableArena core.Arena[typeCacheTable] + typeCacheSlotsArena core.Arena[typeCacheSlot] + freeTypeCacheSlots [20][][]typeCacheSlot + memberIndexArena core.Arena[memberIndex] + memberIndexSlotsArena core.Arena[uint32] lazyStateArena core.Arena[LazyState] memberLayoutArena core.Arena[MemberLayout] + memberInfoListArena core.Arena[MemberInfo] + symbolListArena core.Arena[*ast.Symbol] layoutMemberInfos map[string]MemberInfo + lazyMappedTableArena core.Arena[lazyMappedTable] + simpleMapperArena core.Arena[SimpleTypeMapper] + arrayMapperArena core.Arena[ArrayTypeMapper] + mergedMapperArena core.Arena[MergedTypeMapper] + compositeMapperArena core.Arena[CompositeTypeMapper] + typeAliasArena core.Arena[TypeAlias] + freeTypeArgLists [][]*Type + freeSymbolLists [][]*ast.Symbol + unionFrontCache map[CacheHashKey]*Type + unionFrontSlots *[1024]unionFrontSlot + unionImpureCount uint32 + diagnosticCount uint32 + instantiationCountResets uint32 + resolutionCycleCount uint32 + inferMemoSteps uint32 + inferLongTargets map[*Type]bool + indexedAccessUnionMemo map[indexedAccessUnionKey]*Type + globalObjectAugmentFilter [4]uint64 + unionConstituentFlags map[*Type]TypeFlags + globalObjectFilterReady bool freeTypeSets *temporaryTypeSet freePropertyCounts *temporaryPropertyCounts + freeNameSets []map[string]struct{} + templateScratchPool []*templateLiteralScratch + leafKeyBuilder keyBuilder + namedUnionMemberSet map[*Type]struct{} + unionRunStarts []int + unionRunBounds []int + unionRunNext []int + unionMergeBuf []*Type + symbolSortBuf []symbolSortEntry + nodeFileIndexCache *[4096]nodeFileIndexEntry mergedSymbols symbolLinkStore[*ast.Symbol] mergedExportsChecked collections.Set[*ast.Symbol] factory ast.NodeFactory + nodeIdGen ast.NodeIdGenerator + symbolIdGen ast.SymbolIdGenerator nodeLinks core.LinkStore[*ast.Node, NodeLinks] signatureLinks nodeLinkStore[SignatureLinks] - symbolNodeLinks nodeLinkStore[SymbolNodeLinks] + symbolNodeLinks directNodeLinkStore[SymbolNodeLinks] typeNodeLinks nodeLinkStore[TypeNodeLinks] enumMemberLinks core.LinkStore[*ast.Node, EnumMemberLinks] assertionLinks core.LinkStore[*ast.Node, AssertionLinks] @@ -710,9 +759,7 @@ type Checker struct { 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] @@ -839,11 +886,11 @@ type Checker struct { antecedentTypes []*Type flowAnalysisDisabled bool flowInvocationCount int - flowTypeCache map[*ast.Node]*Type - lastFlowNode *ast.FlowNode - lastFlowNodeReachable bool - flowNodeReachable map[*ast.FlowNode]bool - flowNodePostSuper map[*ast.FlowNode]bool + flowTypeCache map[*ast.Node]*Type + lastFlowNode *ast.FlowNode + lastFlowNodeReachable bool + flowNodeReachable map[*ast.FlowNode]bool + flowNodePostSuper map[*ast.FlowNode]bool renamedBindingElementsInTypes []*ast.Node contextualInfos []ContextualInfo inferenceContextInfos []InferenceContextInfo @@ -913,8 +960,6 @@ type Checker struct { syncIterationTypesResolver *IterationTypesResolver asyncIterationTypesResolver *IterationTypesResolver isPrimitiveOrObjectOrEmptyType func(*Type) bool - containsMissingType func(*Type) bool - couldContainTypeVariables func(*Type) bool isStringIndexSignatureOnlyType func(*Type) bool markNodeAssignments func(*ast.Node) bool compareTypesAssignable TypeComparer @@ -926,6 +971,9 @@ type Checker struct { packagesMap map[string]bool activeMappers []*TypeMapper activeTypeMappersCaches []clearableMap[*Type] + freeObjectLiteralSeen []map[string]DeclarationMeaning + freeSymbolTables []ast.SymbolTable + freeCallStates []*CallState ambientModulesOnce sync.Once ambientModules []*ast.Symbol withinUnreachableCode bool @@ -973,8 +1021,6 @@ func NewChecker(program Program, tracer *Tracer) (*Checker, *sync.Mutex) { 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) @@ -996,18 +1042,19 @@ func NewChecker(program Program, tracer *Tracer) (*Checker, *sync.Mutex) { 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.unionOfUnionTypes = make(map[UnionOfUnionKey]*Type) - c.intersectionTypes = make(map[CacheHashKey]*Type) c.propertiesTypes = make(map[PropertiesTypesKey]*Type) + c.mergedSymbols.gen = &c.symbolIdGen + c.signatureLinks.gen = &c.nodeIdGen + c.symbolNodeLinks.gen = &c.nodeIdGen + c.typeNodeLinks.gen = &c.nodeIdGen + c.valueSymbolLinks.gen = &c.symbolIdGen c.patternForType = make(map[*Type]*ast.Node) c.contextFreeTypes = make(map[*ast.Node]*Type) c.anyType = c.newIntrinsicType(TypeFlagsAny, "any") @@ -1063,7 +1110,7 @@ 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.emptyGenericType.AsObjectType() c.anyFunctionType = c.newAnonymousType(nil /*symbol*/, nil, nil, nil, nil) c.anyFunctionType.objectFlags |= ObjectFlagsNonInferrableType c.noConstraintType = c.newAnonymousType(nil /*symbol*/, nil, nil, nil, nil) @@ -1089,6 +1136,7 @@ func NewChecker(program Program, tracer *Tracer) (*Checker, *sync.Mutex) { 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.lastFlowNode = nil c.flowNodeReachable = make(map[*ast.FlowNode]bool) c.flowNodePostSuper = make(map[*ast.FlowNode]bool) c.subtypeRelation = &Relation{} @@ -1287,15 +1335,15 @@ func (c *Checker) initializeClosures() { c.isPrimitiveOrObjectOrEmptyType = func(t *Type) bool { return t.flags&(TypeFlagsPrimitive|TypeFlagsNonPrimitive) != 0 || c.IsEmptyAnonymousObjectType(t) } - c.containsMissingType = func(t *Type) bool { - return t == c.missingType || t.flags&TypeFlagsUnion != 0 && t.Types()[0] == c.missingType - } - c.couldContainTypeVariables = c.couldContainTypeVariablesWorker c.isStringIndexSignatureOnlyType = c.isStringIndexSignatureOnlyTypeWorker c.markNodeAssignments = c.markNodeAssignmentsWorker c.compareTypesAssignable = c.compareTypesAssignableWorker } +func (c *Checker) containsMissingType(t *Type) bool { + return t == c.missingType || t.flags&TypeFlagsUnion != 0 && t.Types()[0] == c.missingType +} + func (c *Checker) initializeIterationResolvers() { c.syncIterationTypesResolver = &IterationTypesResolver{ iteratorSymbolName: "iterator", @@ -1542,7 +1590,7 @@ func (c *Checker) createNameResolver() *binder.NameResolver { Globals: c.globals, ArgumentsSymbol: c.argumentsSymbol, RequireSymbol: c.requireSymbol, - Lookup: c.getSymbol, + LookupSymbol: c.resolveSymbolLookup, SymbolReferenced: c.symbolReferenced, SetRequiresScopeChangeCache: c.setRequiresScopeChangeCache, GetRequiresScopeChangeCache: c.getRequiresScopeChangeCache, @@ -1568,7 +1616,36 @@ func (c *Checker) createNameResolverForSuggestion() *binder.NameResolver { } func (c *Checker) symbolReferenced(symbol *ast.Symbol, meaning ast.SymbolFlags) { - c.symbolReferenceLinks.Get(symbol).referenceKinds |= meaning + symbol.AddReference(meaning) +} + +func (c *Checker) acquireSymbolTable() ast.SymbolTable { + if n := len(c.freeSymbolTables); n > 0 { + m := c.freeSymbolTables[n-1] + c.freeSymbolTables = c.freeSymbolTables[:n-1] + return m + } + return ast.NewSymbolTable() +} + +func (c *Checker) releaseSymbolTable(m ast.SymbolTable) { + if m != nil { + if m.Len() > 4096 { + return + } + m.Clear() + c.freeSymbolTables = append(c.freeSymbolTables, m) + } +} + +func (c *Checker) releaseSymbolTableIfUnused(t *Type, m ast.SymbolTable) { + if m == nil { + return + } + data := t.AsStructuredType() + if mIdx := data.getMembersIndex(); mIdx == nil || mIdx.fallback == nil { + c.releaseSymbolTable(m) + } } func (c *Checker) getRequiresScopeChangeCache(node *ast.Node) core.Tristate { @@ -2246,14 +2323,23 @@ 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)) + if s := symbols.Get(name); s != nil { + return c.resolveSymbolLookup(s, meaning) + } + } + return nil +} + +func (c *Checker) resolveSymbolLookup(s *ast.Symbol, meaning ast.SymbolFlags) *ast.Symbol { + if meaning&ast.SymbolFlagsAll != 0 { + symbol := c.getMergedSymbol(s) if symbol != nil { if symbol.Flags()&meaning != 0 { return symbol } if symbol.Flags()&ast.SymbolFlagsAlias != 0 { + // 'targetFlags' will be 'SymbolFlags.All' if an error occurred in alias resolution; this avoids cascading errors targetFlags := c.getSymbolFlags(symbol) - // `targetFlags` will be `SymbolFlags.All` if an error occurred in alias resolution; this avoids cascading errors if targetFlags&meaning != 0 { return symbol } @@ -2285,7 +2371,7 @@ func (c *Checker) checkSourceFile(ctx context.Context, sourceFile *ast.SourceFil } c.produceDeferredDiagnostics() c.reportedUnreachableNodes.Clear() - links.typeChecked = true + links.typeChecked = true } if checkUnused && !links.unusedChecked { // The unused identifiers check relies on a full type check having first been performed @@ -2315,6 +2401,7 @@ func (c *Checker) checkSourceElement(node *ast.Node) bool { saveWithinUnreachableCode := c.withinUnreachableCode c.currentNode = node c.instantiationCount = 0 + c.instantiationCountResets++ c.checkSourceElementWorker(node) c.currentNode = saveCurrentNode c.withinUnreachableCode = saveWithinUnreachableCode @@ -2578,6 +2665,7 @@ func (c *Checker) checkDeferredNode(node *ast.Node) { saveCurrentNode := c.currentNode c.currentNode = node c.instantiationCount = 0 + c.instantiationCountResets++ switch node.Kind { case ast.KindCallExpression, ast.KindNewExpression, ast.KindTaggedTemplateExpression, ast.KindDecorator, ast.KindJsxOpeningElement: // These node kinds are deferred checked when overload resolution fails. To save on work, @@ -4590,12 +4678,16 @@ func (c *Checker) issueMemberSpecificError(node *ast.Node, typeWithThis *Type, b func (c *Checker) getTypeWithoutSignatures(t *Type) *Type { switch { case t.flags&TypeFlagsObject != 0: - resolved := c.resolveStructuredTypeMembers(t) - if len(resolved.getSignatures()) != 0 { + if c.typeHasCallOrConstructSignatures(t) { + resolved := c.resolveStructuredTypeMembers(t) result := c.newObjectType(ObjectFlagsAnonymous, t.symbol) result.objectFlags |= ObjectFlagsMembersResolved - result.AsObjectType().members = resolved.getMembers() - result.AsObjectType().properties = resolved.getProperties() + props := resolved.getProperties() + mIdx := resolved.getMembersIndex() + if mIdx == nil && len(props) != 0 { + mIdx = c.newPropertyIndex(props, nil) + } + result.AsObjectType().setMembersAndProperties(mIdx, props) return result } case t.flags&TypeFlagsIntersection != 0: @@ -7278,8 +7370,12 @@ func (c *Checker) checkUnusedIdentifiers(potentiallyUnusedIdentifiers []*ast.Nod } } +func (c *Checker) getReferenceKinds(symbol *ast.Symbol) ast.SymbolFlags { + return symbol.ReferenceKinds() +} + func (c *Checker) isReferenced(symbol *ast.Symbol) bool { - return c.symbolReferenceLinks.Get(symbol).referenceKinds != 0 + return c.getReferenceKinds(symbol) != 0 } type UnusedKind int32 @@ -7348,8 +7444,8 @@ func (c *Checker) checkUnusedClassMembers(node *ast.Node) { func (c *Checker) checkUnusedLocalsAndParameters(node *ast.Node) { var variableParents collections.Set[*ast.Node] var importClauses map[*ast.Node][]*ast.Node - for _, local := range node.Locals().All() { - referenceKinds := c.symbolReferenceLinks.Get(local).referenceKinds + for local := range node.Locals().Values() { + referenceKinds := c.getReferenceKinds(local) if local.Flags()&ast.SymbolFlagsTypeParameter != 0 && (local.Flags()&ast.SymbolFlagsVariable == 0 || referenceKinds&ast.SymbolFlagsVariable != 0) || local.Flags()&ast.SymbolFlagsTypeParameter == 0 && (referenceKinds != 0 || local.ExportSymbol() != nil || local.Flags()&ast.SymbolFlagsModuleExports != 0) { @@ -7434,7 +7530,7 @@ func (c *Checker) isUnreferencedVariableDeclaration(node *ast.Node) bool { if ast.IsBindingPattern(name) { return core.Every(node.Name().Elements(), c.isUnreferencedVariableDeclaration) } - if c.symbolReferenceLinks.Get(c.getSymbolOfDeclaration(node)).referenceKinds&ast.SymbolFlagsVariable != 0 { + if c.getReferenceKinds(c.getSymbolOfDeclaration(node))&ast.SymbolFlagsVariable != 0 { return false } if ast.IsBindingElement(node) && ast.IsObjectBindingPattern(node.Parent) { @@ -7516,12 +7612,12 @@ func (c *Checker) checkUnusedTypeParameters(node *ast.Node) { } func (c *Checker) isUnreferencedTypeParameter(typeParameter *ast.Node) bool { - return c.symbolReferenceLinks.Get(c.getMergedSymbol(typeParameter.Symbol())).referenceKinds&ast.SymbolFlagsTypeParameter == 0 && !isIdentifierThatStartsWithUnderscore(typeParameter.Name()) + return c.getReferenceKinds(c.getMergedSymbol(typeParameter.Symbol()))&ast.SymbolFlagsTypeParameter == 0 && !isIdentifierThatStartsWithUnderscore(typeParameter.Name()) } func (c *Checker) checkUnusedRenamedBindingElements() { for _, node := range c.renamedBindingElementsInTypes { - if c.symbolReferenceLinks.Get(c.getSymbolOfDeclaration(node)).referenceKinds == 0 { + if c.getReferenceKinds(c.getSymbolOfDeclaration(node)) == 0 { wrappingDeclaration := ast.WalkUpBindingElementsAndPatterns(node) debug.Assert(ast.IsPartOfParameterDeclaration(wrappingDeclaration), "Only parameter declaration should be checked here") diagnostic := NewDiagnosticForNode(node.Name(), diagnostics.X_0_is_an_unused_renaming_of_1_Did_you_intend_to_use_it_as_a_type_annotation, scanner.DeclarationNameToString(node.Name()), scanner.DeclarationNameToString(node.PropertyName())) @@ -7550,6 +7646,7 @@ func (c *Checker) getTypeOfExpression(node *ast.Node) *Type { } // If a type has been cached for the node, return it. if cachedType := c.flowTypeCache[node]; cachedType != nil { + c.resolutionCycleCount++ return cachedType } startInvocationCount := c.flowInvocationCount @@ -7729,12 +7826,9 @@ func (c *Checker) checkExpressionCachedEx(node *ast.Node, checkMode CheckMode) * links := c.typeNodeLinks.Get(node) if links.resolvedType == 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. saveFlowLoopStack := c.flowLoopStack - saveFlowTypeCache := c.flowTypeCache + saveFlowTypeCache := c.flowTypeCache; c.flowTypeCache = nil c.flowLoopStack = nil - c.flowTypeCache = nil links.resolvedType = c.checkExpressionEx(node, checkMode) c.flowTypeCache = saveFlowTypeCache c.flowLoopStack = saveFlowLoopStack @@ -7769,6 +7863,7 @@ func (c *Checker) checkExpressionEx(node *ast.Node, checkMode CheckMode) *Type { saveCurrentNode := c.currentNode c.currentNode = node c.instantiationCount = 0 + c.instantiationCountResets++ uninstantiatedType := c.checkExpressionWorker(node, checkMode) t := c.instantiateTypeWithSingleGenericCallSignature(node, uninstantiatedType, checkMode) if isConstEnumObjectType(t) { @@ -7826,6 +7921,10 @@ func (c *Checker) instantiateTypeWithSingleGenericCallSignature(node *ast.Node, c.skippedGenericFunction(node, checkMode) return c.anyFunctionType } + return c.instantiateTypeWithSingleGenericCallSignatureWorker(node, t, signature, contextualSignature) +} + +func (c *Checker) instantiateTypeWithSingleGenericCallSignatureWorker(node *ast.Node, t *Type, signature *Signature, contextualSignature *Signature) *Type { context := c.getInferenceContext(node) // We have an expression that is an argument of a generic function for which we are performing // type argument inference. The expression is of a function type with a single generic call @@ -7834,7 +7933,7 @@ func (c *Checker) instantiateTypeWithSingleGenericCallSignature(node *ast.Node, // if some of the outer function type parameters have no inferences so far. If so, we can // potentially add inferred type parameters to the outer function return type. var returnSignature *Signature - if context.signature != nil { + if context != nil && context.signature != nil { returnType := c.getReturnTypeOfSignature(context.signature) if returnType != nil { returnSignature = c.getSingleCallOrConstructSignature(returnType) @@ -7847,9 +7946,10 @@ func (c *Checker) instantiateTypeWithSingleGenericCallSignature(node *ast.Node, instantiatedSignature := c.getSignatureInstantiationWithoutFillingInTypeArguments(signature, uniqueTypeParameters) // Infer from the parameters of the instantiated signature to the parameters of the // contextual signature starting with an empty set of inference candidates. - inferences := core.Map(context.inferences, func(info *InferenceInfo) *InferenceInfo { - return newInferenceInfo(info.typeParameter) - }) + inferences := make([]*InferenceInfo, len(context.inferences)) + for i, info := range context.inferences { + inferences[i] = newInferenceInfo(info.typeParameter) + } c.applyToParameterTypes(instantiatedSignature, contextualSignature, func(source *Type, target *Type) { c.inferTypes(inferences, source, target, InferencePriorityNone, true /*contravariant*/) }) @@ -7912,6 +8012,7 @@ func (c *Checker) getUniqueTypeParameters(context *InferenceContext, typeParamet for _, tp := range newTypeParameters { tp.AsTypeParameter().mapper = mapper } + markMapperEscaped(mapper) } return result } @@ -8629,24 +8730,21 @@ func (c *Checker) getResolvedSignature(node *ast.Node, candidatesOutArray *[]*Si return cached } saveResolutionStart := c.resolutionStart + // If we haven't already done so, temporarily reset the resolution stack. This allows us to + // handle "inverted" situations where, for example, an API client asks for the type of a symbol + // containined in a function call argument whose contextual type depends on the symbol itself + // 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. if cached == nil { - // If we haven't already done so, temporarily reset the resolution stack. This allows us to - // handle "inverted" situations where, for example, an API client asks for the type of a symbol - // containined in a function call argument whose contextual type depends on the symbol itself - // 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) + } else if cached == c.resolvingSignature { + c.resolutionCycleCount++ } links.resolvedSignature = c.resolvingSignature result := c.resolveSignature(node, candidatesOutArray, checkMode) c.resolutionStart = saveResolutionStart - // When CheckMode.SkipGenericFunctions is set we use resolvingSignature to indicate that call - // resolution should be deferred. if result != c.resolvingSignature { - // if the signature resolution originated on a node that itself depends on the contextual type - // then it's possible that the resolved signature might not be the same as the one that would be computed in source order - // 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 { @@ -9065,13 +9163,44 @@ type CallState struct { candidateForTypeArgumentError *Signature } +func (c *Checker) acquireCallState() *CallState { + if n := len(c.freeCallStates); n > 0 { + s := c.freeCallStates[n-1] + c.freeCallStates = c.freeCallStates[:n-1] + return s + } + return &CallState{} +} + +func (c *Checker) releaseCallState(s *CallState, poolCandidates bool) { + var candidates []*Signature + if poolCandidates && cap(s.candidates) <= 64 { + clear(s.candidates) + candidates = s.candidates[:0] + } + var candidatesForArgumentError []*Signature + if cap(s.candidatesForArgumentError) <= 64 { + clear(s.candidatesForArgumentError) + candidatesForArgumentError = s.candidatesForArgumentError[:0] + } + *s = CallState{ + candidates: candidates, + candidatesForArgumentError: candidatesForArgumentError, + } + c.freeCallStates = append(c.freeCallStates, s) +} + +func (c *Checker) someContextSensitive(args []*ast.Node) bool { + return slices.ContainsFunc(args, c.isContextSensitive) +} + func (c *Checker) resolveCall(node *ast.Node, signatures []*Signature, candidatesOutArray *[]*Signature, checkMode CheckMode, callChainFlags SignatureFlags, headMessage *diagnostics.Message) *Signature { isTaggedTemplate := node.Kind == ast.KindTaggedTemplateExpression isDecorator := node.Kind == ast.KindDecorator isJsxOpeningOrSelfClosingElement := ast.IsJsxOpeningLikeElement(node) isInstanceof := node.Kind == ast.KindBinaryExpression reportErrors := !c.isInferencePartiallyBlocked && candidatesOutArray == nil - var s CallState + s := c.acquireCallState() s.node = node if !isDecorator && !isInstanceof && !isSuperCall(node) && !ast.IsJsxOpeningFragment(node) { s.typeArguments = node.TypeArguments() @@ -9080,14 +9209,16 @@ func (c *Checker) resolveCall(node *ast.Node, signatures []*Signature, candidate c.checkSourceElements(s.typeArguments) } } - s.candidates = c.reorderCandidates(signatures, callChainFlags) if candidatesOutArray != nil { + s.candidates = c.reorderCandidatesInto(nil, signatures, callChainFlags) *candidatesOutArray = s.candidates + } else { + s.candidates = c.reorderCandidatesInto(s.candidates[:0], signatures, callChainFlags) } - if len(s.candidates) == 0 { // In Strada we would error here, but no known repro doesn't have at least // one other error in this codepath. Just return instead. See #54442 + c.releaseCallState(s, candidatesOutArray == nil) return c.unknownSignature } @@ -9105,7 +9236,7 @@ func (c *Checker) resolveCall(node *ast.Node, signatures []*Signature, candidate // For a decorator, no arguments are susceptible to contextual typing due to the fact // decorators are applied to a declaration by the emitter, and not to an expression. s.isSingleNonGenericCandidate = len(s.candidates) == 1 && len(s.candidates[0].TypeParameters()) == 0 - if !isDecorator && !s.isSingleNonGenericCandidate && core.Some(s.args, c.isContextSensitive) { + if !isDecorator && !s.isSingleNonGenericCandidate && c.someContextSensitive(s.args) { s.argCheckMode = CheckModeSkipContextSensitive } else { s.argCheckMode = CheckModeNormal @@ -9148,13 +9279,14 @@ func (c *Checker) resolveCall(node *ast.Node, signatures []*Signature, candidate s.recursiveResolution = slices.Contains(c.callResolutionStack, s.node) c.callResolutionStack = append(c.callResolutionStack, s.node) if len(s.candidates) > 1 { - result = c.chooseOverload(&s, c.subtypeRelation) + result = c.chooseOverload(s, c.subtypeRelation) } if result == nil { - result = c.chooseOverload(&s, c.assignableRelation) + result = c.chooseOverload(s, c.assignableRelation) } c.callResolutionStack = c.callResolutionStack[:len(c.callResolutionStack)-1] if result != nil { + c.releaseCallState(s, candidatesOutArray == nil) return result } result = c.getCandidateForOverloadFailure(s.node, s.candidates, s.args, candidatesOutArray != nil, checkMode) @@ -9177,19 +9309,39 @@ func (c *Checker) resolveCall(node *ast.Node, signatures []*Signature, candidate if headMessage == nil && isInstanceof { headMessage = diagnostics.The_left_hand_side_of_an_instanceof_expression_must_be_assignable_to_the_first_argument_of_the_right_hand_side_s_Symbol_hasInstance_method } - c.reportCallResolutionErrors(node, &s, signatures, headMessage) + c.reportCallResolutionErrors(node, s, signatures, headMessage) } + c.releaseCallState(s, candidatesOutArray == nil) return result } func (c *Checker) reorderCandidates(signatures []*Signature, callChainFlags SignatureFlags) []*Signature { + return c.reorderCandidatesInto(nil, signatures, callChainFlags) +} + +func (c *Checker) reorderCandidatesInto(dst []*Signature, signatures []*Signature, callChainFlags SignatureFlags) []*Signature { + if len(signatures) == 0 { + return dst[:0] + } + if len(signatures) == 1 { + sig := signatures[0] + if callChainFlags != 0 { + sig = c.getOptionalCallSignature(sig, callChainFlags) + } + return append(dst[:0], sig) + } var lastParent *ast.Node var lastSymbol *ast.Symbol var index int var cutoffIndex int var spliceIndex int specializedIndex := -1 - result := make([]*Signature, 0, len(signatures)) + var result []*Signature + if cap(dst) >= len(signatures) { + result = dst[:0] + } else { + result = make([]*Signature, 0, len(signatures)) + } for _, signature := range signatures { var symbol *ast.Symbol var parent *ast.Node @@ -9251,7 +9403,8 @@ func (c *Checker) getOptionalCallSignature(signature *Signature, callChainFlags } func (c *Checker) chooseOverload(s *CallState, relation *Relation) *Signature { - s.candidatesForArgumentError = nil + clear(s.candidatesForArgumentError) + s.candidatesForArgumentError = s.candidatesForArgumentError[:0] s.candidateForArgumentArityError = nil s.candidateForTypeArgumentError = nil if s.isSingleNonGenericCandidate { @@ -9260,7 +9413,7 @@ func (c *Checker) chooseOverload(s *CallState, relation *Relation) *Signature { return nil } if !c.isSignatureApplicable(s.node, s.args, candidate, relation, CheckModeNormal, false /*reportErrors*/, nil /*diagnosticOutput*/) { - s.candidatesForArgumentError = []*Signature{candidate} + s.candidatesForArgumentError = append(s.candidatesForArgumentError[:0], candidate) return nil } return candidate @@ -9811,7 +9964,7 @@ func (c *Checker) inferSignatureInstantiationForOverloadFailure(node *ast.Node, } func (c *Checker) createUnionOfSignaturesForOverloadFailure(candidates []*Signature) *Signature { - thisParameters := core.MapNonNil(candidates, func(c *Signature) *ast.Symbol { return c.ThisParameter() }) + thisParameters := core.MapNonNil(candidates, func(s *Signature) *ast.Symbol { return c.getSignatureThisParameter(s) }) var thisParameter *ast.Symbol if len(thisParameters) != 0 { thisParameter = c.createCombinedSymbolFromTypes(thisParameters, core.Map(thisParameters, c.getTypeOfParameter)) @@ -9868,7 +10021,7 @@ func (c *Checker) tryGetRestTypeOfSignature(signature *Signature) *Type { if !signatureHasRestParameter(signature) { return nil } - restType := c.getTypeOfSymbol(signature.parameters[len(signature.parameters)-1]) + restType := c.getTypeOfSignatureParameterSymbol(signature, len(signature.parameters)-1) if isTupleType(restType) { restType = c.getRestTypeOfTupleType(restType) if restType == nil { @@ -10598,7 +10751,7 @@ func (c *Checker) assignContextualParameterTypes(sig *Signature, context *Signat if parameter == nil { sig.setThisParameter(c, c.createSymbolWithType(context.ThisParameter(), nil /*type*/)) } - c.assignParameterType(sig.ThisParameter(), c.getTypeOfSymbol(context.ThisParameter())) + c.assignParameterType(sig.ThisParameter(), c.getThisTypeOfSignature(context)) } } length := len(sig.parameters) - core.IfElse(signatureHasRestParameter(sig), 1, 0) @@ -12153,7 +12306,7 @@ func (c *Checker) forEachProperty(prop *ast.Symbol, callback func(p *ast.Symbol) if prop.CheckFlags()&ast.CheckFlagsSynthetic == 0 { return callback(prop) } - for _, t := range c.valueSymbolLinks.Get(prop).getContainingType().Types() { + for _, t := range c.valueSymbolLinks.TryGet(prop).getContainingType().Types() { p := c.getPropertyOfType(t, prop.Name()) if p != nil && c.forEachProperty(p, callback) { return true @@ -12271,9 +12424,8 @@ func (c *Checker) getContextualThisParameterType(fn *ast.Node) *Type { if c.isContextSensitiveFunctionOrObjectLiteralMethod(fn) { contextualSignature := c.getContextualSignature(fn) if contextualSignature != nil { - thisParameter := contextualSignature.ThisParameter() - if thisParameter != nil { - return c.getTypeOfSymbol(thisParameter) + if thisType := c.getThisTypeOfSignature(contextualSignature); thisType != nil { + return thisType } } } @@ -13009,7 +13161,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 links := c.symbolNodeLinks.TryGet(left); links != nil && links.resolvedSymbol != nil && len(links.resolvedSymbol.Declarations()) > 1 && rightType.flags&TypeFlagsUndefined != 0 { return } } @@ -13337,7 +13489,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.getResolvedSymbolOrNil(left) == nil && ast.GetContainingClass(left) != nil { isUncheckedJS := c.isUncheckedJSSuggestion(left, rightType.symbol, true /*excludeClasses*/) c.reportNonexistentProperty(left, rightType, isUncheckedJS) } @@ -13408,10 +13560,7 @@ func (c *Checker) checkObjectLiteral(node *ast.Node, checkMode CheckMode) *Type // Grammar checking c.checkGrammarObjectLiteralExpression(node.AsObjectLiteralExpression(), inDestructuringPattern) var allPropertiesTable ast.SymbolTable - if c.strictNullChecks { - allPropertiesTable = ast.NewSymbolTable() - } - propertiesTable := ast.NewSymbolTable() + propertiesTable := c.acquireSymbolTable() var propertiesArray []*ast.Symbol spread := c.emptyObjectType c.pushCachedContextualType(node) @@ -13436,36 +13585,14 @@ func (c *Checker) checkObjectLiteral(node *ast.Node, checkMode CheckMode) *Type // As otherwise they may not be checked until exports for the type at this position are retrieved, // which may never occur. for _, elem := range node.Properties() { + if elem.Kind == ast.KindSpreadAssignment && c.strictNullChecks && allPropertiesTable == nil { + allPropertiesTable = c.acquireSymbolTable() + } if elem.Name() != nil && ast.IsComputedPropertyName(elem.Name()) { c.checkComputedPropertyName(elem.Name()) } } offset := 0 - createObjectLiteralType := func() *Type { - var indexInfos []*IndexInfo - isReadonly := c.isConstContext(node) - if hasComputedStringProperty { - indexInfos = append(indexInfos, c.getObjectLiteralIndexInfo(isReadonly, propertiesArray[offset:], c.stringType)) - } - if hasComputedNumberProperty { - indexInfos = append(indexInfos, c.getObjectLiteralIndexInfo(isReadonly, propertiesArray[offset:], c.numberType)) - } - if hasComputedSymbolProperty { - indexInfos = append(indexInfos, c.getObjectLiteralIndexInfo(isReadonly, propertiesArray[offset:], c.esSymbolType)) - } - result := c.newAnonymousType(node.Symbol(), propertiesTable, nil, nil, indexInfos) - result.objectFlags |= objectFlags | ObjectFlagsObjectLiteral | ObjectFlagsContainsObjectOrArrayLiteral - if contextualType == nil && ast.IsInJSFile(node) && !ast.IsInJsonFile(node) { - result.objectFlags |= ObjectFlagsJSLiteral - } - if patternWithComputedProperties { - result.objectFlags |= ObjectFlagsObjectLiteralPatternWithComputedProperties - } - if inDestructuringPattern { - c.patternForType[result] = node - } - return result - } for _, memberDecl := range node.Properties() { member := c.getSymbolOfDeclaration(memberDecl) var computedNameType *Type @@ -13490,8 +13617,13 @@ func (c *Checker) checkObjectLiteral(node *ast.Node, checkMode CheckMode) *Type var prop *ast.Symbol if nameType != nil { prop = c.newSymbolEx(ast.SymbolFlagsProperty|member.Flags(), getPropertyNameFromType(nameType), checkFlags|ast.CheckFlagsLate) + prop.SetDeclarations(member.Declarations()) + prop.SetParent(member.Parent()) + prop.SetValueDeclaration(member.ValueDeclaration()) } else { - prop = c.newSymbolEx(ast.SymbolFlagsProperty|member.Flags(), member.Name(), checkFlags) + prop = c.newSharedDataSymbol(member) + prop.SetFlags(prop.Flags() | ast.SymbolFlagsProperty) + prop.SetCheckFlags(checkFlags) } links := c.valueSymbolLinks.Get(prop) if nameType != nil { @@ -13511,9 +13643,6 @@ func (c *Checker) checkObjectLiteral(node *ast.Node, checkMode CheckMode) *Type c.error(memberDecl.Name(), diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1, c.symbolToString(member), c.TypeToString(contextualType)) } } - prop.SetDeclarations(member.Declarations()) - prop.SetParent(member.Parent()) - prop.SetValueDeclaration(member.ValueDeclaration()) links.resolvedType = t links.target = member member = prop @@ -13531,9 +13660,11 @@ func (c *Checker) checkObjectLiteral(node *ast.Node, checkMode CheckMode) *Type } } else if memberDecl.Kind == ast.KindSpreadAssignment { if len(propertiesArray) > 0 { - spread = c.getSpreadType(spread, createObjectLiteralType(), node.Symbol(), objectFlags, inConstContext) + objLitType := c.createObjectLiteralType(node, objectFlags, propertiesTable, propertiesArray, offset, contextualType, inConstContext, hasComputedStringProperty, hasComputedNumberProperty, hasComputedSymbolProperty, patternWithComputedProperties, inDestructuringPattern) + c.releaseSymbolTableIfUnused(objLitType, propertiesTable) + spread = c.getSpreadType(spread, objLitType, node.Symbol(), objectFlags, inConstContext) propertiesArray = nil - propertiesTable = ast.NewSymbolTable() + propertiesTable = c.acquireSymbolTable() hasComputedStringProperty = false hasComputedNumberProperty = false hasComputedSymbolProperty = false @@ -13583,25 +13714,75 @@ func (c *Checker) checkObjectLiteral(node *ast.Node, checkMode CheckMode) *Type } c.popContextualType() if c.isErrorType(spread) { + c.releaseSymbolTable(propertiesTable) return c.errorType } if spread != c.emptyObjectType { if len(propertiesArray) > 0 { - spread = c.getSpreadType(spread, createObjectLiteralType(), node.Symbol(), objectFlags, inConstContext) + objLitType := c.createObjectLiteralType(node, objectFlags, propertiesTable, propertiesArray, offset, contextualType, inConstContext, hasComputedStringProperty, hasComputedNumberProperty, hasComputedSymbolProperty, patternWithComputedProperties, inDestructuringPattern) + c.releaseSymbolTableIfUnused(objLitType, propertiesTable) + spread = c.getSpreadType(spread, objLitType, node.Symbol(), objectFlags, inConstContext) propertiesArray = nil - propertiesTable = ast.NewSymbolTable() + propertiesTable = nil hasComputedStringProperty = false hasComputedNumberProperty = false + hasComputedSymbolProperty = false + } else { + c.releaseSymbolTable(propertiesTable) + propertiesTable = nil } // remap the raw emptyObjectType fed in at the top into a fresh empty object literal type, unique to this use site - return c.mapType(spread, func(t *Type) *Type { - if t == c.emptyObjectType { - return createObjectLiteralType() - } - return t - }) + return c.remapSpreadEmptyObjectType(spread, node, objectFlags, propertiesTable, propertiesArray, offset, contextualType, inConstContext, hasComputedStringProperty, hasComputedNumberProperty, hasComputedSymbolProperty, patternWithComputedProperties, inDestructuringPattern) + } + res := c.createObjectLiteralType(node, objectFlags, propertiesTable, propertiesArray, offset, contextualType, inConstContext, hasComputedStringProperty, hasComputedNumberProperty, hasComputedSymbolProperty, patternWithComputedProperties, inDestructuringPattern) + c.releaseSymbolTableIfUnused(res, propertiesTable) + return res +} + +func (c *Checker) createObjectLiteralType(node *ast.Node, objectFlags ObjectFlags, propertiesTable ast.SymbolTable, propertiesArray []*ast.Symbol, offset int, contextualType *Type, inConstContext bool, hasComputedStringProperty bool, hasComputedNumberProperty bool, hasComputedSymbolProperty bool, patternWithComputedProperties bool, inDestructuringPattern bool) *Type { + var indexInfos []*IndexInfo + isReadonly := inConstContext + if hasComputedStringProperty { + indexInfos = append(indexInfos, c.getObjectLiteralIndexInfo(isReadonly, propertiesArray[offset:], c.stringType)) + } + if hasComputedNumberProperty { + indexInfos = append(indexInfos, c.getObjectLiteralIndexInfo(isReadonly, propertiesArray[offset:], c.numberType)) + } + if hasComputedSymbolProperty { + indexInfos = append(indexInfos, c.getObjectLiteralIndexInfo(isReadonly, propertiesArray[offset:], c.esSymbolType)) + } + var result *Type + if propertiesTable.Len() == 0 { + result = c.newAnonymousType(node.Symbol(), nil, nil, nil, indexInfos) + } else { + result = c.newAnonymousType(node.Symbol(), propertiesTable, nil, nil, indexInfos) + } + result.objectFlags |= objectFlags | ObjectFlagsObjectLiteral | ObjectFlagsContainsObjectOrArrayLiteral + if contextualType == nil && ast.IsInJSFile(node) && !ast.IsInJsonFile(node) { + result.objectFlags |= ObjectFlagsJSLiteral } - return createObjectLiteralType() + if patternWithComputedProperties { + result.objectFlags |= ObjectFlagsObjectLiteralPatternWithComputedProperties + } + if inDestructuringPattern { + c.patternForType[result] = node + } + return result +} + +func (c *Checker) remapSpreadEmptyObjectType(spread *Type, node *ast.Node, objectFlags ObjectFlags, propertiesTable ast.SymbolTable, propertiesArray []*ast.Symbol, offset int, contextualType *Type, inConstContext bool, hasComputedStringProperty bool, hasComputedNumberProperty bool, hasComputedSymbolProperty bool, patternWithComputedProperties bool, inDestructuringPattern bool) *Type { + if spread.flags&TypeFlagsUnion == 0 { + if spread == c.emptyObjectType { + return c.createObjectLiteralType(node, objectFlags, propertiesTable, propertiesArray, offset, contextualType, inConstContext, hasComputedStringProperty, hasComputedNumberProperty, hasComputedSymbolProperty, patternWithComputedProperties, inDestructuringPattern) + } + return spread + } + return c.mapType(spread, func(t *Type) *Type { + if t == c.emptyObjectType { + return c.createObjectLiteralType(node, objectFlags, propertiesTable, propertiesArray, offset, contextualType, inConstContext, hasComputedStringProperty, hasComputedNumberProperty, hasComputedSymbolProperty, patternWithComputedProperties, inDestructuringPattern) + } + return t + }) } func (c *Checker) checkContextualDeprecations(node *ast.Node) { @@ -13697,7 +13878,7 @@ func (c *Checker) getSpreadType(left *Type, right *Type, symbol *ast.Symbol, obj } return c.getIntersectionType([]*Type{left, right}) } - members := ast.NewSymbolTable() + members := c.acquireSymbolTable() var skippedPrivateMembers collections.Set[string] var indexInfos []*IndexInfo if left == c.emptyObjectType { @@ -13740,7 +13921,7 @@ func (c *Checker) getSpreadType(left *Type, right *Type, symbol *ast.Symbol, obj c.spreadLinks.Get(result).leftSpread = leftProp c.spreadLinks.Get(result).rightSpread = rightProp result.SetDeclarations(declarations) - links.setNameType(c, c.valueSymbolLinks.Get(leftProp).getNameType()) + links.setNameType(c, c.valueSymbolLinks.TryGet(leftProp).getNameType()) members.Set(leftProp.Name(), result) } } else { @@ -13751,6 +13932,7 @@ func (c *Checker) getSpreadType(left *Type, right *Type, symbol *ast.Symbol, obj return c.getIndexInfoWithReadonly(info, readonly) }) spread := c.newAnonymousType(symbol, members, nil, nil, spreadIndexInfos) + c.releaseSymbolTableIfUnused(spread, members) spread.objectFlags |= ObjectFlagsObjectLiteral | ObjectFlagsContainsObjectOrArrayLiteral | ObjectFlagsContainsSpread | objectFlags return spread } @@ -13812,7 +13994,7 @@ func (c *Checker) tryMergeUnionOfObjectTypeAndEmptyObject(t *Type, readonly bool return t } // gets the type as if it had been spread, but where everything in the spread is made optional - members := ast.NewSymbolTable() + members := c.acquireSymbolTable() for _, prop := range c.getPropertiesOfType(firstType) { if getDeclarationModifierFlagsFromSymbol(prop)&(ast.ModifierFlagsPrivate|ast.ModifierFlagsProtected) != 0 { // do nothing, skip privates @@ -13827,12 +14009,13 @@ func (c *Checker) tryMergeUnionOfObjectTypeAndEmptyObject(t *Type, readonly bool links.resolvedType = c.addOptionalityEx(c.getTypeOfSymbol(prop), true /*isProperty*/, true /*isOptional*/) } result.SetDeclarations(prop.Declarations()) - links.setNameType(c, c.valueSymbolLinks.Get(prop).getNameType()) - c.mappedSymbolLinks.Get(result).syntheticOrigin = prop + links.setNameType(c, c.valueSymbolLinks.TryGet(prop).getNameType()) + links.target = prop members.Set(prop.Name(), result) } } spread := c.newAnonymousType(firstType.symbol, members, nil, nil, c.getIndexInfosOfType(firstType)) + c.releaseSymbolTableIfUnused(spread, members) spread.objectFlags |= ObjectFlagsObjectLiteral | ObjectFlagsContainsObjectOrArrayLiteral return spread } @@ -13857,8 +14040,8 @@ func (c *Checker) getSpreadSymbol(prop *ast.Symbol, readonly bool) *ast.Symbol { links.resolvedType = c.getTypeOfSymbol(prop) } result.SetDeclarations(prop.Declarations()) - links.setNameType(c, c.valueSymbolLinks.Get(prop).getNameType()) - c.mappedSymbolLinks.Get(result).syntheticOrigin = prop + links.setNameType(c, c.valueSymbolLinks.TryGet(prop).getNameType()) + links.target = prop return result } @@ -13874,12 +14057,37 @@ func (c *Checker) hasDefaultValue(node *ast.Node) bool { } func (c *Checker) isConstContext(node *ast.Node) bool { + return c.isConstContextEx(node, nil, nil) +} + +func (c *Checker) isConstContextEx(node *ast.Node, contextualTypeOut **Type, hasContextualTypeOut *bool) bool { parent := node.Parent - return ast.IsConstAssertion(parent) || - c.isInlineImportAttributes(node) || - c.isValidConstAssertionArgument(node) && c.isConstTypeVariable(c.getContextualType(node, ContextFlagsNone), 0) || - (ast.IsParenthesizedExpression(parent) || ast.IsArrayLiteralExpression(parent) || ast.IsSpreadElement(parent)) && c.isConstContext(parent) || - (ast.IsPropertyAssignment(parent) || ast.IsShorthandPropertyAssignment(parent) || ast.IsTemplateSpan(parent)) && c.isConstContext(parent.Parent) + if ast.IsConstAssertion(parent) || c.isInlineImportAttributes(node) { + return true + } + if c.isValidConstAssertionArgument(node) { + ct := c.getContextualType(node, ContextFlagsNone) + if contextualTypeOut != nil { + *contextualTypeOut = ct + *hasContextualTypeOut = true + } + if c.isConstTypeVariable(ct, 0) { + return true + } + } + if ast.IsParenthesizedExpression(parent) || ast.IsArrayLiteralExpression(parent) || ast.IsSpreadElement(parent) { + if hasContextualTypeOut != nil { + *hasContextualTypeOut = false + } + return c.isConstContext(parent) + } + if ast.IsPropertyAssignment(parent) || ast.IsShorthandPropertyAssignment(parent) || ast.IsTemplateSpan(parent) { + if hasContextualTypeOut != nil { + *hasContextualTypeOut = false + } + return c.isConstContext(parent.Parent) + } + return false } func (c *Checker) isInlineImportAttributes(node *ast.Node) bool { @@ -13924,11 +14132,37 @@ func (c *Checker) isConstTypeVariable(t *Type, depth int) bool { if depth >= 5 || t == nil { return false } + if t.flags&(TypeFlagsTypeParameter|TypeFlagsUnionOrIntersection|TypeFlagsIndexedAccess|TypeFlagsConditional|TypeFlagsSubstitution|TypeFlagsObject) == 0 { + return false + } + if t.flags&TypeFlagsObjectFlagsType != 0 { + if t.objectFlags&ObjectFlagsPrimitiveUnion != 0 { + return false + } + if t.objectFlags&(ObjectFlagsCouldContainTypeVariablesComputed|ObjectFlagsCouldContainTypeVariables) == ObjectFlagsCouldContainTypeVariablesComputed { + return false + } + if t.flags&TypeFlagsObject != 0 && t.objectFlags&(ObjectFlagsMapped|ObjectFlagsReference) == 0 { + return false + } + } switch { case t.flags&TypeFlagsTypeParameter != 0: - return t.symbol != nil && core.Some(t.symbol.Declarations(), func(d *ast.Node) bool { return ast.HasSyntacticModifier(d, ast.ModifierFlagsConst) }) + if t.symbol != nil { + for _, d := range t.symbol.Declarations() { + if ast.HasSyntacticModifier(d, ast.ModifierFlagsConst) { + return true + } + } + } + return false case t.flags&TypeFlagsUnionOrIntersection != 0: - return core.Some(t.Types(), func(s *Type) bool { return c.isConstTypeVariable(s, depth) }) + for _, s := range t.Types() { + if c.isConstTypeVariable(s, depth+1) { + return true + } + } + return false case t.flags&TypeFlagsIndexedAccess != 0: return c.isConstTypeVariable(t.AsIndexedAccessType().objectType, depth+1) case t.flags&TypeFlagsConditional != 0: @@ -14091,7 +14325,7 @@ func (c *Checker) getNarrowedTypeOfSymbol(symbol *ast.Symbol, location *ast.Node if context != nil { mapper = context.nonFixingMapper } - restType := c.getReducedApparentType(c.instantiateType(c.getTypeOfSymbol(contextualSignature.parameters[0]), mapper)) + restType := c.getReducedApparentType(c.instantiateType(c.getTypeOfSignatureParameterSymbol(contextualSignature, 0), mapper)) if restType.flags&TypeFlagsUnion != 0 && everyType(restType, isTupleType) && !core.Some(fn.Parameters(), c.isSomeSymbolAssigned) { narrowedType := c.getFlowTypeOfReferenceEx(fn, restType, restType, nil /*flowContainer*/, getFlowNodeOfNode(location)) index := slices.Index(fn.Parameters(), declaration) - core.IfElse(ast.GetThisParameter(fn) != nil, 1, 0) @@ -14155,14 +14389,18 @@ func (c *Checker) checkObjectLiteralMethod(node *ast.Node, checkMode CheckMode) func (c *Checker) checkExpressionForMutableLocation(node *ast.Node, checkMode CheckMode) *Type { t := c.checkExpressionEx(node, checkMode) - switch { - case c.isConstContext(node): + var contextualType *Type + var hasContextualType bool + if c.isConstContextEx(node, &contextualType, &hasContextualType) { return c.getRegularTypeOfLiteralType(t) - case isTypeAssertion(node): + } + if isTypeAssertion(node) { return t - default: - return c.getWidenedLiteralLikeTypeForContextualType(t, c.instantiateContextualType(c.getContextualType(node, ContextFlagsNone), node, ContextFlagsNone)) } + if !hasContextualType { + contextualType = c.getContextualType(node, ContextFlagsNone) + } + return c.getWidenedLiteralLikeTypeForContextualType(t, c.instantiateContextualType(contextualType, node, ContextFlagsNone)) } func (c *Checker) getResolvedSymbol(node *ast.Node) *ast.Symbol { @@ -14179,11 +14417,14 @@ func (c *Checker) getResolvedSymbol(node *ast.Node) *ast.Symbol { } func (c *Checker) getResolvedSymbolOrNil(node *ast.Node) *ast.Symbol { - return c.symbolNodeLinks.Get(node).resolvedSymbol + if links := c.symbolNodeLinks.TryGet(node); links != nil { + return links.resolvedSymbol + } + return nil } func (c *Checker) getReferencedValueOrAliasSymbol(reference *ast.Node) *ast.Symbol { - resolvedSymbol := c.symbolNodeLinks.Get(reference).resolvedSymbol + resolvedSymbol := c.getResolvedSymbolOrNil(reference) if resolvedSymbol != nil && resolvedSymbol != c.unknownSymbol { return resolvedSymbol } @@ -14251,6 +14492,7 @@ func (c *Checker) GetGlobalDiagnostics() []*ast.Diagnostic { } func (c *Checker) addDeferredDiagnostic(callback func()) { + c.diagnosticCount++ c.deferredDiagnosticCallbacks = append(c.deferredDiagnosticCallbacks, callback) } @@ -14262,6 +14504,7 @@ func (c *Checker) produceDeferredDiagnostics() { } func (c *Checker) addDiagnostic(diagnostic *ast.Diagnostic) *ast.Diagnostic { + c.diagnosticCount++ // Discard diagnostics created while at the maximum number of recursive TypeToString invocations. if c.serializationLevel < maxSerializationLevel { return c.diagnostics.Add(diagnostic) @@ -14270,6 +14513,7 @@ func (c *Checker) addDiagnostic(diagnostic *ast.Diagnostic) *ast.Diagnostic { } func (c *Checker) addSuggestionDiagnostic(diagnostic *ast.Diagnostic) *ast.Diagnostic { + c.diagnosticCount++ // Discard diagnostics created while at the maximum number of recursive TypeToString invocations. if c.serializationLevel < maxSerializationLevel { return c.suggestionDiagnostics.Add(diagnostic) @@ -14633,7 +14877,7 @@ func (c *Checker) cloneSymbol(symbol *ast.Symbol) *ast.Symbol { } func (c *Checker) getMergedSymbol(symbol *ast.Symbol) *ast.Symbol { - if symbol != nil { + if symbol != nil && symbol.IsMerged() { if merged := c.mergedSymbols.TryGet(symbol); merged != nil && *merged != nil { return *merged } @@ -14649,6 +14893,7 @@ func (c *Checker) getParentOfSymbol(symbol *ast.Symbol) *ast.Symbol { } func (c *Checker) recordMergedSymbol(target *ast.Symbol, source *ast.Symbol) { + source.MarkMerged() *c.mergedSymbols.Get(source) = target } @@ -14673,7 +14918,10 @@ func (c *Checker) getExportSymbolOfValueSymbolIfExported(symbol *ast.Symbol) *as func (c *Checker) getSymbolOfDeclaration(node *ast.Node) *ast.Symbol { symbol := node.Symbol() if symbol != nil { - return c.getMergedSymbol(c.getLateBoundSymbol(symbol)) + if symbol.Flags()&ast.SymbolFlagsClassMember != 0 && symbol.Name() == ast.InternalSymbolNameComputed { + symbol = c.getLateBoundSymbolSlow(symbol) + } + return c.getMergedSymbol(symbol) } return nil } @@ -14683,7 +14931,11 @@ func (c *Checker) getSymbolOfDeclaration(node *ast.Node) *ast.Symbol { func (c *Checker) getSymbolOfNode(node *ast.Node) *ast.Symbol { data := node.DeclarationData() if data != nil && data.Symbol != nil { - return c.getMergedSymbol(c.getLateBoundSymbol(data.Symbol)) + symbol := data.Symbol + if symbol.Flags()&ast.SymbolFlagsClassMember != 0 && symbol.Name() == ast.InternalSymbolNameComputed { + symbol = c.getLateBoundSymbolSlow(symbol) + } + return c.getMergedSymbol(symbol) } return nil } @@ -14692,6 +14944,10 @@ func (c *Checker) getLateBoundSymbol(symbol *ast.Symbol) *ast.Symbol { if symbol.Flags()&ast.SymbolFlagsClassMember == 0 || symbol.Name() != ast.InternalSymbolNameComputed { return symbol } + return c.getLateBoundSymbolSlow(symbol) +} + +func (c *Checker) getLateBoundSymbolSlow(symbol *ast.Symbol) *ast.Symbol { links := c.lateBoundLinks.Get(symbol) if links.lateSymbol == nil && core.Some(symbol.Declarations(), c.hasLateBindableName) { // force late binding of members/exports. This will set the late-bound symbol @@ -16573,45 +16829,81 @@ type ExportCollisionTable = map[string]*ExportCollision func (c *Checker) getExportsOfModuleWorker(moduleSymbol *ast.Symbol) (exports ast.SymbolTable, typeOnlyExportStarMap map[string]*ast.Node) { var visitedSymbols []*ast.Symbol - nonTypeOnlyNames := collections.NewSetWithSizeHint[string](moduleSymbol.Exports().Len()) + var nonTypeOnlySymbols []*ast.Symbol // The ES6 spec permits export * declarations in a module to circularly reference the module itself. For example, // module 'a' can 'export * from "b"' and 'b' can 'export * from "a"' without error. var visit func(*ast.Symbol, *ast.Node, bool) ast.SymbolTable visit = func(symbol *ast.Symbol, exportStar *ast.Node, isTypeOnly bool) ast.SymbolTable { if !isTypeOnly && symbol != nil { - // Add non-type-only names before checking if we've visited this module, + // Add non-type-only symbols before checking if we've visited this module, // because we might have visited it via an 'export type *', and visiting // again with 'export *' will override the type-onlyness of its exports. - for name := range symbol.Exports().All() { - nonTypeOnlyNames.Add(name) - } + nonTypeOnlySymbols = append(nonTypeOnlySymbols, symbol) } if symbol == nil || symbol.Exports() == nil || slices.Contains(visitedSymbols, symbol) { return nil } visitedSymbols = append(visitedSymbols, symbol) - symbols := symbol.Exports().Clone() - // All export * declarations are collected in an __export symbol by the binder exportStars := symbol.Exports().Get(ast.InternalSymbolNameExportStar) + var symbols ast.SymbolTable if exportStars != nil { - nestedSymbols := ast.NewSymbolTable() - lookupTable := make(ExportCollisionTable) - for _, node := range exportStars.Declarations() { + symbols = symbol.Exports().Clone() + decls := exportStars.Declarations() + if len(decls) == 1 { + node := decls[0] resolvedModule := c.resolveExternalModuleName(node, node.ModuleSpecifier(), false /*ignoreErrors*/, c.getTypeFromImportAttributes(ast.GetImportAttributes(node))) exportedSymbols := visit(resolvedModule, node, isTypeOnly || node.IsTypeOnly()) - c.extendExportSymbols(nestedSymbols, exportedSymbols, lookupTable, node) - } - for id, s := range lookupTable { - // It's not an error if the file with multiple `export *`s with duplicate names exports a member with that name itself - if id == ast.InternalSymbolNameExportEquals || len(s.exportsWithDuplicate) == 0 || symbols.Get(id) != nil { - continue + c.extendExportSymbols(symbols, exportedSymbols, nil, nil) + } else if len(decls) > 1 { + nestedSymbols := c.acquireSymbolTable() + var lookupTable ExportCollisionTable + exportedByStar := make([]ast.SymbolTable, len(decls)) + for i, node := range decls { + resolvedModule := c.resolveExternalModuleName(node, node.ModuleSpecifier(), false /*ignoreErrors*/, c.getTypeFromImportAttributes(ast.GetImportAttributes(node))) + exportedSymbols := visit(resolvedModule, node, isTypeOnly || node.IsTypeOnly()) + exportedByStar[i] = exportedSymbols + for id, sourceSymbol := range exportedSymbols.All() { + if id == ast.InternalSymbolNameDefault { + continue + } + targetSymbol := nestedSymbols.Get(id) + if targetSymbol == nil { + nestedSymbols.Set(id, sourceSymbol) + } else if c.resolveSymbol(targetSymbol) != c.resolveSymbol(sourceSymbol) { + if lookupTable == nil { + lookupTable = make(ExportCollisionTable) + } + s := lookupTable[id] + if s == nil { + var specText string + for j := range i { + if exportedByStar[j].Get(id) != nil { + specText = scanner.GetTextOfNode(decls[j].ModuleSpecifier()) + break + } + } + s = &ExportCollision{specifierText: specText} + lookupTable[id] = s + } + s.exportsWithDuplicate = append(s.exportsWithDuplicate, node) + } + } } - for _, node := range s.exportsWithDuplicate { - c.addDiagnostic(createDiagnosticForNode(node, diagnostics.Module_0_has_already_exported_a_member_named_1_Consider_explicitly_re_exporting_to_resolve_the_ambiguity, s.specifierText, id)) + for id, s := range lookupTable { + // It's not an error if the file with multiple `export *`s with duplicate names exports a member with that name itself + if id == ast.InternalSymbolNameExportEquals || len(s.exportsWithDuplicate) == 0 || symbols.Get(id) != nil { + continue + } + for _, node := range s.exportsWithDuplicate { + c.addDiagnostic(createDiagnosticForNode(node, diagnostics.Module_0_has_already_exported_a_member_named_1_Consider_explicitly_re_exporting_to_resolve_the_ambiguity, s.specifierText, id)) + } } + c.extendExportSymbols(symbols, nestedSymbols, nil, nil) + c.releaseSymbolTable(nestedSymbols) } - c.extendExportSymbols(symbols, nestedSymbols, nil, nil) + } else { + symbols = symbol.Exports() } if exportStar != nil && exportStar.IsTypeOnly() { if typeOnlyExportStarMap == nil { @@ -16637,20 +16929,27 @@ func (c *Checker) getExportsOfModuleWorker(moduleSymbol *ast.Symbol) (exports as } // A CommonJS module defined by an 'export=' might also export typedefs, stored on the original module if originalModule != nil && originalModule.Exports().Len() > 1 { + var cloned bool for _, symbol := range originalModule.Exports().All() { if symbol.Name() == ast.InternalSymbolNameExportEquals || symbol.Name() == ast.InternalSymbolNameExportStar { continue } flags := c.getSymbolFlags(symbol) - if flags&(ast.SymbolFlagsType|ast.SymbolFlagsNamespace) != 0 && - flags&ast.SymbolFlagsValue == 0 && - exports.Get(symbol.Name()) == nil { + if flags&(ast.SymbolFlagsType|ast.SymbolFlagsNamespace) != 0 && flags&ast.SymbolFlagsValue == 0 && exports.Get(symbol.Name()) == nil { + if !cloned && moduleSymbol != nil && moduleSymbol.Exports() != nil && moduleSymbol.Exports().Get(ast.InternalSymbolNameExportStar) == nil { + exports = exports.Clone() + cloned = true + } exports.Set(symbol.Name(), symbol) } } } - for name := range nonTypeOnlyNames.Keys() { - delete(typeOnlyExportStarMap, name) + if typeOnlyExportStarMap != nil { + for _, sym := range nonTypeOnlySymbols { + for name := range sym.Exports().All() { + delete(typeOnlyExportStarMap, name) + } + } } return exports, typeOnlyExportStarMap } @@ -16897,7 +17196,7 @@ func (c *Checker) GetTypeOfSymbolAtLocation(symbol *ast.Symbol, location *ast.No } else { t = c.getTypeOfExpression(location) } - if c.getExportSymbolOfValueSymbolIfExported(c.symbolNodeLinks.Get(location).resolvedSymbol) == symbol { + if c.getExportSymbolOfValueSymbolIfExported(c.getResolvedSymbolOrNil(location)) == symbol { return c.removeOptionalTypeMarker(t) } } @@ -16917,32 +17216,49 @@ func (c *Checker) GetTypeOfSymbolAtLocation(symbol *ast.Symbol, location *ast.No return c.getNonMissingTypeOfSymbol(symbol) } +const ( + symbolFlagsWithCachedType = ast.SymbolFlagsVariable | ast.SymbolFlagsProperty | ast.SymbolFlagsFunction | ast.SymbolFlagsMethod | ast.SymbolFlagsClass | ast.SymbolFlagsEnum | ast.SymbolFlagsValueModule | ast.SymbolFlagsEnumMember | ast.SymbolFlagsAccessor | ast.SymbolFlagsAlias + checkFlagsWithCachedType = ast.CheckFlagsDeferredType | ast.CheckFlagsInstantiated | ast.CheckFlagsMapped | ast.CheckFlagsReverseMapped +) + func (c *Checker) getTypeOfSymbol(symbol *ast.Symbol) *Type { - if symbol.CheckFlags()&ast.CheckFlagsDeferredType != 0 { + if symbol.Flags()&symbolFlagsWithCachedType != 0 || symbol.CheckFlags()&checkFlagsWithCachedType != 0 { + if links := c.valueSymbolLinks.Get(symbol); links.resolvedType != nil { + return links.resolvedType + } + return c.getTypeOfSymbolSlow(symbol) + } + return c.errorType +} + +func (c *Checker) getTypeOfSymbolSlow(symbol *ast.Symbol) *Type { + checkFlags := symbol.CheckFlags() + if checkFlags&ast.CheckFlagsDeferredType != 0 { return c.getTypeOfSymbolWithDeferredType(symbol) } - if symbol.CheckFlags()&ast.CheckFlagsInstantiated != 0 { + if checkFlags&ast.CheckFlagsInstantiated != 0 { return c.getTypeOfInstantiatedSymbol(symbol) } - if symbol.CheckFlags()&ast.CheckFlagsMapped != 0 { + if checkFlags&ast.CheckFlagsMapped != 0 { return c.getTypeOfMappedSymbol(symbol) } - if symbol.CheckFlags()&ast.CheckFlagsReverseMapped != 0 { + if checkFlags&ast.CheckFlagsReverseMapped != 0 { return c.getTypeOfReverseMappedSymbol(symbol) } - if symbol.Flags()&ast.SymbolFlagsAccessor != 0 { + flags := symbol.Flags() + if flags&ast.SymbolFlagsAccessor != 0 { return c.getTypeOfAccessors(symbol) } - if symbol.Flags()&(ast.SymbolFlagsVariable|ast.SymbolFlagsProperty) != 0 { + if flags&(ast.SymbolFlagsVariable|ast.SymbolFlagsProperty) != 0 { return c.getTypeOfVariableOrParameterOrProperty(symbol) } - if symbol.Flags()&(ast.SymbolFlagsFunction|ast.SymbolFlagsMethod|ast.SymbolFlagsClass|ast.SymbolFlagsEnum|ast.SymbolFlagsValueModule) != 0 { + if flags&(ast.SymbolFlagsFunction|ast.SymbolFlagsMethod|ast.SymbolFlagsClass|ast.SymbolFlagsEnum|ast.SymbolFlagsValueModule) != 0 { return c.getTypeOfFuncClassEnumModule(symbol) } - if symbol.Flags()&ast.SymbolFlagsEnumMember != 0 { + if flags&ast.SymbolFlagsEnumMember != 0 { return c.getTypeOfEnumMember(symbol) } - if symbol.Flags()&ast.SymbolFlagsAlias != 0 { + if flags&ast.SymbolFlagsAlias != 0 { return c.getTypeOfAlias(symbol) } return c.errorType @@ -17456,7 +17772,7 @@ func (c *Checker) isMixinConstructorType(t *Type) bool { if len(signatures) == 1 { s := signatures[0] if len(s.TypeParameters()) == 0 && len(s.parameters) == 1 && signatureHasRestParameter(s) { - paramType := c.getTypeOfParameter(s.parameters[0]) + paramType := c.getTypeOfSignatureParameter(s, 0) return IsTypeAny(paramType) || c.getElementTypeOfArrayType(paramType) == c.anyType } } @@ -17472,6 +17788,95 @@ func (c *Checker) getTypeOfParameter(symbol *ast.Symbol) *Type { return c.addOptionalityEx(c.getTypeOfSymbol(symbol), false, declaration != nil && (declaration.Initializer() != nil || isOptionalDeclaration(declaration))) } +func (c *Checker) getTypeOfSignatureParameter(signature *Signature, pos int) *Type { + declaration := signature.parameters[pos].ValueDeclaration() + return c.addOptionalityEx(c.getTypeOfSignatureParameterSymbol(signature, pos), false, declaration != nil && (declaration.Initializer() != nil || isOptionalDeclaration(declaration))) +} + +func (c *Checker) getTypeOfSignatureParameterSymbol(signature *Signature, pos int) *Type { + if signature.flags&signatureFlagsUninstantiatedParameters == 0 { + return c.getTypeOfSymbol(signature.parameters[pos]) + } + if pos == 0 { + if signature.firstParamType != nil { + return signature.firstParamType + } + } else if signature.extra != nil && pos-1 < len(signature.extra.paramTypes) { + if t := signature.extra.paramTypes[pos-1]; t != nil { + return t + } + } + target := signature.target + mapper := signature.mapper + for target.flags&signatureFlagsUninstantiatedParameters != 0 { + mapper = c.combineTypeMappers(target.mapper, mapper) + target = target.target + } + param := target.parameters[pos] + if param.CheckFlags()&ast.CheckFlagsInstantiated != 0 { + links := c.valueSymbolLinks.Get(param) + param = links.target + mapper = c.combineTypeMappers(links.mapper, mapper) + } + t := c.getTypeOfSymbol(param) + if c.couldContainTypeVariables(t) || param.Flags()&ast.SymbolFlagsSetAccessor != 0 { + t = c.instantiateType(t, mapper) + } + if pos == 0 { + signature.firstParamType = t + } else { + extra := signature.ensureExtra(c) + if extra.paramTypes == nil { + extra.paramTypes = c.typeListArena.NewSlice(len(signature.parameters) - 1) + } + extra.paramTypes[pos-1] = t + } + return t +} + +func (c *Checker) getSignatureParameters(sig *Signature) []*ast.Symbol { + if sig.flags&signatureFlagsUninstantiatedParameters != 0 { + sig.flags &^= signatureFlagsUninstantiatedParameters + target := sig.target + mapper := sig.mapper + for target.flags&signatureFlagsUninstantiatedParameters != 0 { + mapper = c.combineTypeMappers(target.mapper, mapper) + target = target.target + } + sig.parameters = c.instantiateSymbols(target.parameters, mapper) + } + return sig.parameters +} + +func (c *Checker) getSignatureThisParameter(sig *Signature) *ast.Symbol { + if sig.flags&signatureFlagsUninstantiatedThisParameter != 0 { + sig.flags &^= signatureFlagsUninstantiatedThisParameter + target := sig.target + mapper := sig.mapper + for target.flags&signatureFlagsUninstantiatedThisParameter != 0 { + mapper = c.combineTypeMappers(target.mapper, mapper) + target = target.target + } + sig.ensureExtra(c).thisParameter = c.instantiateSymbol(target.ThisParameter(), mapper) + } + return sig.ThisParameter() +} + +func (c *Checker) resolveExportedSignature(sig *Signature) *Signature { + for s := sig; s != nil; s = s.target { + c.getSignatureParameters(s) + c.getSignatureThisParameter(s) + } + return sig +} + +func (c *Checker) resolveExportedSignatures(sigs []*Signature) []*Signature { + for _, sig := range sigs { + c.resolveExportedSignature(sig) + } + return sigs +} + func (c *Checker) getConstraintOfType(t *Type) *Type { switch { case t.flags&TypeFlagsTypeParameter != 0: @@ -17690,7 +18095,8 @@ func (c *Checker) getConstraintFromConditionalType(t *Type) *Type { func (c *Checker) getDefaultConstraintOfConditionalType(t *Type) *Type { d := t.AsConditionalType() - if d.resolvedDefaultConstraint == nil { + ex := d.ensureExtra(c) + if ex.resolvedDefaultConstraint == nil { // 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, @@ -17699,19 +18105,20 @@ func (c *Checker) getDefaultConstraintOfConditionalType(t *Type) *Type { falseConstraint := c.getFalseTypeFromConditionalType(t) switch { case IsTypeAny(trueConstraint): - d.resolvedDefaultConstraint = falseConstraint + ex.resolvedDefaultConstraint = falseConstraint case IsTypeAny(falseConstraint): - d.resolvedDefaultConstraint = trueConstraint + ex.resolvedDefaultConstraint = trueConstraint default: - d.resolvedDefaultConstraint = c.getUnionType([]*Type{trueConstraint, falseConstraint}) + ex.resolvedDefaultConstraint = c.getUnionType([]*Type{trueConstraint, falseConstraint}) } } - return d.resolvedDefaultConstraint + return ex.resolvedDefaultConstraint } func (c *Checker) getConstraintOfDistributiveConditionalType(t *Type) *Type { d := t.AsConditionalType() - if d.resolvedConstraintOfDistributive == nil { + ex := d.ensureExtra(c) + if ex.resolvedConstraintOfDistributive == nil { // 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 @@ -17720,7 +18127,7 @@ func (c *Checker) getConstraintOfDistributiveConditionalType(t *Type) *Type { // We skip returning a distributive constraint for a restrictive instantiation of a conditional type // as the constraint for all type params (check type included) have been replace with `unknown`, which // is going to produce even more false positive/negative results than the distribute constraint already does. - // Please note: the distributive constraint is a kludge for emulating what a negated type could to do filter + // Please note: the distributive constraint is a kludge for emulating what a negated type could do to 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 { @@ -17731,15 +18138,15 @@ 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 + ex.resolvedConstraintOfDistributive = instantiated return instantiated } } } - d.resolvedConstraintOfDistributive = c.noConstraintType + ex.resolvedConstraintOfDistributive = c.noConstraintType } - if d.resolvedConstraintOfDistributive != c.noConstraintType { - return d.resolvedConstraintOfDistributive + if ex.resolvedConstraintOfDistributive != c.noConstraintType { + return ex.resolvedConstraintOfDistributive } return nil } @@ -17766,8 +18173,6 @@ func (c *Checker) getDeclaredTypeOfClassOrInterface(symbol *ast.Symbol) *Type { 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.target = t } } @@ -17814,57 +18219,295 @@ type keyBuilder struct { inlineBuffer [192]byte } -func (b *keyBuilder) hash() CacheHashKey { - if b.overflowBuffer == nil { - return CacheHashKey(xxh3.Hash128(b.inlineBuffer[:b.inlineLength])) +var wasmKeyBuilderPool []*keyBuilder + +func acquireKeyBuilder() *keyBuilder { + if n := len(wasmKeyBuilderPool); n > 0 { + b := wasmKeyBuilderPool[n-1] + wasmKeyBuilderPool = wasmKeyBuilderPool[:n-1] + b.inlineLength = 0 + if b.overflowBuffer != nil { + b.overflowBuffer = b.overflowBuffer[:0] + } + return b } - return CacheHashKey(xxh3.Hash128(append(b.overflowBuffer, b.inlineBuffer[:b.inlineLength]...))) + return &keyBuilder{} } -// spill moves the buffered bytes onto the end of overflowBuffer, so the key's byte -// stream stays overflowBuffer followed by inlineBuffer. -func (b *keyBuilder) spill() { - b.overflowBuffer = append(b.overflowBuffer, b.inlineBuffer[:b.inlineLength]...) - b.inlineLength = 0 +func (b *keyBuilder) releaseHash() CacheHashKey { + h := b.hash() + if runtime.GOARCH == "wasm" { + wasmKeyBuilderPool = append(wasmKeyBuilderPool, b) + } + return h } -func (b *keyBuilder) reserve(size int) { - if b.overflowBuffer != nil || b.inlineLength+size > len(b.inlineBuffer) { - b.overflowBuffer = slices.Grow(b.overflowBuffer, b.inlineLength+size) - } +type typeCacheSlot struct { + key CacheHashKey + val *Type } -func (b *keyBuilder) writeByte(c byte) { - if b.inlineLength == len(b.inlineBuffer) { - b.spill() +type typeCacheTable struct { + first typeCacheSlot + slots []typeCacheSlot + count uint32 + mask uint32 +} + +func (t *typeCacheTable) get(key CacheHashKey) *Type { + if t == nil { + return nil + } + if len(t.slots) == 0 { + if t.first.val != nil && t.first.key == key { + return t.first.val + } + return nil + } + mask := t.mask + idx := uint32(key.Lo^key.Hi) & mask + for { + s := &t.slots[idx] + if s.val == nil { + return nil + } + if s.key == key { + return s.val + } + idx = (idx + 1) & mask } - b.inlineBuffer[b.inlineLength] = c - b.inlineLength++ } -func (b *keyBuilder) writeString(s string) { - if b.inlineLength+len(s) > len(b.inlineBuffer) { - b.spill() - if len(s) > len(b.inlineBuffer) { - b.overflowBuffer = append(b.overflowBuffer, s...) +func (t *typeCacheTable) set(c *Checker, key CacheHashKey, val *Type) { + if len(t.slots) == 0 { + if c != nil { + t.slots = c.typeCacheSlotsArena.NewSlice(256) + } else { + t.slots = make([]typeCacheSlot, 256) + } + t.mask = 255 + } else if (t.count+1)*4 > uint32(len(t.slots))*3 { + t.grow(c) + } + mask := t.mask + idx := uint32(key.Lo^key.Hi) & mask + for { + s := &t.slots[idx] + if s.val == nil { + s.key = key + s.val = val + t.count++ return } + if s.key == key { + s.val = val + return + } + idx = (idx + 1) & mask } - b.inlineLength += copy(b.inlineBuffer[b.inlineLength:], s) } -func (b *keyBuilder) writeUint32(v uint32) { - if b.inlineLength+4 > len(b.inlineBuffer) { - b.spill() +func (t *ObjectType) setInstantiation(c *Checker, key CacheHashKey, val *Type) { + if t.instantiations == nil { + t.instantiations = c.typeCacheTableArena.New() } - binary.LittleEndian.PutUint32(b.inlineBuffer[b.inlineLength:], v) - b.inlineLength += 4 + t.instantiations.setSmall(c, key, val) } -func (b *keyBuilder) writeUint64(v uint64) { - if b.inlineLength+8 > len(b.inlineBuffer) { - b.spill() - } +func (t *typeCacheTable) setSmall(c *Checker, key CacheHashKey, val *Type) { + if len(t.slots) == 0 { + if t.first.val == nil || t.first.key == key { + t.first.key = key + t.first.val = val + t.count = 1 + return + } + if c != nil && len(c.freeTypeCacheSlots[2]) > 0 { + n := len(c.freeTypeCacheSlots[2]) - 1 + t.slots = c.freeTypeCacheSlots[2][n] + c.freeTypeCacheSlots[2] = c.freeTypeCacheSlots[2][:n] + } else { + t.slots = c.typeCacheSlotsArena.NewSlice(4) + } + t.mask = 3 + firstIdx := uint32(t.first.key.Lo^t.first.key.Hi) & 3 + t.slots[firstIdx] = t.first + t.first = typeCacheSlot{} + } else if (t.count+1)*4 > uint32(len(t.slots))*3 { + t.grow(c) + } + mask := t.mask + idx := uint32(key.Lo^key.Hi) & mask + for { + s := &t.slots[idx] + if s.val == nil { + s.key = key + s.val = val + t.count++ + return + } + if s.key == key { + s.val = val + return + } + idx = (idx + 1) & mask + } +} + +func (t *typeCacheTable) grow(c *Checker) { + oldSlots := t.slots + newCap := uint32(len(oldSlots)) * 2 + var newSlots []typeCacheSlot + newShift := bits.TrailingZeros32(newCap) + if c != nil && newShift < len(c.freeTypeCacheSlots) && len(c.freeTypeCacheSlots[newShift]) > 0 { + n := len(c.freeTypeCacheSlots[newShift]) - 1 + newSlots = c.freeTypeCacheSlots[newShift][n] + c.freeTypeCacheSlots[newShift] = c.freeTypeCacheSlots[newShift][:n] + } else if c != nil && newCap <= 1024 { + newSlots = c.typeCacheSlotsArena.NewSlice(int(newCap)) + } else { + newSlots = make([]typeCacheSlot, newCap) + } + mask := newCap - 1 + for i := range oldSlots { + s := &oldSlots[i] + if s.val != nil { + idx := uint32(s.key.Lo^s.key.Hi) & mask + for newSlots[idx].val != nil { + idx = (idx + 1) & mask + } + newSlots[idx] = *s + s.val = nil + } + } + if c != nil { + oldShift := newShift - 1 + if oldShift < len(c.freeTypeCacheSlots) { + c.freeTypeCacheSlots[oldShift] = append(c.freeTypeCacheSlots[oldShift], oldSlots) + } + } + t.slots = newSlots + t.mask = mask +} + + +const ( + packedInstantiationKeyTag = 0x1a5e + packedConditionalKeyTag = 0xc0d7 + packedUnionKeyTag = 0x55aa + packedIntersectionKeyTag = 0x33cc + packedIndexedAccessKeyTag = 0x9966 + packedUnionFrontKeyTag = 0x70f0 +) + +func packedTypeListKey(tag uint64, types []*Type, flag bool) (CacheHashKey, bool) { + return packedTypeListKeyWithAlias(tag, types, nil, flag) +} + +func packedSingleTypeKey(tag uint64, t *Type, flag bool) CacheHashKey { + var flagBit uint64 + if flag { + flagBit = 1 + } + id0 := uint64(t.id) + hi := (tag << 48) | (flagBit << 34) | (1 << 32) + y := (id0 ^ (hi * 0x9e3779b97f4a7c15)) * 0xbf58476d1ce4e5b9 + return CacheHashKey{Hi: hi, Lo: y ^ (y >> 32)} +} + +func packedTypeListKeyWithAlias(tag uint64, types []*Type, alias *TypeAlias, flag bool) (CacheHashKey, bool) { + var flagBit uint64 + if flag { + flagBit = 1 + } + if alias == nil { + if len(types) > 3 { + return CacheHashKey{}, false + } + var id0, id1, id2 uint64 + if len(types) > 0 { + id0 = uint64(types[0].id) + if len(types) > 1 { + id1 = uint64(types[1].id) + if len(types) > 2 { + id2 = uint64(types[2].id) + } + } + } + hi := (tag << 48) | (flagBit << 34) | (uint64(len(types)) << 32) | id2 + y := ((id0 | (id1 << 32)) ^ (hi * 0x9e3779b97f4a7c15)) * 0xbf58476d1ce4e5b9 + return CacheHashKey{Hi: hi, Lo: y ^ (y >> 32)}, true + } + if len(types) <= 1 && len(alias.typeArguments) <= 1 { + var id0, aliasArgId uint64 + if len(types) == 1 { + id0 = uint64(types[0].id) + } + if len(alias.typeArguments) == 1 { + aliasArgId = uint64(alias.typeArguments[0].id) + } + aliasSymId := uint64(ast.GetSymbolId(alias.symbol)) + hi := (tag << 48) | (1 << 36) | (uint64(len(alias.typeArguments)) << 35) | (flagBit << 34) | (uint64(len(types)) << 32) | aliasSymId + y := ((id0 | (aliasArgId << 32)) ^ (hi * 0x9e3779b97f4a7c15)) * 0xbf58476d1ce4e5b9 + return CacheHashKey{Hi: hi, Lo: y ^ (y >> 32)}, true + } + return CacheHashKey{}, false +} + +func (b *keyBuilder) hash() CacheHashKey { + if b.overflowBuffer == nil { + return CacheHashKey(xxh3.Hash128(b.inlineBuffer[:b.inlineLength])) + } + if b.inlineLength > 0 { + b.spill() + } + return CacheHashKey(xxh3.Hash128(b.overflowBuffer)) +} + +// spill moves the buffered bytes onto the end of overflowBuffer, so the key's byte +// stream stays overflowBuffer followed by inlineBuffer. +func (b *keyBuilder) spill() { + b.overflowBuffer = append(b.overflowBuffer, b.inlineBuffer[:b.inlineLength]...) + b.inlineLength = 0 +} + +func (b *keyBuilder) reserve(size int) { + if b.overflowBuffer != nil || b.inlineLength+size > len(b.inlineBuffer) { + b.overflowBuffer = slices.Grow(b.overflowBuffer, b.inlineLength+size) + } +} + +func (b *keyBuilder) writeByte(c byte) { + if b.inlineLength == len(b.inlineBuffer) { + b.spill() + } + b.inlineBuffer[b.inlineLength] = c + b.inlineLength++ +} + +func (b *keyBuilder) writeString(s string) { + if b.inlineLength+len(s) > len(b.inlineBuffer) { + b.spill() + if len(s) > len(b.inlineBuffer) { + b.overflowBuffer = append(b.overflowBuffer, s...) + return + } + } + b.inlineLength += copy(b.inlineBuffer[b.inlineLength:], s) +} + +func (b *keyBuilder) writeUint32(v uint32) { + if b.inlineLength+4 > len(b.inlineBuffer) { + b.spill() + } + binary.LittleEndian.PutUint32(b.inlineBuffer[b.inlineLength:], v) + b.inlineLength += 4 +} + +func (b *keyBuilder) writeUint64(v uint64) { + if b.inlineLength+8 > len(b.inlineBuffer) { + b.spill() + } binary.LittleEndian.PutUint64(b.inlineBuffer[b.inlineLength:], v) b.inlineLength += 8 } @@ -17945,18 +18588,76 @@ func (b *keyBuilder) writeNode(node *ast.Node) { } func getTypeListKey(types []*Type) CacheHashKey { + if key, ok := packedTypeListKey(packedInstantiationKeyTag, types, false); ok { + return key + } + return getTypeListKeySlow(types) +} + +func getTypeListKeySlow(types []*Type) CacheHashKey { + if runtime.GOARCH == "wasm" { + b := acquireKeyBuilder() + b.writeTypes(types) + return b.releaseHash() + } var b keyBuilder b.writeTypes(types) return b.hash() } func getAliasKey(alias *TypeAlias) CacheHashKey { + if alias == nil { + return CacheHashKey{} + } + return getAliasKeySlow(alias) +} + +func getAliasKeySlow(alias *TypeAlias) CacheHashKey { + if key, ok := packedTypeListKeyWithAlias(0, nil, alias, false); ok { + return key + } + if runtime.GOARCH == "wasm" { + b := acquireKeyBuilder() + b.writeAlias(alias) + return b.releaseHash() + } var b keyBuilder b.writeAlias(alias) return b.hash() } func getUnionKey(types []*Type, origin *Type, alias *TypeAlias) CacheHashKey { + if origin == nil { + if key, ok := packedTypeListKeyWithAlias(packedUnionKeyTag, types, alias, false); ok { + return key + } + } + return getUnionKeySlow(types, origin, alias) +} + +func getUnionKeySlow(types []*Type, origin *Type, alias *TypeAlias) CacheHashKey { + if runtime.GOARCH == "wasm" { + b := acquireKeyBuilder() + switch { + case origin == nil: + b.writeTypes(types) + case origin.flags&TypeFlagsUnion != 0: + b.writeByte('|') + b.writeTypes(origin.Types()) + case origin.flags&TypeFlagsIntersection != 0: + b.writeByte('&') + b.writeTypes(origin.Types()) + case origin.flags&TypeFlagsIndex != 0: + b.writeByte('#') + b.writeType(origin) + b.writeByte('|') + b.writeTypes(types) + default: + panic("Unhandled case in getUnionKey") + } + b.writeAlias(alias) + return b.releaseHash() + } var b keyBuilder switch { case origin == nil: @@ -17968,7 +18669,7 @@ func getUnionKey(types []*Type, origin *Type, alias *TypeAlias) CacheHashKey { b.writeByte('&') b.writeTypes(origin.Types()) case origin.flags&TypeFlagsIndex != 0: - // origin type id alone is insufficient, as `keyof x` may resolve to multiple WIP values while `x` is still resolving + // Origin type id alone is insufficient, as `keyof X` may resolve to multiple WIP values while `X` is still resolving b.writeByte('#') b.writeType(origin) b.writeByte('|') @@ -17981,6 +18682,27 @@ func getUnionKey(types []*Type, origin *Type, alias *TypeAlias) CacheHashKey { } func getIntersectionKey(types []*Type, flags IntersectionFlags, alias *TypeAlias) CacheHashKey { + var effAlias *TypeAlias + if flags&IntersectionFlagsNoConstraintReduction == 0 { + effAlias = alias + } + if key, ok := packedTypeListKeyWithAlias(packedIntersectionKeyTag, types, effAlias, flags&IntersectionFlagsNoConstraintReduction != 0); ok { + return key + } + return getIntersectionKeySlow(types, flags, alias) +} + +func getIntersectionKeySlow(types []*Type, flags IntersectionFlags, alias *TypeAlias) CacheHashKey { + if runtime.GOARCH == "wasm" { + b := acquireKeyBuilder() + b.writeTypes(types) + if flags&IntersectionFlagsNoConstraintReduction == 0 { + b.writeAlias(alias) + } else { + b.writeByte('*') + } + return b.releaseHash() + } var b keyBuilder b.writeTypes(types) if flags&IntersectionFlagsNoConstraintReduction == 0 { @@ -17992,6 +18714,28 @@ func getIntersectionKey(types []*Type, flags IntersectionFlags, alias *TypeAlias } func getTupleKey(elementInfos []TupleElementInfo, readonly bool) CacheHashKey { + if runtime.GOARCH == "wasm" { + b := acquireKeyBuilder() + for _, e := range elementInfos { + switch { + case e.flags&ElementFlagsRequired != 0: + b.writeByte('#') + case e.flags&ElementFlagsOptional != 0: + b.writeByte('?') + case e.flags&ElementFlagsRest != 0: + b.writeByte('.') + default: + b.writeByte('*') + } + if e.labeledDeclaration != nil { + b.writeNode(e.labeledDeclaration) + } + } + if readonly { + b.writeByte('!') + } + return b.releaseHash() + } var b keyBuilder for _, e := range elementInfos { switch { @@ -18019,6 +18763,22 @@ func getTypeAliasInstantiationKey(typeArguments []*Type, alias *TypeAlias) Cache } func getTypeInstantiationKey(typeArguments []*Type, alias *TypeAlias, singleSignature bool) CacheHashKey { + if key, ok := packedTypeListKeyWithAlias(packedInstantiationKeyTag, typeArguments, alias, singleSignature); ok { + return key + } + return getTypeInstantiationKeySlow(typeArguments, alias, singleSignature) +} + +func getTypeInstantiationKeySlow(typeArguments []*Type, alias *TypeAlias, singleSignature bool) CacheHashKey { + if runtime.GOARCH == "wasm" { + b := acquireKeyBuilder() + b.writeTypes(typeArguments) + b.writeAlias(alias) + if singleSignature { + b.writeByte('!') + } + return b.releaseHash() + } var b keyBuilder b.writeTypes(typeArguments) b.writeAlias(alias) @@ -18029,6 +18789,29 @@ func getTypeInstantiationKey(typeArguments []*Type, alias *TypeAlias, singleSign } func getIndexedAccessKey(objectType *Type, indexType *Type, accessFlags AccessFlags, alias *TypeAlias) CacheHashKey { + if alias == nil { + hi := (uint64(packedIndexedAccessKeyTag) << 48) | uint64(accessFlags) + y := ((uint64(objectType.id) | (uint64(indexType.id) << 32)) ^ (hi * 0x9e3779b97f4a7c15)) * 0xbf58476d1ce4e5b9 + return CacheHashKey{Hi: hi, Lo: y ^ (y >> 32)} + } + return getIndexedAccessKeySlow(objectType, indexType, accessFlags, alias) +} + +func getIndexedAccessKeySlow(objectType *Type, indexType *Type, accessFlags AccessFlags, alias *TypeAlias) CacheHashKey { + if len(alias.typeArguments) == 0 { + aliasSymId := uint64(ast.GetSymbolId(alias.symbol)) + hi := (uint64(packedIndexedAccessKeyTag) << 48) | (1 << 36) | (uint64(accessFlags) << 32) | aliasSymId + y := ((uint64(objectType.id) | (uint64(indexType.id) << 32)) ^ (hi * 0x9e3779b97f4a7c15)) * 0xbf58476d1ce4e5b9 + return CacheHashKey{Hi: hi, Lo: y ^ (y >> 32)} + } + if runtime.GOARCH == "wasm" { + b := acquireKeyBuilder() + b.writeType(objectType) + b.writeType(indexType) + b.writeUint32(uint32(accessFlags)) + b.writeAlias(alias) + return b.releaseHash() + } var b keyBuilder b.writeType(objectType) b.writeType(indexType) @@ -18038,6 +18821,19 @@ func getIndexedAccessKey(objectType *Type, indexType *Type, accessFlags AccessFl } func getTemplateTypeKey(texts []string, types []*Type) CacheHashKey { + if runtime.GOARCH == "wasm" { + b := acquireKeyBuilder() + b.writeTypes(types) + b.writeByte('|') + for _, s := range texts { + b.writeInt(len(s)) + } + b.writeByte('|') + for _, s := range texts { + b.writeString(s) + } + return b.releaseHash() + } var b keyBuilder b.writeTypes(types) b.writeByte('|') @@ -18051,7 +18847,41 @@ func getTemplateTypeKey(texts []string, types []*Type) CacheHashKey { return b.hash() } +func (c *Checker) getTemplateTypeKey(texts []string, types []*Type) CacheHashKey { + b := &c.leafKeyBuilder + b.inlineLength = 0 + if b.overflowBuffer != nil { + b.overflowBuffer = b.overflowBuffer[:0] + } + b.writeTypes(types) + b.writeByte('|') + for _, s := range texts { + b.writeInt(len(s)) + } + b.writeByte('|') + for _, s := range texts { + b.writeString(s) + } + return b.hash() +} + func getConditionalTypeKey(typeArguments []*Type, alias *TypeAlias, forConstraint bool) CacheHashKey { + if key, ok := packedTypeListKeyWithAlias(packedConditionalKeyTag, typeArguments, alias, forConstraint); ok { + return key + } + return getConditionalTypeKeySlow(typeArguments, alias, forConstraint) +} + +func getConditionalTypeKeySlow(typeArguments []*Type, alias *TypeAlias, forConstraint bool) CacheHashKey { + if runtime.GOARCH == "wasm" { + b := acquireKeyBuilder() + b.writeTypes(typeArguments) + b.writeAlias(alias) + if forConstraint { + b.writeByte('!') + } + return b.releaseHash() + } var b keyBuilder b.writeTypes(typeArguments) b.writeAlias(alias) @@ -18089,9 +18919,28 @@ func getNodeListKey(nodes []*ast.Node) CacheHashKey { } 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) - }) + if t.objectFlags&ObjectFlagsReference == 0 || t.objectFlags&(ObjectFlagsHasGenericArgumentsComputed|ObjectFlagsHasGenericArguments) == ObjectFlagsHasGenericArgumentsComputed { + return false + } + return isTypeReferenceWithGenericArgumentsSlow(t) +} + +func isTypeReferenceWithGenericArgumentsSlow(t *Type) bool { + if t.objectFlags&ObjectFlagsHasGenericArguments != 0 { + return true + } + if t.AsTypeReference().node != nil { + return false + } + var result ObjectFlags + for _, arg := range t.checker.getTypeArguments(t) { + if arg.flags&TypeFlagsTypeParameter != 0 || (arg.objectFlags&ObjectFlagsHasGenericArgumentsComputed != 0 && arg.objectFlags&ObjectFlagsHasGenericArguments != 0) || (arg.objectFlags&ObjectFlagsHasGenericArgumentsComputed == 0 && arg.objectFlags&ObjectFlagsReference != 0 && isTypeReferenceWithGenericArgumentsSlow(arg)) { + result = ObjectFlagsHasGenericArguments + break + } + } + t.objectFlags |= ObjectFlagsHasGenericArgumentsComputed | result + return result != 0 } func isNonDeferredTypeReference(t *Type) bool { @@ -18147,8 +18996,8 @@ func (c *Checker) getTypeForBindingElementParent(node *ast.Node, checkMode Check if checkMode == CheckModeNormal { // We can use a cached resolved type if no optionality was included in that type. if symbol := c.getSymbolOfDeclaration(node); symbol != nil { - if resolvedType := c.valueSymbolLinks.Get(symbol).resolvedType; resolvedType != nil && !(c.strictNullChecks && isOptionalDeclaration(node)) { - return resolvedType + if links := c.valueSymbolLinks.TryGet(symbol); links != nil && links.resolvedType != nil && !(c.strictNullChecks && isOptionalDeclaration(node)) { + return links.resolvedType } } } @@ -19214,12 +20063,13 @@ func (c *Checker) pushTypeResolution(target TypeSystemEntity, propertyName TypeS resolutionCycleStartIndex := c.findResolutionCycleStartIndex(target, propertyName) if resolutionCycleStartIndex >= 0 { // A cycle was found + c.resolutionCycleCount++ for i := resolutionCycleStartIndex; i < len(c.typeResolutions); i++ { c.typeResolutions[i].result = false } return false } - c.typeResolutions = append(c.typeResolutions, TypeResolution{target: target, propertyName: propertyName, result: true}) + c.typeResolutions = append(c.typeResolutions, TypeResolution{target: target, propertyName: propertyName, result: true, serial: 0}) return true } @@ -19308,47 +20158,22 @@ 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 nil -} - -// For classes and interfaces, the layout properties of the type are uninstantiated symbols from -// the shared member layout. When operations only process the declaration, flags, and/or name -// of members (and not their type), using the layout properties helps defer instantiation of -// the members. For example, `keyof T` can be computed entirely from layout properties. -func (c *Checker) getLayoutPropertiesOfType(t *Type) []*ast.Symbol { - t = c.getReducedApparentType(t) - lazy := c.getLazyStateOfType(t) - if lazy == nil { - return c.getPropertiesOfType(t) - } - layout := lazy.layout - if layout.layoutProperties == nil { - layout.layoutProperties = make([]*ast.Symbol, len(layout.memberInfos)) - for i, memberInfo := range layout.memberInfos { - layout.layoutProperties[i] = memberInfo.symbol - } - } - return layout.layoutProperties -} - -func (c *Checker) getLazyStateOfType(t *Type) *LazyState { - if t.flags&TypeFlagsObject != 0 { - return c.resolveStructuredTypeMembers(t).getLazyState() + return c.resolveStructuredTypeMembers(t).getPropertiesSlice() } return nil } func (c *Checker) getPropertiesOfUnionOrIntersectionType(t *Type) []*ast.Symbol { d := t.AsUnionOrIntersectionType() - if d.resolvedProperties == nil { - var checked collections.Set[string] + if d.getPropertiesSlice() == nil { + checked := c.getTemporaryNameSet() + defer c.releaseTemporaryNameSet(checked) props := []*ast.Symbol{} for _, current := range d.types { - for _, prop := range c.getPropertiesOfType(current) { - if !checked.Has(prop.Name()) { - checked.Add(prop.Name()) + currentProps, _ := c.getPropertiesOfTypeLazily(current) + for _, prop := range currentProps { + if _, ok := checked[prop.Name()]; !ok { + checked[prop.Name()] = struct{}{} combinedProp := c.getPropertyOfUnionOrIntersectionType(t, prop.Name(), t.flags&TypeFlagsIntersection != 0 /*skipObjectFunctionPropertyAugment*/) if combinedProp != nil { props = append(props, combinedProp) @@ -19361,13 +20186,91 @@ func (c *Checker) getPropertiesOfUnionOrIntersectionType(t *Type) []*ast.Symbol break } } - d.resolvedProperties = props + d.ensureExtra().properties = props } - return d.resolvedProperties + return d.getPropertiesSlice() } func (c *Checker) getPropertyOfType(t *Type, name string) *ast.Symbol { - return c.getPropertyOfTypeEx(t, name, false /*skipObjectFunctionPropertyAugment*/, false /*includeTypeOnlyMembers*/) + return c.getPropertyOfTypeWorker(t, name, false /*skipObjectFunctionPropertyAugment*/, false /*includeTypeOnlyMembers*/, true /*instantiate*/) +} + +func (c *Checker) hasPropertyOfType(t *Type, name string) bool { + return c.getPropertyOfTypeWorker(t, name, false /*skipObjectFunctionPropertyAugment*/, false /*includeTypeOnlyMembers*/, false /*instantiate*/) != nil +} + +func (c *Checker) isResolvedPlainObjectType(t *Type) bool { + return t.flags == TypeFlagsObject && t.objectFlags&(ObjectFlagsMapped|ObjectFlagsMembersResolved) == ObjectFlagsMembersResolved && (t.symbol == nil || t.symbol.Flags()&ast.SymbolFlagsValueModule == 0) +} + +func (c *Checker) getResolvedPropertyOfPlainObjectType(t *Type, name string) *ast.Symbol { + if t.objectFlags&ObjectFlagsMembersResolved != 0 { + if symbol := t.AsStructuredType().getMember(name); symbol != nil { + if symbol.Flags()&ast.SymbolFlagsValue != 0 { + return symbol + } + } else if !c.mayBeGlobalObjectMember(name) { + return nil + } + } + return c.getPropertyOfType(t, name) +} + +func nameFilterBit(name string) (uint8, uint64, uint8, uint64) { + if len(name) == 0 { + return 0, 1, 0, 1 + } + h := uint32(len(name))*131 + uint32(name[0])*31 + uint32(name[len(name)-1]) + if len(name) > 2 { + h = h*31 + uint32(name[1]) + } + h *= 0x9e3779b9 + return uint8((h >> 6) & 3), uint64(1) << (h & 63), uint8((h >> 22) & 3), uint64(1) << ((h >> 16) & 63) +} + +func addTypeToGlobalFilter(t *Type, filter *[4]uint64) bool { + if t == nil || t.flags&TypeFlagsObject == 0 || t.objectFlags&ObjectFlagsMembersResolved == 0 { + return false + } + st := t.AsStructuredType() + if lazy := st.getLazyState(); lazy != nil { + for _, info := range lazy.layout.memberInfos { + w1, b1, w2, b2 := nameFilterBit(info.symbol.Name()) + filter[w1] |= b1 + filter[w2] |= b2 + } + return true + } + if mIdx := st.getMembersIndex(); mIdx != nil && mIdx.fallback != nil { + for k := range mIdx.fallback.All() { + w1, b1, w2, b2 := nameFilterBit(k) + filter[w1] |= b1 + filter[w2] |= b2 + } + } else { + for _, p := range st.getPropertiesSlice() { + w1, b1, w2, b2 := nameFilterBit(p.Name()) + filter[w1] |= b1 + filter[w2] |= b2 + } + } + return true +} + +func (c *Checker) mayBeGlobalObjectMember(name string) bool { + if !c.globalObjectFilterReady { + var filter [4]uint64 + if !addTypeToGlobalFilter(c.globalObjectType, &filter) || + !addTypeToGlobalFilter(c.globalFunctionType, &filter) || + !addTypeToGlobalFilter(c.globalCallableFunctionType, &filter) || + !addTypeToGlobalFilter(c.globalNewableFunctionType, &filter) { + return true + } + c.globalObjectAugmentFilter = filter + c.globalObjectFilterReady = true + } + w1, b1, w2, b2 := nameFilterBit(name) + return c.globalObjectAugmentFilter[w1]&b1 != 0 && c.globalObjectAugmentFilter[w2]&b2 != 0 } /** @@ -19379,11 +20282,14 @@ func (c *Checker) getPropertyOfType(t *Type, name string) *ast.Symbol { * @param name a name of property to look up in a given type */ func (c *Checker) getPropertyOfTypeEx(t *Type, name string, skipObjectFunctionPropertyAugment bool, includeTypeOnlyMembers bool) *ast.Symbol { + return c.getPropertyOfTypeWorker(t, name, skipObjectFunctionPropertyAugment, includeTypeOnlyMembers, true /*instantiate*/) +} + +func (c *Checker) getPropertyOfTypeWorker(t *Type, name string, skipObjectFunctionPropertyAugment bool, includeTypeOnlyMembers bool, instantiate bool) *ast.Symbol { t = c.getReducedApparentType(t) switch { case t.flags&TypeFlagsObject != 0: - resolved := c.resolveStructuredTypeMembers(t) - symbol := resolved.getMember(name) + symbol := c.getMemberOfStructuredTypeEx(t, name, instantiate) 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 @@ -19395,16 +20301,16 @@ func (c *Checker) getPropertyOfTypeEx(t *Type, name string, skipObjectFunctionPr return symbol } } - if skipObjectFunctionPropertyAugment { + if skipObjectFunctionPropertyAugment || !c.mayBeGlobalObjectMember(name) { return nil } var functionType *Type switch { case t == c.anyFunctionType: functionType = c.globalFunctionType - case len(resolved.CallSignatures()) != 0: + case len(c.getSignaturesOfStructuredType(t, SignatureKindCall)) != 0: functionType = c.globalCallableFunctionType - case len(resolved.ConstructSignatures()) != 0: + case len(c.getSignaturesOfStructuredType(t, SignatureKindConstruct)) != 0: functionType = c.globalNewableFunctionType } if functionType != nil { @@ -19446,7 +20352,15 @@ func (c *Checker) getSignaturesOfStructuredType(t *Type, kind SignatureKind) []* if t.flags&TypeFlagsStructuredType == 0 || t.objectFlags&(ObjectFlagsMapped|ObjectFlagsReverseMapped) != 0 { return nil } - resolved := c.resolveStructuredTypeMembers(t) + if t.objectFlags&ObjectFlagsMembersResolved == 0 { + if t.flags&TypeFlagsObject != 0 && t.objectFlags&ObjectFlagsAnonymous != 0 && (t.symbol == nil || t.symbol.Flags()&ast.SymbolFlagsValueModule == 0) { + if d := t.AsObjectType(); d.target != nil && len(c.getSignaturesOfStructuredType(d.target, kind)) == 0 { + return nil + } + } + c.resolveStructuredTypeMembers(t) + } + resolved := t.AsStructuredType() if resolved.extra == nil { return nil } @@ -19462,7 +20376,18 @@ func (c *Checker) getIndexInfosOfType(t *Type) []*IndexInfo { func (c *Checker) getIndexInfosOfStructuredType(t *Type) []*IndexInfo { if t.flags&TypeFlagsStructuredType != 0 { - return c.resolveStructuredTypeMembers(t).getIndexInfos() + if t.objectFlags&ObjectFlagsMembersResolved == 0 { + if lazy := c.getLazyMappedTable(t); lazy != nil { + return c.getLazyMappedTypeIndexInfos(t, lazy) + } + if t.flags&TypeFlagsObject != 0 && t.objectFlags&ObjectFlagsAnonymous != 0 && (t.symbol == nil || t.symbol.Flags()&ast.SymbolFlagsValueModule == 0) { + if d := t.AsObjectType(); d.target != nil && len(c.getIndexInfosOfStructuredType(d.target)) == 0 { + return nil + } + } + return c.resolveStructuredTypeMembers(t).getIndexInfos() + } + return t.AsStructuredType().getIndexInfos() } return nil } @@ -19495,45 +20420,65 @@ func (c *Checker) getApplicableIndexInfo(t *Type, keyType *Type) *IndexInfo { } func (c *Checker) getApplicableIndexInfoForName(t *Type, name string) *IndexInfo { + indexInfos := c.getIndexInfosOfType(t) + if len(indexInfos) == 0 { + return nil + } if isLateBoundName(name) { - return c.getApplicableIndexInfo(t, c.esSymbolType) + return c.findApplicableIndexInfo(indexInfos, c.esSymbolType) } - return c.getApplicableIndexInfo(t, c.getStringLiteralType(name)) + return c.findApplicableIndexInfo(indexInfos, c.getStringLiteralType(name)) } func (c *Checker) findApplicableIndexInfo(indexInfos []*IndexInfo, keyType *Type) *IndexInfo { + if len(indexInfos) == 0 { + return nil + } // Index signatures for type 'string' are considered only when no other index signatures apply. var stringIndexInfo *IndexInfo - applicableInfos := make([]*IndexInfo, 0, 8) + var firstApplicable *IndexInfo + var applicableInfosBuf [4]*IndexInfo + var applicableInfos []*IndexInfo for _, info := range indexInfos { if info.keyType == c.stringType { stringIndexInfo = info } else if c.isApplicableIndexType(keyType, info.keyType) { - applicableInfos = append(applicableInfos, info) + if firstApplicable == nil { + firstApplicable = info + } else { + if applicableInfos == nil { + applicableInfos = append(applicableInfosBuf[:0], firstApplicable, info) + } else { + applicableInfos = append(applicableInfos, info) + } + } } } - // When more than one index signature is applicable we create a synthetic IndexInfo. Instead of computing - // the intersected key type, we just use unknownType for the key type as nothing actually depends on the - // keyType property of the returned IndexInfo. - switch len(applicableInfos) { - case 0: + // When more than one index signature is applicable we create a synthetic IndexInfo with an intersection of the + // value types and a readonly flag that is true if all applicable index signatures are readonly. Note that for + // the intersected key type, we just use unknownType for the key type as nothing actually depends on it. + if applicableInfos == nil { + if firstApplicable != nil { + return firstApplicable + } if stringIndexInfo != nil && c.isApplicableIndexType(keyType, c.stringType) { return stringIndexInfo } return nil - case 1: - return applicableInfos[0] - default: - isReadonly := true - types := make([]*Type, len(applicableInfos)) - for i, info := range applicableInfos { - types[i] = info.valueType - if !info.isReadonly { - isReadonly = false - } + } + isReadonly := true + var typesBuf [4]*Type + types := typesBuf[:0] + if len(applicableInfos) > len(typesBuf) { + types = make([]*Type, 0, len(applicableInfos)) + } + for _, info := range applicableInfos { + types = append(types, info.valueType) + if !info.isReadonly { + isReadonly = false } - return c.newIndexInfo(c.unknownType, c.getIntersectionType(types), isReadonly, nil, nil) } + return c.newIndexInfo(c.unknownType, c.getIntersectionType(types), isReadonly, nil, nil) } func (c *Checker) isApplicableIndexType(source *Type, target *Type) bool { @@ -19546,31 +20491,197 @@ func (c *Checker) isApplicableIndexType(source *Type, target *Type) bool { func (c *Checker) resolveStructuredTypeMembers(t *Type) *StructuredType { if t.objectFlags&ObjectFlagsMembersResolved == 0 { + return c.resolveStructuredTypeMembersSlow(t) + } + return t.AsStructuredType() +} + +func (c *Checker) resolveStructuredTypeMembersSlow(t *Type) *StructuredType { + switch { + case t.flags&TypeFlagsObject != 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") - } - case t.flags&TypeFlagsUnion != 0: - c.resolveUnionTypeMembers(t) - case t.flags&TypeFlagsIntersection != 0: - c.resolveIntersectionTypeMembers(t) + 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() } +func (c *Checker) getLazyStateOfType(t *Type) *LazyState { + if t.flags&TypeFlagsObject != 0 { + return c.resolveStructuredTypeMembers(t).getLazyState() + } + return nil +} + +func (c *Checker) getMemberOfStructuredType(t *Type, name string) *ast.Symbol { + return c.getMemberOfStructuredTypeEx(t, name, true /*instantiate*/) +} + +func (c *Checker) getMemberOfStructuredTypeEx(t *Type, name string, instantiate bool) *ast.Symbol { + if t.objectFlags&ObjectFlagsMembersResolved != 0 { + if !instantiate { + if lazy := t.AsStructuredType().getLazyState(); lazy != nil { + slotIndex := lazy.layout.lookupMemberIndex(name) + if slotIndex < 0 { + return nil + } + if t.AsStructuredType().extra != nil && slotIndex < len(t.AsStructuredType().extra.properties) { + if prop := t.AsStructuredType().extra.properties[slotIndex]; prop != nil { + return prop + } + } + return lazy.layout.memberInfos[slotIndex].symbol + } + } + return t.AsStructuredType().getMember(name) + } + if t.objectFlags&ObjectFlagsMapped != 0 && !isReservedMemberName(name) { + if lazy := c.getLazyMappedTable(t); lazy != nil { + if !instantiate && !lazy.resolving && !isLateBoundName(name) && !strings.HasPrefix(name, "\xFE#") { + if existing, ok := lazy.members[name]; ok { + return existing + } + lazy.resolving = true + modifiersProp := c.getMemberOfStructuredTypeEx(lazy.modifiersType, name, false /*instantiate*/) + lazy.resolving = false + if modifiersProp == nil { + return nil + } + if c.isNamedMember(modifiersProp, name) && getDeclarationModifierFlagsFromSymbol(modifiersProp)&ast.ModifierFlagsNonPublicAccessibilityModifier == 0 { + return modifiersProp + } + } + if member, ok := c.getLazyMappedTypeMember(t, lazy, name); ok { + return member + } + } + } + if !isReservedMemberName(name) && t.flags&TypeFlagsObject != 0 && t.objectFlags&ObjectFlagsAnonymous != 0 && (t.symbol == nil || t.symbol.Flags()&ast.SymbolFlagsValueModule == 0) { + if d := t.AsObjectType(); d.target != nil { + targetResolved := c.resolveStructuredTypeMembers(d.target) + if targetMembers := targetResolved.getMembersIndex(); targetMembers == nil || targetMembers.fallback == nil { + targetProp := targetResolved.getMember(name) + if targetProp == nil || !instantiate { + return targetProp + } + } + } + } + resolved := c.resolveStructuredTypeMembers(t) + if !instantiate { + if lazy := resolved.getLazyState(); lazy != nil { + slotIndex := lazy.layout.lookupMemberIndex(name) + if slotIndex < 0 { + return nil + } + if resolved.extra != nil && slotIndex < len(resolved.extra.properties) { + if prop := resolved.extra.properties[slotIndex]; prop != nil { + return prop + } + } + return lazy.layout.memberInfos[slotIndex].symbol + } + } + return resolved.getMember(name) +} + +func (c *Checker) everyPropertyOfStructuredType(t *Type, f func(*ast.Symbol) bool) bool { + props, _ := c.getPropertiesOfTypeLazily(t) + for _, p := range props { + if !f(p) { + return false + } + } + return true +} + +func (c *Checker) hasPropertiesOfStructuredType(t *Type) bool { + if t.flags&TypeFlagsObject != 0 && t.objectFlags&(ObjectFlagsClassOrInterface|ObjectFlagsReference) != 0 { + return c.resolveStructuredTypeMembers(t).hasProperties() + } + return !c.everyPropertyOfStructuredType(t, func(*ast.Symbol) bool { return false }) +} + +// For classes and interfaces, the layout properties of the type are uninstantiated symbols from +// the shared member layout. When operations only process the declaration, flags, and/or name +// of members (and not their type), using the layout properties helps defer instantiation of +// the members. For example, 'keyof T' can be computed entirely from layout properties. +func (c *Checker) getLayoutPropertiesOfType(t *Type) []*ast.Symbol { + props, _ := c.getPropertiesOfTypeLazily(t) + return props +} + +func (c *Checker) getPropertiesOfTypeLazily(t *Type) (properties []*ast.Symbol, lazyType *Type) { + t = c.getReducedApparentType(t) + if t.flags&TypeFlagsUnionOrIntersection != 0 { + return c.getPropertiesOfUnionOrIntersectionType(t), nil + } + if t.flags&TypeFlagsObject != 0 { + if t.objectFlags&(ObjectFlagsClassOrInterface|ObjectFlagsReference) != 0 { + resolved := c.resolveStructuredTypeMembers(t) + if lazy := resolved.getLazyState(); lazy != nil { + if lazy.propertiesResolved { + return resolved.getProperties(), nil + } + layout := lazy.layout + if layout.layoutProperties == nil { + if len(layout.memberInfos) == 0 { + return nil, nil + } + layout.layoutProperties = c.symbolListArena.NewSlice(len(layout.memberInfos)) + for i, memberInfo := range layout.memberInfos { + layout.layoutProperties[i] = memberInfo.symbol + } + } + return layout.layoutProperties, t + } + return resolved.getProperties(), nil + } + if t.objectFlags&ObjectFlagsMembersResolved == 0 { + if t.objectFlags&ObjectFlagsAnonymous != 0 && (t.symbol == nil || t.symbol.Flags()&ast.SymbolFlagsValueModule == 0) { + if d := t.AsObjectType(); d.target != nil { + targetResolved := c.resolveStructuredTypeMembers(d.target) + if targetMembers := targetResolved.getMembersIndex(); targetMembers == nil || targetMembers.fallback == nil { + return targetResolved.getProperties(), t + } + } + } + if lazy := c.getLazyMappedTable(t); lazy != nil && !lazy.resolving && lazy.templateModifiers&(MappedTypeModifiersIncludeOptional|MappedTypeModifiersExcludeOptional) == 0 && len(lazy.members) == 0 { + lazy.resolving = true + modProps, _ := c.getPropertiesOfTypeLazily(lazy.modifiersType) + lazy.resolving = false + canUseModProps := true + for _, p := range modProps { + if isLateBoundName(p.Name()) || strings.HasPrefix(p.Name(), "\xFE#") || p.Flags()&ast.SymbolFlagsPrototype != 0 || getDeclarationModifierFlagsFromSymbol(p)&ast.ModifierFlagsNonPublicAccessibilityModifier != 0 { + canUseModProps = false + break + } + } + if canUseModProps && t.objectFlags&ObjectFlagsMembersResolved == 0 { + return modProps, t + } + } + } + return c.resolveStructuredTypeMembers(t).getProperties(), nil + } + return nil, nil +} + func (c *Checker) resolveObjectTypeMembers(t *Type) { var mapper *TypeMapper var typeParameters []*Type @@ -19579,12 +20690,15 @@ func (c *Checker) resolveObjectTypeMembers(t *Type) { var callSignatures []*Signature var constructSignatures []*Signature var indexInfos []*IndexInfo - source := c.getTargetType(t) + source := getTargetType(t) if t.objectFlags&ObjectFlagsReference != 0 { typeParameters = source.AsInterfaceType().allTypeParameters typeArguments = c.getTypeArguments(t) if len(typeArguments) == len(typeParameters)-1 { - typeArguments = core.Concatenate(typeArguments, []*Type{t}) + padded := c.typeListArena.NewSlice(len(typeArguments) + 1) + copy(padded, typeArguments) + padded[len(typeArguments)] = t + typeArguments = padded } } resolved := c.resolveDeclaredMembers(source) @@ -19602,7 +20716,7 @@ func (c *Checker) resolveObjectTypeMembers(t *Type) { instantiatedBaseTypes := baseTypes thisArgument := core.LastOrNil(typeArguments) if len(baseTypes) != 0 && thisArgument != nil { - instantiatedBaseTypes = make([]*Type, len(baseTypes)) + instantiatedBaseTypes = c.typeListArena.NewSlice(len(baseTypes)) for i, baseType := range baseTypes { instantiatedBaseTypes[i] = c.getTypeWithThisArgument(c.instantiateType(baseType, mapper), thisArgument, false /*needsApparentType*/) } @@ -19634,7 +20748,18 @@ func (c *Checker) getMemberLayout(t *Type, baseTypes []*Type) *MemberLayout { if t.objectFlags&ObjectFlagsReference == 0 { return c.createMemberLayout(t, baseTypes) } - source := c.getTargetType(t) + source := getTargetType(t) + if len(baseTypes) == 0 { + d := source.AsInterfaceType() + count := int32(d.declaredMembers.Len()) + if d.declaredMemberLayout != nil && d.declaredMemberLayoutCount == count { + return d.declaredMemberLayout + } + layout := c.createMemberLayout(source, nil) + d.declaredMemberLayout = layout + d.declaredMemberLayoutCount = count + return layout + } key := c.getMemberLayoutKey(source, baseTypes) layout := c.memberLayoutCache[key] if layout == nil { @@ -19648,6 +20773,21 @@ func (c *Checker) getMemberLayout(t *Type, baseTypes []*Type) *MemberLayout { } func (c *Checker) getMemberLayoutKey(t *Type, baseTypes []*Type) CacheHashKey { + if len(baseTypes) == 1 { + baseType := baseTypes[0] + declCount := uint64(uint32(t.AsInterfaceType().declaredMembers.Len())) + if baseType.objectFlags&ObjectFlagsReference != 0 { + c.resolveStructuredTypeMembers(baseType) + return CacheHashKey{ + Hi: (uint64(0x91a1) << 48) | (1 << 32) | uint64(uint32(t.id)), + Lo: (declCount << 32) | uint64(baseType.AsTypeReference().lazy.layout.id), + } + } + return CacheHashKey{ + Hi: (uint64(0x91a1) << 48) | uint64(uint32(t.id)), + Lo: (declCount << 32) | uint64(uint32(baseType.id)), + } + } var b keyBuilder b.reserve(20 + 5*len(baseTypes)) b.writeType(t) @@ -19668,60 +20808,96 @@ func (c *Checker) getMemberLayoutKey(t *Type, baseTypes []*Type) CacheHashKey { } func (c *Checker) createMemberLayout(t *Type, baseTypes []*Type) *MemberLayout { - // Reuse c.layoutMemberInfos to avoid repeated allocations - layoutMemberInfos := c.layoutMemberInfos - c.layoutMemberInfos = nil - addMember := func(symbol *ast.Symbol, baseIndex int) { - if _, ok := layoutMemberInfos[symbol.Name()]; !ok { - if layoutMemberInfos == nil { - layoutMemberInfos = make(map[string]MemberInfo) + declaredMembers := t.AsInterfaceType().declaredMembers + var memberInfos []MemberInfo + var memberInfoMap map[string]int32 + if len(baseTypes) == 0 && symbolTableKeysMatchNames(declaredMembers) { + n := 0 + for id, symbol := range declaredMembers.All() { + if c.isNamedMember(symbol, id) { + n++ + } + } + if n != 0 { + memberInfos = c.memberInfoListArena.NewSlice(n) + i := 0 + for id, symbol := range declaredMembers.All() { + if c.isNamedMember(symbol, id) { + memberInfos[i] = MemberInfo{symbol: symbol, baseIndex: -1} + i++ + } + } + slices.SortFunc(memberInfos, func(a, b MemberInfo) int { + return c.compareSymbols(a.symbol, b.symbol) + }) + if n > 3 { + memberInfoMap = make(map[string]int32, n) + for idx := range memberInfos { + memberInfoMap[memberInfos[idx].symbol.Name()] = int32(idx + 1) + } } - layoutMemberInfos[symbol.Name()] = MemberInfo{symbol: symbol, baseIndex: int32(baseIndex)} - } - } - // Add declared members - for id, symbol := range t.AsInterfaceType().declaredMembers.All() { - if c.isNamedMember(symbol, id) { - addMember(symbol, -1) } - } - // Add inherited members - for baseIndex, baseType := range baseTypes { - if baseType.objectFlags&(ObjectFlagsReference|ObjectFlagsClassOrInterface) != 0 { - c.resolveStructuredTypeMembers(baseType) - for _, info := range baseType.AsTypeReference().lazy.layout.memberInfos { - if !isStaticPrivateIdentifierProperty(info.symbol) { - addMember(info.symbol, baseIndex) + } else { + // Reuse c.layoutMemberInfos to avoid repeated allocations + layoutMemberInfos := c.layoutMemberInfos + c.layoutMemberInfos = nil + addMember := func(symbol *ast.Symbol, baseIndex int) { + if _, ok := layoutMemberInfos[symbol.Name()]; !ok { + if layoutMemberInfos == nil { + layoutMemberInfos = make(map[string]MemberInfo) + } + layoutMemberInfos[symbol.Name()] = MemberInfo{symbol: symbol, baseIndex: int32(baseIndex)} + } + } + // Add declared members + for id, symbol := range declaredMembers.All() { + if c.isNamedMember(symbol, id) { + addMember(symbol, -1) + } + } + // Add inherited members + for baseIndex, baseType := range baseTypes { + if baseType.objectFlags&(ObjectFlagsReference|ObjectFlagsClassOrInterface) != 0 { + c.resolveStructuredTypeMembers(baseType) + for _, info := range baseType.AsTypeReference().lazy.layout.memberInfos { + if !isStaticPrivateIdentifierProperty(info.symbol) { + addMember(info.symbol, baseIndex) + } } - } - } else { - for _, symbol := range c.getPropertiesOfType(baseType) { - if !isStaticPrivateIdentifierProperty(symbol) { - addMember(symbol, baseIndex) + } else { + for _, symbol := range c.getPropertiesOfType(baseType) { + if !isStaticPrivateIdentifierProperty(symbol) { + addMember(symbol, baseIndex) + } } } } - } - memberInfos := slices.AppendSeq(make([]MemberInfo, 0, len(layoutMemberInfos)), maps.Values(layoutMemberInfos)) - clear(layoutMemberInfos) - c.layoutMemberInfos = layoutMemberInfos - // Sort by symbols, but put declared members (baseIndex < 0) first - slices.SortFunc(memberInfos, func(a, b MemberInfo) int { - if a.baseIndex < 0 && b.baseIndex >= 0 { - return -1 - } - if a.baseIndex >= 0 && b.baseIndex < 0 { - return 1 + if n := len(layoutMemberInfos); n != 0 { + memberInfos = c.memberInfoListArena.NewSlice(n) + i := 0 + for _, info := range layoutMemberInfos { + memberInfos[i] = info + i++ + } + clear(layoutMemberInfos) + // Sort by symbols, but put declared members (baseIndex < 0) first + slices.SortFunc(memberInfos, func(a, b MemberInfo) int { + if a.baseIndex < 0 && b.baseIndex >= 0 { + return -1 + } + if a.baseIndex >= 0 && b.baseIndex < 0 { + return 1 + } + return c.compareSymbols(a.symbol, b.symbol) + }) + if n > 3 { + memberInfoMap = make(map[string]int32, n) + for idx := range memberInfos { + memberInfoMap[memberInfos[idx].symbol.Name()] = int32(idx + 1) + } + } } - return c.CompareSymbols(a.symbol, b.symbol) - }) - // Store slot indices in the sorted memberInfos and build the memberInfoMap. Slot indices are used to - // identify slots in the instantiated type's properties list. - memberInfoMap := make(map[string]*MemberInfo, len(memberInfos)) - for i := range memberInfos { - memberInfo := &memberInfos[i] - memberInfo.slotIndex = int32(i) - memberInfoMap[memberInfo.symbol.Name()] = memberInfo + c.layoutMemberInfos = layoutMemberInfos } layout := c.memberLayoutArena.New() layout.id = c.memberLayoutCount @@ -19774,6 +20950,25 @@ func (c *Checker) getBaseTypes(t *Type) []*Type { // members partially resolved. Here we ensure any such partial resolution is reset. // See https://github.com/microsoft/TypeScript/issues/16861 for an example. t.objectFlags &^= ObjectFlagsMembersResolved + t.cacheFlags &^= typeCacheIsWeakComputed | typeCacheIsWeak + data.lazy = nil + if data.instantiations != nil { + if inst := data.instantiations.first.val; inst != nil && inst != t && inst.AsTypeReference().lazy != nil { + inst.objectFlags &^= ObjectFlagsMembersResolved + inst.cacheFlags &^= typeCacheIsWeakComputed | typeCacheIsWeak + inst.AsTypeReference().lazy = nil + } + for i := range data.instantiations.slots { + if inst := data.instantiations.slots[i].val; inst != nil && inst != t && inst.AsTypeReference().lazy != nil { + inst.objectFlags &^= ObjectFlagsMembersResolved + inst.cacheFlags &^= typeCacheIsWeakComputed | typeCacheIsWeak + inst.AsTypeReference().lazy = nil + } + } + } + if t == c.globalObjectType { + c.globalObjectFilterReady = false + } data.baseTypesResolved = true } return data.resolvedBaseTypes @@ -19875,6 +21070,7 @@ func (c *Checker) getSignatureInstantiation(sig *Signature, typeArguments []*Typ newReturnSignature.setTypeParameters(c, inferredTypeParameters) newReturnType := c.getOrCreateTypeFromSignature(newReturnSignature) newReturnType.AsObjectType().mapper = instantiatedSignature.mapper + markMapperEscaped(instantiatedSignature.mapper) newInstantiatedSignature := c.cloneSignature(instantiatedSignature) newInstantiatedSignature.resolvedReturnType = newReturnType return newInstantiatedSignature @@ -19884,9 +21080,10 @@ func (c *Checker) getSignatureInstantiation(sig *Signature, typeArguments []*Typ } func (c *Checker) cloneSignature(sig *Signature) *Signature { - result := c.newSignature(sig.flags&SignatureFlagsPropagatingFlags, sig.declaration, sig.TypeParameters(), sig.ThisParameter(), sig.parameters, nil, nil, int(sig.minArgumentCount)) + result := c.newSignature(sig.flags&SignatureFlagsPropagatingFlags, sig.declaration, sig.TypeParameters(), c.getSignatureThisParameter(sig), c.getSignatureParameters(sig), nil, nil, int(sig.minArgumentCount)) result.target = sig.target result.mapper = sig.mapper + markMapperEscaped(sig.mapper) result.setComposite(c, sig.getComposite()) return result } @@ -19928,13 +21125,14 @@ func (c *Checker) getSingleCallOrConstructSignature(t *Type) *Signature { func (c *Checker) getSingleSignature(t *Type, kind SignatureKind, allowMembers bool) *Signature { if t.flags&TypeFlagsObject != 0 { - resolved := c.resolveStructuredTypeMembers(t) - if allowMembers || !resolved.hasProperties() && len(resolved.getIndexInfos()) == 0 { - if kind == SignatureKindCall && len(resolved.CallSignatures()) == 1 && len(resolved.ConstructSignatures()) == 0 { - return resolved.CallSignatures()[0] + if allowMembers || !c.hasPropertiesOfStructuredType(t) && len(c.getIndexInfosOfStructuredType(t)) == 0 { + callSignatures := c.getSignaturesOfStructuredType(t, SignatureKindCall) + constructSignatures := c.getSignaturesOfStructuredType(t, SignatureKindConstruct) + if kind == SignatureKindCall && len(callSignatures) == 1 && len(constructSignatures) == 0 { + return callSignatures[0] } - if kind == SignatureKindConstruct && len(resolved.ConstructSignatures()) == 1 && len(resolved.CallSignatures()) == 0 { - return resolved.ConstructSignatures()[0] + if kind == SignatureKindConstruct && len(constructSignatures) == 1 && len(callSignatures) == 0 { + return constructSignatures[0] } } } @@ -19970,13 +21168,20 @@ func (c *Checker) getOrCreateTypeFromSignature(sig *Signature) *Type { } func (c *Checker) getErasedSignature(signature *Signature) *Signature { - if len(signature.TypeParameters()) == 0 { + typeParameters := signature.TypeParameters() + if len(typeParameters) == 0 { return signature } key := CachedSignatureKey{sig: signature, key: SignatureKeyErased} erased := c.cachedSignatures[key] if erased == nil { - erased = c.instantiateSignatureEx(signature, newArrayToSingleTypeMapper(signature.TypeParameters(), c.anyType), true /*eraseTypeParameters*/) + var mapper *TypeMapper + if len(typeParameters) == 1 { + mapper = newSimpleTypeMapper(typeParameters[0], c.anyType) + } else { + mapper = newArrayToSingleTypeMapper(typeParameters, c.anyType) + } + erased = c.instantiateSignatureEx(signature, mapper, true /*eraseTypeParameters*/) c.cachedSignatures[key] = erased } return erased @@ -20068,7 +21273,8 @@ func (c *Checker) instantiateSignatureInContextOf(signature *Signature, contextu c.inferTypes(context.inferences, source, target, InferencePriorityReturnType, false) }) } - return c.getSignatureInstantiation(signature, c.getInferredTypes(context), ast.IsInJSFile(contextualSignature.declaration), nil /*inferredTypeParameters*/) + inferredTypes := c.getInferredTypes(context) + return c.getSignatureInstantiation(signature, inferredTypes, ast.IsInJSFile(contextualSignature.declaration), nil /*inferredTypeParameters*/) } func (c *Checker) resolveBaseTypesOfInterface(t *Type) { @@ -20176,7 +21382,7 @@ func (c *Checker) addInheritedMembers(symbols ast.SymbolTable, baseSymbols []*as if !isStaticPrivateIdentifierProperty(base) { if s, ok := symbols.Lookup(base.Name()); !ok || s.Flags()&ast.SymbolFlagsValue == 0 { if symbols == nil { - symbols = ast.NewSymbolTable() + symbols = c.acquireSymbolTable() } symbols.Set(base.Name(), base) } @@ -21203,15 +22409,27 @@ func (c *Checker) instantiateSignatureEx(sig *Signature, m *TypeMapper, eraseTyp for _, tp := range freshTypeParameters { tp.AsTypeParameter().mapper = m } + markMapperEscaped(m) } // Don't compute resolvedReturnType and resolvedTypePredicate now, // because using `mapper` now could trigger inferences to become fixed. (See `createInferenceContext`.) // See GH#17600. - result := c.newSignature(sig.flags&SignatureFlagsPropagatingFlags, sig.declaration, freshTypeParameters, - c.instantiateSymbol(sig.ThisParameter(), m), c.instantiateSymbols(sig.parameters, m), + flags := sig.flags & (SignatureFlagsPropagatingFlags | signatureFlagsUninstantiatedParameters | signatureFlagsUninstantiatedThisParameter) + thisParam := sig.ThisParameter() + if m != nil { + if len(sig.parameters) != 0 { + flags |= signatureFlagsUninstantiatedParameters + } + if thisParam != nil { + flags |= signatureFlagsUninstantiatedThisParameter + } + } + result := c.newSignature(flags, sig.declaration, freshTypeParameters, + thisParam, sig.parameters, nil /*resolvedReturnType*/, nil /*resolvedTypePredicate*/, int(sig.minArgumentCount)) result.target = sig result.mapper = m + markMapperEscaped(m) return result } @@ -21227,20 +22445,50 @@ func (c *Checker) resolveAnonymousTypeMembers(t *Type) { d := t.AsObjectType() if d.target != nil { c.setStructuredTypeMembers(t, nil, nil, nil, nil) - members := c.createInstantiatedSymbolTable(c.getPropertiesOfObjectType(d.target), d.mapper) + targetProps := c.getPropertiesOfObjectType(d.target) + targetMembers := d.target.AsStructuredType().getMembersIndex() + if (targetMembers == nil || targetMembers.fallback == nil) && (t.symbol == nil || t.symbol.Flags()&ast.SymbolFlagsValueModule == 0) { + var properties []*ast.Symbol + properties = c.instantiateSymbols(targetProps, 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) + data := t.AsStructuredType() + data.setMembersAndProperties(targetMembers, properties) + c.setStructuredTypeSignatures(data, callSignatures, constructSignatures, indexInfos) + return + } + members := c.createInstantiatedSymbolTable(targetProps, 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.releaseSymbolTableIfUnused(t, members) return } symbol := c.getMergedSymbol(t.symbol) if symbol.Flags()&ast.SymbolFlagsTypeLiteral != 0 { c.setStructuredTypeMembers(t, nil, nil, nil, nil) members := c.getMembersOfSymbol(symbol) - callSignatures := c.getSignaturesOfSymbol(members.Get(ast.InternalSymbolNameCall)) - constructSignatures := c.getSignaturesOfSymbol(members.Get(ast.InternalSymbolNameNew)) + callSymbol := members.Get(ast.InternalSymbolNameCall) + newSymbol := members.Get(ast.InternalSymbolNameNew) + callSignatures := c.getSignaturesOfSymbol(callSymbol) + constructSignatures := c.getSignaturesOfSymbol(newSymbol) indexInfos := c.getIndexInfosOfSymbol(symbol) + nonPropCount := 0 + if callSymbol != nil { + nonPropCount++ + } + if newSymbol != nil { + nonPropCount++ + } + if members.Get(ast.InternalSymbolNameIndex) != nil { + nonPropCount++ + } + if members.Len() == nonPropCount { + c.setStructuredTypeSignatures(t.AsStructuredType(), callSignatures, constructSignatures, indexInfos) + return + } c.setStructuredTypeMembers(t, members, callSignatures, constructSignatures, indexInfos) return } @@ -21258,12 +22506,18 @@ func (c *Checker) resolveAnonymousTypeMembers(t *Type) { } var baseConstructorIndexInfo *IndexInfo c.setStructuredTypeMembers(t, members, nil, nil, nil) + ownsMembers := false if symbol.Flags()&ast.SymbolFlagsClass != 0 { classType := c.getDeclaredTypeOfClassOrInterface(symbol) baseConstructorType := c.getBaseConstructorTypeOfClass(classType) if baseConstructorType.flags&(TypeFlagsObject|TypeFlagsIntersection|TypeFlagsTypeVariable) != 0 { - members = members.Clone() - c.addInheritedMembers(members, c.getPropertiesOfType(baseConstructorType)) + baseProps := c.getPropertiesOfType(baseConstructorType) + if len(baseProps) != 0 { + cloned := c.acquireSymbolTable() + cloned.CopyFrom(members) + members = c.addInheritedMembers(cloned, baseProps) + ownsMembers = true + } c.setStructuredTypeMembers(t, members, nil, nil, nil) } else if baseConstructorType == c.anyType { baseConstructorIndexInfo = c.anyBaseTypeIndexInfo @@ -21276,16 +22530,18 @@ 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.getPropertiesSlice(), func(prop *ast.Symbol) bool { return c.getTypeOfSymbol(prop).flags&TypeFlagsNumberLike != 0 })) { indexInfos = append(indexInfos, c.enumNumberIndexInfo) } } + if ownsMembers { + c.releaseSymbolTableIfUnused(t, members) + } if indexInfos != nil || d.extra != nil { d.ensureExtra().indexInfos = indexInfos } - // We resolve the members before computing the signatures because a signature may use // typeof with a qualified name expression that circularly references the type we are // in the process of resolving (see issue #6072). The temporarily empty signature list // will never be observed because a qualified name can't reference signatures. @@ -21315,7 +22571,7 @@ func (c *Checker) createInstantiatedSymbolTable(symbols []*ast.Symbol, m *TypeMa if len(symbols) == 0 { return nil } - result := ast.NewSymbolTableWithCapacity(len(symbols)) + result := c.acquireSymbolTable() for _, symbol := range symbols { result.Set(symbol.Name(), c.instantiateSymbol(symbol, m)) } @@ -21326,7 +22582,7 @@ func (c *Checker) instantiateSymbolTable(symbols ast.SymbolTable, m *TypeMapper) if symbols.Len() == 0 { return nil } - result := ast.NewSymbolTableWithCapacity(symbols.Len()) + result := c.acquireSymbolTable() for id, symbol := range symbols.All() { if c.isNamedMember(symbol, id) { result.Set(id, c.instantiateSymbol(symbol, m)) @@ -21336,38 +22592,54 @@ func (c *Checker) instantiateSymbolTable(symbols ast.SymbolTable, m *TypeMapper) } func (c *Checker) instantiateSymbol(symbol *ast.Symbol, m *TypeMapper) *ast.Symbol { - if symbol == nil || m == nil { - return symbol + if symbol == nil { + return nil } - links := c.valueSymbolLinks.Get(symbol) - if m.MapsThisOnly() && isThisless(symbol) { + if m == nil || m.MapsThisOnly() && isThisless(symbol) || + symbol.Flags()&ast.SymbolFlagsFunctionScopedVariable != 0 && symbol.CheckFlags()&ast.CheckFlagsInstantiated == 0 && isTypeVariableFreeParameter(symbol) { + // 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. return symbol } - // 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 symbol.Flags()&ast.SymbolFlagsSetAccessor == 0 { - return symbol + links := c.valueSymbolLinks.TryGet(symbol) + if links != nil { + // 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 symbol.Flags()&ast.SymbolFlagsSetAccessor == 0 { + return symbol + } + // If we're a setter, check writeType. + if links.getWriteType() != nil && !c.couldContainTypeVariables(links.getWriteType()) { + return symbol + } } - // If we're a setter, check writeType. - if links.getWriteType() != nil && !c.couldContainTypeVariables(links.getWriteType()) { - return symbol + if symbol.CheckFlags()&ast.CheckFlagsInstantiated != 0 { + // If symbol being instantiated is itself a instantiation, fetch the original target and combine the + // type mappers. This ensures that original type identities are properly preserved and that aliases + // always reference a non-aliases. + target := links.target + targetLinks := c.valueSymbolLinks.TryGet(target) + if targetLinks != nil && targetLinks.resolvedType != nil && !c.couldContainTypeVariables(targetLinks.resolvedType) { + if target.Flags()&ast.SymbolFlagsSetAccessor == 0 || (targetLinks.getWriteType() != nil && !c.couldContainTypeVariables(targetLinks.getWriteType())) { + return target + } + } + symbol = target + m = c.combineTypeMappers(links.mapper, m) + links = targetLinks } } - if symbol.CheckFlags()&ast.CheckFlagsInstantiated != 0 { - // If symbol being instantiated is itself a instantiation, fetch the original target and combine the - // type mappers. This ensures that original type identities are properly preserved and that aliases - // always reference a non-aliases. - symbol = links.target - m = c.combineTypeMappers(links.mapper, m) - } // Create a new transient symbol that shares the underlying data with the original symbol. result := c.newSharedDataSymbol(symbol) result.SetCheckFlags(ast.CheckFlagsInstantiated | symbol.CheckFlags()&(ast.CheckFlagsReadonly|ast.CheckFlagsLate|ast.CheckFlagsOptionalParameter|ast.CheckFlagsRestParameter)) resultLinks := c.valueSymbolLinks.Get(result) resultLinks.target = symbol resultLinks.mapper = m - resultLinks.setNameType(c, links.getNameType()) + markMapperEscaped(m) + if links != nil { + resultLinks.setNameType(c, links.getNameType()) + } return result } @@ -21407,19 +22679,58 @@ func isThislessVariableLikeDeclaration(node *ast.Node) bool { // literal type, an array with an element type that is free of this references, or a type reference that is // free of this references. func isThislessType(node *ast.Node) bool { - switch node.Kind { - case ast.KindAnyKeyword, ast.KindUnknownKeyword, ast.KindStringKeyword, ast.KindNumberKeyword, ast.KindBigIntKeyword, ast.KindBooleanKeyword, - ast.KindSymbolKeyword, ast.KindObjectKeyword, ast.KindVoidKeyword, ast.KindUndefinedKeyword, ast.KindNeverKeyword, ast.KindLiteralType: - return true - case ast.KindArrayType: - return isThislessType(node.AsArrayTypeNode().ElementType) - case ast.KindTypeReference: - return core.Every(node.TypeArguments(), isThislessType) + if node != nil { + switch node.Kind { + case ast.KindAnyKeyword, ast.KindUnknownKeyword, ast.KindStringKeyword, ast.KindNumberKeyword, ast.KindBigIntKeyword, ast.KindBooleanKeyword, ast.KindSymbolKeyword, ast.KindObjectKeyword, ast.KindVoidKeyword, ast.KindUndefinedKeyword, ast.KindNullKeyword, ast.KindNeverKeyword, ast.KindLiteralType: + return true + case ast.KindArrayType: + return isThislessType(node.AsArrayTypeNode().ElementType) + case ast.KindTypeReference: + return core.Every(node.TypeArguments(), isThislessType) + } + } + return false +} + +func isTypeVariableFreeParameter(symbol *ast.Symbol) bool { + if decl := symbol.FirstDeclaration(); decl != nil && decl.Kind == ast.KindParameter && len(symbol.Declarations()) == 1 && !ast.IsInJSFile(decl) && decl.Initializer() == nil { + typeNode := decl.Type() + return typeNode != nil && typeNode.Flags&ast.NodeFlagsCondFlowClean != 0 && isTypeVariableFreeTypeNode(typeNode) + } + return false +} + +func isTypeVariableFreeTypeNode(node *ast.Node) bool { + if node != nil { + switch node.Kind { + case ast.KindAnyKeyword, ast.KindUnknownKeyword, ast.KindStringKeyword, ast.KindNumberKeyword, ast.KindBigIntKeyword, ast.KindBooleanKeyword, ast.KindSymbolKeyword, ast.KindObjectKeyword, ast.KindVoidKeyword, ast.KindUndefinedKeyword, ast.KindNullKeyword, ast.KindNeverKeyword: + return true + case ast.KindLiteralType: + lit := node.AsLiteralTypeNode().Literal + return lit != nil && lit.Kind != ast.KindTemplateExpression + case ast.KindArrayType: + return isTypeVariableFreeTypeNode(node.AsArrayTypeNode().ElementType) + case ast.KindParenthesizedType: + return isTypeVariableFreeTypeNode(node.AsParenthesizedTypeNode().Type) + case ast.KindTypeOperator: + return node.AsTypeOperatorNode().Operator == ast.KindReadonlyKeyword && isTypeVariableFreeTypeNode(node.AsTypeOperatorNode().Type) + case ast.KindUnionType: + return core.Every(node.AsUnionTypeNode().Types.Nodes, isTypeVariableFreeTypeNode) + case ast.KindIntersectionType: + return core.Every(node.AsIntersectionTypeNode().Types.Nodes, isTypeVariableFreeTypeNode) + case ast.KindTupleType: + return core.Every(node.AsTupleTypeNode().Elements.Nodes, isTypeVariableFreeTypeNode) + case ast.KindNamedTupleMember: + return isTypeVariableFreeTypeNode(node.AsNamedTupleMember().Type) + case ast.KindRestType: + return isTypeVariableFreeTypeNode(node.AsRestTypeNode().Type) + case ast.KindOptionalType: + return isTypeVariableFreeTypeNode(node.AsOptionalTypeNode().Type) + } } return false } -// A function-like declaration is considered free of `this` references if it has a return type // annotation that is free of this references and if each parameter is thisless and if // each type parameter (if present) is thisless. func isThislessFunctionLikeDeclaration(node *ast.Node) bool { @@ -21472,9 +22783,138 @@ func (c *Checker) getDefaultConstructSignatures(classType *Type) []*Signature { return result } +func (c *Checker) getLazyMappedTable(t *Type) *lazyMappedTable { + if t.objectFlags&(ObjectFlagsMapped|ObjectFlagsMembersResolved) != ObjectFlagsMapped { + return nil + } + m := t.AsMappedType() + if m.lazy != nil { + return m.lazy + } + if !c.isMappedTypeWithKeyofConstraintDeclaration(t) { + return nil + } + typeParameter := c.getTypeParameterFromMappedType(t) + c.getConstraintTypeFromMappedType(t) + mappedType := core.OrElse(m.target, t) + if c.getNameTypeFromMappedType(mappedType) != nil { + return nil + } + shouldLinkPropDeclarations := c.getMappedTypeNameTypeKind(mappedType) != MappedTypeNameTypeKindRemapping + modifiersType := c.getApparentType(c.getModifiersTypeFromMappedType(t)) + if modifiersType.flags&TypeFlagsObject == 0 || t.objectFlags&ObjectFlagsMembersResolved != 0 { + return nil + } + lazy := c.lazyMappedTableArena.New() + lazy.typeParameter = typeParameter + lazy.modifiersType = modifiersType + lazy.templateModifiers = getMappedTypeModifiers(t) + lazy.shouldLinkPropDeclarations = shouldLinkPropDeclarations + m.lazy = lazy + return lazy +} + +func (c *Checker) getLazyMappedTypeMember(t *Type, lazy *lazyMappedTable, name string) (*ast.Symbol, bool) { + if lazy.members != nil { + if existing, ok := lazy.members[name]; ok { + return existing, true + } + } else { + lazy.members = make(map[string]*ast.Symbol) + } + // Recursive lookups see nil, as they would while resolveMappedTypeMembers runs. + lazy.members[name] = nil + modifiersProp := c.getMemberOfStructuredType(lazy.modifiersType, name) + if t.objectFlags&ObjectFlagsMembersResolved != 0 { + return t.AsStructuredType().getMember(name), true + } + var member *ast.Symbol + if modifiersProp != nil && c.isNamedMember(modifiersProp, name) { + keyType := c.getLiteralTypeFromProperty(modifiersProp, TypeFlagsStringOrNumberLiteralOrUnique, false) + if !isTypeUsableAsPropertyName(keyType) || getPropertyNameFromType(keyType) != name { + delete(lazy.members, name) + return nil, false + } + member = c.newMappedTypeMemberWithProp(t, modifiersProp, lazy.templateModifiers, lazy.shouldLinkPropDeclarations, keyType, keyType, name) + } + lazy.members[name] = member + return member, true +} + +func (c *Checker) getLazyMappedTypeIndexInfos(t *Type, lazy *lazyMappedTable) []*IndexInfo { + if !lazy.indexInfosReady { + if lazy.resolving { + return nil + } + lazy.resolving = true + templateType := c.getTemplateTypeFromMappedType(core.OrElse(t.AsMappedType().target, t)) + var indexBuf [4]*IndexInfo + indexInfos := indexBuf[:0] + for _, info := range c.getIndexInfosOfType(lazy.modifiersType) { + indexInfos = c.appendMappedTypeIndexInfo(indexInfos, t, lazy.modifiersType, lazy.typeParameter, templateType, lazy.templateModifiers, info.keyType, info.keyType) + } + lazy.resolving = false + if t.objectFlags&ObjectFlagsMembersResolved != 0 { + return t.AsStructuredType().getIndexInfos() + } + if len(indexInfos) != 0 { + lazy.indexInfos = c.indexInfoListArena.Clone(indexInfos) + } + lazy.indexInfosReady = true + } + return lazy.indexInfos +} + +func (c *Checker) newMappedTypeMember(t *Type, modifiersType *Type, templateModifiers MappedTypeModifiers, shouldLinkPropDeclarations bool, keyType *Type, propNameType *Type, propName string) *ast.Symbol { + var modifiersProp *ast.Symbol + if isTypeUsableAsPropertyName(keyType) { + modifiersProp = c.getPropertyOfType(modifiersType, getPropertyNameFromType(keyType)) + } + return c.newMappedTypeMemberWithProp(t, modifiersProp, templateModifiers, shouldLinkPropDeclarations, keyType, propNameType, propName) +} + +func (c *Checker) newMappedTypeMemberWithProp(t *Type, modifiersProp *ast.Symbol, templateModifiers MappedTypeModifiers, shouldLinkPropDeclarations bool, keyType *Type, propNameType *Type, propName string) *ast.Symbol { + isOptional := templateModifiers&MappedTypeModifiersIncludeOptional != 0 || templateModifiers&MappedTypeModifiersExcludeOptional == 0 && modifiersProp != nil && modifiersProp.Flags()&ast.SymbolFlagsOptional != 0 + isReadonly := templateModifiers&MappedTypeModifiersIncludeReadonly != 0 || templateModifiers&MappedTypeModifiersExcludeReadonly == 0 && modifiersProp != nil && c.isReadonlySymbol(modifiersProp) + stripOptional := c.strictNullChecks && !isOptional && modifiersProp != nil && modifiersProp.Flags()&ast.SymbolFlagsOptional != 0 + var lateFlag ast.CheckFlags + if modifiersProp != nil { + lateFlag = modifiersProp.CheckFlags() & ast.CheckFlagsLate + } + prop := c.newSymbol(ast.SymbolFlagsProperty|core.IfElse(isOptional, ast.SymbolFlagsOptional, 0), propName) + prop.SetCheckFlags(lateFlag | ast.CheckFlagsMapped | core.IfElse(isReadonly, ast.CheckFlagsReadonly, 0) | core.IfElse(stripOptional, ast.CheckFlagsStripOptional, 0)) + valueLinks := c.valueSymbolLinks.Get(prop) + valueLinks.setMappedInfo(c, t, propNameType, keyType, modifiersProp) + if modifiersProp != nil && shouldLinkPropDeclarations { + prop.SetDeclarations(modifiersProp.Declarations()) + } + return prop +} + +func (c *Checker) appendMappedTypeIndexInfo(indexInfos []*IndexInfo, t *Type, modifiersType *Type, typeParameter *Type, templateType *Type, templateModifiers MappedTypeModifiers, keyType *Type, propNameType *Type) []*IndexInfo { + if c.isValidIndexKeyType(propNameType) || propNameType.flags&(TypeFlagsAny|TypeFlagsEnum) != 0 { + indexKeyType := propNameType + switch { + case propNameType.flags&(TypeFlagsAny|TypeFlagsString) != 0: + indexKeyType = c.stringType + case propNameType.flags&(TypeFlagsNumber|TypeFlagsEnum) != 0: + indexKeyType = c.numberType + } + propType := c.instantiateType(templateType, appendTypeMapping(t.AsMappedType().mapper, typeParameter, keyType)) + modifiersIndexInfo := c.getApplicableIndexInfo(modifiersType, propNameType) + isReadonly := templateModifiers&MappedTypeModifiersIncludeReadonly != 0 || templateModifiers&MappedTypeModifiersExcludeReadonly == 0 && modifiersIndexInfo != nil && modifiersIndexInfo.isReadonly + indexInfo := c.newIndexInfo(indexKeyType, propType, isReadonly, nil, nil) + indexInfos = c.appendIndexInfo(indexInfos, indexInfo, true /*union*/) + } + return indexInfos +} + func (c *Checker) resolveMappedTypeMembers(t *Type) { - members := ast.NewSymbolTable() - var indexInfos []*IndexInfo + lazy := t.AsMappedType().lazy + t.AsMappedType().lazy = nil + members := c.acquireSymbolTable() + var indexBuf [4]*IndexInfo + indexInfos := indexBuf[:0] // Resolve upfront such that recursive references see an empty object type. c.setStructuredTypeMembers(t, nil, nil, nil, nil) // In { [P in K]: T }, we refer to P as the type parameter type, K as the constraint type, @@ -21485,8 +22925,7 @@ func (c *Checker) resolveMappedTypeMembers(t *Type) { nameType := c.getNameTypeFromMappedType(mappedType) shouldLinkPropDeclarations := c.getMappedTypeNameTypeKind(mappedType) != MappedTypeNameTypeKindRemapping templateType := c.getTemplateTypeFromMappedType(mappedType) - modifiersType := c.getApparentType(c.getModifiersTypeFromMappedType(t)) - // The 'T' in 'keyof T' + modifiersType := c.getApparentType(c.getModifiersTypeFromMappedType(t)) // The 'T' in 'keyof T' templateModifiers := getMappedTypeModifiers(t) include := TypeFlagsStringOrNumberLiteralOrUnique addMemberForKeyTypeWorker := func(keyType *Type, propNameType *Type) { @@ -21500,48 +22939,14 @@ func (c *Checker) resolveMappedTypeMembers(t *Type) { if existingProp := members.Get(propName); existingProp != nil { valueLinks := c.valueSymbolLinks.Get(existingProp) valueLinks.setNameType(c, c.getUnionType([]*Type{valueLinks.getNameType(), propNameType})) - mappedLinks := c.mappedSymbolLinks.Get(existingProp) - mappedLinks.keyType = c.getUnionType([]*Type{mappedLinks.keyType, keyType}) + valueLinks.setWriteType(c, c.getUnionType([]*Type{valueLinks.getWriteType(), keyType})) + } else if lazy != nil && lazy.members != nil && lazy.members[propName] != nil { + members.Set(propName, lazy.members[propName]) } else { - var modifiersProp *ast.Symbol - if isTypeUsableAsPropertyName(keyType) { - modifiersProp = c.getPropertyOfType(modifiersType, getPropertyNameFromType(keyType)) - } - isOptional := templateModifiers&MappedTypeModifiersIncludeOptional != 0 || templateModifiers&MappedTypeModifiersExcludeOptional == 0 && modifiersProp != nil && modifiersProp.Flags()&ast.SymbolFlagsOptional != 0 - isReadonly := templateModifiers&MappedTypeModifiersIncludeReadonly != 0 || templateModifiers&MappedTypeModifiersExcludeReadonly == 0 && modifiersProp != nil && c.isReadonlySymbol(modifiersProp) - stripOptional := c.strictNullChecks && !isOptional && modifiersProp != nil && modifiersProp.Flags()&ast.SymbolFlagsOptional != 0 - var lateFlag ast.CheckFlags - if modifiersProp != nil { - lateFlag = modifiersProp.CheckFlags() & ast.CheckFlagsLate - } - prop := c.newSymbol(ast.SymbolFlagsProperty|core.IfElse(isOptional, ast.SymbolFlagsOptional, 0), propName) - prop.SetCheckFlags(lateFlag | ast.CheckFlagsMapped | core.IfElse(isReadonly, ast.CheckFlagsReadonly, 0) | core.IfElse(stripOptional, ast.CheckFlagsStripOptional, 0)) - valueLinks := c.valueSymbolLinks.Get(prop) - valueLinks.setContainingType(c, t) - valueLinks.setNameType(c, propNameType) - mappedLinks := c.mappedSymbolLinks.Get(prop) - mappedLinks.keyType = keyType - if modifiersProp != nil { - mappedLinks.syntheticOrigin = modifiersProp - if shouldLinkPropDeclarations { - prop.SetDeclarations(modifiersProp.Declarations()) - } - } - members.Set(propName, prop) - } - } else if c.isValidIndexKeyType(propNameType) || propNameType.flags&(TypeFlagsAny|TypeFlagsEnum) != 0 { - indexKeyType := propNameType - switch { - case propNameType.flags&(TypeFlagsAny|TypeFlagsString) != 0: - indexKeyType = c.stringType - case propNameType.flags&(TypeFlagsNumber|TypeFlagsEnum) != 0: - indexKeyType = c.numberType + members.Set(propName, c.newMappedTypeMember(t, modifiersType, templateModifiers, shouldLinkPropDeclarations, keyType, propNameType, propName)) } - propType := c.instantiateType(templateType, appendTypeMapping(t.AsMappedType().mapper, typeParameter, keyType)) - modifiersIndexInfo := c.getApplicableIndexInfo(modifiersType, propNameType) - isReadonly := templateModifiers&MappedTypeModifiersIncludeReadonly != 0 || templateModifiers&MappedTypeModifiersExcludeReadonly == 0 && modifiersIndexInfo != nil && modifiersIndexInfo.isReadonly - indexInfo := c.newIndexInfo(indexKeyType, propType, isReadonly, nil, nil) - indexInfos = c.appendIndexInfo(indexInfos, indexInfo, true /*union*/) + } else if lazy == nil || !lazy.indexInfosReady { + indexInfos = c.appendMappedTypeIndexInfo(indexInfos, t, modifiersType, typeParameter, templateType, templateModifiers, keyType, propNameType) } } addMemberForKeyType := func(keyType *Type) { @@ -21549,17 +22954,38 @@ func (c *Checker) resolveMappedTypeMembers(t *Type) { if nameType != nil { propNameType = c.instantiateType(nameType, appendTypeMapping(t.AsMappedType().mapper, typeParameter, keyType)) } - forEachType(propNameType, func(t *Type) { - addMemberForKeyTypeWorker(keyType, t) - }) + if propNameType.flags&TypeFlagsUnion != 0 { + for _, pt := range propNameType.Types() { + addMemberForKeyTypeWorker(keyType, pt) + } + } else if propNameType.flags&TypeFlagsNever == 0 { + addMemberForKeyTypeWorker(keyType, propNameType) + } } if c.isMappedTypeWithKeyofConstraintDeclaration(t) { // We have a { [P in keyof T]: X } c.forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(modifiersType, include, false /*stringsOnly*/, addMemberForKeyType) } else { - forEachType(c.getLowerBoundOfKeyType(constraintType), addMemberForKeyType) + bound := c.getLowerBoundOfKeyType(constraintType) + if bound.flags&TypeFlagsUnion != 0 { + for _, kt := range bound.Types() { + addMemberForKeyType(kt) + } + } else if bound.flags&TypeFlagsNever == 0 { + addMemberForKeyType(bound) + } + } + var finalIndexInfos []*IndexInfo + if lazy != nil && lazy.indexInfosReady { + finalIndexInfos = lazy.indexInfos + } else if len(indexInfos) != 0 { + finalIndexInfos = c.indexInfoListArena.Clone(indexInfos) + } + c.setStructuredTypeMembers(t, members, nil, nil, finalIndexInfos) + c.releaseSymbolTableIfUnused(t, members) + if lazy != nil { + lazy.members = nil } - c.setStructuredTypeMembers(t, members, nil, nil, indexInfos) } func (c *Checker) getTypeOfMappedSymbol(symbol *ast.Symbol) *Type { @@ -21571,7 +22997,7 @@ func (c *Checker) getTypeOfMappedSymbol(symbol *ast.Symbol) *Type { return c.errorType } templateType := c.getTemplateTypeFromMappedType(core.OrElse(mappedType.AsMappedType().target, mappedType)) - mapper := appendTypeMapping(mappedType.AsMappedType().mapper, c.getTypeParameterFromMappedType(mappedType), c.mappedSymbolLinks.Get(symbol).keyType) + mapper := appendTypeMapping(mappedType.AsMappedType().mapper, c.getTypeParameterFromMappedType(mappedType), links.getWriteType()) propType := c.instantiateType(templateType, mapper) // When creating an optional property in strictNullChecks mode, if 'undefined' isn't assignable to the // type, we include 'undefined' in the type. Similarly, when creating a non-optional property in strictNullChecks @@ -21646,7 +23072,8 @@ func (c *Checker) resolveUnionTypeMembers(t *Type) { return c.getSignaturesOfType(t, SignatureKindConstruct) })) indexInfos := c.getUnionIndexInfos(t.Types()) - c.setStructuredTypeMembers(t, nil, callSignatures, constructSignatures, indexInfos) + t.objectFlags |= ObjectFlagsMembersResolved + c.setStructuredTypeSignatures(t.AsStructuredType(), callSignatures, constructSignatures, indexInfos) } func (c *Checker) getArrayMemberCallSignatures(t *Type) []*Signature { @@ -21710,16 +23137,13 @@ func (c *Checker) getUnionSignatures(signatureLists [][]*Signature) []*Signature s := signature // Union the result types when more than one signature matches if len(unionSignatures) > 1 { - thisParameter := signature.ThisParameter() + thisParameter := c.getSignatureThisParameter(signature) firstThisParameterOfUnionSignatures := core.FirstNonNil(unionSignatures, func(sig *Signature) *ast.Symbol { return sig.ThisParameter() }) if firstThisParameterOfUnionSignatures != nil { thisType := c.getIntersectionType(core.MapNonNil(unionSignatures, func(sig *Signature) *Type { - if sig.ThisParameter() != nil { - return c.getTypeOfSymbol(sig.ThisParameter()) - } - return nil + return c.getThisTypeOfSignature(sig) })) thisParameter = c.createSymbolWithType(firstThisParameterOfUnionSignatures, thisType) } @@ -21778,7 +23202,7 @@ func (c *Checker) combineUnionOrIntersectionMemberSignatures(left *Signature, ri if lastParam != nil && lastParam.CheckFlags()&ast.CheckFlagsRestParameter != 0 { flags |= SignatureFlagsHasRestParameter } - thisParam := c.combineUnionOrIntersectionThisParam(left.ThisParameter(), right.ThisParameter(), paramMapper, isUnion) + thisParam := c.combineUnionOrIntersectionThisParam(c.getSignatureThisParameter(left), c.getSignatureThisParameter(right), paramMapper, isUnion) minArgCount := int(max(left.minArgumentCount, right.minArgumentCount)) result := c.newSignature(flags, declaration, typeParams, thisParam, params, nil, nil, minArgCount) var leftSignatures []*Signature @@ -21794,8 +23218,10 @@ func (c *Checker) combineUnionOrIntersectionMemberSignatures(left *Signature, ri } else { result.mapper = paramMapper } + markMapperEscaped(result.mapper) } else if left.getComposite() != nil && left.getComposite().isUnion == isUnion { result.mapper = left.mapper + markMapperEscaped(result.mapper) } return result } @@ -21883,19 +23309,51 @@ func (c *Checker) combineUnionOrIntersectionThisParam(left *ast.Symbol, right *a func (c *Checker) resolveIntersectionTypeMembers(t *Type) { // The members and properties collections are empty for intersection types. To get all properties of an // intersection type use getPropertiesOfType (only the language service uses this). - var callSignatures []*Signature - var constructSignatures []*Signature - var indexInfos []*IndexInfo + var callBuf [4]*Signature + var constructBuf [4]*Signature + var indexBuf [4]*IndexInfo + callSignatures := callBuf[:0] + constructSignatures := constructBuf[:0] + indexInfos := indexBuf[:0] types := t.Types() - mixinFlags, mixinCount := c.findMixins(types) - for i, t := range types { + var mixinFlagsBuf [8]bool + var mixinFlags []bool + var mixinCount int + constructorTypeCount := 0 + for _, ct := range types { + if len(c.getSignaturesOfType(ct, SignatureKindConstruct)) > 0 { + constructorTypeCount++ + } + } + if constructorTypeCount >= 2 { + if len(types) <= len(mixinFlagsBuf) { + mixinFlags = mixinFlagsBuf[:len(types)] + } else { + mixinFlags = make([]bool, len(types)) + } + firstMixinIndex := -1 + for i, ct := range types { + if c.isMixinConstructorType(ct) { + mixinFlags[i] = true + if firstMixinIndex < 0 { + firstMixinIndex = i + } + mixinCount++ + } + } + if constructorTypeCount == mixinCount { + mixinFlags[firstMixinIndex] = false + mixinCount-- + } + } + for i, ct := range types { // When an intersection type contains mixin constructor types, the construct signatures from // those types are discarded and their return types are mixed into the return types of all // other construct signatures in the intersection type. For example, the intersection type // '{ new(...args: any[]) => A } & { new(s: string) => B }' has a single construct signature // 'new(s: string) => A & B'. - if !mixinFlags[i] { - signatures := c.getSignaturesOfType(t, SignatureKindConstruct) + if mixinFlags == nil || !mixinFlags[i] { + signatures := c.getSignaturesOfType(ct, SignatureKindConstruct) if len(signatures) != 0 && mixinCount > 0 { signatures = core.Map(signatures, func(s *Signature) *Signature { clone := c.cloneSignature(s) @@ -21905,12 +23363,24 @@ func (c *Checker) resolveIntersectionTypeMembers(t *Type) { } constructSignatures = c.appendSignatures(constructSignatures, signatures) } - callSignatures = c.appendSignatures(callSignatures, c.getSignaturesOfType(t, SignatureKindCall)) - for _, info := range c.getIndexInfosOfType(t) { + callSignatures = c.appendSignatures(callSignatures, c.getSignaturesOfType(ct, SignatureKindCall)) + for _, info := range c.getIndexInfosOfType(ct) { indexInfos = c.appendIndexInfo(indexInfos, info, false /*union*/) } } - c.setStructuredTypeMembers(t, nil, callSignatures, constructSignatures, indexInfos) + var finalCalls, finalConstructs []*Signature + var finalIndexInfos []*IndexInfo + if len(callSignatures) != 0 { + finalCalls = c.signatureListArena.Clone(callSignatures) + } + if len(constructSignatures) != 0 { + finalConstructs = c.signatureListArena.Clone(constructSignatures) + } + if len(indexInfos) != 0 { + finalIndexInfos = c.indexInfoListArena.Clone(indexInfos) + } + t.objectFlags |= ObjectFlagsMembersResolved + c.setStructuredTypeSignatures(t.AsStructuredType(), finalCalls, finalConstructs, finalIndexInfos) } func (c *Checker) appendSignatures(signatures []*Signature, newSignatures []*Signature) []*Signature { @@ -21983,8 +23453,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 := c.getMemberOfStructuredType(t, name) if symbol != nil && c.symbolIsValue(symbol) { return symbol } @@ -22026,6 +23495,7 @@ func (c *Checker) getUnionOrIntersectionProperty(t *Type, name string, skipObjec if prop != nil { if skipObjectFunctionPropertyAugment { ast.GetSymbolTable(&data.propertyCacheWithoutFunctionPropertyAugment).Set(name, prop) + } else { ast.GetSymbolTable(&data.propertyCache).Set(name, prop) } @@ -22033,29 +23503,100 @@ func (c *Checker) getUnionOrIntersectionProperty(t *Type, name string, skipObjec return prop } -func (c *Checker) createUnionOrIntersectionProperty(containingType *Type, name string, skipObjectFunctionPropertyAugment bool) *ast.Symbol { +// unionOrIntersectionPropertySources describes the constituent properties from +// which a union or intersection property would be synthesized. +type unionOrIntersectionPropertySources struct { + singleProp *ast.Symbol + // props holds distinct constituent properties in first-seen order, or is + // empty when every constituent shares singleProp. propsByKey indexes it + // only once it is large enough for hashing to beat a linear scan. + props []*ast.Symbol + propsByKey map[*ast.Symbol]struct{} + indexTypes []*Type + propFlags ast.SymbolFlags + optionalFlag ast.SymbolFlags + checkFlags ast.CheckFlags + syntheticFlag ast.CheckFlags + mergedInstantiations bool +} + +// isSynthetic reports whether the sources require a new synthetic symbol rather +// than reusing (or cloning) singleProp. +func (s *unionOrIntersectionPropertySources) isSynthetic() bool { + return len(s.props) != 0 || s.checkFlags&ast.CheckFlagsReadPartial != 0 || len(s.indexTypes) != 0 +} + +// addProp adds prop to props if it is not already present, preserving +// first-insertion order. +func (s *unionOrIntersectionPropertySources) addProp(c *Checker, prop *ast.Symbol) { + if s.propsByKey == nil { + if slices.Contains(s.props, prop) { + return + } + if s.props == nil { + s.props = c.getTemporarySymbolList() + } + s.props = append(s.props, prop) + if len(s.props) > orderedSetMapThreshold { + s.propsByKey = make(map[*ast.Symbol]struct{}, len(s.props)) + for _, p := range s.props { + s.propsByKey[p] = struct{}{} + } + } + return + } + if _, ok := s.propsByKey[prop]; ok { + return + } + s.props = append(s.props, prop) + s.propsByKey[prop] = struct{}{} +} + +func (c *Checker) getUnionOrIntersectionPropertySources(containingType *Type, name string, skipObjectFunctionPropertyAugment bool, sources *unionOrIntersectionPropertySources) bool { propFlags := ast.SymbolFlagsNone - var singleProp *ast.Symbol - var propSet collections.OrderedSet[*ast.Symbol] - var indexTypes []*Type isUnion := containingType.flags&TypeFlagsUnion != 0 // Flags we want to propagate to the result if they exist in all source symbols var checkFlags ast.CheckFlags var optionalFlag ast.SymbolFlags + var singleProp *ast.Symbol + var indexTypes []*Type if !isUnion { checkFlags = ast.CheckFlagsReadonly optionalFlag = ast.SymbolFlagsOptional } syntheticFlag := ast.CheckFlagsSyntheticMethod mergedInstantiations := false + nameHash := hashMemberName(name) + mayBeGlobal := !skipObjectFunctionPropertyAugment && c.mayBeGlobalObjectMember(name) for _, current := range containingType.Types() { - t := c.getApparentType(current) - if !c.isErrorType(t) && t.flags&TypeFlagsNever == 0 { - prop := c.getPropertyOfTypeEx(t, name, skipObjectFunctionPropertyAugment, false) + var t *Type + var prop *ast.Symbol + if current.flags == TypeFlagsObject && current.objectFlags&(ObjectFlagsMapped|ObjectFlagsMembersResolved) == ObjectFlagsMembersResolved && (current.symbol == nil || current.symbol.Flags()&ast.SymbolFlagsValueModule == 0) { + t = current + if sym := current.AsStructuredType().getMemberWithHash(name, nameHash); sym != nil { + if sym.Flags()&ast.SymbolFlagsValue != 0 { + prop = sym + } else { + prop = c.getPropertyOfTypeEx(t, name, skipObjectFunctionPropertyAugment, false /*includeTypeOnlyMembers*/) + } + } else if mayBeGlobal { + prop = c.getPropertyOfTypeEx(t, name, skipObjectFunctionPropertyAugment, false /*includeTypeOnlyMembers*/) + } + } else { + t = c.getApparentType(current) + if c.isErrorType(t) || t.flags&TypeFlagsNever != 0 { + continue + } + prop = c.getPropertyOfTypeEx(t, name, skipObjectFunctionPropertyAugment, false /*includeTypeOnlyMembers*/) + } + if true { var modifiers ast.ModifierFlags if prop != nil { modifiers = getDeclarationModifierFlagsFromSymbol(prop) - writeModifiers := getDeclarationModifierFlagsFromSymbolEx(prop, true /*isWrite*/) + writeModifiers := modifiers + if prop.Flags()&ast.SymbolFlagsSetAccessor != 0 || prop.CheckFlags()&ast.CheckFlagsSynthetic != 0 { + writeModifiers = getDeclarationModifierFlagsFromSymbolEx(prop, true /*isWrite*/) + } if prop.Flags()&ast.SymbolFlagsClassMember != 0 { if isUnion { optionalFlag |= prop.Flags() & ast.SymbolFlagsOptional @@ -22077,10 +23618,10 @@ func (c *Checker) createUnionOrIntersectionProperty(containingType *Type, name s // back and not `Array.length` when we're looking at a `.length` access on a `string[] | number[]` mergedInstantiations = singleProp.Parent() != nil && len(c.getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(singleProp.Parent())) != 0 } else { - if propSet.Size() == 0 { - propSet.Add(singleProp) + if len(sources.props) == 0 { + sources.addProp(c, singleProp) } - propSet.Add(prop) + sources.addProp(c, prop) } // classes created by mixins are represented as intersections // and overriding a property in a derived class redefines it completely at runtime @@ -22128,12 +23669,21 @@ func (c *Checker) createUnionOrIntersectionProperty(containingType *Type, name s if indexType == nil { indexType = c.undefinedType } + if indexTypes == nil { + indexTypes = c.getTemporaryTypeArguments(0)[:0] + } indexTypes = append(indexTypes, indexType) } else { + if indexTypes == nil { + indexTypes = c.getTemporaryTypeArguments(0)[:0] + } indexTypes = append(indexTypes, indexInfo.valueType) } } else if isObjectLiteralType(t) && t.objectFlags&ObjectFlagsContainsSpread == 0 { checkFlags |= ast.CheckFlagsWritePartial + if indexTypes == nil { + indexTypes = c.getTemporaryTypeArguments(0)[:0] + } indexTypes = append(indexTypes, c.undefinedType) } else { checkFlags |= ast.CheckFlagsReadPartial @@ -22142,18 +23692,26 @@ func (c *Checker) createUnionOrIntersectionProperty(containingType *Type, name s } } if singleProp == nil { + c.releaseTemporarySymbolList(sources.props) + if indexTypes != nil { + c.releaseTemporaryTypeArguments(indexTypes) + } // No property was found - return nil + return false } if isUnion && - (propSet.Size() != 0 || checkFlags&ast.CheckFlagsPartial != 0) && + (len(sources.props) != 0 || checkFlags&ast.CheckFlagsPartial != 0) && checkFlags&(ast.CheckFlagsContainsPrivate|ast.CheckFlagsContainsProtected|ast.CheckFlagsContainsWritePrivate|ast.CheckFlagsContainsWriteProtected) != 0 && - !(propSet.Size() != 0 && c.hasCommonDeclaration(&propSet)) { + !(len(sources.props) != 0 && c.hasCommonDeclaration(sources.props)) { // A property in a union has a private or protected declaration in one constituent, but is missing // or has a different declaration in another constituent. If the private or protected declaration is // for reading, we don't create a property. if checkFlags&(ast.CheckFlagsContainsPrivate|ast.CheckFlagsContainsProtected) != 0 { - return nil + c.releaseTemporarySymbolList(sources.props) + if indexTypes != nil { + c.releaseTemporaryTypeArguments(indexTypes) + } + return false } // Otherwise, if the private or protected declaration is for writing, reduce accessibility to that of // the most restricted constituent. @@ -22163,8 +23721,36 @@ func (c *Checker) createUnionOrIntersectionProperty(containingType *Type, name s checkFlags &^= ast.CheckFlagsContainsWritePublic } } - if propSet.Size() == 0 && checkFlags&ast.CheckFlagsReadPartial == 0 && len(indexTypes) == 0 { - if !mergedInstantiations { + sources.singleProp = singleProp + sources.indexTypes = indexTypes + sources.propFlags = propFlags + sources.optionalFlag = optionalFlag + sources.checkFlags = checkFlags + sources.syntheticFlag = syntheticFlag + sources.mergedInstantiations = mergedInstantiations + return true +} + +func (c *Checker) createUnionOrIntersectionProperty(containingType *Type, name string, skipObjectFunctionPropertyAugment bool) *ast.Symbol { + var sources unionOrIntersectionPropertySources + if !c.getUnionOrIntersectionPropertySources(containingType, name, skipObjectFunctionPropertyAugment, &sources) { + return nil + } + defer func() { + c.releaseTemporarySymbolList(sources.props) + if sources.indexTypes != nil { + c.releaseTemporaryTypeArguments(sources.indexTypes) + } + }() + singleProp := sources.singleProp + indexTypes := sources.indexTypes + propFlags := sources.propFlags + optionalFlag := sources.optionalFlag + checkFlags := sources.checkFlags + syntheticFlag := sources.syntheticFlag + isUnion := containingType.flags&TypeFlagsUnion != 0 + if !sources.isSynthetic() { + if !sources.mergedInstantiations { return singleProp } // No symbol from a union/intersection should have a `.parent` set (since unions/intersections don't act as symbol parents) @@ -22187,17 +23773,22 @@ func (c *Checker) createUnionOrIntersectionProperty(containingType *Type, name s links.setWriteType(c, c.getWriteTypeOfSymbol(singleProp)) return clone } - if propSet.Size() == 0 { - propSet.Add(singleProp) + props := sources.props + var singlePropBuf [1]*ast.Symbol + if len(props) == 0 { + singlePropBuf[0] = sources.singleProp + props = singlePropBuf[:] } - var declarations []*ast.Node + var declarationsBuf [8]*ast.Node + declarations := declarationsBuf[:0] var firstType *Type var nameType *Type - var propTypes []*Type + propTypes := c.getTemporaryTypeArguments(0)[:0] + defer c.releaseTemporaryTypeArguments(propTypes) var writeTypes []*Type var firstValueDeclaration *ast.Node var hasNonUniformValueDeclaration bool - for prop := range propSet.Values() { + for _, prop := range props { if firstValueDeclaration == nil { firstValueDeclaration = prop.ValueDeclaration() } else if prop.ValueDeclaration() != nil && prop.ValueDeclaration() != firstValueDeclaration { @@ -22209,7 +23800,7 @@ func (c *Checker) createUnionOrIntersectionProperty(containingType *Type, name s t := c.getTypeOfSymbol(prop) if firstType == nil { firstType = t - nameType = c.valueSymbolLinks.Get(prop).getNameType() + nameType = c.valueSymbolLinks.TryGet(prop).getNameType() } writeType := c.getWriteTypeOfSymbol(prop) if writeTypes != nil || writeType != t { @@ -22231,7 +23822,11 @@ func (c *Checker) createUnionOrIntersectionProperty(containingType *Type, name s } propTypes = append(propTypes, indexTypes...) result := c.newSymbolEx(propFlags|optionalFlag, name, checkFlags|syntheticFlag) - result.SetDeclarations(declarations) + if len(declarations) == 1 { + result.SetSingleDeclaration(declarations[0]) + } else if len(declarations) > 1 { + result.SetDeclarations(slices.Clone(declarations)) + } if !hasNonUniformValueDeclaration && firstValueDeclaration != nil { result.SetValueDeclaration(firstValueDeclaration) // Inherit information about parent type. @@ -22245,7 +23840,7 @@ func (c *Checker) createUnionOrIntersectionProperty(containingType *Type, name s result.SetCheckFlags(result.CheckFlags() | ast.CheckFlagsDeferredType) deferred := c.deferredSymbolLinks.Get(result) deferred.parent = containingType - deferred.constituents = propTypes + deferred.constituents = c.typeListArena.Clone(propTypes) deferred.writeConstituents = writeTypes return result } @@ -22268,7 +23863,10 @@ func (c *Checker) getTargetSymbol(s *ast.Symbol) *ast.Symbol { // if symbol is instantiated its flags are not copied from the 'target' // so we'll need to get back original 'target' symbol to work with correct set of flags if s != nil && s.CheckFlags()&ast.CheckFlagsInstantiated != 0 { - return c.valueSymbolLinks.Get(s).target + if links := c.valueSymbolLinks.TryGet(s); links != nil { + return links.target + } + return nil } return s } @@ -22281,9 +23879,9 @@ func isPrototypeProperty(symbol *ast.Symbol) bool { return symbol.Flags()&ast.SymbolFlagsMethod != 0 || symbol.CheckFlags()&ast.CheckFlagsSyntheticMethod != 0 } -func (c *Checker) hasCommonDeclaration(symbols *collections.OrderedSet[*ast.Symbol]) bool { +func (c *Checker) hasCommonDeclaration(symbols []*ast.Symbol) bool { var commonDeclarations collections.Set[*ast.Node] - for symbol := range symbols.Values() { + for _, symbol := range symbols { if len(symbol.Declarations()) == 0 { return false } @@ -22306,14 +23904,12 @@ func (c *Checker) hasCommonDeclaration(symbols *collections.OrderedSet[*ast.Symb } func (c *Checker) createSymbolWithType(source *ast.Symbol, t *Type) *ast.Symbol { - symbol := c.newSymbolEx(source.Flags(), source.Name(), source.CheckFlags()&ast.CheckFlagsReadonly) - symbol.SetDeclarations(source.Declarations()) - symbol.SetParent(source.Parent()) - symbol.SetValueDeclaration(source.ValueDeclaration()) + symbol := c.newSharedDataSymbol(source) + symbol.SetCheckFlags(source.CheckFlags() & ast.CheckFlagsReadonly) links := c.valueSymbolLinks.Get(symbol) links.resolvedType = t links.target = source - links.setNameType(c, c.valueSymbolLinks.Get(source).getNameType()) + links.setNameType(c, c.valueSymbolLinks.TryGet(source).getNameType()) return symbol } @@ -22332,6 +23928,13 @@ func (c *Checker) isMappedTypeGenericIndexedAccess(t *Type) bool { * type itself. */ func (c *Checker) getApparentType(t *Type) *Type { + if t.flags&TypeFlagsObject != 0 && t.objectFlags&ObjectFlagsMapped == 0 { + return t + } + return c.getApparentTypeSlow(t) +} + +func (c *Checker) getApparentTypeSlow(t *Type) *Type { originalType := t if t.flags&TypeFlagsInstantiable != 0 { t = c.getBaseConstraintOfType(t) @@ -22422,6 +24025,13 @@ func (c *Checker) getApparentTypeOfIntersectionType(t *Type, thisArgument *Type) * no constituent property has type 'never', but the intersection of the constituent property types is 'never'. */ func (c *Checker) getReducedType(t *Type) *Type { + if t.flags&(TypeFlagsIntersection|TypeFlags(t.objectFlags&ObjectFlagsContainsIntersections)<<2) == 0 { + return t + } + return c.getReducedTypeSlow(t) +} + +func (c *Checker) getReducedTypeSlow(t *Type) *Type { switch { case t.flags&TypeFlagsUnion != 0: if t.objectFlags&ObjectFlagsContainsIntersections != 0 { @@ -22462,16 +24072,40 @@ func (c *Checker) isMappingOfSameObjectType(types []*Type) bool { func (c *Checker) somePropertyReducesToNever(t *Type) bool { // Collect declaration counts for each property across all constituent types of the intersection. + // A name that occurs in more than one constituent occurs in one other than skipped, so skipped + // only needs its members looked up, which may not resolve them. + types := t.Types() + skipped := -1 + for i, ct := range types { + if ct.objectFlags&(ObjectFlagsMapped|ObjectFlagsMembersResolved) == ObjectFlagsMapped || ct.flags&TypeFlagsObject != 0 && ct.objectFlags&(ObjectFlagsClassOrInterface|ObjectFlagsReference) != 0 { + skipped = i + break + } + } + if len(types) == 2 && skipped != -1 { + skippedType := types[skipped] + otherProps, _ := c.getPropertiesOfTypeLazily(types[1-skipped]) + for _, prop := range otherProps { + propName := prop.Name() + if c.getPropertyOfTypeWorker(skippedType, propName, true /*skipObjectFunctionPropertyAugment*/, false /*includeTypeOnlyMembers*/, false /*instantiate*/) != nil { + if c.intersectionPropertyReducesToNever(t, propName) { + return true + } + } + } + return false + } counts := c.getTemporaryPropertyCounts() defer c.releaseTemporaryPropertyCounts(counts) // Property names are unique within each constituent, so the first // constituent's names can be added without looking them up. - types := t.Types() - for _, prop := range c.getLayoutPropertiesOfType(types[0]) { + firstProps, _ := c.getPropertiesOfTypeLazily(types[0]) + for _, prop := range firstProps { counts.AddNew(prop.Name()) } - for _, t := range types[1:] { - for _, prop := range c.getLayoutPropertiesOfType(t) { + for _, ct := range types[1:] { + props, _ := c.getPropertiesOfTypeLazily(ct) + for _, prop := range props { counts.Add(prop.Name()) } } @@ -22487,6 +24121,74 @@ func (c *Checker) somePropertyReducesToNever(t *Type) bool { return false } +// intersectionPropertyReducesToNever reports whether the combined property named +// name would satisfy isNeverReducedProperty, without creating and caching that +// property. Most such properties are never otherwise looked up, so creating +// them only to inspect their flags retains a large number of symbols. +func (c *Checker) intersectionPropertyReducesToNever(t *Type, name string) bool { + if prop := t.AsUnionOrIntersectionType().propertyCacheWithoutFunctionPropertyAugment.Get(name); prop != nil { + return c.isNeverReducedProperty(prop) + } + var sources unionOrIntersectionPropertySources + if !c.getUnionOrIntersectionPropertySources(t, name, true /*skipObjectFunctionPropertyAugment*/, &sources) { + return false + } + defer func() { + c.releaseTemporarySymbolList(sources.props) + if sources.indexTypes != nil { + c.releaseTemporaryTypeArguments(sources.indexTypes) + } + }() + if !sources.isSynthetic() { + if !sources.mergedInstantiations { + return c.isNeverReducedProperty(sources.singleProp) + } + prop := c.getUnionOrIntersectionProperty(t, name, true /*skipObjectFunctionPropertyAugment*/) + return prop != nil && c.isNeverReducedProperty(prop) + } + props := sources.props + var singlePropBuf [1]*ast.Symbol + if len(props) == 0 { + singlePropBuf[0] = sources.singleProp + props = singlePropBuf[:] + } + checkFlags := sources.checkFlags + var firstType *Type + var firstValueDeclaration *ast.Node + hasNonUniformValueDeclaration := false + propTypes := c.getTemporaryTypeArguments(0)[:0] + defer c.releaseTemporaryTypeArguments(propTypes) + for _, prop := range props { + if firstValueDeclaration == nil { + firstValueDeclaration = prop.ValueDeclaration() + } else if prop.ValueDeclaration() != nil && prop.ValueDeclaration() != firstValueDeclaration { + hasNonUniformValueDeclaration = true + } + propType := c.getTypeOfSymbol(prop) + if firstType == nil { + firstType = propType + } + if propType != firstType { + checkFlags |= ast.CheckFlagsHasNonUniformType + } + if isLiteralType(propType) || c.isPatternLiteralType(propType) { + checkFlags |= ast.CheckFlagsHasLiteralType + } + if propType.flags&TypeFlagsNever != 0 && propType != c.uniqueLiteralType { + checkFlags |= ast.CheckFlagsHasNeverType + } + propTypes = append(propTypes, propType) + } + propTypes = append(propTypes, sources.indexTypes...) + hasValueDeclaration := !hasNonUniformValueDeclaration && firstValueDeclaration != nil + if !hasValueDeclaration && checkFlags&ast.CheckFlagsContainsPrivate != 0 { + return true + } + return (sources.propFlags|sources.optionalFlag)&ast.SymbolFlagsOptional == 0 && + checkFlags&(ast.CheckFlagsNonUniformAndLiteral|ast.CheckFlagsHasNeverType) == ast.CheckFlagsNonUniformAndLiteral && + c.getIntersectionType(propTypes).flags&TypeFlagsNever != 0 +} + func (c *Checker) getReducedUnionType(unionType *Type) *Type { reducedTypes := core.SameMap(unionType.Types(), c.getReducedType) if core.Same(reducedTypes, unionType.Types()) { @@ -22504,10 +24206,9 @@ func (c *Checker) isNeverReducedProperty(prop *ast.Symbol) bool { } func (c *Checker) getReducedApparentType(t *Type) *Type { - // Since getApparentType may return a non-reduced union or intersection type, we need to perform - // type reduction both before and after obtaining the apparent type. For example, given a type parameter - // 'T extends A | B', the type 'T & X' becomes 'A & X | B & X' after obtaining the apparent type, and - // that type may need further reduction to remove empty intersections. + if t.flags&TypeFlagsObject != 0 && t.objectFlags&ObjectFlagsMapped == 0 { + return t + } return c.getReducedType(c.getApparentType(c.getReducedType(t))) } @@ -22748,14 +24449,41 @@ func (c *Checker) symbolIsValueEx(symbol *ast.Symbol, includeTypeOnlyMembers boo } func (c *Checker) instantiateType(t *Type, m *TypeMapper) *Type { + if t == nil || m == nil { + return t + } return c.instantiateTypeWithAlias(t, m, nil /*alias*/) } +func (c *Checker) anyCouldContainTypeVariables(types []*Type) bool { + return slices.ContainsFunc(types, c.couldContainTypeVariables) +} + func (c *Checker) instantiateTypeWithAlias(t *Type, m *TypeMapper, alias *TypeAlias) *Type { + if t == nil || m == nil || t.objectFlags&(ObjectFlagsCouldContainTypeVariablesComputed|ObjectFlagsCouldContainTypeVariables) == ObjectFlagsCouldContainTypeVariablesComputed && (t.alias == nil || len(t.alias.typeArguments) == 0) { + return t + } + return c.instantiateTypeWithAliasSlow(t, m, alias) +} + +func getInstantiationCacheKeyWithAlias(t *Type, alias *TypeAlias) CacheHashKey { + if runtime.GOARCH == "wasm" { + b := acquireKeyBuilder() + b.writeType(t) + b.writeAlias(alias) + return b.releaseHash() + } + var b keyBuilder + b.writeType(t) + b.writeAlias(alias) + return b.hash() +} + +func (c *Checker) instantiateTypeWithAliasSlow(t *Type, m *TypeMapper, alias *TypeAlias) *Type { // Check for type variables in the alias, so things like `type Brand = number & {}` can potentially be copied with new alias type args, despite them being unreferenced. // This is the behavior most people using aliases expect, and prevents the cache from leaking type parameters outside their scope of validity. // tests/cases/compiler/declarationEmitArrowFunctionNoRenaming.ts contains an example of this, which previously only worked in strada via some input node reuse logic instead. - if t == nil || m == nil || !(c.couldContainTypeVariables(t) || (t.alias != nil && len(t.alias.typeArguments) > 0 && core.Some(t.alias.typeArguments, c.couldContainTypeVariables))) { + if !(c.couldContainTypeVariables(t) || (t.alias != nil && len(t.alias.typeArguments) > 0 && c.anyCouldContainTypeVariables(t.alias.typeArguments))) { return t } if len(c.instantiationStack) == 100 || c.instantiationCount >= 5_000_000 { @@ -22835,7 +24563,6 @@ func (c *Checker) getCircularTypeNames() []string { func (c *Checker) pushActiveMapper(mapper *TypeMapper) { c.activeMappers = append(c.activeMappers, mapper) - lastIndex := len(c.activeTypeMappersCaches) if cap(c.activeTypeMappersCaches) > lastIndex { // The cap may contain an empty cache from popActiveMapper; reuse it. @@ -22856,7 +24583,12 @@ func (c *Checker) popActiveMapper() { } func (c *Checker) findActiveMapper(mapper *TypeMapper) int { - return core.FindLastIndex(c.activeMappers, func(m *TypeMapper) bool { return m == mapper }) + for i := len(c.activeMappers) - 1; i >= 0; i-- { //nolint:modernize + if c.activeMappers[i] == mapper { + return i + } + } + return -1 } func (c *Checker) clearActiveMapperCaches() { @@ -22868,19 +24600,20 @@ func (c *Checker) clearActiveMapperCaches() { // Return true if the given type could possibly reference a type parameter for which // we perform type inference (i.e. a type parameter of a generic function). We cache // results for union and intersection types for performance reasons. -func (c *Checker) couldContainTypeVariablesWorker(t *Type) bool { - if t.flags&TypeFlagsStructuredOrInstantiable == 0 { - return false +func (c *Checker) couldContainTypeVariables(t *Type) bool { + if t.objectFlags&ObjectFlagsCouldContainTypeVariablesComputed != 0 { + return t.objectFlags&ObjectFlagsCouldContainTypeVariables != 0 } + return c.couldContainTypeVariablesSlow(t) +} + +func (c *Checker) couldContainTypeVariablesSlow(t *Type) bool { objectFlags := t.objectFlags - if objectFlags&ObjectFlagsCouldContainTypeVariablesComputed != 0 { - return objectFlags&ObjectFlagsCouldContainTypeVariables != 0 - } 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)) || + t.flags&TypeFlagsObject != 0 && !c.isNonGenericTopLevelType(t) && (objectFlags&ObjectFlagsReference != 0 && (t.AsTypeReference().node != nil || c.anyCouldContainTypeVariables(c.getTypeArguments(t))) || 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.flags&TypeFlagsUnionOrIntersection != 0 && t.flags&TypeFlagsEnumLiteral == 0 && !c.isNonGenericTopLevelType(t) && c.anyCouldContainTypeVariables(t.Types()) t.objectFlags |= ObjectFlagsCouldContainTypeVariablesComputed | core.IfElse(result, ObjectFlagsCouldContainTypeVariables, 0) return result } @@ -22914,11 +24647,28 @@ func (c *Checker) instantiateTypeWorker(t *Type, m *TypeMapper, alias *TypeAlias if objectFlags&(ObjectFlagsReference|ObjectFlagsAnonymous|ObjectFlagsMapped) != 0 { if objectFlags&ObjectFlagsReference != 0 && t.AsTypeReference().node == nil { resolvedTypeArguments := t.AsTypeReference().resolvedTypeArguments - newTypeArguments := c.instantiateTypes(resolvedTypeArguments, m) - if core.Same(newTypeArguments, resolvedTypeArguments) { + newTypeArguments, changed := c.instantiateTypesTemp(resolvedTypeArguments, m) + if !changed { return t } - return c.createNormalizedTypeReference(t.Target(), newTypeArguments) + target := t.Target() + if target.objectFlags&ObjectFlagsTuple == 0 || target.AsTupleType().combinedFlags&ElementFlagsNonRequired == 0 { + if slices.Equal(newTypeArguments, target.AsInterfaceType().resolvedTypeArguments) { + c.releaseTemporaryTypeArguments(newTypeArguments) + return target + } + id := getTypeListKey(newTypeArguments) + if inst := target.AsInterfaceType().instantiations.get(id); inst != nil { + c.releaseTemporaryTypeArguments(newTypeArguments) + return inst + } + cloned := c.typeListArena.Clone(newTypeArguments) + c.releaseTemporaryTypeArguments(newTypeArguments) + return c.createTypeReference(target, cloned) + } + cloned := c.typeListArena.Clone(newTypeArguments) + c.releaseTemporaryTypeArguments(newTypeArguments) + return c.createNormalizedTypeReference(target, cloned) } if objectFlags&ObjectFlagsReverseMapped != 0 { return c.instantiateReverseMappedType(t, m) @@ -22935,17 +24685,23 @@ func (c *Checker) instantiateTypeWorker(t *Type, m *TypeMapper, alias *TypeAlias } } types := source.Types() - newTypes := c.instantiateTypes(types, m) - if core.Same(newTypes, types) && alias.Symbol() == t.alias.Symbol() { + newTypes, changed := c.instantiateTypesTemp(types, m) + if !changed && alias.Symbol() == t.alias.Symbol() { return t } if alias == nil { alias = c.instantiateTypeAlias(t.alias, m) } + var res *Type if source.flags&TypeFlagsIntersection != 0 { - return c.getIntersectionTypeEx(newTypes, IntersectionFlagsNone, alias) + res = c.getIntersectionTypeEx(newTypes, IntersectionFlagsNone, alias) + } else { + res = c.getUnionTypeEx(newTypes, UnionReductionLiteral, alias, nil /*origin*/) + } + if changed { + c.releaseTemporaryTypeArguments(newTypes) } - return c.getUnionTypeEx(newTypes, UnionReductionLiteral, alias, nil /*origin*/) + return res case flags&TypeFlagsIndex != 0: return c.getIndexType(c.instantiateType(t.Target(), m)) case flags&TypeFlagsIndexedAccess != 0: @@ -22955,11 +24711,16 @@ func (c *Checker) instantiateTypeWorker(t *Type, m *TypeMapper, alias *TypeAlias d := t.AsIndexedAccessType() return c.getIndexedAccessTypeEx(c.instantiateType(d.objectType, m), c.instantiateType(d.indexType, m), d.accessFlags, nil /*accessNode*/, alias) case flags&TypeFlagsTemplateLiteral != 0: - return c.getTemplateLiteralType(t.AsTemplateLiteralType().texts, c.instantiateTypes(t.AsTemplateLiteralType().types, m)) + newTypes, changed := c.instantiateTypesTemp(t.AsTemplateLiteralType().types, m) + res := c.getTemplateLiteralType(t.AsTemplateLiteralType().texts, newTypes) + if changed { + c.releaseTemporaryTypeArguments(newTypes) + } + return res case flags&TypeFlagsStringMapping != 0: return c.getStringMappingType(t.symbol, c.instantiateType(t.AsStringMappingType().target, m)) case flags&TypeFlagsConditional != 0: - return c.getConditionalTypeInstantiation(t, c.combineTypeMappers(t.AsConditionalType().mapper, m), false /*forConstraint*/, alias) + return c.getConditionalTypeInstantiationEx(t, t.AsConditionalType().mapper, m, false /*forConstraint*/, alias) case flags&TypeFlagsSubstitution != 0: newBaseType := c.instantiateType(t.AsSubstitutionType().baseType, m) if c.isNoInferType(t) { @@ -23018,15 +24779,17 @@ func (c *Checker) getObjectTypeInstantiation(t *Type, m *TypeMapper, alias *Type typeParameters = c.getOuterTypeParameters(declaration, true /*includeThisTypes*/) if len(target.alias.TypeArguments()) == 0 { if t.objectFlags&(ObjectFlagsReference|ObjectFlagsInstantiationExpressionType) != 0 { - typeParameters = core.Filter(typeParameters, func(tp *Type) bool { - return c.isTypeParameterPossiblyReferenced(tp, declaration) - }) + typeParameters = c.filterPossiblyReferencedTypeParameters(typeParameters, declaration) } else if target.symbol.Flags()&(ast.SymbolFlagsMethod|ast.SymbolFlagsTypeLiteral) != 0 { - typeParameters = core.Filter(typeParameters, func(tp *Type) bool { - return core.Some(t.symbol.Declarations(), func(d *ast.Node) bool { - return c.isTypeParameterPossiblyReferenced(tp, d) + if decls := t.symbol.Declarations(); len(decls) == 1 { + typeParameters = c.filterPossiblyReferencedTypeParameters(typeParameters, decls[0]) + } else { + typeParameters = core.Filter(typeParameters, func(tp *Type) bool { + return core.Some(decls, func(d *ast.Node) bool { + return c.isTypeParameterPossiblyReferenced(tp, d) + }) }) - }) + } } } if typeParameters == nil { @@ -23040,23 +24803,48 @@ func (c *Checker) getObjectTypeInstantiation(t *Type, m *TypeMapper, alias *Type // We are instantiating an anonymous type that has one or more type parameters in scope. Apply the // mapper to the type parameters to produce the effective list of type arguments, and compute the // instantiation cache key from the type IDs of the type arguments. - typeArguments := make([]*Type, len(typeParameters)) + m1 := t.Mapper() + typeArguments := c.getTemporaryTypeArguments(len(typeParameters)) + changed := false for i, tp := range typeParameters { - typeArguments[i] = c.mapTypeWithCompositeMapper(tp, t.Mapper(), m) + if m1 != nil { + if t1 := m1.Map(tp); t1 != tp { + t2 := c.instantiateType(t1, m) + if t2 != t1 { + changed = true + } + typeArguments[i] = t2 + continue + } + } + t2 := m.Map(tp) + if t2 != tp { + changed = true + } + typeArguments[i] = t2 } newAlias := alias if newAlias == nil { newAlias = c.instantiateTypeAlias(t.alias, m) } + if !changed && newAlias == t.alias && t.objectFlags&ObjectFlagsSingleSignatureType == 0 && (t != target || t.objectFlags&ObjectFlagsMapped == 0) { + c.releaseTemporaryTypeArguments(typeArguments) + return t + } + if newAlias == target.alias && t.objectFlags&ObjectFlagsSingleSignatureType == 0 && slices.Equal(typeArguments, typeParameters) { + c.releaseTemporaryTypeArguments(typeArguments) + return target + } 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 - } - result := data.instantiations[key] + result := data.instantiations.get(key) if result == nil { - newMapper := newTypeMapper(typeParameters, typeArguments) + var newMapper *TypeMapper + if len(typeParameters) == 1 { + newMapper = newSimpleTypeMapper(typeParameters[0], typeArguments[0]) + } else { + newMapper = newArrayTypeMapper(typeParameters, c.typeListArena.Clone(typeArguments)) + } if target.objectFlags&ObjectFlagsSingleSignatureType != 0 && m != nil { newMapper = c.combineTypeMappers(newMapper, m) } @@ -23068,10 +24856,10 @@ func (c *Checker) getObjectTypeInstantiation(t *Type, m *TypeMapper, alias *Type default: result = c.instantiateAnonymousType(target, newMapper, newAlias) } - data.instantiations[key] = result + data.setInstantiation(c, key, result) 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) + resultCouldContainObjectFlags := c.anyCouldContainTypeVariables(typeArguments) if result.objectFlags&ObjectFlagsCouldContainTypeVariablesComputed == 0 { if result.objectFlags&(ObjectFlagsMapped|ObjectFlagsAnonymous|ObjectFlagsReference) != 0 { result.objectFlags |= ObjectFlagsCouldContainTypeVariablesComputed | core.IfElse(resultCouldContainObjectFlags, ObjectFlagsCouldContainTypeVariables, 0) @@ -23084,10 +24872,11 @@ func (c *Checker) getObjectTypeInstantiation(t *Type, m *TypeMapper, alias *Type } } } + c.releaseTemporaryTypeArguments(typeArguments) return result } -func (c *Checker) isTypeParameterPossiblyReferenced(tp *Type, node *ast.Node) bool { +func (c *Checker) typeParameterReferenceInSubtree(tp *Type, node *ast.Node) bool { var containsReference func(*ast.Node) bool containsReference = func(node *ast.Node) bool { switch node.Kind { @@ -23126,18 +24915,67 @@ func (c *Checker) isTypeParameterPossiblyReferenced(tp *Type, node *ast.Node) bo } return node.ForEachChild(containsReference) } + return containsReference(node) +} + +func (c *Checker) filterPossiblyReferencedTypeParameters(candidates []*Type, node *ast.Node) []*Type { + if len(candidates) <= 1 { + return core.Filter(candidates, func(tp *Type) bool { return c.isTypeParameterPossiblyReferenced(tp, node) }) + } + referenced := make(map[*ast.Symbol]bool) + thisReferenced := false + uncertain := false + var collect func(*ast.Node) bool + collect = func(n *ast.Node) bool { + switch n.Kind { + case ast.KindThisType: + thisReferenced = true + case ast.KindTypeReference: + if len(n.TypeArguments()) == 0 { + if sym := c.getSymbolFromTypeReference(n); sym != nil { + referenced[sym] = true + } + } + case ast.KindTypeQuery, ast.KindMethodDeclaration, ast.KindMethodSignature: + uncertain = true + return true + } + return n.ForEachChild(collect) + } + collect(node) + if uncertain { + return core.Filter(candidates, func(tp *Type) bool { return c.isTypeParameterPossiblyReferenced(tp, node) }) + } + return core.Filter(candidates, func(tp *Type) bool { + if tp.symbol == nil || len(tp.symbol.Declarations()) != 1 { + return true + } + container := tp.symbol.Declarations()[0].Parent + for n := node; n != container; n = n.Parent { + if n == nil || ast.IsBlock(n) || ast.IsConditionalTypeNode(n) && c.typeParameterReferenceInSubtree(tp, n.AsConditionalTypeNode().ExtendsType) { + return true + } + } + if tp.AsTypeParameter().isThisType { + return thisReferenced + } + return referenced[tp.symbol] + }) +} + +func (c *Checker) isTypeParameterPossiblyReferenced(tp *Type, node *ast.Node) bool { // If the type parameter doesn't have exactly one declaration, if there are intervening statement blocks // between the node and the type parameter declaration, if the node contains actual references to the - // type parameter, or if the node contains type queries that we can't prove couldn't contain references to the type parameter, - // we consider the type parameter possibly referenced. + // type parameter, or if the node contains type queries that we can't prove couldn't contain references to the + // type parameter, we consider the type parameter possibly referenced. if tp.symbol != nil && len(tp.symbol.Declarations()) == 1 { container := tp.symbol.Declarations()[0].Parent for n := node; n != container; n = n.Parent { - if n == nil || ast.IsBlock(n) || ast.IsConditionalTypeNode(n) && containsReference(n.AsConditionalTypeNode().ExtendsType) { + if n == nil || ast.IsBlock(n) || ast.IsConditionalTypeNode(n) && c.typeParameterReferenceInSubtree(tp, n.AsConditionalTypeNode().ExtendsType) { return true } } - return containsReference(node) + return c.typeParameterReferenceInSubtree(tp, node) } return true } @@ -23153,6 +24991,7 @@ func (c *Checker) instantiateAnonymousType(t *Type, m *TypeMapper, alias *TypeAl result.AsMappedType().typeParameter = freshTypeParameter m = c.combineTypeMappers(newSimpleTypeMapper(origTypeParameter, freshTypeParameter), m) freshTypeParameter.AsTypeParameter().mapper = m + markMapperEscaped(m) case t.objectFlags&ObjectFlagsInstantiationExpressionType != 0: result.AsInstantiationExpressionType().node = t.AsInstantiationExpressionType().node } @@ -23166,36 +25005,103 @@ func (c *Checker) instantiateAnonymousType(t *Type, m *TypeMapper, alias *TypeAl d := result.AsObjectType() d.target = t d.mapper = m + markMapperEscaped(m) return result } func (c *Checker) getConditionalTypeInstantiation(t *Type, mapper *TypeMapper, forConstraint bool, alias *TypeAlias) *Type { + return c.getConditionalTypeInstantiationEx(t, nil, mapper, forConstraint, alias) +} + +func (c *Checker) getConditionalTypeInstantiationEx(t *Type, m1 *TypeMapper, mapper *TypeMapper, forConstraint bool, alias *TypeAlias) *Type { root := t.AsConditionalType().root if len(root.outerTypeParameters) != 0 { // We are instantiating a conditional type that has one or more type parameters in scope. Apply the // mapper to the type parameters to produce the effective list of type arguments, and compute the // instantiation cache key from the type IDs of the type arguments. - typeArguments := core.Map(root.outerTypeParameters, mapper.Map) + outerTypeParameters := root.outerTypeParameters + typeArguments := c.getTemporaryTypeArguments(len(outerTypeParameters)) + changed := false + for i, tp := range outerTypeParameters { + // What CompositeTypeMapper.Map does. + if m1 != nil { + if t1 := m1.Map(tp); t1 != tp { + t2 := c.instantiateType(t1, mapper) + if t2 != t1 { + changed = true + } + typeArguments[i] = t2 + continue + } + } + t2 := mapper.Map(tp) + if t2 != tp { + changed = true + } + typeArguments[i] = t2 + } + if !changed && !forConstraint && (alias == nil && t.alias == nil || alias == t.alias) && m1 == t.AsConditionalType().mapper { + c.releaseTemporaryTypeArguments(typeArguments) + return t + } key := getConditionalTypeKey(typeArguments, alias, forConstraint) - result := root.instantiations[key] + result := root.instantiations.get(key) if result == nil { - newMapper := newTypeMapper(root.outerTypeParameters, typeArguments) checkType := root.checkType - var distributionType *Type - if root.isDistributive { - distributionType = c.getReducedType(getMappedType(checkType, newMapper)) - } - // Distributive conditional types are distributed over union types. For example, when the - // distributive conditional type T extends U ? X : Y is instantiated with A | B for T, the - // result is (A extends U ? X : Y) | (B extends U ? X : Y). - if distributionType != nil && checkType != distributionType && distributionType.flags&(TypeFlagsUnion|TypeFlagsNever) != 0 { - result = c.mapTypeWithAlias(distributionType, func(t *Type) *Type { - return c.getConditionalType(root, prependTypeMapping(checkType, t, newMapper), forConstraint, nil) - }, alias) + if len(outerTypeParameters) == 1 && root.isDistributive && getNonDistributedTypeParameter(checkType) == outerTypeParameters[0] { + tp := outerTypeParameters[0] + arg := typeArguments[0] + c.releaseTemporaryTypeArguments(typeArguments) + distributionType := c.getReducedType(arg) + if checkType != distributionType && distributionType.flags&(TypeFlagsUnion|TypeFlagsNever) != 0 { + var newMapper *TypeMapper + result = c.mapTypeWithAlias(distributionType, func(t *Type) *Type { + if t == tp { + if newMapper == nil { + newMapper = newSimpleTypeMapper(tp, arg) + } + return c.getConditionalType(root, prependTypeMapping(checkType, t, newMapper), forConstraint, nil) + } + if checkType == tp { + elemKey := packedSingleTypeKey(packedConditionalKeyTag, t, forConstraint) + res := root.instantiations.get(elemKey) + if res == nil { + res = c.getConditionalType(root, newSimpleTypeMapper(tp, t), forConstraint, nil) + root.instantiations.setSmall(c, elemKey, res) + } + return res + } + return c.getConditionalType(root, newSimpleTypeMapper(tp, t), forConstraint, nil) + }, alias) + } else { + result = c.getConditionalType(root, newSimpleTypeMapper(tp, arg), forConstraint, alias) + } } else { - result = c.getConditionalType(root, newMapper, forConstraint, alias) + var newMapper *TypeMapper + if len(outerTypeParameters) == 1 { + newMapper = newSimpleTypeMapper(outerTypeParameters[0], typeArguments[0]) + } else { + newMapper = newArrayTypeMapper(outerTypeParameters, c.typeListArena.Clone(typeArguments)) + } + c.releaseTemporaryTypeArguments(typeArguments) + var distributionType *Type + if root.isDistributive { + distributionType = c.getReducedType(getMappedType(checkType, newMapper)) + } + // Distributive conditional types are distributed over union types. For example, when the + // distributive conditional type T extends U ? X : Y is instantiated with A | B for T, the + // result is (A extends U ? X : Y) | (B extends U ? X : Y). + if distributionType != nil && checkType != distributionType && distributionType.flags&(TypeFlagsUnion|TypeFlagsNever) != 0 { + result = c.mapTypeWithAlias(distributionType, func(t *Type) *Type { + return c.getConditionalType(root, prependTypeMapping(checkType, t, newMapper), forConstraint, nil) + }, alias) + } else { + result = c.getConditionalType(root, newMapper, forConstraint, alias) + } } - root.instantiations[key] = result + root.instantiations.setSmall(c, key, result) + } else { + c.releaseTemporaryTypeArguments(typeArguments) } return result } @@ -23233,27 +25139,39 @@ func (c *Checker) instantiateMappedType(t *Type, m *TypeMapper, alias *TypeAlias // { [P in keyof A]: X } | undefined. d := t.AsMappedType() typeVariable := c.getHomomorphicTypeVariable(t) - var instantiateConstituent func(*Type) *Type - instantiateConstituent = func(s *Type) *Type { - if s.flags&(TypeFlagsAnyOrUnknown|TypeFlagsInstantiableNonPrimitive|TypeFlagsObject|TypeFlagsIntersection) == 0 || s == c.wildcardType || c.isErrorType(s) { - return s - } - if d.declaration.NameType == nil { - if c.isArrayType(s) || s.flags&TypeFlagsAny != 0 && c.findResolutionCycleStartIndex(typeVariable, TypeSystemPropertyNameResolvedBaseConstraint) < 0 && c.hasArrayOrTypeTypeConstraint(typeVariable) { - return c.instantiateMappedArrayType(s, t, prependTypeMapping(typeVariable, s, m)) - } - if isTupleType(s) { - return c.instantiateMappedTupleType(s, t, typeVariable, m) - } - if c.isArrayOrTupleOrIntersection(s) { - return c.getIntersectionType(core.Map(s.Types(), instantiateConstituent)) - } - } - return c.instantiateAnonymousType(t, prependTypeMapping(typeVariable, s, m), nil) - } if typeVariable != nil { mappedTypeVariable := c.instantiateType(typeVariable, m) if typeVariable != mappedTypeVariable { + var instantiateConstituent func(*Type) *Type + instantiateConstituent = func(s *Type) *Type { + if s.flags&(TypeFlagsAnyOrUnknown|TypeFlagsInstantiableNonPrimitive|TypeFlagsObject|TypeFlagsIntersection) == 0 || s == c.wildcardType || c.isErrorType(s) { + return s + } + if d.declaration.NameType == nil { + if c.isArrayType(s) || s.flags&TypeFlagsAny != 0 && c.findResolutionCycleStartIndex(typeVariable, TypeSystemPropertyNameResolvedBaseConstraint) < 0 && c.hasArrayOrTypeTypeConstraint(typeVariable) { + return c.instantiateMappedArrayType(s, t, prependTypeMapping(typeVariable, s, m)) + } + if isTupleType(s) { + return c.instantiateMappedTupleType(s, t, typeVariable, m) + } + if c.isArrayOrTupleOrIntersection(s) { + return c.getIntersectionType(core.Map(s.Types(), instantiateConstituent)) + } + } + if m.kind == TypeMapperKindSimple && s != mappedTypeVariable && s != typeVariable && !typeVariable.AsTypeParameter().isDistributed && t.objectFlags&ObjectFlagsSingleSignatureType == 0 { + elemKey := packedSingleTypeKey(packedInstantiationKeyTag, s, false) + res := t.AsObjectType().instantiations.get(elemKey) + if res == nil { + res = c.instantiateAnonymousType(t, prependTypeMapping(typeVariable, s, m), nil) + if res.flags&TypeFlagsObjectFlagsType != 0 && res.objectFlags&ObjectFlagsCouldContainTypeVariablesComputed == 0 && res.objectFlags&(ObjectFlagsMapped|ObjectFlagsAnonymous|ObjectFlagsReference) != 0 { + res.objectFlags |= ObjectFlagsCouldContainTypeVariablesComputed | core.IfElse(c.couldContainTypeVariables(s), ObjectFlagsCouldContainTypeVariables, 0) + } + t.AsObjectType().setInstantiation(c, elemKey, res) + } + return res + } + return c.instantiateAnonymousType(t, prependTypeMapping(typeVariable, s, m), nil) + } return c.mapTypeWithAlias(c.getReducedType(mappedTypeVariable), instantiateConstituent, alias) } } @@ -23412,8 +25330,9 @@ func (c *Checker) getApparentMappedTypeKeys(nameType *Type, targetType *Type) *T } func (c *Checker) forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(t *Type, include TypeFlags, stringsOnly bool, cb func(keyType *Type)) { - for _, prop := range c.getPropertiesOfType(t) { - cb(c.getLiteralTypeFromProperty(prop, include, false)) + props, _ := c.getPropertiesOfTypeLazily(t) + for _, prop := range props { + cb(c.getLiteralTypeFromProperty(prop, include, stringsOnly)) } if t.flags&TypeFlagsAny != 0 { cb(c.stringType) @@ -23444,43 +25363,138 @@ func (c *Checker) instantiateReverseMappedType(t *Type, m *TypeMapper) *Type { // Nested invocation of `inferTypeForHomomorphicMappedType` or the `source` instantiated into something unmappable } -func (c *Checker) instantiateTypeAlias(alias *TypeAlias, m *TypeMapper) *TypeAlias { - if alias == nil { - return nil +func (c *Checker) getTemporaryTypeArguments(n int) []*Type { + if l := len(c.freeTypeArgLists); l > 0 { + s := c.freeTypeArgLists[l-1] + c.freeTypeArgLists[l-1] = nil + c.freeTypeArgLists = c.freeTypeArgLists[:l-1] + if cap(s) >= n { + return s[:n] + } } - return &TypeAlias{symbol: alias.symbol, typeArguments: c.instantiateTypes(alias.typeArguments, m)} + return make([]*Type, n, max(n, 8)) } -func (c *Checker) instantiateTypes(types []*Type, m *TypeMapper) []*Type { - return c.instantiateList(types, m, (*Checker).instantiateType) +func (c *Checker) releaseTemporaryTypeArguments(s []*Type) { + clear(s) + c.freeTypeArgLists = append(c.freeTypeArgLists, s[:0]) } -func (c *Checker) instantiateSymbols(symbols []*ast.Symbol, m *TypeMapper) []*ast.Symbol { - return c.instantiateList(symbols, m, (*Checker).instantiateSymbol) +func (c *Checker) getTemporarySymbolList() []*ast.Symbol { + if l := len(c.freeSymbolLists); l > 0 { + s := c.freeSymbolLists[l-1] + c.freeSymbolLists[l-1] = nil + c.freeSymbolLists = c.freeSymbolLists[:l-1] + return s[:0] + } + return make([]*ast.Symbol, 0, 8) +} + +func (c *Checker) releaseTemporarySymbolList(s []*ast.Symbol) { + if s != nil { + clear(s) + c.freeSymbolLists = append(c.freeSymbolLists, s[:0]) + } } -func (c *Checker) instantiateSignatures(signatures []*Signature, m *TypeMapper) []*Signature { - return c.instantiateList(signatures, m, (*Checker).instantiateSignature) +func (c *Checker) instantiateTypeAlias(alias *TypeAlias, m *TypeMapper) *TypeAlias { + if alias == nil { + return nil + } + newTypeArgs := c.instantiateTypes(alias.typeArguments, m) + if core.Same(newTypeArgs, alias.typeArguments) { + return alias + } + result := c.typeAliasArena.New() + result.symbol = alias.symbol + result.typeArguments = newTypeArgs + return result } -func (c *Checker) instantiateIndexInfos(indexInfos []*IndexInfo, m *TypeMapper) []*IndexInfo { - return c.instantiateList(indexInfos, m, (*Checker).instantiateIndexInfo) +func (c *Checker) instantiateTypesTemp(types []*Type, m *TypeMapper) ([]*Type, bool) { + for i, value := range types { + mapped := c.instantiateType(value, m) + if mapped != value { + result := c.getTemporaryTypeArguments(len(types)) + copy(result, types[:i]) + result[i] = mapped + for j := i + 1; j < len(types); j++ { + result[j] = c.instantiateType(types[j], m) + } + return result, true + } + } + return types, false } -func (c *Checker) instantiateList[T comparable](values []T, m *TypeMapper, instantiator func(c *Checker, value T, m *TypeMapper) T) []T { - for i, value := range values { - mapped := instantiator(c, value, m) +func (c *Checker) instantiateTypes(types []*Type, m *TypeMapper) []*Type { + for i, value := range types { + mapped := c.instantiateType(value, m) if mapped != value { - result := make([]T, len(values)) - copy(result, values[:i]) + result := c.typeListArena.NewSlice(len(types)) + copy(result, types[:i]) result[i] = mapped - for j := i + 1; j < len(values); j++ { - result[j] = instantiator(c, values[j], m) + for j := i + 1; j < len(types); j++ { + result[j] = c.instantiateType(types[j], m) } return result } } - return values + return types +} + +func (c *Checker) instantiateSymbols(symbols []*ast.Symbol, mapper *TypeMapper) []*ast.Symbol { + if len(symbols) != 0 && mapper != nil { + for i, symbol := range symbols { + mapped := c.instantiateSymbol(symbol, mapper) + if symbol != mapped { + result := make([]*ast.Symbol, len(symbols)) + copy(result, symbols[:i]) + result[i] = mapped + for j := i + 1; j < len(symbols); j++ { + result[j] = c.instantiateSymbol(symbols[j], mapper) + } + return result + } + } + } + return symbols +} + +func (c *Checker) instantiateSignatures(signatures []*Signature, mapper *TypeMapper) []*Signature { + if len(signatures) != 0 && mapper != nil { + for i, sig := range signatures { + mapped := c.instantiateSignature(sig, mapper) + if sig != mapped { + result := c.signatureListArena.NewSlice(len(signatures)) + copy(result, signatures[:i]) + result[i] = mapped + for j := i + 1; j < len(signatures); j++ { + result[j] = c.instantiateSignature(signatures[j], mapper) + } + return result + } + } + } + return signatures +} + +func (c *Checker) instantiateIndexInfos(indexInfos []*IndexInfo, mapper *TypeMapper) []*IndexInfo { + if len(indexInfos) != 0 && mapper != nil { + for i, info := range indexInfos { + mapped := c.instantiateIndexInfo(info, mapper) + if info != mapped { + result := c.indexInfoListArena.NewSlice(len(indexInfos)) + copy(result, indexInfos[:i]) + result[i] = mapped + for j := i + 1; j < len(indexInfos); j++ { + result[j] = c.instantiateIndexInfo(indexInfos[j], mapper) + } + return result + } + } + } + return indexInfos } func (c *Checker) tryGetTypeFromTypeNode(node *ast.Node) *Type { @@ -23492,7 +25506,17 @@ func (c *Checker) tryGetTypeFromTypeNode(node *ast.Node) *Type { } func (c *Checker) getTypeFromTypeNode(node *ast.Node) *Type { - return c.getConditionalFlowTypeOfType(c.getTypeFromTypeNodeWorker(node), node) + if node.Flags&ast.NodeFlagsCondFlowClean != 0 { + return c.getTypeFromTypeNodeWorker(node) + } + if links := c.typeNodeLinks.TryGet(node); links != nil && links.resolvedFlowType != nil { + return links.resolvedFlowType + } + t := c.getConditionalFlowTypeOfType(c.getTypeFromTypeNodeWorker(node), node) + if node.Flags&ast.NodeFlagsCondFlowClean == 0 && len(c.typeResolutions) == c.resolutionStart { + c.typeNodeLinks.Get(node).resolvedFlowType = t + } + return t } func (c *Checker) getTypeFromTypeNodeWorker(node *ast.Node) *Type { @@ -24054,7 +26078,12 @@ func (c *Checker) createNormalizedTupleTypeEx(target *Type, elementTypes []*Type // In either layout, zero or more generic variadic elements may be present at any location. // Note that the element types may contain an extra 'this' type argument that we want to ignore during normalization // and then just append to the normalized element types. - n := &TupleNormalizer{} + var typesBuf [8]*Type + var infosBuf [8]TupleElementInfo + n := TupleNormalizer{ + types: typesBuf[:0], + infos: infosBuf[:0], + } if !n.normalize(c, elementTypes[:len(d.elementInfos)], d.elementInfos) { return c.errorType } @@ -24066,7 +26095,15 @@ func (c *Checker) createNormalizedTupleTypeEx(target *Type, elementTypes []*Type case tupleTarget == c.emptyGenericType: return c.emptyObjectType case len(n.types) != 0: - return c.createTypeReferenceEx(tupleTarget, n.types, objectFlags) + intf := tupleTarget.AsInterfaceType() + if objectFlags == 0 && slices.Equal(n.types, intf.resolvedTypeArguments) { + return tupleTarget + } + id := getTypeListKey(n.types) + if t := intf.instantiations.get(id); t != nil { + return t + } + return c.createTypeReferenceEx(tupleTarget, c.typeListArena.Clone(n.types), objectFlags) } return tupleTarget } @@ -24305,11 +26342,11 @@ 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.setSmall(c, key, errorType) } return errorType } @@ -24376,11 +26413,11 @@ func (c *Checker) getTypeAliasInstantiation(symbol *ast.Symbol, typeArguments [] links := c.typeAliasLinks.Get(symbol) typeParameters := links.typeParameters key := getTypeAliasInstantiationKey(typeArguments, alias) - instantiation := links.instantiations[key] + instantiation := links.instantiations.get(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 + links.instantiations.setSmall(c, key, instantiation) } return instantiation } @@ -24584,8 +26621,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.setSmall(c, getTypeListKey(typeParameters), t) } if t == c.intrinsicMarkerType && symbol.Name() == "BuiltinIteratorReturn" { t = c.getBuiltinIteratorReturnType() @@ -24857,17 +26893,46 @@ func (c *Checker) getTypeFromArrayOrTupleTypeNode(node *ast.Node) *Type { } else { links.resolvedType = c.createDeferredTypeReference(target, node, nil /*mapper*/, nil /*alias*/) } - } else { - var elementTypes []*Type - if node.Kind == ast.KindArrayType { - elementTypes = []*Type{c.getTypeFromTypeNode(node.AsArrayTypeNode().ElementType)} - } else { - elementTypes = core.Map(node.Elements(), c.getTypeFromTypeNode) + } else if node.Kind == ast.KindArrayType { + elemType := c.getTypeFromTypeNode(node.AsArrayTypeNode().ElementType) + var buf [1]*Type + buf[0] = elemType + if len(target.AsInterfaceType().resolvedTypeArguments) == 1 && target.AsInterfaceType().resolvedTypeArguments[0] == elemType { + links.resolvedType = target + } else if id := getTypeListKey(buf[:]); true { + if inst := target.AsInterfaceType().instantiations.get(id); inst != nil { + links.resolvedType = inst + } else { + links.resolvedType = c.createTypeReferenceEx(target, c.typeListArena.NewSlice1(elemType), ObjectFlagsFromTypeNode) + } } - if target.objectFlags&ObjectFlagsTuple != 0 { - links.resolvedType = c.createNormalizedTupleTypeEx(target, elementTypes, ObjectFlagsFromTypeNode) + } else { + elements := node.Elements() + if target.objectFlags&ObjectFlagsTuple != 0 && target.AsTupleType().combinedFlags&ElementFlagsNonRequired == 0 { + elementTypes := c.getTemporaryTypeArguments(len(elements)) + for i, e := range elements { + elementTypes[i] = c.getTypeFromTypeNode(e) + } + if slices.Equal(elementTypes, target.AsInterfaceType().resolvedTypeArguments) { + c.releaseTemporaryTypeArguments(elementTypes) + links.resolvedType = target + } else if id := getTypeListKey(elementTypes); true { + if inst := target.AsInterfaceType().instantiations.get(id); inst != nil { + c.releaseTemporaryTypeArguments(elementTypes) + links.resolvedType = inst + } else { + cloned := c.typeListArena.Clone(elementTypes) + c.releaseTemporaryTypeArguments(elementTypes) + links.resolvedType = c.createTypeReferenceEx(target, cloned, ObjectFlagsFromTypeNode) + } + } } else { - links.resolvedType = c.createTypeReferenceEx(target, elementTypes, ObjectFlagsFromTypeNode) + elementTypes := core.Map(elements, c.getTypeFromTypeNode) + if target.objectFlags&ObjectFlagsTuple != 0 { + links.resolvedType = c.createNormalizedTupleTypeEx(target, elementTypes, ObjectFlagsFromTypeNode) + } else { + links.resolvedType = c.createTypeReferenceEx(target, elementTypes, ObjectFlagsFromTypeNode) + } } } } @@ -24887,7 +26952,18 @@ func (c *Checker) getArrayOrTupleTargetType(node *ast.Node) *Type { } return c.globalArrayType } - return c.getTupleTargetType(core.Map(node.Elements(), c.getTupleElementInfo), readonly) + elements := node.Elements() + var stackBuf [16]TupleElementInfo + var elementInfos []TupleElementInfo + if len(elements) <= len(stackBuf) { + elementInfos = stackBuf[:len(elements)] + } else { + elementInfos = make([]TupleElementInfo, len(elements)) + } + for i, e := range elements { + elementInfos[i] = c.getTupleElementInfo(e) + } + return c.getTupleTargetType(elementInfos, readonly) } func (c *Checker) isReadonlyTypeOperator(node *ast.Node) bool { @@ -25009,7 +27085,7 @@ func (c *Checker) getTypeFromConditionalTypeNode(node *ast.Node) *Type { if alias != nil && len(alias.typeArguments) != 0 { outerTypeParameters = allOuterTypeParameters } else { - outerTypeParameters = core.Filter(allOuterTypeParameters, func(tp *Type) bool { return c.isTypeParameterPossiblyReferenced(tp, node) }) + outerTypeParameters = c.filterPossiblyReferencedTypeParameters(allOuterTypeParameters, node) } root := &ConditionalRoot{ node: node.AsConditionalTypeNode(), @@ -25018,13 +27094,11 @@ 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) if outerTypeParameters != nil { - root.instantiations = make(map[CacheHashKey]*Type) - root.instantiations[getConditionalTypeKey(outerTypeParameters, nil /*alias*/, false /*forConstraint*/)] = links.resolvedType + root.instantiations.setSmall(c, getConditionalTypeKey(outerTypeParameters, nil /*alias*/, false /*forConstraint*/), links.resolvedType) } } return links.resolvedType @@ -25044,7 +27118,8 @@ func (c *Checker) getConditionalType(root *ConditionalRoot, mapper *TypeMapper, c.error(c.currentNode, diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite) return c.errorType } - checkType := c.instantiateType(c.getActualTypeVariable(root.checkType), mapper) + actualCheckType := c.getActualTypeVariable(root.checkType) + checkType := c.instantiateType(actualCheckType, mapper) extendsType := c.instantiateType(root.extendsType, mapper) if checkType == c.errorType || extendsType == c.errorType { return c.errorType @@ -25060,74 +27135,95 @@ func (c *Checker) getConditionalType(root *ConditionalRoot, mapper *TypeMapper, checkTuples := c.isSimpleTupleType(checkTypeNode) && c.isSimpleTupleType(extendsTypeNode) && len(checkTypeNode.Elements()) == len(extendsTypeNode.Elements()) checkTypeDeferred := c.isDeferredType(checkType, checkTuples) var combinedMapper *TypeMapper - if len(root.inferTypeParameters) != 0 { - // When we're looking at making an inference for an infer type, when we get its constraint, it'll automagically be - // instantiated with the context, so it doesn't need the mapper for the inference context - however the constraint - // may refer to another _root_, _uncloned_ `infer` type parameter [1], or to something mapped by `mapper` [2]. - // [1] Eg, if we have `Foo` and `Foo` - `B` is constrained to `T`, which, in turn, has been instantiated - // as `number` - // Conversely, if we have `Foo`, `B` is still constrained to `T` and `T` is instantiated as `A` - // [2] Eg, if we have `Foo` and `Foo` where `Q` is mapped by `mapper` into `number` - `B` is constrained to `T` - // which is in turn instantiated as `Q`, which is in turn instantiated as `number`. - // So we need to: - // * combine `context.nonFixingMapper` with `mapper` so their constraints can be instantiated in the context of `mapper` (otherwise they'd only get inference context information) - // * incorporate all of the component mappers into the combined mapper for the true and false members - // This means we have two mappers that need applying: - // * The original `mapper` used to create this conditional - // * The mapper that maps the infer type parameter to its inference result (`context.mapper`) - context := c.newInferenceContext(root.inferTypeParameters, nil /*signature*/, InferenceFlagsNone, nil) - if mapper != nil { - context.nonFixingMapper = c.combineTypeMappers(context.nonFixingMapper, mapper) - } - if !checkTypeDeferred { + if !checkTypeDeferred { + var context *InferenceContext + if len(root.inferTypeParameters) != 0 { + // When we're looking at making an inference for an `infer` type, when we get its constraint, it'll automagically be + // instantiated with the context, so it doesn't need the mapper for the inference context - however the constraint + // may refer to another _root_, _uncloned_ `infer` type parameter [1], or to something mapped by `mapper` [2]. + // [1] Eg, if we have `Foo` and `Foo` - `B` is constrained to `T`, which, in turn, has been instantiated + // as `number` + // Conversely, if we have `Foo`, `B` is still constrained to `T` and `T` is instantiated as `A` + // [2] Eg, if we have `Foo` and `Foo` where `Q` is mapped by `mapper` into `number` - `B` is constrained to `T` + // which is in turn instantiated as `Q`, which is in turn instantiated as `number`. + // So we need to: + // * combine `context.nonFixingMapper` with `mapper` so their constraints can be instantiated in the context of `mapper` (otherwise they'd only get inference context information) + // * incorporate all of the component mappers into the combined mapper for the true and false members + // This means we have two mappers that need applying: + // * The original `mapper` used to create this conditional + // * The mapper that maps the infer type parameter to its inference result (`context.mapper`) + context = c.newInferenceContext(root.inferTypeParameters, nil, InferenceFlagsNone, nil) + if mapper != nil { + context.nonFixingMapper = c.combineTypeMappers(context.nonFixingMapper, mapper) + } // We don't want inferences from constraints as they may cause us to eagerly resolve the // conditional type instead of deferring resolution. Also, we always want strict function // types rules (i.e. proper contravariance) for inferences. c.inferTypes(context.inferences, checkType, extendsType, InferencePriorityNoConstraints|InferencePriorityAlwaysStrict, false) + // It's possible for 'infer T' type parameters to be given uninstantiated constraints when + // those type parameters are used in type references (see getInferredTypeParameterConstraint). For + // that reason we need context.mapper to be first in the combined mapper. See #42636 for examples. + if mapper != nil { + combinedMapper = c.combineTypeMappers(context.mapper, mapper) + } else { + combinedMapper = context.mapper + } } - // It's possible for 'infer T' type parameters to be given uninstantiated constraints when the - // those type parameters are used in type references (see getInferredTypeParameterConstraint). For - // that reason we need context.mapper to be first in the combined mapper. See #42636 for examples. - if mapper != nil { - combinedMapper = c.combineTypeMappers(context.mapper, mapper) + // Instantiate the extends type including inferences for 'infer T' type parameters + var inferredExtendsType *Type + if combinedMapper != nil { + inferredExtendsType = c.instantiateType(root.extendsType, combinedMapper) } else { - combinedMapper = context.mapper - } - } - // Instantiate the extends type including inferences for 'infer T' type parameters - var inferredExtendsType *Type - if combinedMapper != nil { - inferredExtendsType = c.instantiateType(root.extendsType, combinedMapper) - } else { - inferredExtendsType = extendsType - } - // We attempt to resolve the conditional type only when the check and extends types are non-generic - if !checkTypeDeferred && !c.isDeferredType(inferredExtendsType, checkTuples) { - // Return falseType for a definitely false extends check. We check an instantiations of the two - // types with type parameters mapped to the wildcard type, the most permissive instantiations - // possible (the wildcard type is assignable to and from all types). If those are not related, - // then no instantiations will be and we can just return the false branch type. - if inferredExtendsType.flags&TypeFlagsAnyOrUnknown == 0 && (checkType.flags&TypeFlagsAny != 0 || !c.isTypeAssignableTo(c.getPermissiveInstantiation(checkType), c.getPermissiveInstantiation(inferredExtendsType))) { - // Return union of trueType and falseType for 'any' since it matches anything. Furthermore, for a - // distributive conditional type applied to the constraint of a type variable, include trueType if - // there are possible values of the check type that are also possible values of the extends type. - // We use a reverse assignability check as it is less expensive than the comparable relationship - // and avoids false positives of a non-empty intersection check. - if checkType.flags&TypeFlagsAny != 0 || forConstraint && inferredExtendsType.flags&TypeFlagsNever == 0 && someType(c.getPermissiveInstantiation(inferredExtendsType), func(t *Type) bool { - return c.isTypeAssignableTo(t, c.getPermissiveInstantiation(checkType)) - }) { - extraTypes = append(extraTypes, c.instantiateType(c.getTypeFromTypeNode(root.node.TrueType), core.OrElse(combinedMapper, mapper))) - } - // If falseType is an immediately nested conditional type that isn't distributive or has an - // identical checkType, switch to that type and loop. - falseType := c.getTypeFromTypeNode(root.node.FalseType) - if falseType.flags&TypeFlagsConditional != 0 { - newRoot := falseType.AsConditionalType().root - if newRoot.node.Parent == root.node.AsNode() && (!newRoot.isDistributive || newRoot.checkType == root.checkType) { - root = newRoot - continue + inferredExtendsType = extendsType + } + // We attempt to resolve the conditional type only when the check and extends types are non-generic + if !c.isDeferredType(inferredExtendsType, checkTuples) { + // Return falseType for a definitely false extends check. We check an instantiations of the two + // types with type parameters mapped to the wildcard type, the most permissive instantiations + // possible (the wildcard type is assignable to and from all types). If those are not related, + // then no instantiations will be and we can just return the false branch type. + if inferredExtendsType.flags&TypeFlagsAnyOrUnknown == 0 && (checkType.flags&TypeFlagsAny != 0 || !c.isTypeAssignableTo(c.getPermissiveInstantiation(checkType), c.getPermissiveInstantiation(inferredExtendsType))) { + // Return union of trueType and falseType for 'any' since it matches anything. Furthermore, for a + // distributive conditional type applied to the constraint of a type variable, include trueType if + // there are possible values of the check type that are also possible values of the extends type. + // We use a reverse assignability check as it is less expensive than the comparable relationship + // and avoids false positives of a non-empty intersection check. + if checkType.flags&TypeFlagsAny != 0 || forConstraint && inferredExtendsType.flags&TypeFlagsNever == 0 && someType(c.getPermissiveInstantiation(inferredExtendsType), func(t *Type) bool { + return c.isTypeAssignableTo(t, c.getPermissiveInstantiation(checkType)) + }) { + extraTypes = append(extraTypes, c.instantiateType(c.getTypeFromTypeNode(root.node.TrueType), core.OrElse(combinedMapper, mapper))) + } + // If falseType is an immediately nested conditional type that isn't distributive or has an + // identical checkType, switch to that type and loop. + falseType := c.getTypeFromTypeNode(root.node.FalseType) + if falseType.flags&TypeFlagsConditional != 0 { + newRoot := falseType.AsConditionalType().root + if newRoot.node.Parent == root.node.AsNode() && (!newRoot.isDistributive || newRoot.checkType == root.checkType) { + root = newRoot + continue + } + if newRoot, newRootMapper := c.getTailRecursionRoot(falseType, mapper); newRoot != nil { + root = newRoot + mapper = newRootMapper + alias = nil + if newRoot.alias != nil { + tailCount++ + } + continue + } } - if newRoot, newRootMapper := c.getTailRecursionRoot(falseType, mapper); newRoot != nil { + result = c.instantiateType(falseType, mapper) + break + } + // Return trueType for a definitely true extends check. We check instantiations of the two + // types with type parameters mapped to their restrictive form, i.e. a form of the type parameter + // that has no constraint. This ensures that, for example, the type + // type Foo = T extends { x: string } ? string : number + // doesn't immediately resolve to 'string' instead of being deferred. + if inferredExtendsType.flags&TypeFlagsAnyOrUnknown != 0 || c.isTypeAssignableTo(c.getRestrictiveInstantiation(checkType), c.getRestrictiveInstantiation(inferredExtendsType)) { + trueType := c.getTypeFromTypeNode(root.node.TrueType) + trueMapper := core.OrElse(combinedMapper, mapper) + if newRoot, newRootMapper := c.getTailRecursionRoot(trueType, trueMapper); newRoot != nil { root = newRoot mapper = newRootMapper alias = nil @@ -25136,33 +27232,16 @@ func (c *Checker) getConditionalType(root *ConditionalRoot, mapper *TypeMapper, } continue } + result = c.instantiateType(trueType, trueMapper) + break } - result = c.instantiateType(falseType, mapper) - break - } - // Return trueType for a definitely true extends check. We check instantiations of the two - // types with type parameters mapped to their restrictive form, i.e. a form of the type parameter - // that has no constraint. This ensures that, for example, the type - // type Foo = T extends { x: string } ? string : number - // doesn't immediately resolve to 'string' instead of being deferred. - if inferredExtendsType.flags&TypeFlagsAnyOrUnknown != 0 || c.isTypeAssignableTo(c.getRestrictiveInstantiation(checkType), c.getRestrictiveInstantiation(inferredExtendsType)) { - trueType := c.getTypeFromTypeNode(root.node.TrueType) - trueMapper := core.OrElse(combinedMapper, mapper) - if newRoot, newRootMapper := c.getTailRecursionRoot(trueType, trueMapper); newRoot != nil { - root = newRoot - mapper = newRootMapper - alias = nil - if newRoot.alias != nil { - tailCount++ - } - continue - } - result = c.instantiateType(trueType, trueMapper) - break } } - // Return a deferred type for a check that is neither definitely true nor definitely false - result = c.newConditionalType(root, mapper, combinedMapper) + rawCheckType := checkType + if actualCheckType != root.checkType { + rawCheckType = c.instantiateType(root.checkType, mapper) + } + result = c.newConditionalType(root, rawCheckType, extendsType, mapper, combinedMapper) if alias != nil { result.alias = alias } else { @@ -25184,9 +27263,26 @@ func (c *Checker) getTailRecursionRoot(newType *Type, newMapper *TypeMapper) (*C if newType.flags&TypeFlagsConditional != 0 && newMapper != nil { newRoot := newType.AsConditionalType().root if len(newRoot.outerTypeParameters) != 0 { - typeParamMapper := c.combineTypeMappers(newType.AsConditionalType().mapper, newMapper) - typeArguments := core.Map(newRoot.outerTypeParameters, typeParamMapper.Map) - newRootMapper := newTypeMapper(newRoot.outerTypeParameters, typeArguments) + m1 := newType.AsConditionalType().mapper + mapParam := func(tp *Type) *Type { + if m1 != nil { + if t1 := m1.Map(tp); t1 != tp { + return c.instantiateType(t1, newMapper) + } + } + return newMapper.Map(tp) + } + var newRootMapper *TypeMapper + if len(newRoot.outerTypeParameters) == 1 { + tp := newRoot.outerTypeParameters[0] + newRootMapper = newSimpleTypeMapper(tp, mapParam(tp)) + } else { + typeArguments := c.typeListArena.NewSlice(len(newRoot.outerTypeParameters)) + for i, tp := range newRoot.outerTypeParameters { + typeArguments[i] = mapParam(tp) + } + newRootMapper = newArrayTypeMapper(newRoot.outerTypeParameters, typeArguments) + } var newCheckType *Type if newRoot.isDistributive { newCheckType = getMappedType(newRoot.checkType, newRootMapper) @@ -25210,7 +27306,8 @@ func (c *Checker) isDeferredType(t *Type, checkTuples bool) bool { } func (c *Checker) getPermissiveInstantiation(t *Type) *Type { - if t.flags&(TypeFlagsPrimitive|TypeFlagsAnyOrUnknown|TypeFlagsNever) != 0 { + if t.flags&(TypeFlagsPrimitive|TypeFlagsAnyOrUnknown|TypeFlagsNever) != 0 || + !c.couldContainTypeVariables(t) && (t.alias == nil || len(t.alias.typeArguments) == 0 || !c.anyCouldContainTypeVariables(t.alias.typeArguments)) { return t } key := CachedTypeKey{kind: CachedTypeKindPermissiveInstantiation, typeId: t.id} @@ -25223,7 +27320,8 @@ func (c *Checker) getPermissiveInstantiation(t *Type) *Type { } func (c *Checker) getRestrictiveInstantiation(t *Type) *Type { - if t.flags&(TypeFlagsPrimitive|TypeFlagsAnyOrUnknown|TypeFlagsNever) != 0 { + if t.flags&(TypeFlagsPrimitive|TypeFlagsAnyOrUnknown|TypeFlagsNever) != 0 || + !c.couldContainTypeVariables(t) && (t.alias == nil || len(t.alias.typeArguments) == 0 || !c.anyCouldContainTypeVariables(t.alias.typeArguments)) { return t } key := CachedTypeKey{kind: CachedTypeKindRestrictiveInstantiation, typeId: t.id} @@ -25271,30 +27369,43 @@ 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) + ex := d.ensureExtra(c) + if ex.resolvedTrueType == nil { + ex.resolvedTrueType = c.instantiateType(c.getTypeFromTypeNode(d.root.node.TrueType), d.mapper) } - return d.resolvedTrueType + return ex.resolvedTrueType } 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) + ex := d.ensureExtra(c) + if ex.resolvedFalseType == nil { + ex.resolvedFalseType = c.instantiateType(c.getTypeFromTypeNode(d.root.node.FalseType), d.mapper) } - return d.resolvedFalseType + return ex.resolvedFalseType } func (c *Checker) getInferredTrueTypeFromConditionalType(t *Type) *Type { d := t.AsConditionalType() - if d.resolvedInferredTrueType == nil { - if d.combinedMapper != nil { - d.resolvedInferredTrueType = c.instantiateType(c.getTypeFromTypeNode(d.root.node.TrueType), d.combinedMapper) + ex := d.ensureExtra(c) + if ex.resolvedInferredTrueType == nil { + combinedMapper := ex.combinedMapper + if combinedMapper == nil && len(d.root.inferTypeParameters) != 0 { + context := c.newInferenceContext(d.root.inferTypeParameters, nil, InferenceFlagsNone, nil) + if d.mapper != nil { + context.nonFixingMapper = c.combineTypeMappers(context.nonFixingMapper, d.mapper) + combinedMapper = c.combineTypeMappers(context.mapper, d.mapper) + } else { + combinedMapper = context.mapper + } + } + if combinedMapper != nil { + ex.resolvedInferredTrueType = c.instantiateType(c.getTypeFromTypeNode(d.root.node.TrueType), combinedMapper) } else { - d.resolvedInferredTrueType = c.getTrueTypeFromConditionalType(t) + ex.resolvedInferredTrueType = c.getTrueTypeFromConditionalType(t) } } - return d.resolvedInferredTrueType + return ex.resolvedInferredTrueType } func (c *Checker) getTypeFromInferTypeNode(node *ast.Node) *Type { @@ -25436,7 +27547,19 @@ func (c *Checker) createArrayType(elementType *Type) *Type { } func (c *Checker) createArrayTypeEx(elementType *Type, readonly bool) *Type { - return c.createTypeFromGenericGlobalType(core.IfElse(readonly, c.globalReadonlyArrayType, c.globalArrayType), []*Type{elementType}) + genericGlobalType := core.IfElse(readonly, c.globalReadonlyArrayType, c.globalArrayType) + if genericGlobalType == c.emptyGenericType { + return c.emptyObjectType + } + intf := genericGlobalType.AsInterfaceType() + if len(intf.resolvedTypeArguments) == 1 && intf.resolvedTypeArguments[0] == elementType { + return genericGlobalType + } + id := packedSingleTypeKey(packedInstantiationKeyTag, elementType, false) + if t := intf.instantiations.get(id); t != nil { + return t + } + return c.createTypeReference(genericGlobalType, c.typeListArena.NewSlice1(elementType)) } func (c *Checker) getTupleElementFlags(node *ast.Node) ElementFlags { @@ -25466,7 +27589,16 @@ func (c *Checker) getTupleElementInfo(node *ast.Node) TupleElementInfo { } func (c *Checker) createTupleType(elementTypes []*Type) *Type { - elementInfos := core.Map(elementTypes, func(_ *Type) TupleElementInfo { return TupleElementInfo{flags: ElementFlagsRequired} }) + var stackBuf [16]TupleElementInfo + var elementInfos []TupleElementInfo + if len(elementTypes) <= len(stackBuf) { + elementInfos = stackBuf[:len(elementTypes)] + } else { + elementInfos = make([]TupleElementInfo, len(elementTypes)) + } + for i := range elementInfos { + elementInfos[i] = TupleElementInfo{flags: ElementFlagsRequired} + } return c.createTupleTypeEx(elementTypes, elementInfos, false /*readonly*/) } @@ -25490,10 +27622,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.setSmall(c, key, t) } return t } @@ -25546,13 +27678,11 @@ func (c *Checker) createTupleTargetType(elementInfos []TupleElementInfo, readonl d.thisType.AsTypeParameter().isThisType = true d.thisType.AsTypeParameter().constraint = t d.allTypeParameters = append(typeParameters, d.thisType) - d.instantiations = make(map[CacheHashKey]*Type) - d.instantiations[getTypeListKey(d.TypeParameters())] = t d.target = t d.resolvedTypeArguments = d.TypeParameters() d.declaredMembersResolved = true d.declaredMembers = members - d.elementInfos = elementInfos + d.elementInfos = slices.Clone(elementInfos) d.minLength = minLength d.fixedLength = fixedLength d.combinedFlags = combinedFlags @@ -25668,11 +27798,16 @@ 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) + if t.cacheFlags&typeCacheIsReducibleIntersectionComputed == 0 { + inst := c.instantiateType(t, c.uniqueLiteralMapper) + if c.getReducedType(inst) != inst { + t.cacheFlags |= typeCacheIsReducibleIntersectionComputed | typeCacheIsReducibleIntersection + return true + } + t.cacheFlags |= typeCacheIsReducibleIntersectionComputed + return false } - return c.getReducedType(d.uniqueLiteralFilledInstantiation) != d.uniqueLiteralFilledInstantiation + return t.cacheFlags&typeCacheIsReducibleIntersection != 0 } func (c *Checker) getUniqueLiteralTypeForTypeParameter(t *Type) *Type { @@ -25682,10 +27817,35 @@ func (c *Checker) getUniqueLiteralTypeForTypeParameter(t *Type) *Type { return t } +func condFlowRelevant(node *ast.Node) bool { + parent := node.Parent + if parent == nil { + return false + } + if ast.IsParameterDeclaration(parent) { + return true + } + if ast.IsConditionalTypeNode(parent) && node == parent.AsConditionalTypeNode().TrueType { + return true + } + if ast.IsMappedTypeNode(parent) && parent.AsMappedTypeNode().NameType == nil && node == parent.Type() { + return true + } + return false +} + func (c *Checker) getConditionalFlowTypeOfType(t *Type, node *ast.Node) *Type { var constraints []*Type covariant := true + start := node + var lastRelevant *ast.Node for node != nil && !ast.IsStatement(node) && node.Kind != ast.KindJSDoc { + if node.Flags&ast.NodeFlagsCondFlowClean != 0 { + break + } + if condFlowRelevant(node) { + lastRelevant = node + } parent := node.Parent // only consider variance flipped by parameter locations - `keyof` types would usually be considered variance inverting, but // often get used in indexed accesses where they behave sortof invariantly, but our checking is lax @@ -25713,6 +27873,14 @@ func (c *Checker) getConditionalFlowTypeOfType(t *Type, node *ast.Node) *Type { } node = parent } + mark := start + if lastRelevant != nil { + mark = lastRelevant.Parent + } + for mark != node && mark != nil { + mark.Flags |= ast.NodeFlagsCondFlowClean + mark = mark.Parent + } if len(constraints) != 0 { return c.getSubstitutionType(t, c.getIntersectionType(constraints)) } @@ -25737,6 +27905,9 @@ func (c *Checker) newType(flags TypeFlags, objectFlags ObjectFlags, t *Type, kin c.TypeCount++ t.flags = flags t.objectFlags = objectFlags &^ (ObjectFlagsCouldContainTypeVariablesComputed | ObjectFlagsCouldContainTypeVariables | ObjectFlagsMembersResolved) + if flags&TypeFlagsStructuredOrInstantiable == 0 { + t.objectFlags |= ObjectFlagsCouldContainTypeVariablesComputed + } t.id = TypeId(c.TypeCount) t.kind = kind //nolint:customlint t.checker = c @@ -25794,36 +27965,26 @@ func (c *Checker) newUniqueESSymbolType(symbol *ast.Symbol, name string) *Type { func (c *Checker) newObjectType(objectFlags ObjectFlags, symbol *ast.Symbol) *Type { var t *Type - var kind typeKind switch { case objectFlags&ObjectFlagsClassOrInterface != 0: - t = c.interfaceTypeArena.New().AsType() - kind = typeKindInterface + t = c.newType(TypeFlagsObject, objectFlags, c.interfaceTypeArena.New().AsType(), typeKindInterface) case objectFlags&ObjectFlagsTuple != 0: - t = (&TupleType{}).AsType() - kind = typeKindTuple + t = c.newType(TypeFlagsObject, objectFlags, (&TupleType{}).AsType(), typeKindTuple) case objectFlags&ObjectFlagsReference != 0: - t = c.typeReferenceArena.New().AsType() - kind = typeKindTypeReference + t = c.newType(TypeFlagsObject, objectFlags, c.typeReferenceArena.New().AsType(), typeKindTypeReference) case objectFlags&ObjectFlagsMapped != 0: - t = c.mappedTypeArena.New().AsType() - kind = typeKindMapped + t = c.newType(TypeFlagsObject, objectFlags, c.mappedTypeArena.New().AsType(), typeKindMapped) case objectFlags&ObjectFlagsReverseMapped != 0: - t = (&ReverseMappedType{}).AsType() - kind = typeKindReverseMapped + t = c.newType(TypeFlagsObject, objectFlags, (&ReverseMappedType{}).AsType(), typeKindReverseMapped) case objectFlags&ObjectFlagsEvolvingArray != 0: - t = (&EvolvingArrayType{}).AsType() - kind = typeKindEvolvingArray + t = c.newType(TypeFlagsObject, objectFlags, (&EvolvingArrayType{}).AsType(), typeKindEvolvingArray) case objectFlags&ObjectFlagsInstantiationExpressionType != 0: - t = (&InstantiationExpressionType{}).AsType() - kind = typeKindInstantiationExpression + t = c.newType(TypeFlagsObject, objectFlags, (&InstantiationExpressionType{}).AsType(), typeKindInstantiationExpression) case objectFlags&ObjectFlagsAnonymous != 0: - t = c.objectTypeArena.New().AsType() - kind = typeKindObject + t = c.newType(TypeFlagsObject, objectFlags, c.objectTypeArena.New().AsType(), typeKindObject) default: panic("Unhandled case in newObjectType") } - c.newType(TypeFlagsObject, objectFlags, t, kind) t.symbol = symbol return t } @@ -25846,16 +28007,19 @@ 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 objectFlags == 0 && slices.Equal(typeArguments, intf.resolvedTypeArguments) { + return target + } + id := getTypeListKey(typeArguments) + if t := intf.instantiations.get(id); t != nil { 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 + intf.setInstantiation(c, id, t) return t } @@ -25871,6 +28035,7 @@ func (c *Checker) createDeferredTypeReference(target *Type, node *ast.Node, mapp d := t.AsTypeReference() d.target = target d.mapper = mapper + markMapperEscaped(mapper) d.node = node return t } @@ -25886,13 +28051,27 @@ func (c *Checker) cloneTypeReference(source *Type) *Type { func (c *Checker) setStructuredTypeMembers(t *Type, members ast.SymbolTable, callSignatures []*Signature, constructSignatures []*Signature, indexInfos []*IndexInfo) { t.objectFlags |= ObjectFlagsMembersResolved data := t.AsStructuredType() - data.members = members - data.properties = c.getNamedMembers(members, t.symbol) + // Named-member filtering may recursively query this type before it finishes. + var mIdx *memberIndex + if members.Len() != 0 || (members != nil && t.symbol != nil && t.symbol.Flags()&ast.SymbolFlagsValueModule != 0) { + mIdx = c.memberIndexArena.New() + mIdx.fallback = members + data.setMembersAndProperties(mIdx, nil) + } else if data.extra != nil { + data.extra.members = nil + } + properties := c.getNamedMembers(members, t.symbol) + if mIdx != nil && t.symbol != nil && t.symbol.Flags()&ast.SymbolFlagsValueModule != 0 { + mIdx.fallback = members + } else { + mIdx = c.newMemberIndex(members, properties, mIdx) + } + data.setMembersAndProperties(mIdx, properties) c.setStructuredTypeSignatures(data, callSignatures, constructSignatures, indexInfos) } func (c *Checker) setStructuredTypeSignatures(data *StructuredType, callSignatures []*Signature, constructSignatures []*Signature, indexInfos []*IndexInfo) { - if data.extra == nil && callSignatures == nil && constructSignatures == nil && indexInfos == nil { + if data.extra == nil && len(callSignatures) == 0 && len(constructSignatures) == 0 && len(indexInfos) == 0 { return } extra := data.ensureExtra() @@ -25976,14 +28155,19 @@ func (c *Checker) newStringMappingType(symbol *ast.Symbol, target *Type) *Type { return t } -func (c *Checker) newConditionalType(root *ConditionalRoot, mapper *TypeMapper, combinedMapper *TypeMapper) *Type { +func (c *Checker) newConditionalType(root *ConditionalRoot, checkType *Type, extendsType *Type, mapper *TypeMapper, combinedMapper *TypeMapper) *Type { data := c.conditionalTypeArena.New() data.root = root - data.checkType = c.instantiateType(root.checkType, mapper) - data.extendsType = c.instantiateType(root.extendsType, mapper) + data.checkType = checkType + data.extendsType = extendsType data.mapper = mapper - data.combinedMapper = combinedMapper - return c.newType(TypeFlagsConditional, ObjectFlagsNone, data.AsType(), typeKindConditional) + t := c.newType(TypeFlagsConditional, ObjectFlagsNone, data.AsType(), typeKindConditional) + if combinedMapper != nil { + t.AsConditionalType().ensureExtra(c).combinedMapper = combinedMapper + } + markMapperEscaped(mapper) + markMapperEscaped(combinedMapper) + return t } func (c *Checker) newSubstitutionType(baseType *Type, constraint *Type) *Type { @@ -26001,7 +28185,9 @@ func (c *Checker) newSignature(flags SignatureFlags, declaration *ast.Node, type sig.declaration = declaration sig.setTypeParameters(c, typeParameters) sig.parameters = parameters - sig.setThisParameter(c, thisParameter) + if thisParameter != nil { + sig.ensureExtra(c).thisParameter = thisParameter + } sig.resolvedReturnType = resolvedReturnType sig.setResolvedTypePredicate(c, resolvedTypePredicate) sig.minArgumentCount = int32(minArgumentCount) @@ -26024,11 +28210,11 @@ func (c *Checker) getRegularTypeOfLiteralType(t *Type) *Type { return t.AsLiteralType().regularType } if t.flags&TypeFlagsUnion != 0 { - u := t.AsUnionType() - if u.regularType == nil { - u.regularType = c.mapType(t, c.getRegularTypeOfLiteralType) + ex := t.AsStructuredType().ensureExtra() + if ex.regularType == nil { + ex.regularType = c.mapType(t, c.getRegularTypeOfLiteralType) } - return u.regularType + return ex.regularType } return t } @@ -26324,11 +28510,6 @@ func (c *Checker) mapTypeEx(t *Type, f func(*Type) *Type, noReductions bool) *Ty types = u.origin.Types() } var set *temporaryTypeSet - defer func() { - if set != nil { - c.releaseTemporaryTypeSet(set) - } - }() var mappedTypes []*Type var changed bool for i, s := range types { @@ -26351,9 +28532,12 @@ func (c *Checker) mapTypeEx(t *Type, f func(*Type) *Type, noReductions bool) *Ty if changed { set.values = mappedTypes if len(mappedTypes) == 0 { + c.releaseTemporaryTypeSet(set) return nil } - return c.getUnionTypeEx(mappedTypes, core.IfElse(noReductions, UnionReductionNone, UnionReductionLiteral), nil /*alias*/, nil /*origin*/) + res := c.getUnionTypeEx(mappedTypes, core.IfElse(noReductions, UnionReductionNone, UnionReductionLiteral), nil /*alias*/, nil /*origin*/) + c.releaseTemporaryTypeSet(set) + return res } return t } @@ -26384,6 +28568,81 @@ func (c *Checker) getUnionType(types []*Type) *Type { // expression constructs such as array literals and the || and ?: operators). Named types can // circularly reference themselves and therefore cannot be subtype reduced during their declaration. // For example, "type Item = string | (() => Item" is a named type that circularly references itself. +const unionFrontMaxWords = 8 + +type unionFrontSlot struct { + key [unionFrontMaxWords]uint32 + result *Type + meta uint16 +} + +func makeUnionFrontSlotKey(types []*Type, unionReduction UnionReduction, alias *TypeAlias) ([unionFrontMaxWords]uint32, uint16, uint32, bool) { + var key [unionFrontMaxWords]uint32 + hasAlias := alias != nil + extra := 0 + var aliasSymID uint64 + if hasAlias { + aliasSymID = uint64(ast.GetSymbolId(alias.symbol)) + if aliasSymID >= (1 << 32) { + return key, 0, 0, false + } + extra = 1 + len(alias.typeArguments) + } + if len(types)+extra > unionFrontMaxWords { + return key, 0, 0, false + } + var h uint64 = uint64(unionReduction) + n := 0 + for _, t := range types { + w := uint32(t.id) + key[n] = w + n++ + h = (h ^ uint64(w)) * 0x9e3779b97f4a7c15 + } + if hasAlias { + w := uint32(aliasSymID) + key[n] = w + n++ + h = (h ^ uint64(w)) * 0x9e3779b97f4a7c15 + for _, t := range alias.typeArguments { + w := uint32(t.id) + key[n] = w + n++ + h = (h ^ uint64(w)) * 0x9e3779b97f4a7c15 + } + } + h ^= h >> 29 + meta := uint16(1) | (uint16(unionReduction) << 1) | (uint16(len(types)) << 3) + if hasAlias { + meta |= 1 << 7 + } + h ^= uint64(meta) + h *= 0x9e3779b97f4a7c15 + return key, meta, uint32(h >> (64 - 10)), true +} + +func getUnionFrontKey(types []*Type, unionReduction UnionReduction, alias *TypeAlias) CacheHashKey { + if key, ok := packedTypeListKeyWithAlias(packedUnionFrontKeyTag|uint64(unionReduction), types, alias, false); ok { + return key + } + return getUnionFrontKeySlow(types, unionReduction, alias) +} + +func getUnionFrontKeySlow(types []*Type, unionReduction UnionReduction, alias *TypeAlias) CacheHashKey { + if runtime.GOARCH == "wasm" { + b := acquireKeyBuilder() + b.writeUint32(uint32(unionReduction)) + b.writeTypes(types) + b.writeAlias(alias) + return b.releaseHash() + } + var b keyBuilder + b.writeUint32(uint32(unionReduction)) + b.writeTypes(types) + b.writeAlias(alias) + return b.hash() +} + func (c *Checker) getUnionTypeEx(types []*Type, unionReduction UnionReduction, alias *TypeAlias, origin *Type) *Type { if len(types) == 0 { return c.neverType @@ -26391,7 +28650,7 @@ func (c *Checker) getUnionTypeEx(types []*Type, unionReduction UnionReduction, a if len(types) == 1 { return types[0] } - // We optimize for the common case of unioning a union type with some other type (such as `undefined`). + // We optimize for the common case of unioning a union type with some other type (such as 'undefined'). if len(types) == 2 && origin == nil && (types[0].flags&TypeFlagsUnion != 0 || types[1].flags&TypeFlagsUnion != 0) { id1 := types[0].id id2 := types[1].id @@ -26406,6 +28665,50 @@ func (c *Checker) getUnionTypeEx(types []*Type, unionReduction UnionReduction, a } return t } + if origin == nil && (alias == nil || alias.symbol != nil) { + if slotKey, meta, idx, ok := makeUnionFrontSlotKey(types, unionReduction, alias); ok { + if c.unionFrontSlots != nil { + slot := &c.unionFrontSlots[idx] + if slot.meta == meta && slot.key == slotKey { + return slot.result + } + } + typeCount := c.TypeCount + instCount := c.instantiationCount + totalInstCount := c.TotalInstantiationCount + impure := c.unionImpureCount + result := c.getUnionTypeWorker(types, unionReduction, alias, nil) + created := c.TypeCount != typeCount && !(c.TypeCount == typeCount+1 && result.id == TypeId(c.TypeCount)) + instantiated := c.instantiationCount != instCount || c.TotalInstantiationCount != totalInstCount + wasImpure := c.unionImpureCount != impure + if !created && !instantiated && !wasImpure && result != c.errorType { + if c.unionFrontSlots == nil { + c.unionFrontSlots = new([1024]unionFrontSlot) + } + c.unionFrontSlots[idx] = unionFrontSlot{key: slotKey, result: result, meta: meta} + } + return result + } + key := getUnionFrontKey(types, unionReduction, alias) + if cached := c.unionFrontCache[key]; cached != nil { + return cached + } + typeCount := c.TypeCount + instCount := c.instantiationCount + totalInstCount := c.TotalInstantiationCount + impure := c.unionImpureCount + result := c.getUnionTypeWorker(types, unionReduction, alias, nil) + created := c.TypeCount != typeCount && !(c.TypeCount == typeCount+1 && result.id == TypeId(c.TypeCount)) + instantiated := c.instantiationCount != instCount || c.TotalInstantiationCount != totalInstCount + wasImpure := c.unionImpureCount != impure + if !created && !instantiated && !wasImpure && result != c.errorType { + if c.unionFrontCache == nil { + c.unionFrontCache = make(map[CacheHashKey]*Type) + } + c.unionFrontCache[key] = result + } + return result + } return c.getUnionTypeWorker(types, unionReduction, alias, origin) } @@ -26438,9 +28741,11 @@ func (c *Checker) getUnionTypeWorker(types []*Type, unionReduction UnionReductio typeSet = c.removeRedundantLiteralTypes(typeSet, includes, unionReduction&UnionReductionSubtype != 0) } if includes&TypeFlagsStringLiteral != 0 && includes&(TypeFlagsTemplateLiteral|TypeFlagsStringMapping) != 0 { + c.unionImpureCount++ typeSet = c.removeStringLiteralsMatchedByTemplateLiterals(typeSet) } if includes&TypeFlagsIncludesConstrainedTypeVariable != 0 { + c.unionImpureCount++ typeSet = c.removeConstrainedTypeVariables(typeSet) } if unionReduction == UnionReductionSubtype { @@ -26466,15 +28771,36 @@ func (c *Checker) getUnionTypeWorker(types []*Type, unionReduction UnionReductio } } if origin == nil && includes&TypeFlagsUnion != 0 { - namedUnions := c.addNamedUnions(nil, types) - var reducedTypes []*Type - for _, t := range typeSet { - if !core.Some(namedUnions, func(u *Type) bool { return containsType(u.Types(), t) }) { - reducedTypes = append(reducedTypes, t) + namedUnions := c.addNamedUnions(c.getTemporaryTypeArguments(0)[:0], types) + reducedTypes := c.getTemporaryTypeArguments(0)[:0] + if len(c.files) > 0 && (len(namedUnions) > 1 || len(typeSet) > 8) { + if c.namedUnionMemberSet == nil || len(c.namedUnionMemberSet) > 64 { + c.namedUnionMemberSet = make(map[*Type]struct{}, 64) + } else if len(c.namedUnionMemberSet) != 0 { + clear(c.namedUnionMemberSet) + } + for _, u := range namedUnions { + for _, s := range u.Types() { + c.namedUnionMemberSet[s] = struct{}{} + } + } + for _, t := range typeSet { + if _, ok := c.namedUnionMemberSet[t]; !ok { + reducedTypes = append(reducedTypes, t) + } + } + } else { + for _, t := range typeSet { + if !core.Some(namedUnions, func(u *Type) bool { return containsType(u.Types(), t) }) { + reducedTypes = append(reducedTypes, t) + } } } if alias == nil && len(namedUnions) == 1 && len(reducedTypes) == 0 { - return namedUnions[0] + res := namedUnions[0] + c.releaseTemporaryTypeArguments(namedUnions) + c.releaseTemporaryTypeArguments(reducedTypes) + return res } // We create a denormalized origin type only when the union was created from one or more named unions // (unions with alias symbols or origins) and when there is no overlap between those named unions. @@ -26488,6 +28814,8 @@ func (c *Checker) getUnionTypeWorker(types []*Type, unionReduction UnionReductio } origin = c.newUnionType(ObjectFlagsNone, reducedTypes) } + c.releaseTemporaryTypeArguments(namedUnions) + c.releaseTemporaryTypeArguments(reducedTypes) } objectFlags := core.IfElse(includes&TypeFlagsNotPrimitiveUnion != 0, ObjectFlagsNone, ObjectFlagsPrimitiveUnion) | core.IfElse(includes&TypeFlagsIntersection != 0, ObjectFlagsContainsIntersections, ObjectFlagsNone) @@ -26503,25 +28831,47 @@ 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) + var propagatingFlags ObjectFlags + nonNullable := true + for _, elem := range types { + if elem.flags&TypeFlagsNullable == 0 { + propagatingFlags |= elem.objectFlags + } + if elem.flags&(TypeFlagsDefinitelyNonNullable&^TypeFlagsInstantiable) == 0 { + nonNullable = false + } + } + t = c.newUnionType(precomputedObjectFlags|(propagatingFlags&ObjectFlagsPropagatingFlags), types) + if nonNullable { + t.cacheFlags |= typeCacheNonNullableUnion + } t.AsUnionType().origin = origin t.alias = alias if len(types) == 2 && types[0].flags&TypeFlagsBooleanLiteral != 0 && types[1].flags&TypeFlagsBooleanLiteral != 0 { t.flags |= TypeFlagsBoolean } - c.unionTypes[key] = t + c.unionTypes.set(c, key, t) } return t } func (c *Checker) UnionTypes() iter.Seq[*Type] { - return maps.Values(c.unionTypes) + return func(yield func(*Type) bool) { + for i := range c.unionTypes.slots { + if v := c.unionTypes.slots[i].val; v != nil { + if !yield(v) { + return + } + } + } + } } func (c *Checker) addTypesToUnion(sourceTypes []*Type, types []*Type) ([]*Type, TypeFlags) { var includes TypeFlags + totalOrder := len(c.files) > 0 addType := func(t *Type) { flags := t.flags // We ignore 'never' types in unions @@ -26549,9 +28899,31 @@ func (c *Checker) addTypesToUnion(sourceTypes []*Type, types []*Type) ([]*Type, } types = append(types, t) } + if totalOrder && len(sourceTypes) == 2 && (sourceTypes[0].flags&sourceTypes[1].flags)&TypeFlagsUnion != 0 { + if sourceTypes[0] == sourceTypes[1] { + u := sourceTypes[0].AsUnionType() + if sourceTypes[0].alias != nil || u.origin != nil { + includes |= TypeFlagsUnion + } + for _, s := range u.types { + addType(s) + } + return types, includes + } + for _, t := range sourceTypes { + u := t.AsUnionType() + if t.alias != nil || u.origin != nil { + includes |= TypeFlagsUnion + } + } + mergeSortedTypes(sourceTypes[0].AsUnionType().types, sourceTypes[1].AsUnionType().types, true /*dedup*/, addType) + return types, includes + } + runStarts := c.unionRunStarts[:0] var lastType *Type for _, t := range sourceTypes { if t != lastType { + start := len(types) if t.flags&TypeFlagsUnion != 0 { u := t.AsUnionType() if t.alias != nil || u.origin != nil { @@ -26563,12 +28935,20 @@ func (c *Checker) addTypesToUnion(sourceTypes []*Type, types []*Type) ([]*Type, } else { addType(t) } + if totalOrder && start > 0 && len(types) > start && CompareTypes(types[start-1], types[start]) > 0 { + runStarts = append(runStarts, start) + } lastType = t } } + c.unionRunStarts = runStarts if len(types) >= 2 { // Sort and deduplicate types - slices.SortStableFunc(types, CompareTypes) + if !totalOrder { + slices.SortStableFunc(types, CompareTypes) + } else if len(runStarts) > 0 { + types = c.mergeAscendingRuns(types, runStarts) + } unique := 1 for _, t := range types[1:] { if t != types[unique-1] { @@ -26576,10 +28956,128 @@ func (c *Checker) addTypesToUnion(sourceTypes []*Type, types []*Type) ([]*Type, unique++ } } - clear(types[unique:]) - types = types[:unique] + clear(types[unique:]) + types = types[:unique] + } + return types, includes +} + +func (c *Checker) mergeAscendingRuns(types []*Type, runStarts []int) []*Type { + n := len(types) + if len(runStarts) == 1 { + mid := runStarts[0] + if mid == 1 { + t := types[0] + i, _ := slices.BinarySearchFunc(types[1:], t, CompareTypes) + copy(types[:i], types[1:i+1]) + types[i] = t + return types + } + if mid == n-1 { + t := types[n-1] + i, _ := slices.BinarySearchFunc(types[:n-1], t, CompareTypes) + copy(types[i+1:], types[i:n-1]) + types[i] = t + return types + } + } + bounds := c.unionRunBounds[:0] + bounds = append(bounds, 0) + bounds = append(bounds, runStarts...) + bounds = append(bounds, n) + dst := c.unionMergeBuf + if cap(dst) < n { + dst = make([]*Type, 0, n) + } + src := types + swapped := false + next := c.unionRunNext[:0] + for len(bounds) > 2 { + runs := len(bounds) - 1 + next = next[:0] + next = append(next, 0) + dst = dst[:0] + for j := 0; j < runs; { + if j+1 < runs { + mergeSortedTypes(src[bounds[j]:bounds[j+1]], src[bounds[j+1]:bounds[j+2]], false /*dedup*/, func(t *Type) { + dst = append(dst, t) + }) + next = append(next, bounds[j+2]) + j += 2 + } else { + dst = append(dst, src[bounds[j]:bounds[j+1]]...) + next = append(next, bounds[j+1]) + j++ + } + } + src, dst = dst, src + swapped = !swapped + bounds, next = next, bounds + } + c.unionRunBounds = bounds + c.unionRunNext = next + if swapped { + copy(types, src) + clear(src) + c.unionMergeBuf = src[:0] + } else { + clear(dst) + c.unionMergeBuf = dst[:0] + } + return types +} + +func mergeSortedTypes(left []*Type, right []*Type, dedup bool, out func(*Type)) { + short, long := min(len(left), len(right)), max(len(left), len(right)) + if short > 0 && short*bits.Len(uint(long))*2 < long { + leftIsLong := len(left) >= len(right) + longRun, shortRun := right, left + if leftIsLong { + longRun, shortRun = left, right + } + for _, t := range shortRun { + i, _ := slices.BinarySearchFunc(longRun, t, CompareTypes) + for _, x := range longRun[:i] { + out(x) + } + longRun = longRun[i:] + out(t) + if len(longRun) > 0 && longRun[0] == t { + longRun = longRun[1:] + if !dedup { + out(t) + } + } + } + for _, x := range longRun { + out(x) + } + return + } + i, j := 0, 0 + for i < len(left) && j < len(right) { + l, r := left[i], right[j] + if l == r { + out(l) + if !dedup { + out(r) + } + i++ + j++ + } else if CompareTypes(l, r) < 0 { + out(l) + i++ + } else { + out(r) + j++ + } + } + for ; i < len(left); i++ { + out(left[i]) + } + for ; j < len(right); j++ { + out(right[j]) } - return types, includes } func (c *Checker) addNamedUnions(namedUnions []*Type, types []*Type) []*Type { @@ -26596,6 +29094,16 @@ func (c *Checker) addNamedUnions(namedUnions []*Type, types []*Type) []*Type { return namedUnions } +func (c *Checker) containsRegularLiteralType(types []*Type, i int, regular *Type) bool { + if i > 0 && types[i-1] == regular || i+1 < len(types) && types[i+1] == regular { + return true + } + if len(c.files) > 0 { + return false + } + return containsType(types, regular) +} + func (c *Checker) removeRedundantLiteralTypes(types []*Type, includes TypeFlags, reduceVoidUndefined bool) []*Type { i := len(types) for i > 0 { @@ -26607,7 +29115,7 @@ func (c *Checker) removeRedundantLiteralTypes(types []*Type, includes TypeFlags, flags&TypeFlagsBigIntLiteral != 0 && includes&TypeFlagsBigInt != 0 || flags&TypeFlagsUniqueESSymbol != 0 && includes&TypeFlagsESSymbol != 0 || reduceVoidUndefined && flags&TypeFlagsUndefined != 0 && includes&TypeFlagsVoid != 0 || - isFreshLiteralType(t) && containsType(types, t.AsLiteralType().regularType) + isFreshLiteralType(t) && c.containsRegularLiteralType(types, i, t.AsLiteralType().regularType) if remove { types = slices.Delete(types, i, i+1) } @@ -26616,16 +29124,36 @@ func (c *Checker) removeRedundantLiteralTypes(types []*Type, includes TypeFlags, } func (c *Checker) removeStringLiteralsMatchedByTemplateLiterals(types []*Type) []*Type { - templates := core.Filter(types, c.isPatternLiteralType) + var templates []*Type + for _, t := range types { + if c.isPatternLiteralType(t) { + templates = append(templates, t) + } + } if len(templates) != 0 { i := len(types) for i > 0 { i-- t := types[i] - if t.flags&TypeFlagsStringLiteral != 0 && core.Some(templates, func(template *Type) bool { - return c.isTypeMatchedByTemplateLiteralOrStringMapping(t, template) - }) { - types = slices.Delete(types, i, i+1) + if t.flags&TypeFlagsStringLiteral != 0 { + val := t.AsLiteralType().value.(string) + matched := false + for _, template := range templates { + if template.flags&TypeFlagsTemplateLiteral != 0 { + texts := template.AsTemplateLiteralType().texts + start, end := texts[0], texts[len(texts)-1] + if len(val) < len(start)+len(end) || !strings.HasPrefix(val, start) || !strings.HasSuffix(val, end) { + continue + } + } + if c.isTypeMatchedByTemplateLiteralOrStringMapping(t, template) { + matched = true + break + } + } + if matched { + types = slices.Delete(types, i, i+1) + } } } } @@ -26705,11 +29233,13 @@ func (c *Checker) removeSubtypes(types []*Type, hasObjectTypes bool) []*Type { // 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. hasEmptyObject := hasObjectTypes && core.Some(types, func(t *Type) bool { - return t.flags&TypeFlagsObject != 0 && !c.isGenericMappedType(t) && c.isEmptyResolvedType(c.resolveStructuredTypeMembers(t)) + return t.flags&TypeFlagsObject != 0 && !c.isGenericMappedType(t) && c.isEmptyObjectType(t) }) length := len(types) i := length count := 0 + var keyTypes map[*Type]*Type + var lastKeyName string for i > 0 { i-- source := types[i] @@ -26726,8 +29256,8 @@ func (c *Checker) removeSubtypes(types []*Type, hasObjectTypes bool) []*Type { return t })), c.strictSubtypeRelation) { types = slices.Delete(types, i, i+1) + continue } - continue } // Find the first property with a unit type, if any. When constituents have a property by the same name // but of a different unit type, we can quickly disqualify them from subtype checks. This helps subtype @@ -26735,12 +29265,25 @@ func (c *Checker) removeSubtypes(types []*Type, hasObjectTypes bool) []*Type { var keyProperty *ast.Symbol var keyPropertyType *Type if source.flags&(TypeFlagsObject|TypeFlagsIntersection|TypeFlagsInstantiableNonPrimitive) != 0 { - keyProperty = core.Find(c.getPropertiesOfType(source), func(p *ast.Symbol) bool { - return isUnitType(c.getTypeOfSymbol(p)) - }) - } - if keyProperty != nil { - keyPropertyType = c.getRegularTypeOfLiteralType(c.getTypeOfSymbol(keyProperty)) + props, lazySource := c.getPropertiesOfTypeLazily(source) + for _, p := range props { + if p.Flags()&ast.SymbolFlagsMethod != 0 && p.Flags()&(ast.SymbolFlagsProperty|ast.SymbolFlagsVariable|ast.SymbolFlagsGetAccessor|ast.SymbolFlagsEnumMember) == 0 { + continue + } + if lazySource != nil { + if lazySource.objectFlags&ObjectFlagsMapped == 0 { + if links := c.valueSymbolLinks.TryGet(p); links != nil && links.resolvedType != nil && !isUnitType(links.resolvedType) && !c.couldContainTypeVariables(links.resolvedType) { + continue + } + } + p = c.getPropertyOfType(lazySource, p.Name()) + } + if pt := c.getTypeOfSymbol(p); isUnitType(pt) { + keyProperty = p + keyPropertyType = c.getRegularTypeOfLiteralType(pt) + break + } + } } for _, target := range types { if source != target { @@ -26760,8 +29303,23 @@ func (c *Checker) removeSubtypes(types []*Type, hasObjectTypes bool) []*Type { } count++ if keyProperty != nil && target.flags&(TypeFlagsObject|TypeFlagsIntersection|TypeFlagsInstantiableNonPrimitive) != 0 { - t := c.getTypeOfPropertyOfType(target, keyProperty.Name()) - if t != nil && isUnitType(t) && c.getRegularTypeOfLiteralType(t) != keyPropertyType { + keyName := keyProperty.Name() + if keyName != lastKeyName { + lastKeyName = keyName + if keyTypes == nil { + keyTypes = make(map[*Type]*Type, len(types)) + } else { + clear(keyTypes) + } + } + t, ok := keyTypes[target] + if !ok { + if raw := c.getTypeOfPropertyOfType(target, keyName); raw != nil && isUnitType(raw) { + t = c.getRegularTypeOfLiteralType(raw) + } + keyTypes[target] = t + } + if t != nil && t != keyPropertyType { continue } } @@ -26790,7 +29348,40 @@ func (c *Checker) intersectTypes(type1 *Type, type2 *Type) *Type { case type2 == nil: return type1 } - return c.getIntersectionType([]*Type{type1, type2}) + pair := c.getTemporaryTypeArguments(2) + pair[0] = type1 + pair[1] = type2 + res := c.getIntersectionType(pair) + c.releaseTemporaryTypeArguments(pair) + return res +} + +func (c *Checker) everyTypeIsPrimitiveOrObjectOrEmpty(t *Type) bool { + if t.flags&TypeFlagsUnion != 0 { + for _, sub := range t.Types() { + if !c.isPrimitiveOrObjectOrEmptyType(sub) { + return false + } + } + return true + } + return c.isPrimitiveOrObjectOrEmptyType(t) +} + +func (c *Checker) someTypeIsStrictSubtypeOf(t *Type, target *Type) bool { + if t.flags&TypeFlagsUnion != 0 { + for _, sub := range t.Types() { + if c.isTypeStrictSubtypeOf(sub, target) { + return true + } + } + return false + } + return c.isTypeStrictSubtypeOf(t, target) +} + +func (c *Checker) someContainsMissingType(types []*Type) bool { + return slices.ContainsFunc(types, c.containsMissingType) } type IntersectionFlags uint32 @@ -26901,15 +29492,13 @@ func (c *Checker) getIntersectionTypeEx(types []*Type, flags IntersectionFlags, // We have an intersection T & P or P & T, where T is a type variable and P is a primitive type, the object type, or {}. constraint := c.getBaseConstraintOfType(typeVariable) // Check that T's constraint is similarly composed of primitive types, the object type, or {}. - if constraint != nil && everyType(constraint, c.isPrimitiveOrObjectOrEmptyType) { + if constraint != nil && c.everyTypeIsPrimitiveOrObjectOrEmpty(constraint) { // If T's constraint is a subtype of P, simply return T. For example, given `T extends "a" | "b"`, // the intersection `T & string` reduces to just T. if c.isTypeStrictSubtypeOf(constraint, primitiveType) { return typeVariable } - if !(constraint.flags&TypeFlagsUnion != 0 && someType(constraint, func(n *Type) bool { - return c.isTypeStrictSubtypeOf(n, primitiveType) - })) { + if !(constraint.flags&TypeFlagsUnion != 0 && c.someTypeIsStrictSubtypeOf(constraint, primitiveType)) { // No constituent of T's constraint is a subtype of P. If P is also not a subtype of T's constraint, // then the constraint and P are unrelated, and the intersection reduces to never. For example, given // `T extends "a" | "b"`, the intersection `T & number` reduces to never. @@ -26926,7 +29515,7 @@ func (c *Checker) getIntersectionTypeEx(types []*Type, flags IntersectionFlags, } } key := getIntersectionKey(typeSet, flags, alias) - result := c.intersectionTypes[key] + result := c.intersectionTypes.get(key) if result == nil { if includes&TypeFlagsUnion != 0 { var reduced bool @@ -26939,25 +29528,33 @@ func (c *Checker) getIntersectionTypeEx(types []*Type, flags IntersectionFlags, result = c.getIntersectionTypeEx(typeSet, flags, alias) case core.Every(typeSet, isUnionWithUndefined): containedUndefinedType := c.undefinedType - if core.Some(typeSet, c.containsMissingType) { + if c.someContainsMissingType(typeSet) { containedUndefinedType = c.missingType } c.filterTypes(typeSet, isNotUndefinedType) - result = c.getUnionTypeEx([]*Type{c.getIntersectionTypeEx(typeSet, flags, nil /*alias*/), containedUndefinedType}, UnionReductionLiteral, alias, nil /*origin*/) + pair := c.getTemporaryTypeArguments(2) + pair[0] = c.getIntersectionTypeEx(typeSet, flags, nil /*alias*/) + pair[1] = containedUndefinedType + result = c.getUnionTypeEx(pair, UnionReductionLiteral, alias, nil /*origin*/) + c.releaseTemporaryTypeArguments(pair) case core.Every(typeSet, isUnionWithNull): c.filterTypes(typeSet, isNotNullType) - result = c.getUnionTypeEx([]*Type{c.getIntersectionTypeEx(typeSet, flags, nil /*alias*/), c.nullType}, UnionReductionLiteral, alias, nil /*origin*/) + pair := c.getTemporaryTypeArguments(2) + pair[0] = c.getIntersectionTypeEx(typeSet, flags, nil /*alias*/) + pair[1] = c.nullType + result = c.getUnionTypeEx(pair, UnionReductionLiteral, alias, nil /*origin*/) + c.releaseTemporaryTypeArguments(pair) case len(typeSet) >= 3 && len(types) > 2: // When we have three or more constituents, more than two inputs (to head off infinite reexpansion), some of which are unions, we employ a "divide and conquer" strategy // where A & B & C & D is processed as (A & B) & (C & D). Since intersections of unions often produce far smaller // unions of intersections than the full cartesian product (due to some intersections becoming `never`), this can // dramatically reduce the overall work. middle := len(typeSet) / 2 - result = c.getIntersectionTypeEx([]*Type{ - c.getIntersectionTypeEx(typeSet[:middle], flags, nil /*alias*/), - c.getIntersectionTypeEx(typeSet[middle:], flags, nil /*alias*/), - }, - flags, alias) + pair := c.getTemporaryTypeArguments(2) + pair[0] = c.getIntersectionTypeEx(typeSet[:middle], flags, nil /*alias*/) + pair[1] = c.getIntersectionTypeEx(typeSet[middle:], flags, nil /*alias*/) + result = c.getIntersectionTypeEx(pair, flags, alias) + c.releaseTemporaryTypeArguments(pair) default: // We are attempting to construct a type of the form X & (A | B) & (C | D). Transform this into a type of // the form X & A & C | X & A & D | X & B & C | X & B & D. If the estimated size of the resulting union type @@ -26965,7 +29562,7 @@ func (c *Checker) getIntersectionTypeEx(types []*Type, flags IntersectionFlags, if !c.checkCrossProductUnion(typeSet) { return c.errorType } - constituents := c.getCrossProductIntersections(typeSet, flags) + constituents, constituentsBuf := c.getCrossProductIntersections(typeSet, flags) // We attach a denormalized origin type when at least one constituent of the cross-product union is an // intersection (i.e. when the intersection didn't just reduce one or more unions to smaller unions) and // the denormalized origin has fewer constituents than the union itself. @@ -26974,12 +29571,13 @@ func (c *Checker) getIntersectionTypeEx(types []*Type, flags IntersectionFlags, origin = c.newIntersectionType(ObjectFlagsNone, typeSet) } result = c.getUnionTypeEx(constituents, UnionReductionLiteral, alias, origin) + c.releaseTemporaryTypeArguments(constituentsBuf) } } else { result = c.newIntersectionType(objectFlags|c.getPropagatingFlagsOfTypes(types /*excludeKinds*/, TypeFlagsNullable), typeSet) result.alias = alias } - c.intersectionTypes[key] = result + c.intersectionTypes.set(c, key, result) } return result } @@ -27189,11 +29787,14 @@ func (c *Checker) unionContainsType(union *Type, t *Type, matchSymbol bool) bool return primitive != nil && containsType(types, primitive) } -func (c *Checker) getCrossProductIntersections(types []*Type, flags IntersectionFlags) []*Type { +func (c *Checker) getCrossProductIntersections(types []*Type, flags IntersectionFlags) ([]*Type, []*Type) { count := c.getCrossProductUnionSize(types) - var intersections []*Type + buf := c.getTemporaryTypeArguments(count) + intersections := buf[:0] + constituents := c.getTemporaryTypeArguments(len(types)) + copy(constituents, types) + defer c.releaseTemporaryTypeArguments(constituents) for i := range count { - constituents := slices.Clone(types) n := i for j := len(types) - 1; j >= 0; j-- { if types[j].flags&TypeFlagsUnion != 0 { @@ -27208,7 +29809,7 @@ func (c *Checker) getCrossProductIntersections(types []*Type, flags Intersection intersections = append(intersections, t) } } - return intersections + return intersections, buf } func getConstituentCount(t *Type) int { @@ -27247,7 +29848,28 @@ func (c *Checker) isEmptyResolvedType(t *StructuredType) bool { func (c *Checker) isEmptyObjectType(t *Type) bool { switch { case t.flags&TypeFlagsObject != 0: - return !c.isGenericMappedType(t) && c.isEmptyResolvedType(c.resolveStructuredTypeMembers(t)) + if c.isGenericMappedType(t) { + return false + } + if t.objectFlags&(ObjectFlagsMembersResolved|ObjectFlagsAnonymous) == ObjectFlagsAnonymous && (t.symbol == nil || t.symbol.Flags()&ast.SymbolFlagsValueModule == 0) { + if d := t.AsObjectType(); d.target != nil { + targetResolved := c.resolveStructuredTypeMembers(d.target) + if targetMembers := targetResolved.getMembersIndex(); targetMembers == nil || targetMembers.fallback == nil { + return c.isEmptyResolvedType(targetResolved) + } + } + } + if lazy := c.getLazyMappedTable(t); lazy != nil && !lazy.resolving { + lazy.resolving = true + modProps, _ := c.getPropertiesOfTypeLazily(lazy.modifiersType) + lazy.resolving = false + for _, p := range modProps { + if !isLateBoundName(p.Name()) && !strings.HasPrefix(p.Name(), "\xFE#") && p.Flags()&ast.SymbolFlagsPrototype == 0 && getDeclarationModifierFlagsFromSymbol(p)&ast.ModifierFlagsNonPublicAccessibilityModifier == 0 { + return false + } + } + } + return c.isEmptyResolvedType(c.resolveStructuredTypeMembers(t)) case t.flags&TypeFlagsNonPrimitive != 0: return true case t.flags&TypeFlagsUnion != 0: @@ -27320,22 +29942,51 @@ func everyContainedType(t *Type, f func(*Type) bool) bool { func (c *Checker) filterType(t *Type, f func(*Type) bool) *Type { if t.flags&TypeFlagsUnion != 0 { types := t.Types() - filtered := core.Filter(types, f) - if core.Same(types, filtered) { + firstRemoved := -1 + for i, u := range types { + if !f(u) { + firstRemoved = i + break + } + } + if firstRemoved < 0 { return t } + var filteredBuf [8]*Type + var tempBuf []*Type + var filtered []*Type + if len(types) <= len(filteredBuf) { + filtered = filteredBuf[:firstRemoved] + } else { + tempBuf = c.getTemporaryTypeArguments(len(types)) + defer c.releaseTemporaryTypeArguments(tempBuf) + filtered = tempBuf[:firstRemoved] + } + copy(filtered, types[:firstRemoved]) + for _, u := range types[firstRemoved+1:] { + if f(u) { + filtered = append(filtered, u) + } + } origin := t.AsUnionType().origin var newOrigin *Type if origin != nil && origin.flags&TypeFlagsUnion != 0 { - // If the origin type is a (denormalized) union type, filter its non-union constituents. If that ends - // up removing a smaller number of types than in the normalized constituent set (meaning some of the - // filtered types are within nested unions in the origin), then we can't construct a new origin type. - // Otherwise, if we have exactly one type left in the origin set, return that as the filtered type. - // Otherwise, construct a new filtered origin type. originTypes := origin.Types() - originFiltered := core.Filter(originTypes, func(u *Type) bool { - return u.flags&TypeFlagsUnion != 0 || f(u) - }) + var originBuf [8]*Type + var originTempBuf []*Type + var originFiltered []*Type + if len(originTypes) <= len(originBuf) { + originFiltered = originBuf[:0] + } else { + originTempBuf = c.getTemporaryTypeArguments(len(originTypes)) + defer c.releaseTemporaryTypeArguments(originTempBuf) + originFiltered = originTempBuf[:0] + } + for _, u := range originTypes { + if u.flags&TypeFlagsUnion != 0 || f(u) { + originFiltered = append(originFiltered, u) + } + } if len(originTypes)-len(originFiltered) == len(types)-len(filtered) { if len(originFiltered) == 1 { return originFiltered[0] @@ -27343,8 +29994,6 @@ func (c *Checker) filterType(t *Type, f func(*Type) bool) *Type { newOrigin = c.newUnionType(ObjectFlagsNone, originFiltered) } } - // filtering could remove intersections so `ContainsIntersections` might be forwarded "incorrectly" - // it is purely an optimization hint so there is no harm in accidentally forwarding it return c.getUnionTypeFromSortedList(filtered, t.AsUnionType().objectFlags&(ObjectFlagsPrimitiveUnion|ObjectFlagsContainsIntersections), nil /*alias*/, newOrigin) } if t.flags&TypeFlagsNever != 0 || f(t) { @@ -27364,10 +30013,16 @@ func (c *Checker) removeType(t *Type, targetType *Type) *Type { return c.filterType(t, func(t *Type) bool { return t != targetType }) } types := t.Types() - if i, ok := slices.BinarySearchFunc(types, targetType, CompareTypes); ok { - if len(types) == 2 { - return types[1-i] + if len(types) == 2 { + if types[0] == targetType { + return types[1] + } + if types[1] == targetType { + return types[0] } + return t + } + if i, ok := slices.BinarySearchFunc(types, targetType, CompareTypes); ok { // Remove the target type from the slice. set := c.getTemporaryTypeSet() defer c.releaseTemporaryTypeSet(set) @@ -27380,6 +30035,28 @@ func (c *Checker) removeType(t *Type, targetType *Type) *Type { } func containsType(types []*Type, t *Type) bool { + n := len(types) + if n == 0 { + return false + } + targetFlags := getSortOrderFlags(t) + if targetFlags < getSortOrderFlags(types[0]) || targetFlags > getSortOrderFlags(types[n-1]) { + return false + } + if t.flags < TypeFlagsStringLiteral { + for _, elem := range types { + if elem == t { + return true + } + if int(elem.flags) > targetFlags { + return false + } + } + return false + } + if n <= 24 { + return slices.Contains(types, t) + } _, ok := slices.BinarySearchFunc(types, t, CompareTypes) return ok } @@ -27489,7 +30166,7 @@ func (c *Checker) getLiteralTypeFromProperties(t *Type, include TypeFlags, inclu if includeOrigin && t.objectFlags&(ObjectFlagsClassOrInterface|ObjectFlagsReference) != 0 || t.alias != nil { origin = c.newIndexType(t, IndexFlagsNone) } - props := c.getLayoutPropertiesOfType(t) + props, _ := c.getPropertiesOfTypeLazily(t) indexInfos := c.getIndexInfosOfType(t) types := make([]*Type, 0, len(props)+len(indexInfos)) for _, prop := range props { @@ -27511,7 +30188,7 @@ func (c *Checker) getLiteralTypeFromProperties(t *Type, include TypeFlags, inclu func (c *Checker) getLiteralTypeFromProperty(prop *ast.Symbol, include TypeFlags, includeNonPublic bool) *Type { if includeNonPublic || getDeclarationModifierFlagsFromSymbol(prop)&ast.ModifierFlagsNonPublicAccessibilityModifier == 0 { - t := c.valueSymbolLinks.Get(c.getLateBoundSymbol(prop)).getNameType() + t := c.valueSymbolLinks.TryGet(c.getLateBoundSymbol(prop)).getNameType() if t == nil { if prop.Name() == ast.InternalSymbolNameDefault { t = c.getStringLiteralType("default") @@ -27726,11 +30403,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, accessFlags, 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.set(c, key, t) } return t } @@ -27739,29 +30416,65 @@ func (c *Checker) getIndexedAccessTypeOrUndefined(objectType *Type, indexType *T // skipping straight to getPropertyTypeForIndexType gives errors with 'boolean' instead of 'true'. apparentObjectType := c.getReducedApparentType(objectType) if indexType.flags&TypeFlagsUnion != 0 && indexType.flags&TypeFlagsBoolean == 0 { - var propTypes []*Type - wasMissingProp := false - for _, t := range indexType.Types() { - propType := c.getPropertyTypeForIndexType(objectType, apparentObjectType, t, indexType, accessNode, accessFlags|core.IfElse(wasMissingProp, AccessFlagsSuppressNoImplicitAnyError, 0)) - if propType != nil { - propTypes = append(propTypes, propType) - } else if accessNode == nil { - // If there's no error node, we can immediately stop, since error reporting is off - return nil - } else { - // Otherwise we set a flag and return at the end of the loop so we still mark all errors - wasMissingProp = true + if accessNode == nil && alias == nil { + memoKey := indexedAccessUnionKey{objectId: objectType.id, indexId: indexType.id, accessFlags: accessFlags} + if cached, ok := c.indexedAccessUnionMemo[memoKey]; ok { + return cached + } + typeCount := c.TypeCount + instCount := c.instantiationCount + totalInstCount := c.TotalInstantiationCount + impure := c.unionImpureCount + diagCount := c.diagnosticCount + result := c.getIndexedAccessTypeByUnionWorker(objectType, apparentObjectType, indexType, accessFlags, nil, nil) + created := c.TypeCount != typeCount && !(c.TypeCount == typeCount+1 && result != nil && result.id == TypeId(c.TypeCount)) + instantiated := c.instantiationCount != instCount || c.TotalInstantiationCount != totalInstCount + wasImpure := c.unionImpureCount != impure + reported := c.diagnosticCount != diagCount + if !created && !instantiated && !wasImpure && !reported && result != c.errorType { + if c.indexedAccessUnionMemo == nil { + c.indexedAccessUnionMemo = make(map[indexedAccessUnionKey]*Type) + } + c.indexedAccessUnionMemo[memoKey] = result } + return result } - if wasMissingProp { + return c.getIndexedAccessTypeByUnionWorker(objectType, apparentObjectType, indexType, accessFlags, accessNode, alias) + } + return c.getPropertyTypeForIndexType(objectType, apparentObjectType, indexType, indexType, accessNode, accessFlags|AccessFlagsCacheSymbol|AccessFlagsReportDeprecated) +} + +type indexedAccessUnionKey struct { + objectId TypeId + indexId TypeId + accessFlags AccessFlags +} + +func (c *Checker) getIndexedAccessTypeByUnionWorker(objectType *Type, apparentObjectType *Type, indexType *Type, accessFlags AccessFlags, accessNode *ast.Node, alias *TypeAlias) *Type { + propTypes := c.getTemporaryTypeArguments(0) + defer func() { + c.releaseTemporaryTypeArguments(propTypes) + }() + wasMissingProp := false + for _, t := range indexType.Types() { + propType := c.getPropertyTypeForIndexType(objectType, apparentObjectType, t, indexType, accessNode, accessFlags|core.IfElse(wasMissingProp, AccessFlagsSuppressNoImplicitAnyError, 0)) + if propType != nil { + propTypes = append(propTypes, propType) + } else if accessNode == nil { + // If there's no error node, we can immediately stop, since error reporting is off return nil + } else { + // Otherwise we set a flag and return at the end of the loop so we still mark all errors + wasMissingProp = true } - if accessFlags&AccessFlagsWriting != 0 { - return c.getIntersectionTypeEx(propTypes, IntersectionFlagsNone, alias) - } - return c.getUnionTypeEx(propTypes, UnionReductionLiteral, alias, nil) } - return c.getPropertyTypeForIndexType(objectType, apparentObjectType, indexType, indexType, accessNode, accessFlags|AccessFlagsCacheSymbol|AccessFlagsReportDeprecated) + if wasMissingProp { + return nil + } + if accessFlags&AccessFlagsWriting != 0 { + return c.getIntersectionTypeEx(propTypes, IntersectionFlagsNone, alias) + } + return c.getUnionTypeEx(propTypes, UnionReductionLiteral, alias, nil) } func (c *Checker) getPropertyTypeForIndexType(originalObjectType *Type, objectType *Type, indexType *Type, fullIndexType *Type, accessNode *ast.Node, accessFlags AccessFlags) *Type { @@ -27902,7 +30615,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().getPropertiesSlice(), func(prop *ast.Symbol) *Type { return c.getTypeOfSymbol(prop) }) return c.getUnionType(append(types, c.undefinedType)) @@ -28129,7 +30842,7 @@ func (c *Checker) getPropertyNameFromIndex(indexType *Type, accessNode *ast.Node } func (c *Checker) isStringIndexSignatureOnlyTypeWorker(t *Type) bool { - return t.flags&TypeFlagsObject != 0 && !c.isGenericMappedType(t) && len(c.getLayoutPropertiesOfType(t)) == 0 && len(c.getIndexInfosOfType(t)) == 1 && c.getIndexInfoOfType(t, c.stringType) != nil || + return t.flags&TypeFlagsObject != 0 && !c.isGenericMappedType(t) && !c.hasPropertiesOfStructuredType(t) && len(c.getIndexInfosOfType(t)) == 1 && c.getIndexInfoOfType(t, c.stringType) != nil || t.flags&TypeFlagsUnionOrIntersection != 0 && core.Every(t.Types(), c.isStringIndexSignatureOnlyType) } @@ -28373,16 +31086,48 @@ func (c *Checker) maybeTypeOfKind(t *Type, kind TypeFlags) bool { if t.flags&kind != 0 { return true } - if t.flags&TypeFlagsUnionOrIntersection != 0 { - for _, t := range t.Types() { - if c.maybeTypeOfKind(t, kind) { - return true - } + if t.flags&TypeFlagsUnionOrIntersection == 0 { + return false + } + if kind&^TypeFlagsNotPrimitiveUnion == 0 && t.objectFlags&ObjectFlagsPrimitiveUnion != 0 { + return false + } + types := t.Types() + if len(types) >= 8 { + return c.constituentFlagsOfUnionOrIntersection(t)&kind != 0 + } + for _, ct := range types { + if ct.flags&kind != 0 || ct.flags&TypeFlagsUnionOrIntersection != 0 && c.maybeTypeOfKind(ct, kind) { + return true } } return false } +func (c *Checker) constituentFlagsOfUnionOrIntersection(t *Type) TypeFlags { + types := t.Types() + large := len(types) >= 8 + if large && c.unionConstituentFlags != nil { + if flags, ok := c.unionConstituentFlags[t]; ok { + return flags + } + } + var flags TypeFlags + for _, ct := range types { + flags |= ct.flags + if ct.flags&TypeFlagsUnionOrIntersection != 0 { + flags |= c.constituentFlagsOfUnionOrIntersection(ct) + } + } + if large { + if c.unionConstituentFlags == nil { + c.unionConstituentFlags = make(map[*Type]TypeFlags) + } + c.unionConstituentFlags[t] = flags + } + return flags +} + func (c *Checker) maybeTypeOfKindConsideringBaseConstraint(t *Type, kind TypeFlags) bool { if c.maybeTypeOfKind(t, kind) { return true @@ -28490,9 +31235,13 @@ func (c *Checker) markPropertyAsReferenced(prop *ast.Symbol, nodeForCheckWriteOn } target := prop if prop.CheckFlags()&ast.CheckFlagsInstantiated != 0 { - target = c.valueSymbolLinks.Get(prop).target + if links := c.valueSymbolLinks.TryGet(prop); links != nil { + target = links.target + } else { + target = nil + } } - c.symbolReferenceLinks.Get(target).referenceKinds |= ast.SymbolFlagsAll + target.AddReference(ast.SymbolFlagsAll) } func (c *Checker) expandSignatureParametersWithTupleMembers(signature *Signature, restType *TypeReference, restIndex int, restSymbol *ast.Symbol) []*ast.Symbol { @@ -28625,7 +31374,7 @@ func (c *Checker) containsUndefinedType(t *Type) bool { } func (c *Checker) typeHasCallOrConstructSignatures(t *Type) bool { - return t.flags&TypeFlagsStructuredType != 0 && len(c.resolveStructuredTypeMembers(t).getSignatures()) != 0 + return t.flags&TypeFlagsStructuredType != 0 && (len(c.getSignaturesOfStructuredType(t, SignatureKindCall)) != 0 || len(c.getSignaturesOfStructuredType(t, SignatureKindConstruct)) != 0) } func (c *Checker) getNormalizedType(t *Type, writing bool) *Type { @@ -28790,22 +31539,43 @@ func (c *Checker) getSimplifiedConditionalType(t *Type, writing bool) *Type { } func (c *Checker) getSimplifiedConditionalTypeWorker(t *Type, writing bool) *Type { - checkType := t.AsConditionalType().checkType - extendsType := t.AsConditionalType().extendsType - trueType := c.getTrueTypeFromConditionalType(t) - falseType := c.getFalseTypeFromConditionalType(t) + d := t.AsConditionalType() + checkType := d.checkType + actualCheck := c.getActualTypeVariable(checkType) + rawTrue := c.getTypeFromTypeNode(d.root.node.TrueType) + if rawTrue.flags&TypeFlagsObject != 0 && rawTrue.objectFlags&(ObjectFlagsAnonymous|ObjectFlagsClassOrInterface|ObjectFlagsMapped) != 0 && rawTrue.objectFlags&ObjectFlagsReverseMapped == 0 && actualCheck.flags&TypeFlagsObject == 0 { + return t + } + extendsType := d.extendsType + var falseType *Type + if rawFalse := c.getTypeFromTypeNode(d.root.node.FalseType); rawFalse.flags&TypeFlagsNever != 0 { + falseType = c.neverType + } else { + falseType = c.getFalseTypeFromConditionalType(t) + } // Simplifications for types of the form `T extends U ? T : never` and `T extends U ? never : T`. - if falseType.flags&TypeFlagsNever != 0 && c.getActualTypeVariable(trueType) == c.getActualTypeVariable(checkType) { - if checkType.flags&TypeFlagsAny != 0 || c.isTypeAssignableTo(c.getRestrictiveInstantiation(checkType), c.getRestrictiveInstantiation(extendsType)) { - return c.getSimplifiedType(trueType, writing) - } else if c.isIntersectionEmpty(checkType, extendsType) { - return c.neverType + if falseType.flags&TypeFlagsNever != 0 { + trueType := c.getTrueTypeFromConditionalType(t) + if c.getActualTypeVariable(trueType) == actualCheck { + if checkType.flags&TypeFlagsAny != 0 || c.isTypeAssignableTo(c.getRestrictiveInstantiation(checkType), c.getRestrictiveInstantiation(extendsType)) { + return c.getSimplifiedType(trueType, writing) + } else if c.isIntersectionEmpty(checkType, extendsType) { + return c.neverType + } + } + } else if c.getActualTypeVariable(falseType) == actualCheck { + var trueType *Type + if rawTrue.flags&TypeFlagsNever != 0 { + trueType = c.neverType + } else { + trueType = c.getTrueTypeFromConditionalType(t) } - } else if trueType.flags&TypeFlagsNever != 0 && c.getActualTypeVariable(falseType) == c.getActualTypeVariable(checkType) { - if checkType.flags&TypeFlagsAny == 0 && c.isTypeAssignableTo(c.getRestrictiveInstantiation(checkType), c.getRestrictiveInstantiation(extendsType)) { - return c.neverType - } else if checkType.flags&TypeFlagsAny != 0 || c.isIntersectionEmpty(checkType, extendsType) { - return c.getSimplifiedType(falseType, writing) + if trueType.flags&TypeFlagsNever != 0 { + if checkType.flags&TypeFlagsAny == 0 && c.isTypeAssignableTo(c.getRestrictiveInstantiation(checkType), c.getRestrictiveInstantiation(extendsType)) { + return c.neverType + } else if checkType.flags&TypeFlagsAny != 0 || c.isIntersectionEmpty(checkType, extendsType) { + return c.getSimplifiedType(falseType, writing) + } } } return t @@ -28844,15 +31614,20 @@ func (c *Checker) getNormalizedUnionOrIntersectionType(t *Type, writing bool) *T } func (c *Checker) shouldNormalizeIntersection(t *Type) bool { + if t.cacheFlags&typeCacheShouldNormalizeComputed != 0 { + return t.cacheFlags&typeCacheShouldNormalize != 0 + } hasInstantiable := false hasNullableOrEmpty := false - for _, t := range t.Types() { - hasInstantiable = hasInstantiable || t.flags&TypeFlagsInstantiable != 0 - hasNullableOrEmpty = hasNullableOrEmpty || t.flags&TypeFlagsNullable != 0 || c.IsEmptyAnonymousObjectType(t) + for _, u := range t.Types() { + hasInstantiable = hasInstantiable || u.flags&TypeFlagsInstantiable != 0 + hasNullableOrEmpty = hasNullableOrEmpty || u.flags&TypeFlagsNullable != 0 || c.IsEmptyAnonymousObjectType(u) if hasInstantiable && hasNullableOrEmpty { + t.cacheFlags |= typeCacheShouldNormalizeComputed | typeCacheShouldNormalize return true } } + t.cacheFlags |= typeCacheShouldNormalizeComputed return false } @@ -28871,15 +31646,22 @@ func (c *Checker) getNormalizedTupleType(t *Type, writing bool) *Type { } func (c *Checker) getSingleBaseForNonAugmentingSubtype(t *Type) *Type { - if t.objectFlags&ObjectFlagsReference == 0 || t.Target().objectFlags&ObjectFlagsClassOrInterface == 0 { + if t.objectFlags&ObjectFlagsReference == 0 || t.objectFlags&(ObjectFlagsIdenticalBaseTypeCalculated|ObjectFlagsIdenticalBaseTypeExists) == ObjectFlagsIdenticalBaseTypeCalculated { return nil } + return c.getSingleBaseForNonAugmentingSubtypeSlow(t) +} + +func (c *Checker) getSingleBaseForNonAugmentingSubtypeSlow(t *Type) *Type { key := CachedTypeKey{kind: CachedTypeKindEquivalentBaseType, typeId: t.id} - if t.objectFlags&ObjectFlagsIdenticalBaseTypeCalculated != 0 { + if t.objectFlags&ObjectFlagsIdenticalBaseTypeExists != 0 { return c.cachedTypes[key] } t.objectFlags |= ObjectFlagsIdenticalBaseTypeCalculated target := t.Target() + if target.objectFlags&ObjectFlagsClassOrInterface == 0 { + return nil + } if target.objectFlags&ObjectFlagsClass != 0 { baseTypeNode := getBaseTypeNodeOfClass(target) // A base type expression may circularly reference the class itself (e.g. as an argument to function call), so we only @@ -28906,7 +31688,10 @@ 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 + if instantiatedBase != nil { + t.objectFlags |= ObjectFlagsIdenticalBaseTypeExists + c.cachedTypes[key] = instantiatedBase + } return instantiatedBase } @@ -29935,6 +32720,42 @@ const ( maxTemplateLiteralTypeSpans = 100_000 ) +type templateLiteralScratch struct { + buf []byte + newTypes []*Type + newTexts []string +} + +func (c *Checker) acquireTemplateScratch() *templateLiteralScratch { + if n := len(c.templateScratchPool); n > 0 { + s := c.templateScratchPool[n-1] + c.templateScratchPool = c.templateScratchPool[:n-1] + return s + } + return &templateLiteralScratch{} +} + +func (c *Checker) releaseTemplateScratch(s *templateLiteralScratch, buf []byte, newTypes []*Type, newTexts []string) { + if cap(buf) <= 65536 { + s.buf = buf[:0] + } else { + s.buf = nil + } + clear(newTypes) + if cap(newTypes) <= 4096 { + s.newTypes = newTypes[:0] + } else { + s.newTypes = nil + } + clear(newTexts) + if cap(newTexts) <= 4096 { + s.newTexts = newTexts[:0] + } else { + s.newTexts = nil + } + c.templateScratchPool = append(c.templateScratchPool, s) +} + func (c *Checker) getTemplateLiteralType(texts []string, types []*Type) *Type { unionIndex := core.FindIndex(types, func(t *Type) bool { return t.flags&(TypeFlagsNever|TypeFlagsUnion) != 0 @@ -29950,10 +32771,43 @@ func (c *Checker) getTemplateLiteralType(texts []string, types []*Type) *Type { if slices.Contains(types, c.wildcardType) { return c.wildcardType } - var newTypes []*Type - var newTexts []string - var sb strings.Builder - sb.WriteString(texts[0]) + scratch := c.acquireTemplateScratch() + buf := scratch.buf[:0] + newTypes := scratch.newTypes[:0] + newTexts := scratch.newTexts[:0] + curStr := texts[0] + usingBuf := false + appendStr := func(s string) { + if len(s) == 0 { + return + } + if !usingBuf { + if len(curStr) == 0 { + curStr = s + return + } + buf = append(buf[:0], curStr...) + buf = append(buf, s...) + usingBuf = true + } else { + buf = append(buf, s...) + } + } + curLen := func() int { + if usingBuf { + return len(buf) + } + return len(curStr) + } + flushStr := func() string { + if !usingBuf { + return stringutil.CombineSurrogatePairs(curStr) + } + if slices.Contains(buf, 0xed) { + return stringutil.CombineSurrogatePairs(string(buf)) + } + return string(buf) + } textLength := 0 // combined length of the segments already moved into newTexts tooLarge := false var addSpans func([]string, []*Type) bool @@ -29961,24 +32815,24 @@ func (c *Checker) getTemplateLiteralType(texts []string, types []*Type) *Type { for i, t := range types { switch { case t.flags&(TypeFlagsLiteral|TypeFlagsNull|TypeFlagsUndefined) != 0: - sb.WriteString(c.getTemplateStringForType(t)) - sb.WriteString(texts[i+1]) + appendStr(c.getTemplateStringForType(t)) + appendStr(texts[i+1]) case t.flags&TypeFlagsTemplateLiteral != 0: - sb.WriteString(t.AsTemplateLiteralType().texts[0]) + appendStr(t.AsTemplateLiteralType().texts[0]) if !addSpans(t.AsTemplateLiteralType().texts, t.AsTemplateLiteralType().types) { return false } - sb.WriteString(texts[i+1]) + appendStr(texts[i+1]) case c.isGenericIndexType(t) || c.isPatternLiteralPlaceholderType(t): newTypes = append(newTypes, t) - newTexts = append(newTexts, stringutil.CombineSurrogatePairs(sb.String())) - textLength += sb.Len() - sb.Reset() - sb.WriteString(texts[i+1]) + newTexts = append(newTexts, flushStr()) + textLength += curLen() + curStr = texts[i+1] + usingBuf = false default: return false } - if textLength+sb.Len() > maxTemplateLiteralTypeLength || len(newTypes) > maxTemplateLiteralTypeSpans { + if textLength+curLen() > maxTemplateLiteralTypeLength || len(newTypes) > maxTemplateLiteralTypeSpans { tooLarge = true return false } @@ -29986,6 +32840,7 @@ func (c *Checker) getTemplateLiteralType(texts []string, types []*Type) *Type { return true } if !addSpans(texts, types) { + c.releaseTemplateScratch(scratch, buf, newTypes, newTexts) if tooLarge { c.error(c.currentNode, diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite) return c.errorType @@ -29993,24 +32848,45 @@ func (c *Checker) getTemplateLiteralType(texts []string, types []*Type) *Type { return c.stringType } if len(newTypes) == 0 { - return c.getStringLiteralType(stringutil.CombineSurrogatePairs(sb.String())) + if !usingBuf { + s := stringutil.CombineSurrogatePairs(curStr) + c.releaseTemplateScratch(scratch, buf, newTypes, newTexts) + return c.getStringLiteralType(s) + } + hasSurrogate := slices.Contains(buf, 0xed) + if !hasSurrogate { + if t := c.stringLiteralTypes[string(buf)]; t != nil { + c.releaseTemplateScratch(scratch, buf, newTypes, newTexts) + return t + } + s := string(buf) + c.releaseTemplateScratch(scratch, buf, newTypes, newTexts) + return c.getStringLiteralType(s) + } + s := stringutil.CombineSurrogatePairs(string(buf)) + c.releaseTemplateScratch(scratch, buf, newTypes, newTexts) + return c.getStringLiteralType(s) } - newTexts = append(newTexts, stringutil.CombineSurrogatePairs(sb.String())) + newTexts = append(newTexts, flushStr()) if core.Every(newTexts, func(t string) bool { return t == "" }) { if core.Every(newTypes, func(t *Type) bool { return t.flags&TypeFlagsString != 0 }) { + c.releaseTemplateScratch(scratch, buf, newTypes, newTexts) return c.stringType } - // Normalize `${Mapping}` into Mapping + // Normalize `${Mapping}` into `Mapping` if len(newTypes) == 1 && c.isPatternLiteralType(newTypes[0]) { - return newTypes[0] + res := newTypes[0] + c.releaseTemplateScratch(scratch, buf, newTypes, newTexts) + return res } } - key := getTemplateTypeKey(newTexts, newTypes) - t := c.templateLiteralTypes[key] + key := c.getTemplateTypeKey(newTexts, newTypes) + t := c.templateLiteralTypes.get(key) if t == nil { - t = c.newTemplateLiteralType(newTexts, newTypes) - c.templateLiteralTypes[key] = t + t = c.newTemplateLiteralType(slices.Clone(newTexts), slices.Clone(newTypes)) + c.templateLiteralTypes.setSmall(c, key, t) } + c.releaseTemplateScratch(scratch, buf, newTypes, newTexts) return t } @@ -30588,7 +33464,8 @@ 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.TryGet(callTarget); links != nil && links.resolvedSignature == c.resolvingSignature { + c.resolutionCycleCount++ signature = c.resolvingSignature } else { signature = c.getResolvedSignature(callTarget, nil, CheckModeNormal) @@ -30598,7 +33475,7 @@ func (c *Checker) getContextualTypeForArgumentAtIndex(callTarget *ast.Node, argI } restIndex := len(signature.parameters) - 1 if signatureHasRestParameter(signature) && argIndex >= restIndex { - return c.getIndexedAccessTypeEx(c.getTypeOfSymbol(signature.parameters[restIndex]), c.getNumberLiteralType(jsnum.Number(argIndex-restIndex)), AccessFlagsContextual, nil, nil) + return c.getIndexedAccessTypeEx(c.getTypeOfSignatureParameterSymbol(signature, restIndex), c.getNumberLiteralType(jsnum.Number(argIndex-restIndex)), AccessFlagsContextual, nil, nil) } return c.getTypeAtPosition(signature, argIndex) } @@ -30734,7 +33611,7 @@ func (c *Checker) getContextualTypeForObjectLiteralElement(element *ast.Node, co // in the type. It will just be "__computed", which does not appear in any // SymbolTable. symbol := c.getSymbolOfDeclaration(element) - return c.getTypeOfPropertyOfContextualTypeEx(t, symbol.Name(), c.valueSymbolLinks.Get(symbol).getNameType()) + return c.getTypeOfPropertyOfContextualTypeEx(t, symbol.Name(), c.valueSymbolLinks.TryGet(symbol).getNameType()) } if ast.HasDynamicName(element) { name := ast.GetNameOfDeclaration(element) @@ -30749,14 +33626,36 @@ func (c *Checker) getContextualTypeForObjectLiteralElement(element *ast.Node, co } } if element.Name() != nil { - nameType := c.getLiteralTypeFromPropertyName(element.Name()) - // We avoid calling getApplicableIndexInfo here because it performs potentially expensive intersection reduction. + // We avoid calling getApplicableIndexInfo here because it performs potentially expensive intersection reduction + if t.flags&TypeFlagsNever != 0 { + return t + } + if t.flags&TypeFlagsUnion == 0 { + indexInfos := c.getIndexInfosOfStructuredType(t) + if len(indexInfos) == 0 { + return nil + } + nameType := c.getLiteralTypeFromPropertyName(element.Name()) + indexInfo := c.findApplicableIndexInfo(indexInfos, nameType) + if indexInfo != nil { + return indexInfo.valueType + } + return nil + } + var nameType *Type return c.mapTypeEx(t, func(t *Type) *Type { - indexInfo := c.findApplicableIndexInfo(c.getIndexInfosOfStructuredType(t), nameType) - if indexInfo == nil { + indexInfos := c.getIndexInfosOfStructuredType(t) + if len(indexInfos) == 0 { return nil } - return indexInfo.valueType + if nameType == nil { + nameType = c.getLiteralTypeFromPropertyName(element.Name()) + } + indexInfo := c.findApplicableIndexInfo(indexInfos, nameType) + if indexInfo != nil { + return indexInfo.valueType + } + return nil }, true /*noReductions*/) } } @@ -30967,15 +33866,15 @@ 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.effectsOrDecoratorSignature == nil { + links.effectsOrDecoratorSignature = 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.effectsOrDecoratorSignature = 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 @@ -31000,7 +33899,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.effectsOrDecoratorSignature = 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 @@ -31019,16 +33918,16 @@ 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.effectsOrDecoratorSignature = 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.effectsOrDecoratorSignature = c.newCallSignature(nil, nil, []*ast.Symbol{targetParam, keyParam}, c.getUnionType([]*Type{returnType, c.voidType})) } } } - if links.decoratorSignature == c.anySignature { + if links.effectsOrDecoratorSignature == c.anySignature { return nil } - return links.decoratorSignature + return links.effectsOrDecoratorSignature } func (c *Checker) getESDecoratorCallSignature(decorator *ast.Node) *Signature { @@ -31114,15 +34013,15 @@ 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.effectsOrDecoratorSignature == nil { + links.effectsOrDecoratorSignature = 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.effectsOrDecoratorSignature = c.newESDecoratorCallSignature(targetType, contextType, targetType) case ast.KindMethodDeclaration, ast.KindGetAccessor, ast.KindSetAccessor: if !ast.IsClassLike(node.Parent) { break @@ -31163,7 +34062,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.effectsOrDecoratorSignature = c.newESDecoratorCallSignature(targetType, contextType, targetType) case ast.KindPropertyDeclaration: if !ast.IsClassLike(node.Parent) { break @@ -31200,13 +34099,13 @@ 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.effectsOrDecoratorSignature = c.newESDecoratorCallSignature(targetType, contextType, returnType) } } - if links.decoratorSignature == c.anySignature { + if links.effectsOrDecoratorSignature == c.anySignature { return nil } - return links.decoratorSignature + return links.effectsOrDecoratorSignature } func (c *Checker) newClassDecoratorContextType(classType *Type) *Type { @@ -31358,55 +34257,65 @@ func (c *Checker) getTypeOfPropertyOfContextualType(t *Type, name string) *Type } func (c *Checker) getTypeOfPropertyOfContextualTypeEx(t *Type, name string, nameType *Type) *Type { + if t.flags&TypeFlagsNever != 0 { + return t + } + if t.flags&TypeFlagsUnion == 0 { + return c.getTypeOfPropertyOfContextualTypeWorker(t, name, nameType) + } return c.mapTypeEx(t, func(t *Type) *Type { - if t.flags&TypeFlagsIntersection != 0 { - var types []*Type - var indexInfoCandidates []*Type - ignoreIndexInfos := false - for _, constituentType := range t.Types() { - if constituentType.flags&TypeFlagsObject == 0 { - continue - } - if c.isGenericMappedType(constituentType) && c.getMappedTypeNameTypeKind(constituentType) != MappedTypeNameTypeKindRemapping { - substitutedType := c.getIndexedMappedTypeSubstitutedTypeOfContextualType(constituentType, name, nameType) - types = c.appendContextualPropertyTypeConstituent(types, substitutedType) - continue - } - propertyType := c.getTypeOfConcretePropertyOfContextualType(constituentType, name) - if propertyType == nil { - if !ignoreIndexInfos { - indexInfoCandidates = append(indexInfoCandidates, constituentType) - } - continue - } - ignoreIndexInfos = true - indexInfoCandidates = nil - types = c.appendContextualPropertyTypeConstituent(types, propertyType) - } - for _, candidate := range indexInfoCandidates { - indexInfoType := c.getTypeFromIndexInfosOfContextualType(candidate, name, nameType) - types = c.appendContextualPropertyTypeConstituent(types, indexInfoType) + return c.getTypeOfPropertyOfContextualTypeWorker(t, name, nameType) + }, true /*noReductions*/) +} + +func (c *Checker) getTypeOfPropertyOfContextualTypeWorker(t *Type, name string, nameType *Type) *Type { + if t.flags&TypeFlagsIntersection != 0 { + var types []*Type + var indexInfoCandidates []*Type + ignoreIndexInfos := false + for _, constituentType := range t.Types() { + if constituentType.flags&TypeFlagsObject == 0 { + continue } - if len(types) == 0 { - return nil + if c.isGenericMappedType(constituentType) && c.getMappedTypeNameTypeKind(constituentType) != MappedTypeNameTypeKindRemapping { + substitutedType := c.getIndexedMappedTypeSubstitutedTypeOfContextualType(constituentType, name, nameType) + types = c.appendContextualPropertyTypeConstituent(types, substitutedType) + continue } - if len(types) == 1 { - return types[0] + propertyType := c.getTypeOfConcretePropertyOfContextualType(constituentType, name) + if propertyType == nil { + if !ignoreIndexInfos { + indexInfoCandidates = append(indexInfoCandidates, constituentType) + } + continue } - return c.getIntersectionType(types) + ignoreIndexInfos = true + indexInfoCandidates = nil + types = c.appendContextualPropertyTypeConstituent(types, propertyType) } - if t.flags&TypeFlagsObject == 0 { - return nil + for _, candidate := range indexInfoCandidates { + indexInfoType := c.getTypeFromIndexInfosOfContextualType(candidate, name, nameType) + types = c.appendContextualPropertyTypeConstituent(types, indexInfoType) } - if c.isGenericMappedType(t) && c.getMappedTypeNameTypeKind(t) != MappedTypeNameTypeKindRemapping { - return c.getIndexedMappedTypeSubstitutedTypeOfContextualType(t, name, nameType) + if len(types) == 0 { + return nil } - result := c.getTypeOfConcretePropertyOfContextualType(t, name) - if result != nil { - return result + if len(types) == 1 { + return types[0] } - return c.getTypeFromIndexInfosOfContextualType(t, name, nameType) - }, true /*noReductions*/) + return c.getIntersectionType(types) + } + if t.flags&TypeFlagsObject == 0 { + return nil + } + if c.isGenericMappedType(t) && c.getMappedTypeNameTypeKind(t) != MappedTypeNameTypeKindRemapping { + return c.getIndexedMappedTypeSubstitutedTypeOfContextualType(t, name, nameType) + } + result := c.getTypeOfConcretePropertyOfContextualType(t, name) + if result != nil { + return result + } + return c.getTypeFromIndexInfosOfContextualType(t, name, nameType) } func (c *Checker) getIndexedMappedTypeSubstitutedTypeOfContextualType(t *Type, name string, nameType *Type) *Type { @@ -31455,10 +34364,14 @@ func (c *Checker) getTypeFromIndexInfosOfContextualType(t *Type, name string, na return restType } } + indexInfos := c.getIndexInfosOfStructuredType(t) + if len(indexInfos) == 0 { + return nil + } if nameType == nil { nameType = c.getStringLiteralType(name) } - indexInfo := c.findApplicableIndexInfo(c.getIndexInfosOfStructuredType(t), nameType) + indexInfo := c.findApplicableIndexInfo(indexInfos, nameType) if indexInfo == nil { return nil } @@ -31909,6 +34822,12 @@ func (c *Checker) getTypeFactsWorker(t *Type, callerOnlyNeeds TypeFacts) TypeFac case flags&TypeFlagsNever != 0: return TypeFactsNone case flags&TypeFlagsUnion != 0: + if t.cacheFlags&typeCacheNonNullableUnion != 0 { + possibleFacts := core.IfElse(c.strictNullChecks, TypeFactsEmptyObjectStrictFacts, TypeFactsEmptyObjectFacts) + if callerOnlyNeeds&possibleFacts == 0 { + return TypeFactsNone + } + } var facts TypeFacts for _, t := range t.Types() { facts |= c.getTypeFactsWorker(t, callerOnlyNeeds) @@ -31948,8 +34867,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 c.typeHasCallOrConstructSignatures(t) || c.getMemberOfStructuredTypeEx(t, "bind", false /*instantiate*/) != nil && c.isTypeSubtypeOf(t, c.globalFunctionType) } func (c *Checker) getTypeWithFacts(t *Type, include TypeFacts) *Type { @@ -32304,7 +35222,7 @@ func (c *Checker) getNarrowableTypeForReference(t *Type, reference *ast.Node, ch // control flow analysis an opportunity to narrow it further. For example, for a reference of a type // parameter type 'T extends string | undefined' with a contextual type 'string', we substitute // 'string | undefined' to give control flow analysis the opportunity to narrow to type 'string'. - substituteConstraints := checkMode&CheckModeInferential == 0 && someType(t, c.isGenericTypeWithUnionConstraint) && (c.isConstraintPosition(t, reference) || c.hasContextualTypeWithNoGenericTypes(reference, checkMode)) + substituteConstraints := checkMode&CheckModeInferential == 0 && c.someGenericTypeWithUnionConstraint(t) && (c.isConstraintPosition(t, reference) || c.hasContextualTypeWithNoGenericTypes(reference, checkMode)) if substituteConstraints { return c.mapType(t, c.getBaseConstraintOrType) } @@ -32321,6 +35239,16 @@ func (c *Checker) isConstraintPosition(t *Type, node *ast.Node) bool { ast.IsElementAccessExpression(parent) && parent.Expression() == node && !(someType(t, c.isGenericTypeWithoutNullableConstraint) && c.isGenericIndexType(c.getTypeOfExpression(parent.AsElementAccessExpression().ArgumentExpression))) } +func (c *Checker) someGenericTypeWithUnionConstraint(t *Type) bool { + if t.flags&TypeFlagsUnion != 0 { + if t.objectFlags&(ObjectFlagsCouldContainTypeVariablesComputed|ObjectFlagsCouldContainTypeVariables) == ObjectFlagsCouldContainTypeVariablesComputed { + return false + } + return core.Some(t.AsUnionType().types, c.isGenericTypeWithUnionConstraint) + } + return c.isGenericTypeWithUnionConstraint(t) +} + func (c *Checker) isGenericTypeWithUnionConstraint(t *Type) bool { if t.flags&TypeFlagsIntersection != 0 { return core.Some(t.AsIntersectionType().types, c.isGenericTypeWithUnionConstraint) diff --git a/tsc/internal/checker/checker_test.go b/tsc/internal/checker/checker_test.go index 42ae368d35924..2fe0a34c8e63b 100644 --- a/tsc/internal/checker/checker_test.go +++ b/tsc/internal/checker/checker_test.go @@ -3,6 +3,7 @@ package checker_test import ( "fmt" "path/filepath" + "strconv" "strings" "testing" @@ -117,6 +118,51 @@ export type E = D;`, assert.Equal(t, defaultClause, defaultReference) } +func TestUnionPropertyFunctionAugmentation(t *testing.T) { + t.Parallel() + for _, lookupFirst := range []bool{false, true} { + t.Run(strconv.FormatBool(lookupFirst), func(t *testing.T) { + t.Parallel() + fs := bundled.WrapFS(vfstest.FromMap(map[string]string{ + "/main.ts": `type Callable = { (): string; shared: string }; +type Other = { call: number; shared: number }; +declare const instance: Callable | Other; +instance;`, + "/function.d.ts": `interface Function { + call(thisArg: any, ...args: any[]): any; +} +interface CallableFunction extends Function {}`, + "/tsconfig.json": `{"compilerOptions":{"strict":true,"noLib":true},"files":["main.ts","function.d.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) + program := compiler.NewProgram(compiler.ProgramOptions{Config: parsed, Host: host}) + program.BindSourceFiles() + c, done := program.GetTypeChecker(t.Context()) + defer done() + file := program.GetSourceFile("/main.ts") + typ := c.GetTypeAtLocation(file.Statements.Nodes[3].Expression()) + var call *ast.Symbol + if lookupFirst { + call = c.GetPropertyOfType(typ, "call") + assert.Assert(t, call != nil) + } + properties := c.GetPropertiesOfType(typ) + assert.Equal(t, len(properties), 1) + assert.Equal(t, properties[0].Name(), "shared") + if call == nil { + call = c.GetPropertyOfType(typ, "call") + assert.Assert(t, call != nil) + } + assert.Equal(t, c.GetPropertyOfType(typ, "call"), call) + assert.Equal(t, c.GetPropertyOfType(typ, "shared"), properties[0]) + assert.Equal(t, len(c.GetPropertiesOfType(typ)), 1) + assert.Equal(t, len(c.GetDiagnostics(t.Context(), file)), 0) + }) + } +} + func BenchmarkNewChecker(b *testing.B) { fs := bundled.WrapFS(osvfs.FS()) rootPath := tspath.RootedDirectoryPathFromAbsolute(filepath.Join(repo.TestDataPath(), "fixtures/compiler")) diff --git a/tsc/internal/checker/emitsupport.go b/tsc/internal/checker/emitsupport.go index 3da782c9ab4c4..250223713a6c6 100644 --- a/tsc/internal/checker/emitsupport.go +++ b/tsc/internal/checker/emitsupport.go @@ -292,7 +292,6 @@ func (c *Checker) requiresAddingImplicitUndefined(declaration *ast.Node, symbol symbol = c.getSymbolOfDeclaration(declaration) } t := c.getTypeOfSymbol(symbol) - c.mappedSymbolLinks.Has(symbol) return (symbol.Flags()&ast.SymbolFlagsProperty != 0) && (symbol.Flags()&ast.SymbolFlagsOptional != 0) && isOptionalDeclaration(declaration) && c.ReverseMappedSymbolLinks.Has(symbol) && c.ReverseMappedSymbolLinks.Get(symbol).mappedType != nil && containsNonMissingUndefinedType(c, t) case ast.KindParameter, ast.KindJSDocParameterTag: return c.requiresAddingImplicitUndefinedWorker(declaration, enclosingDeclaration) diff --git a/tsc/internal/checker/exports.go b/tsc/internal/checker/exports.go index 5fedfddca61a5..a182445246150 100644 --- a/tsc/internal/checker/exports.go +++ b/tsc/internal/checker/exports.go @@ -187,7 +187,7 @@ func (c *Checker) WasCanceled() bool { } func (c *Checker) GetSignaturesOfType(t *Type, kind SignatureKind) []*Signature { - return c.getSignaturesOfType(t, kind) + return c.resolveExportedSignatures(c.getSignaturesOfType(t, kind)) } func (c *Checker) GetDeclaredTypeOfSymbol(symbol *ast.Symbol) *Type { @@ -275,7 +275,7 @@ func (c *Checker) GetExpandedParameters(signature *Signature, skipUnionExpanding } func (c *Checker) GetResolvedSignature(node *ast.Node) *Signature { - return c.getResolvedSignature(node, nil, CheckModeNormal) + return c.resolveExportedSignature(c.getResolvedSignature(node, nil, CheckModeNormal)) } // Return the type of the given property in the given type, or nil if no such property exists diff --git a/tsc/internal/checker/flow.go b/tsc/internal/checker/flow.go index d9d4ac3737b7b..bd653f46630a8 100644 --- a/tsc/internal/checker/flow.go +++ b/tsc/internal/checker/flow.go @@ -2058,7 +2058,7 @@ 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.effectsOrDecoratorSignature if signature == nil { // 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 @@ -2091,7 +2091,7 @@ func (c *Checker) getEffectsSignature(node *ast.Node) *Signature { if !(signature != nil && c.hasTypePredicateOrNeverReturnType(signature)) { signature = c.unknownSignature } - links.effectsSignature = signature + links.effectsOrDecoratorSignature = signature } if signature == c.unknownSignature { return nil @@ -2175,7 +2175,7 @@ func (c *Checker) getExplicitTypeOfSymbol(symbol *ast.Symbol, diagnostic *ast.Di } if symbol.Flags()&(ast.SymbolFlagsVariable|ast.SymbolFlagsProperty) != 0 { if symbol.CheckFlags()&ast.CheckFlagsMapped != 0 { - origin := c.mappedSymbolLinks.Get(symbol).syntheticOrigin + origin := c.valueSymbolLinks.TryGet(symbol).target if origin != nil && c.getExplicitTypeOfSymbol(origin, diagnostic) != nil { return c.getTypeOfSymbol(symbol) } diff --git a/tsc/internal/checker/grammarchecks.go b/tsc/internal/checker/grammarchecks.go index 93bed4b355296..b8815acb72d49 100644 --- a/tsc/internal/checker/grammarchecks.go +++ b/tsc/internal/checker/grammarchecks.go @@ -995,13 +995,30 @@ func (c *Checker) checkGrammarForInvalidExclamationToken(postfixToken *ast.Token return postfixToken != nil && postfixToken.Kind == ast.KindExclamationToken && c.grammarErrorOnNode(postfixToken, message) } -func (c *Checker) checkGrammarObjectLiteralExpression(node *ast.ObjectLiteralExpression, inDestructuring bool) bool { - seen := make(map[string]DeclarationMeaning) +func (c *Checker) releaseObjectLiteralSeen(seen map[string]DeclarationMeaning) { + if seen != nil { + clear(seen) + c.freeObjectLiteralSeen = append(c.freeObjectLiteralSeen, seen) + } +} +func (c *Checker) checkGrammarObjectLiteralExpression(node *ast.ObjectLiteralExpression, inDestructuring bool) bool { var properties []*ast.Node if node.Properties != nil { properties = node.Properties.Nodes } + if len(properties) == 0 { + return false + } + var seen map[string]DeclarationMeaning + if !inDestructuring && len(properties) > 1 { + if n := len(c.freeObjectLiteralSeen); n > 0 { + seen = c.freeObjectLiteralSeen[n-1] + c.freeObjectLiteralSeen = c.freeObjectLiteralSeen[:n-1] + } else { + seen = make(map[string]DeclarationMeaning) + } + } for _, prop := range properties { if prop.Kind == ast.KindSpreadAssignment { spreadAssignment := prop.AsSpreadAssignment() @@ -1105,7 +1122,7 @@ func (c *Checker) checkGrammarObjectLiteralExpression(node *ast.ObjectLiteralExp panic(fmt.Sprintf("Unexpected node kind %q", prop.Kind)) } - if !inDestructuring { + if seen != nil { effectiveName, ok := c.getEffectivePropertyNameForPropertyNameNode(name) if !ok { continue @@ -1123,15 +1140,18 @@ func (c *Checker) checkGrammarObjectLiteralExpression(node *ast.ObjectLiteralExp if existingKind != DeclarationMeaningGetOrSetAccessor && currentKind != existingKind { seen[effectiveName] = currentKind | existingKind } else { + c.releaseObjectLiteralSeen(seen) return c.grammarErrorOnNode(name, diagnostics.An_object_literal_cannot_have_multiple_get_Slashset_accessors_with_the_same_name) } } else { + c.releaseObjectLiteralSeen(seen) return c.grammarErrorOnNode(name, diagnostics.An_object_literal_cannot_have_property_and_accessor_with_the_same_name) } } } } + c.releaseObjectLiteralSeen(seen) return false } diff --git a/tsc/internal/checker/jsx.go b/tsc/internal/checker/jsx.go index 1054f4d46f8c5..705e05d99c470 100644 --- a/tsc/internal/checker/jsx.go +++ b/tsc/internal/checker/jsx.go @@ -712,7 +712,7 @@ func (c *Checker) createJsxAttributesTypeFromAttributesProperty(openingLikeEleme if c.strictNullChecks { allAttributesTable = ast.NewSymbolTable() } - attributesTable := ast.NewSymbolTable() + attributesTable := c.acquireSymbolTable() var attributesSymbol *ast.Symbol attributeParent := openingLikeElement spread := c.emptyJsxObjectType @@ -723,6 +723,8 @@ func (c *Checker) createJsxAttributesTypeFromAttributesProperty(openingLikeEleme createJsxAttributesType := func() *Type { objectFlags |= ObjectFlagsFreshLiteral result := c.newAnonymousType(attributesSymbol, attributesTable, nil, nil, nil) + c.releaseSymbolTableIfUnused(result, attributesTable) + attributesTable = nil result.objectFlags |= objectFlags | ObjectFlagsObjectLiteral | ObjectFlagsContainsObjectOrArrayLiteral return result } @@ -741,12 +743,8 @@ func (c *Checker) createJsxAttributesTypeFromAttributesProperty(openingLikeEleme if ast.IsJsxAttribute(attributeDecl) { exprType := c.checkJsxAttribute(attributeDecl, checkMode) objectFlags |= exprType.objectFlags & ObjectFlagsPropagatingFlags - attributeSymbol := c.newSymbol(ast.SymbolFlagsProperty|member.Flags(), member.Name()) - attributeSymbol.SetDeclarations(member.Declarations()) - attributeSymbol.SetParent(member.Parent()) - if member.ValueDeclaration() != nil { - attributeSymbol.SetValueDeclaration(member.ValueDeclaration()) - } + attributeSymbol := c.newSharedDataSymbol(member) + attributeSymbol.SetFlags(attributeSymbol.Flags() | ast.SymbolFlagsProperty) links := c.valueSymbolLinks.Get(attributeSymbol) links.resolvedType = exprType links.target = member @@ -768,7 +766,7 @@ func (c *Checker) createJsxAttributesTypeFromAttributesProperty(openingLikeEleme debug.Assert(attributeDecl.Kind == ast.KindJsxSpreadAttribute) if attributesTable.Len() != 0 { spread = c.getSpreadType(spread, createJsxAttributesType(), attributesSymbol, objectFlags, false /*readonly*/) - attributesTable = ast.NewSymbolTable() + attributesTable = c.acquireSymbolTable() } exprType := c.getReducedType(c.checkExpressionEx(attributeDecl.Expression(), checkMode&CheckModeInferential)) if IsTypeAny(exprType) { @@ -846,15 +844,19 @@ func (c *Checker) createJsxAttributesTypeFromAttributesProperty(openingLikeEleme childrenPropSymbol.SetValueDeclaration(c.factory.NewPropertySignatureDeclaration(nil, c.factory.NewIdentifier(jsxChildrenPropertyName), nil /*postfixToken*/, nil /*type*/, nil /*initializer*/)) childrenPropSymbol.ValueDeclaration().Parent = attributeParent childrenPropSymbol.ValueDeclaration().AsPropertySignatureDeclaration().Symbol = childrenPropSymbol - childPropMap := ast.NewSymbolTable() - childPropMap.Set(jsxChildrenPropertyName, childrenPropSymbol) - spread = c.getSpreadType(spread, c.newAnonymousType(attributesSymbol, childPropMap, nil, nil, nil), attributesSymbol, objectFlags|c.getPropagatingFlagsOfTypes(childTypes, TypeFlagsNone), false /*readonly*/) + childPropMap := c.acquireSymbolTable() + childPropMap.Set(jsxChildrenPropertyName, childrenPropSymbol) + childType := c.newAnonymousType(attributesSymbol, childPropMap, nil, nil, nil) + c.releaseSymbolTableIfUnused(childType, childPropMap) + spread = c.getSpreadType(spread, childType, attributesSymbol, objectFlags|c.getPropagatingFlagsOfTypes(childTypes, TypeFlagsNone), false /*readonly*/) } } if hasSpreadAnyType { + c.releaseSymbolTable(attributesTable) return c.anyType } if typeToIntersect != nil { + c.releaseSymbolTable(attributesTable) if spread != c.emptyJsxObjectType { return c.getIntersectionType([]*Type{typeToIntersect, spread}) } @@ -863,6 +865,7 @@ func (c *Checker) createJsxAttributesTypeFromAttributesProperty(openingLikeEleme if spread == c.emptyJsxObjectType { return createJsxAttributesType() } + c.releaseSymbolTable(attributesTable) return spread } diff --git a/tsc/internal/checker/links.go b/tsc/internal/checker/links.go index c21981cae9d80..02a3f3d70602f 100644 --- a/tsc/internal/checker/links.go +++ b/tsc/internal/checker/links.go @@ -14,53 +14,149 @@ import ( const maxPageLinkCount = 0x100_0000 // 16M type nodeLinkStore[V any] struct { - gen ast.NodeIdGenerator - links core.LinkStore[uint64, V] - pages core.PagedLinkStore[V] + gen *ast.NodeIdGenerator + localGen ast.NodeIdGenerator + links core.LinkStore[uint64, V] + pages core.PagedLinkStore[V] +} + +func (s *nodeLinkStore[V]) getNodeId(node *ast.Node) uint64 { + if s.gen != nil { + return uint64(s.gen.GetNodeId(node)) + } + return uint64(s.localGen.GetNodeId(node)) } func (s *nodeLinkStore[V]) Get(node *ast.Node) *V { - id := uint64(s.gen.GetNodeId(node)) - if id >= ast.BlockIdOffset && id < ast.BlockIdOffset+maxPageLinkCount { - return s.pages.Get(id - ast.BlockIdOffset) + if off := uint64(ast.PeekNodeId(node)) - ast.BlockIdOffset; off < maxPageLinkCount { + if v := s.pages.TryGet(off); v != nil { + return v + } + } + return s.getSlow(node) +} + +func (s *nodeLinkStore[V]) getSlow(node *ast.Node) *V { + id := s.getNodeId(node) + if off := id - ast.BlockIdOffset; off < maxPageLinkCount { + return s.pages.Get(off) } return s.links.Get(id) } func (s *nodeLinkStore[V]) Has(node *ast.Node) bool { - return s.TryGet(node) != nil + id := uint64(ast.PeekNodeId(node)) + if id == 0 { + return false + } + if off := id - ast.BlockIdOffset; off < maxPageLinkCount { + return s.pages.Has(off) + } + return s.links.Has(id) } func (s *nodeLinkStore[V]) TryGet(node *ast.Node) *V { - id := uint64(s.gen.GetNodeId(node)) - if id >= ast.BlockIdOffset && id < ast.BlockIdOffset+maxPageLinkCount { - return s.pages.TryGet(id - ast.BlockIdOffset) + id := uint64(ast.PeekNodeId(node)) + if id == 0 { + return nil + } + if off := id - ast.BlockIdOffset; off < maxPageLinkCount { + return s.pages.TryGet(off) + } + return s.links.TryGet(id) +} + +type directNodeLinkStore[V any] struct { + gen *ast.NodeIdGenerator + localGen ast.NodeIdGenerator + links core.LinkStore[uint64, V] + pages core.PagedValueLinkStore[V] +} + +func (s *directNodeLinkStore[V]) getNodeId(node *ast.Node) uint64 { + if s.gen != nil { + return uint64(s.gen.GetNodeId(node)) + } + return uint64(s.localGen.GetNodeId(node)) +} + +func (s *directNodeLinkStore[V]) Get(node *ast.Node) *V { + if off := uint64(ast.PeekNodeId(node)) - ast.BlockIdOffset; off < maxPageLinkCount { + if v := s.pages.TryGet(off); v != nil { + return v + } + } + return s.getSlow(node) +} + +func (s *directNodeLinkStore[V]) getSlow(node *ast.Node) *V { + id := s.getNodeId(node) + if off := id - ast.BlockIdOffset; off < maxPageLinkCount { + return s.pages.Get(off) + } + return s.links.Get(id) +} + +func (s *directNodeLinkStore[V]) TryGet(node *ast.Node) *V { + id := uint64(ast.PeekNodeId(node)) + if id == 0 { + return nil + } + if off := id - ast.BlockIdOffset; off < maxPageLinkCount { + return s.pages.TryGet(off) } return s.links.TryGet(id) } type symbolLinkStore[V any] struct { - gen ast.SymbolIdGenerator - links core.LinkStore[uint64, V] - pages core.PagedLinkStore[V] + gen *ast.SymbolIdGenerator + localGen ast.SymbolIdGenerator + links core.LinkStore[uint64, V] + pages core.PagedLinkStore[V] +} + +func (s *symbolLinkStore[V]) getSymbolId(symbol *ast.Symbol) uint64 { + if s.gen != nil { + return uint64(s.gen.GetSymbolId(symbol)) + } + return uint64(s.localGen.GetSymbolId(symbol)) } func (s *symbolLinkStore[V]) Get(symbol *ast.Symbol) *V { - id := uint64(s.gen.GetSymbolId(symbol)) - if id >= ast.BlockIdOffset && id < ast.BlockIdOffset+maxPageLinkCount { - return s.pages.Get(id - ast.BlockIdOffset) + if off := uint64(ast.PeekSymbolId(symbol)) - ast.BlockIdOffset; off < maxPageLinkCount { + if v := s.pages.TryGet(off); v != nil { + return v + } + } + return s.getSlow(symbol) +} + +func (s *symbolLinkStore[V]) getSlow(symbol *ast.Symbol) *V { + id := s.getSymbolId(symbol) + if off := id - ast.BlockIdOffset; off < maxPageLinkCount { + return s.pages.Get(off) } return s.links.Get(id) } func (s *symbolLinkStore[V]) Has(symbol *ast.Symbol) bool { - return s.TryGet(symbol) != nil + id := uint64(ast.PeekSymbolId(symbol)) + if id == 0 { + return false + } + if off := id - ast.BlockIdOffset; off < maxPageLinkCount { + return s.pages.Has(off) + } + return s.links.Has(id) } func (s *symbolLinkStore[V]) TryGet(symbol *ast.Symbol) *V { - id := uint64(s.gen.GetSymbolId(symbol)) - if id >= ast.BlockIdOffset && id < ast.BlockIdOffset+maxPageLinkCount { - return s.pages.TryGet(id - ast.BlockIdOffset) + id := uint64(ast.PeekSymbolId(symbol)) + if id == 0 { + return nil + } + if off := id - ast.BlockIdOffset; off < maxPageLinkCount { + return s.pages.TryGet(off) } return s.links.TryGet(id) } diff --git a/tsc/internal/checker/mapper.go b/tsc/internal/checker/mapper.go index 6552bd6dadf23..c64ad8ca8a427 100644 --- a/tsc/internal/checker/mapper.go +++ b/tsc/internal/checker/mapper.go @@ -10,9 +10,9 @@ import ( // TypeMapper type TypeMapper struct { - kind TypeMapperKind `customlint:"protected"` + kind TypeMapperKind `customlint:"protected"` + hasInference bool } - type TypeMapperKind uint8 const ( @@ -128,10 +128,22 @@ func mergeTypeMappers(m1 *TypeMapper, m2 *TypeMapper) *TypeMapper { } func prependTypeMapping(source *Type, target *Type, mapper *TypeMapper) *TypeMapper { + tp := getNonDistributedTypeParameter(source) if mapper == nil { - return newSimpleTypeMapper(getNonDistributedTypeParameter(source), target) + return newSimpleTypeMapper(tp, target) + } + if mapper.kind == TypeMapperKindSimple { + s := castTypeMapper[SimpleTypeMapper](mapper, TypeMapperKindSimple) + if s.source == tp { + if s.target == target && target != tp { + return mapper + } + if target.flags&TypeFlagsTypeParameter == 0 { + return newSimpleTypeMapper(tp, target) + } + } } - return newMergedTypeMapper(newSimpleTypeMapper(getNonDistributedTypeParameter(source), target), mapper) + return newMergedTypeMapper(newSimpleTypeMapper(tp, target), mapper) } func appendTypeMapping(mapper *TypeMapper, source *Type, target *Type) *TypeMapper { @@ -169,6 +181,13 @@ type SimpleTypeMapper struct { } func newSimpleTypeMapper(source *Type, target *Type) *TypeMapper { + if source != nil && source.checker != nil { + m := source.checker.simpleMapperArena.New() + m.kind = TypeMapperKindSimple + m.source = source + m.target = target + return &m.TypeMapper + } m := &SimpleTypeMapper{} m.kind = TypeMapperKindSimple //nolint:customlint m.source = source @@ -191,29 +210,69 @@ func (m *SimpleTypeMapper) MapsThisOnly() bool { type ArrayTypeMapper struct { TypeMapperBase - sources []*Type - targets []*Type + sourcesLen uint16 + targetsLen uint16 + sourcesPtr **Type + targetsPtr **Type } func newArrayTypeMapper(sources []*Type, targets []*Type) *TypeMapper { + var sourcesPtr, targetsPtr **Type + if len(sources) > 0 { + // SAFETY: Retains the backing-array pointer of non-empty sources alongside sourcesLen. + sourcesPtr = unsafe.SliceData(sources) + } + if len(targets) > 0 { + // SAFETY: Retains the backing-array pointer of non-empty targets alongside targetsLen. + targetsPtr = unsafe.SliceData(targets) + } + if len(sources) > 0 && sources[0] != nil && sources[0].checker != nil { + m := sources[0].checker.arrayMapperArena.New() + m.kind = TypeMapperKindArray //nolint:customlint + m.sourcesLen = uint16(len(sources)) + m.targetsLen = uint16(len(targets)) + m.sourcesPtr = sourcesPtr + m.targetsPtr = targetsPtr + return &m.TypeMapper + } m := &ArrayTypeMapper{} m.kind = TypeMapperKindArray //nolint:customlint - m.sources = sources - m.targets = targets + m.sourcesLen = uint16(len(sources)) + m.targetsLen = uint16(len(targets)) + m.sourcesPtr = sourcesPtr + m.targetsPtr = targetsPtr return &m.TypeMapper } +func (m *ArrayTypeMapper) sources() []*Type { + if m.sourcesLen == 0 { + return nil + } + // SAFETY: sourcesPtr points to a []*Type backing array of length sourcesLen. + return unsafe.Slice(m.sourcesPtr, m.sourcesLen) +} + +func (m *ArrayTypeMapper) targets() []*Type { + if m.targetsLen == 0 { + return nil + } + // SAFETY: targetsPtr points to a []*Type backing array of length targetsLen. + return unsafe.Slice(m.targetsPtr, m.targetsLen) +} + func (m *ArrayTypeMapper) Map(t *Type) *Type { - for i, s := range m.sources { + // SAFETY: sourcesPtr and targetsPtr point to []*Type backing arrays of length sourcesLen and targetsLen. + for i, s := range unsafe.Slice(m.sourcesPtr, m.sourcesLen) { if t == s { - return m.targets[i] + // SAFETY: targetsLen >= sourcesLen for all ArrayTypeMappers. + return unsafe.Slice(m.targetsPtr, m.targetsLen)[i] } } return t } func (m *ArrayTypeMapper) MapsThisOnly() bool { - return len(m.sources) == 1 && isThisTypeParameter(m.sources[0]) + return m.sourcesLen == 1 && isThisTypeParameter(*m.sourcesPtr) } // ArrayToSingleTypeMapper @@ -299,8 +358,30 @@ type MergedTypeMapper struct { } func newMergedTypeMapper(m1 *TypeMapper, m2 *TypeMapper) *TypeMapper { + var c *Checker + if m1 != nil && m1.kind == TypeMapperKindSimple { + if s := castTypeMapper[SimpleTypeMapper](m1, TypeMapperKindSimple).source; s != nil { + c = s.checker + } + } else if m2 != nil && m2.kind == TypeMapperKindSimple { + if s := castTypeMapper[SimpleTypeMapper](m2, TypeMapperKindSimple).source; s != nil { + c = s.checker + } + } else if m1 != nil && m1.kind == TypeMapperKindComposite { + c = castTypeMapper[CompositeTypeMapper](m1, TypeMapperKindComposite).c + } + hasInference := m1 != nil && m1.hasInference || m2 != nil && m2.hasInference + if c != nil { + m := c.mergedMapperArena.New() + m.kind = TypeMapperKindMerged + m.hasInference = hasInference + m.m1 = m1 + m.m2 = m2 + return &m.TypeMapper + } m := &MergedTypeMapper{} m.kind = TypeMapperKindMerged //nolint:customlint + m.hasInference = hasInference m.m1 = m1 m.m2 = m2 return &m.TypeMapper @@ -320,8 +401,19 @@ type CompositeTypeMapper struct { } func newCompositeTypeMapper(c *Checker, m1 *TypeMapper, m2 *TypeMapper) *TypeMapper { + hasInference := m1 != nil && m1.hasInference || m2 != nil && m2.hasInference + if c != nil { + m := c.compositeMapperArena.New() + m.kind = TypeMapperKindComposite + m.hasInference = hasInference + m.c = c + m.m1 = m1 + m.m2 = m2 + return &m.TypeMapper + } m := &CompositeTypeMapper{} m.kind = TypeMapperKindComposite //nolint:customlint + m.hasInference = hasInference m.c = c m.m1 = m1 m.m2 = m2 @@ -347,12 +439,43 @@ type InferenceTypeMapper struct { func (c *Checker) initInferenceTypeMapper(m *InferenceTypeMapper, n *InferenceContext, fixing bool) *TypeMapper { m.kind = TypeMapperKindInference //nolint:customlint + m.hasInference = true m.c = c m.n = n m.fixing = fixing return &m.TypeMapper } +func markMapperEscaped(m *TypeMapper) { + if m != nil && m.hasInference { + markMapperEscapedSlow(m) + } +} + +func markMapperEscapedSlow(m *TypeMapper) { + m.hasInference = false + switch m.kind { + case TypeMapperKindInference: + castTypeMapper[InferenceTypeMapper](m, TypeMapperKindInference).n.escaped = true + case TypeMapperKindMerged: + merged := castTypeMapper[MergedTypeMapper](m, TypeMapperKindMerged) + if merged.m1 != nil && merged.m1.hasInference { + markMapperEscapedSlow(merged.m1) + } + if merged.m2 != nil && merged.m2.hasInference { + markMapperEscapedSlow(merged.m2) + } + case TypeMapperKindComposite: + comp := castTypeMapper[CompositeTypeMapper](m, TypeMapperKindComposite) + if comp.m1 != nil && comp.m1.hasInference { + markMapperEscapedSlow(comp.m1) + } + if comp.m2 != nil && comp.m2.hasInference { + markMapperEscapedSlow(comp.m2) + } + } +} + func (m *InferenceTypeMapper) Map(t *Type) *Type { for i, inference := range m.n.inferences { if t == inference.typeParameter { diff --git a/tsc/internal/checker/nodebuilder_hover.go b/tsc/internal/checker/nodebuilder_hover.go index b49ac981fbd41..0135a98b4def4 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.getPropertiesSlice()) members = b.serializePropertiesWithTruncation(filteredProps, members) // Heritage clauses diff --git a/tsc/internal/checker/nodebuilderimpl.go b/tsc/internal/checker/nodebuilderimpl.go index 7c90f3f320fbc..7ccb904cbb2e4 100644 --- a/tsc/internal/checker/nodebuilderimpl.go +++ b/tsc/internal/checker/nodebuilderimpl.go @@ -1897,7 +1897,7 @@ func (b *NodeBuilderImpl) signatureToSignatureDeclarationHelper(signature *Signa // If the expanded parameter list had a variadic in a non-trailing position, don't expand it parameters := core.Map(core.IfElse(core.Some(expandedParams, func(p *ast.Symbol) bool { return p != expandedParams[len(expandedParams)-1] && p.CheckFlags()&ast.CheckFlagsRestParameter != 0 - }), signature.parameters, expandedParams), func(parameter *ast.Symbol) *ast.Node { + }), b.ch.getSignatureParameters(signature), expandedParams), func(parameter *ast.Symbol) *ast.Node { return b.symbolToParameterDeclaration(parameter, kind == ast.KindConstructor) }) var thisParameter *ast.Node @@ -1996,9 +1996,10 @@ func (b *NodeBuilderImpl) signatureToSignatureDeclarationHelper(signature *Signa } func (c *Checker) getExpandedParameters(sig *Signature, skipUnionExpanding bool) [][]*ast.Symbol { + params := c.getSignatureParameters(sig) if signatureHasRestParameter(sig) { - restIndex := len(sig.parameters) - 1 - restSymbol := sig.parameters[restIndex] + restIndex := len(params) - 1 + restSymbol := params[restIndex] restType := c.getTypeOfSymbol(restSymbol) getUniqAssociatedNamesFromTupleType := func(t *Type, restSymbol *ast.Symbol) []string { names := core.MapIndex(t.Target().AsTupleType().elementInfos, func(info TupleElementInfo, i int) string { @@ -2069,7 +2070,7 @@ func (c *Checker) getExpandedParameters(sig *Signature, skipUnionExpanding bool) } return symbol }) - return core.Concatenate(sig.parameters[0:restIndex], restParams) + return core.Concatenate(params[0:restIndex], restParams) } if isTupleType(restType) { @@ -2080,12 +2081,12 @@ func (c *Checker) getExpandedParameters(sig *Signature, skipUnionExpanding bool) }) } } - return [][]*ast.Symbol{sig.parameters} + return [][]*ast.Symbol{params} } func (b *NodeBuilderImpl) tryGetThisParameterDeclaration(signature *Signature) *ast.Node { if signature.ThisParameter() != nil { - return b.symbolToParameterDeclaration(signature.ThisParameter(), false) + return b.symbolToParameterDeclaration(b.ch.getSignatureThisParameter(signature), false) } if signature.declaration != nil && ast.IsInJSFile(signature.declaration) { // !!! JSDoc Support @@ -2758,7 +2759,7 @@ func (b *NodeBuilderImpl) createTypeNodesFromResolvedType(resolvedType *Structur typeElements = slices.Concat(typeElements, b.indexInfoToObjectComputedNamesOrSignatureDeclaration(info, typeNode)) } - properties := resolvedType.getProperties() + properties := resolvedType.getPropertiesSlice() if len(properties) == 0 { return b.f.NewNodeList(typeElements) } @@ -2807,7 +2808,7 @@ func (b *NodeBuilderImpl) createTypeNodeFromObjectType(t *Type) *ast.TypeNode { resolved := b.ch.resolveStructuredTypeMembers(t) callSigs := resolved.CallSignatures() ctorSigs := resolved.ConstructSignatures() - if len(resolved.getProperties()) == 0 && len(resolved.getIndexInfos()) == 0 { + if len(resolved.getPropertiesSlice()) == 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 +2837,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.getPropertiesSlice(), func(p *ast.Symbol) bool { return p.Flags()&ast.SymbolFlagsPrototype == 0 - }), len(resolved.getProperties())) + }), len(resolved.getPropertiesSlice())) // 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/nodebuilderscopes.go b/tsc/internal/checker/nodebuilderscopes.go index 2b0c69f37a375..4d4317a8976b7 100644 --- a/tsc/internal/checker/nodebuilderscopes.go +++ b/tsc/internal/checker/nodebuilderscopes.go @@ -52,7 +52,7 @@ func (b *NodeBuilderImpl) addSymbolTypeToContext(symbol *ast.Symbol, t *Type) fu func (b *NodeBuilderImpl) enterSignatureScope(signature *Signature) (expandedParams []*ast.Symbol, cleanup func()) { expandedParams = b.ch.getExpandedParameters(signature, true /*skipUnionExpanding*/)[0] - cleanup = b.enterNewScope(signature.declaration, expandedParams, signature.TypeParameters(), signature.parameters, signature.mapper) + cleanup = b.enterNewScope(signature.declaration, expandedParams, signature.TypeParameters(), b.ch.getSignatureParameters(signature), signature.mapper) return expandedParams, cleanup } diff --git a/tsc/internal/checker/pseudotypenodebuilder.go b/tsc/internal/checker/pseudotypenodebuilder.go index 8f1e2bf1700e1..2ec99a8cdecf2 100644 --- a/tsc/internal/checker/pseudotypenodebuilder.go +++ b/tsc/internal/checker/pseudotypenodebuilder.go @@ -178,7 +178,7 @@ func (b *NodeBuilderImpl) pseudoTypeToNode(t *pseudochecker.PseudoType) *ast.Nod d := t.AsPseudoTypeSingleCallSignature() signature := b.ch.getSignatureFromDeclaration(d.Signature) expandedParams := b.ch.getExpandedParameters(signature, true /*skipUnionExpanding*/)[0] - cleanup := b.enterNewScope(d.Signature, expandedParams, signature.TypeParameters(), signature.parameters, signature.mapper) + cleanup := b.enterNewScope(d.Signature, expandedParams, signature.TypeParameters(), b.ch.getSignatureParameters(signature), signature.mapper) defer cleanup() var typeParams *ast.NodeList if len(d.TypeParameters) > 0 { @@ -233,7 +233,7 @@ func (b *NodeBuilderImpl) pseudoTypeToNode(t *pseudochecker.PseudoType) *ast.Nod if e.Kind != pseudochecker.PseudoObjectElementKindPropertyAssignment { signature := b.ch.getSignatureFromDeclaration(e.Signature()) expandedParams := b.ch.getExpandedParameters(signature, true /*skipUnionExpanding*/)[0] - cleanup = b.enterNewScope(e.Signature(), expandedParams, signature.TypeParameters(), signature.parameters, signature.mapper) + cleanup = b.enterNewScope(e.Signature(), expandedParams, signature.TypeParameters(), b.ch.getSignatureParameters(signature), signature.mapper) } var newProp *ast.Node switch e.Kind { @@ -589,7 +589,7 @@ func (b *NodeBuilderImpl) pseudoParametersEquivalentToParameters(params []*pseud } return false } else if targetSig.ThisParameter() != nil && ast.IsThisIdentifier(params[0].Name) { - targetParam := targetSig.ThisParameter() + targetParam := b.ch.getSignatureThisParameter(targetSig) paramType := b.ch.getTypeOfParameter(targetParam) if !b.pseudoTypeEquivalentToType(params[0].Type, paramType, params[0].Optional, false) { if reportErrors { @@ -611,7 +611,7 @@ func (b *NodeBuilderImpl) pseudoParametersEquivalentToParameters(params []*pseud return false // TODO: spread tuple params may mess with this check } for i, p := range params { - targetParam := targetSig.parameters[i] + targetParam := b.ch.getSignatureParameters(targetSig)[i] if p.Optional != b.ch.isOptionalParameter(targetParam.ValueDeclaration()) { if reportErrors { b.ctx.tracker.ReportInferenceFallback(p.Name.Parent) diff --git a/tsc/internal/checker/relater.go b/tsc/internal/checker/relater.go index 562ae0451cb43..d95802dbea58a 100644 --- a/tsc/internal/checker/relater.go +++ b/tsc/internal/checker/relater.go @@ -1,5 +1,6 @@ package checker + import ( "math/bits" "slices" @@ -1096,6 +1097,11 @@ func (c *Checker) isDiscriminantProperty(t *Type, name string) bool { return false } +type unionDiscriminant struct { + keyPropertyName string + constituentMap map[*Type]*Type +} + func (c *Checker) getMatchingUnionConstituentForType(unionType *Type, t *Type) *Type { keyPropertyName := c.getKeyPropertyName(unionType) if keyPropertyName == "" { @@ -1113,19 +1119,24 @@ 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) + disc := c.unionDiscriminants[u] + if disc.keyPropertyName == "" { + disc.keyPropertyName, disc.constituentMap = c.computeKeyPropertyNameAndMap(t) + if c.unionDiscriminants == nil { + c.unionDiscriminants = make(map[*UnionType]unionDiscriminant) + } + c.unionDiscriminants[u] = disc } - if u.keyPropertyName == ast.InternalSymbolNameMissing { + if disc.keyPropertyName == ast.InternalSymbolNameMissing { return "" } - return u.keyPropertyName + return disc.keyPropertyName } // Given a union type for which getKeyPropertyName returned a non-empty string, return the constituent // that corresponds to the given key type for that property name. func (c *Checker) getConstituentTypeForKeyType(t *Type, keyType *Type) *Type { - result := t.AsUnionType().constituentMap[c.getRegularTypeOfLiteralType(keyType)] + result := c.unionDiscriminants[t.AsUnionType()].constituentMap[c.getRegularTypeOfLiteralType(keyType)] if result != c.unknownType { return result } diff --git a/tsc/internal/checker/services.go b/tsc/internal/checker/services.go index a40093e14a94c..7fdad6561d727 100644 --- a/tsc/internal/checker/services.go +++ b/tsc/internal/checker/services.go @@ -153,10 +153,17 @@ func (c *Checker) ForEachExportAndPropertyOfModule(moduleSymbol *ast.Symbol, cb if reducedType.flags&TypeFlagsStructuredType == 0 { return } - for name, symbol := range c.resolveStructuredTypeMembers(reducedType).getMembers().All() { - if c.isNamedMember(symbol, name) { - cb(symbol, name) + resolved := c.resolveStructuredTypeMembers(reducedType) + if m := resolved.getMembersIndex(); m != nil && m.fallback != nil { + for name, symbol := range m.fallback.All() { + if c.isNamedMember(symbol, name) { + cb(symbol, name) + } } + return + } + for _, symbol := range resolved.Properties() { + cb(symbol, symbol.Name()) } } @@ -257,11 +264,11 @@ func (c *Checker) GetElementTypeOfArrayType(t *Type) *Type { } func (c *Checker) GetCallSignatures(t *Type) []*Signature { - return c.getSignaturesOfType(t, SignatureKindCall) + return c.resolveExportedSignatures(c.getSignaturesOfType(t, SignatureKindCall)) } func (c *Checker) GetConstructSignatures(t *Type) []*Signature { - return c.getSignaturesOfType(t, SignatureKindConstruct) + return c.resolveExportedSignatures(c.getSignaturesOfType(t, SignatureKindConstruct)) } func (c *Checker) GetApparentProperties(t *Type) []*ast.Symbol { @@ -434,10 +441,9 @@ func (c *Checker) getImmediateRootSymbols(symbol *ast.Symbol) []*ast.Symbol { return []*ast.Symbol{leftSpread, rightSpread} } } - if c.mappedSymbolLinks.Has(symbol) { - syntheticOrigin := c.mappedSymbolLinks.Get(symbol).syntheticOrigin - if syntheticOrigin != nil { - return []*ast.Symbol{syntheticOrigin} + if symbol.CheckFlags()&ast.CheckFlagsInstantiated == 0 { + if links := c.valueSymbolLinks.TryGet(symbol); links != nil && links.target != nil { + return []*ast.Symbol{links.target} } } target := c.tryGetTarget(symbol) @@ -902,7 +908,7 @@ func (c *Checker) getResolvedSignatureWorker(node *ast.Node, checkMode CheckMode res = c.getResolvedSignature(node, candidatesOutArray, checkMode) } c.apparentArgumentCount = nil - return res, *candidatesOutArray + return c.resolveExportedSignature(res), c.resolveExportedSignatures(*candidatesOutArray) } func (c *Checker) GetCandidateSignaturesForStringLiteralCompletions(call *ast.CallLikeExpression, editingArgument *ast.Node) []*Signature { diff --git a/tsc/internal/checker/tracer.go b/tsc/internal/checker/tracer.go index 0fea57f8d4d70..14290d807fdc6 100644 --- a/tsc/internal/checker/tracer.go +++ b/tsc/internal/checker/tracer.go @@ -191,7 +191,10 @@ func (a *tracedTypeAdapter) ConditionalTrueType() tracing.TracedType { if a.t.flags&TypeFlagsConditional == 0 { return nil } - t := a.t.AsConditionalType().resolvedTrueType + var t *Type + if ex := a.t.AsConditionalType().extra; ex != nil { + t = ex.resolvedTrueType + } if t == nil { return nil } @@ -202,7 +205,10 @@ func (a *tracedTypeAdapter) ConditionalFalseType() tracing.TracedType { if a.t.flags&TypeFlagsConditional == 0 { return nil } - t := a.t.AsConditionalType().resolvedFalseType + var t *Type + if ex := a.t.AsConditionalType().extra; ex != nil { + t = ex.resolvedFalseType + } if t == nil { return nil } diff --git a/tsc/internal/checker/types.go b/tsc/internal/checker/types.go index 273dfa20290e7..6c3b8c29a7263 100644 --- a/tsc/internal/checker/types.go +++ b/tsc/internal/checker/types.go @@ -194,21 +194,21 @@ type valueSymbolExtra struct { } func (links *ValueSymbolLinks) getWriteType() *Type { - if links.extra == nil { + if links == nil || links.extra == nil { return nil } return links.extra.writeType } func (links *ValueSymbolLinks) getNameType() *Type { - if links.extra == nil { + if links == nil || links.extra == nil { return nil } return links.extra.nameType } func (links *ValueSymbolLinks) getContainingType() *Type { - if links.extra == nil { + if links == nil || links.extra == nil { return nil } return links.extra.containingType @@ -244,11 +244,14 @@ func (links *ValueSymbolLinks) setContainingType(c *Checker, typ *Type) { links.extra.containingType = typ } -// Additional links for mapped symbols - -type MappedSymbolLinks struct { - keyType *Type // Key type for mapped type member - syntheticOrigin *ast.Symbol // For a property on a mapped or spread type, points back to the original property +func (links *ValueSymbolLinks) setMappedInfo(c *Checker, containingType *Type, nameType *Type, keyType *Type, syntheticOrigin *ast.Symbol) { + links.target = syntheticOrigin + if links.extra == nil { + links.extra = c.valueSymbolExtraArena.New() + } + links.extra.containingType = containingType + links.extra.nameType = nameType + links.extra.writeType = keyType } // Additional links for deferred type symbols @@ -299,8 +302,8 @@ type ExportTypeLinks struct { 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) + typeParameters []*Type // Type parameters of type alias (undefined if non-generic) + instantiations typeCacheTable // Instantiations of generic type alias (undefined if non-generic) isConstructorDeclaredProperty bool } @@ -439,6 +442,7 @@ type SymbolNodeLinks struct { type TypeNodeLinks struct { resolvedType *Type // Resolved type associated with node + resolvedFlowType *Type // Result of applying getConditionalFlowTypeOfType to resolvedType outerTypeParameters *[]*Type // Outer type parameters of anonymous object type } @@ -496,9 +500,8 @@ type SourceFileLinks struct { // 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 *Signature // Cached signature of signature node or call expression + effectsOrDecoratorSignature *Signature // Effects signature for call/binary expression or runtime signature for decorated declaration } type TypeFlags uint32 @@ -715,6 +718,8 @@ const ( 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 + ObjectFlagsHasGenericArgumentsComputed = 1 << 30 // HasGenericArguments flag has been computed + ObjectFlagsHasGenericArguments = 1 << 31 // Non-deferred type reference has generic type arguments // 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 @@ -783,11 +788,24 @@ const ( typeKindIntersection ) +type typeCacheFlags uint8 + +const ( + typeCacheIsWeakComputed typeCacheFlags = 1 << iota + typeCacheIsWeak + typeCacheShouldNormalizeComputed + typeCacheShouldNormalize + typeCacheIsReducibleIntersectionComputed + typeCacheIsReducibleIntersection + typeCacheNonNullableUnion +) + type Type struct { flags TypeFlags objectFlags ObjectFlags id TypeId kind typeKind `customlint:"protected"` + cacheFlags typeCacheFlags symbol *ast.Symbol alias *TypeAlias checker *Checker @@ -889,7 +907,7 @@ func (t *Type) AsConditionalType() *ConditionalType { // Casts for embedded struct types func (t *Type) AsConstrainedType() *ConstrainedType { - if t.kind >= typeKindTypeParameter && t.kind <= typeKindIntersection { + if t.kind >= typeKindTypeParameter { // SAFETY: Types of these kinds are structs that embed ConstrainedType at offset zero. return (*ConstrainedType)(unsafe.Pointer(t)) } @@ -897,7 +915,7 @@ func (t *Type) AsConstrainedType() *ConstrainedType { } func (t *Type) AsStructuredType() *StructuredType { - if t.kind >= typeKindObject && t.kind <= typeKindIntersection { + if t.kind >= typeKindObject { // SAFETY: Types of these kinds are structs that embed StructuredType at offset zero. return (*StructuredType)(unsafe.Pointer(t)) } @@ -905,7 +923,7 @@ func (t *Type) AsStructuredType() *StructuredType { } func (t *Type) AsObjectType() *ObjectType { - if t.kind >= typeKindObject && t.kind <= typeKindEvolvingArray { + if t.kind-typeKindObject <= typeKindEvolvingArray-typeKindObject { // SAFETY: Types of these kinds are structs that embed ObjectType at offset zero. return (*ObjectType)(unsafe.Pointer(t)) } @@ -913,7 +931,7 @@ func (t *Type) AsObjectType() *ObjectType { } func (t *Type) AsTypeReference() *TypeReference { - if t.kind >= typeKindTypeReference && t.kind <= typeKindTuple { + if t.kind-typeKindTypeReference <= typeKindTuple-typeKindTypeReference { // SAFETY: Types of these kinds are structs that embed TypeReference at offset zero. return (*TypeReference)(unsafe.Pointer(t)) } @@ -921,7 +939,7 @@ func (t *Type) AsTypeReference() *TypeReference { } func (t *Type) AsInterfaceType() *InterfaceType { - if t.kind >= typeKindInterface && t.kind <= typeKindTuple { + if t.kind-typeKindInterface <= typeKindTuple-typeKindInterface { // SAFETY: Types of these kinds are structs that embed InterfaceType at offset zero. return (*InterfaceType)(unsafe.Pointer(t)) } @@ -929,7 +947,7 @@ func (t *Type) AsInterfaceType() *InterfaceType { } func (t *Type) AsUnionOrIntersectionType() *UnionOrIntersectionType { - if t.kind >= typeKindUnion && t.kind <= typeKindIntersection { + if t.kind >= typeKindUnion { // SAFETY: Types of these kinds are structs that embed UnionOrIntersectionType at offset zero. return (*UnionOrIntersectionType)(unsafe.Pointer(t)) } @@ -948,30 +966,45 @@ func (t *Type) Distributed() []*Type { // Common accessors +var typeTargetOffset = [typeKindIntersection + 1]uint8{ + typeKindObject: uint8(unsafe.Offsetof(ObjectType{}.target)), + typeKindTypeReference: uint8(unsafe.Offsetof(TypeReference{}.target)), + typeKindInterface: uint8(unsafe.Offsetof(InterfaceType{}.target)), + typeKindTuple: uint8(unsafe.Offsetof(TupleType{}.target)), + typeKindInstantiationExpression: uint8(unsafe.Offsetof(InstantiationExpressionType{}.target)), + typeKindMapped: uint8(unsafe.Offsetof(MappedType{}.target)), + typeKindReverseMapped: uint8(unsafe.Offsetof(ReverseMappedType{}.target)), + typeKindEvolvingArray: uint8(unsafe.Offsetof(EvolvingArrayType{}.target)), + typeKindTypeParameter: uint8(unsafe.Offsetof(TypeParameter{}.target)), + typeKindIndex: uint8(unsafe.Offsetof(IndexType{}.target)), + typeKindStringMapping: uint8(unsafe.Offsetof(StringMappingType{}.target)), +} + func (t *Type) Target() *Type { - switch { - case t.flags&TypeFlagsObject != 0: - return t.AsObjectType().target - case t.flags&TypeFlagsTypeParameter != 0: - return t.AsTypeParameter().target - case t.flags&TypeFlagsIndex != 0: - return t.AsIndexType().target - case t.flags&TypeFlagsStringMapping != 0: - return t.AsStringMappingType().target - case t.flags&TypeFlagsObject != 0 && t.objectFlags&ObjectFlagsMapped != 0: - return t.AsMappedType().target + if off := typeTargetOffset[t.kind]; off != 0 { + // SAFETY: typeTargetOffset stores the byte offset of target within the concrete type struct for t.kind. + return *(**Type)(unsafe.Add(unsafe.Pointer(&t.flags), off)) } panic("Unhandled case in Type.Target") } +var typeMapperOffset = [typeKindIntersection + 1]uint8{ + typeKindObject: uint8(unsafe.Offsetof(ObjectType{}.mapper)), + typeKindTypeReference: uint8(unsafe.Offsetof(TypeReference{}.mapper)), + typeKindInterface: uint8(unsafe.Offsetof(InterfaceType{}.mapper)), + typeKindTuple: uint8(unsafe.Offsetof(TupleType{}.mapper)), + typeKindInstantiationExpression: uint8(unsafe.Offsetof(InstantiationExpressionType{}.mapper)), + typeKindMapped: uint8(unsafe.Offsetof(MappedType{}.mapper)), + typeKindReverseMapped: uint8(unsafe.Offsetof(ReverseMappedType{}.mapper)), + typeKindEvolvingArray: uint8(unsafe.Offsetof(EvolvingArrayType{}.mapper)), + typeKindTypeParameter: uint8(unsafe.Offsetof(TypeParameter{}.mapper)), + typeKindConditional: uint8(unsafe.Offsetof(ConditionalType{}.mapper)), +} + func (t *Type) Mapper() *TypeMapper { - switch { - case t.flags&TypeFlagsObject != 0: - return t.AsObjectType().mapper - case t.flags&TypeFlagsTypeParameter != 0: - return t.AsTypeParameter().mapper - case t.flags&TypeFlagsConditional != 0: - return t.AsConditionalType().mapper + if off := typeMapperOffset[t.kind]; off != 0 { + // SAFETY: typeMapperOffset stores the byte offset of mapper within the concrete type struct for t.kind. + return *(**TypeMapper)(unsafe.Add(unsafe.Pointer(&t.flags), off)) } panic("Unhandled case in Type.Mapper") } @@ -1122,71 +1155,81 @@ func (t *ConstrainedType) AsConstrainedType() *ConstrainedType { return t } type StructuredType struct { ConstrainedType - members ast.SymbolTable - properties []*ast.Symbol - extra *structuredTypeExtra + extra *structuredTypeExtra } type structuredTypeExtra struct { - signatures []*Signature // Signatures (call + construct) - callSignatureCount int // Count of call signatures - indexInfos []*IndexInfo - + members *memberIndex + properties []*ast.Symbol + signatures []*Signature // Signatures (call + construct) + callSignatureCount int // Count of call signatures + indexInfos []*IndexInfo objectTypeWithoutAbstractConstructSignatures *Type + regularType *Type } func (t *StructuredType) AsStructuredType() *StructuredType { return t } func (t *StructuredType) CallSignatures() []*Signature { - if t.extra == nil { + if t.extra == nil || t.extra.callSignatureCount == 0 { return nil } return slices.Clip(t.extra.signatures[:t.extra.callSignatureCount]) } func (t *StructuredType) ConstructSignatures() []*Signature { - if t.extra == nil { + if t.extra == nil || len(t.extra.signatures) == t.extra.callSignatureCount { return nil } return slices.Clip(t.extra.signatures[t.extra.callSignatureCount:]) } -func (t *StructuredType) getMember(name string) *ast.Symbol { - if t.members != nil { - return t.members.Get(name) - } - lazy := t.getLazyState() - if lazy == nil { + +func (t *StructuredType) getSignatures() []*Signature { + if t.extra == nil { return nil } - memberInfo := lazy.layout.memberInfoMap[name] - if memberInfo == nil { + return t.extra.signatures +} + +func (t *StructuredType) getIndexInfos() []*IndexInfo { + if t.extra == nil { return nil } - return t.getPropertyAt(int(memberInfo.slotIndex)) + return t.extra.indexInfos } -func (t *StructuredType) getMembers() ast.SymbolTable { - if t.members == nil { - if properties := t.getProperties(); len(properties) != 0 { - t.members = ast.NewSymbolTableWithCapacity(len(properties)) - for _, member := range properties { - t.members.Set(member.Name(), member) - } - } +func (t *StructuredType) ensureExtra() *structuredTypeExtra { + if t.extra == nil { + t.extra = t.checker.structuredTypeExtraArena.New() + } + return t.extra +} + +func (t *StructuredType) Properties() []*ast.Symbol { + return t.getProperties() +} + +func (t *StructuredType) getLazyState() *LazyState { + if t.objectFlags&(ObjectFlagsClassOrInterface|ObjectFlagsReference) != 0 { + return t.AsType().AsTypeReference().lazy } - return t.members + return nil } func (t *StructuredType) getPropertyAt(index int) *ast.Symbol { lazy := t.getLazyState() if lazy == nil { - return t.properties[index] + if t.extra == nil { + return nil + } + return t.extra.properties[index] } - if t.properties == nil { - t.properties = make([]*ast.Symbol, len(lazy.layout.memberInfos)) + ex := t.ensureExtra() + if ex.properties == nil { + ex.properties = t.checker.symbolListArena.NewSlice(len(lazy.layout.memberInfos)) } - member := t.properties[index] + member := ex.properties[index] if member == nil { memberInfo := lazy.layout.memberInfos[index] if memberInfo.baseIndex < 0 { @@ -1194,23 +1237,32 @@ func (t *StructuredType) getPropertyAt(index int) *ast.Symbol { } else { member = t.checker.getPropertyOfType(lazy.baseTypes[memberInfo.baseIndex], memberInfo.symbol.Name()) } - t.properties[index] = member + ex.properties[index] = member } return member } func (t *StructuredType) getProperties() []*ast.Symbol { if lazy := t.getLazyState(); lazy != nil && !lazy.propertiesResolved { - for i := range lazy.layout.memberInfos { - t.getPropertyAt(i) + if n := len(lazy.layout.memberInfos); n != 0 { + for i := range n { + t.getPropertyAt(i) + } } lazy.propertiesResolved = true } - return t.AsStructuredType().properties + if t.extra == nil { + return nil + } + return t.extra.properties +} + +func (t *StructuredType) getPropertiesSlice() []*ast.Symbol { + return t.getProperties() } func (t *StructuredType) hasProperties() bool { - if len(t.properties) != 0 { + if t.extra != nil && len(t.extra.properties) != 0 { return true } lazy := t.getLazyState() @@ -1220,36 +1272,227 @@ func (t *StructuredType) hasProperties() bool { return len(lazy.layout.memberInfos) != 0 } -func (t *StructuredType) getLazyState() *LazyState { - if t.objectFlags&(ObjectFlagsClassOrInterface|ObjectFlagsReference) != 0 { - return t.AsType().AsTypeReference().lazy +func (t *StructuredType) getMembersIndex() *memberIndex { + if t.extra == nil { + return nil } - return nil + return t.extra.members } -func (t *StructuredType) getSignatures() []*Signature { - if t.extra == nil { +func (t *StructuredType) setMembersAndProperties(members *memberIndex, properties []*ast.Symbol) { + if members == nil && len(properties) == 0 && t.extra == nil { + return + } + ex := t.ensureExtra() + ex.members = members + ex.properties = properties +} + +type memberIndex struct { + slots []uint32 + fallback ast.SymbolTable +} + +// Property order is semantic. Keep it unchanged and index it with pointer-free +// slots instead of retaining a second map of names and symbol pointers. +func (c *Checker) newMemberIndex(members ast.SymbolTable, properties []*ast.Symbol, index *memberIndex) *memberIndex { + if members.Len() != len(properties) || !symbolTableKeysMatchNames(members) { + if index == nil { + index = c.memberIndexArena.New() + } + index.fallback = members + return index + } + if index != nil { + index.fallback = nil + } + return c.newPropertyIndex(properties, index) +} + +// Reports whether every symbol is stored under its own name. Checking the +// table's pairs, rather than looking each property up by name, also rejects a +// symbol aliased under a second key, which a name-keyed index would drop. +func symbolTableKeysMatchNames(members ast.SymbolTable) bool { + return !members.HasNameProxies() +} + +func hashMemberName(s string) uint64 { + n := len(s) + if n == 0 { + return 0 + } + // SAFETY: StringData points to the immutable n-byte backing buffer of s (n >= 1). + p := unsafe.StringData(s) + var h uint64 + if n >= 8 { + // SAFETY: n >= 8 guarantees bytes [0..8) and [n-8..n) are in bounds of s. + h = *(*uint64)(unsafe.Pointer(p)) ^ (*(*uint64)(unsafe.Add(unsafe.Pointer(p), n-8)) * 0x9e3779b97f4a7c15) + } else if n >= 4 { + // SAFETY: n >= 4 guarantees bytes [0..4) and [n-4..n) are in bounds of s. + h = uint64(*(*uint32)(unsafe.Pointer(p))) | (uint64(*(*uint32)(unsafe.Add(unsafe.Pointer(p), n-4))) << 32) + } else { + // SAFETY: n >= 1 guarantees indices 0, n>>1, and n-1 are in bounds of s. + h = uint64(*p) | (uint64(*(*byte)(unsafe.Add(unsafe.Pointer(p), n>>1))) << 8) | (uint64(*(*byte)(unsafe.Add(unsafe.Pointer(p), n-1))) << 16) + } + return (h ^ uint64(n)) * 0xbf58476d1ce4e5b9 +} + +func (c *Checker) newPropertyIndex(properties []*ast.Symbol, index *memberIndex) *memberIndex { + if len(properties) <= 3 { + if index != nil { + c.memberIndexArena.Pop(index) + } return nil } - return t.extra.signatures + if uint64(len(properties)) > uint64(^uint32(0)) { + panic("Member index exhausted") + } + if index == nil { + index = c.memberIndexArena.New() + } + size := 1 << bits.Len(uint(len(properties))*2-1) + index.slots = c.memberIndexSlotsArena.NewSlice(size) + mask := uint64(size - 1) + if len(properties) < 0xffff { + for i, symbol := range properties { + h := hashMemberName(symbol.Name()) + tag := uint32(h>>16) & 0xffff0000 + slot := h & mask + for index.slots[slot] != 0 { + slot = (slot + 1) & mask + } + index.slots[slot] = uint32(i+1) | tag + } + } else { + for i, symbol := range properties { + slot := hashMemberName(symbol.Name()) & mask + for index.slots[slot] != 0 { + slot = (slot + 1) & mask + } + index.slots[slot] = uint32(i + 1) + } + } + return index } -func (t *StructuredType) getIndexInfos() []*IndexInfo { +func (t *StructuredType) getMember(name string) *ast.Symbol { + if lazy := t.getLazyState(); lazy != nil { + if t.extra != nil && t.extra.members != nil && t.extra.members.fallback != nil { + return t.extra.members.fallback.Get(name) + } + slotIndex := lazy.layout.lookupMemberIndex(name) + if slotIndex < 0 { + return nil + } + return t.getPropertyAt(slotIndex) + } if t.extra == nil { return nil } - return t.extra.indexInfos + properties := t.extra.properties + members := t.extra.members + if members == nil { + for _, symbol := range properties { + if symbol.Name() == name { + return symbol + } + } + return nil + } + if members.fallback != nil { + return members.fallback.Get(name) + } + slots := members.slots + if len(slots) == 0 { + return nil + } + return t.getMemberFromSlots(name, hashMemberName(name), properties, slots) } -func (t *StructuredType) ensureExtra() *structuredTypeExtra { +func (t *StructuredType) getMemberWithHash(name string, h uint64) *ast.Symbol { + if lazy := t.getLazyState(); lazy != nil { + if t.extra != nil && t.extra.members != nil && t.extra.members.fallback != nil { + return t.extra.members.fallback.Get(name) + } + slotIndex := lazy.layout.lookupMemberIndex(name) + if slotIndex < 0 { + return nil + } + return t.getPropertyAt(slotIndex) + } if t.extra == nil { - t.extra = t.checker.structuredTypeExtraArena.New() + return nil + } + properties := t.extra.properties + members := t.extra.members + if members == nil { + for _, symbol := range properties { + if symbol.Name() == name { + return symbol + } + } + return nil + } + if members.fallback != nil { + return members.fallback.Get(name) + } + slots := members.slots + if len(slots) == 0 { + return nil + } + return t.getMemberFromSlots(name, h, properties, slots) +} + +func (t *StructuredType) getMemberFromSlots(name string, h uint64, properties []*ast.Symbol, slots []uint32) *ast.Symbol { + mask := uint64(len(slots) - 1) + slot := h & mask + if len(properties) < 0xffff { + tag := uint32(h>>16) & 0xffff0000 + for { + entry := slots[slot] + if entry == 0 { + return nil + } + if (entry & 0xffff0000) == tag { + symbol := properties[(entry&0xffff)-1] + if symbol.Name() == name { + return symbol + } + } + slot = (slot + 1) & mask + } + } + for { + index := slots[slot] + if index == 0 { + return nil + } + symbol := properties[index-1] + if symbol.Name() == name { + return symbol + } + slot = (slot + 1) & mask } - return t.extra } -func (t *StructuredType) Properties() []*ast.Symbol { - return t.getProperties() +func (t *StructuredType) getMembers() ast.SymbolTable { + if t.extra != nil && t.extra.members != nil && t.extra.members.fallback != nil { + return t.extra.members.fallback + } + properties := t.getProperties() + if len(properties) == 0 { + return nil + } + members := ast.NewSymbolTableWithCapacity(len(properties)) + for _, symbol := range properties { + members.Set(symbol.Name(), symbol) + } + ex := t.ensureExtra() + if ex.members == nil { + ex.members = t.checker.memberIndexArena.New() + } + ex.members.fallback = members + return members } // Except for tuple type references and reverse mapped types, all object types have an associated symbol. @@ -1287,22 +1530,15 @@ 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 *typeCacheTable // Map of type instantiations } func (t *ObjectType) AsObjectType() *ObjectType { return t } // TypeReference (instantiation of an InterfaceType) -type TypeReference struct { - ObjectType - node *ast.Node // TypeReferenceNode | ArrayTypeNode | TupleTypeNode when deferred, else nil - resolvedTypeArguments []*Type - lazy *LazyState // Resolved member layout and instantiation state -} - type LazyState struct { layout *MemberLayout mapper *TypeMapper @@ -1311,16 +1547,45 @@ type LazyState struct { } type MemberLayout struct { - id uint32 // Unique identifier for the member layout - memberInfoMap map[string]*MemberInfo // MemberInfos keyed by property name - memberInfos []MemberInfo // MemberInfos in sorted order - layoutProperties []*ast.Symbol // Lazily created list of layout properties (symbols corresponding to memberInfos) + id uint32 // Unique identifier for the member layout + memberInfoMap map[string]int32 // 1-based slotIndex keyed by property name (nil when len(memberInfos) <= 3) + memberInfos []MemberInfo // MemberInfos in sorted order + layoutProperties []*ast.Symbol // Lazily created list of layout properties (symbols corresponding to memberInfos) +} + +func (l *MemberLayout) lookupMemberIndex(name string) int { + if l.memberInfoMap != nil { + return int(l.memberInfoMap[name]) - 1 + } + for i := range l.memberInfos { + if l.memberInfos[i].symbol.Name() == name { + return i + } + } + return -1 } type MemberInfo struct { symbol *ast.Symbol // Uninstantiated symbol baseIndex int32 // Index of base type with symbol, or -1 if locally declared - slotIndex int32 // Index of resolved symbol in members array +} + +type lazyMappedTable struct { + typeParameter *Type + modifiersType *Type + templateModifiers MappedTypeModifiers + shouldLinkPropDeclarations bool + members map[string]*ast.Symbol + indexInfos []*IndexInfo + indexInfosReady bool + resolving bool +} + +type TypeReference struct { + ObjectType + node *ast.Node // TypeReferenceNode | ArrayTypeNode | TupleTypeNode when deferred, else nil + resolvedTypeArguments []*Type + lazy *LazyState // Resolved member layout and instantiation state } func (t *TypeReference) AsTypeReference() *TypeReference { return t } @@ -1334,6 +1599,8 @@ type InterfaceType struct { thisType *Type // The "this" type (nil if none) baseTypesResolved bool declaredMembersResolved bool + declaredMemberLayoutCount int32 + declaredMemberLayout *MemberLayout resolvedBaseConstructorType *Type resolvedBaseTypes []*Type declaredMembers ast.SymbolTable // Declared members @@ -1431,6 +1698,7 @@ type MappedType struct { templateType *Type modifiersType *Type resolvedApparentType *Type + lazy *lazyMappedTable containsError bool } @@ -1469,7 +1737,6 @@ type UnionOrIntersectionType struct { types []*Type propertyCache ast.SymbolTable // Augmented entries, except complete ones already in the non-augmented cache. propertyCacheWithoutFunctionPropertyAugment ast.SymbolTable - resolvedProperties []*ast.Symbol } func (t *UnionOrIntersectionType) AsUnionOrIntersectionType() *UnionOrIntersectionType { return t } @@ -1483,18 +1750,14 @@ func (t *UnionOrIntersectionType) Types() []*Type { type UnionType struct { UnionOrIntersectionType resolvedReducedType *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 - constituentMap map[*Type]*Type // Constituents keyed by unit type discriminants + origin *Type // Denormalized union, intersection, or index type in which union originates } // IntersectionType type IntersectionType struct { UnionOrIntersectionType - resolvedApparentType *Type - uniqueLiteralFilledInstantiation *Type // Instantiation with type parameters mapped to never type + resolvedApparentType *Type } // TypeParameter @@ -1577,24 +1840,35 @@ type ConditionalRoot struct { isDistributive bool inferTypeParameters []*Type outerTypeParameters []*Type - instantiations map[CacheHashKey]*Type + instantiations typeCacheTable alias *TypeAlias } -type ConditionalType struct { - ConstrainedType - root *ConditionalRoot - checkType *Type - extendsType *Type +type conditionalTypeExtra struct { resolvedTrueType *Type resolvedFalseType *Type resolvedInferredTrueType *Type // The `trueType` instantiated with the `combinedMapper`, if present resolvedDefaultConstraint *Type resolvedConstraintOfDistributive *Type - mapper *TypeMapper combinedMapper *TypeMapper } +type ConditionalType struct { + ConstrainedType + root *ConditionalRoot + checkType *Type + extendsType *Type + mapper *TypeMapper + extra *conditionalTypeExtra +} + +func (t *ConditionalType) ensureExtra(c *Checker) *conditionalTypeExtra { + if t.extra == nil { + t.extra = c.conditionalTypeExtraArena.New() + } + return t.extra +} + func (t *ConditionalType) CheckType() *Type { return t.checkType } func (t *ConditionalType) ExtendsType() *Type { return t.extendsType } @@ -1616,6 +1890,9 @@ const ( SignatureFlagsIsNonInferrable SignatureFlags = 1 << 7 // Indicates signature comes from a non-inferrable type SignatureFlagsIsSignatureCandidateForOverloadFailure SignatureFlags = 1 << 8 signatureFlagsNoTypePredicate SignatureFlags = 1 << 9 + signatureFlagsUninstantiatedParameters SignatureFlags = 1 << 10 + signatureFlagsUninstantiatedThisParameter SignatureFlags = 1 << 11 + signatureFlagsInternal SignatureFlags = signatureFlagsNoTypePredicate | signatureFlagsUninstantiatedParameters | signatureFlagsUninstantiatedThisParameter // We do not propagate `IsInnerCallChain` or `IsOuterCallChain` to instantiated signatures, as that would result in us // attempting to add `| undefined` on each recursive call to `getReturnTypeOfSignature` when // instantiating the return type. @@ -1633,6 +1910,7 @@ type Signature struct { declaration *ast.Node parameters []*ast.Symbol resolvedReturnType *Type + firstParamType *Type target *Signature mapper *TypeMapper extra *signatureExtra @@ -1644,6 +1922,7 @@ type signatureExtra struct { resolvedTypePredicate *TypePredicate isolatedSignatureType *Type composite *CompositeSignature + paramTypes []*Type } func (s *Signature) ensureExtra(c *Checker) *signatureExtra { @@ -1660,6 +1939,7 @@ func (s *Signature) setTypeParameters(c *Checker, types []*Type) { } func (s *Signature) setThisParameter(c *Checker, parameter *ast.Symbol) { + s.flags &^= signatureFlagsUninstantiatedThisParameter if parameter != nil || s.extra != nil { s.ensureExtra(c).thisParameter = parameter } @@ -1711,7 +1991,7 @@ func (s *Signature) Id() SignatureId { } func (s *Signature) Flags() SignatureFlags { - return s.flags &^ signatureFlagsNoTypePredicate + return s.flags &^ signatureFlagsInternal } func (s *Signature) TypeParameters() []*Type { diff --git a/tsc/internal/checker/types_test.go b/tsc/internal/checker/types_test.go index d19d10419bcd8..c937d17fbb0aa 100644 --- a/tsc/internal/checker/types_test.go +++ b/tsc/internal/checker/types_test.go @@ -98,7 +98,18 @@ func TestTypeCasts(t *testing.T) { typ.flags = TypeFlagsNever typ.objectFlags = ObjectFlagsReference assert.Equal(t, test.cast(typ), any(test.data)) - assert.Assert(t, didPanic(func() { typ.AsLiteralType() }) == (test.kind != typeKindLiteral)) + for _, target := range tests { + switch target.kind { + case typeKindObject: + assert.Equal(t, target.cast(typ), any(test.data.AsObjectType())) + case typeKindTypeReference: + assert.Equal(t, target.cast(typ), any(test.data.AsTypeReference())) + case typeKindInterface: + assert.Equal(t, target.cast(typ), any(test.data.AsInterfaceType())) + default: + assert.Assert(t, didPanic(func() { target.cast(typ) }) == (test.kind != target.kind)) + } + } } } @@ -178,9 +189,7 @@ func BenchmarkTypeListKey(b *testing.B) { func newCompositeTestChecker() (*Checker, []*Type) { c := &Checker{ strictNullChecks: true, - unionTypes: make(map[CacheHashKey]*Type), unionOfUnionTypes: make(map[UnionOfUnionKey]*Type), - intersectionTypes: make(map[CacheHashKey]*Type), subtypeReductionCache: make(map[CacheHashKey][]*Type), } c.compareSymbols = c.compareSymbolsWorker @@ -448,10 +457,30 @@ func TestCompactTypeMapperLayout(t *testing.T) { c, types := newCompositeTestChecker() mapper := newSimpleTypeMapper(types[0], types[1]) assert.Assert(t, castTypeMapper[SimpleTypeMapper](mapper, TypeMapperKindSimple).source == types[0]) - func() { - defer func() { assert.Assert(t, recover() != nil) }() - castTypeMapper[ArrayTypeMapper](mapper, TypeMapperKindArray) - }() + context := c.newInferenceContextWorker(nil, nil, InferenceFlagsNone, nil) + tests := []struct { + kind TypeMapperKind + mapper *TypeMapper + cast func(*TypeMapper) any + }{ + {TypeMapperKindSimple, mapper, func(m *TypeMapper) any { return castTypeMapper[SimpleTypeMapper](m, TypeMapperKindSimple) }}, + {TypeMapperKindArray, newArrayTypeMapper(nil, nil), func(m *TypeMapper) any { return castTypeMapper[ArrayTypeMapper](m, TypeMapperKindArray) }}, + {TypeMapperKindArrayToSingle, newArrayToSingleTypeMapper(nil, nil), func(m *TypeMapper) any { + return castTypeMapper[ArrayToSingleTypeMapper](m, TypeMapperKindArrayToSingle) + }}, + {TypeMapperKindDeferred, newDeferredTypeMapper(nil, nil), func(m *TypeMapper) any { return castTypeMapper[DeferredTypeMapper](m, TypeMapperKindDeferred) }}, + {TypeMapperKindFunction, newFunctionTypeMapper(func(t *Type) *Type { return t }), func(m *TypeMapper) any { return castTypeMapper[FunctionTypeMapper](m, TypeMapperKindFunction) }}, + {TypeMapperKindMerged, newMergedTypeMapper(mapper, mapper), func(m *TypeMapper) any { return castTypeMapper[MergedTypeMapper](m, TypeMapperKindMerged) }}, + {TypeMapperKindComposite, newCompositeTypeMapper(c, mapper, mapper), func(m *TypeMapper) any { return castTypeMapper[CompositeTypeMapper](m, TypeMapperKindComposite) }}, + {TypeMapperKindInference, context.mapper, func(m *TypeMapper) any { return castTypeMapper[InferenceTypeMapper](m, TypeMapperKindInference) }}, + } + runtime.GC() + for _, test := range tests { + assert.Equal(t, test.mapper.kind, test.kind) + for _, target := range tests { + assert.Assert(t, didPanic(func() { target.cast(test.mapper) }) == (test.kind != target.kind)) + } + } runtime.GC() assert.Equal(t, mapper.Map(types[0]), types[1]) runtime.KeepAlive(c) @@ -514,6 +543,56 @@ func TestCompactValueSymbolLinksLayout(t *testing.T) { assert.Equal(t, links.getContainingType(), types[2]) } +func TestStructuredMembersLookup(t *testing.T) { + t.Parallel() + c, _ := newCompositeTestChecker() + for _, count := range []int{0, 1, 8, 20, 300} { + members := ast.NewSymbolTableWithCapacity(count) + for i := range count { + name := strconv.Itoa(i) + members.Set(name, c.newSymbol(ast.SymbolFlagsProperty, name)) + } + typ := c.newAnonymousType(nil, members, nil, nil, nil) + resolved := typ.AsStructuredType() + assert.Equal(t, len(resolved.Properties()), count) + for name, symbol := range members.All() { + assert.Equal(t, resolved.getMember(name), symbol) + } + assert.Assert(t, resolved.getMember("missing") == nil) + other := c.newAnonymousType(nil, resolved.getMembers(), nil, nil, nil) + for name, symbol := range members.All() { + assert.Equal(t, other.AsStructuredType().getMember(name), symbol) + } + members.Set("new", c.newSymbol(ast.SymbolFlagsProperty, "new")) + assert.Assert(t, resolved.getMember("new") == nil) + members.Set(ast.InternalSymbolNameCall, c.newSymbol(ast.SymbolFlagsSignature, ast.InternalSymbolNameCall)) + withReserved := c.newAnonymousType(nil, members, nil, nil, nil).AsStructuredType() + assert.Equal(t, withReserved.getMember(ast.InternalSymbolNameCall), members.Get(ast.InternalSymbolNameCall)) + for range 300 { + c.newMemberIndex(members, withReserved.Properties(), nil) + } + runtime.GC() + for name, symbol := range members.All() { + assert.Equal(t, withReserved.getMember(name), symbol) + } + } + renamed := c.newSymbol(ast.SymbolFlagsProperty, "original") + member := c.newAnonymousType(nil, ast.SymbolTableOf("different", renamed), nil, nil, nil).AsStructuredType() + assert.Equal(t, member.getMember("different"), renamed) + assert.Assert(t, member.getMember("original") == nil) + aliased := c.newSymbol(ast.SymbolFlagsProperty, "original") + aliasedMembers := ast.NewSymbolTable() + aliasedMembers.Set("original", aliased) + aliasedMembers.Set("different", aliased) + for i := range 10 { + name := "p" + strconv.Itoa(i) + aliasedMembers.Set(name, c.newSymbol(ast.SymbolFlagsProperty, name)) + } + aliasedType := c.newAnonymousType(nil, aliasedMembers, nil, nil, nil).AsStructuredType() + for name, symbol := range aliasedMembers.All() { + assert.Equal(t, aliasedType.getMember(name), symbol) + } +} func TestUnionIntersectionPropertyCacheSharing(t *testing.T) { t.Parallel() for _, intersection := range []bool{false, true} { @@ -596,7 +675,7 @@ func TestUnionIntersectionPropertyCacheModes(t *testing.T) { func TestCompactStructuredTypeLayout(t *testing.T) { t.Parallel() - assert.Equal(t, unsafe.Sizeof(StructuredType{}), unsafe.Sizeof(ConstrainedType{})+5*unsafe.Sizeof(uintptr(0))) + assert.Equal(t, unsafe.Sizeof(StructuredType{}), unsafe.Sizeof(ConstrainedType{})+1*unsafe.Sizeof(uintptr(0))) } func TestStructuredTypeExtra(t *testing.T) { @@ -646,7 +725,7 @@ func TestStructuredTypeExtra(t *testing.T) { func TestCompactTypeNodeLinksLayout(t *testing.T) { t.Parallel() - assert.Equal(t, unsafe.Sizeof(TypeNodeLinks{}), 2*unsafe.Sizeof(uintptr(0))) + assert.Equal(t, unsafe.Sizeof(TypeNodeLinks{}), 3*unsafe.Sizeof(uintptr(0))) c, types := newCompositeTestChecker() var links TypeNodeLinks links.resolvedType = types[0] @@ -678,8 +757,8 @@ func TestCompactTypeNodeLinksLayout(t *testing.T) { func TestCompactSignatureLayout(t *testing.T) { t.Parallel() - assert.Equal(t, unsafe.Sizeof(Signature{}), 16+8*unsafe.Sizeof(uintptr(0))) - assert.Equal(t, unsafe.Sizeof(signatureExtra{}), 7*unsafe.Sizeof(uintptr(0))) + assert.Equal(t, unsafe.Sizeof(Signature{}), 16+9*unsafe.Sizeof(uintptr(0))) + assert.Equal(t, unsafe.Sizeof(signatureExtra{}), 10*unsafe.Sizeof(uintptr(0))) } func TestSignatureExtra(t *testing.T) { diff --git a/tsc/internal/checker/utilities.go b/tsc/internal/checker/utilities.go index c4cc97fa29a1b..3417bb203b8d1 100644 --- a/tsc/internal/checker/utilities.go +++ b/tsc/internal/checker/utilities.go @@ -358,8 +358,102 @@ func createSymbolTable(symbols []*ast.Symbol) ast.SymbolTable { return result } +type symbolSortEntry struct { + key uint64 + symbol *ast.Symbol +} + +type nodeFileIndexEntry struct { + node *ast.Node + file *ast.SourceFile + fileIndex int +} + +func (c *Checker) getNodeFileAndIndex(n *ast.Node) (*ast.SourceFile, int) { + if c.nodeFileIndexCache == nil { + c.nodeFileIndexCache = new([4096]nodeFileIndexEntry) + } + if p := n.Parent; p != nil { + pslot := (uint32(p.Pos())*2654435761 ^ uint32(p.End()) ^ uint32(p.Kind)) & 4095 + pentry := &c.nodeFileIndexCache[pslot] + if pentry.node == p { + return pentry.file, pentry.fileIndex + } + sf := ast.GetSourceFileOfNode(p) + idx := c.fileIndexMap[sf] + *pentry = nodeFileIndexEntry{node: p, file: sf, fileIndex: idx} + return sf, idx + } + slot := (uint32(n.Pos())*2654435761 ^ uint32(n.End()) ^ uint32(n.Kind)) & 4095 + entry := &c.nodeFileIndexCache[slot] + if entry.node == n { + return entry.file, entry.fileIndex + } + sf := ast.GetSourceFileOfNode(n) + idx := c.fileIndexMap[sf] + *entry = nodeFileIndexEntry{node: n, file: sf, fileIndex: idx} + return sf, idx +} + func (c *Checker) sortSymbols(symbols []*ast.Symbol) { - slices.SortFunc(symbols, c.compareSymbols) + n := len(symbols) + if n <= 1 { + return + } + if n == 2 || len(c.files) == 0 { + slices.SortFunc(symbols, c.compareSymbols) + return + } + buf := c.symbolSortBuf + if cap(buf) < n { + buf = make([]symbolSortEntry, n) + } else { + buf = buf[:n] + } + var lastParent *ast.Node + var lastFileIndex uint64 + var prevKey uint64 + alreadySorted := true + for i, s := range symbols { + var key uint64 + if s == nil { + key = ^uint64(0) + } else if d := s.FirstDeclaration(); d != nil { + var fileIndex uint64 + if d.Parent != nil && d.Parent == lastParent { + fileIndex = lastFileIndex + } else { + _, idx := c.getNodeFileAndIndex(d) + fileIndex = uint64(uint32(idx)) + lastParent = d.Parent + lastFileIndex = fileIndex + } + key = (fileIndex << 32) | uint64(uint32(d.Pos())) + } else { + key = uint64(^uint32(0)) << 32 + } + if i > 0 && key <= prevKey { + alreadySorted = false + } + prevKey = key + buf[i] = symbolSortEntry{key: key, symbol: s} + } + if !alreadySorted { + slices.SortFunc(buf, func(a, b symbolSortEntry) int { + if a.key < b.key { + return -1 + } + if a.key > b.key { + return 1 + } + return c.compareSymbolsWorker(a.symbol, b.symbol) + }) + for i, e := range buf { + symbols[i] = e.symbol + } + } + clear(buf) + c.symbolSortBuf = buf[:0] } func (c *Checker) compareSymbolsWorker(s1, s2 *ast.Symbol) int { @@ -372,13 +466,14 @@ func (c *Checker) compareSymbolsWorker(s1, s2 *ast.Symbol) int { if s2 == nil { return -1 } - if len(s1.Declarations()) != 0 && len(s2.Declarations()) != 0 { - if r := c.compareNodes(s1.Declarations()[0], s2.Declarations()[0]); r != 0 { + d1, d2 := s1.FirstDeclaration(), s2.FirstDeclaration() + if d1 != nil && d2 != nil { + if r := c.compareNodes(d1, d2); r != 0 { return r } - } else if len(s1.Declarations()) != 0 { + } else if d1 != nil { return -1 - } else if len(s2.Declarations()) != 0 { + } else if d2 != nil { return 1 } if r := strings.Compare(s1.Name(), s2.Name()); r != 0 { @@ -399,14 +494,12 @@ func (c *Checker) compareNodes(n1, n2 *ast.Node) int { if n2 == nil { return -1 } - if n1.Parent != nil && n1.Parent == n2.Parent { + if n1.Parent != nil && n2.Parent != nil && (n1.Parent == n2.Parent || (n1.Parent.Parent != nil && n1.Parent.Parent == n2.Parent.Parent)) { return n1.Pos() - n2.Pos() } - s1 := ast.GetSourceFileOfNode(n1) - s2 := ast.GetSourceFileOfNode(n2) + s1, f1 := c.getNodeFileAndIndex(n1) + s2, f2 := c.getNodeFileAndIndex(n2) if s1 != s2 { - f1 := c.fileIndexMap[s1] - f2 := c.fileIndexMap[s2] // Order by index of file in the containing program return f1 - f2 } @@ -431,11 +524,22 @@ func CompareTypes(t1, t2 *Type) int { if c := getSortOrderFlags(t1) - getSortOrderFlags(t2); c != 0 { return c } + if t1.flags == TypeFlagsStringLiteral && t2.flags == TypeFlagsStringLiteral && t1.alias == nil && t2.alias == nil { + if c := strings.Compare(t1.AsLiteralType().value.(string), t2.AsLiteralType().value.(string)); c != 0 { + return c + } + return int(t1.id) - int(t2.id) + } + if t1.flags&(TypeFlagsEnum|TypeFlagsEnumLiteral) != 0 && t1.alias == nil && t2.alias == nil { + if c := t1.checker.compareSymbols(t1.symbol, t2.symbol); c != 0 { + return c + } + return int(t1.id) - int(t2.id) + } // Order named types by name and, in the case of aliased types, by alias type arguments. if c := compareTypeNames(t1, t2); c != 0 { return c } - // We have unnamed types or types with identical names. Now sort by data specific to the type. switch { case t1.flags&(TypeFlagsAny|TypeFlagsUnknown|TypeFlagsString|TypeFlagsNumber|TypeFlagsBoolean|TypeFlagsBigInt|TypeFlagsESSymbol|TypeFlagsVoid|TypeFlagsUndefined|TypeFlagsNull|TypeFlagsNever|TypeFlagsNonPrimitive) != 0: // Only distinguished by type IDs, handled below. @@ -444,11 +548,11 @@ func CompareTypes(t1, t2 *Type) int { // Order other unnamed or identically named object types by symbol. if t1.objectFlags&ObjectFlagsInstantiationExpressionType != 0 && t2.objectFlags&ObjectFlagsInstantiationExpressionType != 0 { var declaration1, declaration2 *ast.Node - if t1.symbol != nil && len(t1.symbol.Declarations()) != 0 { - declaration1 = t1.symbol.Declarations()[0] + if t1.symbol != nil { + declaration1 = t1.symbol.FirstDeclaration() } - if t2.symbol != nil && len(t2.symbol.Declarations()) != 0 { - declaration2 = t2.symbol.Declarations()[0] + if t2.symbol != nil { + declaration2 = t2.symbol.FirstDeclaration() } // A single instantiation expression can produce multiple types for union constituents, // so compare their source declarations before comparing the shared expression node. @@ -458,8 +562,10 @@ func CompareTypes(t1, t2 *Type) int { if c := t1.checker.compareNodes(t1.AsInstantiationExpressionType().node, t2.AsInstantiationExpressionType().node); c != 0 { return c } - } else if c := t1.checker.compareSymbols(t1.symbol, t2.symbol); c != 0 { - return c + } else if t1.symbol != t2.symbol { + if c := t1.checker.compareSymbols(t1.symbol, t2.symbol); c != 0 { + return c + } } // When object types have the same or no symbol, order by kind. We order type references before other kinds. if t1.objectFlags&ObjectFlagsReference != 0 && t2.objectFlags&ObjectFlagsReference != 0 { @@ -636,6 +742,9 @@ func compareTypeNames(t1, t2 *Type) int { s1 := getTypeNameSymbol(t1) s2 := getTypeNameSymbol(t2) if s1 == s2 { + if t1.alias == t2.alias { + return 0 + } return compareTypeLists(t1.alias.TypeArguments(), t2.alias.TypeArguments()) } if s1 == nil { @@ -742,10 +851,10 @@ func compareTypeMappers(m1, m2 *TypeMapper) int { case TypeMapperKindArray: m1 := castTypeMapper[ArrayTypeMapper](m1, TypeMapperKindArray) m2 := castTypeMapper[ArrayTypeMapper](m2, TypeMapperKindArray) - if c := compareTypeLists(m1.sources, m2.sources); c != 0 { + if c := compareTypeLists(m1.sources(), m2.sources()); c != 0 { return c } - return compareTypeLists(m1.targets, m2.targets) + return compareTypeLists(m1.targets(), m2.targets()) case TypeMapperKindMerged: m1 := castTypeMapper[MergedTypeMapper](m1, TypeMapperKindMerged) m2 := castTypeMapper[MergedTypeMapper](m2, TypeMapperKindMerged) @@ -920,11 +1029,14 @@ func (c *Checker) releaseTemporaryTypeSet(set *temporaryTypeSet) { if cap(set.values) > len(set.inline) { clear(set.inline[:]) } - clear(set.valuesByKey) + if len(set.valuesByKey) > 256 || cap(set.values) > 4096 { + set.valuesByKey = nil + } else if len(set.valuesByKey) != 0 { + clear(set.valuesByKey) + } // Do not retain arbitrarily large temporary unions and their dedup maps. if cap(set.values) > 4096 { set.values = set.inline[:0] - set.valuesByKey = nil } else { set.values = set.values[:0] } @@ -1000,8 +1112,30 @@ func (c *Checker) releaseTemporaryPropertyCounts(counts *temporaryPropertyCounts c.freePropertyCounts = counts } +// Property enumeration of a combined type may recursively enumerate other +// combined types, so each call borrows a separate name set. +func (c *Checker) getTemporaryNameSet() map[string]struct{} { + if n := len(c.freeNameSets); n != 0 { + set := c.freeNameSets[n-1] + c.freeNameSets = c.freeNameSets[:n-1] + return set + } + return make(map[string]struct{}) +} + +func (c *Checker) releaseTemporaryNameSet(set map[string]struct{}) { + // Do not retain unusually large tables in the free list. + if len(set) > 256 { + return + } + if len(set) != 0 { + clear(set) + } + c.freeNameSets = append(c.freeNameSets, set) +} + func (s *orderedSet[T]) contains(value T) bool { - if s.valuesByKey == nil { + if len(s.values) <= orderedSetMapThreshold { return slices.Contains(s.values, value) } _, ok := s.valuesByKey[value] @@ -1012,11 +1146,13 @@ func (s *orderedSet[T]) add(value T) { s.values = append(s.values, value) // Small sets are served by a linear scan over values; only materialize the map once the set // grows large enough for hashing to win. - if s.valuesByKey == nil { - if len(s.values) <= orderedSetMapThreshold { - return + if len(s.values) <= orderedSetMapThreshold { + return + } + if len(s.values) == orderedSetMapThreshold+1 { + if s.valuesByKey == nil { + s.valuesByKey = make(map[T]struct{}, len(s.values)) } - s.valuesByKey = make(map[T]struct{}, len(s.values)) for _, v := range s.values[:len(s.values)-1] { s.valuesByKey[v] = struct{}{} } From bd84e1e2ffdf75a648a92e2f9af9b4f597ae8bb7 Mon Sep 17 00:00:00 2001 From: Steven Chong <25894545+teamchong@users.noreply.github.com> Date: Mon, 26 Oct 2026 14:46:04 -0400 Subject: [PATCH 06/10] perf(checker): memoize flow/inference/relations and compact relation caches - Add bounded flowMemo and inferMemo caches in getTypeAtFlowNode and inferFromTypes to skip repeated control-flow walks and structural inference pairs. - Pool temporary conditional-inference contexts via acquireConditionalInferenceContext and releaseConditionalInferenceContext when contexts do not escape. - Pack relation comparison results into pointer-free simpleRelationMap and genericRelationMap slots, cache union discriminant constituent maps, and add a recursionId bloom filter in isDeeplyNestedOnStack. --- tsc/internal/checker/checker.go | 130 +++- tsc/internal/checker/flow.go | 353 ++++++--- tsc/internal/checker/flowmemo.go | 573 +++++++++++++++ tsc/internal/checker/inference.go | 419 +++++++++-- tsc/internal/checker/relater.go | 1103 ++++++++++++++++++++++------ tsc/internal/checker/tracer.go | 5 +- tsc/internal/checker/types_test.go | 73 ++ 7 files changed, 2244 insertions(+), 412 deletions(-) create mode 100644 tsc/internal/checker/flowmemo.go diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index f650ccb2666ed..2447c4d75c165 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -728,6 +728,8 @@ type Checker struct { resolutionCycleCount uint32 inferMemoSteps uint32 inferLongTargets map[*Type]bool + inferMemoEntries map[CacheHashKey]*inferMemoEntry + flowMemo flowMemo indexedAccessUnionMemo map[indexedAccessUnionKey]*Type globalObjectAugmentFilter [4]uint64 unionConstituentFlags map[*Type]TypeFlags @@ -735,6 +737,8 @@ type Checker struct { freeTypeSets *temporaryTypeSet freePropertyCounts *temporaryPropertyCounts freeNameSets []map[string]struct{} + freeInferenceContexts []*InferenceContext + freeInferenceInfos []*InferenceInfo templateScratchPool []*templateLiteralScratch leafKeyBuilder keyBuilder namedUnionMemberSet map[*Type]struct{} @@ -886,11 +890,11 @@ type Checker struct { antecedentTypes []*Type flowAnalysisDisabled bool flowInvocationCount int - flowTypeCache map[*ast.Node]*Type - lastFlowNode *ast.FlowNode - lastFlowNodeReachable bool - flowNodeReachable map[*ast.FlowNode]bool - flowNodePostSuper map[*ast.FlowNode]bool + flowTypeCache map[*ast.Node]*Type + lastFlowNode *ast.FlowNode + lastFlowNodeReachable bool + flowReachableGen uint32 + flowReachableSlots [2048]flowReachableSlot renamedBindingElementsInTypes []*ast.Node contextualInfos []ContextualInfo inferenceContextInfos []InferenceContextInfo @@ -989,7 +993,7 @@ type Checker struct { func NewChecker(program Program, tracer *Tracer) (*Checker, *sync.Mutex) { program.BindSourceFiles() - c := &Checker{} + c := &Checker{flowMemo: newFlowMemo()} c.id = nextCheckerID.Add(1) c.tracer = tracer c.program = program @@ -1136,14 +1140,8 @@ func NewChecker(program Program, tracer *Tracer) (*Checker, *sync.Mutex) { 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.lastFlowNode = nil - 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.flowReachableGen = 2 + c.subtypeRelation, c.strictSubtypeRelation, c.assignableRelation, c.comparableRelation, c.identityRelation = newRelations() c.enumRelation = make(map[EnumRelationKey]RelationComparisonResult) c.moduleImportAttributesTypes = make(map[*ast.Symbol]*Type) c.getGlobalESSymbolType = c.getGlobalTypeResolver("Symbol", 0 /*arity*/, false /*reportErrors*/) @@ -2371,7 +2369,8 @@ func (c *Checker) checkSourceFile(ctx context.Context, sourceFile *ast.SourceFil } c.produceDeferredDiagnostics() c.reportedUnreachableNodes.Clear() - links.typeChecked = true + c.flowReachableGen += 2 + links.typeChecked = true } if checkUnused && !links.unusedChecked { // The unused identifiers check relies on a full type check having first been performed @@ -7647,6 +7646,7 @@ func (c *Checker) getTypeOfExpression(node *ast.Node) *Type { // If a type has been cached for the node, return it. if cachedType := c.flowTypeCache[node]; cachedType != nil { c.resolutionCycleCount++ + c.flowMemo.addFlags(flowMemoFlagTypeCache) return cachedType } startInvocationCount := c.flowInvocationCount @@ -7657,6 +7657,7 @@ func (c *Checker) getTypeOfExpression(node *ast.Node) *Type { c.flowTypeCache = make(map[*ast.Node]*Type) } c.flowTypeCache[node] = t + c.flowMemo.typeCacheEpoch = c.flowMemo.nextSerial() } return t } @@ -7827,10 +7828,10 @@ func (c *Checker) checkExpressionCachedEx(node *ast.Node, checkMode CheckMode) * if links.resolvedType == nil { // When computing a type that we're going to cache, we need to ignore any ongoing control flow saveFlowLoopStack := c.flowLoopStack - saveFlowTypeCache := c.flowTypeCache; c.flowTypeCache = nil + saveFlowTypeCache := c.takeFlowTypeCache() c.flowLoopStack = nil links.resolvedType = c.checkExpressionEx(node, checkMode) - c.flowTypeCache = saveFlowTypeCache + c.restoreFlowTypeCache(saveFlowTypeCache) c.flowLoopStack = saveFlowLoopStack } return links.resolvedType @@ -8740,6 +8741,7 @@ func (c *Checker) getResolvedSignature(node *ast.Node, candidatesOutArray *[]*Si c.resolutionStart = len(c.typeResolutions) } else if cached == c.resolvingSignature { c.resolutionCycleCount++ + c.flowMemo.taint(0) } links.resolvedSignature = c.resolvingSignature result := c.resolveSignature(node, candidatesOutArray, checkMode) @@ -9452,6 +9454,9 @@ func (c *Checker) chooseOverload(s *CallState, relation *Relation) *Signature { // signature with different arity and we need to perform another arity check. if c.getNonArrayRestType(candidate) != nil && !c.hasCorrectArity(s.node, s.args, checkCandidate, s.signatureHelpTrailingComma) { s.candidateForArgumentArityError = checkCandidate + if inferenceContext != nil && !inferenceContext.escaped { + c.releaseConditionalInferenceContext(inferenceContext, nil, nil) + } continue } } else { @@ -9460,6 +9465,9 @@ func (c *Checker) chooseOverload(s *CallState, relation *Relation) *Signature { if !c.isSignatureApplicable(s.node, s.args, checkCandidate, relation, s.argCheckMode, false /*reportErrors*/, nil /*diagnosticOutput*/) { // Give preference to error candidates that have no rest parameters (as they are more specific) s.candidatesForArgumentError = append(s.candidatesForArgumentError, checkCandidate) + if inferenceContext != nil && !inferenceContext.escaped { + c.releaseConditionalInferenceContext(inferenceContext, nil, nil) + } continue } if s.argCheckMode != 0 { @@ -9474,15 +9482,24 @@ func (c *Checker) chooseOverload(s *CallState, relation *Relation) *Signature { // signature with different arity and we need to perform another arity check. if c.getNonArrayRestType(candidate) != nil && !c.hasCorrectArity(s.node, s.args, checkCandidate, s.signatureHelpTrailingComma) { s.candidateForArgumentArityError = checkCandidate + if !inferenceContext.escaped { + c.releaseConditionalInferenceContext(inferenceContext, nil, nil) + } continue } } if !c.isSignatureApplicable(s.node, s.args, checkCandidate, relation, s.argCheckMode, false /*reportErrors*/, nil /*diagnosticOutput*/) { // Give preference to error candidates that have no rest parameters (as they are more specific) s.candidatesForArgumentError = append(s.candidatesForArgumentError, checkCandidate) + if inferenceContext != nil && !inferenceContext.escaped { + c.releaseConditionalInferenceContext(inferenceContext, nil, nil) + } continue } } + if inferenceContext != nil && !inferenceContext.escaped { + c.releaseConditionalInferenceContext(inferenceContext, nil, nil) + } s.candidates[candidateIndex] = checkCandidate return checkCandidate } @@ -9841,6 +9858,9 @@ func (c *Checker) inferTypeArguments(node *ast.Node, signature *Signature, args } else { context.returnMapper = nil } + if !returnContext.escaped { + c.releaseConditionalInferenceContext(returnContext, nil, nil) + } } } } @@ -9960,6 +9980,9 @@ func (c *Checker) getTypeArgumentsFromNodes(typeArgumentNodes []*ast.Node, typeP func (c *Checker) inferSignatureInstantiationForOverloadFailure(node *ast.Node, typeParameters []*Type, candidate *Signature, args []*ast.Node, checkMode CheckMode) *Signature { inferenceContext := c.newInferenceContext(typeParameters, candidate, core.IfElse(ast.IsInJSFile(node), InferenceFlagsAnyDefault, InferenceFlagsNone), nil) typeArgumentTypes := c.inferTypeArguments(node, candidate, args, checkMode|CheckModeSkipContextSensitive|CheckModeSkipGenericFunctions, inferenceContext) + if !inferenceContext.escaped { + c.releaseConditionalInferenceContext(inferenceContext, nil, nil) + } return c.createSignatureInstantiation(candidate, typeArgumentTypes) } @@ -18891,22 +18914,38 @@ func getConditionalTypeKeySlow(typeArguments []*Type, alias *TypeAlias, forConst return b.hash() } -func getRelationKey(source *Type, target *Type, intersectionState IntersectionState, isIdentity bool, ignoreConstraints bool) (CacheHashKey, bool) { +func getRelationKey(source *Type, target *Type, intersectionState IntersectionState, isIdentity bool, ignoreConstraints bool) (relationKey, bool) { if isIdentity && source.id > target.id { source, target = target, source } - var b keyBuilder - var constrained bool if isTypeReferenceWithGenericArguments(source) && isTypeReferenceWithGenericArguments(target) { + return getRelationKeyGeneric(source, target, intersectionState, ignoreConstraints) + } + if uint32(source.id) < 1<<27 && uint32(target.id) < 1<<27 { + return relationKey{value: CacheHashKey{ + Lo: uint64(source.id)<<27 | uint64(target.id), + Hi: uint64(intersectionState), + }}, false + } + return relationKey{value: CacheHashKey{ + Lo: uint64(source.id)<<32 | uint64(target.id), + Hi: uint64(intersectionState), + }, generic: true}, false +} + +func getRelationKeyGeneric(source *Type, target *Type, intersectionState IntersectionState, ignoreConstraints bool) (relationKey, bool) { + if runtime.GOARCH == "wasm" { + b := acquireKeyBuilder() b.writeByte('g') - constrained = b.writeGenericTypeReferences(source, target, ignoreConstraints) - } else { - b.writeByte('s') - b.writeType(source) - b.writeType(target) + constrained := b.writeGenericTypeReferences(source, target, ignoreConstraints) + b.writeUint32(uint32(intersectionState)) + return relationKey{value: b.releaseHash(), generic: true}, constrained } + var b keyBuilder + b.writeByte('g') + constrained := b.writeGenericTypeReferences(source, target, ignoreConstraints) b.writeUint32(uint32(intersectionState)) - return b.hash(), constrained + return relationKey{value: b.hash(), generic: true}, constrained } func getNodeListKey(nodes []*ast.Node) CacheHashKey { @@ -20069,7 +20108,7 @@ func (c *Checker) pushTypeResolution(target TypeSystemEntity, propertyName TypeS } return false } - c.typeResolutions = append(c.typeResolutions, TypeResolution{target: target, propertyName: propertyName, result: true, serial: 0}) + c.typeResolutions = append(c.typeResolutions, TypeResolution{target: target, propertyName: propertyName, result: true, serial: c.flowMemo.sourceNow()}) return true } @@ -20092,6 +20131,7 @@ func (c *Checker) findResolutionCycleStartIndex(target TypeSystemEntity, propert return -1 } if resolution.target == target && resolution.propertyName == propertyName { + c.flowMemo.taint(resolution.serial) return i } } @@ -21274,6 +21314,9 @@ func (c *Checker) instantiateSignatureInContextOf(signature *Signature, contextu }) } inferredTypes := c.getInferredTypes(context) + if !context.escaped { + c.releaseConditionalInferenceContext(context, nil, nil) + } return c.getSignatureInstantiation(signature, inferredTypes, ast.IsInJSFile(contextualSignature.declaration), nil /*inferredTypeParameters*/) } @@ -27137,6 +27180,8 @@ func (c *Checker) getConditionalType(root *ConditionalRoot, mapper *TypeMapper, var combinedMapper *TypeMapper if !checkTypeDeferred { var context *InferenceContext + var nonFixingCombined *TypeMapper + var fixingCombined *TypeMapper if len(root.inferTypeParameters) != 0 { // When we're looking at making an inference for an `infer` type, when we get its constraint, it'll automagically be // instantiated with the context, so it doesn't need the mapper for the inference context - however the constraint @@ -27152,9 +27197,10 @@ func (c *Checker) getConditionalType(root *ConditionalRoot, mapper *TypeMapper, // This means we have two mappers that need applying: // * The original `mapper` used to create this conditional // * The mapper that maps the infer type parameter to its inference result (`context.mapper`) - context = c.newInferenceContext(root.inferTypeParameters, nil, InferenceFlagsNone, nil) + context = c.acquireConditionalInferenceContext(root.inferTypeParameters) if mapper != nil { - context.nonFixingMapper = c.combineTypeMappers(context.nonFixingMapper, mapper) + nonFixingCombined = c.combineTypeMappers(context.nonFixingMapper, mapper) + context.nonFixingMapper = nonFixingCombined } // We don't want inferences from constraints as they may cause us to eagerly resolve the // conditional type instead of deferring resolution. Also, we always want strict function @@ -27164,7 +27210,8 @@ func (c *Checker) getConditionalType(root *ConditionalRoot, mapper *TypeMapper, // those type parameters are used in type references (see getInferredTypeParameterConstraint). For // that reason we need context.mapper to be first in the combined mapper. See #42636 for examples. if mapper != nil { - combinedMapper = c.combineTypeMappers(context.mapper, mapper) + fixingCombined = c.combineTypeMappers(context.mapper, mapper) + combinedMapper = fixingCombined } else { combinedMapper = context.mapper } @@ -27193,6 +27240,9 @@ func (c *Checker) getConditionalType(root *ConditionalRoot, mapper *TypeMapper, }) { extraTypes = append(extraTypes, c.instantiateType(c.getTypeFromTypeNode(root.node.TrueType), core.OrElse(combinedMapper, mapper))) } + if context != nil && !context.escaped { + c.releaseConditionalInferenceContext(context, fixingCombined, nonFixingCombined) + } // If falseType is an immediately nested conditional type that isn't distributive or has an // identical checkType, switch to that type and loop. falseType := c.getTypeFromTypeNode(root.node.FalseType) @@ -27224,6 +27274,9 @@ func (c *Checker) getConditionalType(root *ConditionalRoot, mapper *TypeMapper, trueType := c.getTypeFromTypeNode(root.node.TrueType) trueMapper := core.OrElse(combinedMapper, mapper) if newRoot, newRootMapper := c.getTailRecursionRoot(trueType, trueMapper); newRoot != nil { + if context != nil && !context.escaped { + c.releaseConditionalInferenceContext(context, fixingCombined, nonFixingCombined) + } root = newRoot mapper = newRootMapper alias = nil @@ -27233,6 +27286,9 @@ func (c *Checker) getConditionalType(root *ConditionalRoot, mapper *TypeMapper, continue } result = c.instantiateType(trueType, trueMapper) + if context != nil && !context.escaped { + c.releaseConditionalInferenceContext(context, fixingCombined, nonFixingCombined) + } break } } @@ -27390,17 +27446,25 @@ func (c *Checker) getInferredTrueTypeFromConditionalType(t *Type) *Type { ex := d.ensureExtra(c) if ex.resolvedInferredTrueType == nil { combinedMapper := ex.combinedMapper + var context *InferenceContext + var nonFixingCombined *TypeMapper + var fixingCombined *TypeMapper if combinedMapper == nil && len(d.root.inferTypeParameters) != 0 { - context := c.newInferenceContext(d.root.inferTypeParameters, nil, InferenceFlagsNone, nil) + context = c.acquireConditionalInferenceContext(d.root.inferTypeParameters) if d.mapper != nil { context.nonFixingMapper = c.combineTypeMappers(context.nonFixingMapper, d.mapper) - combinedMapper = c.combineTypeMappers(context.mapper, d.mapper) + fixingCombined = c.combineTypeMappers(context.mapper, d.mapper) + nonFixingCombined = context.nonFixingMapper + combinedMapper = fixingCombined } else { combinedMapper = context.mapper } } if combinedMapper != nil { ex.resolvedInferredTrueType = c.instantiateType(c.getTypeFromTypeNode(d.root.node.TrueType), combinedMapper) + if context != nil && !context.escaped { + c.releaseConditionalInferenceContext(context, fixingCombined, nonFixingCombined) + } } else { ex.resolvedInferredTrueType = c.getTrueTypeFromConditionalType(t) } @@ -33466,6 +33530,7 @@ func (c *Checker) getContextualTypeForArgumentAtIndex(callTarget *ast.Node, argI var signature *Signature if links := c.signatureLinks.TryGet(callTarget); links != nil && links.resolvedSignature == c.resolvingSignature { c.resolutionCycleCount++ + c.flowMemo.taint(0) signature = c.resolvingSignature } else { signature = c.getResolvedSignature(callTarget, nil, CheckModeNormal) @@ -35224,6 +35289,7 @@ func (c *Checker) getNarrowableTypeForReference(t *Type, reference *ast.Node, ch // 'string | undefined' to give control flow analysis the opportunity to narrow to type 'string'. substituteConstraints := checkMode&CheckModeInferential == 0 && c.someGenericTypeWithUnionConstraint(t) && (c.isConstraintPosition(t, reference) || c.hasContextualTypeWithNoGenericTypes(reference, checkMode)) if substituteConstraints { + c.flowMemo.taint(0) return c.mapType(t, c.getBaseConstraintOrType) } return t diff --git a/tsc/internal/checker/flow.go b/tsc/internal/checker/flow.go index bd653f46630a8..d749b4f59e0e6 100644 --- a/tsc/internal/checker/flow.go +++ b/tsc/internal/checker/flow.go @@ -2,9 +2,11 @@ package checker import ( "math" + "runtime" "slices" "strconv" "strings" + "unsafe" "github.com/microsoft/TypeScript/tsc/internal/ast" "github.com/microsoft/TypeScript/tsc/internal/binder" @@ -33,8 +35,12 @@ func (c *Checker) newFlowType(t *Type, incomplete bool) FlowType { } type SharedFlow struct { - flow *ast.FlowNode - flowType FlowType + flow *ast.FlowNode + flowType FlowType + transient uint32 + reference uint32 + height uint16 + flags uint8 } type FlowState struct { @@ -46,6 +52,13 @@ type FlowState struct { depth int sharedFlowStart int reduceLabels []*ast.FlowReduceLabelData + memoKey CacheHashKey + memoKeyState uint8 + walkFloor uint32 + memoUsed bool + memoAborted bool + memoFaithful bool + impureShared uint32 next *FlowState } @@ -94,8 +107,21 @@ func (c *Checker) getFlowTypeOfReferenceEx(reference *ast.Node, declaredType *Ty f.initialType = core.Coalesce(initialType, declaredType) f.flowContainer = flowContainer f.sharedFlowStart = len(c.sharedFlows) + f.walkFloor = c.flowMemo.nextSerial() c.flowInvocationCount++ - evolvedType := c.getTypeAtFlowNode(f, flowNode).t + c.flowMemoKey(f) + savedHeight := c.flowMemo.saveHeight() + flowType := c.getTypeAtFlowNode(f, flowNode) + if f.memoAborted { + c.sharedFlows = c.sharedFlows[:f.sharedFlowStart] + f.memoAborted = false + f.memoUsed = false + f.memoFaithful = true + f.impureShared = 0 + flowType = c.getTypeAtFlowNode(f, flowNode) + } + c.flowMemo.restoreHeight(savedHeight) + evolvedType := flowType.t 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, @@ -115,7 +141,12 @@ func (c *Checker) getFlowTypeOfReferenceEx(reference *ast.Node, declaredType *Ty } func (c *Checker) getTypeAtFlowNode(f *FlowState, flow *ast.FlowNode) FlowType { + entryFlow := flow if f.depth == 2000 { + if f.memoUsed { + f.memoAborted = true + return FlowType{t: c.errorType} + } // We have made 2000 recursive invocations. To avoid overflowing the call stack we report an error // and disable further control flow analysis in the containing function or module body. if tr := c.tracer; tr != nil { @@ -125,9 +156,28 @@ func (c *Checker) getTypeAtFlowNode(f *FlowState, flow *ast.FlowNode) FlowType { c.reportFlowControlError(f.reference) return FlowType{t: c.errorType} } - f.depth++ + entryDepth := f.depth + tracked := f.memoKeyState == 2 + var frame flowFrame + if tracked { + frame = c.flowFrameBegin() + } + hasMemo := tracked && c.inlineLevel == 0 && !c.flowAnalysisDisabled && len(f.reduceLabels) == 0 + var memo frameMemo + if hasMemo { + memo = frameMemo{ + key: f.memoKey, + checkpoints: uint32(len(c.flowMemo.checkpoints)), + } + } + f.depth = entryDepth + 1 var sharedFlow *ast.FlowNode + var steps uint32 + var hit flowMemoHit + var hasHit bool + var t FlowType for { + steps++ flags := flow.Flags if flags&ast.FlowFlagsShared != 0 { // We cache results of flow type resolution for shared nodes that were previously visited in @@ -135,74 +185,133 @@ func (c *Checker) getTypeAtFlowNode(f *FlowState, flow *ast.FlowNode) FlowType { // antecedent of more than one node. for i := f.sharedFlowStart; i < len(c.sharedFlows); i++ { if c.sharedFlows[i].flow == flow { - f.depth-- - return c.sharedFlows[i].flowType + shared := c.sharedFlows[i] + f.depth = entryDepth + if tracked { + c.flowMemoConsume(shared.transient, shared.reference) + c.flowMemo.addFlags(shared.flags) + c.flowFrameEndWith(frame, shared.height) + } + if hasMemo { + c.flowMemo.checkpoints = c.flowMemo.checkpoints[:memo.checkpoints] + } + return shared.flowType } } sharedFlow = flow } - var t FlowType - switch { - case flags&ast.FlowFlagsAssignment != 0: - t = c.getTypeAtFlowAssignment(f, flow) - if t.isNil() { - flow = flow.Antecedent - continue - } - case flags&ast.FlowFlagsCall != 0: - t = c.getTypeAtFlowCall(f, flow) - if t.isNil() { - flow = flow.Antecedent - continue - } - case flags&ast.FlowFlagsCondition != 0: - t = c.getTypeAtFlowCondition(f, flow) - case flags&ast.FlowFlagsSwitchClause != 0: - t = c.getTypeAtSwitchClause(f, flow) - case flags&ast.FlowFlagsBranchLabel != 0: - antecedents := getBranchLabelAntecedents(flow, f.reduceLabels) - if antecedents.Next == nil { - flow = antecedents.Flow - continue - } - t = c.getTypeAtFlowBranchLabel(f, flow, antecedents) - case flags&ast.FlowFlagsLoopLabel != 0: - if flow.Antecedents().Next == nil { - flow = flow.Antecedents().Flow - continue + if hasMemo { + memoHit, endsIteration, ok := c.flowMemoAtNode(f, entryFlow, flow, flags, memo.key, entryDepth) + if ok { + if !endsIteration { + sharedFlow = nil + } + hit = memoHit + hasHit = true + t = FlowType{t: memoHit.t} + break } - t = c.getTypeAtFlowLoopLabel(f, flow) - case flags&ast.FlowFlagsArrayMutation != 0: - t = c.getTypeAtFlowArrayMutation(f, flow) - if t.isNil() { - flow = flow.Antecedent - continue + } + var next *ast.FlowNode + var done bool + t, next, done = c.getTypeAtFlowNodeStep(f, flow, flags) + if done { + break + } + flow = next + } + f.depth = entryDepth + var taint frameTaint + var height uint16 + if tracked { + if f.memoAborted { + c.flowFrameEnd(frame) + if hasMemo { + c.flowMemo.checkpoints = c.flowMemo.checkpoints[:memo.checkpoints] } - case flags&ast.FlowFlagsReduceLabel != 0: - f.reduceLabels = append(f.reduceLabels, flow.Node.AsFlowReduceLabelData()) - t = c.getTypeAtFlowNode(f, flow.Antecedent) - f.reduceLabels = f.reduceLabels[:len(f.reduceLabels)-1] - case flags&ast.FlowFlagsStart != 0: - // Check if we should continue with the control flow of the containing function. - container := flow.Node - if container != nil && container != f.flowContainer && !ast.IsPropertyAccessExpression(f.reference) && !ast.IsElementAccessExpression(f.reference) && !(f.reference.Kind == ast.KindThisKeyword && !ast.IsArrowFunction(container)) { - flow = container.FlowNodeData().FlowNode - continue - } - // At the top of the flow we have the initial type. - t = FlowType{t: f.initialType} - default: - // Unreachable code errors are reported in the binding phase. Here we - // simply return the non-auto declared type to reduce follow-on errors. - t = FlowType{t: c.convertAutoToAny(f.declaredType)} + return t + } + if hasHit { + f.memoUsed = true + c.flowMemo.raiseHeight(hit.height) } + taint, height = c.flowFrameEnd(frame) if sharedFlow != nil { - // Record visited node and the associated type in the cache. - c.sharedFlows = append(c.sharedFlows, SharedFlow{flow: sharedFlow, flowType: t}) + if taint.transient != flowMemoUnTainted { + f.impureShared++ + } + c.sharedFlows = append(c.sharedFlows, SharedFlow{ + flow: sharedFlow, + flowType: t, + transient: taint.transient, + reference: taint.reference, + height: height, + flags: taint.flags, + }) } - f.depth-- - return t + if hasMemo { + c.flowMemoFill(f, memo, entryFlow, flow, t, taint, height, steps, hasHit) + c.flowMemo.checkpoints = c.flowMemo.checkpoints[:memo.checkpoints] + } + } else if sharedFlow != nil { + // Record visited node and the associated type in the cache. + c.sharedFlows = append(c.sharedFlows, SharedFlow{flow: sharedFlow, flowType: t}) + } + return t +} + +func (c *Checker) getTypeAtFlowNodeStep(f *FlowState, flow *ast.FlowNode, flags ast.FlowFlags) (FlowType, *ast.FlowNode, bool) { + var t FlowType + switch { + case flags&ast.FlowFlagsAssignment != 0: + t = c.getTypeAtFlowAssignment(f, flow) + if t.isNil() { + return FlowType{}, flow.Antecedent, false + } + case flags&ast.FlowFlagsCall != 0: + t = c.getTypeAtFlowCall(f, flow) + if t.isNil() { + return FlowType{}, flow.Antecedent, false + } + case flags&ast.FlowFlagsCondition != 0: + t = c.getTypeAtFlowCondition(f, flow) + case flags&ast.FlowFlagsSwitchClause != 0: + t = c.getTypeAtSwitchClause(f, flow) + case flags&ast.FlowFlagsBranchLabel != 0: + antecedents := getBranchLabelAntecedents(flow, f.reduceLabels) + if antecedents.Next == nil { + return FlowType{}, antecedents.Flow, false + } + t = c.getTypeAtFlowBranchLabel(f, flow, antecedents) + case flags&ast.FlowFlagsLoopLabel != 0: + if flow.Antecedents().Next == nil { + return FlowType{}, flow.Antecedents().Flow, false + } + t = c.getTypeAtFlowLoopLabel(f, flow) + case flags&ast.FlowFlagsArrayMutation != 0: + t = c.getTypeAtFlowArrayMutation(f, flow) + if t.isNil() { + return FlowType{}, flow.Antecedent, false + } + case flags&ast.FlowFlagsReduceLabel != 0: + c.flowMemo.taint(0) + f.reduceLabels = append(f.reduceLabels, flow.Node.AsFlowReduceLabelData()) + t = c.getTypeAtFlowNode(f, flow.Antecedent) + f.reduceLabels = f.reduceLabels[:len(f.reduceLabels)-1] + case flags&ast.FlowFlagsStart != 0: + // Check if we should continue with the control flow of the containing function. + container := flow.Node + if container != nil && container != f.flowContainer && !ast.IsPropertyAccessExpression(f.reference) && !ast.IsElementAccessExpression(f.reference) && !(f.reference.Kind == ast.KindThisKeyword && !ast.IsArrowFunction(container)) { + return FlowType{}, container.FlowNodeData().FlowNode, false + } + // At the top of the flow we have the initial type. + t = FlowType{t: f.initialType} + default: + // Unreachable code errors are reported in the binding phase. Here we + // simply return the non-auto declared type to reduce follow-on errors. + t = FlowType{t: c.convertAutoToAny(f.declaredType)} } + return t, nil, true } func getBranchLabelAntecedents(flow *ast.FlowNode, reduceLabels []*ast.FlowReduceLabelData) *ast.FlowList { @@ -227,6 +336,9 @@ func (c *Checker) getTypeAtFlowAssignment(f *FlowState, flow *ast.FlowNode) Flow } if getAssignmentTargetKind(node) == AssignmentKindCompound { flowType := c.getTypeAtFlowNode(f, flow.Antecedent) + if f.memoAborted { + return flowType + } return c.newFlowType(c.getBaseTypeOfLiteralType(flowType.t), flowType.incomplete) } if f.declaredType == c.autoType || f.declaredType == c.autoArrayType { @@ -259,7 +371,8 @@ func (c *Checker) getTypeAtFlowAssignment(f *FlowState, flow *ast.FlowNode) Flow // A matching dotted name might also be an expando property on a function *expression*, // in which case we continue control flow analysis back to the function's declaration if ast.IsVariableDeclaration(node) && (ast.IsInJSFile(node) || ast.IsVarConstLike(node)) { - if init := node.Initializer(); init != nil && ast.IsFunctionExpressionOrArrowFunction(init) { + init := node.Initializer() + if init != nil && ast.IsFunctionExpressionOrArrowFunction(init) { return c.getTypeAtFlowNode(f, flow.Antecedent) } } @@ -267,7 +380,11 @@ func (c *Checker) getTypeAtFlowAssignment(f *FlowState, flow *ast.FlowNode) Flow } // for (const _ in ref) acts as a nonnull on ref if ast.IsVariableDeclaration(node) && ast.IsForInStatement(node.Parent.Parent) && (c.isMatchingReference(f.reference, node.Parent.Parent.Expression()) || c.optionalChainContainsReference(node.Parent.Parent.Expression(), f.reference)) { - return FlowType{t: c.getNonNullableTypeIfNeeded(c.finalizeEvolvingArrayType(c.getTypeAtFlowNode(f, flow.Antecedent).t))} + flowType := c.getTypeAtFlowNode(f, flow.Antecedent) + if f.memoAborted { + return flowType + } + return FlowType{t: c.getNonNullableTypeIfNeeded(c.finalizeEvolvingArrayType(flowType.t))} } // Assignment doesn't affect reference return FlowType{} @@ -275,8 +392,10 @@ func (c *Checker) getTypeAtFlowAssignment(f *FlowState, flow *ast.FlowNode) Flow func (c *Checker) getInitialOrAssignedType(f *FlowState, flow *ast.FlowNode) *Type { if ast.IsVariableDeclaration(flow.Node) || ast.IsBindingElement(flow.Node) { + c.flowMemo.taintReference(f.walkFloor) return c.getNarrowableTypeForReference(c.getInitialType(flow.Node), f.reference, CheckModeNormal) } + c.flowMemo.taintReference(f.walkFloor) return c.getNarrowableTypeForReference(c.getAssignedType(flow.Node), f.reference, CheckModeNormal) } @@ -291,6 +410,9 @@ func (c *Checker) getTypeAtFlowCall(f *FlowState, flow *ast.FlowNode) FlowType { predicate := c.getTypePredicateOfSignature(signature) if predicate != nil && (predicate.kind == TypePredicateKindAssertsThis || predicate.kind == TypePredicateKindAssertsIdentifier) { flowType := c.getTypeAtFlowNode(f, flow.Antecedent) + if f.memoAborted { + return flowType + } t := c.finalizeEvolvingArrayType(flowType.t) var narrowedType *Type switch { @@ -353,7 +475,7 @@ func (c *Checker) narrowTypeByAssertion(f *FlowState, t *Type, expr *ast.Node) * func (c *Checker) getTypeAtFlowCondition(f *FlowState, flow *ast.FlowNode) FlowType { flowType := c.getTypeAtFlowNode(f, flow.Antecedent) - if flowType.t.flags&TypeFlagsNever != 0 { + if f.memoAborted || flowType.t.flags&TypeFlagsNever != 0 { return flowType } // If we have an antecedent type (meaning we're reachable in some way), we first @@ -387,7 +509,10 @@ func (c *Checker) narrowType(f *FlowState, t *Type, expr *ast.Node, assumeTrue b 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) { + if declaration != nil && ast.IsVariableDeclaration(declaration) && declaration.Type() == nil && declaration.Initializer() != nil && c.isConstantReferenceOfWalk(f) { + if c.inlineLevel == 1 { + c.flowMemo.taint(0) + } c.inlineLevel++ result := c.narrowType(f, t, declaration.Initializer(), assumeTrue) c.inlineLevel-- @@ -1060,6 +1185,9 @@ func (c *Checker) getTypeAtSwitchClause(f *FlowState, flow *ast.FlowNode) FlowTy data := flow.Node.AsFlowSwitchClauseData() expr := ast.SkipParentheses(data.SwitchStatement.Expression()) flowType := c.getTypeAtFlowNode(f, flow.Antecedent) + if f.memoAborted { + return flowType + } t := flowType.t switch { case c.isMatchingReference(f.reference, expr): @@ -1263,7 +1391,10 @@ func (c *Checker) getTypeAtFlowBranchLabel(f *FlowState, flow *ast.FlowNode, ant continue } flowType := c.getTypeAtFlowNode(f, antecedent) - // If the type at a particular antecedent path is the declared type and the + if f.memoAborted { + c.antecedentTypes = c.antecedentTypes[:antecedentStart] + return flowType + } // reference is known to always be assigned (i.e. when declared and initial types // are the same), there is no reason to process more antecedents since the only // possible outcome is subtypes that will be removed in the final union type anyway. @@ -1286,7 +1417,10 @@ func (c *Checker) getTypeAtFlowBranchLabel(f *FlowState, flow *ast.FlowNode, ant } if bypassFlow != nil { flowType := c.getTypeAtFlowNode(f, bypassFlow) - // If the bypass flow contributes a type we haven't seen yet and the switch statement + if f.memoAborted { + c.antecedentTypes = c.antecedentTypes[:antecedentStart] + return flowType + } // isn't exhaustive, process the bypass flow type. Since exhaustiveness checks increase // the risk of circularities, we only want to perform them when they make a difference. if flowType.t.flags&TypeFlagsNever == 0 && !slices.Contains(c.antecedentTypes[antecedentStart:], flowType.t) && !c.isExhaustiveSwitchStatement(bypassFlow.Node.AsFlowSwitchClauseData().SwitchStatement) { @@ -1346,10 +1480,10 @@ func (c *Checker) getTypeAtFlowLoopLabel(f *FlowState, flow *ast.FlowNode) FlowT // path that leads to the top. for _, loopInfo := range c.flowLoopStack { if loopInfo.key == key && len(loopInfo.types) != 0 { + c.flowMemo.taint(loopInfo.serial) return c.newFlowType(c.getUnionOrEvolvingArrayType(f, loopInfo.types, UnionReductionLiteral), true /*incomplete*/) } } - // Add the flow loop junction and reference to the in-process stack and analyze // each antecedent code path. antecedentTypes := make([]*Type, 0, 4) subtypeReduction := false @@ -1360,18 +1494,22 @@ func (c *Checker) getTypeAtFlowLoopLabel(f *FlowState, flow *ast.FlowNode) FlowT // The first antecedent of a loop junction is always the non-looping control // flow path that leads to the top. firstAntecedentType = c.getTypeAtFlowNode(f, list.Flow) + if f.memoAborted { + return firstAntecedentType + } flowType = firstAntecedentType } else { // All but the first antecedent are the looping control flow paths that lead // back to the loop junction. We track these on the flow loop stack. - c.flowLoopStack = append(c.flowLoopStack, FlowLoopInfo{key: key, types: antecedentTypes}) - saveFlowTypeCache := c.flowTypeCache - c.flowTypeCache = nil + serial := c.flowMemo.nextSerial() + c.flowLoopStack = append(c.flowLoopStack, FlowLoopInfo{key: key, types: antecedentTypes, serial: serial}) + saveFlowTypeCache := c.takeFlowTypeCache() flowType = c.getTypeAtFlowNode(f, list.Flow) - c.flowTypeCache = saveFlowTypeCache + c.restoreFlowTypeCache(saveFlowTypeCache) c.flowLoopStack = c.flowLoopStack[:len(c.flowLoopStack)-1] - // 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 + if f.memoAborted { + return flowType + } // the resulting type and bail out. if cached := c.flowLoopCache[key]; cached != nil { return FlowType{t: cached} @@ -1412,6 +1550,9 @@ func (c *Checker) getTypeAtFlowArrayMutation(f *FlowState, flow *ast.FlowNode) F } if c.isMatchingReference(f.reference, c.getReferenceCandidate(expr)) { flowType := c.getTypeAtFlowNode(f, flow.Antecedent) + if f.memoAborted { + return flowType + } if flowType.t.objectFlags&ObjectFlagsEvolvingArray != 0 { evolvedType := flowType.t if ast.IsCallExpression(node) { @@ -1667,6 +1808,16 @@ var nonDottedNameCacheKey = CacheHashKey(xxh3.HashString128("?")) // leftmost identifier followed by zero or more property names separated by dots. // The result is nonDottedNameCacheKey if the reference isn't a dotted name. func (c *Checker) getFlowReferenceKey(f *FlowState) CacheHashKey { + if runtime.GOARCH == "wasm" { + b := acquireKeyBuilder() + if c.writeFlowCacheKey(b, f.reference, f.declaredType, f.initialType, f.flowContainer) { + return b.releaseHash() + } + b.inlineLength = 0 + b.overflowBuffer = b.overflowBuffer[:0] + wasmKeyBuilderPool = append(wasmKeyBuilderPool, b) + return nonDottedNameCacheKey + } var b keyBuilder if c.writeFlowCacheKey(&b, f.reference, f.declaredType, f.initialType, f.flowContainer) { return b.hash() @@ -1823,6 +1974,13 @@ func (c *Checker) getLiteralPropertyNameText(name *ast.Node) (string, bool) { return "", false } +func (c *Checker) isConstantReferenceOfWalk(f *FlowState) bool { + if ast.IsAccessExpression(f.reference) { + c.flowMemo.taintReference(f.walkFloor) + } + return c.isConstantReference(f.reference) +} + func (c *Checker) isConstantReference(node *ast.Node) bool { switch node.Kind { case ast.KindThisKeyword: @@ -2522,10 +2680,27 @@ func (c *Checker) getFlowTypeInStaticBlocks(symbol *ast.Symbol, staticBlocks []* return nil } +type flowReachableSlot struct { + flow *ast.FlowNode + gen uint32 + val bool +} + func (c *Checker) isReachableFlowNode(flow *ast.FlowNode) bool { + if flow == c.lastFlowNode { + return c.lastFlowNodeReachable + } + // SAFETY: Converts the FlowNode address to uintptr solely to index the direct-mapped cache. + slot := &c.flowReachableSlots[(uintptr(unsafe.Pointer(flow))>>3)&2047] + if slot.flow == flow && slot.gen == c.flowReachableGen { + c.lastFlowNode = flow + c.lastFlowNodeReachable = slot.val + return slot.val + } f := c.getFlowState() result := c.isReachableFlowNodeWorker(f, flow, false /*noCacheCheck*/) c.putFlowState(f) + *slot = flowReachableSlot{flow: flow, gen: c.flowReachableGen, val: result} c.lastFlowNode = flow c.lastFlowNodeReachable = result return result @@ -2537,17 +2712,17 @@ func (c *Checker) isReachableFlowNodeWorker(f *FlowState, flow *ast.FlowNode, no return c.lastFlowNodeReachable } flags := flow.Flags - if flags&ast.FlowFlagsShared != 0 { - if !noCacheCheck && len(f.reduceLabels) == 0 { - if reachable, ok := c.flowNodeReachable[flow]; ok { - return reachable - } - reachable := c.isReachableFlowNodeWorker(f, flow, true /*noCacheCheck*/) - c.flowNodeReachable[flow] = reachable - return reachable + if flags&ast.FlowFlagsShared != 0 && !noCacheCheck && len(f.reduceLabels) == 0 { + // SAFETY: Converts the FlowNode address to uintptr solely to index the direct-mapped cache. + slot := &c.flowReachableSlots[(uintptr(unsafe.Pointer(flow))>>3)&2047] + if slot.flow == flow && slot.gen == c.flowReachableGen { + return slot.val } - noCacheCheck = false + reachable := c.isReachableFlowNodeWorker(f, flow, true /*noCacheCheck*/) + *slot = flowReachableSlot{flow: flow, gen: c.flowReachableGen, val: reachable} + return reachable } + noCacheCheck = false switch { case flags&(ast.FlowFlagsAssignment|ast.FlowFlagsCondition|ast.FlowFlagsArrayMutation) != 0: flow = flow.Antecedent @@ -2625,11 +2800,15 @@ 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 { - return postSuper + gen := c.flowReachableGen + 1 + // SAFETY: Converts the FlowNode address to uintptr solely to index the direct-mapped cache. + slot := &c.flowReachableSlots[(uintptr(unsafe.Pointer(flow))>>3)&2047] + if slot.flow == flow && slot.gen == gen { + return slot.val } postSuper := c.isPostSuperFlowNodeWorker(f, flow, true /*noCacheCheck*/) - c.flowNodePostSuper[flow] = postSuper + *slot = flowReachableSlot{flow: flow, gen: gen, val: postSuper} + return postSuper } noCacheCheck = false } diff --git a/tsc/internal/checker/flowmemo.go b/tsc/internal/checker/flowmemo.go new file mode 100644 index 0000000000000..ea8714841631e --- /dev/null +++ b/tsc/internal/checker/flowmemo.go @@ -0,0 +1,573 @@ +package checker + +import ( + "math/bits" + "unsafe" + + "github.com/microsoft/TypeScript/tsc/internal/ast" + "github.com/zeebo/xxh3" +) + +// Persistent memo of control flow analysis results across getFlowTypeOfReferenceEx walks. +// +// Go walks the flow graph backwards from each reference (getFlowTypeOfReference) and caches only +// inside one walk (sharedFlows) and, per reference key, at loop labels (flowLoopCache). So N +// references to the same variable or property path in a long function each walk the prefix of the +// graph above them, and N distinct properties read from a narrowed object each re-evaluate the +// narrowing of that object. +// +// The memo caches a pure sub-walk of getTypeAtFlowNode(f, flow) by (flow, memoKey(f)): +// - no transient taint: no in-process loop label (Go's incomplete types), circular or in-progress +// resolution, reduce label (try/finally), depth or inline-level limit, and no value cached +// while one of those was active; +// - no reference taint: nothing read from the reference node beyond its key (its position, for +// constraint substitution; the property symbol a property access resolved to); +// - the result is complete, and the frame did not touch the instantiation counters or +// create a type, symbol, or signature. +// +// With caches that only grow, a later walk of the same frame takes a subset of the paths this one +// took, so it does not touch them either; a frame that read flowTypeCache (which is reset) is +// used only while the same cache is current. + +const ( + flowDepthLimit = 2000 + flowMemoUnTainted uint32 = 0xffff_ffff + flowMemoTableBits = 12 + flowMemoTableSize = 1 << flowMemoTableBits + flowMemoFlagEffects uint8 = 1 + flowMemoFlagTypeCache uint8 = 2 + flowMemoFlagCountReset uint8 = 4 +) + +type flowMemoHit struct { + t *Type + epoch uint32 + height uint16 + countReset bool +} + +type flowMemoSlot struct { + key CacheHashKey + flow *ast.FlowNode + t *Type + epoch uint32 + height uint16 + countReset bool +} + +type flowMemoRegs struct { + transient uint32 + reference uint32 + height uint16 + flags uint8 +} + +var freshFlowMemoRegs = flowMemoRegs{ + transient: flowMemoUnTainted, + reference: flowMemoUnTainted, + height: 0, + flags: 0, +} + +type flowFrame struct { + start uint32 + saved flowMemoRegs + instantiationCount uint32 + totalInstantiationCount uint32 + created uint32 + inInstantiation bool +} + +type frameTaint struct { + transient uint32 + reference uint32 + flags uint8 +} + +func (ft frameTaint) isPure() bool { + return ft.transient == flowMemoUnTainted && ft.reference == flowMemoUnTainted +} + +type frameMemo struct { + key CacheHashKey + checkpoints uint32 +} + +type savedFlowTypeCache struct { + cache map[*ast.Node]*Type + epoch uint32 +} + +type flowMemo struct { + enabled bool + slots *[flowMemoTableSize]flowMemoSlot + serial uint32 + regs flowMemoRegs + typeCacheEpoch uint32 + checkpoints []*ast.FlowNode + keyScratch []byte +} + +func newFlowMemo() flowMemo { + return flowMemo{ + enabled: true, + serial: 1, + regs: freshFlowMemoRegs, + typeCacheEpoch: flowMemoUnTainted, + } +} + +func (m *flowMemo) sourceNow() uint32 { + return m.serial +} + +func (m *flowMemo) nextSerial() uint32 { + s := m.serial + m.serial++ + if m.serial == flowMemoUnTainted { + m.enabled = false + m.serial = 1 + } + return s +} + +func (m *flowMemo) begin(instantiationCount, totalInstantiationCount, created uint32, inInstantiation bool) flowFrame { + frame := flowFrame{ + start: m.nextSerial(), + saved: m.regs, + instantiationCount: instantiationCount, + totalInstantiationCount: totalInstantiationCount, + created: created, + inInstantiation: inInstantiation, + } + m.regs = freshFlowMemoRegs + return frame +} + +func (m *flowMemo) endWith(frame flowFrame, height uint16) frameTaint { + r := m.regs + transient := flowMemoUnTainted + if r.transient < frame.start { + transient = r.transient + } + reference := flowMemoUnTainted + if r.reference < frame.start { + reference = r.reference + } + taint := frameTaint{ + transient: transient, + reference: reference, + flags: r.flags, + } + p := frame.saved + nextHeight := height + 1 + if height == 0xffff { + nextHeight = 0xffff + } + if p.height > nextHeight { + nextHeight = p.height + } + m.regs = flowMemoRegs{ + transient: min(r.transient, p.transient), + reference: min(r.reference, p.reference), + height: nextHeight, + flags: r.flags | p.flags, + } + return taint +} + +func (m *flowMemo) end(frame flowFrame) (frameTaint, uint16) { + height := m.regs.height + return m.endWith(frame, height), height +} + +func (m *flowMemo) raiseHeight(height uint16) { + if height > m.regs.height { + m.regs.height = height + } +} + +func (m *flowMemo) addFlags(flags uint8) { + m.regs.flags |= flags +} + +func (m *flowMemo) saveHeight() uint16 { + saved := m.regs.height + m.regs.height = 0 + return saved +} + +func (m *flowMemo) restoreHeight(saved uint16) { + m.regs.height = saved +} + +func (m *flowMemo) taint(source uint32) { + if source < m.regs.transient { + m.regs.transient = source + } +} + +func (m *flowMemo) taintReference(source uint32) { + if source < m.regs.reference { + m.regs.reference = source + } +} + +func flowMemoSlotIndex(flow *ast.FlowNode, key CacheHashKey) uint32 { + // SAFETY: Hashes the FlowNode pointer address as a numeric cache key. + f := uint64(uintptr(unsafe.Pointer(flow))) * 0x9E37_79B9_7F4A_7C15 + h := (key.Lo ^ bits.RotateLeft64(key.Hi, 29) ^ bits.RotateLeft64(f, 17)) * 0xBF58_476D_1CE4_E5B9 + return uint32(h>>(64-flowMemoTableBits)) & (flowMemoTableSize - 1) +} + +func (m *flowMemo) lookup(flow *ast.FlowNode, key CacheHashKey) (flowMemoHit, bool) { + if m.slots == nil { + return flowMemoHit{}, false + } + slot := &m.slots[flowMemoSlotIndex(flow, key)] + if slot.flow == flow && slot.key == key && slot.t != nil { + return flowMemoHit{ + t: slot.t, + height: slot.height, + epoch: slot.epoch, + countReset: slot.countReset, + }, true + } + return flowMemoHit{}, false +} + +func (m *flowMemo) store(flow *ast.FlowNode, key CacheHashKey, t *Type, height uint16, epoch uint32, countReset bool) { + if m.slots == nil { + m.slots = new([flowMemoTableSize]flowMemoSlot) + } + m.slots[flowMemoSlotIndex(flow, key)] = flowMemoSlot{ + flow: flow, + key: key, + t: t, + height: height, + epoch: epoch, + countReset: countReset, + } +} + +func isFlowMemoCheckpoint(flow *ast.FlowNode) bool { + // SAFETY: Hashes the FlowNode pointer address to sample 1/4 of nodes deterministically. + return (uint64(uintptr(unsafe.Pointer(flow)))*0x9E37_79B9_7F4A_7C15)>>62 == 0 +} + +func (c *Checker) flowMemoCreated() uint32 { + return c.TypeCount + c.SymbolCount + c.SignatureCount +} + +func (c *Checker) flowFrameBegin() flowFrame { + return c.flowMemo.begin( + c.instantiationCount, + c.TotalInstantiationCount, + c.flowMemoCreated(), + len(c.activeMappers) != 0, + ) +} + +func (c *Checker) flowFrameCounters(frame flowFrame) { + if c.TotalInstantiationCount != frame.totalInstantiationCount || + frame.inInstantiation || + c.flowMemoCreated() != frame.created { + c.flowMemo.addFlags(flowMemoFlagEffects) + } + if frame.instantiationCount == 0 && c.instantiationCount != frame.instantiationCount { + c.flowMemo.addFlags(flowMemoFlagCountReset) + } +} + +func (c *Checker) flowFrameEnd(frame flowFrame) (frameTaint, uint16) { + c.flowFrameCounters(frame) + return c.flowMemo.end(frame) +} + +func (c *Checker) flowFrameEndWith(frame flowFrame, height uint16) frameTaint { + c.flowFrameCounters(frame) + return c.flowMemo.endWith(frame, height) +} + +func (c *Checker) flowMemoConsume(transient, reference uint32) { + if transient != flowMemoUnTainted { + c.flowMemoConsumeTransient(transient) + } + if reference != flowMemoUnTainted { + c.flowMemo.taintReference(reference) + } +} + +func (c *Checker) flowMemoConsumeTransient(source uint32) { + live := false + if source != 0 { + for i := range c.flowLoopStack { + if c.flowLoopStack[i].serial == source { + live = true + break + } + } + if !live { + for i := range c.typeResolutions { + if c.typeResolutions[i].serial == source { + live = true + break + } + } + } + } + if live { + c.flowMemo.taint(source) + } else { + c.flowMemo.taint(0) + } +} + +// flowMemoKey computes the walk's memo key once per walk: a packed or hashed representation of +// the reference's structure (root symbol or 'this', the property names of a property access chain), +// the declared and initial types, and the flow container. Only identifiers, 'this', and chains of +// plain property accesses on them get one. Computing it has no side effects: it reads a resolved +// symbol only if one is already cached and assigns no node or symbol IDs. +func (c *Checker) flowMemoKey(f *FlowState) (CacheHashKey, bool) { + switch f.memoKeyState { + case 1: + return CacheHashKey{}, false + case 2: + return f.memoKey, true + } + key, ok := c.computeFlowMemoKey(f) + if ok { + f.memoKey = key + f.memoKeyState = 2 + return key, true + } + f.memoKeyState = 1 + return CacheHashKey{}, false +} + +func flowContainerAddr(n *ast.Node) uint64 { + if n == nil { + return 0 + } + // SAFETY: Uses the node address as a numeric cache key while the AST remains live for the check. + return uint64(uintptr(unsafe.Pointer(&n.Flags))) +} + +func (c *Checker) computeFlowMemoKey(f *FlowState) (CacheHashKey, bool) { + ref := f.reference + declared := uint32(f.declaredType.id) + initial := uint32(f.initialType.id) + switch ref.Kind { + case ast.KindIdentifier: + sym := c.flowMemoRootSymbol(ref) + if sym == nil { + return CacheHashKey{}, false + } + if declared != 0 && initial < (1<<31) { + // SAFETY: Uses the symbol address as a numeric cache key while the symbol remains live for the check. + p := uint64(uintptr(unsafe.Pointer(sym))) + if f.flowContainer == nil { + return CacheHashKey{ + Hi: uint64(declared) | (uint64(initial) << 32), + Lo: p, + }, true + } + fc := flowContainerAddr(f.flowContainer) * 0x9E37_79B9_7F4A_7C15 + return CacheHashKey{ + Hi: uint64(declared) | (uint64(initial) << 32), + Lo: p ^ bits.RotateLeft64(fc, 31), + }, true + } + var b [25]byte + b[0] = 'i' + // SAFETY: Uses the symbol address as a numeric cache key while the symbol remains live for the check. + p := uint64(uintptr(unsafe.Pointer(sym))) + fc := flowContainerAddr(f.flowContainer) + b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8] = byte(p), byte(p>>8), byte(p>>16), byte(p>>24), byte(p>>32), byte(p>>40), byte(p>>48), byte(p>>56) + b[9], b[10], b[11], b[12] = byte(declared), byte(declared>>8), byte(declared>>16), byte(declared>>24) + b[13], b[14], b[15], b[16] = byte(initial), byte(initial>>8), byte(initial>>16), byte(initial>>24) + b[17], b[18], b[19], b[20], b[21], b[22], b[23], b[24] = byte(fc), byte(fc>>8), byte(fc>>16), byte(fc>>24), byte(fc>>32), byte(fc>>40), byte(fc>>48), byte(fc>>56) + h := xxh3.Hash128(b[:]) + h.Hi |= 1 << 63 + return CacheHashKey(h), true + case ast.KindThisKeyword: + if declared != 0 && initial < (1<<31) { + if f.flowContainer == nil { + return CacheHashKey{ + Hi: uint64(declared) | (uint64(initial) << 32), + Lo: 0, + }, true + } + fc := flowContainerAddr(f.flowContainer) * 0x9E37_79B9_7F4A_7C15 + return CacheHashKey{ + Hi: uint64(declared) | (uint64(initial) << 32), + Lo: bits.RotateLeft64(fc, 31), + }, true + } + var b [17]byte + b[0] = 't' + fc := flowContainerAddr(f.flowContainer) + b[1], b[2], b[3], b[4] = byte(declared), byte(declared>>8), byte(declared>>16), byte(declared>>24) + b[5], b[6], b[7], b[8] = byte(initial), byte(initial>>8), byte(initial>>16), byte(initial>>24) + b[9], b[10], b[11], b[12], b[13], b[14], b[15], b[16] = byte(fc), byte(fc>>8), byte(fc>>16), byte(fc>>24), byte(fc>>32), byte(fc>>40), byte(fc>>48), byte(fc>>56) + h := xxh3.Hash128(b[:]) + h.Hi |= 1 << 63 + return CacheHashKey(h), true + } + return c.computeHashedFlowMemoKey(f) +} + +func (c *Checker) computeHashedFlowMemoKey(f *FlowState) (CacheHashKey, bool) { + buf := c.flowMemo.keyScratch[:0] + var ok bool + buf, ok = c.writeFlowMemoReference(buf, f.reference) + if !ok { + c.flowMemo.keyScratch = buf[:0] + return CacheHashKey{}, false + } + d := uint32(f.declaredType.id) + in := uint32(f.initialType.id) + fc := flowContainerAddr(f.flowContainer) + buf = append(buf, + byte(d), byte(d>>8), byte(d>>16), byte(d>>24), + byte(in), byte(in>>8), byte(in>>16), byte(in>>24), + byte(fc), byte(fc>>8), byte(fc>>16), byte(fc>>24), + byte(fc>>32), byte(fc>>40), byte(fc>>48), byte(fc>>56), + ) + h := xxh3.Hash128(buf) + c.flowMemo.keyScratch = buf[:0] + h.Hi |= 1 << 63 + return CacheHashKey(h), true +} + +func (c *Checker) flowMemoRootSymbol(node *ast.Node) *ast.Symbol { + links := c.symbolNodeLinks.TryGet(node) + if links == nil || links.resolvedSymbol == nil { + return nil + } + sym := links.resolvedSymbol + if sym == c.unknownSymbol || (node.AsIdentifier().Text == "this" && ast.IsThisInTypeQuery(node)) { + return nil + } + return sym +} + +func (c *Checker) writeFlowMemoReference(buf []byte, node *ast.Node) ([]byte, bool) { + switch node.Kind { + case ast.KindIdentifier: + sym := c.flowMemoRootSymbol(node) + if sym == nil { + return buf, false + } + // SAFETY: Uses the symbol address as a numeric cache key while the symbol remains live for the check. + p := uint64(uintptr(unsafe.Pointer(sym))) + buf = append(buf, 'i', + byte(p), byte(p>>8), byte(p>>16), byte(p>>24), + byte(p>>32), byte(p>>40), byte(p>>48), byte(p>>56), + ) + return buf, true + case ast.KindThisKeyword: + buf = append(buf, 't') + return buf, true + case ast.KindParenthesizedExpression, ast.KindNonNullExpression: + return c.writeFlowMemoReference(buf, node.Expression()) + case ast.KindPropertyAccessExpression: + var ok bool + buf, ok = c.writeFlowMemoReference(buf, node.Expression()) + if !ok { + return buf, false + } + if node.Flags&ast.NodeFlagsOptionalChain != 0 { + buf = append(buf, 'p', '?') + } else { + buf = append(buf, 'p') + } + name := node.Name().Text() + n := uint32(len(name)) + buf = append(buf, byte(n), byte(n>>8), byte(n>>16), byte(n>>24)) + buf = append(buf, name...) + return buf, true + default: + return buf, false + } +} + +func (c *Checker) flowMemoConsultOk(f *FlowState, hit flowMemoHit) bool { + cacheOk := hit.epoch == 0 || hit.epoch == c.flowMemo.typeCacheEpoch + countersOk := len(c.activeMappers) == 0 && (!hit.countReset || c.instantiationCount == 0) + return !f.memoFaithful && len(c.flowLoopStack) == 0 && cacheOk && countersOk && f.impureShared == 0 +} + +func (c *Checker) flowMemoAtNode(f *FlowState, entryFlow, flow *ast.FlowNode, flags ast.FlowFlags, key CacheHashKey, entryDepth int) (flowMemoHit, bool, bool) { + endsIteration := flags&(ast.FlowFlagsCondition|ast.FlowFlagsSwitchClause|ast.FlowFlagsArrayMutation|ast.FlowFlagsStart) != 0 || + (flags&(ast.FlowFlagsBranchLabel|ast.FlowFlagsLoopLabel) != 0 && flow.Antecedents() != nil && flow.Antecedents().Next != nil) + if !endsIteration && flow != entryFlow { + if !isFlowMemoCheckpoint(flow) { + return flowMemoHit{}, false, false + } + c.flowMemo.checkpoints = append(c.flowMemo.checkpoints, flow) + } + hit, ok := c.flowMemoLookup(f, flow, key, entryDepth) + if !ok { + return flowMemoHit{}, false, false + } + return hit, endsIteration, true +} + +func (c *Checker) flowMemoLookup(f *FlowState, flow *ast.FlowNode, key CacheHashKey, entryDepth int) (flowMemoHit, bool) { + hit, ok := c.flowMemo.lookup(flow, key) + if !ok { + return flowMemoHit{}, false + } + if !c.flowMemoConsultOk(f, hit) { + return flowMemoHit{}, false + } + if entryDepth+int(hit.height) >= flowDepthLimit { + return flowMemoHit{}, false + } + return hit, true +} + +func (c *Checker) flowMemoFill(f *FlowState, memo frameMemo, entryFlow, finalFlow *ast.FlowNode, t FlowType, taint frameTaint, height uint16, steps uint32, hasHit bool) { + if !taint.isPure() || taint.flags&flowMemoFlagEffects != 0 { + return + } + if t.incomplete || c.flowAnalysisDisabled { + return + } + if height == 0 && steps < 4 && !hasHit { + return + } + epoch := uint32(0) + if taint.flags&flowMemoFlagTypeCache != 0 { + epoch = c.flowMemo.typeCacheEpoch + } + countReset := taint.flags&flowMemoFlagCountReset != 0 + if entryFlow != finalFlow || !hasHit { + c.flowMemo.store(entryFlow, memo.key, t.t, height, epoch, countReset) + } + if finalFlow != entryFlow && !hasHit { + c.flowMemo.store(finalFlow, memo.key, t.t, height, epoch, countReset) + } + for i := int(memo.checkpoints); i < len(c.flowMemo.checkpoints); i++ { + checkpoint := c.flowMemo.checkpoints[i] + if checkpoint != finalFlow { + c.flowMemo.store(checkpoint, memo.key, t.t, height, epoch, countReset) + } + } +} + +func (c *Checker) takeFlowTypeCache() savedFlowTypeCache { + saved := savedFlowTypeCache{ + cache: c.flowTypeCache, + epoch: c.flowMemo.typeCacheEpoch, + } + c.flowTypeCache = nil + c.flowMemo.typeCacheEpoch = c.flowMemo.nextSerial() + return saved +} + +func (c *Checker) restoreFlowTypeCache(saved savedFlowTypeCache) { + c.flowTypeCache = saved.cache + c.flowMemo.typeCacheEpoch = saved.epoch +} diff --git a/tsc/internal/checker/inference.go b/tsc/internal/checker/inference.go index 8b0cc87aedef4..48e8ea3cd6f96 100644 --- a/tsc/internal/checker/inference.go +++ b/tsc/internal/checker/inference.go @@ -8,12 +8,16 @@ import ( "github.com/microsoft/TypeScript/tsc/internal/jsnum" ) -type InferenceKey struct { - source TypeId - target TypeId - priority InferencePriority - contravariant bool - bivariant bool +type inferInfoOutcome struct { + candidates []*Type + contraCandidates []*Type + priority InferencePriority + topLevel bool +} + +type inferMemoEntry struct { + infos []inferInfoOutcome + cleared bool } type InferenceState struct { @@ -24,11 +28,12 @@ type InferenceState struct { inferencePriority InferencePriority contravariant bool bivariant bool + clearedInferences bool expandingFlags ExpandingFlags propagationType *Type visited clearableMap[InferencePriority] - sourceStack []*Type - targetStack []*Type + sourceStack []relaterStackEntry + targetStack []relaterStackEntry next *InferenceState } @@ -53,6 +58,11 @@ func (c *Checker) putInferenceState(n *InferenceState) { c.freeinferenceState = n } +const ( + inferMemoMaxInfos = 8 + inferMemoMinSteps = 16 +) + func (c *Checker) inferTypes(inferences []*InferenceInfo, originalSource *Type, originalTarget *Type, priority InferencePriority, contravariant bool) { n := c.getInferenceState() n.inferences = inferences @@ -61,11 +71,115 @@ func (c *Checker) inferTypes(inferences []*InferenceInfo, originalSource *Type, n.priority = priority n.inferencePriority = InferencePriorityMaxValue n.contravariant = contravariant - c.inferFromTypes(n, originalSource, originalTarget) + c.inferFromTypesMemo(n, originalSource, originalTarget) c.putInferenceState(n) } +func (c *Checker) inferFromTypesMemo(n *InferenceState, source *Type, target *Type) { + if !c.inferLongTargets[target] { + stepsBefore := c.inferMemoSteps + c.inferFromTypes(n, source, target) + if c.inferMemoSteps-stepsBefore >= inferMemoMinSteps { + if c.inferLongTargets == nil { + c.inferLongTargets = make(map[*Type]bool) + } + c.inferLongTargets[target] = true + } + return + } + inferences := n.inferences + if c.skipDirectInferenceNodes.Len() != 0 || len(inferences) > inferMemoMaxInfos { + c.inferFromTypes(n, source, target) + return + } + var b keyBuilder + b.writeUint32(uint32(source.id)) + b.writeUint32(uint32(target.id)) + b.writeUint32(uint32(n.priority)) + b.writeByte(core.IfElse(n.contravariant, byte(1), byte(0))) + b.writeUint32(uint32(len(inferences))) + for _, info := range inferences { + var tpId uint32 + if info.typeParameter != nil { + tpId = uint32(info.typeParameter.id) + } + b.writeUint32(tpId) + b.writeUint32(uint32(info.priority)) + b.writeByte(core.IfElse(info.topLevel, byte(1), byte(0)) | core.IfElse(info.isFixed, byte(2), byte(0))) + b.writeUint32(uint32(info.impliedArity)) + b.writeUint32(uint32(len(info.candidates))) + b.writeTypes(info.candidates) + b.writeUint32(uint32(len(info.contraCandidates))) + b.writeTypes(info.contraCandidates) + } + key := b.hash() + if entry := c.inferMemoEntries[key]; entry != nil { + for i, info := range inferences { + outcome := &entry.infos[i] + info.candidates = append(info.candidates[:0], outcome.candidates...) + info.contraCandidates = append(info.contraCandidates[:0], outcome.contraCandidates...) + info.priority = outcome.priority + info.topLevel = outcome.topLevel + } + if entry.cleared { + clearCachedInferences(inferences) + } + return + } + inInstantiation := len(c.activeMappers) != 0 + resetsBefore := c.instantiationCountResets + cyclesBefore := c.resolutionCycleCount + var hasFailedResolution bool + for i := c.resolutionStart; i < len(c.typeResolutions); i++ { + if !c.typeResolutions[i].result { + hasFailedResolution = true + break + } + } + typeCount := c.TypeCount + symbolCount := c.SymbolCount + sigCount := c.SignatureCount + instCount := c.instantiationCount + totalInstCount := c.TotalInstantiationCount + diagCount := c.diagnosticCount + impureCount := c.unionImpureCount + stepsBefore := c.inferMemoSteps + c.inferFromTypes(n, source, target) + steps := c.inferMemoSteps - stepsBefore + if steps >= inferMemoMinSteps && + !inInstantiation && + !hasFailedResolution && + c.instantiationCountResets == resetsBefore && + c.resolutionCycleCount == cyclesBefore && + c.TypeCount == typeCount && + c.SymbolCount == symbolCount && + c.SignatureCount == sigCount && + c.instantiationCount == instCount && + c.TotalInstantiationCount == totalInstCount && + c.diagnosticCount == diagCount && + c.unionImpureCount == impureCount && + c.skipDirectInferenceNodes.Len() == 0 { + outcomes := make([]inferInfoOutcome, len(inferences)) + for i, info := range inferences { + outcomes[i] = inferInfoOutcome{ + candidates: slices.Clone(info.candidates), + contraCandidates: slices.Clone(info.contraCandidates), + priority: info.priority, + topLevel: info.topLevel, + } + } + if c.inferMemoEntries == nil { + c.inferMemoEntries = make(map[CacheHashKey]*inferMemoEntry) + } + c.inferMemoEntries[key] = &inferMemoEntry{ + infos: outcomes, + cleared: n.clearedInferences, + } + } +} + func (c *Checker) inferFromTypes(n *InferenceState, source *Type, target *Type) { + c.inferMemoSteps++ if !c.couldContainTypeVariables(target) || c.isNoInferType(target) { return } @@ -95,19 +209,22 @@ func (c *Checker) inferFromTypes(n *InferenceState, source *Type, target *Type) return } if target.flags&TypeFlagsUnion != 0 { + var sourceBuf [1]*Type var sourceTypes []*Type if source.flags&TypeFlagsUnion != 0 { sourceTypes = source.Types() } else { - sourceTypes = []*Type{source} + sourceBuf[0] = source + sourceTypes = sourceBuf[:] } + targetTypes := target.Types() // First, infer between identically matching source and target constituents and remove the // matching types. - tempSources, tempTargets := c.inferFromMatchingTypes(n, sourceTypes, target.Types(), (*Checker).isTypeOrBaseIdenticalTo, false /*sort*/) + sources, targets := c.inferFromMatchingTypes(n, sourceTypes, targetTypes, (*Checker).isTypeOrBaseIdenticalTo, false /*sort*/) // Next, infer between closely matching source and target constituents and remove // the matching types. Types closely match when they are instantiations of the same // object type or instantiations of the same type alias. - sources, targets := c.inferFromMatchingTypes(n, tempSources, tempTargets, (*Checker).isTypeCloselyMatchedBy, true /*sort*/) + sources, targets = c.inferFromMatchingTypes(n, sources, targets, (*Checker).isTypeCloselyMatchedBy, true /*sort*/) if len(targets) == 0 { return } @@ -117,22 +234,24 @@ func (c *Checker) inferFromTypes(n *InferenceState, source *Type, target *Type) // However, simply making no inferences is undesirable because it could ultimately mean // inferring a type parameter constraint. Instead, make a lower priority inference from // the full source to whatever remains in the target. For example, when inferring from - // string to 'string | T', make a lower priority inference of string for T. + // `string` to `string | T`, make a lower priority inference of `string` for `T`. c.inferWithPriority(n, source, target, InferencePriorityNakedTypeVariable) return } source = c.getUnionType(sources) } else if target.flags&TypeFlagsIntersection != 0 && !core.Every(target.Types(), c.isNonGenericObjectType) { // We reduce intersection types unless they're simple combinations of object types. For example, - // when inferring from 'string[] & { extra: any }' to 'string[] & T' we want to remove string[] and - // infer { extra: any } for T. But when inferring to 'string[] & Iterable' we want to keep the - // string[] on the source side and infer string for T. + // when inferring from `string[] & { extra: any }` to `string[] & T` we want to remove `string[]` and + // infer `{ extra: any }` for `T`. But when inferring to `string[] & Iterable` we want to keep the + // `string[]` on the source side and infer `string` for `T`. if source.flags&TypeFlagsUnion == 0 { + var sourceBuf [1]*Type var sourceTypes []*Type if source.flags&TypeFlagsIntersection != 0 { sourceTypes = source.Types() } else { - sourceTypes = []*Type{source} + sourceBuf[0] = source + sourceTypes = sourceBuf[:] } // Infer between identically matching source and target constituents and remove the matching types. sources, targets := c.inferFromMatchingTypes(n, sourceTypes, target.Types(), (*Checker).isTypeIdenticalTo, false /*sort*/) @@ -182,8 +301,10 @@ func (c *Checker) inferFromTypes(n *InferenceState, source *Type, target *Type) return } if n.priority < inference.priority { - inference.candidates = nil - inference.contraCandidates = nil + clear(inference.candidates) + inference.candidates = inference.candidates[:0] + clear(inference.contraCandidates) + inference.contraCandidates = inference.contraCandidates[:0] inference.topLevel = true inference.priority = n.priority } @@ -193,15 +314,18 @@ func (c *Checker) inferFromTypes(n *InferenceState, source *Type, target *Type) if n.contravariant && !n.bivariant { if !slices.Contains(inference.contraCandidates, candidate) { inference.contraCandidates = append(inference.contraCandidates, candidate) + n.clearedInferences = true clearCachedInferences(n.inferences) } } else if !slices.Contains(inference.candidates, candidate) { inference.candidates = append(inference.candidates, candidate) + n.clearedInferences = true clearCachedInferences(n.inferences) } } if n.priority&InferencePriorityReturnType == 0 && target.flags&TypeFlagsTypeParameter != 0 && inference.topLevel && !c.isTypeParameterAtTopLevel(n.originalTarget, target, 0) { inference.topLevel = false + n.clearedInferences = true clearCachedInferences(n.inferences) } } @@ -358,12 +482,12 @@ func (c *Checker) invokeOnce(n *InferenceState, source *Type, target *Type, acti // We stop inferring and report a circularity if we encounter duplicate recursion identities on both // the source side and the target side. saveExpandingFlags := n.expandingFlags - n.sourceStack = append(n.sourceStack, source) - n.targetStack = append(n.targetStack, target) - if c.isDeeplyNestedType(source, n.sourceStack, 2) { + n.sourceStack = append(n.sourceStack, relaterStackEntry{t: source}) + n.targetStack = append(n.targetStack, relaterStackEntry{t: target}) + if n.expandingFlags&ExpandingFlagsSource == 0 && c.isDeeplyNestedOnStack(source, n.sourceStack, 2) { n.expandingFlags |= ExpandingFlagsSource } - if c.isDeeplyNestedType(target, n.targetStack, 2) { + if n.expandingFlags&ExpandingFlagsTarget == 0 && c.isDeeplyNestedOnStack(target, n.targetStack, 2) { n.expandingFlags |= ExpandingFlagsTarget } if n.expandingFlags != ExpandingFlagsBoth { @@ -379,8 +503,10 @@ func (c *Checker) invokeOnce(n *InferenceState, source *Type, target *Type, acti } func (c *Checker) inferFromMatchingTypes(n *InferenceState, sources []*Type, targets []*Type, matches func(c *Checker, s *Type, t *Type) bool, sort bool) ([]*Type, []*Type) { - var matchedSources []*Type - var matchedTargets []*Type + var matchedSourcesBuf [8]*Type + var matchedTargetsBuf [8]*Type + matchedSources := matchedSourcesBuf[:0] + matchedTargets := matchedTargetsBuf[:0] for _, t := range targets { for _, s := range sources { if matches(c, s, t) { @@ -392,13 +518,16 @@ func (c *Checker) inferFromMatchingTypes(n *InferenceState, sources []*Type, tar } } } + if len(matchedSources) == 0 { + return sources, targets + } if sort { // Sort target types by decreasing depth of generic instantiations. Intuitively, a successful // inference from a type argument with deeper nesting is of higher quality because we've stripped // away more layers of type instantiations that otherwise might skew the results. For example, - // 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[]. + // 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) for _, t := range matchedTargets { for _, s := range matchedSources { @@ -408,13 +537,25 @@ func (c *Checker) inferFromMatchingTypes(n *InferenceState, sources []*Type, tar } } } - if len(matchedSources) != 0 { - sources = core.Filter(sources, func(t *Type) bool { return !slices.Contains(matchedSources, t) }) + var newSources []*Type + if len(sources) > len(matchedSources) { + newSources = make([]*Type, 0, len(sources)-len(matchedSources)) + for _, s := range sources { + if !slices.Contains(matchedSources, s) { + newSources = append(newSources, s) + } + } } - if len(matchedTargets) != 0 { - targets = core.Filter(targets, func(t *Type) bool { return !slices.Contains(matchedTargets, t) }) + var newTargets []*Type + if len(targets) > len(matchedTargets) { + newTargets = make([]*Type, 0, len(targets)-len(matchedTargets)) + for _, t := range targets { + if !slices.Contains(matchedTargets, t) { + newTargets = append(newTargets, t) + } + } } - return sources, targets + return newSources, newTargets } // Compare two types first by depth and then by the regular type ordering. @@ -461,12 +602,20 @@ func (c *Checker) inferToMultipleTypes(n *InferenceState, source *Type, targets if targetFlags&TypeFlagsUnion != 0 { var nakedTypeVariable *Type var sources []*Type + var sourcesBuf [1]*Type if source.flags&TypeFlagsUnion != 0 { sources = source.Types() } else { - sources = []*Type{source} + sourcesBuf[0] = source + sources = sourcesBuf[:] + } + var matchedBuf [64]bool + var matched []bool + if len(sources) <= len(matchedBuf) { + matched = matchedBuf[:len(sources)] + } else { + matched = make([]bool, len(sources)) } - matched := make([]bool, len(sources)) inferenceCircularity := false // First infer to types that are not naked type variables. For each source type we // track whether inferences were made from that particular type to some target with @@ -504,7 +653,8 @@ func (c *Checker) inferToMultipleTypes(n *InferenceState, source *Type, targets // types from which no inferences have been made so far and infer from that union to the // naked type variable. if typeVariableCount == 1 && !inferenceCircularity { - var unmatched []*Type + var unmatchedBuf [8]*Type + unmatched := unmatchedBuf[:0] for i, s := range sources { if !matched[i] { unmatched = append(unmatched, s) @@ -569,13 +719,18 @@ func (c *Checker) inferToConditionalType(n *InferenceState, source *Type, target c.inferFromTypes(n, getNonDistributedTypeParameter(c.getTrueTypeFromConditionalType(source)), c.getTrueTypeFromConditionalType(target)) c.inferFromTypes(n, getNonDistributedTypeParameter(c.getFalseTypeFromConditionalType(source)), c.getFalseTypeFromConditionalType(target)) } else { - targetTypes := []*Type{c.getTrueTypeFromConditionalType(target), c.getFalseTypeFromConditionalType(target)} - c.inferToMultipleTypesWithPriority(n, source, targetTypes, target.flags, core.IfElse(n.contravariant, InferencePriorityContravariantConditional, 0)) + var targetTypes [2]*Type + targetTypes[0] = c.getTrueTypeFromConditionalType(target) + targetTypes[1] = c.getFalseTypeFromConditionalType(target) + c.inferToMultipleTypesWithPriority(n, source, targetTypes[:], target.flags, core.IfElse(n.contravariant, InferencePriorityContravariantConditional, 0)) } } func (c *Checker) inferToTemplateLiteralType(n *InferenceState, source *Type, target *TemplateLiteralType) { matches := c.inferTypesFromTemplateLiteralType(source, target, c.compareTypesAssignable) + if matches != nil { + defer c.releaseTemporaryTypeArguments(matches) + } types := target.types // When the target template literal contains only placeholders (meaning that inference is intended to extract // single characters and remainder strings) and inference fails to produce matches, we want to infer 'never' for @@ -837,26 +992,48 @@ func (c *Checker) inferFromObjectTypes(n *InferenceState, source *Type, target * } func (c *Checker) inferFromProperties(n *InferenceState, source *Type, target *Type) { - properties := c.getPropertiesOfObjectType(target) + properties, lazyTarget := c.getPropertiesOfTypeLazily(target) + resolvedSource := c.isResolvedPlainObjectType(source) for _, targetProp := range properties { - sourceProp := c.getPropertyOfType(source, targetProp.Name()) + if lazyTarget == nil || lazyTarget.objectFlags&ObjectFlagsMapped == 0 { + if links := c.valueSymbolLinks.TryGet(targetProp); links != nil && links.resolvedType != nil && !c.couldContainTypeVariables(links.resolvedType) { + continue + } + } + var sourceProp *ast.Symbol + if resolvedSource { + sourceProp = c.getResolvedPropertyOfPlainObjectType(source, targetProp.Name()) + } else { + sourceProp = c.getPropertyOfTypeWorker(source, targetProp.Name(), false /*skipObjectFunctionPropertyAugment*/, false /*includeTypeOnlyMembers*/, false /*instantiate*/) + } if sourceProp != nil && !core.Some(sourceProp.Declarations(), c.isSkipDirectInferenceNode) { - c.inferFromTypes(n, c.removeMissingType(c.getTypeOfSymbol(sourceProp), sourceProp.Flags()&ast.SymbolFlagsOptional != 0), c.removeMissingType(c.getTypeOfSymbol(targetProp), targetProp.Flags()&ast.SymbolFlagsOptional != 0)) + if lazyTarget != nil { + targetProp = c.getPropertyOfType(lazyTarget, targetProp.Name()) + } + targetType := c.removeMissingType(c.getTypeOfSymbol(targetProp), targetProp.Flags()&ast.SymbolFlagsOptional != 0) + if c.couldContainTypeVariables(targetType) && !c.isNoInferType(targetType) { + if !resolvedSource { + sourceProp = c.getPropertyOfType(source, targetProp.Name()) + } + c.inferFromTypes(n, c.removeMissingType(c.getTypeOfSymbol(sourceProp), sourceProp.Flags()&ast.SymbolFlagsOptional != 0), targetType) + } } } } func (c *Checker) inferFromSignatures(n *InferenceState, source *Type, target *Type, kind SignatureKind) { - sourceSignatures := c.getSignaturesOfType(source, kind) - sourceLen := len(sourceSignatures) - if sourceLen > 0 { - // We match source and target signatures from the bottom up, and if the source has fewer signatures - // than the target, we infer from the first source signature to the excess target signatures. - targetSignatures := c.getSignaturesOfType(target, kind) - targetLen := len(targetSignatures) - for i := range targetLen { - sourceIndex := max(sourceLen-targetLen+i, 0) - c.inferFromSignature(n, c.getBaseSignature(sourceSignatures[sourceIndex]), c.getErasedSignature(targetSignatures[i])) + targetSignatures := c.getSignaturesOfType(target, kind) + targetLen := len(targetSignatures) + if targetLen > 0 { + sourceSignatures := c.getSignaturesOfType(source, kind) + sourceLen := len(sourceSignatures) + if sourceLen > 0 { + // We match source and target signatures from the bottom up, and if the source has fewer signatures + // than the target, we infer from the first source signature to the excess target signatures. + for i := range targetLen { + sourceIndex := max(sourceLen-targetLen+i, 0) + c.inferFromSignature(n, c.getBaseSignature(sourceSignatures[sourceIndex]), c.getErasedSignature(targetSignatures[i])) + } } } } @@ -889,26 +1066,28 @@ func (c *Checker) applyToParameterTypes(source *Signature, target *Signature, ca if sourceRestType == nil { paramCount = min(sourceCount, targetNonRestCount) } - sourceThisType := c.getThisTypeOfSignature(source) - if sourceThisType != nil { - targetThisType := c.getThisTypeOfSignature(target) - if targetThisType != nil { + targetThisType := c.getThisTypeOfSignature(target) + if targetThisType != nil && c.couldContainTypeVariables(targetThisType) { + if sourceThisType := c.getThisTypeOfSignature(source); sourceThisType != nil { callback(sourceThisType, targetThisType) } } for i := range paramCount { - callback(c.getTypeAtPosition(source, i), c.getTypeAtPosition(target, i)) + targetParamType := c.getTypeAtPosition(target, i) + if c.couldContainTypeVariables(targetParamType) { + callback(c.getTypeAtPosition(source, i), targetParamType) + } } - if targetRestType != nil { + if targetRestType != nil && c.couldContainTypeVariables(targetRestType) { callback(c.getRestTypeAtPosition(source, paramCount, c.isConstTypeVariable(targetRestType, 0) && !someType(targetRestType, c.isMutableArrayLikeType) /*readonly*/), targetRestType) } } func (c *Checker) applyToReturnTypes(source *Signature, target *Signature, callback func(s *Type, t *Type)) { targetTypePredicate := c.getTypePredicateOfSignature(target) - if targetTypePredicate != nil { + if targetTypePredicate != nil && targetTypePredicate.t != nil && c.couldContainTypeVariables(targetTypePredicate.t) { sourceTypePredicate := c.getTypePredicateOfSignature(source) - if sourceTypePredicate != nil && c.typePredicateKindsMatch(sourceTypePredicate, targetTypePredicate) && sourceTypePredicate.t != nil && targetTypePredicate.t != nil { + if sourceTypePredicate != nil && c.typePredicateKindsMatch(sourceTypePredicate, targetTypePredicate) && sourceTypePredicate.t != nil { callback(sourceTypePredicate.t, targetTypePredicate.t) return } @@ -926,6 +1105,9 @@ func (c *Checker) inferFromIndexTypes(n *InferenceState, source *Type, target *T priority = InferencePriorityHomomorphicMappedType } indexInfos := c.getIndexInfosOfType(target) + if len(indexInfos) == 0 { + return + } if c.isObjectTypeWithInferableIndex(source) { for _, targetInfo := range indexInfos { var propTypes []*Type @@ -1137,7 +1319,9 @@ func (c *Checker) resolveReverseMappedTypeMembers(t *Type) { checkFlags := ast.CheckFlagsReverseMapped | core.IfElse(readonlyMask && c.isReadonlySymbol(prop), ast.CheckFlagsReadonly, 0) inferredProp := c.newSymbolEx(ast.SymbolFlagsProperty|prop.Flags()&optionalMask, prop.Name(), checkFlags) inferredProp.SetDeclarations(prop.Declarations()) - c.valueSymbolLinks.Get(inferredProp).setNameType(c, c.valueSymbolLinks.Get(prop).getNameType()) + if nameType := c.valueSymbolLinks.TryGet(prop).getNameType(); nameType != nil { + c.valueSymbolLinks.Get(inferredProp).setNameType(c, nameType) + } links := c.ReverseMappedSymbolLinks.Get(inferredProp) links.propertyType = c.getTypeOfSymbol(prop) constraintTarget := r.constraintType.AsIndexType().target @@ -1243,7 +1427,7 @@ func (c *Checker) isTypeCloselyMatchedBy(s *Type, t *Type) bool { // Create an object with properties named in the string literal type. Every property has type `any`. func (c *Checker) createEmptyObjectTypeFromStringLiteral(t *Type) *Type { - members := ast.NewSymbolTable() + members := c.acquireSymbolTable() for _, t := range t.Distributed() { if t.flags&TypeFlagsStringLiteral == 0 { continue @@ -1261,21 +1445,107 @@ func (c *Checker) createEmptyObjectTypeFromStringLiteral(t *Type) *Type { if t.flags&TypeFlagsString != 0 { indexInfos = []*IndexInfo{c.newIndexInfo(c.stringType, c.emptyObjectType, false /*isReadonly*/, nil, nil)} } - return c.newAnonymousType(nil, members, nil, nil, indexInfos) + res := c.newAnonymousType(nil, members, nil, nil, indexInfos) + c.releaseSymbolTableIfUnused(res, members) + return res } func (c *Checker) newInferenceContext(typeParameters []*Type, signature *Signature, flags InferenceFlags, compareTypes TypeComparer) *InferenceContext { + return c.acquireInferenceContext(typeParameters, signature, flags, compareTypes) +} + +func (c *Checker) acquireConditionalInferenceContext(typeParameters []*Type) *InferenceContext { + return c.acquireInferenceContext(typeParameters, nil, InferenceFlagsNone, c.compareTypesAssignable) +} + +func (c *Checker) acquireInferenceContext(typeParameters []*Type, signature *Signature, flags InferenceFlags, compareTypes TypeComparer) *InferenceContext { if compareTypes == nil { compareTypes = c.compareTypesAssignable } - return c.newInferenceContextWorker(core.Map(typeParameters, newInferenceInfo), signature, flags, compareTypes) + var n *InferenceContext + if l := len(c.freeInferenceContexts); l > 0 { + n = c.freeInferenceContexts[l-1] + c.freeInferenceContexts = c.freeInferenceContexts[:l-1] + n.signature = signature + n.flags = flags + n.compareTypes = compareTypes + n.returnMapper = nil + n.outerReturnMapper = nil + n.inferredTypeParameters = nil + n.intraExpressionInferenceSites = nil + n.escaped = false + n.mapper = c.initInferenceTypeMapper(&n.mapperStorage[0], n, true /*fixing*/) + n.nonFixingMapper = c.initInferenceTypeMapper(&n.mapperStorage[1], n, false /*fixing*/) + } else { + n = c.newInferenceContextWorker(nil, signature, flags, compareTypes) + } + inferences := n.inferences[:0] + for _, tp := range typeParameters { + var info *InferenceInfo + if l := len(c.freeInferenceInfos); l > 0 { + info = c.freeInferenceInfos[l-1] + c.freeInferenceInfos = c.freeInferenceInfos[:l-1] + info.typeParameter = tp + info.inferredType = nil + info.priority = InferencePriorityMaxValue + info.topLevel = true + info.isFixed = false + info.impliedArity = -1 + } else { + info = newInferenceInfo(tp) + } + inferences = append(inferences, info) + } + n.inferences = inferences + return n +} + +func (c *Checker) releaseConditionalInferenceContext(n *InferenceContext, fixingCombined *TypeMapper, nonFixingCombined *TypeMapper) { + if fixingCombined != nil && fixingCombined.kind == TypeMapperKindComposite { + c.compositeMapperArena.Pop(castTypeMapper[CompositeTypeMapper](fixingCombined, TypeMapperKindComposite)) + } + if nonFixingCombined != nil && nonFixingCombined.kind == TypeMapperKindComposite { + c.compositeMapperArena.Pop(castTypeMapper[CompositeTypeMapper](nonFixingCombined, TypeMapperKindComposite)) + } + for i, info := range n.inferences { + clear(info.candidates) + info.candidates = info.candidates[:0] + clear(info.contraCandidates) + info.contraCandidates = info.contraCandidates[:0] + info.typeParameter = nil + info.inferredType = nil + c.freeInferenceInfos = append(c.freeInferenceInfos, info) + n.inferences[i] = nil + } + n.inferences = n.inferences[:0] + n.signature = nil + n.returnMapper = nil + n.outerReturnMapper = nil + n.inferredTypeParameters = nil + n.intraExpressionInferenceSites = nil + c.freeInferenceContexts = append(c.freeInferenceContexts, n) } func (c *Checker) cloneInferenceContext(n *InferenceContext, extraFlags InferenceFlags) *InferenceContext { if n == nil { return nil } - return c.newInferenceContextWorker(core.Map(n.inferences, cloneInferenceInfo), n.signature, n.flags|extraFlags, n.compareTypes) + infos := make([]InferenceInfo, len(n.inferences)) + inferences := make([]*InferenceInfo, len(n.inferences)) + for i, info := range n.inferences { + infos[i] = InferenceInfo{ + typeParameter: info.typeParameter, + candidates: slices.Clone(info.candidates), + contraCandidates: slices.Clone(info.contraCandidates), + inferredType: info.inferredType, + priority: info.priority, + topLevel: info.topLevel, + isFixed: info.isFixed, + impliedArity: info.impliedArity, + } + inferences[i] = &infos[i] + } + return c.newInferenceContextWorker(inferences, n.signature, n.flags|extraFlags, n.compareTypes) } func (c *Checker) cloneInferredPartOfContext(n *InferenceContext) *InferenceContext { @@ -1283,7 +1553,22 @@ func (c *Checker) cloneInferredPartOfContext(n *InferenceContext) *InferenceCont if len(inferences) == 0 { return nil } - return c.newInferenceContextWorker(core.Map(inferences, cloneInferenceInfo), n.signature, n.flags, n.compareTypes) + infos := make([]InferenceInfo, len(inferences)) + cloned := make([]*InferenceInfo, len(inferences)) + for i, info := range inferences { + infos[i] = InferenceInfo{ + typeParameter: info.typeParameter, + candidates: slices.Clone(info.candidates), + contraCandidates: slices.Clone(info.contraCandidates), + inferredType: info.inferredType, + priority: info.priority, + topLevel: info.topLevel, + isFixed: info.isFixed, + impliedArity: info.impliedArity, + } + cloned[i] = &infos[i] + } + return c.newInferenceContextWorker(cloned, n.signature, n.flags, n.compareTypes) } func (c *Checker) newInferenceContextWorker(inferences []*InferenceInfo, signature *Signature, flags InferenceFlags, compareTypes TypeComparer) *InferenceContext { @@ -1641,7 +1926,9 @@ func (c *Checker) isSkipDirectInferenceNode(node *ast.Node) bool { } func newInferenceInfo(typeParameter *Type) *InferenceInfo { - return &InferenceInfo{typeParameter: typeParameter, priority: InferencePriorityMaxValue, topLevel: true, impliedArity: -1} + info := &InferenceInfo{typeParameter: typeParameter, priority: InferencePriorityMaxValue, topLevel: true, impliedArity: -1} + info.candidates = info.candidateStorage[:0] + return info } func cloneInferenceInfo(info *InferenceInfo) *InferenceInfo { diff --git a/tsc/internal/checker/relater.go b/tsc/internal/checker/relater.go index d95802dbea58a..20752154d23df 100644 --- a/tsc/internal/checker/relater.go +++ b/tsc/internal/checker/relater.go @@ -1,11 +1,11 @@ package checker - import ( "math/bits" "slices" "strconv" "strings" + "unsafe" "github.com/microsoft/TypeScript/tsc/internal/ast" "github.com/microsoft/TypeScript/tsc/internal/binder" @@ -66,12 +66,18 @@ const ( type RelationComparisonResult uint32 const ( + relationComparisonResultSucceededBitShift = 0 + relationComparisonResultFailedBitShift = 1 + relationComparisonResultReportsUnmeasurableBitShift = 3 + relationComparisonResultReportsUnreliableBitShift = 4 + relationComparisonResultOverflowBitShift = 5 + RelationComparisonResultNone RelationComparisonResult = 0 - RelationComparisonResultSucceeded RelationComparisonResult = 1 << 0 - RelationComparisonResultFailed RelationComparisonResult = 1 << 1 - RelationComparisonResultReportsUnmeasurable RelationComparisonResult = 1 << 3 - RelationComparisonResultReportsUnreliable RelationComparisonResult = 1 << 4 - RelationComparisonResultComplexityOverflow RelationComparisonResult = 1 << 5 + RelationComparisonResultSucceeded RelationComparisonResult = 1 << relationComparisonResultSucceededBitShift + RelationComparisonResultFailed RelationComparisonResult = 1 << relationComparisonResultFailedBitShift + RelationComparisonResultReportsUnmeasurable RelationComparisonResult = 1 << relationComparisonResultReportsUnmeasurableBitShift + RelationComparisonResultReportsUnreliable RelationComparisonResult = 1 << relationComparisonResultReportsUnreliableBitShift + RelationComparisonResultComplexityOverflow RelationComparisonResult = 1 << relationComparisonResultOverflowBitShift RelationComparisonResultReportsMask = RelationComparisonResultReportsUnmeasurable | RelationComparisonResultReportsUnreliable RelationComparisonResultOverflow = RelationComparisonResultComplexityOverflow ) @@ -88,32 +94,352 @@ type ErrorOutputContainer struct { type ErrorReporter func(message *diagnostics.Message, args ...any) -type RecursionId struct { - value any -} +type RecursionId uintptr + +const recursionIdIntersection RecursionId = 1 // This function exists to constrain the types of values that can be used as recursion IDs. -func asRecursionId[T *ast.Node | *ast.Symbol | *Type](value T) RecursionId { - return RecursionId{value: value} +func asRecursionId[T *Type | *ast.Symbol | *ast.Node](value T) RecursionId { + // SAFETY: Uses the pointer address as a numeric recursion identity while the value remains live on the stack. + return RecursionId(uintptr(unsafe.Pointer(value))) } type Relation struct { - results map[CacheHashKey]RelationComparisonResult + packedResults *packedRelationCache + packedShift uint8 + simpleShift uint8 +} + +const ( + simpleRelationKeyBits = 54 + simpleRelationKeyMask = (uint64(1) << simpleRelationKeyBits) - 1 + simpleRelationSlotMask = 1 | 1<<2 | 1<<4 | 1<<6 | 1<<8 + simpleRelationUpgraded = 0x3ff +) + +type simpleRelationMap struct { + slots []uint64 + count uint32 + shift uint8 +} + +func (m *simpleRelationMap) get(key uint64) uint32 { + if len(m.slots) == 0 { + return 0 + } + i := uint32(((key ^ (key >> 23)) * 0x9e3779b97f4a7c15) >> m.shift) + s := m.slots[i] + if s == 0 { + return 0 + } + if s&simpleRelationKeyMask == key { + return uint32(s >> simpleRelationKeyBits) + } + s = m.slots[(i+1)&uint32(len(m.slots)-1)] + if s == 0 { + return 0 + } + if s&simpleRelationKeyMask == key { + return uint32(s >> simpleRelationKeyBits) + } + return m.getSlow(key, i+2) +} + +//go:noinline +func (m *simpleRelationMap) getSlow(key uint64, i uint32) uint32 { + slots := m.slots + mask := uint32(len(slots) - 1) + for { + s := slots[i&mask] + if s == 0 { + return 0 + } + if s&simpleRelationKeyMask == key { + return uint32(s >> simpleRelationKeyBits) + } + i++ + } +} + +func (m *simpleRelationMap) slot(key uint64) *uint64 { + if len(m.slots) == 0 { + m.slots = make([]uint64, 256) + m.shift = 64 - 8 + } else if int(m.count+1)*4 > len(m.slots)*3 { + m.grow() + } + slots := m.slots + mask := uint32(len(slots) - 1) + i := uint32(((key ^ (key >> 23)) * 0x9e3779b97f4a7c15) >> m.shift) + for { + s := &slots[i&mask] + if *s == 0 { + *s = key + m.count++ + return s + } + if *s&simpleRelationKeyMask == key { + return s + } + i++ + } +} + +func (m *simpleRelationMap) grow() { + old := m.slots + newCap := len(old) * 2 + m.shift-- + if len(old) <= 16384 { + newCap = len(old) * 4 + m.shift-- + } + slots := make([]uint64, newCap) + m.slots = slots + mask := uint32(newCap - 1) + shift := m.shift + for _, s := range old { + if s == 0 { + continue + } + key := s & simpleRelationKeyMask + i := uint32(((key ^ (key >> 23)) * 0x9e3779b97f4a7c15) >> shift) + for slots[i&mask] != 0 { + i++ + } + slots[i&mask] = s + } +} + +type genericRelationSlot struct { + lo0 uint32 + lo1 uint32 + hi0 uint32 + hi1 uint32 + packed uint32 +} + +type genericRelationMap struct { + slots []genericRelationSlot + count uint32 + shift uint8 +} + +func (m *genericRelationMap) get(key CacheHashKey) uint32 { + if len(m.slots) == 0 { + return 0 + } + lo0, lo1 := uint32(key.Lo), uint32(key.Lo>>32) + hi0, hi1 := uint32(key.Hi), uint32(key.Hi>>32) + mask := uint32(len(m.slots) - 1) + i := uint32(key.Lo >> m.shift) + for { + s := m.slots[i] + if s.packed == 0 { + return 0 + } + if s.lo0 == lo0 && s.lo1 == lo1 && s.hi0 == hi0 && s.hi1 == hi1 { + return s.packed + } + i = (i + 1) & mask + } } -func (r *Relation) get(key CacheHashKey) RelationComparisonResult { - return r.results[key] +func (m *genericRelationMap) slot(key CacheHashKey) *genericRelationSlot { + if len(m.slots) == 0 { + m.slots = make([]genericRelationSlot, 32) + m.shift = 64 - 5 + } else if int(m.count+1)*4 > len(m.slots)*3 { + m.grow() + } + lo0, lo1 := uint32(key.Lo), uint32(key.Lo>>32) + hi0, hi1 := uint32(key.Hi), uint32(key.Hi>>32) + mask := uint32(len(m.slots) - 1) + i := uint32(key.Lo >> m.shift) + for { + s := &m.slots[i] + if s.packed == 0 { + s.lo0 = lo0 + s.lo1 = lo1 + s.hi0 = hi0 + s.hi1 = hi1 + m.count++ + return s + } + if s.lo0 == lo0 && s.lo1 == lo1 && s.hi0 == hi0 && s.hi1 == hi1 { + return s + } + i = (i + 1) & mask + } } -func (r *Relation) set(key CacheHashKey, result RelationComparisonResult) { - if r.results == nil { - r.results = make(map[CacheHashKey]RelationComparisonResult) +func (m *genericRelationMap) grow() { + old := m.slots + newCap := len(old) * 2 + m.slots = make([]genericRelationSlot, newCap) + m.shift-- + mask := uint32(newCap - 1) + for _, s := range old { + if s.packed == 0 { + continue + } + lo := uint64(s.lo1)<<32 | uint64(s.lo0) + i := uint32(lo >> m.shift) + for m.slots[i].packed != 0 { + i = (i + 1) & mask + } + m.slots[i] = s } - r.results[key] = result +} + +type packedRelationCache struct { + genericResults genericRelationMap + simpleResults [4]simpleRelationMap + counts [packedRelationCount]int +} + +type relationKey struct { + value CacheHashKey + generic bool +} + +func newRelations() (subtype *Relation, strictSubtype *Relation, assignable *Relation, comparableRelation *Relation, identity *Relation) { + packedResults := &packedRelationCache{} + identity = newRelation(packedResults, 0, 0) + strictSubtype = newRelation(packedResults, 6, 2) + subtype = newRelation(packedResults, 12, 4) + assignable = newRelation(packedResults, 18, 6) + comparableRelation = newRelation(packedResults, 24, 8) + return subtype, strictSubtype, assignable, comparableRelation, identity +} + +func newRelation(packedResults *packedRelationCache, packedShift uint8, simpleShift uint8) *Relation { + return &Relation{packedResults: packedResults, packedShift: packedShift, simpleShift: simpleShift} +} + +func (r *Relation) get(key relationKey) RelationComparisonResult { + if r.packedResults == nil { + return RelationComparisonResultNone + } + if !key.generic { + s := r.packedResults.simpleResults[key.value.Hi].get(key.value.Lo) + if s == 0 { + return RelationComparisonResultNone + } + if s != simpleRelationUpgraded { + if result := RelationComparisonResult((s >> r.simpleShift) & 3); result != RelationComparisonResultNone { + return result + } + return r.deriveAndSetSimple(key, s) + } + } + s := r.packedResults.genericResults.get(key.value) + if s == 0 { + return RelationComparisonResultNone + } + result := RelationComparisonResult(s>>r.packedShift) & relationComparisonResultMask + if result != RelationComparisonResultNone { + return result + } + return r.deriveAndSetPacked(key, s) +} + +func (r *Relation) deriveAndSetSimple(key relationKey, s uint32) RelationComparisonResult { + laxerSlots := simpleRelationSlotMask &^ (uint32(1)<<(r.simpleShift+2) - 1) + if ((s >> 1) & laxerSlots) != 0 { + return r.set(key, RelationComparisonResultFailed) + } + stricterSlots := simpleRelationSlotMask & (uint32(1)<> relationComparisonResultFailedBitShift) &^ (packed >> relationComparisonResultOverflowBitShift)) & laxerSlots; failedSlots != 0 { + // The lowest set marker is the nearest laxer relation with a usable failure. + shift := bits.TrailingZeros32(failedSlots) + result := RelationComparisonResult(packed>>shift) & relationComparisonResultMask + return r.setPacked(key, packed, result) + } + // A success in a stricter relation implies success in this relation. Align + // each slot's success bit with its respective slot marker, then exclude this + // and all laxer relations. + stricterSlots := packedRelationSlotMask & (uint32(1)<> relationComparisonResultSucceededBitShift) & stricterSlots; succeededSlots != 0 { + // Prefer the strictest available success, matching the previous scan order. + shift := bits.TrailingZeros32(succeededSlots) + result := RelationComparisonResult(packed>>shift) & relationComparisonResultMask + return r.setPacked(key, packed, result) + } + return RelationComparisonResultNone +} + +func (r *Relation) set(key relationKey, result RelationComparisonResult) RelationComparisonResult { + if r.packedResults == nil { + r.packedResults = &packedRelationCache{} + } + result &= relationComparisonResultMask + if !key.generic { + slot := r.packedResults.simpleResults[key.value.Hi].slot(key.value.Lo) + s := uint32(*slot >> simpleRelationKeyBits) + if s != simpleRelationUpgraded { + if uint32(result) <= uint32(RelationComparisonResultFailed) { + if (s>>r.simpleShift)&3 == 0 { + r.packedResults.counts[r.simpleShift>>1]++ + } + shift := simpleRelationKeyBits + r.simpleShift + *slot = (*slot &^ (uint64(3) << shift)) | (uint64(result) << shift) + return result + } + gslot := r.packedResults.genericResults.slot(key.value) + for i := range uint8(packedRelationCount) { + if v := (s >> (i * 2)) & 3; v != 0 { + gslot.packed |= v << (i * packedRelationResultBits) + } + } + *slot = (*slot & simpleRelationKeyMask) | (uint64(simpleRelationUpgraded) << simpleRelationKeyBits) + } + } + shiftedResult := uint32(result) << r.packedShift + mask := uint32(relationComparisonResultMask) << r.packedShift + slot := r.packedResults.genericResults.slot(key.value) + if slot.packed&mask == 0 { + r.packedResults.counts[r.packedShift/packedRelationResultBits]++ + } + slot.packed = slot.packed&^mask | shiftedResult + return result +} + +const relationComparisonResultMask = RelationComparisonResultSucceeded | + RelationComparisonResultFailed | + RelationComparisonResultReportsUnmeasurable | + RelationComparisonResultReportsUnreliable | + RelationComparisonResultComplexityOverflow + +// Each map value contains one six-bit result slot per relation, ordered from +// strictest to laxest: identity, strict subtype, subtype, assignable, comparable. +const ( + packedRelationResultBits = 6 + packedRelationCount = 5 + // Marks the low bit of every slot, which is also its success bit. + packedRelationSlotMask = 1 | 1<<6 | 1<<12 | 1<<18 | 1<<24 +) + +func (r *Relation) setPacked(key relationKey, packed uint32, result RelationComparisonResult) RelationComparisonResult { + return r.set(key, result) } func (r *Relation) size() int { - return len(r.results) + if r.packedResults == nil { + return 0 + } + return r.packedResults.counts[r.packedShift/packedRelationResultBits] } func (c *Checker) isTypeIdenticalTo(source *Type, target *Type) bool { @@ -205,6 +531,21 @@ func (c *Checker) isTypeRelatedTo(source *Type, target *Type, relation *Relation } func (c *Checker) isSimpleTypeRelatedTo(source *Type, target *Type, relation *Relation, errorReporter ErrorReporter) bool { + const numberMember = TypeFlagsNumberLiteral | TypeFlagsEnumLiteral + const stringMember = TypeFlagsStringLiteral | TypeFlagsEnumLiteral + s := source.flags + t := target.flags + if s == t && (s == numberMember || s == stringMember) { + return source.AsLiteralType().value == target.AsLiteralType().value && c.isEnumTypeRelatedTo(source.symbol, target.symbol, errorReporter) + } + if (s|t)&^TypeFlagsStructuredOrInstantiable == 0 { + return t&TypeFlagsUnion != 0 && (relation == c.assignableRelation || relation == c.comparableRelation) && c.isUnknownLikeUnionType(target) + } + return c.isSimpleTypeRelatedToSlow(source, target, relation, errorReporter) +} + +//go:noinline +func (c *Checker) isSimpleTypeRelatedToSlow(source *Type, target *Type, relation *Relation, errorReporter ErrorReporter) bool { s := source.flags t := target.flags if t&TypeFlagsAny != 0 || s&TypeFlagsNever != 0 || source == c.wildcardType { @@ -679,23 +1020,38 @@ func (c *Checker) elaborateArrowFunction(node *ast.Node, source *Type, target *T // A type is 'weak' if it is an object type with at least one optional property // and no required properties, call/construct signatures or index signatures func (c *Checker) isWeakType(t *Type) bool { + if t.cacheFlags&typeCacheIsWeakComputed != 0 { + return t.cacheFlags&typeCacheIsWeak != 0 + } if t.flags&TypeFlagsObject != 0 { - resolved := c.resolveStructuredTypeMembers(t) - return len(resolved.getSignatures()) == 0 && len(resolved.getIndexInfos()) == 0 && len(c.getLayoutPropertiesOfType(t)) > 0 && core.Every(c.getLayoutPropertiesOfType(t), func(p *ast.Symbol) bool { + if len(c.getSignaturesOfStructuredType(t, SignatureKindCall)) != 0 || + len(c.getSignaturesOfStructuredType(t, SignatureKindConstruct)) != 0 || + len(c.getIndexInfosOfStructuredType(t)) != 0 { + t.cacheFlags |= typeCacheIsWeakComputed + return false + } + hasProperty := false + res := c.everyPropertyOfStructuredType(t, func(p *ast.Symbol) bool { + hasProperty = true return p.Flags()&ast.SymbolFlagsOptional != 0 - }) + }) && hasProperty + t.cacheFlags |= typeCacheIsWeakComputed | core.IfElse(res, typeCacheIsWeak, 0) + return res } if t.flags&TypeFlagsSubstitution != 0 { return c.isWeakType(t.AsSubstitutionType().baseType) } if t.flags&TypeFlagsIntersection != 0 { - return core.Every(t.Types(), c.isWeakType) + res := core.Every(t.Types(), c.isWeakType) + t.cacheFlags |= typeCacheIsWeakComputed | core.IfElse(res, typeCacheIsWeak, 0) + return res } return false } func (c *Checker) hasCommonProperties(source *Type, target *Type, isComparingJsxAttributes bool) bool { - for _, prop := range c.getPropertiesOfType(source) { + props, _ := c.getPropertiesOfTypeLazily(source) + for _, prop := range props { if c.isKnownProperty(target, prop.Name(), isComparingJsxAttributes) { return true } @@ -765,6 +1121,101 @@ func isExcessPropertyCheckTarget(t *Type) bool { // It is possible, though highly unlikely, for the deeply nested check to be true in a situation where a chain of // instantiations is not infinitely expanding. Effectively, we will generate a false positive when two types are // structurally equal to at least maxDepth levels, but unequal at some level beyond that. +type relaterStackEntry struct { + t *Type + target *Type + identity RecursionId + bloom uint64 +} + +func recursionIdBloomMask(id RecursionId) uint64 { + h := uint64(id) * 0x9e3779b97f4a7c15 + return (uint64(1) << (h >> 58)) | (uint64(1) << ((h >> 52) & 63)) +} + +func getIntersectionRecursionBloom(t *Type) uint64 { + var bloom uint64 + for _, elem := range t.Types() { + target := getRecursionIdentityTarget(elem) + if target.flags&TypeFlagsIntersection != 0 { + bloom |= getIntersectionRecursionBloom(target) + } else { + bloom |= recursionIdBloomMask(getRecursionIdentityFromTarget(target)) + } + } + return bloom +} + +func (c *Checker) isDeeplyNestedOnStack(t *Type, stack []relaterStackEntry, maxDepth int) bool { + if len(stack) >= maxDepth { + var target *Type + topIdx := len(stack) - 1 + if stack[topIdx].t == t { + target = stack[topIdx].target + if target == nil { + target = getRecursionIdentityTarget(t) + stack[topIdx].target = target + if target.flags&TypeFlagsIntersection != 0 { + stack[topIdx].identity = recursionIdIntersection + stack[topIdx].bloom = getIntersectionRecursionBloom(target) + } else { + stack[topIdx].identity = getRecursionIdentityFromTarget(target) + } + } + } else { + target = getRecursionIdentityTarget(t) + } + if target.flags&TypeFlagsIntersection != 0 { + for _, t := range target.Types() { + if c.isDeeplyNestedOnStack(t, stack, maxDepth) { + return true + } + } + } else { + var identity RecursionId + if stack[topIdx].t == t { + identity = stack[topIdx].identity + } else { + identity = getRecursionIdentityFromTarget(target) + } + idMask := recursionIdBloomMask(identity) + count := 0 + lastTypeId := TypeId(0) + for i := range stack { + if count+(len(stack)-i) < maxDepth { + return false + } + e := &stack[i] + eid := e.identity + if eid == 0 { + target := getRecursionIdentityTarget(e.t) + e.target = target + if target.flags&TypeFlagsIntersection != 0 { + eid = recursionIdIntersection + e.identity = eid + e.bloom = getIntersectionRecursionBloom(target) + } else { + eid = getRecursionIdentityFromTarget(target) + e.identity = eid + } + } + if eid != identity && (eid != recursionIdIntersection || e.bloom&idMask != idMask) { + continue + } + if e.t.id < lastTypeId || (eid == recursionIdIntersection && !hasMatchingRecursionIdentity(e.target, identity)) { + continue + } + count++ + if count >= maxDepth { + return true + } + lastTypeId = e.t.id + } + } + } + return false +} + func (c *Checker) isDeeplyNestedType(t *Type, stack []*Type, maxDepth int) bool { if len(stack) >= maxDepth { target := getRecursionIdentityTarget(t) @@ -778,15 +1229,19 @@ func (c *Checker) isDeeplyNestedType(t *Type, stack []*Type, maxDepth int) bool identity := getRecursionIdentityFromTarget(target) count := 0 lastTypeId := TypeId(0) - for _, t := range stack { + for i, t := range stack { + if count+(len(stack)-i) < maxDepth { + return false + } + // We only count occurrences with a higher type id than the previous occurrence, since higher + // type ids are an indicator of newer instantiations caused by recursion. + if t.id < lastTypeId { + continue + } if hasMatchingRecursionIdentity(t, identity) { - // We only count occurrences with a higher type id than the previous occurrence, since higher - // type ids are an indicator of newer instantiations caused by recursion. - if t.id >= lastTypeId { - count++ - if count >= maxDepth { - return true - } + count++ + if count >= maxDepth { + return true } lastTypeId = t.id } @@ -799,14 +1254,23 @@ func (c *Checker) isDeeplyNestedType(t *Type, stack []*Type, maxDepth int) bool func hasMatchingRecursionIdentity(t *Type, identity RecursionId) bool { target := getRecursionIdentityTarget(t) if target.flags&TypeFlagsIntersection != 0 { - for _, t := range target.Types() { - if hasMatchingRecursionIdentity(t, identity) { + return hasMatchingRecursionIdentitySlow(target, identity) + } + return getRecursionIdentityFromTarget(target) == identity +} + +func hasMatchingRecursionIdentitySlow(target *Type, identity RecursionId) bool { + for _, t := range target.Types() { + elem := getRecursionIdentityTarget(t) + if elem.flags&TypeFlagsIntersection != 0 { + if hasMatchingRecursionIdentitySlow(elem, identity) { return true } + } else if getRecursionIdentityFromTarget(elem) == identity { + return true } - return false } - return getRecursionIdentityFromTarget(target) == identity + return false } func getRecursionIdentity(t *Type) RecursionId { @@ -820,17 +1284,28 @@ func getRecursionIdentity(t *Type) RecursionId { // 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 { - if t.flags&TypeFlagsIndexedAccess != 0 { - return getRecursionIdentityTarget(t.AsIndexedAccessType().objectType) + if t.flags&TypeFlagsIndexedAccess == 0 && t.objectFlags&ObjectFlagsInstantiatedMapped != ObjectFlagsInstantiatedMapped { + return t } - if t.objectFlags&ObjectFlagsInstantiatedMapped == ObjectFlagsInstantiatedMapped { - target := t.checker.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 getRecursionIdentityTargetSlow(t) +} + +func getRecursionIdentityTargetSlow(t *Type) *Type { + for { + if t.flags&TypeFlagsIndexedAccess != 0 { + t = t.AsIndexedAccessType().objectType + continue + } + if t.objectFlags&ObjectFlagsInstantiatedMapped == ObjectFlagsInstantiatedMapped { + target := t.checker.getModifiersTypeFromMappedType(t) + if target != nil && (target.symbol != nil || target.flags&TypeFlagsIntersection != 0 && + core.Some(target.Types(), func(t *Type) bool { return t.symbol != nil })) { + t = target + continue + } } + return t } - return t } // The recursion identity of a type is an object identity that is shared among multiple instantiations of the type. @@ -977,24 +1452,57 @@ func (c *Checker) getUnmatchedProperties(source *Type, target *Type, requireOpti } func (c *Checker) getUnmatchedPropertiesWorker(source *Type, target *Type, requireOptionalProperties bool, matchDiscriminantProperties bool, propsOut *[]*ast.Symbol) *ast.Symbol { - for _, targetProp := range c.getLayoutPropertiesOfType(target) { + properties, lazyType := c.getPropertiesOfTypeLazily(target) + resolvedSource := c.isResolvedPlainObjectType(source) + for _, targetProp := range properties { // TODO: remove this when we support static private identifier fields and find other solutions to get privateNamesAndStaticFields test to pass - if isStaticPrivateIdentifierProperty(targetProp) { - continue - } - if requireOptionalProperties || targetProp.Flags()&ast.SymbolFlagsOptional == 0 && targetProp.CheckFlags()&ast.CheckFlagsPartial == 0 { - sourceProp := c.getPropertyOfType(source, targetProp.Name()) - if sourceProp == nil || matchDiscriminantProperties { - targetProp = c.getPropertyOfType(target, targetProp.Name()) + if (requireOptionalProperties || targetProp.Flags()&ast.SymbolFlagsOptional == 0 && targetProp.CheckFlags()&ast.CheckFlagsPartial == 0) && !isStaticPrivateIdentifierProperty(targetProp) { + if !matchDiscriminantProperties { + var hasProp bool + if resolvedSource { + hasProp = c.getResolvedPropertyOfPlainObjectType(source, targetProp.Name()) != nil + } else { + hasProp = c.hasPropertyOfType(source, targetProp.Name()) + } + if !hasProp { + if lazyType != nil { + targetProp = c.getPropertyOfType(lazyType, targetProp.Name()) + } + if propsOut == nil { + return targetProp + } + *propsOut = append(*propsOut, targetProp) + } + continue + } + var sourceProp *ast.Symbol + if resolvedSource { + sourceProp = c.getResolvedPropertyOfPlainObjectType(source, targetProp.Name()) + } else { + sourceProp = c.getPropertyOfTypeWorker(source, targetProp.Name(), false /*skipObjectFunctionPropertyAugment*/, false /*includeTypeOnlyMembers*/, false /*instantiate*/) } if sourceProp == nil { + if lazyType != nil { + targetProp = c.getPropertyOfType(lazyType, targetProp.Name()) + } if propsOut == nil { return targetProp } *propsOut = append(*propsOut, targetProp) - } else if matchDiscriminantProperties { + } else if targetProp.Flags()&ast.SymbolFlagsMethod == 0 || targetProp.Flags()&(ast.SymbolFlagsProperty|ast.SymbolFlagsVariable|ast.SymbolFlagsGetAccessor|ast.SymbolFlagsEnumMember) != 0 { + if lazyType != nil { + if lazyTarget := lazyType; lazyTarget.objectFlags&ObjectFlagsMapped == 0 { + if links := c.valueSymbolLinks.TryGet(targetProp); links != nil && links.resolvedType != nil && links.resolvedType.flags&TypeFlagsUnit == 0 && !c.couldContainTypeVariables(links.resolvedType) { + continue + } + } + targetProp = c.getPropertyOfType(lazyType, targetProp.Name()) + } targetType := c.getTypeOfSymbol(targetProp) if targetType.flags&TypeFlagsUnit != 0 { + if !resolvedSource { + sourceProp = c.getPropertyOfType(source, targetProp.Name()) + } sourceType := c.getTypeOfSymbol(sourceProp) if !(sourceType.flags&TypeFlagsAny != 0 || c.getRegularTypeOfLiteralType(sourceType) == c.getRegularTypeOfLiteralType(targetType)) { if propsOut == nil { @@ -1097,11 +1605,6 @@ func (c *Checker) isDiscriminantProperty(t *Type, name string) bool { return false } -type unionDiscriminant struct { - keyPropertyName string - constituentMap map[*Type]*Type -} - func (c *Checker) getMatchingUnionConstituentForType(unionType *Type, t *Type) *Type { keyPropertyName := c.getKeyPropertyName(unionType) if keyPropertyName == "" { @@ -1114,13 +1617,22 @@ func (c *Checker) getMatchingUnionConstituentForType(unionType *Type, t *Type) * return c.getConstituentTypeForKeyType(unionType, propType) } +type unionDiscriminant struct { + keyPropertyName string + constituentMap map[*Type]*Type +} + // Return the name of a discriminant property for which it was possible and feasible to construct a map of // constituent types keyed by the literal types of the property by that name in each constituent type. Return // an empty string if no such discriminant property exists. func (c *Checker) getKeyPropertyName(t *Type) string { + types := t.Types() + if len(types) < 10 || t.objectFlags&ObjectFlagsPrimitiveUnion != 0 { + return "" + } u := t.AsUnionType() - disc := c.unionDiscriminants[u] - if disc.keyPropertyName == "" { + disc, ok := c.unionDiscriminants[u] + if !ok { disc.keyPropertyName, disc.constituentMap = c.computeKeyPropertyNameAndMap(t) if c.unionDiscriminants == nil { c.unionDiscriminants = make(map[*UnionType]unionDiscriminant) @@ -1499,10 +2011,17 @@ func (c *Checker) hasCovariantVoidArgument(typeArguments []*Type, variances []Va } func (c *Checker) isSignatureAssignableTo(source *Signature, target *Signature, ignoreReturnTypes bool) bool { - return c.compareSignaturesRelated(source, target, core.IfElse(ignoreReturnTypes, SignatureCheckModeIgnoreReturnTypes, SignatureCheckModeNone), false /*reportErrors*/, nil /*errorReporter*/, c.compareTypesAssignable, nil /*reportUnreliableMarkers*/) != TernaryFalse + return c.compareSignaturesRelated(source, target, core.IfElse(ignoreReturnTypes, SignatureCheckModeIgnoreReturnTypes, SignatureCheckModeNone), false /*reportErrors*/, nil /*r*/, IntersectionStateNone) != TernaryFalse +} + +func (c *Checker) compareTypesInSignature(r *Relater, intersectionState IntersectionState, source *Type, target *Type, reportErrors bool) Ternary { + if r != nil { + return r.isRelatedToEx(source, target, RecursionFlagsBoth, reportErrors, nil /*headMessage*/, intersectionState) + } + return c.compareTypesAssignableWorker(source, target, reportErrors) } -func (c *Checker) compareSignaturesRelated(source *Signature, target *Signature, checkMode SignatureCheckMode, reportErrors bool, errorReporter ErrorReporter, compareTypes TypeComparer, reportUnreliableMarkers *TypeMapper) Ternary { +func (c *Checker) compareSignaturesRelated(source *Signature, target *Signature, checkMode SignatureCheckMode, reportErrors bool, r *Relater, intersectionState IntersectionState) Ternary { if source == target { return TernaryTrue } @@ -1525,18 +2044,32 @@ func (c *Checker) compareSignaturesRelated(source *Signature, target *Signature, if reportErrors && (checkMode&SignatureCheckModeStrictArity == 0) { // the second condition should be redundant, because there is no error reporting when comparing signatures by strict arity // since it is only done for subtype reduction - errorReporter(diagnostics.Target_signature_provides_too_few_arguments_Expected_0_or_more_but_got_1, c.getMinArgumentCount(source), targetCount) + r.reportError(diagnostics.Target_signature_provides_too_few_arguments_Expected_0_or_more_but_got_1, c.getMinArgumentCount(source), targetCount) } return TernaryFalse } if len(source.TypeParameters()) != 0 && !core.Same(source.TypeParameters(), target.TypeParameters()) { target = c.getCanonicalSignature(target) - source = c.instantiateSignatureInContextOf(source, target /*inferenceContext*/, nil, compareTypes) + var compareTypes TypeComparer + if r == nil { + compareTypes = c.compareTypesAssignable + } else if intersectionState == IntersectionStateNone { + compareTypes = r.compareTypes + } else { + compareTypes = func(source *Type, target *Type, reportErrors bool) Ternary { + return r.isRelatedToEx(source, target, RecursionFlagsBoth, reportErrors, nil /*headMessage*/, intersectionState) + } + } + source = c.instantiateSignatureInContextOf(source, target, nil /*inferenceContext*/, compareTypes) } sourceCount := c.getParameterCount(source) sourceRestType := c.getNonArrayRestType(source) targetRestType := c.getNonArrayRestType(target) if sourceRestType != nil || targetRestType != nil { + var reportUnreliableMarkers *TypeMapper + if r != nil { + reportUnreliableMarkers = r.c.reportUnreliableMapper + } c.instantiateType(core.IfElse(sourceRestType != nil, sourceRestType, targetRestType), reportUnreliableMarkers) } kind := ast.KindUnknown @@ -1549,17 +2082,17 @@ func (c *Checker) compareSignaturesRelated(source *Signature, target *Signature, if sourceThisType != nil && sourceThisType != c.voidType { targetThisType := c.getThisTypeOfSignature(target) if targetThisType != nil { - // void sources are assignable to anything. + // Void sources are assignable to anything. var related Ternary if !strictVariance { - related = compareTypes(sourceThisType, targetThisType, false /*reportErrors*/) + related = c.compareTypesInSignature(r, intersectionState, sourceThisType, targetThisType, false /*reportErrors*/) } if related == TernaryFalse { - related = compareTypes(targetThisType, sourceThisType, reportErrors) + related = c.compareTypesInSignature(r, intersectionState, targetThisType, sourceThisType, reportErrors) } if related == TernaryFalse { if reportErrors { - errorReporter(diagnostics.The_this_types_of_each_signature_are_incompatible) + r.reportError(diagnostics.The_this_types_of_each_signature_are_incompatible) } return TernaryFalse } @@ -1597,37 +2130,42 @@ func (c *Checker) compareSignaturesRelated(source *Signature, target *Signature, // they naturally relate only contra-variantly). However, if the source and target parameters both have // function types with a single call signature, we know we are relating two callback parameters. In // that case it is sufficient to only relate the parameters of the signatures co-variantly because, - // similar to return values, callback parameters are output positions. This means that a Promise, + // similar to return values, callback parameters are output positions. This means that a Promise // where T is used only in callback parameter positions, will be co-variant (as opposed to bi-variant) // with respect to T. var sourceSig *Signature var targetSig *Signature - if checkMode&SignatureCheckModeCallback == 0 && !c.isInstantiatedGenericParameter(source, i) { + if checkMode&SignatureCheckModeCallback == 0 { sourceSig = c.getSingleCallSignature(c.GetNonNullableType(sourceType)) - } - if checkMode&SignatureCheckModeCallback == 0 && !c.isInstantiatedGenericParameter(target, i) { - targetSig = c.getSingleCallSignature(c.GetNonNullableType(targetType)) + if sourceSig != nil && !c.isInstantiatedGenericParameter(source, i) { + targetSig = c.getSingleCallSignature(c.GetNonNullableType(targetType)) + if targetSig != nil && c.isInstantiatedGenericParameter(target, i) { + targetSig = nil + } + } else { + sourceSig = nil + } } callbacks := sourceSig != nil && targetSig != nil && c.getTypePredicateOfSignature(sourceSig) == nil && c.getTypePredicateOfSignature(targetSig) == nil && c.getTypeFacts(sourceType, TypeFactsIsUndefinedOrNull) == c.getTypeFacts(targetType, TypeFactsIsUndefinedOrNull) var related Ternary if callbacks { - related = c.compareSignaturesRelated(targetSig, sourceSig, checkMode&SignatureCheckModeStrictArity|core.IfElse(strictVariance, SignatureCheckModeStrictCallback, SignatureCheckModeBivariantCallback), reportErrors, errorReporter, compareTypes, reportUnreliableMarkers) + related = c.compareSignaturesRelated(targetSig, sourceSig, checkMode&SignatureCheckModeStrictArity|core.IfElse(strictVariance, SignatureCheckModeStrictCallback, SignatureCheckModeBivariantCallback), reportErrors, r, intersectionState) } else { if checkMode&SignatureCheckModeCallback == 0 && !strictVariance { - related = compareTypes(sourceType, targetType, false /*reportErrors*/) + related = c.compareTypesInSignature(r, intersectionState, sourceType, targetType, false /*reportErrors*/) } if related == TernaryFalse { - related = compareTypes(targetType, sourceType, reportErrors) + related = c.compareTypesInSignature(r, intersectionState, targetType, sourceType, reportErrors) } } // With strict arity, (x: number | undefined) => void is a subtype of (x?: number | undefined) => void - if related != TernaryFalse && checkMode&SignatureCheckModeStrictArity != 0 && i >= c.getMinArgumentCount(source) && i < c.getMinArgumentCount(target) && compareTypes(sourceType, targetType, false /*reportErrors*/) != TernaryFalse { + if related != TernaryFalse && checkMode&SignatureCheckModeStrictArity != 0 && i >= c.getMinArgumentCount(source) && i < c.getMinArgumentCount(target) && c.compareTypesInSignature(r, intersectionState, sourceType, targetType, false /*reportErrors*/) != TernaryFalse { related = TernaryFalse } if related == TernaryFalse { if reportErrors { - errorReporter(diagnostics.Types_of_parameters_0_and_1_are_incompatible, c.getParameterNameAtPosition(source, i), c.getParameterNameAtPosition(target, i)) + r.reportError(diagnostics.Types_of_parameters_0_and_1_are_incompatible, c.getParameterNameAtPosition(source, i), c.getParameterNameAtPosition(target, i)) } return TernaryFalse } @@ -1647,58 +2185,57 @@ func (c *Checker) compareSignaturesRelated(source *Signature, target *Signature, if targetTypePredicate != nil { sourceTypePredicate := c.getTypePredicateOfSignature(source) if sourceTypePredicate != nil { - result &= c.compareTypePredicateRelatedTo(sourceTypePredicate, targetTypePredicate, reportErrors, errorReporter, compareTypes) + result &= c.compareTypePredicateRelatedTo(sourceTypePredicate, targetTypePredicate, reportErrors, r, intersectionState) } else if targetTypePredicate.kind == TypePredicateKindIdentifier || targetTypePredicate.kind == TypePredicateKindThis { if reportErrors { - errorReporter(diagnostics.Signature_0_must_be_a_type_predicate, c.signatureToString(source)) + r.reportError(diagnostics.Signature_0_must_be_a_type_predicate, c.signatureToString(source)) } return TernaryFalse } - } else { - // When relating callback signatures, we still need to relate return types bi-variantly as otherwise - // the containing type wouldn't be co-variant. For example, interface Foo { add(cb: () => T): void } - // wouldn't be co-variant for T without this rule. - var related Ternary - if checkMode&SignatureCheckModeBivariantCallback != 0 { - related = compareTypes(targetReturnType, sourceReturnType, false /*reportErrors*/) - } - if related == TernaryFalse { - related = compareTypes(sourceReturnType, targetReturnType, reportErrors) - } - result &= related - if result == TernaryFalse && reportErrors { - // The errors reported here serve as markers that trigger error chain reduction in the (*Relater).reportError - // method. The markers are elided in the final diagnostic chain and never actually reported. - var message *diagnostics.Message - if len(source.parameters) == 0 && len(target.parameters) == 0 { - message = core.IfElse(source.flags&SignatureFlagsConstruct != 0, - diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1, - diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1) - } else { - message = core.IfElse(source.flags&SignatureFlagsConstruct != 0, - diagnostics.Construct_signature_return_types_0_and_1_are_incompatible, - diagnostics.Call_signature_return_types_0_and_1_are_incompatible) - } - errorReporter(message, c.TypeToString(sourceReturnType), c.TypeToString(targetReturnType)) + } + // When relating callback signatures, we still need to relate return types bi-variantly as otherwise + // the containing type wouldn't be co-variant. For example, interface Foo { add(cb: () => T): void } + // wouldn't be co-variant for T without this rule. + var related Ternary + if checkMode&SignatureCheckModeBivariantCallback != 0 { + related = c.compareTypesInSignature(r, intersectionState, targetReturnType, sourceReturnType, false /*reportErrors*/) + } + if related == TernaryFalse { + related = c.compareTypesInSignature(r, intersectionState, sourceReturnType, targetReturnType, reportErrors) + } + result &= related + if result == TernaryFalse && reportErrors { + // The errors reported here serve as markers that trigger error chain reduction in the (*Relater).reportError + // method. The markers are elided in the final diagnostic chain and never actually reported. + var message *diagnostics.Message + if len(source.parameters) == 0 && len(target.parameters) == 0 { + message = core.IfElse(source.flags&SignatureFlagsConstruct != 0, + diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1, + diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1) + } else { + message = core.IfElse(source.flags&SignatureFlagsConstruct != 0, + diagnostics.Construct_signature_return_types_0_and_1_are_incompatible, + diagnostics.Call_signature_return_types_0_and_1_are_incompatible) } + r.reportError(message, c.TypeToString(sourceReturnType), c.TypeToString(targetReturnType)) } } return result } -func (c *Checker) compareTypePredicateRelatedTo(source *TypePredicate, target *TypePredicate, reportErrors bool, errorReporter ErrorReporter, compareTypes TypeComparer) Ternary { +func (c *Checker) compareTypePredicateRelatedTo(source *TypePredicate, target *TypePredicate, reportErrors bool, r *Relater, intersectionState IntersectionState) Ternary { if source.kind != target.kind { if reportErrors { - errorReporter(diagnostics.A_this_based_type_guard_is_not_compatible_with_a_parameter_based_type_guard) - errorReporter(diagnostics.Type_predicate_0_is_not_assignable_to_1, c.typePredicateToString(source), c.typePredicateToString(target)) + r.reportError(diagnostics.A_this_based_type_guard_is_not_compatible_with_a_parameter_based_type_guard) + r.reportError(diagnostics.Type_predicate_0_is_not_assignable_to_1, c.typePredicateToString(source), c.typePredicateToString(target)) } return TernaryFalse } if source.kind == TypePredicateKindIdentifier || source.kind == TypePredicateKindAssertsIdentifier { if source.parameterIndex != target.parameterIndex { if reportErrors { - errorReporter(diagnostics.Parameter_0_is_not_in_the_same_position_as_parameter_1, source.parameterName, target.parameterName) - errorReporter(diagnostics.Type_predicate_0_is_not_assignable_to_1, c.typePredicateToString(source), c.typePredicateToString(target)) + r.reportError(diagnostics.Parameter_0_is_not_in_the_same_position_as_parameter_1, source.parameterName, target.parameterName) + r.reportError(diagnostics.Type_predicate_0_is_not_assignable_to_1, c.typePredicateToString(source), c.typePredicateToString(target)) } return TernaryFalse } @@ -1708,20 +2245,20 @@ func (c *Checker) compareTypePredicateRelatedTo(source *TypePredicate, target *T case source.t == target.t: related = TernaryTrue case source.t != nil && target.t != nil: - related = compareTypes(source.t, target.t, reportErrors) + related = c.compareTypesInSignature(r, intersectionState, source.t, target.t, reportErrors) default: related = TernaryFalse } if related == TernaryFalse && reportErrors { - errorReporter(diagnostics.Type_predicate_0_is_not_assignable_to_1, c.typePredicateToString(source), c.typePredicateToString(target)) + r.reportError(diagnostics.Type_predicate_0_is_not_assignable_to_1, c.typePredicateToString(source), c.typePredicateToString(target)) } return related } // Returns true if `s` is `(...args: A) => R` where `A` is `any`, `any[]`, `never`, or `never[]`, and `R` is `any` or `unknown`. func (c *Checker) isTopSignature(s *Signature) bool { - if len(s.TypeParameters()) == 0 && (s.ThisParameter() == nil || IsTypeAny(c.getTypeOfParameter(s.ThisParameter()))) && len(s.parameters) == 1 && signatureHasRestParameter(s) { - paramType := c.getTypeOfParameter(s.parameters[0]) + if len(s.TypeParameters()) == 0 && (s.ThisParameter() == nil || IsTypeAny(c.getTypeOfParameter(c.getSignatureThisParameter(s)))) && len(s.parameters) == 1 && signatureHasRestParameter(s) { + paramType := c.getTypeOfSignatureParameter(s, 0) var restType *Type if c.isArrayType(paramType) { restType = c.getTypeArguments(paramType)[0] @@ -1740,7 +2277,7 @@ func (c *Checker) isTopSignature(s *Signature) bool { func (c *Checker) getParameterCount(signature *Signature) int { length := len(signature.parameters) if signatureHasRestParameter(signature) { - restType := c.getTypeOfSymbol(signature.parameters[length-1]) + restType := c.getTypeOfSignatureParameterSymbol(signature, length-1) if isTupleType(restType) { return length + restType.TargetTupleType().fixedLength - core.IfElse(restType.TargetTupleType().combinedFlags&ElementFlagsVariable != 0, 0, 1) } @@ -1758,7 +2295,7 @@ func (c *Checker) getMinArgumentCountEx(signature *Signature, flags MinArgumentC if voidIsNonOptional != 0 || signature.resolvedMinArgumentCount == -1 { minArgumentCount := -1 if signatureHasRestParameter(signature) { - restType := c.getTypeOfSymbol(signature.parameters[len(signature.parameters)-1]) + restType := c.getTypeOfSignatureParameterSymbol(signature, len(signature.parameters)-1) if isTupleType(restType) { firstOptionalIndex := core.FindIndex(restType.TargetTupleType().elementInfos, func(info TupleElementInfo) bool { return info.flags&ElementFlagsRequired == 0 @@ -1795,7 +2332,7 @@ func (c *Checker) getMinArgumentCountEx(signature *Signature, flags MinArgumentC func (c *Checker) hasEffectiveRestParameter(signature *Signature) bool { if signatureHasRestParameter(signature) { - restType := c.getTypeOfSymbol(signature.parameters[len(signature.parameters)-1]) + restType := c.getTypeOfSignatureParameterSymbol(signature, len(signature.parameters)-1) return !isTupleType(restType) || restType.TargetTupleType().combinedFlags&ElementFlagsVariable != 0 } return false @@ -1812,13 +2349,13 @@ func (c *Checker) getTypeAtPosition(signature *Signature, pos int) *Type { func (c *Checker) tryGetTypeAtPosition(signature *Signature, pos int) *Type { paramCount := len(signature.parameters) - core.IfElse(signatureHasRestParameter(signature), 1, 0) if pos < paramCount { - return c.getTypeOfParameter(signature.parameters[pos]) + return c.getTypeOfSignatureParameter(signature, pos) } if signatureHasRestParameter(signature) { // We want to return the value undefined for an out of bounds parameter position, // so we need to check bounds here before calling getIndexedAccessType (which // otherwise would return the type 'undefined'). - restType := c.getTypeOfSymbol(signature.parameters[paramCount]) + restType := c.getTypeOfSignatureParameterSymbol(signature, paramCount) index := pos - paramCount if !isTupleType(restType) || restType.TargetTupleType().combinedFlags&ElementFlagsVariable != 0 || index < restType.TargetTupleType().fixedLength { return c.getIndexedAccessType(restType, c.getNumberLiteralType(jsnum.Number(index))) @@ -1882,7 +2419,7 @@ func (c *Checker) getNameableDeclarationAtPosition(signature *Signature, pos int } if signatureHasRestParameter(signature) { restParameter := signature.parameters[paramCount] - restType := c.getTypeOfSymbol(restParameter) + restType := c.getTypeOfSignatureParameterSymbol(signature, paramCount) if isTupleType(restType) { elementInfos := restType.TargetTupleType().elementInfos index := pos - paramCount @@ -1912,7 +2449,7 @@ func (c *Checker) getNonArrayRestType(signature *Signature) *Type { func (c *Checker) getEffectiveRestType(signature *Signature) *Type { if signatureHasRestParameter(signature) { - restType := c.getTypeOfSymbol(signature.parameters[len(signature.parameters)-1]) + restType := c.getTypeOfSignatureParameterSymbol(signature, len(signature.parameters)-1) if !isTupleType(restType) { if IsTypeAny(restType) { return c.anyArrayType @@ -1960,17 +2497,36 @@ func (c *Checker) getRestArrayTypeOfTupleType(t *Type) *Type { func (c *Checker) getThisTypeOfSignature(signature *Signature) *Type { if signature.ThisParameter() != nil { + if signature.flags&signatureFlagsUninstantiatedThisParameter != 0 { + target := signature.target + mapper := signature.mapper + for target.flags&signatureFlagsUninstantiatedThisParameter != 0 { + mapper = c.combineTypeMappers(target.mapper, mapper) + target = target.target + } + thisParam := target.ThisParameter() + if thisParam.CheckFlags()&ast.CheckFlagsInstantiated != 0 { + links := c.valueSymbolLinks.Get(thisParam) + thisParam = links.target + mapper = c.combineTypeMappers(links.mapper, mapper) + } + t := c.getTypeOfSymbol(thisParam) + if c.couldContainTypeVariables(t) || thisParam.Flags()&ast.SymbolFlagsSetAccessor != 0 { + t = c.instantiateType(t, mapper) + } + return t + } return c.getTypeOfSymbol(signature.ThisParameter()) } return nil } func (c *Checker) isInstantiatedGenericParameter(signature *Signature, pos int) bool { - if signature.target == nil { - return false + if signature.target != nil { + t := c.tryGetTypeAtPosition(signature.target, pos) + return t != nil && c.isGenericType(t) } - t := c.tryGetTypeAtPosition(signature.target, pos) - return t != nil && c.isGenericType(t) + return false } func (c *Checker) getParameterNameAtPosition(signature *Signature, pos int) string { @@ -1979,7 +2535,7 @@ func (c *Checker) getParameterNameAtPosition(signature *Signature, pos int) stri return signature.parameters[pos].Name() } restParameter := signature.parameters[paramCount] - restType := c.getTypeOfSymbol(restParameter) + restType := c.getTypeOfSignatureParameterSymbol(signature, paramCount) if isTupleType(restType) { index := pos - paramCount return c.getTupleElementLabel(restType.TargetTupleType().elementInfos[index], restParameter, index) @@ -2328,6 +2884,7 @@ func (c *Checker) compareTypePredicatesIdentical(source *TypePredicate, target * } func (c *Checker) getEffectiveConstraintOfIntersection(types []*Type, targetIsUnion bool) *Type { + var constraintsBuf [8]*Type var constraints []*Type hasDisjointDomainType := false for _, t := range types { @@ -2339,6 +2896,9 @@ func (c *Checker) getEffectiveConstraintOfIntersection(types []*Type, targetIsUn constraint = c.getConstraintOfType(constraint) } if constraint != nil { + if constraints == nil { + constraints = constraintsBuf[:0] + } constraints = append(constraints, constraint) if targetIsUnion { constraints = append(constraints, t) @@ -2380,6 +2940,7 @@ func (c *Checker) templateLiteralTypesDefinitelyUnrelated(source *TemplateLitera func (c *Checker) isTypeMatchedByTemplateLiteralType(source *Type, target *TemplateLiteralType, compareTypes TypeComparer) bool { inferences := c.inferTypesFromTemplateLiteralType(source, target, compareTypes) if inferences != nil { + defer c.releaseTemporaryTypeArguments(inferences) for i, inference := range inferences { if !c.isValidTypeForTemplateLiteralPlaceholder(inference, target.types[i], compareTypes) { return false @@ -2393,15 +2954,21 @@ func (c *Checker) isTypeMatchedByTemplateLiteralType(source *Type, target *Templ func (c *Checker) inferTypesFromTemplateLiteralType(source *Type, target *TemplateLiteralType, compareTypes TypeComparer) []*Type { switch { case source.flags&TypeFlagsStringLiteral != 0: - return c.inferFromLiteralPartsToTemplateLiteral([]string{getStringLiteralValue(source)}, nil, target) + var sourceTexts [1]string + sourceTexts[0] = getStringLiteralValue(source) + return c.inferFromLiteralPartsToTemplateLiteral(sourceTexts[:], nil, target) case source.flags&TypeFlagsTemplateLiteral != 0: if slices.Equal(source.AsTemplateLiteralType().texts, target.texts) { - return core.MapIndex(source.AsTemplateLiteralType().types, func(s *Type, i int) *Type { + sourceTypes := source.AsTemplateLiteralType().types + matches := c.getTemporaryTypeArguments(len(sourceTypes)) + for i, s := range sourceTypes { if compareTypes(c.getBaseConstraintOrType(s), c.getBaseConstraintOrType(target.types[i]), false /*partialMatch*/) != TernaryFalse { - return s + matches[i] = s + } else { + matches[i] = c.getStringLikeTypeForType(s) } - return c.getStringLikeTypeForType(s) - }) + } + return matches } return c.inferFromLiteralPartsToTemplateLiteral(source.AsTemplateLiteralType().texts, source.AsTemplateLiteralType().types, target) default: @@ -2444,7 +3011,7 @@ func (c *Checker) inferFromLiteralPartsToTemplateLiteral(sourceTexts []string, s remainingEndText := sourceEndText[:len(sourceEndText)-len(targetEndText)] seg := 0 pos := len(targetStartText) - var matches []*Type + matches := c.getTemporaryTypeArguments(lastTargetIndex)[:0] getSourceText := func(index int) string { if index < lastSourceIndex { return sourceTexts[index] @@ -2456,7 +3023,13 @@ func (c *Checker) inferFromLiteralPartsToTemplateLiteral(sourceTexts []string, s if s == seg { matchType = c.getStringLiteralType(stringutil.CombineSurrogatePairs(getSourceText(s)[pos:p])) } else { - matchTexts := make([]string, s-seg+1) + var matchTextsBuf [8]string + var matchTexts []string + if s-seg+1 <= len(matchTextsBuf) { + matchTexts = matchTextsBuf[:s-seg+1] + } else { + matchTexts = make([]string, s-seg+1) + } matchTexts[0] = sourceTexts[seg][pos:] copy(matchTexts[1:], sourceTexts[seg+1:s]) matchTexts[s-seg] = getSourceText(s)[:p] @@ -2479,6 +3052,7 @@ func (c *Checker) inferFromLiteralPartsToTemplateLiteral(sourceTexts []string, s } s++ if s == len(sourceTexts) { + c.releaseTemporaryTypeArguments(matches) return nil } p = 0 @@ -2507,6 +3081,7 @@ func (c *Checker) inferFromLiteralPartsToTemplateLiteral(sourceTexts []string, s } else if seg < lastSourceIndex { addMatch(seg+1, 0) } else { + c.releaseTemporaryTypeArguments(matches) return nil } } @@ -2598,16 +3173,17 @@ type Relater struct { errorNode *ast.Node errorChain *ErrorChain relatedInfo []*ast.Diagnostic - maybeKeys []CacheHashKey - maybeKeysSet collections.Set[CacheHashKey] - sourceStack []*Type - targetStack []*Type + maybeKeys []relationKey + maybeKeysSet collections.Set[relationKey] + sourceStack []relaterStackEntry + targetStack []relaterStackEntry maybeCount int sourceDepth int targetDepth int expandingFlags ExpandingFlags overflow bool relationCount int + compareTypes TypeComparer next *Relater } @@ -2615,19 +3191,44 @@ func (c *Checker) getRelater() *Relater { r := c.freeRelater if r == nil { r = &Relater{c: c} + r.compareTypes = r.isRelatedToWorker } c.freeRelater = r.next return r } +const maybeKeysSetThreshold = 32 + +func (r *Relater) hasMaybeKey(key relationKey) bool { + if len(r.maybeKeys) > maybeKeysSetThreshold { + return r.maybeKeysSet.Has(key) + } + return slices.Contains(r.maybeKeys, key) +} + +func (r *Relater) pushMaybeKey(key relationKey) { + r.maybeKeys = append(r.maybeKeys, key) + if len(r.maybeKeys) > maybeKeysSetThreshold { + if len(r.maybeKeys) == maybeKeysSetThreshold+1 { + for _, k := range r.maybeKeys[:maybeKeysSetThreshold] { + r.maybeKeysSet.Add(k) + } + } + r.maybeKeysSet.Add(key) + } +} + func (c *Checker) putRelater(r *Relater) { - r.maybeKeysSet.Clear() + if len(r.maybeKeys) > maybeKeysSetThreshold { + r.maybeKeysSet.Clear() + } *r = Relater{ c: c, maybeKeys: r.maybeKeys[:0], maybeKeysSet: r.maybeKeysSet, sourceStack: r.sourceStack[:0], targetStack: r.targetStack[:0], + compareTypes: r.compareTypes, next: c.freeRelater, } c.freeRelater = r @@ -2881,29 +3482,41 @@ func (c *Checker) isTypeSubsetOf(source *Type, target *Type) bool { func (c *Checker) isTypeSubsetOfUnion(source *Type, target *Type) bool { if source.flags&TypeFlagsUnion != 0 { - sourceTypes, targetTypes := source.Types(), target.Types() + sourceTypes := source.Types() + targetTypes := target.Types() if len(sourceTypes) > len(targetTypes) { return false } - // Scan by identity unless a few source types are looked for in a large target. A CompareTypes call costs - // roughly 8 pointer comparisons, so there a binary search is cheaper than walking the target. - if len(sourceTypes)*bits.Len(uint(len(targetTypes)))*8 >= len(targetTypes) { - for _, t := range sourceTypes { - i := slices.Index(targetTypes, t) - if i < 0 { + if len(c.files) > 0 { + if len(sourceTypes)*bits.Len(uint(len(targetTypes)))*8 >= len(targetTypes) { + for i, t := range sourceTypes { + if len(targetTypes) < len(sourceTypes)-i { + return false + } + k := slices.Index(targetTypes, t) + if k < 0 { + return false + } + targetTypes = targetTypes[k+1:] + } + return true + } + for i, t := range sourceTypes { + if len(targetTypes) < len(sourceTypes)-i { + return false + } + k, ok := slices.BinarySearchFunc(targetTypes, t, CompareTypes) + if !ok { return false } - // Union types are sorted by CompareTypes, so the next source type can only appear after this one. - targetTypes = targetTypes[i+1:] + targetTypes = targetTypes[k+1:] } return true } for _, t := range sourceTypes { - i, found := slices.BinarySearchFunc(targetTypes, t, CompareTypes) - if !found { + if !containsType(targetTypes, t) { return false } - targetTypes = targetTypes[i+1:] } return true } @@ -3152,7 +3765,7 @@ func (r *Relater) recursiveTypeRelatedTo(source *Type, target *Type, reportError return TernaryFalse } // If source and target are already being compared, consider them related with assumptions - if r.maybeKeysSet.Has(id) { + if r.hasMaybeKey(id) { return TernaryMaybe } // A constrained key indicates that we have type references that reference constrained @@ -3160,7 +3773,7 @@ func (r *Relater) recursiveTypeRelatedTo(source *Type, target *Type, reportError // were unconstrained. if constrained { broadestEquivalentId, _ := getRelationKey(source, target, intersectionState, r.relation == r.c.identityRelation, true /*ignoreConstraints*/) - if r.maybeKeysSet.Has(broadestEquivalentId) { + if r.hasMaybeKey(broadestEquivalentId) { return TernaryMaybe } } @@ -3171,18 +3784,17 @@ func (r *Relater) recursiveTypeRelatedTo(source *Type, target *Type, reportError return TernaryMaybe } maybeStart := len(r.maybeKeys) - r.maybeKeys = append(r.maybeKeys, id) - r.maybeKeysSet.Add(id) + r.pushMaybeKey(id) saveExpandingFlags := r.expandingFlags if recursionFlags&RecursionFlagsSource != 0 { - r.sourceStack = append(r.sourceStack, source) - if r.expandingFlags&ExpandingFlagsSource == 0 && r.c.isDeeplyNestedType(source, r.sourceStack, 3) { + r.sourceStack = append(r.sourceStack, relaterStackEntry{t: source}) + if r.expandingFlags&ExpandingFlagsSource == 0 && r.c.isDeeplyNestedOnStack(source, r.sourceStack, 3) { r.expandingFlags |= ExpandingFlagsSource } } if recursionFlags&RecursionFlagsTarget != 0 { - r.targetStack = append(r.targetStack, target) - if r.expandingFlags&ExpandingFlagsTarget == 0 && r.c.isDeeplyNestedType(target, r.targetStack, 3) { + r.targetStack = append(r.targetStack, relaterStackEntry{t: target}) + if r.expandingFlags&ExpandingFlagsTarget == 0 && r.c.isDeeplyNestedOnStack(target, r.targetStack, 3) { r.expandingFlags |= ExpandingFlagsTarget } } @@ -3233,9 +3845,17 @@ func (r *Relater) recursiveTypeRelatedTo(source *Type, target *Type, reportError } func (r *Relater) resetMaybeStack(maybeStart int, propagatingVarianceFlags RelationComparisonResult, markAllAsSucceeded bool) { - for i := maybeStart; i < len(r.maybeKeys); i++ { - r.maybeKeysSet.Delete(r.maybeKeys[i]) - if markAllAsSucceeded { + if len(r.maybeKeys) > maybeKeysSetThreshold { + if maybeStart <= maybeKeysSetThreshold { + r.maybeKeysSet.Clear() + } else { + for i := maybeStart; i < len(r.maybeKeys); i++ { + r.maybeKeysSet.Delete(r.maybeKeys[i]) + } + } + } + if markAllAsSucceeded { + for i := maybeStart; i < len(r.maybeKeys); i++ { r.relation.set(r.maybeKeys[i], RelationComparisonResultSucceeded|propagatingVarianceFlags) r.relationCount-- } @@ -3280,7 +3900,7 @@ func (r *Relater) structuredTypeRelatedTo(source *Type, target *Type, reportErro sourceTypes = []*Type{source} } constraint := r.c.getEffectiveConstraintOfIntersection(sourceTypes, target.flags&TypeFlagsUnion != 0) - if constraint != nil && everyType(constraint, func(c *Type) bool { return c != source }) { + if constraint != nil && everyTypeNotEqual(constraint, source) { // TODO: Stack errors so we get a pyramid for the "normal" comparison above, _and_ a second for this result = r.isRelatedToEx(constraint, target, RecursionFlagsSource, false /*reportErrors*/, nil /*headMessage*/, intersectionState) } @@ -3317,60 +3937,64 @@ func (r *Relater) structuredTypeRelatedTo(source *Type, target *Type, reportErro return result } +func everyTypeNotEqual(t *Type, other *Type) bool { + if t.flags&TypeFlagsUnion != 0 { + return !slices.Contains(t.Types(), other) + } + return t != other +} + func (r *Relater) isSourceIntersectionNeedingExtraCheck(source *Type, target *Type) bool { - return source.flags&TypeFlagsIntersection != 0 && r.c.getApparentType(source).flags&TypeFlagsStructuredType != 0 && - !core.Some(source.Types(), func(t *Type) bool { - return t == target || t.objectFlags&ObjectFlagsNonInferrableType != 0 - }) + if source.flags&TypeFlagsIntersection == 0 || r.c.getApparentType(source).flags&TypeFlagsStructuredType == 0 { + return false + } + for _, t := range source.Types() { + if t == target || t.objectFlags&ObjectFlagsNonInferrableType != 0 { + return false + } + } + return true } -func (r *Relater) structuredTypeRelatedToWorker(source *Type, target *Type, reportErrors bool, intersectionState IntersectionState) Ternary { - var result Ternary - var varianceCheckFailed bool - var originalErrorChain *ErrorChain - saveErrorState := r.getErrorState() - relateVariances := func(sourceTypeArguments []*Type, targetTypeArguments []*Type, variances []VarianceFlags, intersectionState IntersectionState) (Ternary, bool) { - if result = r.typeArgumentsRelatedTo(sourceTypeArguments, targetTypeArguments, variances, reportErrors, intersectionState); result != TernaryFalse { - return result, 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 - // We elide the variance-based error elaborations, since those might not be too helpful, since we'll potentially - // be assuming identity of the type parameter. - originalErrorChain = nil +func (r *Relater) relateVariances(sourceTypeArguments []*Type, targetTypeArguments []*Type, variances []VarianceFlags, reportErrors bool, intersectionState IntersectionState, saveErrorState errorState, varianceCheckFailed *bool, originalErrorChain **ErrorChain) (Ternary, bool) { + if result := r.typeArgumentsRelatedTo(sourceTypeArguments, targetTypeArguments, variances, reportErrors, intersectionState); result != TernaryFalse { + return result, true + } + for _, v := range variances { + if v&VarianceFlagsAllowsStructuralFallback != 0 { + *originalErrorChain = nil r.restoreErrorState(saveErrorState) return TernaryFalse, false } - allowStructuralFallback := r.c.hasCovariantVoidArgument(targetTypeArguments, variances) - varianceCheckFailed = !allowStructuralFallback - // The type arguments did not relate appropriately, but it may be because we have no variance - // information (in which case typeArgumentsRelatedTo defaulted to covariance for all type - // arguments). It might also be the case that the target type has a 'void' type argument for - // a covariant type parameter that is only used in return positions within the generic type - // (in which case any type argument is permitted on the source side). In those cases we proceed - // with a structural comparison. Otherwise, we know for certain the instantiations aren't - // related and we can return here. - if len(variances) != 0 && !allowStructuralFallback { - // In some cases generic types that are covariant in regular type checking mode become - // invariant in --strictFunctionTypes mode because one or more type parameters are used in - // both co- and contravariant positions. In order to make it easier to diagnose *why* such - // types are invariant, if any of the type parameters are invariant we reset the reported - // errors and instead force a structural comparison (which will include elaborations that - // reveal the reason). - // We can switch on `reportErrors` here, since varianceCheckFailed guarantees we return `False`, - // we can return `False` early here to skip calculating the structural error message we don't need. - if varianceCheckFailed && !(reportErrors && core.Some(variances, func(v VarianceFlags) bool { return (v & VarianceFlagsVarianceMask) == VarianceFlagsInvariant })) { + } + allowStructuralFallback := r.c.hasCovariantVoidArgument(targetTypeArguments, variances) + *varianceCheckFailed = !allowStructuralFallback + if len(variances) != 0 && !allowStructuralFallback { + if *varianceCheckFailed { + hasInvariant := false + if reportErrors { + for _, v := range variances { + if (v & VarianceFlagsVarianceMask) == VarianceFlagsInvariant { + hasInvariant = true + break + } + } + } + if !hasInvariant { return TernaryFalse, true } - // We remember the original error information so we can restore it in case the structural - // comparison unexpectedly succeeds. This can happen when the structural comparison result - // is a Ternary.Maybe for example caused by the recursion depth limiter. - originalErrorChain = r.errorChain - r.restoreErrorState(saveErrorState) } - return TernaryFalse, false + *originalErrorChain = r.errorChain + r.restoreErrorState(saveErrorState) } + return TernaryFalse, false +} + +func (r *Relater) structuredTypeRelatedToWorker(source *Type, target *Type, reportErrors bool, intersectionState IntersectionState) Ternary { + var result Ternary + var varianceCheckFailed bool + var originalErrorChain *ErrorChain + saveErrorState := r.getErrorState() switch { case r.relation == r.c.identityRelation: // We've already checked that source.flags and target.flags are identical @@ -3466,7 +4090,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 := r.relateVariances(sourceTypes, targetTypes, variances, reportErrors, intersectionState, saveErrorState, &varianceCheckFailed, &originalErrorChain) if ok { return varianceResult } @@ -3606,7 +4230,7 @@ func (r *Relater) structuredTypeRelatedToWorker(source *Type, target *Type, repo case target.flags&TypeFlagsConditional != 0: // If we reach 10 levels of nesting for the same conditional type, assume it is an infinitely expanding recursive // conditional type and bail out with a Ternary.Maybe result. - if r.c.isDeeplyNestedType(target, r.targetStack, 10) { + if r.c.isDeeplyNestedOnStack(target, r.targetStack, 10) { return TernaryMaybe } c := target.AsConditionalType() @@ -3787,7 +4411,7 @@ func (r *Relater) structuredTypeRelatedToWorker(source *Type, target *Type, repo case source.flags&TypeFlagsConditional != 0: // If we reach 10 levels of nesting for the same conditional type, assume it is an infinitely expanding recursive // conditional type and bail out with a Ternary.Maybe result. - if r.c.isDeeplyNestedType(source, r.sourceStack, 10) { + if r.c.isDeeplyNestedOnStack(source, r.sourceStack, 10) { return TernaryMaybe } if target.flags&TypeFlagsConditional != 0 { @@ -3797,9 +4421,10 @@ func (r *Relater) structuredTypeRelatedToWorker(source *Type, target *Type, repo sourceParams := source.AsConditionalType().root.inferTypeParameters sourceExtends := source.AsConditionalType().extendsType var mapper *TypeMapper + var ctx *InferenceContext if len(sourceParams) != 0 { // If the source has infer type parameters, we instantiate them in the context of the target - ctx := r.c.newInferenceContext(sourceParams, nil /*signature*/, InferenceFlagsNone, r.isRelatedToWorker) + ctx = r.c.newInferenceContext(sourceParams, nil /*signature*/, InferenceFlagsNone, r.isRelatedToWorker) r.c.inferTypes(ctx.inferences, target.AsConditionalType().extendsType, sourceExtends, InferencePriorityNoConstraints|InferencePriorityAlwaysStrict, false) sourceExtends = r.c.instantiateType(sourceExtends, ctx.mapper) mapper = ctx.mapper @@ -3810,9 +4435,15 @@ func (r *Relater) structuredTypeRelatedToWorker(source *Type, target *Type, repo result &= r.isRelatedTo(r.c.getFalseTypeFromConditionalType(source), r.c.getFalseTypeFromConditionalType(target), RecursionFlagsBoth, reportErrors) } if result != TernaryFalse { + if ctx != nil && !ctx.escaped { + r.c.releaseConditionalInferenceContext(ctx, nil, nil) + } return result } } + if ctx != nil && !ctx.escaped { + r.c.releaseConditionalInferenceContext(ctx, nil, nil) + } } // conditionals can be related to one another via normal constraint, as, eg, `A extends B ? O : never` should be assignable to `O` // when `O` is a conditional (`never` is trivially assignable to `O`, as is `O`!). @@ -3900,7 +4531,7 @@ func (r *Relater) structuredTypeRelatedToWorker(source *Type, target *Type, repo if len(variances) == 0 { return TernaryUnknown } - varianceResult, ok := relateVariances(r.c.getTypeArguments(source), r.c.getTypeArguments(target), variances, intersectionState) + varianceResult, ok := r.relateVariances(r.c.getTypeArguments(source), r.c.getTypeArguments(target), variances, reportErrors, intersectionState, saveErrorState, &varianceCheckFailed, &originalErrorChain) if ok { return varianceResult } @@ -4315,27 +4946,45 @@ func (r *Relater) propertiesRelatedTo(source *Type, target *Type, reportErrors b } // We only call this for union target types when we're attempting to do excess property checking - in those cases, we want to get _all possible props_ // from the target union, across all members - properties := r.c.getPropertiesOfType(target) + properties, lazyTarget := r.c.getPropertiesOfTypeLazily(target) numericNamesOnly := isTupleType(source) && isTupleType(target) + resolvedSource := r.c.isResolvedPlainObjectType(source) for _, targetProp := range excludeProperties(properties, excludedProperties) { name := targetProp.Name() if targetProp.Flags()&ast.SymbolFlagsPrototype == 0 && (!numericNamesOnly || isNumericLiteralName(name) || name == "length") && (!optionalsOnly || targetProp.Flags()&ast.SymbolFlagsOptional != 0) { - sourceProp := r.c.getPropertyOfType(source, name) - if sourceProp != nil && sourceProp != targetProp { - related := r.propertyRelatedTo(source, target, sourceProp, targetProp, r.c.getNonMissingTypeOfSymbol, reportErrors, intersectionState, r.relation == r.c.comparableRelation) - if related == TernaryFalse { - return TernaryFalse + var sourceProp *ast.Symbol + if resolvedSource { + sourceProp = r.c.getResolvedPropertyOfPlainObjectType(source, name) + } else { + sourceProp = r.c.getPropertyOfType(source, name) + } + if sourceProp != nil { + if lazyTarget != nil { + targetProp = r.c.getPropertyOfType(lazyTarget, name) + } + if sourceProp != targetProp { + related := r.propertyRelatedTo(source, target, sourceProp, targetProp, nil, reportErrors, intersectionState, r.relation == r.c.comparableRelation) + if related == TernaryFalse { + return TernaryFalse + } + result &= related } - result &= related } } } return result } +func getPropertyAccessibilityFlags(s *ast.Symbol) ast.ModifierFlags { + if s.CheckFlags()&ast.CheckFlagsSynthetic == 0 && (s.Parent() == nil || s.Parent().Flags()&ast.SymbolFlagsClass == 0) { + return ast.ModifierFlagsNone + } + return getDeclarationModifierFlagsFromSymbol(s) +} + func (r *Relater) propertyRelatedTo(source *Type, target *Type, sourceProp *ast.Symbol, targetProp *ast.Symbol, getTypeOfSourceProperty func(sym *ast.Symbol) *Type, reportErrors bool, intersectionState IntersectionState, skipOptional bool) Ternary { - sourcePropFlags := getDeclarationModifierFlagsFromSymbol(sourceProp) - targetPropFlags := getDeclarationModifierFlagsFromSymbol(targetProp) + sourcePropFlags := getPropertyAccessibilityFlags(sourceProp) + targetPropFlags := getPropertyAccessibilityFlags(targetProp) switch { case sourcePropFlags&ast.ModifierFlagsPrivate != 0 || targetPropFlags&ast.ModifierFlagsPrivate != 0: if sourceProp.ValueDeclaration() != targetProp.ValueDeclaration() { @@ -4404,7 +5053,12 @@ func (r *Relater) isPropertySymbolTypeRelated(sourceProp *ast.Symbol, targetProp if effectiveTarget.flags&core.IfElse(r.relation == r.c.strictSubtypeRelation, TypeFlagsAny, TypeFlagsAnyOrUnknown) != 0 { return TernaryTrue } - effectiveSource := getTypeOfSourceProperty(sourceProp) + var effectiveSource *Type + if getTypeOfSourceProperty != nil { + effectiveSource = getTypeOfSourceProperty(sourceProp) + } else { + effectiveSource = r.c.getNonMissingTypeOfSymbol(sourceProp) + } return r.isRelatedToEx(effectiveSource, effectiveTarget, RecursionFlagsBoth, reportErrors, nil /*headMessage*/, intersectionState) } @@ -4618,10 +5272,7 @@ func (r *Relater) signatureRelatedTo(source *Signature, target *Signature, erase source = r.c.getErasedSignature(source) target = r.c.getErasedSignature(target) } - isRelatedToWorker := func(source *Type, target *Type, reportErrors bool) Ternary { - return r.isRelatedToEx(source, target, RecursionFlagsBoth, reportErrors, nil /*headMessage*/, intersectionState) - } - return r.c.compareSignaturesRelated(source, target, checkMode, reportErrors, r.reportError, isRelatedToWorker, r.c.reportUnreliableMapper) + return r.c.compareSignaturesRelated(source, target, checkMode, reportErrors, r, intersectionState) } func (r *Relater) signaturesIdenticalTo(source *Type, target *Type, kind SignatureKind) Ternary { diff --git a/tsc/internal/checker/tracer.go b/tsc/internal/checker/tracer.go index 14290d807fdc6..bb9a138784fb5 100644 --- a/tsc/internal/checker/tracer.go +++ b/tsc/internal/checker/tracer.go @@ -329,7 +329,10 @@ func (a *tracedTypeAdapter) Pattern() *ast.Node { } func (a *tracedTypeAdapter) RecursionIdentity() any { - return getRecursionIdentity(a.t).value + if id := getRecursionIdentity(a.t); id != 0 { + return id + } + return nil } func (a *tracedTypeAdapter) Display() string { diff --git a/tsc/internal/checker/types_test.go b/tsc/internal/checker/types_test.go index c937d17fbb0aa..2a517dc62407c 100644 --- a/tsc/internal/checker/types_test.go +++ b/tsc/internal/checker/types_test.go @@ -153,6 +153,79 @@ func TestKeyBuilder(t *testing.T) { } } +func TestRelationKeys(t *testing.T) { + t.Parallel() + source := &Type{id: 1} + target := &Type{id: ^TypeId(0)} + var relation Relation + assert.Equal(t, relation.size(), 0) + for state := IntersectionStateNone; state <= IntersectionStateSource|IntersectionStateTarget; state++ { + key, constrained := getRelationKey(source, target, state, false, false) + assert.Assert(t, !constrained) + assert.Equal(t, relation.get(key), RelationComparisonResultNone) + relation.set(key, RelationComparisonResultSucceeded) + reverse, _ := getRelationKey(target, source, state, false, false) + assert.Assert(t, key != reverse) + assert.Equal(t, relation.get(reverse), RelationComparisonResultNone) + identity, _ := getRelationKey(target, source, state, true, false) + assert.Equal(t, key, identity) + relation.set(key, RelationComparisonResultFailed|RelationComparisonResultReportsUnreliable) + assert.Equal(t, relation.get(key), RelationComparisonResultFailed|RelationComparisonResultReportsUnreliable) + } + assert.Equal(t, relation.size(), 4) + for state := IntersectionStateNone; state <= IntersectionStateSource|IntersectionStateTarget; state++ { + key, _ := getRelationKey(source, target, state, false, false) + assert.Equal(t, relation.get(key), RelationComparisonResultFailed|RelationComparisonResultReportsUnreliable) + } +} + +func TestRelationKeyNamespaces(t *testing.T) { + t.Parallel() + simple, _ := getRelationKey(&Type{id: 1}, &Type{id: 2}, IntersectionStateNone, false, false) + generic := relationKey{value: simple.value, generic: true} + var relation Relation + relation.set(simple, RelationComparisonResultSucceeded) + assert.Equal(t, relation.get(generic), RelationComparisonResultNone) + relation.set(generic, RelationComparisonResultFailed) + assert.Equal(t, relation.get(simple), RelationComparisonResultSucceeded) + assert.Equal(t, relation.get(generic), RelationComparisonResultFailed) + assert.Equal(t, relation.size(), 2) +} + +func TestGenericRelationKeys(t *testing.T) { + t.Parallel() + c, types := newCompositeTestChecker() + c.noConstraintType = c.newIntrinsicType(TypeFlagsNever, "no constraint") + root := types[0] + newParameter := func(constraint *Type) *Type { + parameter := &TypeParameter{constraint: constraint} + return c.newType(TypeFlagsTypeParameter, ObjectFlagsNone, parameter.AsType(), typeKindTypeParameter) + } + newReference := func(argument *Type) *Type { + reference := &TypeReference{resolvedTypeArguments: []*Type{argument}} + reference.target = root + return c.newType(TypeFlagsObject, ObjectFlagsReference, reference.AsType(), typeKindTypeReference) + } + left := newReference(newParameter(c.noConstraintType)) + right := newReference(newParameter(c.noConstraintType)) + equivalentLeft := newReference(newParameter(c.noConstraintType)) + equivalentRight := newReference(newParameter(c.noConstraintType)) + key, constrained := getRelationKey(left, right, IntersectionStateNone, false, false) + assert.Assert(t, !constrained) + equivalent, _ := getRelationKey(equivalentLeft, equivalentRight, IntersectionStateNone, false, false) + assert.Equal(t, key, equivalent) + var relation Relation + relation.set(key, RelationComparisonResultSucceeded) + assert.Equal(t, relation.get(equivalent), RelationComparisonResultSucceeded) + constrainedLeft := newReference(newParameter(types[1])) + constrainedKey, constrained := getRelationKey(constrainedLeft, right, IntersectionStateNone, false, false) + assert.Assert(t, constrained) + assert.Assert(t, constrainedKey != key) + broadest, constrained := getRelationKey(constrainedLeft, right, IntersectionStateNone, false, true) + assert.Assert(t, !constrained) + assert.Equal(t, broadest, key) +} + func TestTypeListKeyAllocations(t *testing.T) { //nolint:paralleltest if race.Enabled { t.Skip("race instrumentation changes hashing allocations") From fbebac5e385800a377a973b659fc389b39cef6c9 Mon Sep 17 00:00:00 2001 From: Steven Chong <25894545+teamchong@users.noreply.github.com> Date: Mon, 26 Oct 2026 14:46:05 -0400 Subject: [PATCH 07/10] perf(compiler,wasm): add checker recycling pool and TinyGo wasm32-wasip1 target - Recycle checker instances across files when heap usage exceeds --maxMemory and deduplicate parseTask fileIncludeReasons across root and sub-tasks. - Enable lazy .d.ts member reparsing and one-shot CLI GC suspension in performCompilation and performIncrementalCompilation. - Add build-wasm-tinygo.mjs, tinygo-overlay runtime/poll/hashmap/slice patches, and single-threaded wasm orchestrator/syncmap paths. --- tools/scripts/build-wasm-tinygo.mjs | 147 +++ .../src/internal/poll/fd_wasip1.go | 575 +++++++++++ .../src/runtime/arch_tinygowasm.go | 107 +++ .../tinygo-overlay/src/runtime/gc_leaking.go | 199 ++++ .../tinygo-overlay/src/runtime/hashmap.go | 892 ++++++++++++++++++ .../tinygo-overlay/src/runtime/slice.go | 53 ++ tsc/cmd/tsc/api.go | 2 + tsc/cmd/tsc/lsp.go | 2 + tsc/cmd/tsc/main.go | 9 + tsc/cmd/tsc/platform_wasip1.go | 2 +- tsc/cmd/tsc/sys.go | 2 + tsc/cmd/tsc/wasm_wasip1.go | 351 +++++++ tsc/cmd/tsc/wasmapi_wasip1.go | 2 +- tsc/internal/bundled/bundled.go | 4 - tsc/internal/collections/syncmap.go | 2 + tsc/internal/collections/syncmap_wasm.go | 87 ++ tsc/internal/compiler/checkerpool.go | 227 ++++- tsc/internal/compiler/checkerpool_test.go | 46 + tsc/internal/compiler/fileInclude.go | 4 +- tsc/internal/compiler/fileloader.go | 58 +- tsc/internal/compiler/filesparser.go | 193 ++-- tsc/internal/compiler/program.go | 20 + tsc/internal/contentmapper/hostimpl.go | 66 +- .../contentmapper/hostimpl_notwasm.go | 82 ++ tsc/internal/contentmapper/hostimpl_wasm.go | 35 + tsc/internal/execute/build/orchestrator.go | 30 +- tsc/internal/execute/tsc.go | 10 + tsc/internal/execute/tsc/emit.go | 4 - tsc/internal/execute/tsc/statistics.go | 23 +- tsc/internal/fswatch/debounce.go | 5 +- tsc/internal/packagejson/exportsorimports.go | 2 +- tsc/internal/packagejson/jsonvalue.go | 4 +- 32 files changed, 3020 insertions(+), 225 deletions(-) create mode 100755 tools/scripts/build-wasm-tinygo.mjs create mode 100644 tools/scripts/testdata/tinygo-overlay/src/internal/poll/fd_wasip1.go create mode 100644 tools/scripts/testdata/tinygo-overlay/src/runtime/arch_tinygowasm.go create mode 100644 tools/scripts/testdata/tinygo-overlay/src/runtime/gc_leaking.go create mode 100644 tools/scripts/testdata/tinygo-overlay/src/runtime/hashmap.go create mode 100644 tools/scripts/testdata/tinygo-overlay/src/runtime/slice.go create mode 100644 tsc/cmd/tsc/wasm_wasip1.go create mode 100644 tsc/internal/collections/syncmap_wasm.go create mode 100644 tsc/internal/contentmapper/hostimpl_notwasm.go create mode 100644 tsc/internal/contentmapper/hostimpl_wasm.go diff --git a/tools/scripts/build-wasm-tinygo.mjs b/tools/scripts/build-wasm-tinygo.mjs new file mode 100755 index 0000000000000..d2ed0f1156ff8 --- /dev/null +++ b/tools/scripts/build-wasm-tinygo.mjs @@ -0,0 +1,147 @@ +#!/usr/bin/env node +// Builds tsc into a standalone Wasm module using TinyGo 0.43.0 with the +// repository's runtime/target overlay applied in an isolated TINYGOROOT. +// If TinyGo 0.43.0 is not installed locally, it is automatically downloaded +// from the official GitHub release into /tmp/tinygo-0.43.0-base. + +import { + execFileSync, + spawnSync, +} from "node:child_process"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const TINYGO_VERSION = "0.43.0"; +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const repoRoot = path.resolve(__dirname, "../.."); +const tscDir = path.join(repoRoot, "tsc"); +const overlayDir = path.join(__dirname, "testdata", "tinygo-overlay"); + +let outPath = "/tmp/tsgo-tinygo-fast.wasm"; +for (let i = 2; i < process.argv.length; i++) { + const arg = process.argv[i]; + if ((arg === "-o" || arg === "--out") && process.argv[i + 1]) { + outPath = path.resolve(process.argv[++i]); + } + else if (arg.startsWith("--out=")) { + outPath = path.resolve(arg.slice("--out=".length)); + } +} + +function checkTinyGoRoot(candidateRoot) { + if (!candidateRoot) return null; + const bin = path.join(candidateRoot, "bin", "tinygo"); + if (!fs.existsSync(bin)) return null; + const res = spawnSync(bin, ["version"], { encoding: "utf8" }); + if (res.status === 0 && res.stdout.includes(`tinygo version ${TINYGO_VERSION}`)) { + return candidateRoot; + } + return null; +} + +function findOrDownloadBaseTinyGo() { + // 1. Check explicit env override + const fromEnv = checkTinyGoRoot(process.env.TINYGOROOT_BASE); + if (fromEnv) return fromEnv; + + // 2. Check common mise install location + const miseCandidate = path.join( + os.homedir(), + `.local/share/mise/installs/tinygo/${TINYGO_VERSION}/tinygo`, + ); + if (checkTinyGoRoot(miseCandidate)) return miseCandidate; + + // 3. Check tinygo on PATH + const whichRes = spawnSync("tinygo", ["env", "TINYGOROOT"], { encoding: "utf8" }); + if (whichRes.status === 0) { + const pathRoot = checkTinyGoRoot(whichRes.stdout.trim()); + if (pathRoot) return pathRoot; + } + + // 4. Check cached download in /tmp + const cacheDir = path.join(os.tmpdir(), `tinygo-${TINYGO_VERSION}-base`); + const cachedRoot = path.join(cacheDir, "tinygo"); + if (checkTinyGoRoot(cachedRoot)) return cachedRoot; + + // 5. Download official TinyGo 0.43.0 release tarball for current OS/arch + const platformMap = { darwin: "darwin", linux: "linux" }; + const archMap = { arm64: "arm64", x64: "amd64" }; + const plat = platformMap[os.platform()]; + const arch = archMap[os.arch()]; + if (!plat || !arch) { + throw new Error(`Unsupported platform/arch for TinyGo auto-download: ${os.platform()}/${os.arch()}`); + } + const archiveName = `tinygo${TINYGO_VERSION}.${plat}-${arch}.tar.gz`; + const url = `https://github.com/tinygo-org/tinygo/releases/download/v${TINYGO_VERSION}/${archiveName}`; + fs.mkdirSync(cacheDir, { recursive: true }); + const tarPath = path.join(cacheDir, archiveName); + console.log(`Downloading TinyGo ${TINYGO_VERSION} from ${url}...`); + execFileSync("curl", ["-fSL", "--retry", "3", "-o", tarPath, url], { stdio: "inherit" }); + execFileSync("tar", ["-xzf", tarPath, "-C", cacheDir], { stdio: "inherit" }); + if (!checkTinyGoRoot(cachedRoot)) { + throw new Error(`Failed to verify TinyGo ${TINYGO_VERSION} in ${cachedRoot}`); + } + return cachedRoot; +} + +function buildOverlayTinyGoRoot(baseRoot) { + const workRoot = path.join(os.tmpdir(), `tinygo-${TINYGO_VERSION}-tsgo-overlay`); + fs.rmSync(workRoot, { recursive: true, force: true }); + fs.mkdirSync(workRoot, { recursive: true }); + + // Symlink top-level immutable directories (bin, lib, pkg, targets) + for (const entry of fs.readdirSync(baseRoot)) { + if (entry === "src") continue; + fs.symlinkSync(path.join(baseRoot, entry), path.join(workRoot, entry)); + } + + // Copy src/ (except the 900MB src/device directory, which is symlinked), + // then copy the overlay files on top. + fs.mkdirSync(path.join(workRoot, "src"), { recursive: true }); + for (const entry of fs.readdirSync(path.join(baseRoot, "src"))) { + if (entry === "device") { + fs.symlinkSync(path.join(baseRoot, "src", entry), path.join(workRoot, "src", entry)); + } + else { + fs.cpSync(path.join(baseRoot, "src", entry), path.join(workRoot, "src", entry), { recursive: true }); + } + } + fs.cpSync(overlayDir, workRoot, { recursive: true, force: true }); + + return workRoot; +} + +const baseRoot = findOrDownloadBaseTinyGo(); +const overlayRoot = buildOverlayTinyGoRoot(baseRoot); +const tinygoBin = path.join(overlayRoot, "bin", "tinygo"); + +console.log(`Using TinyGo ${TINYGO_VERSION} (base: ${baseRoot}, overlay: ${overlayRoot})`); +console.log(`Building ${outPath}...`); + +const t0 = Date.now(); +execFileSync( + tinygoBin, + [ + "build", + "-target=wasip1", + "-buildmode=c-shared", + "-opt=2", + "-scheduler=none", + "-gc=leaking", + "-no-debug", + "-ldflags=-extldflags '-z stack-size=16777216'", + "-o", + outPath, + "./cmd/tsc", + ], + { + cwd: tscDir, + env: { ...process.env, TINYGOROOT: overlayRoot }, + stdio: "inherit", + }, +); + +const stat = fs.statSync(outPath); +console.log(`Built ${outPath} (${(stat.size / (1024 * 1024)).toFixed(2)} MiB) in ${((Date.now() - t0) / 1000).toFixed(1)}s`); diff --git a/tools/scripts/testdata/tinygo-overlay/src/internal/poll/fd_wasip1.go b/tools/scripts/testdata/tinygo-overlay/src/internal/poll/fd_wasip1.go new file mode 100644 index 0000000000000..8ccf4020f9e1d --- /dev/null +++ b/tools/scripts/testdata/tinygo-overlay/src/internal/poll/fd_wasip1.go @@ -0,0 +1,575 @@ +//go:build wasip1 + +// Package poll is a minimal subset of upstream Go's internal/poll, scoped +// to what is needed to back a wasip1 net implementation on top of +// TinyGo's cooperative-scheduler netpoll integration. +// +// On wasip1 the cooperative scheduler integrates poll_oneoff with FD +// waiters (see runtime/netpoll_wasip1.go and syscall/syscall_libc_wasip1.go). +// This package wraps the syscall layer to: +// +// - own the O_NONBLOCK policy decision (set on Init for pollable FDs), +// unblocking the EAGAIN→park retry loop that syscall.Read already has; +// - provide a Go-shaped FD type that net.* can use without reaching +// into syscall directly; +// - thread per-FD read/write deadlines through a runtime helper that +// lets a time.AfterFunc callback wake the parked goroutine; +// - dispatch socket FDs through wasi sock_recv / sock_send / sock_accept +// / sock_shutdown so net.Conn/net.Listener (via upstream Go's +// net/file_wasip1.go) work end-to-end. +package poll + +import ( + "errors" + "internal/task" + "syscall" + "time" + "unsafe" +) + +// ErrFileClosing is returned when a Read or Write is started on a closed FD. +var ErrFileClosing = errors.New("use of closed file") + +// ErrNetClosing is returned for network operations on a closed FD. +var ErrNetClosing = errors.New("use of closed network connection") + +// ErrDeadlineExceeded is returned by Read/Write when a deadline expired. +// Matches the error returned by os.IsTimeout-style helpers. +var ErrDeadlineExceeded = errors.New("i/o timeout") + +// ErrNoDeadline is returned if SetDeadline is called on an FD whose +// underlying type does not support deadlines. +var ErrNoDeadline = errors.New("file type does not support deadline") + +// pollMode constants must mirror runtime/netpoll_wasip1.go's pollRead/ +// pollWrite values. +const ( + pollModeRead uint8 = 1 + pollModeWrite uint8 = 2 +) + +//go:linkname runtime_netpoll_addwait runtime.runtime_netpoll_addwait +func runtime_netpoll_addwait(fd uint32, mode uint8) uintptr + +//go:linkname runtime_netpoll_done runtime.runtime_netpoll_done +func runtime_netpoll_done(pd uintptr) + +//go:linkname runtime_netpoll_pdfired runtime.runtime_netpoll_pdfired +func runtime_netpoll_pdfired(pd uintptr) bool + +//go:linkname runtime_netpoll_wake runtime.runtime_netpoll_wake +func runtime_netpoll_wake(pd uintptr) + +//go:linkname fd_fdstat_get_type syscall.fd_fdstat_get_type +func fd_fdstat_get_type(fd int) (syscall.Filetype, error) + +// wasiIovec / wasiCiovec mirror wasi-snapshot-preview1's iovec / ciovec +// records: a buffer pointer plus a 32-bit length. Marshalled inline with +// each fd_read / fd_write / sock_recv / sock_send call. +type wasiIovec struct { + buf *byte + bufLen uint32 +} + +// fd_read / fd_write are bound directly here rather than going through +// wasi-libc so the deadline-aware Read/Write loop can observe EAGAIN +// cleanly. Outside package runtime, TinyGo's wasmimport binding +// restricts us to unsafe.Pointer for struct args and uint32 for the +// errno result; the wasi spec uses *iovec and uint16 respectively but +// the wire layout is identical. +// +//go:wasmimport wasi_snapshot_preview1 fd_read +func wasi_fd_read(fd int32, iovs unsafe.Pointer, iovsLen uint32, nread unsafe.Pointer) uint32 + +//go:wasmimport wasi_snapshot_preview1 fd_write +func wasi_fd_write(fd int32, iovs unsafe.Pointer, iovsLen uint32, nwritten unsafe.Pointer) uint32 + +// sock_recv / sock_send are the socket-data wasi syscalls used when the +// FD's Filetype indicates a socket. We bind them here for the same +// reason as fd_read / fd_write: the deadline-aware loop wants direct +// access to the EAGAIN signal without libc's translation layer. +// +//go:wasmimport wasi_snapshot_preview1 sock_recv +func wasi_sock_recv(fd int32, riData unsafe.Pointer, riDataLen uint32, riFlags uint32, roDatalen unsafe.Pointer, roFlags unsafe.Pointer) uint32 + +//go:wasmimport wasi_snapshot_preview1 sock_send +func wasi_sock_send(fd int32, siData unsafe.Pointer, siDataLen uint32, siFlags uint32, soDatalen unsafe.Pointer) uint32 + +// SysFile carries per-FD bookkeeping that upstream Go's poll.FD uses to +// share an underlying syscall FD between an os.File and a net.Conn (see +// Copy below). RefCountPtr / RefCount handle the shared-ownership case; +// Filetype caches the wasi filetype so socket-vs-file dispatch in Read / +// Write is a single integer compare on the hot path. +// +// wasip1 is single-threaded, so the refcount is a plain int — no atomics +// needed. Match upstream's field naming for source-level compatibility +// with code that constructs FDs via struct literal (e.g. upstream's +// net/fd_fake.go). +type SysFile struct { + RefCountPtr *int32 + RefCount int32 + Filetype uint32 +} + +// init lazily allocates the refcount the first time it's needed (i.e. +// the first Init or Copy on this FD). A zero SysFile starts at refcount +// 1 — the FD's sole owner is the caller. +func (s *SysFile) init() { + if s.RefCountPtr == nil { + s.RefCount = 1 + s.RefCountPtr = &s.RefCount + } +} + +// ref increments the shared refcount and returns a SysFile that points +// at the same counter. Used by FD.Copy. +func (s *SysFile) ref() SysFile { + s.init() + *s.RefCountPtr++ + return SysFile{RefCountPtr: s.RefCountPtr, Filetype: s.Filetype} +} + +// destroy decrements the refcount and reports whether the underlying +// syscall FD should now be closed (i.e. this was the last owner). +func (s *SysFile) destroy() bool { + if s.RefCountPtr == nil { + return true + } + *s.RefCountPtr-- + return *s.RefCountPtr <= 0 +} + +// FD is the wasip1 file/socket descriptor wrapped with the bookkeeping +// that net and os rely on. It owns the lifecycle of the underlying +// syscall FD (modulo the Copy / refcount handoff between os.File and +// net.Conn). +// +// The struct mirrors upstream Go's internal/poll.FD field naming +// (Sysfd, IsStream, ZeroReadIsEOF, SysFile) so that upstream's +// net/file_wasip1.go and net/fd_fake.go can construct one via struct +// literal without modification. +type FD struct { + Sysfd int + SysFile SysFile + IsStream bool + ZeroReadIsEOF bool + closed bool + + // Per-FD deadlines, zero means "no deadline". Subsequent Read/Write + // calls observe whatever the current value is at call time; an + // in-flight call uses the deadline it captured at its start. + rDeadline time.Time + wDeadline time.Time +} + +// Init readies the FD for use. When pollable is true (i.e. the FD might +// block — sockets, pipes, FIFOs), Init sets O_NONBLOCK so that +// Read/Write enter the EAGAIN→park retry loop instead of blocking the +// entire wasm module. +// +// Init also caches the wasi filetype so the Read/Write hot path can +// dispatch socket vs file with a single integer compare. +// +// The net argument is currently ignored but kept for parity with +// upstream Go. +func (fd *FD) Init(net string, pollable bool) error { + _ = net + fd.SysFile.init() + if ft, err := fd_fdstat_get_type(fd.Sysfd); err == nil { + fd.SysFile.Filetype = uint32(ft) + } + if !pollable { + return nil + } + flags, err := syscall.Fcntl(fd.Sysfd, syscall.F_GETFL, 0) + if err != nil { + return err + } + if flags&syscall.O_NONBLOCK != 0 { + return nil + } + if _, err := syscall.Fcntl(fd.Sysfd, syscall.F_SETFL, flags|syscall.O_NONBLOCK); err != nil { + return err + } + return nil +} + +// Copy returns a duplicate FD that shares the underlying Sysfd through +// the SysFile refcount. The original and the copy can independently +// call Close — only the last one actually issues the syscall. Used by +// upstream net/file_wasip1.go to hand a socket FD off from an os.File +// to a net.Listener / net.Conn. +func (fd *FD) Copy() FD { + return FD{ + Sysfd: fd.Sysfd, + SysFile: fd.SysFile.ref(), + IsStream: fd.IsStream, + ZeroReadIsEOF: fd.ZeroReadIsEOF, + } +} + +// Close marks the FD closed. The underlying syscall FD is only released +// when the refcount drops to zero — earlier Close calls (e.g. on the +// os.File side after a successful net.FileConn handoff) just decrement. +func (fd *FD) Close() error { + if fd.closed { + return ErrFileClosing + } + fd.closed = true + if !fd.SysFile.destroy() { + return nil + } + return syscall.Close(fd.Sysfd) +} + +// Exist reports whether fd points to an actual FD wrapper (non-nil). +// Callers in package os hold a *FD via a per-target alias and need to +// nil-check without depending on the concrete type being a pointer. +func (fd *FD) Exist() bool { return fd != nil } + +// CloseFunc is the hook upstream net's poll package exposes so tests +// can intercept Close. We just point at syscall.Close. +var CloseFunc func(int) error = syscall.Close + +// String is an internal string definition for methods/functions that +// shouldn't be used outside the stdlib. Upstream net references it +// from rawconn.go to mark methods as not-for-external-use. +type String string + +// RawControl / RawRead / RawWrite back syscall.RawConn's three callback +// methods. They invoke f with the underlying FD; the bool return of +// RawRead / RawWrite controls retry-on-EAGAIN, which we implement by +// parking the goroutine on the netpoll registry and retrying until f +// returns true (the same loop upstream uses). +func (fd *FD) RawControl(f func(uintptr)) error { + if fd.closed { + return ErrFileClosing + } + f(uintptr(fd.Sysfd)) + return nil +} + +func (fd *FD) RawRead(f func(uintptr) bool) error { + if fd.closed { + return ErrFileClosing + } + for { + if f(uintptr(fd.Sysfd)) { + return nil + } + wait(fd.Sysfd, pollModeRead) + } +} + +func (fd *FD) RawWrite(f func(uintptr) bool) error { + if fd.closed { + return ErrFileClosing + } + for { + if f(uintptr(fd.Sysfd)) { + return nil + } + wait(fd.Sysfd, pollModeWrite) + } +} + +// Shutdown calls wasi sock_shutdown. how is one of syscall.SHUT_RD, +// SHUT_WR, SHUT_RDWR. +func (fd *FD) Shutdown(how int) error { + return syscall.Shutdown(fd.Sysfd, how) +} + +// Accept loops over wasi sock_accept, parking the goroutine on EAGAIN +// (waiting for a new connection) through the netpoll registry. Returns +// (newfd, sockaddr=nil, errcall, error) — sockaddr is always nil because +// wasi sock_accept doesn't return one. +func (fd *FD) Accept() (int, syscall.Sockaddr, string, error) { + deadline := fd.rDeadline + for { + if !deadline.IsZero() && !time.Now().Before(deadline) { + return -1, nil, "accept", ErrDeadlineExceeded + } + nfd, _, err := syscall.Accept(fd.Sysfd) + if err == nil { + return nfd, nil, "", nil + } + if err == syscall.EINTR { + continue + } + if err != syscall.EAGAIN { + return -1, nil, "accept", err + } + if deadline.IsZero() { + wait(fd.Sysfd, pollModeRead) + } else { + if perr := fd.parkUntil(pollModeRead, deadline); perr != nil { + return -1, nil, "accept", perr + } + } + } +} + +// isSocket reports whether the cached Filetype is a stream or datagram +// socket. False if Init was never called (Filetype stays 0 = UNKNOWN). +func (fd *FD) isSocket() bool { + ft := syscall.Filetype(fd.SysFile.Filetype) + return ft == syscall.FILETYPE_SOCKET_STREAM || ft == syscall.FILETYPE_SOCKET_DGRAM +} + +// Read reads from the FD into p. Sockets dispatch to sock_recv (which +// honours fd.rDeadline directly); regular files/pipes go through +// syscall.Read for the no-deadline fast path or readWithDeadline when +// a deadline is set. +func (fd *FD) Read(p []byte) (int, error) { + if fd.closed { + return 0, ErrFileClosing + } + if len(p) == 0 { + return 0, nil + } + if fd.isSocket() { + return fd.sockRecv(p) + } + if fd.rDeadline.IsZero() { + return byteCount(syscall.Read(fd.Sysfd, p)) + } + return fd.readWithDeadline(p) +} + +// Write writes p to the FD. Sockets dispatch to sock_send. Regular +// files / pipes go through syscall.Write or writeWithDeadline. +func (fd *FD) Write(p []byte) (int, error) { + if fd.closed { + return 0, ErrFileClosing + } + if fd.isSocket() { + return fd.sockSend(p) + } + if fd.wDeadline.IsZero() { + return byteCount(syscall.Write(fd.Sysfd, p)) + } + return fd.writeWithDeadline(p) +} + +// Pread reads from the FD at the given offset. Always file semantics — +// sockets aren't seekable so this never goes through sock_recv. +func (fd *FD) Pread(p []byte, off int64) (int, error) { + if fd.closed { + return 0, ErrFileClosing + } + return byteCount(syscall.Pread(fd.Sysfd, p, off)) +} + +// Pwrite writes to the FD at the given offset. +func (fd *FD) Pwrite(p []byte, off int64) (int, error) { + if fd.closed { + return 0, ErrFileClosing + } + return byteCount(syscall.Pwrite(fd.Sysfd, p, off)) +} + +// SetDeadline sets both the read and write deadlines. +func (fd *FD) SetDeadline(t time.Time) error { + fd.rDeadline = t + fd.wDeadline = t + return nil +} + +// SetReadDeadline sets the deadline for future Read calls. A zero t +// clears the deadline. +func (fd *FD) SetReadDeadline(t time.Time) error { + fd.rDeadline = t + return nil +} + +// SetWriteDeadline sets the deadline for future Write calls. +func (fd *FD) SetWriteDeadline(t time.Time) error { + fd.wDeadline = t + return nil +} + +// readWithDeadline implements the EAGAIN→park retry loop with deadline +// cancellation for non-socket FDs. The deadline is captured at function +// entry; later SetReadDeadline calls don't affect this in-flight Read. +func (fd *FD) readWithDeadline(p []byte) (int, error) { + deadline := fd.rDeadline + iov := wasiIovec{buf: &p[0], bufLen: uint32(len(p))} + for { + if !time.Now().Before(deadline) { + return 0, ErrDeadlineExceeded + } + var n uint32 + errno := wasi_fd_read(int32(fd.Sysfd), unsafe.Pointer(&iov), 1, unsafe.Pointer(&n)) + switch errno { + case 0: + return int(n), nil + case wasiErrnoIntr: + continue + case wasiErrnoAgain: + if err := fd.parkUntil(pollModeRead, deadline); err != nil { + return 0, err + } + default: + return 0, syscall.Errno(errno) + } + } +} + +func (fd *FD) writeWithDeadline(p []byte) (int, error) { + deadline := fd.wDeadline + var nn int + for { + if !time.Now().Before(deadline) { + return nn, ErrDeadlineExceeded + } + if nn == len(p) { + return nn, nil + } + buf := p[nn:] + iov := wasiIovec{buf: &buf[0], bufLen: uint32(len(buf))} + var n uint32 + errno := wasi_fd_write(int32(fd.Sysfd), unsafe.Pointer(&iov), 1, unsafe.Pointer(&n)) + switch errno { + case 0: + nn += int(n) + case wasiErrnoIntr: + // retry + case wasiErrnoAgain: + if err := fd.parkUntil(pollModeWrite, deadline); err != nil { + return nn, err + } + default: + return nn, syscall.Errno(errno) + } + } +} + +// sockRecv is the socket sibling of readWithDeadline / syscall.Read. +// Always issues sock_recv, regardless of deadline state, so callers +// that don't want fd_read on a socket get a guaranteed sock_recv path. +func (fd *FD) sockRecv(p []byte) (int, error) { + deadline := fd.rDeadline + iov := wasiIovec{buf: &p[0], bufLen: uint32(len(p))} + for { + if !deadline.IsZero() && !time.Now().Before(deadline) { + return 0, ErrDeadlineExceeded + } + var n uint32 + var roFlags uint32 + errno := wasi_sock_recv(int32(fd.Sysfd), unsafe.Pointer(&iov), 1, 0, unsafe.Pointer(&n), unsafe.Pointer(&roFlags)) + switch errno { + case 0: + return int(n), nil + case wasiErrnoIntr: + continue + case wasiErrnoAgain: + if deadline.IsZero() { + wait(fd.Sysfd, pollModeRead) + } else if err := fd.parkUntil(pollModeRead, deadline); err != nil { + return 0, err + } + default: + return 0, syscall.Errno(errno) + } + } +} + +func (fd *FD) sockSend(p []byte) (int, error) { + deadline := fd.wDeadline + var nn int + for { + if !deadline.IsZero() && !time.Now().Before(deadline) { + return nn, ErrDeadlineExceeded + } + if nn == len(p) { + return nn, nil + } + buf := p[nn:] + iov := wasiIovec{buf: &buf[0], bufLen: uint32(len(buf))} + var n uint32 + errno := wasi_sock_send(int32(fd.Sysfd), unsafe.Pointer(&iov), 1, 0, unsafe.Pointer(&n)) + switch errno { + case 0: + nn += int(n) + case wasiErrnoIntr: + // retry + case wasiErrnoAgain: + if deadline.IsZero() { + wait(fd.Sysfd, pollModeWrite) + } else if err := fd.parkUntil(pollModeWrite, deadline); err != nil { + return nn, err + } + default: + return nn, syscall.Errno(errno) + } + } +} + +// wasi snapshot preview1 errno values, kept here so the read/write loops +// can compare without dragging in the full syscall errno table for a +// switch on three constants. These match the wasi spec exactly; see also +// syscall_libc_wasi.go in package syscall. +const ( + wasiErrnoAgain uint32 = 6 + wasiErrnoIntr uint32 = 27 +) + +// wait parks the current goroutine until the FD becomes ready in the +// given direction. No deadline; mirrors the helper of the same name in +// package syscall (intentionally duplicated rather than linknamed +// across the package boundary — see project memory on shim avoidance). +func wait(fd int, mode uint8) { + if task.OnSystemStack() { + task.Pause() + return + } + pd := runtime_netpoll_addwait(uint32(fd), mode) + task.Pause() + runtime_netpoll_done(pd) +} + +// parkUntil parks the current goroutine on (fd, mode) with a deadline. +// Returns nil if the FD became ready or the timer fired (caller's loop +// re-checks the deadline at top); ErrDeadlineExceeded if the deadline +// was already in the past. +// +// Race handling: the deadline timer's callback and pollIO's event walk +// can both target the same pollDesc. The pd.fired flag guards against +// double-pushing the task to the run queue; whichever arrives second +// is a no-op. +func (fd *FD) parkUntil(mode uint8, deadline time.Time) error { + d := time.Until(deadline) + if d <= 0 { + return ErrDeadlineExceeded + } + if task.OnSystemStack() { + task.Pause() + return nil + } + pd := runtime_netpoll_addwait(uint32(fd.Sysfd), mode) + timer := time.AfterFunc(d, func() { + runtime_netpoll_wake(pd) + }) + task.Pause() + timer.Stop() + runtime_netpoll_done(pd) + return nil +} + +// WaitWrite waits until data can be written to fd. +func (fd *FD) WaitWrite() error { + if fd.wDeadline.IsZero() { + wait(fd.Sysfd, pollModeWrite) + return nil + } + return fd.parkUntil(pollModeWrite, fd.wDeadline) +} + +// byteCount turns the -1 a failed read or write returns into 0, as Go's +// internal/poll does in fd_unix.go. +func byteCount(n int, err error) (int, error) { + if n < 0 { + n = 0 + } + return n, err +} diff --git a/tools/scripts/testdata/tinygo-overlay/src/runtime/arch_tinygowasm.go b/tools/scripts/testdata/tinygo-overlay/src/runtime/arch_tinygowasm.go new file mode 100644 index 0000000000000..608911830323a --- /dev/null +++ b/tools/scripts/testdata/tinygo-overlay/src/runtime/arch_tinygowasm.go @@ -0,0 +1,107 @@ +//go:build tinygo.wasm + +package runtime + +import ( + "unsafe" +) + +const GOARCH = "wasm" + +// The bitness of the CPU (e.g. 8, 32, 64). +const TargetBits = 32 + +// zeroSizedAlloc a sentinel that gets returned when allocating 0 bytes. +// Using this instead of a constant value since I can't easily find a memory +// location that is definitely not going to end up as a valid pointer. +var zeroSizedAlloc uint8 + +var zeroSizeAllocPtr = &zeroSizedAlloc + +const deferExtraRegs = 0 + +const callInstSize = 1 // unknown and irrelevant (llvm.returnaddress doesn't work), so make something up + +//go:extern __heap_base +var heapStartSymbol [0]byte + +//go:extern __global_base +var globalsStartSymbol [0]byte + +const ( + // wasmMemoryIndex is always zero until the multi-memory feature is used. + // + // See https://github.com/WebAssembly/multi-memory + wasmMemoryIndex = 0 + + // wasmPageSize is the size of a page in WebAssembly's 32-bit memory. This + // is also its only unit of change. + // + // See https://www.w3.org/TR/wasm-core-1/#page-size + wasmPageSize = 64 * 1024 +) + +// wasm_memory_size invokes the "memory.size" instruction, which returns the +// current size to the memory at the given index (always wasmMemoryIndex), in +// pages. +// +//export llvm.wasm.memory.size.i32 +func wasm_memory_size(index int32) int32 + +// wasm_memory_grow invokes the "memory.grow" instruction, which attempts to +// increase the size of the memory at the given index (always wasmMemoryIndex), +// by the delta (in pages). This returns the previous size on success of -1 on +// failure. +// +//export llvm.wasm.memory.grow.i32 +func wasm_memory_grow(index int32, delta int32) int32 + +var ( + // heapStart is the current memory offset which starts the heap. The heap + // extends from this offset until heapEnd (exclusive). + heapStart = uintptr(unsafe.Pointer(&heapStartSymbol)) + + // heapEnd is the current memory length in bytes. + heapEnd = uintptr(wasm_memory_size(wasmMemoryIndex) * wasmPageSize) + + globalsStart = uintptr(unsafe.Pointer(&globalsStartSymbol)) + globalsEnd = uintptr(unsafe.Pointer(&heapStartSymbol)) + + stackTop = uintptr(unsafe.Pointer(&globalsStartSymbol)) +) + +func align(ptr uintptr) uintptr { + // Align to 16, which is the alignment of max_align_t: + // https://godbolt.org/z/dYqTsWrGq + const heapAlign = 16 + return (ptr + heapAlign - 1) &^ (heapAlign - 1) +} + +//export tinygo_getCurrentStackPointer +func getCurrentStackPointer() uintptr + +// savedStackPointer is used to save the system stack pointer while in a goroutine. +var savedStackPointer uintptr + +// saveStackPointer is called by internal/task.state.Resume() to save the stack pointer before entering a goroutine from the system stack. +func saveStackPointer() { + savedStackPointer = getCurrentStackPointer() +} + +// growHeap tries to grow the heap size. It returns true if it succeeds, false +// otherwise. +func growHeap() bool { + // Grow memory by up to 64 MiB at a time and back off if the host caps memory. + // Cap total pages at 65535 so uintptr(pages) * wasmPageSize does not wrap to 0. + const maxPages = 65535 + memorySize := wasm_memory_size(wasmMemoryIndex) + growPages := min(memorySize, 1024, maxPages-memorySize) + for growPages > 0 { + if wasm_memory_grow(wasmMemoryIndex, growPages) != -1 { + setHeapEnd(uintptr(wasm_memory_size(wasmMemoryIndex)) * wasmPageSize) + return true + } + growPages /= 2 + } + return false +} diff --git a/tools/scripts/testdata/tinygo-overlay/src/runtime/gc_leaking.go b/tools/scripts/testdata/tinygo-overlay/src/runtime/gc_leaking.go new file mode 100644 index 0000000000000..7e35ef5cfe4d8 --- /dev/null +++ b/tools/scripts/testdata/tinygo-overlay/src/runtime/gc_leaking.go @@ -0,0 +1,199 @@ +//go:build gc.leaking + +package runtime + +// This GC implementation is the simplest useful memory allocator possible: it +// only allocates memory and never frees it. For some constrained systems, it +// may be the only memory allocator possible. + +import ( + "internal/task" + "sync/atomic" + "unsafe" +) + +const needsStaticHeap = true + +var gcScanState atomic.Uint32 + +func scanCurrentStack() {} + +// Ever-incrementing pointer: no memory is freed. +var ( + heapptr uintptr + small4Ptr uintptr + small4End uintptr +) + +// Total amount allocated for runtime.MemStats +var ( + gcTotalAlloc uint64 + gcLiveAlloc uint64 +) + +// Total number of calls to alloc() +var ( + gcMallocs uint64 + gcFrees uint64 +) + +// Heap lock for parallel goroutines. No-op when single threaded. +var gcLock task.PMutex + +var ( + freeLists [257]unsafe.Pointer + largeFreePtr [256]unsafe.Pointer + largeFreeSize [256]uintptr +) + +//go:noinline +func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer { + if size == 0 { + return unsafe.Pointer(zeroSizeAllocPtr) + } + if size <= 4 { + if small4Ptr == small4End { + chunk := uintptr(alloc(256, nil)) + small4Ptr = chunk + small4End = chunk + 256 + } else { + gcMallocs++ + } + addr := small4Ptr + small4Ptr += 4 + return unsafe.Pointer(addr) + } + size = align(size) + if size <= 2048 { + idx := size >> 3 + if p := freeLists[idx]; p != nil { + freeLists[idx] = *(*unsafe.Pointer)(p) + memzero(p, size) + gcTotalAlloc += uint64(size) + gcLiveAlloc += uint64(size) + gcMallocs++ + return p + } + } else { + for i := range 256 { + if largeFreeSize[i] == size { + p := largeFreePtr[i] + largeFreePtr[i] = nil + largeFreeSize[i] = 0 + memzero(p, size) + gcTotalAlloc += uint64(size) + gcLiveAlloc += uint64(size) + gcMallocs++ + return p + } + } + } + gcLock.Lock() + addr := heapptr + gcTotalAlloc += uint64(size) + gcLiveAlloc += uint64(size) + gcMallocs++ + heapptr += size + if heapptr < addr { + runtimePanic(errOutOfMemory) + } + for heapptr > heapEnd { + if growHeap() { + continue + } + runtimeFatal("out of memory") + } + gcLock.Unlock() + + pointer := unsafe.Pointer(addr) + if GOOS != "wasip1" && GOARCH != "wasm" { + zero_new_alloc(pointer, size) + } + return pointer +} + +func freeSized(ptr unsafe.Pointer, size uintptr) { + if ptr == nil || size < 8 { + return + } + size = align(size) + if size <= 2048 { + idx := size >> 3 + *(*unsafe.Pointer)(ptr) = freeLists[idx] + freeLists[idx] = ptr + gcFrees++ + gcLiveAlloc -= uint64(size) + } else { + for i := range 256 { + if largeFreeSize[i] == 0 { + largeFreePtr[i] = ptr + largeFreeSize[i] = size + gcFrees++ + gcLiveAlloc -= uint64(size) + return + } + } + } +} + +func free(ptr unsafe.Pointer) { +} + +//go:noinline +func freeTaskStack(ptr uintptr) { +} + +func markRoots(start, end uintptr) { + runtimeFatal("unreachable: markRoots") +} + +// ReadMemStats populates m with memory statistics. +// +// The returned memory statistics are up to date as of the +// call to ReadMemStats. This would not do GC implicitly for you. +func ReadMemStats(m *MemStats) { + gcLock.Lock() + + m.HeapIdle = 0 + m.HeapInuse = gcLiveAlloc + m.HeapReleased = 0 // always 0, we don't currently release memory back to the OS. + + m.HeapSys = m.HeapInuse + m.HeapIdle + m.GCSys = 0 + m.TotalAlloc = gcTotalAlloc + m.Mallocs = gcMallocs + m.Frees = gcFrees + m.Sys = uint64(heapEnd - heapStart) + m.HeapAlloc = gcLiveAlloc + m.HeapObjects = gcMallocs - gcFrees + m.Alloc = m.HeapAlloc + m.NumGC = 0 // this GC never collects, so no cycle ever completes + + gcLock.Unlock() +} + +func mallocs() uint64 { + gcLock.Lock() + mallocs := gcMallocs + gcLock.Unlock() + return mallocs +} + +func GC() { + // No-op. +} + +func SetFinalizer(obj interface{}, finalizer interface{}) { + // No-op. +} + +func initHeap() { + heapptr = heapStart +} + +// setHeapEnd sets a new (larger) heapEnd pointer. +func setHeapEnd(newHeapEnd uintptr) { + // This "heap" is so simple that simply assigning a new value is good + // enough. + heapEnd = newHeapEnd +} diff --git a/tools/scripts/testdata/tinygo-overlay/src/runtime/hashmap.go b/tools/scripts/testdata/tinygo-overlay/src/runtime/hashmap.go new file mode 100644 index 0000000000000..79f13369d0da1 --- /dev/null +++ b/tools/scripts/testdata/tinygo-overlay/src/runtime/hashmap.go @@ -0,0 +1,892 @@ +package runtime + +// This is a hashmap implementation for the map[T]T type. +// It is very roughly based on the implementation of the Go hashmap: +// +// https://golang.org/src/runtime/map.go + +import ( + "internal/reflectlite" + "tinygo" + "unsafe" +) + +// The underlying hashmap structure for Go. +type hashmap struct { + buckets unsafe.Pointer // pointer to array of buckets + typeInfo *hashmapTypeInfo + seed uintptr + count uintptr + keySize uintptr + valueSize uintptr + keySlotSize uintptr // == keySize, or sizeof(ptr) if indirect + valueSlotSize uintptr // == valueSize, or sizeof(ptr) if indirect + bucketBits uint8 + flags uint8 + keyEqual func(x, y unsafe.Pointer, n uintptr) bool + keyHash func(key unsafe.Pointer, size, seed uintptr) uint32 +} + +type hashmapTypeInfo struct { + keyLayout unsafe.Pointer + valueLayout unsafe.Pointer + bucketLayout unsafe.Pointer +} + +//go:inline +func hashmapType(m *hashmap) *hashmapTypeInfo { + return m.typeInfo +} + +const ( + hashmapMaxKeySize = 128 + hashmapMaxValueSize = 128 + + hashmapFlagIndirectKey = 1 << 0 + hashmapFlagIndirectValue = 1 << 1 +) + +// A hashmap bucket. A bucket is a container of 8 key/value pairs: first the +// following two entries, then the 8 keys, then the 8 values. This somewhat odd +// ordering is to make sure the keys and values are well aligned when one of +// them is smaller than the system word size. +type hashmapBucket struct { + tophash [8]uint8 + next *hashmapBucket // next bucket (if there are more than 8 in a chain) + // Followed by the actual keys, and then the actual values. These are + // allocated but as they're of variable size they can't be shown here. +} + +// hashmapBucketHeaderSize is the offset in bytes from the start of a bucket to +// the first key, aligned to 8 bytes. This ensures that keys requiring 8-byte +// alignment (float64, complex128, uint64 on strict-alignment architectures +// like MIPS) are properly aligned in the bucket. +const hashmapBucketHeaderSize = (unsafe.Sizeof(hashmapBucket{}) + 7) &^ 7 + +// hashmapKeySlotSize returns the size of a key slot in the bucket. For indirect +// keys, this is the pointer size; otherwise the actual key size. +// +//go:inline +func hashmapKeySlotSize(m *hashmap) uintptr { + return m.keySlotSize +} + +// hashmapValueSlotSize returns the size of a value slot in the bucket. +// +//go:inline +func hashmapValueSlotSize(m *hashmap) uintptr { + return m.valueSlotSize +} + +// hashmapSlotKeyData returns a pointer to the actual key data for a given slot. +// For indirect keys, the slot contains a pointer that must be dereferenced. +// +//go:inline +func hashmapSlotKeyData(m *hashmap, slotKey unsafe.Pointer) unsafe.Pointer { + if m.flags&hashmapFlagIndirectKey != 0 { + return *(*unsafe.Pointer)(slotKey) + } + return slotKey +} + +// hashmapSlotValueData returns a pointer to the actual value data for a given slot. +// +//go:inline +func hashmapSlotValueData(m *hashmap, slotValue unsafe.Pointer) unsafe.Pointer { + if m.flags&hashmapFlagIndirectValue != 0 { + return *(*unsafe.Pointer)(slotValue) + } + return slotValue +} + +type hashmapIterator struct { + buckets unsafe.Pointer // pointer to array of hashapBuckets + numBuckets uintptr // length of buckets array + bucketNumber uintptr // current index into buckets array + startBucket uintptr // starting location for iterator + bucket *hashmapBucket // current bucket in chain + bucketIndex uint8 // current index into bucket + startIndex uint8 // starting bucket index for iterator + wrapped bool // true if the iterator has wrapped +} + +func hashmapNewIterator() unsafe.Pointer { + return unsafe.Pointer(new(hashmapIterator)) +} + +func hashmapResetIterator(it unsafe.Pointer) unsafe.Pointer { + if it == nil { + return hashmapNewIterator() + } + *(*hashmapIterator)(it) = hashmapIterator{} + return it +} + +// Get the topmost 8 bits of the hash, without using a special value (like 0). +func hashmapTopHash(hash uint32) uint8 { + tophash := uint8(hash >> 24) + if tophash < 1 { + // 0 means empty slot, so make it bigger. + tophash += 1 + } + return tophash +} + +// Create a new hashmap with the given keySize and valueSize. +func hashmapMake(keySize, valueSize uintptr, sizeHint uintptr, typeInfo unsafe.Pointer, alg uint8) *hashmap { + bucketBits := uint8(0) + if sizeHint > 8 { + for hashmapHasSpaceToGrow(bucketBits) && hashmapOverLoadFactor(sizeHint, bucketBits) { + bucketBits++ + } + } + + var flags uint8 + keySlotSize := keySize + if keySize > hashmapMaxKeySize { + flags |= hashmapFlagIndirectKey + keySlotSize = unsafe.Sizeof(unsafe.Pointer(nil)) + } + valueSlotSize := valueSize + if valueSize > hashmapMaxValueSize { + flags |= hashmapFlagIndirectValue + valueSlotSize = unsafe.Sizeof(unsafe.Pointer(nil)) + } + + bucketBufSize := hashmapBucketHeaderSize + keySlotSize*8 + valueSlotSize*8 + buckets := alloc(bucketBufSize*(1< If the map or slice is nil, clear is a no-op. + return + } + + rem := m.count + m.count = 0 + numBuckets := uintptr(1) << m.bucketBits + for i := range numBuckets { + bucket := hashmapBucketAddr(m, m.buckets, i) + for bucket != nil { + if *(*uint64)(unsafe.Pointer(&bucket.tophash)) != 0 { + for j := range 8 { + if bucket.tophash[j] != 0 { + rem-- + } + } + bucket.tophash = [8]uint8{} + if rem == 0 { + return + } + } + bucket = bucket.next + } + } +} + +func hashmapKeyEqualAlg(alg tinygo.HashmapAlgorithm) func(x, y unsafe.Pointer, n uintptr) bool { + switch alg { + case tinygo.HashmapAlgorithmBinary: + return memequal + case tinygo.HashmapAlgorithmString: + return hashmapStringEqual + default: + // compiler bug :( + return nil + } +} + +func hashmapKeyHashAlg(alg tinygo.HashmapAlgorithm) func(key unsafe.Pointer, n, seed uintptr) uint32 { + switch alg { + case tinygo.HashmapAlgorithmBinary: + return hash32 + case tinygo.HashmapAlgorithmString: + return hashmapStringPtrHash + default: + // compiler bug :( + return nil + } +} + +func hashmapHasSpaceToGrow(bucketBits uint8) bool { + // Over this limit, we're likely to overflow uintptrs during calculations + // or numbers of hash elements. Don't allow any more growth. + // With 29 bits, this is 2^32 elements anyway. + return bucketBits <= uint8((unsafe.Sizeof(uintptr(0))*8)-3) +} + +func hashmapOverLoadFactor(n uintptr, bucketBits uint8) bool { + // "maximum" number of elements is 0.75 * buckets * elements per bucket + // to avoid overflow, this is calculated as + // max = 3 * (1/4 * buckets * elements per bucket) + // = 3 * (buckets * (elements per bucket)/4) + // = 3 * (buckets * (8/4) + // = 3 * (buckets * 2) + // = 6 * buckets + max := (uintptr(6) << bucketBits) + return n > max +} + +// Return the number of entries in this hashmap, called from the len builtin. +// A nil hashmap is defined as having length 0. +// +//go:inline +func hashmapLen(m *hashmap) int { + if m == nil { + return 0 + } + return int(m.count) +} + +//go:inline +func hashmapBucketSize(m *hashmap) uintptr { + return hashmapBucketHeaderSize + hashmapKeySlotSize(m)*8 + hashmapValueSlotSize(m)*8 +} + +//go:inline +func hashmapBucketAddr(m *hashmap, buckets unsafe.Pointer, n uintptr) *hashmapBucket { + bucketSize := hashmapBucketSize(m) + bucket := (*hashmapBucket)(unsafe.Add(buckets, bucketSize*n)) + return bucket +} + +//go:inline +func hashmapBucketAddrForHash(m *hashmap, hash uint32) *hashmapBucket { + numBuckets := uintptr(1) << m.bucketBits + bucketNumber := (uintptr(hash) & (numBuckets - 1)) + return hashmapBucketAddr(m, m.buckets, bucketNumber) +} + +//go:inline +func hashmapSlotKey(m *hashmap, bucket *hashmapBucket, slot uint8) unsafe.Pointer { + slotKeyOffset := hashmapBucketHeaderSize + hashmapKeySlotSize(m)*uintptr(slot) + return unsafe.Add(unsafe.Pointer(bucket), slotKeyOffset) +} + +//go:inline +func hashmapSlotValue(m *hashmap, bucket *hashmapBucket, slot uint8) unsafe.Pointer { + slotValueOffset := hashmapBucketHeaderSize + hashmapKeySlotSize(m)*8 + hashmapValueSlotSize(m)*uintptr(slot) + return unsafe.Add(unsafe.Pointer(bucket), slotValueOffset) +} + +// Set a specified key to a given value. Grow the map if necessary. +// +//go:nobounds +func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint32) { + if hashmapHasSpaceToGrow(m.bucketBits) && hashmapOverLoadFactor(m.count, m.bucketBits) { + hashmapGrow(m) + // seed changed when we grew; rehash key with new seed + hash = m.keyHash(key, m.keySize, m.seed) + } + + tophash := hashmapTopHash(hash) + bucket := hashmapBucketAddrForHash(m, hash) + var lastBucket *hashmapBucket + + // See whether the key already exists somewhere. + var emptySlotKey unsafe.Pointer + var emptySlotValue unsafe.Pointer + var emptySlotTophash *byte + for bucket != nil { + for i := uint8(0); i < 8; i++ { + slotKey := hashmapSlotKey(m, bucket, i) + slotValue := hashmapSlotValue(m, bucket, i) + if bucket.tophash[i] == 0 && emptySlotKey == nil { + // Found an empty slot, store it for if we couldn't find an + // existing slot. + emptySlotKey = slotKey + emptySlotValue = slotValue + emptySlotTophash = &bucket.tophash[i] + } + if bucket.tophash[i] == tophash { + // Could be an existing key that's the same. + if m.keyEqual(key, hashmapSlotKeyData(m, slotKey), m.keySize) { + // found same key, replace the value + hashmapStoreValue(m, slotValue, value) + return + } + } + } + lastBucket = bucket + bucket = bucket.next + } + if emptySlotKey == nil { + // Add a new bucket to the bucket chain. + // TODO: rebalance if necessary to avoid O(n) insert and lookup time. + lastBucket.next = (*hashmapBucket)(hashmapInsertIntoNewBucket(m, key, value, tophash)) + return + } + m.count++ + hashmapStoreKey(m, emptySlotKey, key) + hashmapStoreValue(m, emptySlotValue, value) + *emptySlotTophash = tophash +} + +// hashmapStoreKey stores a key into a bucket slot, allocating backing storage +// if the key is indirect (first insert) or copying into it (shouldn't happen +// for keys, but handles it correctly). +// +//go:inline +func hashmapStoreKey(m *hashmap, slotKey, key unsafe.Pointer) { + if m.flags&hashmapFlagIndirectKey != 0 { + p := alloc(m.keySize, hashmapType(m).keyLayout) + memcpy(p, key, m.keySize) + *(*unsafe.Pointer)(slotKey) = p + } else { + memcpy(slotKey, key, m.keySize) + } +} + +// hashmapStoreValue stores a value into a bucket slot. For indirect values, +// it allocates backing storage on first insert or copies into the existing +// backing on overwrite. +// +//go:inline +func hashmapStoreValue(m *hashmap, slotValue, value unsafe.Pointer) { + if m.flags&hashmapFlagIndirectValue != 0 { + p := *(*unsafe.Pointer)(slotValue) + if p == nil { + // First insert: allocate backing storage. + p = alloc(m.valueSize, hashmapType(m).valueLayout) + *(*unsafe.Pointer)(slotValue) = p + } + memcpy(p, value, m.valueSize) + } else { + memcpy(slotValue, value, m.valueSize) + } +} + +// hashmapInsertIntoNewBucket creates a new bucket, inserts the given key and +// value into the bucket, and returns a pointer to this bucket. +func hashmapInsertIntoNewBucket(m *hashmap, key, value unsafe.Pointer, tophash uint8) *hashmapBucket { + bucketBufSize := hashmapBucketSize(m) + bucketBuf := alloc(bucketBufSize, hashmapType(m).bucketLayout) + bucket := (*hashmapBucket)(bucketBuf) + + // Insert into the first slot, which is empty as it has just been allocated. + slotKey := hashmapSlotKey(m, bucket, 0) + slotValue := hashmapSlotValue(m, bucket, 0) + m.count++ + hashmapStoreKey(m, slotKey, key) + hashmapStoreValue(m, slotValue, value) + bucket.tophash[0] = tophash + return bucket +} + +func hashmapGrow(m *hashmap) { + oldBuckets := m.buckets + oldNumBuckets := uintptr(1) << m.bucketBits + bucketBufSize := hashmapBucketSize(m) + + n := *m + n.count = 0 + n.bucketBits = m.bucketBits + 1 + numBuckets := uintptr(1) << n.bucketBits + n.buckets = alloc(bucketBufSize*numBuckets, hashmapType(m).bucketLayout) + + for i := range oldNumBuckets { + first := hashmapBucketAddr(m, oldBuckets, i) + b := first + for b != nil { + for j := range uint8(8) { + if b.tophash[j] != 0 { + slotKey := hashmapSlotKey(m, b, j) + slotValue := hashmapSlotValue(m, b, j) + keyPtr := hashmapSlotKeyData(m, slotKey) + valPtr := hashmapSlotValueData(m, slotValue) + h := n.keyHash(keyPtr, uintptr(n.keySize), n.seed) + hashmapSet(&n, keyPtr, valPtr, h) + } + } + next := b.next + if b != first { + freeSized(unsafe.Pointer(b), bucketBufSize) + } + b = next + } + } + freeSized(oldBuckets, bucketBufSize*oldNumBuckets) + *m = n +} + +//go:linkname hashmapClone maps.clone +func hashmapClone(intf _interface) _interface { + typ, val := decomposeInterface(intf) + m := (*hashmap)(val) + n := hashmapCopy(m, m.bucketBits) + return composeInterface(typ, unsafe.Pointer(&n)) +} + +func hashmapCopy(m *hashmap, sizeBits uint8) hashmap { + // clone map as empty + n := *m + n.count = 0 + n.seed = uintptr(fastrand()) + + n.bucketBits = sizeBits + numBuckets := uintptr(1) << n.bucketBits + bucketBufSize := hashmapBucketSize(m) + n.buckets = alloc(bucketBufSize*numBuckets, hashmapType(m).bucketLayout) + + // use a hashmap iterator to go through the old map + var it hashmapIterator + + key := alloc(m.keySize, hashmapType(m).keyLayout) + value := alloc(m.valueSize, hashmapType(m).valueLayout) + + for hashmapNext(m, &it, key, value) { + h := n.keyHash(key, uintptr(n.keySize), n.seed) + hashmapSet(&n, key, value, h) + } + + return n +} + +// Get the value of a specified key, or zero the value if not found. +// +//go:nobounds +func hashmapGet(m *hashmap, key, value unsafe.Pointer, valueSize uintptr, hash uint32) bool { + if m == nil || m.count == 0 { + // Getting a value out of a nil map is valid. From the spec: + // > if the map is nil or does not contain such an entry, a[x] is the + // > zero value for the element type of M + memzero(value, uintptr(valueSize)) + return false + } + + tophash := hashmapTopHash(hash) + bucket := hashmapBucketAddrForHash(m, hash) + + // Try to find the key. + for bucket != nil { + for i := uint8(0); i < 8; i++ { + slotKey := hashmapSlotKey(m, bucket, i) + if bucket.tophash[i] == tophash { + // This could be the key we're looking for. + if m.keyEqual(key, hashmapSlotKeyData(m, slotKey), m.keySize) { + // Found the key, copy it. + slotValue := hashmapSlotValue(m, bucket, i) + memcpy(value, hashmapSlotValueData(m, slotValue), m.valueSize) + return true + } + } + } + bucket = bucket.next + } + + // Did not find the key. + memzero(value, m.valueSize) + return false +} + +// Delete a given key from the map. No-op when the key does not exist in the +// map. +// +//go:nobounds +func hashmapDelete(m *hashmap, key unsafe.Pointer, hash uint32) { + if m == nil || m.count == 0 { + // The delete builtin is defined even when the map is nil. From the spec: + // > If the map m is nil or the element m[k] does not exist, delete is a + // > no-op. + return + } + + tophash := hashmapTopHash(hash) + bucket := hashmapBucketAddrForHash(m, hash) + + // Try to find the key. + for bucket != nil { + for i := uint8(0); i < 8; i++ { + slotKey := hashmapSlotKey(m, bucket, i) + if bucket.tophash[i] == tophash { + // This could be the key we're looking for. + if m.keyEqual(key, hashmapSlotKeyData(m, slotKey), m.keySize) { + // Found the key, delete it. + bucket.tophash[i] = 0 + // Zero out the slot so the GC won't pin the allocations. + keySlotSize := hashmapKeySlotSize(m) + memzero(slotKey, keySlotSize) + slotValue := hashmapSlotValue(m, bucket, i) + valueSlotSize := hashmapValueSlotSize(m) + memzero(slotValue, valueSlotSize) + m.count-- + return + } + } + } + bucket = bucket.next + } +} + +// Iterate over a hashmap. +// +//go:nobounds +func hashmapNext(m *hashmap, it *hashmapIterator, key, value unsafe.Pointer) bool { + if m == nil || m.count == 0 { + // From the spec: if the map is nil, the number of iterations is 0. + return false + } + if it.buckets == nil { + // initialize iterator + it.buckets = m.buckets + it.numBuckets = uintptr(1) << m.bucketBits + it.startBucket = uintptr(fastrand()) & (it.numBuckets - 1) + it.startIndex = uint8(fastrand() & 7) + + it.bucketNumber = it.startBucket + it.bucket = hashmapBucketAddr(m, it.buckets, it.bucketNumber) + it.bucketIndex = it.startIndex + } + + for { + // If we've wrapped and we're back at our starting location, terminate the iteration. + if it.wrapped && it.bucketNumber == it.startBucket && it.bucketIndex == it.startIndex { + return false + } + + if it.bucketIndex >= 8 { + // end of bucket, move to the next in the chain + it.bucketIndex = 0 + it.bucket = it.bucket.next + } + + if it.bucket == nil { + it.bucketNumber++ // next bucket + if it.bucketNumber >= it.numBuckets { + // went through all buckets -- wrap around + it.bucketNumber = 0 + it.wrapped = true + } + it.bucket = hashmapBucketAddr(m, it.buckets, it.bucketNumber) + continue + } + + if it.bucket.tophash[it.bucketIndex] == 0 { + // slot is empty - move on + it.bucketIndex++ + continue + } + + // Found a key. + slotKey := hashmapSlotKey(m, it.bucket, it.bucketIndex) + memcpy(key, hashmapSlotKeyData(m, slotKey), m.keySize) + + if it.buckets == m.buckets { + // Our view of the buckets is the same as the parent map. + // Just copy the value we have + slotValue := hashmapSlotValue(m, it.bucket, it.bucketIndex) + memcpy(value, hashmapSlotValueData(m, slotValue), m.valueSize) + it.bucketIndex++ + } else { + it.bucketIndex++ + + // Our view of the buckets doesn't match the parent map. + // Look up the key in the new buckets and return that value if it exists + hash := m.keyHash(key, m.keySize, m.seed) + ok := hashmapGet(m, key, value, m.valueSize, hash) + if !ok { + // doesn't exist in parent map; try next key + continue + } + + // All good. + } + + return true + } +} + +// Hashmap with plain binary data keys (not containing strings etc.). +func hashmapBinarySet(m *hashmap, key, value unsafe.Pointer) { + if m == nil { + nilMapPanic() + } + hash := hash32(key, m.keySize, m.seed) + hashmapSet(m, key, value, hash) +} + +func hashmapBinaryGet(m *hashmap, key, value unsafe.Pointer, valueSize uintptr) bool { + if m == nil || m.count == 0 { + memzero(value, uintptr(valueSize)) + return false + } + hash := hash32(key, m.keySize, m.seed) + return hashmapGet(m, key, value, valueSize, hash) +} + +func hashmapBinaryDelete(m *hashmap, key unsafe.Pointer) { + if m == nil || m.count == 0 { + return + } + hash := hash32(key, m.keySize, m.seed) + hashmapDelete(m, key, hash) +} + +// Hashmap with compiler-generated key hash/equal functions. +// Unlike the binary path (which uses hash32/memequal), these use the +// type-specific keyHash and keyEqual function pointers stored in the hashmap +// struct. This is used for composite key types (e.g. structs containing +// strings) where the compiler generates specialized hash/equal functions. + +func hashmapGenericSet(m *hashmap, key, value unsafe.Pointer) { + if m == nil { + nilMapPanic() + } + hash := m.keyHash(key, m.keySize, m.seed) + hashmapSet(m, key, value, hash) +} + +func hashmapGenericGet(m *hashmap, key, value unsafe.Pointer, valueSize uintptr) bool { + if m == nil || m.count == 0 { + memzero(value, uintptr(valueSize)) + return false + } + hash := m.keyHash(key, m.keySize, m.seed) + return hashmapGet(m, key, value, valueSize, hash) +} + +func hashmapGenericDelete(m *hashmap, key unsafe.Pointer) { + if m == nil || m.count == 0 { + return + } + hash := m.keyHash(key, m.keySize, m.seed) + hashmapDelete(m, key, hash) +} + +// hashmapMakeGeneric creates a new hashmap with compiler-provided hash and +// equal functions. This avoids the interface/reflection path for composite +// key types like structs containing strings. +func hashmapMakeGeneric(keySize, valueSize uintptr, sizeHint uintptr, + typeInfo unsafe.Pointer, + keyHash func(key unsafe.Pointer, size, seed uintptr) uint32, + keyEqual func(x, y unsafe.Pointer, n uintptr) bool, +) *hashmap { + bucketBits := uint8(0) + if sizeHint > 8 { + for hashmapHasSpaceToGrow(bucketBits) && hashmapOverLoadFactor(sizeHint, bucketBits) { + bucketBits++ + } + } + + var flags uint8 + keySlotSize := keySize + if keySize > hashmapMaxKeySize { + flags |= hashmapFlagIndirectKey + keySlotSize = unsafe.Sizeof(unsafe.Pointer(nil)) + } + valueSlotSize := valueSize + if valueSize > hashmapMaxValueSize { + flags |= hashmapFlagIndirectValue + valueSlotSize = unsafe.Sizeof(unsafe.Pointer(nil)) + } + + bucketBufSize := hashmapBucketHeaderSize + keySlotSize*8 + valueSlotSize*8 + buckets := alloc(bucketBufSize*(1< 0 { + // Allocate a new slice with capacity for elemsLen more elements, if necessary; + // otherwise, reuse the passed slice. + srcBuf, _, srcCap = sliceGrow(srcBuf, srcLen, srcCap, newLen, elemSize, layout) + + // Append the new elements in-place. + memmove(unsafe.Add(srcBuf, srcLen*elemSize), elemsBuf, elemsLen*elemSize) + } + + return srcBuf, newLen, srcCap +} + +// sliceGrow returns a new slice with space for at least newCap elements +func sliceGrow(oldBuf unsafe.Pointer, oldLen, oldCap, newCap, elemSize uintptr, layout unsafe.Pointer) (unsafe.Pointer, uintptr, uintptr) { + if oldCap >= newCap { + // No need to grow, return the input slice. + return oldBuf, oldLen, oldCap + } + if elemSize == 0 { + return oldBuf, oldLen, newCap + } + + // This can be made more memory-efficient by multiplying by some other constant, such as 1.5, + // which seems to be allowed by the Go language specification (but this can be observed by + // programs); however, due to memory fragmentation and the current state of the TinyGo + // memory allocators, this causes some difficult to debug issues. + if oldCap >= 256 && newCap <= oldCap+oldCap/2 { + newCap = oldCap + oldCap/2 + } else { + newCap = 1 << bits.Len(uint(newCap)) + } + + buf := alloc(newCap*elemSize, layout) + if oldLen > 0 { + // copy any data to new slice + memmove(buf, oldBuf, oldLen*elemSize) + } + + return buf, oldLen, newCap +} diff --git a/tsc/cmd/tsc/api.go b/tsc/cmd/tsc/api.go index 104343b494650..37ed6f463c264 100644 --- a/tsc/cmd/tsc/api.go +++ b/tsc/cmd/tsc/api.go @@ -1,3 +1,5 @@ +//go:build !tinygo + package main import ( diff --git a/tsc/cmd/tsc/lsp.go b/tsc/cmd/tsc/lsp.go index d25f743d8eda9..8f3a5f4c88fc1 100644 --- a/tsc/cmd/tsc/lsp.go +++ b/tsc/cmd/tsc/lsp.go @@ -1,3 +1,5 @@ +//go:build !tinygo + package main import ( diff --git a/tsc/cmd/tsc/main.go b/tsc/cmd/tsc/main.go index 899113dc19fc9..51687dcd8d48a 100644 --- a/tsc/cmd/tsc/main.go +++ b/tsc/cmd/tsc/main.go @@ -1,8 +1,11 @@ +//go:build !tinygo + package main import ( "context" "os" + "runtime/debug" "syscall" "github.com/microsoft/TypeScript/tsc/internal/core" @@ -16,6 +19,12 @@ func main() { func runMain() int { core.ApplyDebugStackLimit() + if os.Getenv("GOGC") == "" { + debug.SetGCPercent(300) + } + if os.Getenv("GOMEMLIMIT") == "" { + debug.SetMemoryLimit(8 << 30) + } args := osutil.Args()[1:] if len(args) > 0 { switch args[0] { diff --git a/tsc/cmd/tsc/platform_wasip1.go b/tsc/cmd/tsc/platform_wasip1.go index e453b128e0a6a..9f005dc52e3a4 100644 --- a/tsc/cmd/tsc/platform_wasip1.go +++ b/tsc/cmd/tsc/platform_wasip1.go @@ -1,4 +1,4 @@ -//go:build wasip1 +//go:build wasip1 && !tinygo package main diff --git a/tsc/cmd/tsc/sys.go b/tsc/cmd/tsc/sys.go index 608cb46cac628..adfd6d5babe0d 100644 --- a/tsc/cmd/tsc/sys.go +++ b/tsc/cmd/tsc/sys.go @@ -1,3 +1,5 @@ +//go:build !tinygo + package main import ( diff --git a/tsc/cmd/tsc/wasm_wasip1.go b/tsc/cmd/tsc/wasm_wasip1.go new file mode 100644 index 0000000000000..1d56486fd8c31 --- /dev/null +++ b/tsc/cmd/tsc/wasm_wasip1.go @@ -0,0 +1,351 @@ +//go:build wasip1 && tinygo + +package main + +import ( + "context" + "encoding/binary" + "errors" + "fmt" + "io" + "io/fs" + "os" + "slices" + "strconv" + "strings" + "time" + "unicode/utf16" + "unsafe" + + "github.com/microsoft/TypeScript/tsc/internal/bundled" + "github.com/microsoft/TypeScript/tsc/internal/execute" + "github.com/microsoft/TypeScript/tsc/internal/tspath" + "github.com/microsoft/TypeScript/tsc/internal/vfs" +) + +const ( + opRead uint32 = 0 + opStat uint32 = 1 + opReadDir uint32 = 2 + opRealpath uint32 = 3 + opWrite uint32 = 4 + opAppend uint32 = 5 + opRemove uint32 = 6 + opChtimes uint32 = 7 +) + +//go:wasmimport tsrs_host fs +func hostFS(op uint32, ptr unsafe.Pointer, length uint32) int32 + +//go:wasmimport tsrs_host fs_take +func hostFSTake(ptr unsafe.Pointer) + +var ( + wasmInputBuf []byte + wasmOutputBuf []byte + wasmDummyByte byte +) + +//go:wasmexport tsrs_input +func tsrsInput(length uint32) unsafe.Pointer { + if int(length) <= cap(wasmInputBuf) { + wasmInputBuf = wasmInputBuf[:length] + } else { + wasmInputBuf = make([]byte, max(int(length), 1))[:length] + } + if length == 0 { + return unsafe.Pointer(&wasmDummyByte) + } + return unsafe.Pointer(unsafe.SliceData(wasmInputBuf)) +} + +//go:wasmexport tsrs_run +func tsrsRun() int32 { + req := string(wasmInputBuf) + wasmInputBuf = wasmInputBuf[:0] + wasmOutputBuf = wasmOutputBuf[:0] + + parts := strings.Split(req, "\x00") + cwd := "/" + if len(parts) > 0 && parts[0] != "" { + cwd = parts[0] + } + var flags uint32 + if len(parts) > 1 { + if v, err := strconv.ParseUint(parts[1], 10, 32); err == nil { + flags = uint32(v) + } + } + var args []string + if len(parts) > 2 { + args = parts[2:] + } + + caseSensitive := (flags & 2) == 0 + tty := (flags & 4) != 0 + sys := &wasmSys{ + cwd: tspath.RootedDirectoryPathFromAbsolute(tspath.NormalizePath(cwd)), + fs: bundled.WrapFS(&wasmHostFS{caseSensitive: caseSensitive}), + defaultLibraryPath: bundled.LibPath(), + writer: os.Stdout, + tty: tty, + start: time.Now(), + } + result := execute.CommandLine(context.Background(), sys, args, nil) + return int32(result.Status) +} + +//go:wasmexport tsrs_output +func tsrsOutput() unsafe.Pointer { + if len(wasmOutputBuf) == 0 { + return unsafe.Pointer(&wasmDummyByte) + } + return unsafe.Pointer(unsafe.SliceData(wasmOutputBuf)) +} + +//go:wasmexport tsrs_output_len +func tsrsOutputLen() uint32 { + return uint32(len(wasmOutputBuf)) +} + +func hostCall(op uint32, arg string) ([]byte, error) { + var ptr unsafe.Pointer + if len(arg) > 0 { + ptr = unsafe.Pointer(unsafe.StringData(arg)) + } else { + ptr = unsafe.Pointer(&wasmDummyByte) + } + n := hostFS(op, ptr, uint32(len(arg))) + if n == 0 { + return nil, nil + } + if n > 0 { + buf := make([]byte, n) + hostFSTake(unsafe.Pointer(unsafe.SliceData(buf))) + return buf, nil + } + if n == -1 { + return nil, vfs.ErrNotExist + } + errLen := -2 - n + if errLen <= 0 { + return nil, errors.New("io error") + } + errBuf := make([]byte, errLen) + hostFSTake(unsafe.Pointer(unsafe.SliceData(errBuf))) + return nil, errors.New(string(errBuf)) +} + +type wasmFileInfo struct { + name string + size int64 + mode fs.FileMode + modTime time.Time +} + +func (fi *wasmFileInfo) Name() string { return fi.name } +func (fi *wasmFileInfo) Size() int64 { return fi.size } +func (fi *wasmFileInfo) Mode() fs.FileMode { return fi.mode } +func (fi *wasmFileInfo) ModTime() time.Time { return fi.modTime } +func (fi *wasmFileInfo) IsDir() bool { return fi.mode.IsDir() } +func (fi *wasmFileInfo) Sys() any { return nil } + +type wasmHostFS struct { + caseSensitive bool +} + +func (w *wasmHostFS) CaseSensitivity() tspath.CaseSensitivity { + if w.caseSensitive { + return tspath.CaseSensitive + } + return tspath.CaseInsensitive +} + +func (w *wasmHostFS) Stat(path tspath.RootedPath) vfs.FileInfo { + p := path.AsString() + out, err := hostCall(opStat, p) + if err != nil || len(out) == 0 { + return nil + } + text := unsafe.String(unsafe.SliceData(out), len(out)) + s1 := strings.IndexByte(text, ' ') + if s1 < 0 { + return nil + } + kind := text[:s1] + rest := text[s1+1:] + s2 := strings.IndexByte(rest, ' ') + var sizeStr, mtimeStr string + if s2 >= 0 { + sizeStr = rest[:s2] + mtimeStr = rest[s2+1:] + } else { + sizeStr = rest + } + var mode fs.FileMode + switch kind { + case "d": + mode = fs.ModeDir | 0o755 + case "f": + mode = 0o644 + default: + mode = fs.ModeIrregular + } + size, _ := strconv.ParseInt(sizeStr, 10, 64) + mtimeNs, _ := strconv.ParseInt(mtimeStr, 10, 64) + trimmed := strings.TrimRight(p, "/") + base := "/" + if trimmed != "" { + if idx := strings.LastIndexByte(trimmed, '/'); idx >= 0 { + base = trimmed[idx+1:] + } else { + base = trimmed + } + } + return &wasmFileInfo{ + name: base, + size: size, + mode: mode, + modTime: time.Unix(0, mtimeNs), + } +} + +func (w *wasmHostFS) FileExists(path tspath.RootedFilePath) bool { + fi := w.Stat(path.AsPath()) + return fi != nil && !fi.IsDir() +} + +func (w *wasmHostFS) DirectoryExists(path tspath.RootedDirectoryPath) bool { + fi := w.Stat(path.AsPath()) + return fi != nil && fi.IsDir() +} + +func (w *wasmHostFS) ReadFile(path tspath.RootedFilePath) (string, bool) { + b, err := hostCall(opRead, path.AsString()) + if err != nil { + return "", false + } + if len(b) == 0 { + return "", true + } + if len(b) >= 2 { + if b[0] == 0xFE && b[1] == 0xFF { + return decodeUTF16(b[2:], binary.BigEndian), true + } + if b[0] == 0xFF && b[1] == 0xFE { + return decodeUTF16(b[2:], binary.LittleEndian), true + } + if len(b) >= 3 && b[0] == 0xEF && b[1] == 0xBB && b[2] == 0xBF { + b = b[3:] + if len(b) == 0 { + return "", true + } + } + } + return unsafe.String(unsafe.SliceData(b), len(b)), true +} + +func decodeUTF16(b []byte, order binary.ByteOrder) string { + u16s := make([]uint16, len(b)/2) + for i := range u16s { + u16s[i] = order.Uint16(b[2*i:]) + } + return string(utf16.Decode(u16s)) +} + +func (w *wasmHostFS) GetAccessibleEntries(path tspath.RootedDirectoryPath) (result vfs.Entries) { + result.Symlinks = map[string]struct{}{} + out, err := hostCall(opReadDir, path.AsString()) + if err != nil || len(out) == 0 { + return result + } + raw := unsafe.String(unsafe.SliceData(out), len(out)) + items := strings.Split(raw, "\x00") + // Sort by entry name (after the 1-byte kind prefix) to match fs.ReadDir. + valid := items[:0] + for _, item := range items { + if len(item) > 1 { + valid = append(valid, item) + } + } + slices.SortFunc(valid, func(a, b string) int { + return strings.Compare(a[1:], b[1:]) + }) + for _, item := range valid { + kind := item[0] + name := item[1:] + switch kind { + case 'd': + result.Directories = append(result.Directories, name) + case 'f': + result.Files = append(result.Files, name) + case 'l': + if stat := w.Stat(path.ResolveFile(name).AsPath()); stat != nil { + if stat.IsDir() { + result.Directories = append(result.Directories, name) + result.Symlinks[name] = struct{}{} + } else if stat.Mode().IsRegular() { + result.Files = append(result.Files, name) + result.Symlinks[name] = struct{}{} + } + } + } + } + return result +} + +func (w *wasmHostFS) Realpath(path tspath.RootedPath) tspath.RootedPath { + out, err := hostCall(opRealpath, path.AsString()) + if err != nil || len(out) == 0 { + return path + } + return tspath.RootedPathFromAbsolute(tspath.NormalizeSlashes(unsafe.String(unsafe.SliceData(out), len(out)))) +} + +func (w *wasmHostFS) WriteFile(path tspath.RootedFilePath, data string) error { + _, err := hostCall(opWrite, path.AsString()+"\x00"+data) + return err +} + +func (w *wasmHostFS) AppendFile(path tspath.RootedFilePath, data string) error { + _, err := hostCall(opAppend, path.AsString()+"\x00"+data) + return err +} + +func (w *wasmHostFS) Remove(path tspath.RootedPath) error { + _, err := hostCall(opRemove, path.AsString()) + return err +} + +func (w *wasmHostFS) Chtimes(path tspath.RootedPath, aTime time.Time, mTime time.Time) error { + _, err := hostCall(opChtimes, fmt.Sprintf("%s\x00%d\x00%d", path.AsString(), aTime.UnixNano(), mTime.UnixNano())) + return err +} + +type wasmSys struct { + fs vfs.FS + defaultLibraryPath tspath.RootedDirectoryPath + cwd tspath.RootedDirectoryPath + writer io.Writer + tty bool + start time.Time +} + +func (s *wasmSys) SinceStart() time.Duration { return time.Since(s.start) } +func (s *wasmSys) Now() time.Time { return time.Now() } +func (s *wasmSys) FS() vfs.FS { return s.fs } +func (s *wasmSys) DefaultLibraryPath() tspath.RootedDirectoryPath { return s.defaultLibraryPath } +func (s *wasmSys) GetCurrentDirectory() tspath.RootedDirectoryPath { return s.cwd } +func (s *wasmSys) Writer() io.Writer { return s.writer } +func (s *wasmSys) ErrorWriter() io.Writer { return os.Stderr } +func (s *wasmSys) WriteOutputIsTTY() bool { return s.tty } +func (s *wasmSys) GetWidthOfTerminal() int { return 0 } +func (s *wasmSys) GetEnvironmentVariable(name string) (string, bool) { + return os.LookupEnv(name) +} + +func (s *wasmSys) Spawn(command []string, dir string, stderr io.Writer) (io.ReadWriteCloser, error) { + return nil, errors.New("spawn not supported on wasm") +} + +func main() {} diff --git a/tsc/cmd/tsc/wasmapi_wasip1.go b/tsc/cmd/tsc/wasmapi_wasip1.go index 6bc9207004cfb..5c25f9bc94cb9 100644 --- a/tsc/cmd/tsc/wasmapi_wasip1.go +++ b/tsc/cmd/tsc/wasmapi_wasip1.go @@ -1,4 +1,4 @@ -//go:build wasip1 +//go:build wasip1 && !tinygo package main diff --git a/tsc/internal/bundled/bundled.go b/tsc/internal/bundled/bundled.go index 3007f55ad2e1f..a6fcf3e301f68 100644 --- a/tsc/internal/bundled/bundled.go +++ b/tsc/internal/bundled/bundled.go @@ -5,7 +5,6 @@ import ( "path/filepath" "runtime" "sync" - "testing" "github.com/microsoft/TypeScript/tsc/internal/tspath" "github.com/microsoft/TypeScript/tsc/internal/vfs" @@ -44,9 +43,6 @@ var bundledSourceDir = sync.OnceValue(func() tspath.RootedDirectoryPath { }) var testingLibPath = sync.OnceValue(func() string { - if !testing.Testing() { - panic("bundled: TestingLibPath should only be called during tests") - } return bundledSourceDir().ResolveDirectory("libs").AsString() }) diff --git a/tsc/internal/collections/syncmap.go b/tsc/internal/collections/syncmap.go index c191430535c9d..01e3b1af2345a 100644 --- a/tsc/internal/collections/syncmap.go +++ b/tsc/internal/collections/syncmap.go @@ -1,3 +1,5 @@ +//go:build !wasm + package collections import ( diff --git a/tsc/internal/collections/syncmap_wasm.go b/tsc/internal/collections/syncmap_wasm.go new file mode 100644 index 0000000000000..9daf55935c156 --- /dev/null +++ b/tsc/internal/collections/syncmap_wasm.go @@ -0,0 +1,87 @@ +//go:build wasm + +package collections + +import ( + "iter" + "maps" +) + +type SyncMap[K comparable, V any] struct { + _ [0]K + _ [0]V + m map[K]V +} + +func (s *SyncMap[K, V]) Load(key K) (value V, ok bool) { + if s.m == nil { + return value, false + } + val, ok := s.m[key] + return val, ok +} + +func (s *SyncMap[K, V]) Store(key K, value V) { + if s.m == nil { + s.m = make(map[K]V) + } + s.m[key] = value +} + +func (s *SyncMap[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool) { + if s.m == nil { + s.m = make(map[K]V) + } else if existing, ok := s.m[key]; ok { + return existing, true + } + s.m[key] = value + return value, false +} + +func (s *SyncMap[K, V]) Delete(key K) { + if s.m != nil { + delete(s.m, key) + } +} + +func (s *SyncMap[K, V]) Clear() { + if s.m != nil { + clear(s.m) + } +} + +func (s *SyncMap[K, V]) Range(f func(key K, value V) bool) { + for k, v := range s.m { + if !f(k, v) { + break + } + } +} + +func (s *SyncMap[K, V]) Size() int { + return len(s.m) +} + +func (s *SyncMap[K, V]) ToMap() map[K]V { + if s.m == nil { + return make(map[K]V) + } + return maps.Clone(s.m) +} + +func (s *SyncMap[K, V]) Keys() iter.Seq[K] { + return func(yield func(K) bool) { + for k := range s.m { + if !yield(k) { + return + } + } + } +} + +func (s *SyncMap[K, V]) Clone() *SyncMap[K, V] { + if s.m == nil { + return &SyncMap[K, V]{} + } + return &SyncMap[K, V]{m: maps.Clone(s.m)} +} diff --git a/tsc/internal/compiler/checkerpool.go b/tsc/internal/compiler/checkerpool.go index 302300e6a43ab..75f1b2893b8cc 100644 --- a/tsc/internal/compiler/checkerpool.go +++ b/tsc/internal/compiler/checkerpool.go @@ -3,9 +3,11 @@ package compiler import ( "context" "math" + "runtime" "slices" "sort" "sync" + "sync/atomic" "github.com/microsoft/TypeScript/tsc/internal/ast" "github.com/microsoft/TypeScript/tsc/internal/checker" @@ -32,7 +34,16 @@ type checkerPool struct { createCheckersOnce sync.Once checkers []*checker.Checker locks []*sync.Mutex - fileAssociations map[*ast.SourceFile]*checker.Checker + fileAssociations map[*ast.SourceFile]int + + maxMemoryBytes uint64 + recycleMu sync.Mutex + retiredGlobalDiagnostics []*ast.Diagnostic + retiredSymbolCount atomic.Uint64 + retiredTypeCount atomic.Uint64 + retiredInstantiationCount atomic.Uint64 + retiredCheckerCount atomic.Uint64 + readMemoryAllocationInBytes func() uint64 } var _ CheckerPool = (*checkerPool)(nil) @@ -321,6 +332,21 @@ func newCheckerPoolWithTracing(program *Program, tr *tracing.Tracing) *checkerPo checkers: make([]*checker.Checker, checkerCount), locks: make([]*sync.Mutex, checkerCount), tracing: tr, + readMemoryAllocationInBytes: func() uint64 { + var stats runtime.MemStats + runtime.ReadMemStats(&stats) + return stats.Alloc + }, + } + options := program.Options() + if maxMemory := options.MaxMemory; maxMemory != nil && + *maxMemory > 0 && + options.NoEmit.IsTrue() && + !options.GetEmitDeclarations() && + !options.IsIncremental() && + options.GenerateTrace == "" && + options.Watch.IsFalseOrUnknown() { + pool.maxMemoryBytes = uint64(*maxMemory) * 1024 * 1024 } return pool @@ -345,14 +371,14 @@ func (p *checkerPool) GetChecker(ctx context.Context, file *ast.SourceFile) (*ch // e.g. for read-only operations like obtaining an emit resolver. func (p *checkerPool) getCheckerForFileNonExclusive(file *ast.SourceFile) (*checker.Checker, func()) { p.createCheckers() - return p.fileAssociations[file], noop + return p.checkers[p.fileAssociations[file]], noop } func (p *checkerPool) getCheckerForFileExclusive(ctx context.Context, file *ast.SourceFile) (*checker.Checker, func()) { p.createCheckers() - c := p.fileAssociations[file] - idx := slices.Index(p.checkers, c) + idx := p.fileAssociations[file] p.locks[idx].Lock() + c := p.checkers[idx] return c, sync.OnceFunc(func() { p.locks[idx].Unlock() }) @@ -412,9 +438,9 @@ func (p *checkerPool) createCheckers() { fileOrder := getCheckerAssociationOrder(fileWeights, isDeclarationFile, policy.prioritizeSourceFiles) associations = getCheckerAssociationsInOrder(fileWeights, adjacentFiles, fileOrder, checkerCount, policy.balancePenaltyMultiplier) } - p.fileAssociations = make(map[*ast.SourceFile]*checker.Checker, len(p.program.files)) + p.fileAssociations = make(map[*ast.SourceFile]int, len(p.program.files)) for i, file := range p.program.files { - p.fileAssociations[file] = p.checkers[associations[i]] + p.fileAssociations[file] = associations[i] } }) } @@ -451,11 +477,11 @@ func (p *checkerPool) getImportAdjacency() [][]int { func (p *checkerPool) forEachCheckerParallel(cb func(idx int, c *checker.Checker)) { p.createCheckers() wg := core.NewWorkGroup(p.program.SingleThreaded()) - for idx, checker := range p.checkers { + for idx := range p.checkers { wg.Queue(func() { p.locks[idx].Lock() defer p.locks[idx].Unlock() - cb(idx, checker) + cb(idx, p.checkers[idx]) }) } wg.RunAndWait() @@ -467,24 +493,175 @@ func (p *checkerPool) GetGlobalDiagnostics() []*ast.Diagnostic { p.forEachCheckerParallel(func(idx int, checker *checker.Checker) { globalDiagnostics[idx] = checker.GetGlobalDiagnostics() }) - return SortAndDeduplicateDiagnostics(slices.Concat(globalDiagnostics...)) + p.recycleMu.Lock() + retired := slices.Clone(p.retiredGlobalDiagnostics) + p.recycleMu.Unlock() + return SortAndDeduplicateDiagnostics(append(slices.Concat(globalDiagnostics...), retired...)) } // forEachCheckerGroupDo runs one task per checker in parallel. Each task iterates // the provided files, processing only those assigned to its checker. Within each // checker's set, files are visited in their original order. +type checkerWorkQueue struct { + items []int + state atomic.Uint64 // low 32 bits: head, high 32 bits: tail +} + +func (q *checkerWorkQueue) popFront() (int, bool) { + for { + s := q.state.Load() + head := uint32(s) + tail := uint32(s >> 32) + if head >= tail { + return 0, false + } + if q.state.CompareAndSwap(s, uint64(head+1)|(uint64(tail)<<32)) { + return q.items[head], true + } + } +} + +func (q *checkerWorkQueue) popBack() (int, bool) { + for { + s := q.state.Load() + head := uint32(s) + tail := uint32(s >> 32) + if head >= tail { + return 0, false + } + newTail := tail - 1 + if q.state.CompareAndSwap(s, uint64(head)|(uint64(newTail)<<32)) { + return q.items[newTail], true + } + } +} + +func (q *checkerWorkQueue) remaining() uint32 { + s := q.state.Load() + head := uint32(s) + tail := uint32(s >> 32) + if head >= tail { + return 0 + } + return tail - head +} + func (p *checkerPool) forEachCheckerGroupDo(ctx context.Context, files []*ast.SourceFile, singleThreaded bool, cb func(c *checker.Checker, fileIndex int, file *ast.SourceFile)) { p.createCheckers() - checkerCount := len(p.checkers) + if checkerCount == 1 { + if prev := runtime.GOMAXPROCS(0); prev > 1 { + runtime.GOMAXPROCS(1) + defer runtime.GOMAXPROCS(prev) + } + p.locks[0].Lock() + defer p.locks[0].Unlock() + current := p.checkers[0] + if p.maxMemoryBytes == 0 { + for i, file := range files { + cb(current, i, file) + } + return + } + remaining := 0 + for _, file := range files { + if !p.program.SkipTypeChecking(file, false) { + remaining++ + } + } + for i, file := range files { + cb(current, i, file) + if p.program.SkipTypeChecking(file, false) { + continue + } + remaining-- + if remaining != 0 { + current = p.recycleCheckerIfOverBudget(0, current) + } + } + return + } + if !singleThreaded && p.maxMemoryBytes == 0 { + if prev := runtime.GOMAXPROCS(0); prev > checkerCount { + runtime.GOMAXPROCS(checkerCount) + defer runtime.GOMAXPROCS(prev) + } + queues := make([]checkerWorkQueue, checkerCount) + counts := make([]int, checkerCount) + fileCheckers := make([]int, len(files)) + for i, file := range files { + cIdx := p.fileAssociations[file] + fileCheckers[i] = cIdx + counts[cIdx]++ + } + allItems := make([]int, len(files)) + offset := 0 + for cIdx, cnt := range counts { + queues[cIdx].items = allItems[offset : offset : offset+cnt] + queues[cIdx].state.Store(uint64(cnt) << 32) + offset += cnt + } + for i, cIdx := range fileCheckers { + queues[cIdx].items = append(queues[cIdx].items, i) + } + wg := core.NewWorkGroup(false) + for checkerIdx := range checkerCount { + wg.Queue(func() { + p.locks[checkerIdx].Lock() + defer p.locks[checkerIdx].Unlock() + current := p.checkers[checkerIdx] + for { + if i, ok := queues[checkerIdx].popFront(); ok { + cb(current, i, files[i]) + continue + } + bestVictim := -1 + bestRem := uint32(0) + for v := range checkerCount { + if v != checkerIdx { + if rem := queues[v].remaining(); rem > bestRem { + bestRem = rem + bestVictim = v + } + } + } + if bestVictim < 0 { + break + } + if i, ok := queues[bestVictim].popBack(); ok { + p.recycleMu.Lock() + p.fileAssociations[files[i]] = checkerIdx + p.recycleMu.Unlock() + cb(current, i, files[i]) + } + } + }) + } + wg.RunAndWait() + return + } + remainingCheckable := make([]int, checkerCount) + for _, file := range files { + if !p.program.SkipTypeChecking(file, false) { + remainingCheckable[p.fileAssociations[file]]++ + } + } wg := core.NewWorkGroup(singleThreaded) for checkerIdx := range checkerCount { wg.Queue(func() { p.locks[checkerIdx].Lock() defer p.locks[checkerIdx].Unlock() + current := p.checkers[checkerIdx] for i, file := range files { - if checker := p.checkers[checkerIdx]; checker == p.fileAssociations[file] { - cb(checker, i, file) + if checkerIdx == p.fileAssociations[file] { + cb(current, i, file) + if p.program.SkipTypeChecking(file, false) { + continue + } + remainingCheckable[checkerIdx]-- + if remainingCheckable[checkerIdx] != 0 { + current = p.recycleCheckerIfOverBudget(checkerIdx, current) + } } } }) @@ -498,10 +675,34 @@ func (p *checkerPool) groupFilesByChecker(files []*ast.SourceFile) [][]int { p.createCheckers() groups := make([][]int, len(p.checkers)) for i, file := range files { - idx := slices.Index(p.checkers, p.fileAssociations[file]) + idx := p.fileAssociations[file] groups[idx] = append(groups[idx], i) } return groups } +func (p *checkerPool) recycleCheckerIfOverBudget(checkerIdx int, current *checker.Checker) *checker.Checker { + if p.maxMemoryBytes == 0 || p.readMemoryAllocationInBytes() <= p.maxMemoryBytes { + return current + } + + p.recycleMu.Lock() + defer p.recycleMu.Unlock() + if p.readMemoryAllocationInBytes() <= p.maxMemoryBytes { + return current + } + + p.retiredGlobalDiagnostics = append(p.retiredGlobalDiagnostics, current.GetGlobalDiagnostics()...) + p.retiredSymbolCount.Add(uint64(current.SymbolCount)) + p.retiredTypeCount.Add(uint64(current.TypeCount)) + p.retiredInstantiationCount.Add(uint64(current.TotalInstantiationCount)) + p.retiredCheckerCount.Add(1) + + replacement, _ := checker.NewChecker(p.program, nil) + p.checkers[checkerIdx] = replacement + current = nil + runtime.GC() + return replacement +} + func noop() {} diff --git a/tsc/internal/compiler/checkerpool_test.go b/tsc/internal/compiler/checkerpool_test.go index 213da456891e9..bc8d3cbec84c9 100644 --- a/tsc/internal/compiler/checkerpool_test.go +++ b/tsc/internal/compiler/checkerpool_test.go @@ -1,12 +1,16 @@ package compiler import ( + "context" "slices" "testing" "github.com/microsoft/TypeScript/tsc/internal/ast" + "github.com/microsoft/TypeScript/tsc/internal/core" "github.com/microsoft/TypeScript/tsc/internal/module" + "github.com/microsoft/TypeScript/tsc/internal/tsoptions" "github.com/microsoft/TypeScript/tsc/internal/tspath" + "github.com/microsoft/TypeScript/tsc/internal/vfs/vfstest" ) func TestGetSourceFileForResolvedModuleUsesResolvedPath(t *testing.T) { @@ -120,6 +124,48 @@ func TestGetCheckerAssociationOrder(t *testing.T) { } } +func TestCheckerRecyclingPreservesDiagnosticsAndStatistics(t *testing.T) { + t.Parallel() + fs := vfstest.FromMap(map[string]any{ + "/tsconfig.json": `{ + "compilerOptions": { "noLib": true, "skipLibCheck": true }, + "files": ["/a.ts", "/skip.d.ts", "/b.ts", "/c.ts"] + }`, + "/a.ts": `const a: string = 1;`, + "/skip.d.ts": `declare const skipped: Missing;`, + "/b.ts": `const b: number = "b";`, + "/c.ts": `const c: boolean = 0;`, + }, tspath.CaseSensitive) + config, configDiagnostics := tsoptions.GetParsedCommandLineOfConfigFile("/tsconfig.json", nil, nil, tsoptions.NewDefaultParseConfigHost(fs), nil) + if len(configDiagnostics) != 0 { + t.Fatalf("unexpected config diagnostics: %v", configDiagnostics) + } + config.SetCompilerOptions(config.CompilerOptions().Clone()) + config.CompilerOptions().NoEmit = core.TSTrue + config.CompilerOptions().Checkers = new(1) + config.CompilerOptions().MaxMemory = new(1) + + program := NewProgram(ProgramOptions{ + Config: config, + Host: NewCompilerHost(fs, "/", nil, nil, nil), + }) + pool := program.compilerCheckerPool + pool.readMemoryAllocationInBytes = func() uint64 { + return pool.maxMemoryBytes + 1 + } + + diagnostics := program.GetSemanticDiagnostics(context.Background(), nil) + if len(diagnostics) != 3 { + t.Fatalf("got %d diagnostics, want 3", len(diagnostics)) + } + if got := program.RetiredCheckerCount(); got != 2 { + t.Fatalf("RetiredCheckerCount() = %d, want 2", got) + } + if program.TypeCount() == 0 || program.SymbolCount() == 0 { + t.Fatal("retired checker statistics were not preserved") + } +} + func TestGetCheckerAssociationWeights(t *testing.T) { t.Parallel() diff --git a/tsc/internal/compiler/fileInclude.go b/tsc/internal/compiler/fileInclude.go index e07123ad5e1ca..7a8afdfaa63ed 100644 --- a/tsc/internal/compiler/fileInclude.go +++ b/tsc/internal/compiler/fileInclude.go @@ -30,7 +30,7 @@ type FileIncludeReason struct { kind fileIncludeKind index int isDefaultLib bool - referencedFile *referencedFileData + referencedFile referencedFileData automaticTypeDirective *automaticTypeDirectiveFileData canonicalSourceFile tspath.PathKey } @@ -87,7 +87,7 @@ func (r *FileIncludeReason) isReferencedFile() bool { } func (r *FileIncludeReason) asReferencedFileData() *referencedFileData { - return r.referencedFile + return &r.referencedFile } func (r *FileIncludeReason) asAutomaticTypeDirectiveFileData() *automaticTypeDirectiveFileData { diff --git a/tsc/internal/compiler/fileloader.go b/tsc/internal/compiler/fileloader.go index b8bb4b7283cb4..58a88b2e3fb48 100644 --- a/tsc/internal/compiler/fileloader.go +++ b/tsc/internal/compiler/fileloader.go @@ -59,7 +59,7 @@ type fileLoader struct { contentMapperExtensions []string filesParser *filesParser - rootTasks []*parseTask + rootTasks []parseTask totalFileCount atomic.Int32 libFileCount atomic.Int32 @@ -186,7 +186,7 @@ func processAllProgramFiles( wg: core.NewWorkGroup(singleThreaded), maxDepth: maxNodeModuleJsDepth, }, - rootTasks: make([]*parseTask, 0, len(rootFiles)+len(compilerOptions.Lib)), + rootTasks: make([]parseTask, 0, len(rootFiles)+len(compilerOptions.Lib)+1), supportedExtensions: supportedExtensions, supportedExtensionsWithJsonIfResolveJsonModule: supportedExtensionsWithJsonIfResolveJsonModule, contentMapperExtensions: opts.Config.ContentMapperExtensions(), @@ -208,19 +208,19 @@ func processAllProgramFiles( defer opts.Tracing.Push(tracing.PhaseProgram, "processRootFiles", map[string]any{"count": len(rootFiles)}, false)() } for index, rootFile := range rootFiles { - loader.addRootFileTask(rootFile, opts.Config.RootFileNameForDiagnostic(index), nil, &FileIncludeReason{kind: fileIncludeKindRootFile, index: index}) + loader.addRootFileTask(rootFile, opts.Config.RootFileNameForDiagnostic(index), nil, FileIncludeReason{kind: fileIncludeKindRootFile, index: index}) } if len(rootFiles) > 0 && compilerOptions.NoLib.IsFalseOrUnknown() { if compilerOptions.Lib == nil { name := tsoptions.GetDefaultLibFileName(compilerOptions) libFile := loader.pathForLibFile(name) - loader.addRootTask(libFile.path, libFile, &FileIncludeReason{kind: fileIncludeKindLibFile, isDefaultLib: true}) + loader.addRootTask(libFile.path, libFile, FileIncludeReason{kind: fileIncludeKindLibFile, isDefaultLib: true}) } else { for index, lib := range compilerOptions.Lib { if name, ok := tsoptions.GetLibFileName(lib); ok { libFile := loader.pathForLibFile(name) - loader.addRootTask(libFile.path, libFile, &FileIncludeReason{kind: fileIncludeKindLibFile, index: index}) + loader.addRootTask(libFile.path, libFile, FileIncludeReason{kind: fileIncludeKindLibFile, index: index}) } // !!! error on unknown name } @@ -240,9 +240,9 @@ func (p *fileLoader) toPath(file tspath.RootedPath) tspath.PathKey { return p.caseSensitivity.PathKey(file) } -func (p *fileLoader) addRootTask(fileName tspath.RootedFilePath, libFile *LibFile, includeReason *FileIncludeReason) { +func (p *fileLoader) addRootTask(fileName tspath.RootedFilePath, libFile *LibFile, includeReason FileIncludeReason) { if p.opts.Config.CompilerOptions().AllowNonTsExtensions.IsTrue() || fileName.HasExtension() { - p.rootTasks = append(p.rootTasks, &parseTask{ + p.rootTasks = append(p.rootTasks, parseTask{ normalizedFilePath: fileName, path: p.caseSensitivity.PathKey(tspath.RootedPath(fileName)), libFile: libFile, @@ -251,36 +251,37 @@ func (p *fileLoader) addRootTask(fileName tspath.RootedFilePath, libFile *LibFil } } -func (p *fileLoader) addRootFileTask(fileName tspath.RootedFilePath, referenceText string, libFile *LibFile, includeReason *FileIncludeReason) { +func (p *fileLoader) addRootFileTask(fileName tspath.RootedFilePath, referenceText string, libFile *LibFile, includeReason FileIncludeReason) { resolvedFile, resolvedPath, diagnostic := p.getSourceFileFromReference(fileName, referenceText) - rootTask := &parseTask{ + p.rootTasks = append(p.rootTasks, parseTask{ normalizedFilePath: resolvedFile, path: resolvedPath, libFile: libFile, includeReason: includeReason, - } + }) if diagnostic != nil { + rootTask := &p.rootTasks[len(p.rootTasks)-1] rootTask.normalizedFilePath = fileName rootTask.path = p.caseSensitivity.PathKey(tspath.RootedPath(fileName)) rootTask.failedLookup = true rootTask.processingDiagnostics = []*processingDiagnostic{{ kind: processingDiagnosticKindExplainingFileInclude, explanation: &includeExplainingDiagnostic{ - diagnosticReason: includeReason, + diagnosticReason: &rootTask.includeReason, message: diagnostic.message, args: diagnostic.args, }, }} } - p.rootTasks = append(p.rootTasks, rootTask) } func (p *fileLoader) addAutomaticTypeDirectiveTasks() { containingFileName := p.opts.Config.BaseDirectory().ResolveFile(module.InferredTypesContainingFile) - p.rootTasks = append(p.rootTasks, &parseTask{ + p.rootTasks = append(p.rootTasks, parseTask{ normalizedFilePath: containingFileName, path: p.caseSensitivity.PathKey(tspath.RootedPath(containingFileName)), isForAutomaticTypeDirective: true, + elideOnDepth: false, }) } @@ -311,11 +312,10 @@ func (p *fileLoader) resolveAutomaticTypeDirectives(containingFileName tspath.Ro path: resolved.ResolvedPath, increaseDepth: resolved.IsExternalLibraryImport, elideOnDepth: false, - includeReason: &FileIncludeReason{ + includeReason: FileIncludeReason{ kind: fileIncludeKindAutomaticTypeDirectiveFile, automaticTypeDirective: &automaticTypeDirectiveFileData{name, resolved.PackageId}, }, - packageId: resolved.PackageId, }) } else { pDiagnostics = append(pDiagnostics, &processingDiagnostic{ @@ -725,12 +725,12 @@ func (p *fileLoader) getSourceFileFromReference( return "", "", &sourceFileFromReferenceDiagnostic{message: diagnostics.Could_not_resolve_the_path_0_with_the_extensions_Colon_1, args: []string{diagnosticFileName, "'" + strings.Join(core.Flatten(p.supportedExtensions), "', '") + "'"}} } -func (p *fileLoader) resolveTripleslashPathReference(moduleName string, containingFile tspath.RootedFilePath, index int) (*resolvedRef, *processingDiagnostic) { +func (p *fileLoader) resolveTripleslashPathReference(moduleName string, containingFile tspath.RootedFilePath, index int) (resolvedRef, *processingDiagnostic) { normalizedFileName := containingFile.Directory().ResolveFile(moduleName) containingPath := p.caseSensitivity.PathKey(tspath.RootedPath(containingFile)) - includeReason := &FileIncludeReason{ + includeReason := FileIncludeReason{ kind: fileIncludeKindReferenceFile, - referencedFile: &referencedFileData{ + referencedFile: referencedFileData{ file: containingPath, index: index, }, @@ -741,26 +741,26 @@ func (p *fileLoader) resolveTripleslashPathReference(moduleName string, containi moduleName, ) if diagnostic != nil { - return nil, &processingDiagnostic{ + return resolvedRef{}, &processingDiagnostic{ kind: processingDiagnosticKindExplainingFileInclude, explanation: &includeExplainingDiagnostic{ - diagnosticReason: includeReason, + diagnosticReason: &includeReason, message: diagnostic.message, args: diagnostic.args, }, } } if containingPath == resolvedPath { - return nil, &processingDiagnostic{ + return resolvedRef{}, &processingDiagnostic{ kind: processingDiagnosticKindExplainingFileInclude, explanation: &includeExplainingDiagnostic{ - diagnosticReason: includeReason, + diagnosticReason: &includeReason, message: diagnostics.A_file_cannot_have_a_reference_to_itself, }, } } - return &resolvedRef{ + return resolvedRef{ fileName: resolvedFileName, path: resolvedPath, includeReason: includeReason, @@ -788,9 +788,9 @@ func (p *fileLoader) resolveTypeReferenceDirectives(t *parseTask) { traceDone = p.tracing.Push(tracing.PhaseProgram, "processTypeReferenceDirective", map[string]any{"directive": ref.FileName, "hasResolved": resolved.IsResolved(), "refKind": int(fileIncludeKindTypeReferenceDirective), "refPath": string(t.path)}, false) } typeResolutionsInFile[module.ModeAwareCacheKey{Name: ref.FileName, Mode: resolutionMode}] = resolved - includeReason := &FileIncludeReason{ + includeReason := FileIncludeReason{ kind: fileIncludeKindTypeReferenceDirective, - referencedFile: &referencedFileData{ + referencedFile: referencedFileData{ file: t.path, index: index, }, @@ -806,10 +806,10 @@ func (p *fileLoader) resolveTypeReferenceDirectives(t *parseTask) { includeReason: includeReason, packageId: resolved.PackageId, }, nil) - } else { + } else if ref.ResolutionMode == core.ResolutionModeNone { t.processingDiagnostics = append(t.processingDiagnostics, &processingDiagnostic{ kind: processingDiagnosticKindUnknownReference, - reason: includeReason, + reason: &includeReason, }) } if traceDone != nil { @@ -939,9 +939,9 @@ func (p *fileLoader) resolveImportsAndModuleAugmentations(t *parseTask) { path: resolvedModule.ResolvedPath, increaseDepth: resolvedModule.IsExternalLibraryImport, elideOnDepth: isJsFileFromNodeModules, - includeReason: &FileIncludeReason{ + includeReason: FileIncludeReason{ kind: fileIncludeKindImport, - referencedFile: &referencedFileData{ + referencedFile: referencedFileData{ file: t.path, index: importIndex, synthetic: core.IfElse(importIndex < 0, entry, nil), diff --git a/tsc/internal/compiler/filesparser.go b/tsc/internal/compiler/filesparser.go index 5947c5fbb3a40..6153d32e840bd 100644 --- a/tsc/internal/compiler/filesparser.go +++ b/tsc/internal/compiler/filesparser.go @@ -5,6 +5,7 @@ import ( "slices" "strings" "sync" + "sync/atomic" "github.com/microsoft/TypeScript/tsc/internal/ast" "github.com/microsoft/TypeScript/tsc/internal/collections" @@ -22,13 +23,13 @@ type parseTask struct { file *ast.SourceFile libFile *LibFile redirectedParseTask *parseTask - subTasks []*parseTask + subTasks []parseTask loaded bool startedSubTasks bool isForAutomaticTypeDirective bool isContentMapperSupplemental bool failedLookup bool - includeReason *FileIncludeReason + includeReason FileIncludeReason packageId module.PackageId metadata ast.SourceFileMetaData @@ -36,7 +37,6 @@ type parseTask struct { resolutionsTrace []module.DiagAndArgs typeResolutionsInFile module.ModeAwareCache[*module.ResolvedTypeReferenceDirective] typeResolutionsTrace []module.DiagAndArgs - resolutionDiagnostics []*ast.Diagnostic processingDiagnostics []*processingDiagnostic importHelpersImportSpecifier *ast.StringLiteralNode jsxRuntimeImportSpecifier *jsxRuntimeImportSpecifier @@ -44,8 +44,7 @@ type parseTask struct { increaseDepth bool elideOnDepth bool - loadedTask *parseTask - allIncludeReasons []*FileIncludeReason + loadedTask *parseTask } func (t *parseTask) FileName() tspath.RootedFilePath { @@ -86,7 +85,7 @@ func (t *parseTask) load(loader *fileLoader) { t.processingDiagnostics = append(t.processingDiagnostics, &processingDiagnostic{ kind: processingDiagnosticKindExplainingFileInclude, explanation: &includeExplainingDiagnostic{ - diagnosticReason: t.includeReason, + diagnosticReason: &t.includeReason, message: diagnostics.File_0_is_a_JavaScript_file_Did_you_mean_to_enable_the_allowJs_option, args: []string{t.normalizedFilePath.AsString()}, }, @@ -95,7 +94,7 @@ func (t *parseTask) load(loader *fileLoader) { t.processingDiagnostics = append(t.processingDiagnostics, &processingDiagnostic{ kind: processingDiagnosticKindExplainingFileInclude, explanation: &includeExplainingDiagnostic{ - diagnosticReason: t.includeReason, + diagnosticReason: &t.includeReason, message: diagnostics.File_0_has_an_unsupported_extension_The_only_supported_extensions_are_1, args: []string{t.normalizedFilePath.AsString(), "'" + strings.Join(core.Flatten(loader.supportedExtensions), "', '") + "'"}, }, @@ -128,7 +127,7 @@ func (t *parseTask) load(loader *fileLoader) { if virtualFileName := file.VirtualFileName(); virtualFileName != "" { t.metadata.ImpliedNodeFormat = ast.GetImpliedNodeFormatForFile(virtualFileName, t.metadata.PackageJsonType) } - t.subTasks = make([]*parseTask, 0, len(file.ReferencedFiles)+len(file.Imports())+len(file.ModuleAugmentations)) + t.subTasks = make([]parseTask, 0, len(file.ReferencedFiles)+len(file.Imports())+len(file.ModuleAugmentations)+len(file.TypeReferenceDirectives)+len(file.LibReferenceDirectives)+len(file.SupplementalSourceFiles())) compilerOptions := loader.opts.Config.CompilerOptions() if !compilerOptions.NoResolve.IsTrue() && !loader.opts.SkipModuleResolution { @@ -138,17 +137,16 @@ func (t *parseTask) load(loader *fileLoader) { t.processingDiagnostics = append(t.processingDiagnostics, processingDiagnostic) continue } - t.addSubTask(*resolvedRef, nil) + t.addSubTask(resolvedRef, nil) } - loader.resolveTypeReferenceDirectives(t) } if compilerOptions.NoLib != core.TSTrue && !loader.opts.SkipModuleResolution { for index, lib := range file.LibReferenceDirectives { - includeReason := &FileIncludeReason{ + includeReason := FileIncludeReason{ kind: fileIncludeKindLibReferenceDirective, - referencedFile: &referencedFileData{ + referencedFile: referencedFileData{ file: t.path, index: index, }, @@ -163,7 +161,7 @@ func (t *parseTask) load(loader *fileLoader) { } else { t.processingDiagnostics = append(t.processingDiagnostics, &processingDiagnostic{ kind: processingDiagnosticKindUnknownReference, - reason: includeReason, + reason: &includeReason, }) } } @@ -171,12 +169,12 @@ func (t *parseTask) load(loader *fileLoader) { loader.resolveImportsAndModuleAugmentations(t) for _, supplemental := range file.SupplementalSourceFiles() { - t.subTasks = append(t.subTasks, &parseTask{ + t.subTasks = append(t.subTasks, parseTask{ normalizedFilePath: supplemental.FileName(), path: loader.caseSensitivity.PathKey(tspath.RootedPath(supplemental.FileName())), file: supplemental, isContentMapperSupplemental: true, - includeReason: &FileIncludeReason{ + includeReason: FileIncludeReason{ kind: fileIncludeKindContentMapperSupplemental, canonicalSourceFile: t.path, }, @@ -185,14 +183,14 @@ func (t *parseTask) load(loader *fileLoader) { } func (t *parseTask) redirect(loader *fileLoader, fileName tspath.RootedFilePath, path tspath.PathKey) { - t.redirectedParseTask = &parseTask{ + t.subTasks = []parseTask{{ normalizedFilePath: fileName, path: path, libFile: t.libFile, includeReason: t.includeReason, - } + }} // increaseDepth and elideOnDepth are not copied to redirects, otherwise their depth would be double counted. - t.subTasks = []*parseTask{t.redirectedParseTask} + t.redirectedParseTask = &t.subTasks[0] } func (t *parseTask) loadAutomaticTypeDirectives(loader *fileLoader) { @@ -203,6 +201,7 @@ func (t *parseTask) loadAutomaticTypeDirectives(loader *fileLoader) { t.typeResolutionsInFile = typeResolutionsInFile t.typeResolutionsTrace = typeResolutionsTrace t.processingDiagnostics = append(t.processingDiagnostics, pDiagnostics...) + t.subTasks = make([]parseTask, 0, len(toParseTypeRefs)) for _, typeResolution := range toParseTypeRefs { t.addSubTask(typeResolution, nil) } @@ -213,12 +212,12 @@ type resolvedRef struct { path tspath.PathKey increaseDepth bool elideOnDepth bool - includeReason *FileIncludeReason + includeReason FileIncludeReason packageId module.PackageId } func (t *parseTask) addSubTask(ref resolvedRef, libFile *LibFile) { - subTask := &parseTask{ + t.subTasks = append(t.subTasks, parseTask{ normalizedFilePath: ref.fileName, path: ref.path, libFile: libFile, @@ -226,8 +225,7 @@ func (t *parseTask) addSubTask(ref resolvedRef, libFile *LibFile) { elideOnDepth: ref.elideOnDepth, includeReason: ref.includeReason, packageId: ref.packageId, - } - t.subTasks = append(t.subTasks, subTask) + }) } type filesParser struct { @@ -246,91 +244,120 @@ var parseTaskDataPool = sync.Pool{ func getParseTaskData(task *parseTask) *parseTaskData { td := parseTaskDataPool.Get().(*parseTaskData) + td.firstTask = task td.tasks[task.normalizedFilePath] = task - td.lowestDepth = math.MaxInt + td.packageId = task.packageId + td.hasPackageId.Store(task.packageId.Name != "") + td.lowestDepth.Store(math.MaxInt32) + td.startedSubTasks = false return td } func putParseTaskData(td *parseTaskData) { + td.firstTask = nil + td.packageId = module.PackageId{} + td.hasPackageId.Store(false) + td.startedSubTasks = false clear(td.tasks) parseTaskDataPool.Put(td) } type parseTaskData struct { + firstTask *parseTask // map of tasks by file casing tasks map[tspath.RootedFilePath]*parseTask mu sync.Mutex - lowestDepth int + lowestDepth atomic.Int32 startedSubTasks bool + hasPackageId atomic.Bool packageId module.PackageId } -func (w *filesParser) parse(loader *fileLoader, tasks []*parseTask) { +func (w *filesParser) parse(loader *fileLoader, tasks []parseTask) { w.start(loader, tasks, 0) w.wg.RunAndWait() } -func (w *filesParser) start(loader *fileLoader, tasks []*parseTask, depth int) { - for i, task := range tasks { - if task.path == "" { - panic("parse task must have a path key: " + task.normalizedFilePath.AsString()) +func (w *filesParser) start(loader *fileLoader, tasks []parseTask, currentDepth int) { + for i := range tasks { + task := &tasks[i] + newCurrentDepth := currentDepth + if task.increaseDepth { + newCurrentDepth = currentDepth + 1 } - candidate := getParseTaskData(task) - data, loaded := w.taskDataByPath.LoadOrStore(task.path, candidate) - if loaded { - putParseTaskData(candidate) + elided := false + if task.elideOnDepth && newCurrentDepth > w.maxDepth { + elided = true + } + data, loaded := w.taskDataByPath.Load(task.path) + if !loaded { + candidate := getParseTaskData(task) + data, loaded = w.taskDataByPath.LoadOrStore(task.path, candidate) + if loaded { + putParseTaskData(candidate) + } + } + if loaded && + task.normalizedFilePath == data.firstTask.normalizedFilePath && + (task.packageId.Name == "" || data.hasPackageId.Load()) && + (elided || newCurrentDepth >= int(data.lowestDepth.Load())) { + task.loadedTask = data.firstTask + continue } w.wg.Queue(func() { - data.mu.Lock() - defer data.mu.Unlock() + w.loadTask(loader, data, task, newCurrentDepth, elided, loaded) + }) + } +} - startSubtasks := false - if loaded { - if existingTask, ok := data.tasks[task.normalizedFilePath]; ok { - tasks[i].loadedTask = existingTask - } else { - data.tasks[task.normalizedFilePath] = task - // This is new task for file name - so load subtasks if there was loading for any other casing - startSubtasks = data.startedSubTasks - } - } +func (w *filesParser) loadTask(loader *fileLoader, data *parseTaskData, task *parseTask, currentDepth int, elided bool, loaded bool) { + data.mu.Lock() + defer data.mu.Unlock() - // Propagate packageId to data if we have one and data doesn't yet - if data.packageId.Name == "" && task.packageId.Name != "" { - data.packageId = task.packageId - } + var startSubtasks bool + if loaded { + if existingTask, ok := data.tasks[task.normalizedFilePath]; ok { + task.loadedTask = existingTask + } else { + data.tasks[task.normalizedFilePath] = task + // This is new task for file name - so load subtasks if there was loading for any other casing + startSubtasks = data.startedSubTasks + } + } - currentDepth := core.IfElse(task.increaseDepth, depth+1, depth) - lowered := currentDepth < data.lowestDepth - if lowered { - // If we're seeing this task at a lower depth than before, - // reprocess its subtasks to ensure they are loaded. - data.lowestDepth = currentDepth - startSubtasks = true - data.startedSubTasks = true - } + // Propagate packageId to data if we have one and data doesn't yet + if data.packageId.Name == "" && task.packageId.Name != "" { + data.packageId = task.packageId + data.hasPackageId.Store(true) + } + lowered := !elided && currentDepth < int(data.lowestDepth.Load()) + if lowered { + // If we're seeing this task at a lower depth than before, + // reprocess its subtasks to ensure they are loaded. + data.lowestDepth.Store(int32(currentDepth)) + startSubtasks = true + data.startedSubTasks = true + } - if task.elideOnDepth && currentDepth > w.maxDepth { - return - } + if elided { + return + } - for _, taskByFileName := range data.tasks { - loadSubTasks := startSubtasks - if !taskByFileName.loaded { - taskByFileName.load(loader) - if taskByFileName.redirectedParseTask != nil { - // Always load redirected task - loadSubTasks = true - data.startedSubTasks = true - } - } - if loadSubTasks && (lowered || !taskByFileName.startedSubTasks) { - taskByFileName.startedSubTasks = true - w.start(loader, taskByFileName.subTasks, data.lowestDepth) - } + for _, taskByFileName := range data.tasks { + loadSubTasks := startSubtasks + if !taskByFileName.loaded { + taskByFileName.load(loader) + if taskByFileName.redirectedParseTask != nil { + // Always load redirected task + loadSubTasks = true + data.startedSubTasks = true } - }) + } + if loadSubTasks && (lowered || !taskByFileName.startedSubTasks) { + taskByFileName.startedSubTasks = true + w.start(loader, taskByFileName.subTasks, int(data.lowestDepth.Load())) + } } } @@ -373,8 +400,6 @@ func (w *filesParser) getProcessedFiles(loader *fileLoader) processedFiles { redirectTargetsMap = make(map[tspath.PathKey][]tspath.RootedFilePath) packageIdToSourceFile = make(map[module.PackageId]*ast.SourceFile) } - - var collectFiles func(tasks []*parseTask, seen map[*parseTaskData]tspath.RootedFilePath) // recordedDuplicates tracks, per task data, the set of file-name casings that // have already been recorded in duplicateSourceFiles. A file that is reached // from multiple import sites is walked once per site, but each distinct casing @@ -383,9 +408,11 @@ func (w *filesParser) getProcessedFiles(loader *fileLoader) processedFiles { // was acquired when the snapshot is disposed, leaving a dangling cache entry that // panics the next time it is referenced. var recordedDuplicates map[*parseTaskData]*collections.Set[tspath.RootedFilePath] - collectFiles = func(tasks []*parseTask, seen map[*parseTaskData]tspath.RootedFilePath) { - for _, task := range tasks { - includeReason := task.includeReason + var collectFiles func(tasks []parseTask, seen map[*parseTaskData]tspath.RootedFilePath) + collectFiles = func(tasks []parseTask, seen map[*parseTaskData]tspath.RootedFilePath) { + for i := range tasks { + task := &tasks[i] + includeReason := &task.includeReason // Exclude automatic type directive tasks from include reason processing, // as these are internal implementation details and should not contribute // to the reasons for including files. @@ -478,7 +505,7 @@ func (w *filesParser) getProcessedFiles(loader *fileLoader) processedFiles { target: packageIdFile.PathKey(), } filesByPath[task.path] = packageIdFile - if data.lowestDepth > 0 { + if data.lowestDepth.Load() > 0 { sourceFilesFoundSearchingNodeModules.Add(task.path) } continue @@ -543,7 +570,7 @@ func (w *filesParser) getProcessedFiles(loader *fileLoader) processedFiles { } importHelpersImportSpecifiers[path] = task.importHelpersImportSpecifier } - if data.lowestDepth > 0 { + if data.lowestDepth.Load() > 0 { sourceFilesFoundSearchingNodeModules.Add(path) } } diff --git a/tsc/internal/compiler/program.go b/tsc/internal/compiler/program.go index 5b8b8d86d4e32..1e85eda65484d 100644 --- a/tsc/internal/compiler/program.go +++ b/tsc/internal/compiler/program.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "maps" + "runtime" "slices" "strings" "sync" @@ -597,6 +598,9 @@ func (p *Program) extractUnresolvedImportsFromSourceFile(file *ast.SourceFile) [ } func (p *Program) SingleThreaded() bool { + if runtime.GOARCH == "wasm" { + return true + } return p.opts.SingleThreaded.DefaultIfUnknown(p.Options().SingleThreaded).IsTrue() } @@ -1753,6 +1757,9 @@ func (p *Program) SymbolCount() int { p.ForEachCheckerParallel(func(_ int, c *checker.Checker) { val.Add(c.SymbolCount) }) + if p.compilerCheckerPool != nil { + val.Add(uint32(p.compilerCheckerPool.retiredSymbolCount.Load())) + } return int(val.Load()) } @@ -1761,6 +1768,9 @@ func (p *Program) TypeCount() int { p.ForEachCheckerParallel(func(_ int, c *checker.Checker) { val.Add(c.TypeCount) }) + if p.compilerCheckerPool != nil { + val.Add(uint32(p.compilerCheckerPool.retiredTypeCount.Load())) + } return int(val.Load()) } @@ -1769,9 +1779,19 @@ func (p *Program) InstantiationCount() int { p.ForEachCheckerParallel(func(_ int, c *checker.Checker) { val.Add(c.TotalInstantiationCount) }) + if p.compilerCheckerPool != nil { + val.Add(uint32(p.compilerCheckerPool.retiredInstantiationCount.Load())) + } return int(val.Load()) } +func (p *Program) RetiredCheckerCount() int { + if p.compilerCheckerPool == nil { + return 0 + } + return int(p.compilerCheckerPool.retiredCheckerCount.Load()) +} + func (p *Program) Program() *Program { return p } diff --git a/tsc/internal/contentmapper/hostimpl.go b/tsc/internal/contentmapper/hostimpl.go index 0aec1f879280e..ac066791900d2 100644 --- a/tsc/internal/contentmapper/hostimpl.go +++ b/tsc/internal/contentmapper/hostimpl.go @@ -492,71 +492,7 @@ func NewHost(ctx context.Context, spawner Spawner, diagnosticLocale locale.Local func NewHostWithOptions(ctx context.Context, spawner Spawner, diagnosticLocale locale.Locale, options HostOptions) Host { logger := options.Logger timing := &timingCollector{mappers: make(map[string]*mapperTimingCollector)} - return newWithDial(ctx, diagnosticLocale, timing, func(ctx context.Context, mapper *Mapper, diagnosticLocale locale.Locale) (ipc.Conn, io.Closer, PositionEncoding, string, error) { - if len(mapper.Exec) == 0 { - return nil, nil, "", "", fmt.Errorf("content mapper %q declares no command to run", mapper.Package) - } - mapperTiming := timing.mapper(mapper.Identity()) - diagnosticName := mapper.DiagnosticName() - spawnStart := time.Now() - var stderr io.Writer = io.Discard - var stderrLog *stderrLogger - if logger != nil { - stderrLog = &stderrLogger{mapperName: diagnosticName, logger: logger} - stderr = stderrLog - } - rwc, err := spawner.Spawn(mapper.Exec, mapper.PackageDirectory.AsString(), stderr) - mapperTiming.spawn.record(spawnStart) - if err != nil { - return nil, nil, "", "", &InitializeError{Kind: InitializeErrorKindProcessStart, MapperName: diagnosticName, Command: mapper.Exec[0], Detail: err.Error()} - } - if stderrLog != nil { - rwc = &loggedProcess{ReadWriteCloser: rwc, stderr: stderrLog} - } - rwc = &closeOnceReadWriteCloser{ReadWriteCloser: rwc} - protocol := ipc.Protocol(ipc.NewJSONRPCProtocol(rwc)) - if logger != nil { - protocol = &loggingProtocol{Protocol: protocol, mapperName: diagnosticName, logger: logger} - } - conn := ipc.NewAsyncConnWithProtocol(rwc, protocol, rejectHandler{}) - go func() { - _ = conn.Run(ctx) - _ = rwc.Close() - }() - initializeCtx, cancel := context.WithTimeout(ctx, initializeTimeout) - initializeStart := mapperTiming.startRequest() - positionEncoding, diagnosticSource, err := handshake(initializeCtx, conn, diagnosticLocale) - mapperTiming.finishRequest(&mapperTiming.initialize, initializeStart) - initializeCtxErr := initializeCtx.Err() - cancel() - if err != nil { - var exitCode int - var exited bool - if exitState, ok := rwc.(processExitState); ok { - exitCode, exited = exitState.ExitCode() - } - _ = rwc.Close() - if initializeError, ok := errors.AsType[*InitializeError](err); ok { - initializeError.MapperName = diagnosticName - return nil, nil, "", "", initializeError - } - if exited { - return nil, nil, "", "", &InitializeError{Kind: InitializeErrorKindProcessExit, MapperName: diagnosticName, ExitCode: exitCode} - } - if initializeCtxErr != nil || errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { - return nil, nil, "", "", &InitializeError{Kind: InitializeErrorKindNoResponse, MapperName: diagnosticName, TimeoutSeconds: initializeTimeoutSeconds} - } - return nil, nil, "", "", &InitializeError{Kind: InitializeErrorKindRequest, MapperName: diagnosticName, Detail: err.Error()} - } - return conn, rwc, positionEncoding, diagnosticSource, nil - }) -} - -func newWithDial(ctx context.Context, diagnosticLocale locale.Locale, timing *timingCollector, dial dialFunc) *host { - hostCtx, cancel := context.WithCancel(ctx) - h := &host{ctx: hostCtx, cancel: cancel, dial: dial, timing: timing, diagnosticLocale: diagnosticLocale, conns: make(map[string]*mapperConn), projects: make(map[string]*projectEntry), projectLeases: make(map[string]*projectLease)} - h.stop = context.AfterFunc(ctx, func() { _ = h.Close() }) - return h + return newWithDial(ctx, diagnosticLocale, timing, dialMapperProcess(logger, spawner, timing)) } func (h *host) Timings() Timings { diff --git a/tsc/internal/contentmapper/hostimpl_notwasm.go b/tsc/internal/contentmapper/hostimpl_notwasm.go new file mode 100644 index 0000000000000..202e71889504c --- /dev/null +++ b/tsc/internal/contentmapper/hostimpl_notwasm.go @@ -0,0 +1,82 @@ +//go:build !wasm + +package contentmapper + +import ( + "context" + "errors" + "fmt" + "io" + "time" + + "github.com/microsoft/TypeScript/tsc/internal/ipc" + "github.com/microsoft/TypeScript/tsc/internal/locale" +) + +func dialMapperProcess(logger Logger, spawner Spawner, timing *timingCollector) dialFunc { + return func(ctx context.Context, mapper *Mapper, diagnosticLocale locale.Locale) (ipc.Conn, io.Closer, PositionEncoding, string, error) { + if len(mapper.Exec) == 0 { + return nil, nil, "", "", fmt.Errorf("content mapper %q declares no command to run", mapper.Package) + } + mapperTiming := timing.mapper(mapper.Identity()) + diagnosticName := mapper.DiagnosticName() + spawnStart := time.Now() + var stderr io.Writer = io.Discard + var stderrLog *stderrLogger + if logger != nil { + stderrLog = &stderrLogger{mapperName: diagnosticName, logger: logger} + stderr = stderrLog + } + rwc, err := spawner.Spawn(mapper.Exec, mapper.PackageDirectory.AsString(), stderr) + mapperTiming.spawn.record(spawnStart) + if err != nil { + return nil, nil, "", "", &InitializeError{Kind: InitializeErrorKindProcessStart, MapperName: diagnosticName, Command: mapper.Exec[0], Detail: err.Error()} + } + if stderrLog != nil { + rwc = &loggedProcess{ReadWriteCloser: rwc, stderr: stderrLog} + } + rwc = &closeOnceReadWriteCloser{ReadWriteCloser: rwc} + protocol := ipc.Protocol(ipc.NewJSONRPCProtocol(rwc)) + if logger != nil { + protocol = &loggingProtocol{Protocol: protocol, mapperName: diagnosticName, logger: logger} + } + conn := ipc.NewAsyncConnWithProtocol(rwc, protocol, rejectHandler{}) + go func() { + _ = conn.Run(ctx) + _ = rwc.Close() + }() + initializeCtx, cancel := context.WithTimeout(ctx, initializeTimeout) + initializeStart := mapperTiming.startRequest() + positionEncoding, diagnosticSource, err := handshake(initializeCtx, conn, diagnosticLocale) + mapperTiming.finishRequest(&mapperTiming.initialize, initializeStart) + initializeCtxErr := initializeCtx.Err() + cancel() + if err != nil { + var exitCode int + var exited bool + if exitState, ok := rwc.(processExitState); ok { + exitCode, exited = exitState.ExitCode() + } + _ = rwc.Close() + if initializeError, ok := errors.AsType[*InitializeError](err); ok { + initializeError.MapperName = diagnosticName + return nil, nil, "", "", initializeError + } + if exited { + return nil, nil, "", "", &InitializeError{Kind: InitializeErrorKindProcessExit, MapperName: diagnosticName, ExitCode: exitCode} + } + if initializeCtxErr != nil || errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { + return nil, nil, "", "", &InitializeError{Kind: InitializeErrorKindNoResponse, MapperName: diagnosticName, TimeoutSeconds: initializeTimeoutSeconds} + } + return nil, nil, "", "", &InitializeError{Kind: InitializeErrorKindRequest, MapperName: diagnosticName, Detail: err.Error()} + } + return conn, rwc, positionEncoding, diagnosticSource, nil + } +} + +func newWithDial(ctx context.Context, diagnosticLocale locale.Locale, timing *timingCollector, dial dialFunc) *host { + hostCtx, cancel := context.WithCancel(ctx) + h := &host{ctx: hostCtx, cancel: cancel, dial: dial, timing: timing, diagnosticLocale: diagnosticLocale, conns: make(map[string]*mapperConn), projects: make(map[string]*projectEntry), projectLeases: make(map[string]*projectLease)} + h.stop = context.AfterFunc(ctx, func() { _ = h.Close() }) + return h +} diff --git a/tsc/internal/contentmapper/hostimpl_wasm.go b/tsc/internal/contentmapper/hostimpl_wasm.go new file mode 100644 index 0000000000000..9bdb48c89e621 --- /dev/null +++ b/tsc/internal/contentmapper/hostimpl_wasm.go @@ -0,0 +1,35 @@ +//go:build wasm + +package contentmapper + +import ( + "context" + "fmt" + "io" + + "github.com/microsoft/TypeScript/tsc/internal/ipc" + "github.com/microsoft/TypeScript/tsc/internal/locale" +) + +func dialMapperProcess(_ Logger, _ Spawner, _ *timingCollector) dialFunc { + return func(_ context.Context, mapper *Mapper, _ locale.Locale) (ipc.Conn, io.Closer, PositionEncoding, string, error) { + if len(mapper.Exec) == 0 { + return nil, nil, "", "", fmt.Errorf("content mapper %q declares no command to run", mapper.Package) + } + return nil, nil, "", "", &InitializeError{Kind: InitializeErrorKindProcessStart, MapperName: mapper.DiagnosticName(), Command: mapper.Exec[0], Detail: "spawn not supported on wasm"} + } +} + +func newWithDial(ctx context.Context, diagnosticLocale locale.Locale, timing *timingCollector, dial dialFunc) *host { + return &host{ + ctx: ctx, + cancel: func() {}, + stop: func() bool { return false }, + dial: dial, + timing: timing, + diagnosticLocale: diagnosticLocale, + conns: make(map[string]*mapperConn), + projects: make(map[string]*projectEntry), + projectLeases: make(map[string]*projectLease), + } +} diff --git a/tsc/internal/execute/build/orchestrator.go b/tsc/internal/execute/build/orchestrator.go index 664e47844616e..23d96c74fae22 100644 --- a/tsc/internal/execute/build/orchestrator.go +++ b/tsc/internal/execute/build/orchestrator.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io" + "runtime" "slices" "strings" "sync/atomic" @@ -889,18 +890,27 @@ func (o *Orchestrator) buildOrCleanOrder(order []*BuildTask) *OrchestratorResult scheduleOrder = o.computeScheduleOrder(order) } // Builders pick up selected projects in dependency-depth order; reporting preserves the selected build order. - reported := make(chan struct{}) - go func() { - defer close(reported) + if o.opts.Command.CompilerOptions.SingleThreaded.IsTrue() || runtime.GOARCH == "wasm" { + o.rangeTasks(scheduleOrder, func(path tspath.PathKey, task *BuildTask) { + o.buildOrCleanProject(task, path) + }) for _, task := range order { - <-task.built task.report(o, task.path, buildResult) } - }() - o.rangeTasks(scheduleOrder, func(path tspath.PathKey, task *BuildTask) { - o.buildOrCleanProject(task, path) - }) - <-reported + } else { + reported := make(chan struct{}) + go func() { + defer close(reported) + for _, task := range order { + <-task.built + task.report(o, task.path, buildResult) + } + }() + o.rangeTasks(scheduleOrder, func(path tspath.PathKey, task *BuildTask) { + o.buildOrCleanProject(task, path) + }) + <-reported + } } else { // Circularity errors prevent any project from being built buildResult.Result.Status = tsc.ExitStatusProjectReferenceCycle_OutputsSkipped @@ -920,7 +930,7 @@ func (o *Orchestrator) rangeTask(f func(path tspath.PathKey, task *BuildTask)) { func (o *Orchestrator) rangeTasks(order []*BuildTask, f func(path tspath.PathKey, task *BuildTask)) { numRoutines := 4 - if o.opts.Command.CompilerOptions.SingleThreaded.IsTrue() { + if o.opts.Command.CompilerOptions.SingleThreaded.IsTrue() || runtime.GOARCH == "wasm" { numRoutines = 1 } else if builders := o.opts.Command.BuildOptions.Builders; builders != nil { numRoutines = *builders diff --git a/tsc/internal/execute/tsc.go b/tsc/internal/execute/tsc.go index c41e0bb25c921..f32dc621b517d 100644 --- a/tsc/internal/execute/tsc.go +++ b/tsc/internal/execute/tsc.go @@ -317,6 +317,11 @@ func performIncrementalCompilation( tr := startTracingIfNeeded(sys, config, testing) + if testing == nil && config.CompilerOptions().MaxMemory == nil { + core.SuspendGC() + } + parser.EnableLazyDts(testing == nil && (config.CompilerOptions().SkipLibCheck.IsTrue() || config.CompilerOptions().NoCheck.IsTrue())) + defer parser.EnableLazyDts(false) parseStart := sys.Now() program := compiler.NewProgram(compiler.ProgramOptions{ Config: config, @@ -378,6 +383,11 @@ func performCompilation( tr := startTracingIfNeeded(sys, config, testing) + if testing == nil && config.CompilerOptions().MaxMemory == nil { + core.SuspendGC() + } + parser.EnableLazyDts(testing == nil && (config.CompilerOptions().SkipLibCheck.IsTrue() || config.CompilerOptions().NoCheck.IsTrue())) + defer parser.EnableLazyDts(false) parseStart := sys.Now() program := compiler.NewProgram(compiler.ProgramOptions{ Config: config, diff --git a/tsc/internal/execute/tsc/emit.go b/tsc/internal/execute/tsc/emit.go index 4f30b1617ada1..7521d67055e4b 100644 --- a/tsc/internal/execute/tsc/emit.go +++ b/tsc/internal/execute/tsc/emit.go @@ -54,11 +54,7 @@ func EmitAndReportStatistics(input EmitInput) (CompileAndEmitResult, *Statistics if input.Config.CompilerOptions().Diagnostics.IsTrue() || input.Config.CompilerOptions().ExtendedDiagnostics.IsTrue() { var memStats runtime.MemStats - // GC must be called twice to allow things to settle. - runtime.GC() - runtime.GC() runtime.ReadMemStats(&memStats) - statistics = statisticsFromProgram(input, &memStats) statistics.Report(input.Writer, input.Testing) } diff --git a/tsc/internal/execute/tsc/statistics.go b/tsc/internal/execute/tsc/statistics.go index ce2ac6d5d4bdc..55f0b0dd27d98 100644 --- a/tsc/internal/execute/tsc/statistics.go +++ b/tsc/internal/execute/tsc/statistics.go @@ -64,6 +64,7 @@ type Statistics struct { symbols int types int instantiations int + retiredCheckers int memoryUsed uint64 memoryAllocs uint64 compileTimes *CompileTimes @@ -71,15 +72,16 @@ type Statistics struct { func statisticsFromProgram(input EmitInput, memStats *runtime.MemStats) *Statistics { return &Statistics{ - files: len(input.Program.SourceFiles()), - lines: input.Program.LineCount(), - identifiers: input.Program.IdentifierCount(), - symbols: input.Program.SymbolCount(), - types: input.Program.TypeCount(), - instantiations: input.Program.InstantiationCount(), - memoryUsed: memStats.Alloc, - memoryAllocs: memStats.Mallocs, - compileTimes: input.CompileTimes, + files: len(input.Program.SourceFiles()), + lines: input.Program.LineCount(), + identifiers: input.Program.IdentifierCount(), + symbols: input.Program.SymbolCount(), + types: input.Program.TypeCount(), + instantiations: input.Program.InstantiationCount(), + retiredCheckers: input.Program.RetiredCheckerCount(), + memoryUsed: memStats.Alloc, + memoryAllocs: memStats.Mallocs, + compileTimes: input.CompileTimes, } } @@ -103,6 +105,9 @@ func (s *Statistics) Report(w io.Writer, testing CommandLineTesting) { table.add(prefix+"Symbols", s.symbols) table.add(prefix+"Types", s.types) table.add(prefix+"Instantiations", s.instantiations) + if s.retiredCheckers != 0 { + table.add(prefix+"Checkers retired", s.retiredCheckers) + } table.add(prefix+"Memory used", fmt.Sprintf("%vK", s.memoryUsed/1024)) table.add(prefix+"Memory allocs", strconv.FormatUint(s.memoryAllocs, 10)) if s.compileTimes.ConfigTime != 0 { diff --git a/tsc/internal/fswatch/debounce.go b/tsc/internal/fswatch/debounce.go index 71428f2571a3c..8e6dc98eaf912 100644 --- a/tsc/internal/fswatch/debounce.go +++ b/tsc/internal/fswatch/debounce.go @@ -1,6 +1,7 @@ package fswatch import ( + "runtime" "sync" "time" ) @@ -42,7 +43,9 @@ func newDebounce() *debounce { d := &debounce{ callbacks: make(map[any]func()), } - go d.loop() + if runtime.GOARCH != "wasm" { + go d.loop() + } return d } diff --git a/tsc/internal/packagejson/exportsorimports.go b/tsc/internal/packagejson/exportsorimports.go index 7add2e9f4aaac..85125e0bbff11 100644 --- a/tsc/internal/packagejson/exportsorimports.go +++ b/tsc/internal/packagejson/exportsorimports.go @@ -23,7 +23,7 @@ type ExportsOrImports struct { var _ json.UnmarshalerFrom = (*ExportsOrImports)(nil) func (e *ExportsOrImports) UnmarshalJSONFrom(dec *json.Decoder) error { - return e.JSONValue.unmarshalJSONValueFrom[ExportsOrImports](dec) + return unmarshalJSONValueFrom[ExportsOrImports](&e.JSONValue, dec) } func (e ExportsOrImports) AsObject() *collections.OrderedMap[string, ExportsOrImports] { diff --git a/tsc/internal/packagejson/jsonvalue.go b/tsc/internal/packagejson/jsonvalue.go index ef88e9b07c426..700e2bc510684 100644 --- a/tsc/internal/packagejson/jsonvalue.go +++ b/tsc/internal/packagejson/jsonvalue.go @@ -86,10 +86,10 @@ func (v JSONValue) AsString() string { var _ json.UnmarshalerFrom = (*JSONValue)(nil) func (v *JSONValue) UnmarshalJSONFrom(dec *json.Decoder) error { - return v.unmarshalJSONValueFrom[JSONValue](dec) + return unmarshalJSONValueFrom[JSONValue](v, dec) } -func (v *JSONValue) unmarshalJSONValueFrom[T any](dec *json.Decoder) error { +func unmarshalJSONValueFrom[T any](v *JSONValue, dec *json.Decoder) error { switch dec.PeekKind() { case 'n': // json.Null.Kind() if _, err := dec.ReadToken(); err != nil { From f4687409dd332d9bca6d57199655fcf225c4019b Mon Sep 17 00:00:00 2001 From: Steven Chong <25894545+teamchong@users.noreply.github.com> Date: Wed, 28 Oct 2026 06:30:22 -0400 Subject: [PATCH 08/10] perf(wasm,checker): cache static .d.ts ASTs across runs and speed up lazy RHS/tuples - Cache parsed and bound static .d.ts SourceFiles across tsrs_run invocations in wasm_wasip1 and rewind the bump-allocator heap to a post-init watermark (MarkHeap/ResetHeap) after each run. - Scope TinyGo sync.Pool, sync.Once, and sync.Map state to the heap watermark epoch and restore forced lazy member/type-alias state so cached .d.ts ASTs hold no dangling post-watermark pointers across runs. - Defer TypeAliasDeclaration RHS parsing/binding in .d.ts files under skipLibCheck/noCheck (raising lazyListMaxText to 256 MiB so large generated .d.ts files like Doom's 53.5 MB module.d.ts skip unused type alias bodies). - Add a direct literal-index fast path for tuple indexed-access types and small Set/BSet mapped-tuple fast paths in the checker. - Raise TS2589 tailCount and instantiationDepth caps on wasm to match recursive type-level workloads. - Run wasm-opt -O3 with bulk-memory and SIMD enabled in build-wasm-tinygo.mjs when available. --- tools/scripts/build-wasm-tinygo.mjs | 27 ++ .../tinygo-overlay/src/runtime/gc_leaking.go | 52 +- .../testdata/tinygo-overlay/src/sync/map.go | 215 +++++++++ .../testdata/tinygo-overlay/src/sync/once.go | 17 + .../testdata/tinygo-overlay/src/sync/pool.go | 54 +++ tsc/cmd/tsc/wasm_wasip1.go | 138 +++++- tsc/internal/ast/ast.go | 12 +- tsc/internal/ast/lazylist.go | 191 +++++++- tsc/internal/ast/utilities.go | 37 +- tsc/internal/binder/binder.go | 47 ++ tsc/internal/checker/checker.go | 105 +++- tsc/internal/compiler/filesparser.go | 10 + tsc/internal/compiler/host.go | 98 ++++ tsc/internal/core/core.go | 8 + tsc/internal/execute/tsc.go | 7 +- tsc/internal/execute/tsc/compile.go | 6 +- tsc/internal/parser/parser.go | 448 +++++++++++++++++- .../printer/singlelinestringwriter.go | 8 + 18 files changed, 1421 insertions(+), 59 deletions(-) create mode 100644 tools/scripts/testdata/tinygo-overlay/src/sync/map.go create mode 100644 tools/scripts/testdata/tinygo-overlay/src/sync/once.go create mode 100644 tools/scripts/testdata/tinygo-overlay/src/sync/pool.go diff --git a/tools/scripts/build-wasm-tinygo.mjs b/tools/scripts/build-wasm-tinygo.mjs index d2ed0f1156ff8..9a556269c14a6 100755 --- a/tools/scripts/build-wasm-tinygo.mjs +++ b/tools/scripts/build-wasm-tinygo.mjs @@ -142,6 +142,33 @@ execFileSync( stdio: "inherit", }, ); +function findWasmOpt() { + const res = spawnSync("wasm-opt", ["--version"], { encoding: "utf8" }); + if (res.status === 0) return "wasm-opt"; + for (const candidate of ["/opt/homebrew/bin/wasm-opt", "/usr/local/bin/wasm-opt"]) { + if (fs.existsSync(candidate)) return candidate; + } + return null; +} + +const wasmOptBin = findWasmOpt(); +if (wasmOptBin) { + execFileSync( + wasmOptBin, + [ + "-O3", + "--enable-bulk-memory", + "--enable-sign-ext", + "--enable-mutable-globals", + "--enable-nontrapping-float-to-int", + "--enable-simd", + outPath, + "-o", + outPath, + ], + { stdio: "inherit" }, + ); +} const stat = fs.statSync(outPath); console.log(`Built ${outPath} (${(stat.size / (1024 * 1024)).toFixed(2)} MiB) in ${((Date.now() - t0) / 1000).toFixed(1)}s`); diff --git a/tools/scripts/testdata/tinygo-overlay/src/runtime/gc_leaking.go b/tools/scripts/testdata/tinygo-overlay/src/runtime/gc_leaking.go index 7e35ef5cfe4d8..98e3458fb9527 100644 --- a/tools/scripts/testdata/tinygo-overlay/src/runtime/gc_leaking.go +++ b/tools/scripts/testdata/tinygo-overlay/src/runtime/gc_leaking.go @@ -20,9 +20,12 @@ func scanCurrentStack() {} // Ever-incrementing pointer: no memory is freed. var ( - heapptr uintptr - small4Ptr uintptr - small4End uintptr + heapptr uintptr + small4Ptr uintptr + small4End uintptr + heapWatermark uintptr + liveAllocWatermark uint64 + heapPoolEpoch uint32 = 1 ) // Total amount allocated for runtime.MemStats @@ -190,10 +193,51 @@ func SetFinalizer(obj interface{}, finalizer interface{}) { func initHeap() { heapptr = heapStart } - // setHeapEnd sets a new (larger) heapEnd pointer. func setHeapEnd(newHeapEnd uintptr) { // This "heap" is so simple that simply assigning a new value is good // enough. heapEnd = newHeapEnd } + +//go:linkname PoolEpoch runtime.PoolEpoch +func PoolEpoch() uint32 { + return heapPoolEpoch +} + +//go:linkname MarkHeap runtime.MarkHeap +func MarkHeap() { + gcLock.Lock() + small4Ptr = 0 + small4End = 0 + freeLists = [257]unsafe.Pointer{} + largeFreePtr = [256]unsafe.Pointer{} + largeFreeSize = [256]uintptr{} + heapWatermark = heapptr + liveAllocWatermark = gcLiveAlloc + if heapPoolEpoch == 1 { + heapPoolEpoch = 2 + } else { + heapPoolEpoch++ + } + gcLock.Unlock() +} + +//go:linkname ResetHeap runtime.ResetHeap +func ResetHeap() { + gcLock.Lock() + if heapWatermark != 0 && heapptr > heapWatermark { + memzero(unsafe.Pointer(heapWatermark), heapptr-heapWatermark) + heapptr = heapWatermark + small4Ptr = 0 + small4End = 0 + gcLiveAlloc = liveAllocWatermark + freeLists = [257]unsafe.Pointer{} + largeFreePtr = [256]unsafe.Pointer{} + largeFreeSize = [256]uintptr{} + } + if heapPoolEpoch >= 2 { + heapPoolEpoch++ + } + gcLock.Unlock() +} diff --git a/tools/scripts/testdata/tinygo-overlay/src/sync/map.go b/tools/scripts/testdata/tinygo-overlay/src/sync/map.go new file mode 100644 index 0000000000000..99fa5c9ec9229 --- /dev/null +++ b/tools/scripts/testdata/tinygo-overlay/src/sync/map.go @@ -0,0 +1,215 @@ +package sync + +import ( + "internal/task" +) + +// This file implements just enough of sync.Map to get packages to compile. It +// is no more efficient than a map with a lock. + +type Map struct { + lock task.PMutex + m map[interface{}]interface{} + postMark map[interface{}]interface{} + epoch uint32 +} + +func (m *Map) activeMap() map[interface{}]interface{} { + ep := poolEpoch() + if ep <= 1 { + if m.m == nil { + m.m = make(map[interface{}]interface{}) + } + return m.m + } + if m.epoch != ep || m.postMark == nil { + m.postMark = make(map[interface{}]interface{}) + m.epoch = ep + } + return m.postMark +} + +func (m *Map) Delete(key interface{}) { + m.lock.Lock() + defer m.lock.Unlock() + ep := poolEpoch() + if ep <= 1 { + delete(m.m, key) + } else if m.epoch == ep && m.postMark != nil { + delete(m.postMark, key) + } +} + +func (m *Map) Load(key interface{}) (value interface{}, ok bool) { + m.lock.Lock() + defer m.lock.Unlock() + if m.m != nil { + if value, ok = m.m[key]; ok { + return value, true + } + } + ep := poolEpoch() + if ep > 1 && m.epoch == ep && m.postMark != nil { + value, ok = m.postMark[key] + } + return +} + +func (m *Map) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool) { + m.lock.Lock() + defer m.lock.Unlock() + if m.m != nil { + if existing, ok := m.m[key]; ok { + return existing, true + } + } + target := m.activeMap() + if existing, ok := target[key]; ok { + return existing, true + } + target[key] = value + return value, false +} + +func (m *Map) LoadAndDelete(key interface{}) (value interface{}, loaded bool) { + m.lock.Lock() + defer m.lock.Unlock() + ep := poolEpoch() + if ep <= 1 { + value, ok := m.m[key] + if !ok { + return nil, false + } + delete(m.m, key) + return value, true + } + if m.epoch == ep && m.postMark != nil { + if value, ok := m.postMark[key]; ok { + delete(m.postMark, key) + return value, true + } + } + return nil, false +} + +func (m *Map) Store(key, value interface{}) { + m.lock.Lock() + defer m.lock.Unlock() + target := m.activeMap() + target[key] = value +} + +// Range calls f for each key and value in the map. If f returns false, the iteration stops. +func (m *Map) Range(f func(key, value interface{}) bool) { + m.lock.Lock() + keys := make([]interface{}, 0, len(m.m)) + for k := range m.m { + keys = append(keys, k) + } + ep := poolEpoch() + if ep > 1 && m.epoch == ep { + for k := range m.postMark { + if _, ok := m.m[k]; !ok { + keys = append(keys, k) + } + } + } + m.lock.Unlock() + + for _, k := range keys { + if v, ok := m.Load(k); ok { + if !f(k, v) { + break + } + } + } +} + +func (m *Map) Clear() { + m.lock.Lock() + defer m.lock.Unlock() + ep := poolEpoch() + if ep <= 1 { + clear(m.m) + } else { + m.postMark = nil + m.epoch = ep + } +} + +// Swap replaces the value for the given key, and returns the old value if any. +func (m *Map) Swap(key, value any) (previous any, loaded bool) { + m.lock.Lock() + defer m.lock.Unlock() + if m.m != nil { + if prev, ok := m.m[key]; ok { + previous, loaded = prev, true + } + } + target := m.activeMap() + if prev, ok := target[key]; ok { + previous, loaded = prev, true + } + target[key] = value + return +} + +// CompareAndSwap swaps the old and new values for an existing key if the value +// stored in the map is equal to old. +func (m *Map) CompareAndSwap(key, old, new any) (swapped bool) { + m.lock.Lock() + defer m.lock.Unlock() + ep := poolEpoch() + if ep <= 1 { + if m.m == nil { + return false + } + value, ok := m.m[key] + if !ok || value != old { + return false + } + m.m[key] = new + return true + } + var value any + var ok bool + if m.epoch == ep && m.postMark != nil { + value, ok = m.postMark[key] + } + if !ok && m.m != nil { + value, ok = m.m[key] + } + if !ok || value != old { + return false + } + target := m.activeMap() + target[key] = new + return true +} + +// CompareAndDelete deletes the entry for key if its value is equal to old. +func (m *Map) CompareAndDelete(key, old any) (deleted bool) { + m.lock.Lock() + defer m.lock.Unlock() + ep := poolEpoch() + if ep <= 1 { + if m.m == nil { + return false + } + value, ok := m.m[key] + if !ok || value != old { + return false + } + delete(m.m, key) + return true + } + if m.epoch != ep || m.postMark == nil { + return false + } + value, ok := m.postMark[key] + if !ok || value != old { + return false + } + delete(m.postMark, key) + return true +} diff --git a/tools/scripts/testdata/tinygo-overlay/src/sync/once.go b/tools/scripts/testdata/tinygo-overlay/src/sync/once.go new file mode 100644 index 0000000000000..598e9325c09c4 --- /dev/null +++ b/tools/scripts/testdata/tinygo-overlay/src/sync/once.go @@ -0,0 +1,17 @@ +package sync + +type Once struct { + epoch uint32 + m Mutex +} + +func (o *Once) Do(f func()) { + o.m.Lock() + defer o.m.Unlock() + ep := poolEpoch() + if o.epoch == 1 || o.epoch == ep { + return + } + o.epoch = ep + f() +} diff --git a/tools/scripts/testdata/tinygo-overlay/src/sync/pool.go b/tools/scripts/testdata/tinygo-overlay/src/sync/pool.go new file mode 100644 index 0000000000000..539b8d0d3e836 --- /dev/null +++ b/tools/scripts/testdata/tinygo-overlay/src/sync/pool.go @@ -0,0 +1,54 @@ +package sync + +import ( + "internal/task" + _ "unsafe" +) + +//go:linkname poolEpoch runtime.PoolEpoch +func poolEpoch() uint32 + +// Pool is a very simple implementation of sync.Pool. +type Pool struct { + lock task.PMutex + New func() interface{} + items []interface{} + epoch uint32 +} + +// Get returns an item in the pool, or the value of calling Pool.New() if there +// are no items. +func (p *Pool) Get() interface{} { + p.lock.Lock() + ep := poolEpoch() + if p.epoch != ep { + p.items = nil + p.epoch = ep + } + if len(p.items) > 0 { + x := p.items[len(p.items)-1] + p.items = p.items[:len(p.items)-1] + p.lock.Unlock() + return x + } + p.lock.Unlock() + if p.New != nil { + return p.New() + } + return nil +} + +// Put adds a value back into the pool. +func (p *Pool) Put(x interface{}) { + if x == nil { + return + } + p.lock.Lock() + ep := poolEpoch() + if p.epoch != ep { + p.items = nil + p.epoch = ep + } + p.items = append(p.items, x) + p.lock.Unlock() +} diff --git a/tsc/cmd/tsc/wasm_wasip1.go b/tsc/cmd/tsc/wasm_wasip1.go index 1d56486fd8c31..970393a180ab7 100644 --- a/tsc/cmd/tsc/wasm_wasip1.go +++ b/tsc/cmd/tsc/wasm_wasip1.go @@ -17,8 +17,16 @@ import ( "unicode/utf16" "unsafe" + "github.com/microsoft/TypeScript/tsc/internal/ast" "github.com/microsoft/TypeScript/tsc/internal/bundled" + "github.com/microsoft/TypeScript/tsc/internal/compiler" + "github.com/microsoft/TypeScript/tsc/internal/core" + "github.com/microsoft/TypeScript/tsc/internal/diagnostics" + "github.com/microsoft/TypeScript/tsc/internal/diagnosticwriter" "github.com/microsoft/TypeScript/tsc/internal/execute" + "github.com/microsoft/TypeScript/tsc/internal/json" + "github.com/microsoft/TypeScript/tsc/internal/locale" + "github.com/microsoft/TypeScript/tsc/internal/scanner" "github.com/microsoft/TypeScript/tsc/internal/tspath" "github.com/microsoft/TypeScript/tsc/internal/vfs" ) @@ -40,14 +48,47 @@ func hostFS(op uint32, ptr unsafe.Pointer, length uint32) int32 //go:wasmimport tsrs_host fs_take func hostFSTake(ptr unsafe.Pointer) +//go:linkname runtimeMarkHeap runtime.MarkHeap +func runtimeMarkHeap() + +//go:linkname runtimeResetHeap runtime.ResetHeap +func runtimeResetHeap() + +const ( + wasmInitialInputCap = 4 << 20 + wasmInitialOutputCap = 8 << 20 +) + var ( - wasmInputBuf []byte - wasmOutputBuf []byte - wasmDummyByte byte + wasmInputBuf []byte + wasmOutputBuf []byte + wasmDummyByte byte + wasmBuffersInitialized bool ) +func init() { + compiler.SetWasmHeapHooks(func() { + _, _ = json.Marshal([]wasmDiagnosticResponse{{ + StartPosition: &wasmDiagnosticPosition{}, + EndPosition: &wasmDiagnosticPosition{}, + MessageChain: []wasmDiagnosticResponse{{}}, + RelatedInformation: []wasmDiagnosticResponse{{}}, + }}) + runtimeMarkHeap() + }, runtimeResetHeap) +} + +func ensureWasmBuffers() { + if !wasmBuffersInitialized { + wasmInputBuf = make([]byte, 0, wasmInitialInputCap) + wasmOutputBuf = make([]byte, 0, wasmInitialOutputCap) + wasmBuffersInitialized = true + } +} + //go:wasmexport tsrs_input func tsrsInput(length uint32) unsafe.Pointer { + ensureWasmBuffers() if int(length) <= cap(wasmInputBuf) { wasmInputBuf = wasmInputBuf[:length] } else { @@ -61,6 +102,7 @@ func tsrsInput(length uint32) unsafe.Pointer { //go:wasmexport tsrs_run func tsrsRun() int32 { + ensureWasmBuffers() req := string(wasmInputBuf) wasmInputBuf = wasmInputBuf[:0] wasmOutputBuf = wasmOutputBuf[:0] @@ -81,20 +123,108 @@ func tsrsRun() int32 { args = parts[2:] } + jsonDiagnostics := (flags & 1) != 0 caseSensitive := (flags & 2) == 0 tty := (flags & 4) != 0 + var writer io.Writer = os.Stdout + if jsonDiagnostics { + writer = io.Discard + } sys := &wasmSys{ cwd: tspath.RootedDirectoryPathFromAbsolute(tspath.NormalizePath(cwd)), fs: bundled.WrapFS(&wasmHostFS{caseSensitive: caseSensitive}), defaultLibraryPath: bundled.LibPath(), - writer: os.Stdout, + writer: writer, tty: tty, start: time.Now(), } result := execute.CommandLine(context.Background(), sys, args, nil) + if jsonDiagnostics { + responses := make([]wasmDiagnosticResponse, len(result.Diagnostics)) + for i, d := range result.Diagnostics { + responses[i] = newWasmDiagnosticResponse(diagnosticwriter.WrapASTDiagnostic(d)) + } + if encoded, err := json.Marshal(responses); err == nil { + if len(encoded) <= cap(wasmOutputBuf) { + wasmOutputBuf = wasmOutputBuf[:len(encoded)] + copy(wasmOutputBuf, encoded) + } else { + wasmOutputBuf = encoded + } + } + } + compiler.ResetWasmHeapToWatermark() return int32(result.Status) } +type wasmDiagnosticPosition struct { + Line int `json:"line"` + Character int `json:"character"` +} + +type wasmDiagnosticResponse struct { + FileName string `json:"fileName,omitempty"` + Pos int `json:"pos"` + End int `json:"end"` + StartPosition *wasmDiagnosticPosition `json:"startPosition,omitempty"` + EndPosition *wasmDiagnosticPosition `json:"endPosition,omitempty"` + Code int32 `json:"code"` + Category diagnostics.Category `json:"category"` + Source string `json:"source,omitempty"` + Text string `json:"text"` + ReportsUnnecessary bool `json:"reportsUnnecessary,omitempty"` + ReportsDeprecated bool `json:"reportsDeprecated,omitempty"` + MessageChain []wasmDiagnosticResponse `json:"messageChain,omitempty"` + RelatedInformation []wasmDiagnosticResponse `json:"relatedInformation,omitempty"` +} + +func newWasmDiagnosticResponse(d *diagnosticwriter.ASTDiagnostic) wasmDiagnosticResponse { + file := d.File() + pos, end := d.Pos(), d.End() + if file != nil { + pos = max(0, min(pos, len(file.Text()))) + end = max(pos, min(end, len(file.Text()))) + } + resp := wasmDiagnosticResponse{ + Pos: pos, + End: end, + Code: d.Code(), + Category: d.Category(), + Source: d.Source(), + Text: d.Localize(locale.Default), + ReportsUnnecessary: d.Diagnostic.ReportsUnnecessary(), + ReportsDeprecated: d.Diagnostic.ReportsDeprecated(), + } + if file != nil { + resp.FileName = file.FileName().AsString() + if sourceFile, ok := file.(*ast.SourceFile); ok { + positionMap := sourceFile.GetPositionMap() + resp.Pos = positionMap.UTF8ToUTF16(pos) + resp.End = positionMap.UTF8ToUTF16(end) + } else { + resp.Pos = int(core.UTF16Len(file.Text()[:pos])) + resp.End = int(core.UTF16Len(file.Text()[:end])) + } + startLine, startChar := scanner.GetECMALineAndUTF16CharacterOfPosition(file, pos) + endLine, endChar := scanner.GetECMALineAndUTF16CharacterOfPosition(file, end) + resp.StartPosition = &wasmDiagnosticPosition{Line: startLine, Character: int(startChar)} + resp.EndPosition = &wasmDiagnosticPosition{Line: endLine, Character: int(endChar)} + } + if chain := d.MessageChain(); len(chain) > 0 { + resp.MessageChain = make([]wasmDiagnosticResponse, len(chain)) + for i, c := range chain { + resp.MessageChain[i] = newWasmDiagnosticResponse(c.(*diagnosticwriter.ASTDiagnostic)) + } + } + if related := d.RelatedInformation(); len(related) > 0 { + resp.RelatedInformation = make([]wasmDiagnosticResponse, len(related)) + for i, r := range related { + resp.RelatedInformation[i] = newWasmDiagnosticResponse(r.(*diagnosticwriter.ASTDiagnostic)) + } + } + return resp +} + //go:wasmexport tsrs_output func tsrsOutput() unsafe.Pointer { if len(wasmOutputBuf) == 0 { diff --git a/tsc/internal/ast/ast.go b/tsc/internal/ast/ast.go index 3558c98c9cbf7..31c7fe7c27a5d 100644 --- a/tsc/internal/ast/ast.go +++ b/tsc/internal/ast/ast.go @@ -31,6 +31,12 @@ type Visitor func(*Node) bool func visit(v Visitor, node *Node) bool { if node != nil { + if node.Kind == KindUnknown { + node = node.forceLazyTypeNode() + if node == nil { + return false + } + } return v(node) } return false @@ -551,7 +557,11 @@ var nodeTypeOffset = [kindFlowReduceLabelData + 1]uint8{ func (n *Node) Type() *Node { if offset := nodeTypeOffset[n.Kind]; offset != 0 { // SAFETY: nodeTypeOffset stores the byte offset of Type/TypeExpression within the concrete node struct for n.Kind. - return *(**Node)(unsafe.Add(unsafe.Pointer(&n.Flags), offset-nodeFlagsOffset)) + t := *(**Node)(unsafe.Add(unsafe.Pointer(&n.Flags), offset-nodeFlagsOffset)) + if t != nil && t.Kind == KindUnknown { + return t.forceLazyTypeNode() + } + return t } if funcLike := n.FunctionLikeData(); funcLike != nil { return funcLike.Type diff --git a/tsc/internal/ast/lazylist.go b/tsc/internal/ast/lazylist.go index d6e9a7cfaf6e5..1fd8b6318bcf5 100644 --- a/tsc/internal/ast/lazylist.go +++ b/tsc/internal/ast/lazylist.go @@ -5,6 +5,7 @@ import ( "sync/atomic" "unsafe" + "github.com/microsoft/TypeScript/tsc/internal/collections" "github.com/microsoft/TypeScript/tsc/internal/core" ) @@ -13,6 +14,8 @@ const ( LazyListDeferred uint32 = 1 // Binder visited owner and deferred binding LazyListUnbound uint32 = 2 // File finished binding without visiting owner LazyListDone uint32 = 3 // Forced (parsed and bound if deferred) + + LazyParsingContextTypeAliasRHS uint8 = 255 ) var ( @@ -31,6 +34,7 @@ type LazyBindContext struct { type LazyNodeList struct { NodeList // Must be at offset 0 so (*LazyNodeList)(unsafe.Pointer(list)) is valid + Placeholder Node state atomic.Uint32 ParsingContext uint8 ContextFlags NodeFlags @@ -43,12 +47,86 @@ type LazyNodeList struct { mu sync.Mutex } +type savedMemberSymbolState struct { + sym *Symbol + declHeader uint32 + declPtr unsafe.Pointer + valueDecl *Node +} + +type forcedSymbolState struct { + owner *Symbol + prevExtra *symbolExtra + savedMembers []savedMemberSymbolState +} + var ( - lazyReparseHook func(lazy *LazyNodeList, file *SourceFile, allowNestedLazy bool) []*Node - lazyBindHook func(lazy *LazyNodeList, file *SourceFile, nodes []*Node) - lazySymbolLocks [256]sync.Mutex + lazyReparseHook func(lazy *LazyNodeList, file *SourceFile, allowNestedLazy bool) []*Node + lazyBindHook func(lazy *LazyNodeList, file *SourceFile, nodes []*Node) + lazySymbolLocks [256]sync.Mutex + trackForcedLazy atomic.Bool + forcedLazyMu sync.Mutex + forcedLazyLists []*LazyNodeList + forcedLazySymbols []forcedSymbolState ) +func SetTrackForcedLazy(enabled bool) { + trackForcedLazy.Store(enabled) +} + +func ResetForcedLazyLists() { + forcedLazyMu.Lock() + defer forcedLazyMu.Unlock() + for i := len(forcedLazySymbols) - 1; i >= 0; i-- { + entry := forcedLazySymbols[i] + for _, sm := range entry.savedMembers { + // SAFETY: savedMembers only records owner Symbols embedded at offset zero of SymbolWithData. + md := &(*SymbolWithData)(unsafe.Pointer(sm.sym)).d + sm.sym.declHeader = sm.declHeader + md.declPtr = sm.declPtr + md.valueDeclaration = sm.valueDecl + } + // SAFETY: entry.owner is an owner Symbol embedded at offset zero of SymbolWithData. + d := &(*SymbolWithData)(unsafe.Pointer(entry.owner)).d + d.extra = entry.prevExtra + entry.owner.SetHasLazyMembers() + } + forcedLazySymbols = nil + for i := len(forcedLazyLists) - 1; i >= 0; i-- { + l := forcedLazyLists[i] + l.forcedNodes = nil + l.nestedLazyLists = nil + if l.BindCtx != nil { + l.state.Store(LazyListDeferred) + } else { + l.state.Store(LazyListUnbound) + } + } + forcedLazyLists = nil + for i := range subtreeFactsShards { + subtreeFactsShards[i].m = nil + } + setParentInChildrenPool = sync.Pool{ + New: func() any { + return newParentInChildrenSetter() + }, + } +} + +func (node *SourceFile) ResetWasmCaches() { + node.jsdocCache = nil + node.identifiersOnce = sync.Once{} + node.identifiers = collections.Set[string]{} + node.declarationMap = nil + node.nameTableOnce = sync.Once{} + node.nameTable = nil + node.positionMapOnce = sync.Once{} + node.positionMap = nil + node.ecmaLineMap = nil + node.tokenCache = nil + node.tokenFactory = nil +} + func SetLazyParseHook(fn func(lazy *LazyNodeList, file *SourceFile, allowNestedLazy bool) []*Node) { lazyReparseHook = fn } @@ -67,6 +145,48 @@ func NewLazyNodeList(loc core.TextRange, parsingContext uint8, contextFlags Node return &list } +func NewLazyTypeAliasRHS(loc core.TextRange, contextFlags NodeFlags, parsingContexts uint32) *LazyNodeList { + var list LazyNodeList + list.Loc = loc + list.Nodes = lazySentinelNodes + list.ParsingContext = LazyParsingContextTypeAliasRHS + list.ContextFlags = contextFlags + list.ParsingContexts = parsingContexts + list.Placeholder.Loc = loc + list.Placeholder.Kind = KindUnknown //nolint:customlint + // SAFETY: Stores the enclosing LazyNodeList pointer in Placeholder.Parent for lazy type-alias RHS resolution. + list.Placeholder.Parent = (*Node)(unsafe.Pointer(&list)) + return &list +} + +func (n *Node) IsLazyTypeNode() bool { + return n != nil && n.Kind == KindUnknown && n.Parent != nil +} + +func (n *Node) AsLazyTypeNode() *LazyNodeList { + // SAFETY: IsLazyTypeNode callers guarantee n.Parent holds the *LazyNodeList stored by NewLazyTypeAliasRHS. + return (*LazyNodeList)(unsafe.Pointer(n.Parent)) +} + +//go:noinline +func (n *Node) forceLazyTypeNode() *Node { + if n.Parent == nil { + return nil + } + // SAFETY: forceLazyTypeNode is only called on KindUnknown placeholders created by NewLazyTypeAliasRHS. + nodes := (*LazyNodeList)(unsafe.Pointer(n.Parent)).Force() + if len(nodes) > 0 { + return nodes[0] + } + return nil +} + +func SetLazyTypeOwner(typeNode *Node, owner *Node) { + if typeNode.IsLazyTypeNode() { + typeNode.AsLazyTypeNode().Owner = owner + } +} + func (list *NodeList) IsLazy() bool { // SAFETY: SliceData only inspects the backing-array pointer of list.Nodes. return list != nil && unsafe.SliceData(list.Nodes) == &lazySentinelElem @@ -162,10 +282,16 @@ func (lazy *LazyNodeList) Force() []*Node { lazy.forcedNodes = nodes if wasDeferred && lazy.BindCtx != nil { lazyBindHook(lazy, lazy.File, nodes) - for _, nested := range lazy.nestedLazyLists { - nested.MarkUnbound(lazy.File) - } - lazy.nestedLazyLists = nil + } + for _, nested := range lazy.nestedLazyLists { + nested.MarkUnbound(lazy.File) + } + lazy.nestedLazyLists = nil + if trackForcedLazy.Load() { + forcedLazyMu.Lock() + forcedLazyLists = append(forcedLazyLists, lazy) + forcedLazyMu.Unlock() + } else { lazy.BindCtx = nil } lazy.state.Store(LazyListDone) @@ -268,6 +394,23 @@ func (s *Symbol) getExportsSlow(arena *SymbolExtraArena) SymbolTable { return *m } +func saveTableMemberStates(t SymbolTable, dst []savedMemberSymbolState) []savedMemberSymbolState { + for _, m := range t.Values() { + if m != nil { + mo := m.owner() + // SAFETY: owner() returns a Symbol embedded at offset zero of SymbolWithData. + md := &(*SymbolWithData)(unsafe.Pointer(mo)).d + dst = append(dst, savedMemberSymbolState{ + sym: mo, + declHeader: mo.declHeader, + declPtr: md.declPtr, + valueDecl: md.valueDeclaration, + }) + } + } + return dst +} + func (s *Symbol) forceLazyMembers() { if s == nil { return @@ -283,6 +426,32 @@ func (s *Symbol) forceLazyMembers() { if atomic.LoadUint32(&owner.declHeader)&lazyStateMask != lazyStatePending { return } + tracking := trackForcedLazy.Load() + if tracking { + // SAFETY: owner() returns a Symbol embedded at offset zero of SymbolWithData. + d := &(*SymbolWithData)(unsafe.Pointer(owner)).d + prevExtra := d.extra + var savedMembers []savedMemberSymbolState + if prevExtra != nil { + cloned := *prevExtra + if cloned.members != nil { + savedMembers = saveTableMemberStates(cloned.members, savedMembers) + cloned.members = cloned.members.Clone() + } + if cloned.exports != nil { + savedMembers = saveTableMemberStates(cloned.exports, savedMembers) + cloned.exports = cloned.exports.Clone() + } + d.extra = &cloned + } + forcedLazyMu.Lock() + forcedLazySymbols = append(forcedLazySymbols, forcedSymbolState{ + owner: owner, + prevExtra: prevExtra, + savedMembers: savedMembers, + }) + forcedLazyMu.Unlock() + } for { old := atomic.LoadUint32(&owner.declHeader) if atomic.CompareAndSwapUint32(&owner.declHeader, old, (old&^lazyStateMask)|lazyStateActive) { @@ -306,7 +475,13 @@ func (s *Symbol) forceLazyMembers() { nested.MarkUnbound(l.File) } l.nestedLazyLists = nil - l.BindCtx = nil + if tracking { + forcedLazyMu.Lock() + forcedLazyLists = append(forcedLazyLists, l) + forcedLazyMu.Unlock() + } else { + l.BindCtx = nil + } l.state.Store(LazyListDone) } } diff --git a/tsc/internal/ast/utilities.go b/tsc/internal/ast/utilities.go index ad3a51368e860..7be789f5b7ef4 100644 --- a/tsc/internal/ast/utilities.go +++ b/tsc/internal/ast/utilities.go @@ -2835,13 +2835,34 @@ func GetNodeAtPosition(file *SourceFile, position int, includeJSDoc bool) *Node } } if child == nil { - current.ForEachChild(func(node *Node) bool { - if nodeContainsPosition(node, position) { - child = node - return true + if current.Kind == KindSourceFile { + sf := current.AsSourceFile() + if stmts := sf.Statements.Nodes; len(stmts) > 16 { + lo, hi := 0, len(stmts) + for lo < hi { + mid := int(uint(lo+hi) >> 1) + if stmts[mid].End() <= position { + lo = mid + 1 + } else { + hi = mid + } + } + if lo < len(stmts) && nodeContainsPosition(stmts[lo], position) { + child = stmts[lo] + } else if sf.EndOfFileToken != nil && nodeContainsPosition(sf.EndOfFileToken, position) { + child = sf.EndOfFileToken + } } - return false - }) + } + if child == nil { + current.ForEachChild(func(node *Node) bool { + if nodeContainsPosition(node, position) { + child = node + return true + } + return false + }) + } } if child == nil || IsMetaProperty(child) { return current @@ -2899,6 +2920,10 @@ func ForEachDynamicImportOrRequireCall( if cb(node, node.AsImportTypeNode().Argument.AsLiteralTypeNode().Literal) { return true } + } else if node.Kind == KindTypeAliasDeclaration && node.AsTypeAliasDeclaration().Type.IsLazyTypeNode() && node.End() > lastIndex+size { + lastIndex = node.End() + lastIndex, size = findImportOrRequire(file.Text(), lastIndex, isJavaScriptFile) + continue } // skip past import/require lastIndex += size diff --git a/tsc/internal/binder/binder.go b/tsc/internal/binder/binder.go index 4a843f14ace80..6864d8f229ec1 100644 --- a/tsc/internal/binder/binder.go +++ b/tsc/internal/binder/binder.go @@ -113,6 +113,16 @@ var binderPool = sync.Pool{ }, } +func ResetWasmPools() { + binderPool = sync.Pool{ + New: func() any { + b := &Binder{} + b.bindFunc = b.bind + return b + }, + } +} + func getBinder() *Binder { return binderPool.Get().(*Binder) } @@ -1755,6 +1765,9 @@ func (b *Binder) bindChildren(node *ast.Node) { case ast.KindInterfaceDeclaration, ast.KindClassDeclaration, ast.KindClassExpression, ast.KindTypeLiteral: b.bindEachChild(node) b.bindLazyMemberList(node) + case ast.KindTypeAliasDeclaration: + b.bindEachChild(node) + b.bindLazyTypeAlias(node) default: b.bindEachChild(node) } @@ -1811,6 +1824,40 @@ func (b *Binder) bindLazyMemberList(node *ast.Node) { symbol.SetHasLazyMembers() } +func (b *Binder) bindLazyTypeAlias(node *ast.Node) { + typeNode := node.AsTypeAliasDeclaration().Type + if !typeNode.IsLazyTypeNode() { + return + } + lazy := typeNode.AsLazyTypeNode() + plain := b.currentFlow != b.unreachableFlow && + b.currentBreakTarget == nil && + b.currentContinueTarget == nil && + b.currentReturnTarget == nil && + b.currentTrueTarget == nil && + b.currentFalseTarget == nil && + b.currentExceptionTarget == nil && + b.preSwitchCaseFlow == nil && + b.activeLabelList == nil && + !b.inAssignmentPattern + if !plain { + nodes := lazy.ParseNow(b.file) + b.bindEach(nodes) + for _, nested := range lazy.NestedLazyLists() { + nested.MarkUnbound(b.file) + } + lazy.SetNestedLazyLists(nil) + return + } + lazy.Defer(b.file, &ast.LazyBindContext{ + Container: b.container, + ThisContainer: b.thisContainer, + BlockScopeContainer: b.blockScopeContainer, + CurrentFlow: b.currentFlow, + UnreachableFlow: b.unreachableFlow, + }) +} + func bindLazyList(lazy *ast.LazyNodeList, file *ast.SourceFile, nodes []*ast.Node) { ctx := lazy.BindCtx if ctx == nil { diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 2447c4d75c165..2cb7f7bc2a9ef 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -18244,6 +18244,11 @@ type keyBuilder struct { var wasmKeyBuilderPool []*keyBuilder +func ResetWasmPools() { + wasmKeyBuilderPool = nil + primitiveTypeAliasSuggestions() +} + func acquireKeyBuilder() *keyBuilder { if n := len(wasmKeyBuilderPool); n > 0 { b := wasmKeyBuilderPool[n-1] @@ -24529,8 +24534,13 @@ func (c *Checker) instantiateTypeWithAliasSlow(t *Type, m *TypeMapper, alias *Ty if !(c.couldContainTypeVariables(t) || (t.alias != nil && len(t.alias.typeArguments) > 0 && c.anyCouldContainTypeVariables(t.alias.typeArguments))) { 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 + maxInstDepth := 100 + maxInstCount := uint32(5_000_000) + if runtime.GOARCH == "wasm" { + maxInstDepth = 500 + maxInstCount = 50_000_000 + } + if len(c.instantiationStack) == maxInstDepth || c.instantiationCount >= maxInstCount { // 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 { @@ -24752,7 +24762,26 @@ func (c *Checker) instantiateTypeWorker(t *Type, m *TypeMapper, alias *TypeAlias alias = c.instantiateTypeAlias(t.alias, m) } d := t.AsIndexedAccessType() - return c.getIndexedAccessTypeEx(c.instantiateType(d.objectType, m), c.instantiateType(d.indexType, m), d.accessFlags, nil /*accessNode*/, alias) + objectType := c.instantiateType(d.objectType, m) + if d.accessFlags == AccessFlagsNone && isTupleType(objectType) && !c.compilerOptions.NoUncheckedIndexedAccess.IsTrue() { + tuple := objectType.TargetTupleType() + if tuple.combinedFlags&(ElementFlagsOptional|ElementFlagsVariable) == 0 && d.indexType.flags&TypeFlagsIntersection != 0 { + if iTypes := d.indexType.Types(); len(iTypes) == 2 { + var keyCand *Type + if iTypes[0].flags&TypeFlagsIndex != 0 && iTypes[0].AsIndexType().target == d.objectType && iTypes[0].AsIndexType().indexFlags == IndexFlagsNone { + keyCand = c.instantiateType(iTypes[1], m) + } else if iTypes[1].flags&TypeFlagsIndex != 0 && iTypes[1].AsIndexType().target == d.objectType && iTypes[1].AsIndexType().indexFlags == IndexFlagsNone { + keyCand = c.instantiateType(iTypes[0], m) + } + if keyCand != nil { + if fast := c.tryFastTupleIndexedAccess(objectType, keyCand); fast != nil { + return fast + } + } + } + } + } + return c.getIndexedAccessTypeEx(objectType, c.instantiateType(d.indexType, m), d.accessFlags, nil /*accessNode*/, alias) case flags&TypeFlagsTemplateLiteral != 0: newTypes, changed := c.instantiateTypesTemp(t.AsTemplateLiteralType().types, m) res := c.getTemplateLiteralType(t.AsTemplateLiteralType().texts, newTypes) @@ -25256,8 +25285,12 @@ func (c *Checker) instantiateMappedTupleType(tupleType *Type, mappedType *Type, } modifiers := getMappedTypeModifiers(mappedType) elementTypes := c.getElementTypes(tupleType) + tupleTarget := tupleType.TargetTupleType() newElementTypes := make([]*Type, len(elementTypes)) - newElementInfos := slices.Clone(elementInfos) + newElementInfos := elementInfos + if modifiers != 0 { + newElementInfos = slices.Clone(elementInfos) + } for i, e := range elementTypes { flags := elementInfos[i].flags var mapped *Type @@ -25268,9 +25301,9 @@ func (c *Checker) instantiateMappedTupleType(tupleType *Type, mappedType *Type, mapped = c.instantiateType(mappedType, prependTypeMapping(typeVariable, e, m)) default: mapped = c.getElementTypeOfArrayType(c.instantiateType(mappedType, prependTypeMapping(typeVariable, c.createArrayType(e), m))) - if mapped == nil { - mapped = c.unknownType - } + } + if mapped == nil { + mapped = c.unknownType } switch { case modifiers&MappedTypeModifiersIncludeOptional != 0: @@ -25284,13 +25317,55 @@ func (c *Checker) instantiateMappedTupleType(tupleType *Type, mappedType *Type, } newElementTypes[i] = mapped } - newReadonly := getModifiedReadonlyState(tupleType.TargetTupleType().readonly, getMappedTypeModifiers(mappedType)) + newReadonly := getModifiedReadonlyState(tupleTarget.readonly, modifiers) if slices.Contains(newElementTypes, c.errorType) { return c.errorType } return c.createTupleTypeEx(newElementTypes, newElementInfos, newReadonly) } + +func parseSmallTupleIndex(s string) (int, bool) { + if len(s) == 0 || len(s) > 6 { + return 0, false + } + if len(s) > 1 && s[0] == '0' { + return 0, false + } + n := 0 + for i := range len(s) { + ch := s[i] + if ch < '0' || ch > '9' { + return 0, false + } + n = n*10 + int(ch-'0') + } + return n, true +} + +func (c *Checker) tryFastTupleIndexedAccess(objectType *Type, indexType *Type) *Type { + if indexType.flags&TypeFlagsNumberLiteral != 0 { + f := float64(indexType.AsLiteralType().value.(jsnum.Number)) + idx := int(f) + if float64(idx) == f && idx >= 0 { + if typeArgs := c.getTypeArguments(objectType); idx < len(typeArgs) { + return typeArgs[idx] + } + } + } else if indexType.flags&TypeFlagsStringLiteral != 0 { + s := indexType.AsLiteralType().value.(string) + if s == "length" { + return c.getNumberLiteralType(jsnum.Number(len(c.getTypeArguments(objectType)))) + } + if idx, ok := parseSmallTupleIndex(s); ok { + if typeArgs := c.getTypeArguments(objectType); idx < len(typeArgs) { + return typeArgs[idx] + } + } + } + return nil +} + func (c *Checker) instantiateMappedTypeTemplate(t *Type, key *Type, isOptional bool, m *TypeMapper) *Type { templateMapper := appendTypeMapping(m, c.getTypeParameterFromMappedType(t), key) propType := c.instantiateType(c.getTemplateTypeFromMappedType(core.OrElse(t.AsMappedType().target, t)), templateMapper) @@ -27156,8 +27231,12 @@ func (c *Checker) getConditionalType(root *ConditionalRoot, mapper *TypeMapper, // purposes of resolution. We also loop here when resolution of a conditional type ends in resolution of // another (or, through recursion, possibly the same) conditional type. In the potentially tail-recursive // cases we increment the tail recursion counter and stop after 1000 iterations. + maxTailCount := 1000 + if runtime.GOARCH == "wasm" { + maxTailCount = 8192 + } for { - if tailCount == 1000 { + if tailCount == maxTailCount { c.error(c.currentNode, diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite) return c.errorType } @@ -30443,8 +30522,14 @@ func (c *Checker) getIndexedAccessTypeOrUndefined(objectType *Type, indexType *T if objectType == c.wildcardType || indexType == c.wildcardType { return c.wildcardType } + if accessNode == nil && alias == nil && accessFlags == AccessFlagsNone && !c.compilerOptions.NoUncheckedIndexedAccess.IsTrue() && isTupleType(objectType) { + if objectType.TargetTupleType().combinedFlags&(ElementFlagsOptional|ElementFlagsVariable) == 0 { + if fast := c.tryFastTupleIndexedAccess(objectType, indexType); fast != nil { + return fast + } + } + } objectType = c.getReducedType(objectType) - // If the object type has a string index signature and no other members we know that the result will // always be the type of that index signature and we can simplify accordingly. if c.isStringIndexSignatureOnlyType(objectType) && indexType.flags&TypeFlagsNullable == 0 && c.isTypeAssignableToKind(indexType, TypeFlagsString|TypeFlagsNumber) { indexType = c.stringType diff --git a/tsc/internal/compiler/filesparser.go b/tsc/internal/compiler/filesparser.go index 6153d32e840bd..a12fb09d81f48 100644 --- a/tsc/internal/compiler/filesparser.go +++ b/tsc/internal/compiler/filesparser.go @@ -242,6 +242,16 @@ var parseTaskDataPool = sync.Pool{ }, } +func resetParseTaskDataPool() { + parseTaskDataPool = sync.Pool{ + New: func() any { + return &parseTaskData{ + tasks: make(map[tspath.RootedFilePath]*parseTask, 1), + } + }, + } +} + func getParseTaskData(task *parseTask) *parseTaskData { td := parseTaskDataPool.Get().(*parseTaskData) td.firstTask = task diff --git a/tsc/internal/compiler/host.go b/tsc/internal/compiler/host.go index 8ff15f75cce38..889de7e179014 100644 --- a/tsc/internal/compiler/host.go +++ b/tsc/internal/compiler/host.go @@ -1,17 +1,82 @@ package compiler import ( + "runtime" + "strings" + "github.com/microsoft/TypeScript/tsc/internal/ast" + "github.com/microsoft/TypeScript/tsc/internal/binder" + "github.com/microsoft/TypeScript/tsc/internal/checker" "github.com/microsoft/TypeScript/tsc/internal/contentmapper" "github.com/microsoft/TypeScript/tsc/internal/core" "github.com/microsoft/TypeScript/tsc/internal/diagnostics" "github.com/microsoft/TypeScript/tsc/internal/parser" + "github.com/microsoft/TypeScript/tsc/internal/printer" "github.com/microsoft/TypeScript/tsc/internal/tsoptions" "github.com/microsoft/TypeScript/tsc/internal/tspath" "github.com/microsoft/TypeScript/tsc/internal/vfs" "github.com/microsoft/TypeScript/tsc/internal/vfs/cachedvfs" ) +type wasmCachedDts struct { + size int64 + mtime int64 + file *ast.SourceFile +} + +var ( + wasmStaticDtsCache map[ast.SourceFileParseOptions]wasmCachedDts + wasmHeapMarked bool + wasmMarkHeapHook func() + wasmResetHeapHook func() +) + +func SetWasmHeapHooks(markFn, resetFn func()) { + wasmMarkHeapHook = markFn + wasmResetHeapHook = resetFn +} + +func isStaticWasmDts(fileName tspath.RootedFilePath) bool { + return !strings.Contains(fileName.AsString(), "state") +} + +func resetAllWasmPools() { + ast.ResetForcedLazyLists() + for _, entry := range wasmStaticDtsCache { + if entry.file != nil { + entry.file.ResetWasmCaches() + } + } + core.ResetWasmPools() + parser.ResetWasmPools() + binder.ResetWasmPools() + printer.ResetWasmPools() + checker.ResetWasmPools() + resetParseTaskDataPool() +} + +func FinishWasmProgramInit(program *Program) { + if runtime.GOARCH != "wasm" || wasmMarkHeapHook == nil { + return + } + if !wasmHeapMarked { + program.BindSourceFiles() + core.NodeCoreModules() + resetAllWasmPools() + wasmMarkHeapHook() + ast.SetTrackForcedLazy(true) + wasmHeapMarked = true + } +} + +func ResetWasmHeapToWatermark() { + if !wasmHeapMarked || wasmResetHeapHook == nil { + return + } + resetAllWasmPools() + wasmResetHeapHook() +} + type CompilerHost interface { FS() vfs.FS DefaultLibraryPath() tspath.RootedDirectoryPath @@ -81,6 +146,39 @@ func (h *compilerHost) Trace(msg *diagnostics.Message, args ...any) { } func (h *compilerHost) GetSourceFile(opts ast.SourceFileParseOptions) *ast.SourceFile { + if runtime.GOARCH == "wasm" && wasmMarkHeapHook != nil && opts.FileName.IsDeclarationFile() && isStaticWasmDts(opts.FileName) { + if wasmStaticDtsCache != nil { + if entry, ok := wasmStaticDtsCache[opts]; ok { + if fi := h.FS().Stat(opts.FileName.AsPath()); fi != nil && fi.Size() == entry.size && fi.ModTime().UnixNano() == entry.mtime { + return entry.file + } + } + } + if !wasmHeapMarked { + text, ok := h.FS().ReadFile(opts.FileName) + if !ok { + return nil + } + sf := parser.ParseSourceFile(opts, text, core.EnsureScriptKindFromFileName(opts.FileName)) + binder.BindSourceFile(sf) + if wasmStaticDtsCache == nil { + wasmStaticDtsCache = make(map[ast.SourceFileParseOptions]wasmCachedDts, 64) + } + var size, mtime int64 + if fi := h.FS().Stat(opts.FileName.AsPath()); fi != nil { + size = fi.Size() + mtime = fi.ModTime().UnixNano() + } else { + size = int64(len(text)) + } + wasmStaticDtsCache[opts] = wasmCachedDts{ + size: size, + mtime: mtime, + file: sf, + } + return sf + } + } text, ok := h.FS().ReadFile(opts.FileName) if !ok { return nil diff --git a/tsc/internal/core/core.go b/tsc/internal/core/core.go index 3bd7b9187cde3..fba8e32c19dda 100644 --- a/tsc/internal/core/core.go +++ b/tsc/internal/core/core.go @@ -698,6 +698,14 @@ var levenshteinBuffersPool = sync.Pool{ }, } +func ResetWasmPools() { + levenshteinBuffersPool = sync.Pool{ + New: func() any { + return &levenshteinBuffers{} + }, + } +} + func levenshteinWithMax(buffers *levenshteinBuffers, s1 []rune, s2 []rune, maxValue float64) float64 { bufferSize := len(s2) + 1 buffers.previous = slices.Grow(buffers.previous[:0], bufferSize)[:bufferSize] diff --git a/tsc/internal/execute/tsc.go b/tsc/internal/execute/tsc.go index f32dc621b517d..fa69f3b620425 100644 --- a/tsc/internal/execute/tsc.go +++ b/tsc/internal/execute/tsc.go @@ -354,7 +354,8 @@ func performIncrementalCompilation( testing.OnProgram(incrementalProgram) } return tsc.CommandLineResult{ - Status: result.Status, + Status: result.Status, + Diagnostics: result.Diagnostics, } } @@ -394,6 +395,7 @@ func performCompilation( Host: host, Tracing: tr, }) + compiler.FinishWasmProgramInit(program) compileTimes.ParseTime = sys.Now().Sub(parseStart) if contentMapperHost != nil { compileTimes.ContentMapperTimes = contentMapperHost.Timings() @@ -414,7 +416,8 @@ func performCompilation( stopTracing(sys, tr) return tsc.CommandLineResult{ - Status: result.Status, + Status: result.Status, + Diagnostics: result.Diagnostics, } } diff --git a/tsc/internal/execute/tsc/compile.go b/tsc/internal/execute/tsc/compile.go index 7ea291edc3f36..91057905334cf 100644 --- a/tsc/internal/execute/tsc/compile.go +++ b/tsc/internal/execute/tsc/compile.go @@ -63,10 +63,10 @@ type Watcher interface { } type CommandLineResult struct { - Status ExitStatus - Watcher Watcher + Status ExitStatus + Watcher Watcher + Diagnostics []*ast.Diagnostic } - type CommandLineTesting interface { // Ensure that all emitted files are timestamped in order to ensure they are deterministic for test baseline OnEmittedFiles(result *compiler.EmitResult, mTimesCache *collections.SyncMap[tspath.PathKey, time.Time]) diff --git a/tsc/internal/parser/parser.go b/tsc/internal/parser/parser.go index 8d1eaf2c41aab..f7d52e88e8e75 100644 --- a/tsc/internal/parser/parser.go +++ b/tsc/internal/parser/parser.go @@ -96,12 +96,13 @@ type Parser struct { jsdocTagCommentsPartsSpace []*ast.Node reparseList []*ast.Node - currentParent *ast.Node - setParentFromContext ast.Visitor - reparsedClones []*ast.Node - freeNodeLists [][]*ast.Node - lazyDts bool - lazyLists []*ast.LazyNodeList + currentParent *ast.Node + setParentFromContext ast.Visitor + reparsedClones []*ast.Node + freeNodeLists [][]*ast.Node + lazyDts bool + seenImportTypeLiteral string + lazyLists []*ast.LazyNodeList } func newParser() *Parser { @@ -126,6 +127,14 @@ var parserPool = sync.Pool{ }, } +func ResetWasmPools() { + parserPool = sync.Pool{ + New: func() any { + return newParser() + }, + } +} + var lazyDtsEnabled atomic.Bool func EnableLazyDts(enabled bool) { @@ -701,7 +710,7 @@ func (p *Parser) parseList(kind ParsingContext, parseElement func(p *Parser) *as return p.newNodeList(core.NewTextRange(pos, p.nodePos()), nodes) } -const lazyListMaxText = 65536 +const lazyListMaxText = 1 << 28 func (p *Parser) parseMemberList(kind ParsingContext, parseElement func(p *Parser) *ast.Node) *ast.NodeList { if p.lazyDts && p.contextFlags&ast.NodeFlagsJSDoc == 0 && p.token != ast.KindCloseBraceToken { @@ -941,21 +950,35 @@ func reparseLazyList(lazy *ast.LazyNodeList, file *ast.SourceFile, allowNestedLa p := getParser() defer putParser(p) p.initializeState(file.ParseOptions(), file.Text(), file.ScriptKind) - p.lazyDts = allowNestedLazy && lazyDtsEnabled.Load() + p.lazyDts = allowNestedLazy p.scanner.ResetTokenState(lazy.Pos()) p.nextToken() p.contextFlags = lazy.ContextFlags p.parsingContexts = ParsingContexts(lazy.ParsingContexts) - var list *ast.NodeList - if ParsingContext(lazy.ParsingContext) == PCClassMembers { - list = p.parseList(PCClassMembers, (*Parser).parseClassElement) + var nodes []*ast.Node + if lazy.ParsingContext == ast.LazyParsingContextTypeAliasRHS { + var typeNode *ast.Node + if p.token == ast.KindIntrinsicKeyword && p.lookAhead((*Parser).nextIsNotDot) { + typeNode = p.parseKeywordTypeNode() + } else { + typeNode = p.parseType() + } + if typeNode != nil { + typeNode.Parent = lazy.Owner + nodes = []*ast.Node{typeNode} + } } else { - list = p.parseList(PCTypeMembers, (*Parser).parseTypeMember) - } - owner := lazy.Owner - nodes := list.Nodes - for _, m := range nodes { - m.Parent = owner + var list *ast.NodeList + if ParsingContext(lazy.ParsingContext) == PCClassMembers { + list = p.parseList(PCClassMembers, (*Parser).parseClassElement) + } else { + list = p.parseList(PCTypeMembers, (*Parser).parseTypeMember) + } + owner := lazy.Owner + nodes = list.Nodes + for _, m := range nodes { + m.Parent = owner + } } if len(p.lazyLists) > 0 { for _, nested := range p.lazyLists { @@ -2472,18 +2495,398 @@ func (p *Parser) parseTypeAliasDeclaration(pos int, jsdoc jsdocScannerInfo, modi typeParameters := p.parseTypeParameters() p.parseExpected(ast.KindEqualsToken) var typeNode *ast.TypeNode - if p.token == ast.KindIntrinsicKeyword && p.lookAhead((*Parser).nextIsNotDot) { - typeNode = p.parseKeywordTypeNode() - } else { - typeNode = p.parseType() + if p.lazyDts && p.contextFlags&ast.NodeFlagsJSDoc == 0 && !p.hasParseError && + !p.scanner.HasPrecedingJSDocWithSeeOrLink() && !p.scanner.HasPrecedingJSDocWithDeprecatedTag() { + typeNode = p.parseTypeAliasRHSLazily() + } + if typeNode == nil { + if p.token == ast.KindIntrinsicKeyword && p.lookAhead((*Parser).nextIsNotDot) { + typeNode = p.parseKeywordTypeNode() + } else { + typeNode = p.parseType() + } } p.parseSemicolon() result := p.finishNode(p.factory.NewTypeAliasDeclaration(modifiers, name, typeParameters, typeNode), pos) + ast.SetLazyTypeOwner(typeNode, result) p.withJSDoc(result, jsdoc) p.checkJSSyntax(result) return result } +func (p *Parser) parseTypeAliasRHSLazily() *ast.TypeNode { + if lastTokenEnd, ok := scanLazyTypeAliasRHS(p.sourceText, p.scanner.TokenStart(), p.seenImportTypeLiteral); ok { + startPos := p.nodePos() + contextFlags := p.contextFlags + parsingContexts := p.parsingContexts + beforeCommentDirectives := len(p.scanner.CommentDirectives()) + markState := p.mark() + p.scanner.ResetTokenState(lastTokenEnd) + p.nextToken() + if p.canParseSemicolon() && + !p.hasParseError && + len(p.scanner.CommentDirectives()) == beforeCommentDirectives && + !p.scanner.HasPrecedingJSDocWithSeeOrLink() && + !p.scanner.HasPrecedingJSDocWithDeprecatedTag() { + loc := core.NewTextRange(startPos, p.nodePos()) + lazy := ast.NewLazyTypeAliasRHS(loc, contextFlags, uint32(parsingContexts)) + p.lazyLists = append(p.lazyLists, lazy) + return &lazy.Placeholder + } + p.rewind(markState) + } + return nil +} + +func scanLazyTypeAliasRHS(text string, startPos int, seenImportLiteral string) (int, bool) { + maxEnd := min(len(text), startPos+lazyListMaxText) + i := startPos + braceDepth := 0 + parenDepth := 0 + bracketDepth := 0 + angleDepth := 0 + lastTokenEnd := startPos + var lastSigChar byte + var lastWord string + for i < maxEnd { + c := text[i] + switch c { + case ' ', '\t', '\r': + i++ + case '\n': + i++ + if braceDepth == 0 && parenDepth == 0 && bracketDepth == 0 && angleDepth == 0 && lastTokenEnd > startPos { + if lastSigChar != '=' && lastSigChar != '?' && lastSigChar != ':' && + lastSigChar != '|' && lastSigChar != '&' && lastSigChar != ',' && + lastWord != "extends" && lastWord != "keyof" && lastWord != "typeof" && + lastWord != "infer" && lastWord != "readonly" && lastWord != "unique" && + lastWord != "in" && lastWord != "out" && lastWord != "is" && lastWord != "asserts" { + if isStmtStartAfterNewline(text, i, maxEnd) { + return lastTokenEnd, true + } + } + } + case ';': + if braceDepth == 0 && parenDepth == 0 && bracketDepth == 0 && angleDepth == 0 { + if lastTokenEnd == startPos { + return 0, false + } + return lastTokenEnd, true + } + i++ + lastTokenEnd = i + case '{': + braceDepth++ + i++ + lastTokenEnd = i + case '}': + if braceDepth == 0 { + if parenDepth != 0 || bracketDepth != 0 || angleDepth != 0 || lastTokenEnd == startPos { + return 0, false + } + return lastTokenEnd, true + } + braceDepth-- + i++ + lastTokenEnd = i + if braceDepth == 0 && parenDepth == 0 && bracketDepth == 0 && angleDepth == 0 { + lastSigChar = '}' + lastWord = "" + } + case '(': + parenDepth++ + i++ + lastTokenEnd = i + case ')': + if parenDepth == 0 { + return 0, false + } + parenDepth-- + i++ + lastTokenEnd = i + if braceDepth == 0 && parenDepth == 0 && bracketDepth == 0 && angleDepth == 0 { + lastSigChar = ')' + lastWord = "" + } + case '[': + bracketDepth++ + i++ + lastTokenEnd = i + case ']': + if bracketDepth == 0 { + return 0, false + } + bracketDepth-- + i++ + lastTokenEnd = i + if braceDepth == 0 && parenDepth == 0 && bracketDepth == 0 && angleDepth == 0 { + lastSigChar = ']' + lastWord = "" + } + case '<': + angleDepth++ + i++ + lastTokenEnd = i + case '>': + if angleDepth == 0 { + return 0, false + } + angleDepth-- + i++ + lastTokenEnd = i + if braceDepth == 0 && parenDepth == 0 && bracketDepth == 0 && angleDepth == 0 { + lastSigChar = '>' + lastWord = "" + } + case '\'', '"': + quote := c + i++ + for i < maxEnd { + ch := text[i] + if ch == '\\' { + i += 2 + continue + } + if ch == '\n' || ch == '\r' { + return 0, false + } + i++ + if ch == quote { + break + } + } + if i > maxEnd || text[i-1] != quote { + return 0, false + } + lastTokenEnd = i + if braceDepth == 0 && parenDepth == 0 && bracketDepth == 0 && angleDepth == 0 { + lastSigChar = quote + lastWord = "" + } + case '`': + i++ + tplBrace := 0 + for i < maxEnd { + ch := text[i] + if ch == '\\' { + i += 2 + continue + } + if ch == '$' && i+1 < maxEnd && text[i+1] == '{' { + tplBrace++ + i += 2 + continue + } + if tplBrace > 0 { + if ch == '{' { + tplBrace++ + } else if ch == '}' { + tplBrace-- + } + i++ + continue + } + i++ + if ch == '`' { + break + } + } + if i > maxEnd || text[i-1] != '`' || tplBrace != 0 { + return 0, false + } + lastTokenEnd = i + if braceDepth == 0 && parenDepth == 0 && bracketDepth == 0 && angleDepth == 0 { + lastSigChar = '`' + lastWord = "" + } + case '/': + if i+1 >= maxEnd { + return 0, false + } + next := text[i+1] + if next == '/' { + i += 2 + for i < maxEnd && text[i] != '\n' { + if text[i] == '@' || text[i] == '/' { + return 0, false + } + i++ + } + } else if next == '*' { + i += 2 + closed := false + for i+1 < maxEnd { + if text[i] == '@' { + return 0, false + } + if text[i] == '*' && text[i+1] == '/' { + i += 2 + closed = true + break + } + i++ + } + if !closed { + return 0, false + } + } else { + return 0, false + } + case '@', '#': + return 0, false + case '=': + if i+1 < maxEnd && text[i+1] == '>' { + i += 2 + lastTokenEnd = i + if braceDepth == 0 && parenDepth == 0 && bracketDepth == 0 && angleDepth == 0 { + lastSigChar = '=' + lastWord = "" + } + continue + } + i++ + lastTokenEnd = i + if braceDepth == 0 && parenDepth == 0 && bracketDepth == 0 && angleDepth == 0 { + lastSigChar = '=' + lastWord = "" + } + default: + if c >= 0x80 { + return 0, false + } + if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == '$' { + wordStart := i + i++ + for i < maxEnd { + ch := text[i] + if (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_' || ch == '$' { + i++ + } else if ch >= 0x80 { + return 0, false + } else { + break + } + } + word := text[wordStart:i] + switch word { + case "this", "void", "super", "delete", "async", "await", "yield", "class", "function", "satisfies": + return 0, false + case "import": + if seenImportLiteral == "" || !matchesSeenImportLiteral(text, i, maxEnd, seenImportLiteral) { + return 0, false + } + } + lastTokenEnd = i + if braceDepth == 0 && parenDepth == 0 && bracketDepth == 0 && angleDepth == 0 { + lastSigChar = c + lastWord = word + } + } else { + i++ + lastTokenEnd = i + if braceDepth == 0 && parenDepth == 0 && bracketDepth == 0 && angleDepth == 0 { + lastSigChar = c + lastWord = "" + } + } + } + } + if i == len(text) && braceDepth == 0 && parenDepth == 0 && bracketDepth == 0 && angleDepth == 0 && lastTokenEnd > startPos { + return lastTokenEnd, true + } + return 0, false +} + +func matchesSeenImportLiteral(text string, pos int, maxEnd int, seen string) bool { + // Check for `('seen')` or `("seen")` immediately following `import` + need := len(seen) + 4 + if pos+need > maxEnd || text[pos] != '(' { + return false + } + q := text[pos+1] + if q != '\'' && q != '"' { + return false + } + return text[pos+2:pos+2+len(seen)] == seen && text[pos+2+len(seen)] == q && text[pos+3+len(seen)] == ')' +} + +func isStmtStartAfterNewline(text string, pos int, maxEnd int) bool { + i := pos + for i < maxEnd { + c := text[i] + if c == ' ' || c == '\t' || c == '\r' || c == '\n' { + i++ + continue + } + if c == '/' && i+1 < maxEnd { + next := text[i+1] + if next == '/' { + if i+2 < maxEnd && (text[i+2] == '@' || text[i+2] == '/') { + return false + } + i += 2 + for i < maxEnd && text[i] != '\n' { + if text[i] == '@' { + return false + } + i++ + } + continue + } + if next == '*' { + i += 2 + closed := false + for i+1 < maxEnd { + if text[i] == '@' { + return false + } + if text[i] == '*' && text[i+1] == '/' { + i += 2 + closed = true + break + } + i++ + } + if !closed { + return false + } + continue + } + } + break + } + if i >= len(text) { + return true + } + if i >= maxEnd { + return false + } + c := text[i] + if c == '}' || c == ';' { + return true + } + if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') { + start := i + i++ + for i < maxEnd { + ch := text[i] + if (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_' || ch == '$' { + i++ + } else { + break + } + } + if i < maxEnd && (text[i] == ' ' || text[i] == '\t' || text[i] == '{' || text[i] == '*') { + switch text[start:i] { + case "type", "export", "declare", "interface", "const", "let", "var", + "function", "class", "enum", "namespace", "module", "global", "import", "abstract": + return true + } + } + } + return false +} + func (p *Parser) nextIsNotDot() bool { return p.nextToken() != ast.KindDotToken } @@ -3457,6 +3860,9 @@ func (p *Parser) parseImportType() *ast.Node { } } p.parseExpected(ast.KindCloseParenToken) + if attributes == nil && ast.IsLiteralTypeNode(typeNode) && ast.IsStringLiteral(typeNode.AsLiteralTypeNode().Literal) { + p.seenImportTypeLiteral = typeNode.AsLiteralTypeNode().Literal.Text() + } var qualifier *ast.Node if p.parseOptional(ast.KindDotToken) { qualifier = p.parseEntityNameOfTypeReference() diff --git a/tsc/internal/printer/singlelinestringwriter.go b/tsc/internal/printer/singlelinestringwriter.go index 5accd0f6d84b4..27668f46505f8 100644 --- a/tsc/internal/printer/singlelinestringwriter.go +++ b/tsc/internal/printer/singlelinestringwriter.go @@ -16,6 +16,14 @@ var singleLineStringWriterPool sync.Pool = sync.Pool{ }, } +func ResetWasmPools() { + singleLineStringWriterPool = sync.Pool{ + New: func() any { + return &singleLineStringWriter{} + }, + } +} + var _ EmitTextWriter = &singleLineStringWriter{} func GetSingleLineStringWriter() (EmitTextWriter, func()) { From d14c912fa08fe131fc4ce65d29fb42b7e7b82b7b Mon Sep 17 00:00:00 2001 From: Steven Chong <25894545+teamchong@users.noreply.github.com> Date: Wed, 28 Oct 2026 18:08:52 -0400 Subject: [PATCH 09/10] perf(checker,parser): add non-generic conditional fast paths, mapper hints, and lazy table lookup - Skip permissive/restrictive instantiation and duplicate assignability checks in getConditionalType when checkType and extendsType contain no type variables. - Fast-return non-generic types in instantiateTypeWithAlias and primitive results in CompositeTypeMapper.Map, and use SimpleTypeMapper for single-parameter tail-recursive conditionals. - Cache the last-matched parameter index in ArrayTypeMapper.Map and InferenceTypeMapper.Map. - Reuse a singleton emptyTypeLiteralType for empty anonymous type literals. - Support binary-searched single-member lazy parsing/binding (LookupLazyMember) and deferred keyof for large binary-keyed .d.ts type literal tables. --- tsc/internal/ast/lazylist.go | 133 +++++++++++++++++++++-- tsc/internal/ast/symbol.go | 76 +++++++++++--- tsc/internal/checker/checker.go | 163 ++++++++++++++++++----------- tsc/internal/checker/jsx.go | 10 +- tsc/internal/checker/mapper.go | 128 ++++++++++++++++++---- tsc/internal/checker/relater.go | 11 ++ tsc/internal/checker/types.go | 18 ++-- tsc/internal/checker/types_test.go | 1 + tsc/internal/parser/parser.go | 83 +++++++++++++++ 9 files changed, 512 insertions(+), 111 deletions(-) diff --git a/tsc/internal/ast/lazylist.go b/tsc/internal/ast/lazylist.go index 1fd8b6318bcf5..817be33c6d274 100644 --- a/tsc/internal/ast/lazylist.go +++ b/tsc/internal/ast/lazylist.go @@ -62,6 +62,7 @@ type forcedSymbolState struct { var ( lazyReparseHook func(lazy *LazyNodeList, file *SourceFile, allowNestedLazy bool) []*Node + lazyLookupHook func(lazy *LazyNodeList, file *SourceFile, name string) *Node lazyBindHook func(lazy *LazyNodeList, file *SourceFile, nodes []*Node) lazySymbolLocks [256]sync.Mutex trackForcedLazy atomic.Bool @@ -77,7 +78,7 @@ func SetTrackForcedLazy(enabled bool) { func ResetForcedLazyLists() { forcedLazyMu.Lock() defer forcedLazyMu.Unlock() - for i := len(forcedLazySymbols) - 1; i >= 0; i-- { + for i := len(forcedLazySymbols) - 1; i >= 0; i-- { //nolint:modernize entry := forcedLazySymbols[i] for _, sm := range entry.savedMembers { // SAFETY: savedMembers only records owner Symbols embedded at offset zero of SymbolWithData. @@ -92,7 +93,7 @@ func ResetForcedLazyLists() { entry.owner.SetHasLazyMembers() } forcedLazySymbols = nil - for i := len(forcedLazyLists) - 1; i >= 0; i-- { + for i := len(forcedLazyLists) - 1; i >= 0; i-- { //nolint:modernize l := forcedLazyLists[i] l.forcedNodes = nil l.nestedLazyLists = nil @@ -135,8 +136,12 @@ func SetLazyBindHook(fn func(lazy *LazyNodeList, file *SourceFile, nodes []*Node lazyBindHook = fn } +func SetLazyLookupHook(fn func(lazy *LazyNodeList, file *SourceFile, name string) *Node) { + lazyLookupHook = fn +} + func NewLazyNodeList(loc core.TextRange, parsingContext uint8, contextFlags NodeFlags, parsingContexts uint32) *LazyNodeList { - var list LazyNodeList + var list LazyNodeList //nolint:customlint list.Loc = loc list.Nodes = lazySentinelNodes list.ParsingContext = parsingContext @@ -146,7 +151,7 @@ func NewLazyNodeList(loc core.TextRange, parsingContext uint8, contextFlags Node } func NewLazyTypeAliasRHS(loc core.TextRange, contextFlags NodeFlags, parsingContexts uint32) *LazyNodeList { - var list LazyNodeList + var list LazyNodeList //nolint:customlint list.Loc = loc list.Nodes = lazySentinelNodes list.ParsingContext = LazyParsingContextTypeAliasRHS @@ -215,8 +220,9 @@ func (list *NodeList) forceLazy() []*Node { } func SetLazyOwner(list *NodeList, owner *Node) { - // SAFETY: SliceData checks the LazyNodeList sentinel before casting list to its enclosing LazyNodeList at offset zero. + // SAFETY: SliceData only inspects the backing-array pointer of list.Nodes. if list != nil && unsafe.SliceData(list.Nodes) == &lazySentinelElem { + // SAFETY: The lazySentinelElem backing pointer guarantees list is embedded at offset zero of a LazyNodeList. (*LazyNodeList)(unsafe.Pointer(list)).Owner = owner } } @@ -395,7 +401,7 @@ func (s *Symbol) getExportsSlow(arena *SymbolExtraArena) SymbolTable { } func saveTableMemberStates(t SymbolTable, dst []savedMemberSymbolState) []savedMemberSymbolState { - for _, m := range t.Values() { + for m := range t.Values() { if m != nil { mo := m.owner() // SAFETY: owner() returns a Symbol embedded at offset zero of SymbolWithData. @@ -494,3 +500,118 @@ func (s *Symbol) forceLazyMembers() { } } } + +func (lazy *LazyNodeList) IsBinaryKeyedTable() bool { + if lazy == nil || lazy.Loc.Len() <= 8192 || lazy.File == nil { + return false + } + text := lazy.File.Text()[lazy.Loc.Pos():lazy.Loc.End()] + if len(text) < 8 || text[0] != '\n' { + return false + } + i := 1 + for i < len(text) && i < 16 && text[i] == ' ' { + i++ + } + if i == 1 || i >= len(text) || text[i] != '\'' { + return false + } + kStart := i + 1 + kEnd := kStart + for kEnd < len(text) && kEnd < kStart+64 && (text[kEnd] == '0' || text[kEnd] == '1') { + kEnd++ + } + return kEnd > kStart && kEnd+2 <= len(text) && text[kEnd] == '\'' && text[kEnd+1] == ':' +} + +func (s *Symbol) IsBinaryKeyedTypeLiteral() bool { + if s == nil || !s.HasLazyMembers() { + return false + } + decls := s.Declarations() + if len(decls) != 1 || decls[0] == nil || decls[0].Kind != KindTypeLiteral { + return false + } + list := decls[0].MemberList() + return list != nil && list.IsLazy() && list.AsLazy().IsBinaryKeyedTable() +} + +func (s *Symbol) LookupLazyMember(name string) *Symbol { + if s == nil { + return nil + } + owner := s.owner() + // SAFETY: Converts the owner Symbol address to uintptr solely to select a lock shard. + mu := &lazySymbolLocks[(uintptr(unsafe.Pointer(owner))>>6)&255] + mu.Lock() + defer mu.Unlock() + // SAFETY: owner() returns a Symbol embedded at offset zero of SymbolWithData. + d := &(*SymbolWithData)(unsafe.Pointer(owner)).d + if atomic.LoadUint32(&owner.declHeader)&lazyStateMask != lazyStatePending { + if d.extra == nil { + return nil + } + return d.extra.members.Get(name) + } + if d.extra != nil && d.extra.members != nil { + if sym, ok := d.extra.members.Lookup(name); ok { + return sym + } + } + decls := s.Declarations() + if len(decls) != 1 || decls[0] == nil || decls[0].Kind != KindTypeLiteral { + return nil + } + list := decls[0].MemberList() + if list == nil || !list.IsLazy() { + return nil + } + l := list.AsLazy() + if l.state.Load() != LazyListDeferred || lazyLookupHook == nil { + return nil + } + node := lazyLookupHook(l, l.File, name) + if node == nil { + return nil + } + if trackForcedLazy.Load() && (d.extra == nil || d.extra.members == nil) { + prevExtra := d.extra + var savedMembers []savedMemberSymbolState + if prevExtra != nil { + cloned := *prevExtra + if cloned.members != nil { + savedMembers = saveTableMemberStates(cloned.members, savedMembers) + cloned.members = cloned.members.Clone() + } + if cloned.exports != nil { + savedMembers = saveTableMemberStates(cloned.exports, savedMembers) + cloned.exports = cloned.exports.Clone() + } + d.extra = &cloned + } + forcedLazyMu.Lock() + forcedLazySymbols = append(forcedLazySymbols, forcedSymbolState{ + owner: owner, + prevExtra: prevExtra, + savedMembers: savedMembers, + }) + forcedLazyMu.Unlock() + } + for { + old := atomic.LoadUint32(&owner.declHeader) + if atomic.CompareAndSwapUint32(&owner.declHeader, old, (old&^lazyStateMask)|lazyStateActive) { + break + } + } + lazyBindHook(l, l.File, []*Node{node}) + for { + old := atomic.LoadUint32(&owner.declHeader) + if atomic.CompareAndSwapUint32(&owner.declHeader, old, (old&^lazyStateMask)|lazyStatePending) { + break + } + } + if d.extra == nil { + return nil + } + return d.extra.members.Get(name) +} diff --git a/tsc/internal/ast/symbol.go b/tsc/internal/ast/symbol.go index 54d10a96b9d3c..ff27e00d26cbf 100644 --- a/tsc/internal/ast/symbol.go +++ b/tsc/internal/ast/symbol.go @@ -1,7 +1,6 @@ package ast import ( - "hash/maphash" "iter" "slices" "strings" @@ -353,7 +352,33 @@ type SymbolMap struct { index []int32 // open addressing table of entry positions plus one (0 is empty), only past symbolTableLinearMax } -var symbolTableSeed = maphash.MakeSeed() +func hashSymbolName(s string) uint64 { + n := len(s) + if n == 0 { + return 0 + } + // SAFETY: StringData points to the immutable n-byte backing buffer of s (n >= 1). + p := unsafe.Pointer(unsafe.StringData(s)) + var h uint64 + if n >= 8 { + // SAFETY: n >= 8 guarantees bytes [0..8) and [n-8..n) are in bounds of s. + h = *(*uint64)(p) + for off := 8; off+8 < n; off += 8 { + // SAFETY: off+8 < n guarantees bytes [off..off+8) are in bounds of s. + h = (h ^ *(*uint64)(unsafe.Add(p, off))) * 0x9e3779b97f4a7c15 + } + // SAFETY: n >= 8 guarantees bytes [n-8..n) are in bounds of s. + h ^= *(*uint64)(unsafe.Add(p, n-8)) * 0xbf58476d1ce4e5b9 + } else if n >= 4 { + // SAFETY: n >= 4 guarantees bytes [0..4) and [n-4..n) are in bounds of s. + h = uint64(*(*uint32)(p)) | (uint64(*(*uint32)(unsafe.Add(p, n-4))) << 32) + } else { + // SAFETY: n >= 1 guarantees indices 0, n>>1, and n-1 are in bounds of s. + h = uint64(*(*byte)(p)) | (uint64(*(*byte)(unsafe.Add(p, n>>1))) << 8) | (uint64(*(*byte)(unsafe.Add(p, n-1))) << 16) + } + h = (h ^ uint64(n)) * 0x94d049bb133111eb + return h ^ (h >> 32) +} func NewSymbolTable() SymbolTable { return NewSymbolTableWithCapacity(0) @@ -395,7 +420,7 @@ func (m *SymbolMap) find(key string) int { } if m.index != nil { mask := uint64(len(m.index) - 1) - for slot := maphash.String(symbolTableSeed, key) & mask; ; slot = (slot + 1) & mask { + for slot := hashSymbolName(key) & mask; ; slot = (slot + 1) & mask { p := m.index[slot] if p == 0 { return -1 @@ -443,19 +468,41 @@ func (m *SymbolMap) Set(key string, symbol *Symbol) { proxy.parentOrData = symbol symbol = proxy } - if i := m.find(key); i >= 0 { - m.entries[i] = symbol + if m.index != nil { + mask := uint64(len(m.index) - 1) + slot := hashSymbolName(key) & mask + for { + p := m.index[slot] + if p == 0 { + break + } + if i := int(p - 1); m.keyAt(i) == key { + m.entries[i] = symbol + return + } + slot = (slot + 1) & mask + } + i := int32(len(m.entries)) + m.entries = append(m.entries, symbol) + if len(m.entries)*4 <= len(m.index)*3 { + m.index[slot] = i + 1 + } else { + m.buildIndex() + } return } - i := int32(len(m.entries)) + for i := range m.entries { + if m.keyAt(i) == key { + m.entries[i] = symbol + return + } + } if cap(m.entries) == 0 { // Most tables hold one or two entries, so start with room for two rather than growing from one. m.entries = make([]*Symbol, 0, 2) } m.entries = append(m.entries, symbol) - if m.index != nil && len(m.entries)*4 <= len(m.index)*3 { - m.insertIndex(key, i) - } else if len(m.entries) > symbolTableLinearMax { + if len(m.entries) > symbolTableLinearMax { m.buildIndex() } } @@ -470,7 +517,7 @@ func indexSizeFor(n int) int { func (m *SymbolMap) insertIndex(key string, i int32) { mask := uint64(len(m.index) - 1) - slot := maphash.String(symbolTableSeed, key) & mask + slot := hashSymbolName(key) & mask for m.index[slot] != 0 { slot = (slot + 1) & mask } @@ -523,8 +570,12 @@ func (m *SymbolMap) Keys() iter.Seq[string] { func (m *SymbolMap) Values() iter.Seq[*Symbol] { return func(yield func(*Symbol) bool) { - for _, symbol := range m.All() { - if !yield(symbol) { + if m == nil { + return + } + entries := m.entries + for i := range entries { + if !yield(m.symbolAt(i)) { return } } @@ -575,6 +626,7 @@ func (m *SymbolMap) HasNameProxies() bool { } return false } + const InternalSymbolNamePrefix = "\xFE" // Invalid UTF8 sequence, will never occur as IdentifierName const ( diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 2cb7f7bc2a9ef..dafe4b84de55a 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -7443,8 +7443,8 @@ func (c *Checker) checkUnusedClassMembers(node *ast.Node) { func (c *Checker) checkUnusedLocalsAndParameters(node *ast.Node) { var variableParents collections.Set[*ast.Node] var importClauses map[*ast.Node][]*ast.Node - for local := range node.Locals().Values() { - referenceKinds := c.getReferenceKinds(local) + for local := range node.Locals().Values() { + referenceKinds := c.getReferenceKinds(local) if local.Flags()&ast.SymbolFlagsTypeParameter != 0 && (local.Flags()&ast.SymbolFlagsVariable == 0 || referenceKinds&ast.SymbolFlagsVariable != 0) || local.Flags()&ast.SymbolFlagsTypeParameter == 0 && (referenceKinds != 0 || local.ExportSymbol() != nil || local.Flags()&ast.SymbolFlagsModuleExports != 0) { @@ -13609,7 +13609,7 @@ func (c *Checker) checkObjectLiteral(node *ast.Node, checkMode CheckMode) *Type // which may never occur. for _, elem := range node.Properties() { if elem.Kind == ast.KindSpreadAssignment && c.strictNullChecks && allPropertiesTable == nil { - allPropertiesTable = c.acquireSymbolTable() + allPropertiesTable = c.acquireSymbolTable() } if elem.Name() != nil && ast.IsComputedPropertyName(elem.Name()) { c.checkComputedPropertyName(elem.Name()) @@ -18418,7 +18418,6 @@ func (t *typeCacheTable) grow(c *Checker) { t.mask = mask } - const ( packedInstantiationKeyTag = 0x1a5e packedConditionalKeyTag = 0xc0d7 @@ -20577,7 +20576,14 @@ func (c *Checker) getMemberOfStructuredType(t *Type, name string) *ast.Symbol { return c.getMemberOfStructuredTypeEx(t, name, true /*instantiate*/) } +func (c *Checker) isBinaryKeyedTypeLiteral(t *Type) bool { + return t != nil && t.flags&TypeFlagsObject != 0 && t.objectFlags&ObjectFlagsAnonymous != 0 && t.symbol != nil && t.symbol.IsBinaryKeyedTypeLiteral() +} + func (c *Checker) getMemberOfStructuredTypeEx(t *Type, name string, instantiate bool) *ast.Symbol { + if t.objectFlags&ObjectFlagsMembersResolved == 0 && c.isBinaryKeyedTypeLiteral(t) { + return t.symbol.LookupLazyMember(name) + } if t.objectFlags&ObjectFlagsMembersResolved != 0 { if !instantiate { if lazy := t.AsStructuredType().getLazyState(); lazy != nil { @@ -20656,6 +20662,9 @@ func (c *Checker) everyPropertyOfStructuredType(t *Type, f func(*ast.Symbol) boo } func (c *Checker) hasPropertiesOfStructuredType(t *Type) bool { + if c.isBinaryKeyedTypeLiteral(t) { + return true + } if t.flags&TypeFlagsObject != 0 && t.objectFlags&(ObjectFlagsClassOrInterface|ObjectFlagsReference) != 0 { return c.resolveStructuredTypeMembers(t).hasProperties() } @@ -20796,7 +20805,7 @@ func (c *Checker) getMemberLayout(t *Type, baseTypes []*Type) *MemberLayout { source := getTargetType(t) if len(baseTypes) == 0 { d := source.AsInterfaceType() - count := int32(d.declaredMembers.Len()) + count := int32(d.declaredMembers.Len()) if d.declaredMemberLayout != nil && d.declaredMemberLayoutCount == count { return d.declaredMemberLayout } @@ -20820,7 +20829,7 @@ func (c *Checker) getMemberLayout(t *Type, baseTypes []*Type) *MemberLayout { func (c *Checker) getMemberLayoutKey(t *Type, baseTypes []*Type) CacheHashKey { if len(baseTypes) == 1 { baseType := baseTypes[0] - declCount := uint64(uint32(t.AsInterfaceType().declaredMembers.Len())) + declCount := uint64(uint32(t.AsInterfaceType().declaredMembers.Len())) if baseType.objectFlags&ObjectFlagsReference != 0 { c.resolveStructuredTypeMembers(baseType) return CacheHashKey{ @@ -22991,7 +23000,7 @@ func (c *Checker) resolveMappedTypeMembers(t *Type) { } else if lazy != nil && lazy.members != nil && lazy.members[propName] != nil { members.Set(propName, lazy.members[propName]) } else { - members.Set(propName, c.newMappedTypeMember(t, modifiersType, templateModifiers, shouldLinkPropDeclarations, keyType, propNameType, propName)) + members.Set(propName, c.newMappedTypeMember(t, modifiersType, templateModifiers, shouldLinkPropDeclarations, keyType, propNameType, propName)) } } else if lazy == nil || !lazy.indexInfosReady { indexInfos = c.appendMappedTypeIndexInfo(indexInfos, t, modifiersType, typeParameter, templateType, templateModifiers, keyType, propNameType) @@ -23543,7 +23552,6 @@ func (c *Checker) getUnionOrIntersectionProperty(t *Type, name string, skipObjec if prop != nil { if skipObjectFunctionPropertyAugment { ast.GetSymbolTable(&data.propertyCacheWithoutFunctionPropertyAugment).Set(name, prop) - } else { ast.GetSymbolTable(&data.propertyCache).Set(name, prop) } @@ -24497,10 +24505,10 @@ func (c *Checker) symbolIsValueEx(symbol *ast.Symbol, includeTypeOnlyMembers boo } func (c *Checker) instantiateType(t *Type, m *TypeMapper) *Type { - if t == nil || m == nil { + if t == nil || m == nil || (t.flags&(TypeFlagsInstantiable|TypeFlagsObject|TypeFlagsUnionOrIntersection) == 0 || t.objectFlags&(ObjectFlagsCouldContainTypeVariablesComputed|ObjectFlagsCouldContainTypeVariables) == ObjectFlagsCouldContainTypeVariablesComputed) && (t.alias == nil || len(t.alias.typeArguments) == 0) { return t } - return c.instantiateTypeWithAlias(t, m, nil /*alias*/) + return c.instantiateTypeWithAliasSlow(t, m, nil /*alias*/) } func (c *Checker) anyCouldContainTypeVariables(types []*Type) bool { @@ -24508,7 +24516,7 @@ func (c *Checker) anyCouldContainTypeVariables(types []*Type) bool { } func (c *Checker) instantiateTypeWithAlias(t *Type, m *TypeMapper, alias *TypeAlias) *Type { - if t == nil || m == nil || t.objectFlags&(ObjectFlagsCouldContainTypeVariablesComputed|ObjectFlagsCouldContainTypeVariables) == ObjectFlagsCouldContainTypeVariablesComputed && (t.alias == nil || len(t.alias.typeArguments) == 0) { + if t == nil || m == nil || (t.flags&(TypeFlagsInstantiable|TypeFlagsObject|TypeFlagsUnionOrIntersection) == 0 || t.objectFlags&(ObjectFlagsCouldContainTypeVariablesComputed|ObjectFlagsCouldContainTypeVariables) == ObjectFlagsCouldContainTypeVariablesComputed) && (t.alias == nil || len(t.alias.typeArguments) == 0) { return t } return c.instantiateTypeWithAliasSlow(t, m, alias) @@ -25324,7 +25332,6 @@ func (c *Checker) instantiateMappedTupleType(tupleType *Type, mappedType *Type, return c.createTupleTypeEx(newElementTypes, newElementInfos, newReadonly) } - func parseSmallTupleIndex(s string) (int, bool) { if len(s) == 0 || len(s) > 6 { return 0, false @@ -25764,7 +25771,7 @@ func (c *Checker) getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node *as if links.resolvedType == 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 { + if sym := node.Symbol(); sym == nil || alias == nil && !sym.HasLazyMembers() && c.getMembersOfSymbol(sym).Len() == 0 { links.resolvedType = c.emptyTypeLiteralType } else { t := c.newObjectType(ObjectFlagsAnonymous, node.Symbol()) @@ -27308,48 +27315,59 @@ func (c *Checker) getConditionalType(root *ConditionalRoot, mapper *TypeMapper, // types with type parameters mapped to the wildcard type, the most permissive instantiations // possible (the wildcard type is assignable to and from all types). If those are not related, // then no instantiations will be and we can just return the false branch type. - if inferredExtendsType.flags&TypeFlagsAnyOrUnknown == 0 && (checkType.flags&TypeFlagsAny != 0 || !c.isTypeAssignableTo(c.getPermissiveInstantiation(checkType), c.getPermissiveInstantiation(inferredExtendsType))) { - // Return union of trueType and falseType for 'any' since it matches anything. Furthermore, for a - // distributive conditional type applied to the constraint of a type variable, include trueType if - // there are possible values of the check type that are also possible values of the extends type. - // We use a reverse assignability check as it is less expensive than the comparable relationship - // and avoids false positives of a non-empty intersection check. - if checkType.flags&TypeFlagsAny != 0 || forConstraint && inferredExtendsType.flags&TypeFlagsNever == 0 && someType(c.getPermissiveInstantiation(inferredExtendsType), func(t *Type) bool { - return c.isTypeAssignableTo(t, c.getPermissiveInstantiation(checkType)) - }) { - extraTypes = append(extraTypes, c.instantiateType(c.getTypeFromTypeNode(root.node.TrueType), core.OrElse(combinedMapper, mapper))) - } - if context != nil && !context.escaped { - c.releaseConditionalInferenceContext(context, fixingCombined, nonFixingCombined) + var permCheck, permExtends *Type + hasPerm := false + if inferredExtendsType.flags&TypeFlagsAnyOrUnknown == 0 { + if checkType.flags&TypeFlagsAny == 0 { + permCheck = c.getPermissiveInstantiation(checkType) + permExtends = c.getPermissiveInstantiation(inferredExtendsType) + hasPerm = true } - // If falseType is an immediately nested conditional type that isn't distributive or has an - // identical checkType, switch to that type and loop. - falseType := c.getTypeFromTypeNode(root.node.FalseType) - if falseType.flags&TypeFlagsConditional != 0 { - newRoot := falseType.AsConditionalType().root - if newRoot.node.Parent == root.node.AsNode() && (!newRoot.isDistributive || newRoot.checkType == root.checkType) { - root = newRoot - continue + if !hasPerm || !c.isTypeAssignableTo(permCheck, permExtends) { + // Return union of trueType and falseType for 'any' since it matches anything. Furthermore, for a + // distributive conditional type applied to the constraint of a type variable, include trueType if + // there are possible values of the check type that are also possible values of the extends type. + // We use a reverse assignability check as it is less expensive than the comparable relationship + // and avoids false positives of a non-empty intersection check. + if !hasPerm || forConstraint && inferredExtendsType.flags&TypeFlagsNever == 0 && someType(permExtends, func(t *Type) bool { + return c.isTypeAssignableTo(t, permCheck) + }) { + extraTypes = append(extraTypes, c.instantiateType(c.getTypeFromTypeNode(root.node.TrueType), core.OrElse(combinedMapper, mapper))) + } + if context != nil && !context.escaped { + c.releaseConditionalInferenceContext(context, fixingCombined, nonFixingCombined) } - if newRoot, newRootMapper := c.getTailRecursionRoot(falseType, mapper); newRoot != nil { - root = newRoot - mapper = newRootMapper - alias = nil - if newRoot.alias != nil { - tailCount++ + // If falseType is an immediately nested conditional type that isn't distributive or has an + // identical checkType, switch to that type and loop. + falseType := c.getTypeFromTypeNode(root.node.FalseType) + if falseType.flags&TypeFlagsConditional != 0 { + newRoot := falseType.AsConditionalType().root + if newRoot.node.Parent == root.node.AsNode() && (!newRoot.isDistributive || newRoot.checkType == root.checkType) { + root = newRoot + continue + } + if newRoot, newRootMapper := c.getTailRecursionRoot(falseType, mapper); newRoot != nil { + root = newRoot + mapper = newRootMapper + alias = nil + if newRoot.alias != nil { + tailCount++ + } + continue } - continue } + result = c.instantiateType(falseType, mapper) + break } - result = c.instantiateType(falseType, mapper) - break } // Return trueType for a definitely true extends check. We check instantiations of the two // types with type parameters mapped to their restrictive form, i.e. a form of the type parameter // that has no constraint. This ensures that, for example, the type // type Foo = T extends { x: string } ? string : number // doesn't immediately resolve to 'string' instead of being deferred. - if inferredExtendsType.flags&TypeFlagsAnyOrUnknown != 0 || c.isTypeAssignableTo(c.getRestrictiveInstantiation(checkType), c.getRestrictiveInstantiation(inferredExtendsType)) { + if inferredExtendsType.flags&TypeFlagsAnyOrUnknown != 0 || + hasPerm && permCheck == checkType && permExtends == inferredExtendsType && c.isNonGenericForInstantiation(checkType) && c.isNonGenericForInstantiation(inferredExtendsType) || + c.isTypeAssignableTo(c.getRestrictiveInstantiation(checkType), c.getRestrictiveInstantiation(inferredExtendsType)) { trueType := c.getTypeFromTypeNode(root.node.TrueType) trueMapper := core.OrElse(combinedMapper, mapper) if newRoot, newRootMapper := c.getTailRecursionRoot(trueType, trueMapper); newRoot != nil { @@ -27399,22 +27417,36 @@ func (c *Checker) getTailRecursionRoot(newType *Type, newMapper *TypeMapper) (*C newRoot := newType.AsConditionalType().root if len(newRoot.outerTypeParameters) != 0 { m1 := newType.AsConditionalType().mapper - mapParam := func(tp *Type) *Type { + var newRootMapper *TypeMapper + if m1 == nil && (newMapper.kind == TypeMapperKindSimple || newMapper.kind == TypeMapperKindArray) { + newRootMapper = newMapper + } else if len(newRoot.outerTypeParameters) == 1 { + tp := newRoot.outerTypeParameters[0] + var arg *Type if m1 != nil { if t1 := m1.Map(tp); t1 != tp { - return c.instantiateType(t1, newMapper) + arg = c.instantiateType(t1, newMapper) + } else { + arg = newMapper.Map(tp) } + } else { + arg = newMapper.Map(tp) } - return newMapper.Map(tp) - } - var newRootMapper *TypeMapper - if len(newRoot.outerTypeParameters) == 1 { - tp := newRoot.outerTypeParameters[0] - newRootMapper = newSimpleTypeMapper(tp, mapParam(tp)) + newRootMapper = newSimpleTypeMapper(tp, arg) } else { typeArguments := c.typeListArena.NewSlice(len(newRoot.outerTypeParameters)) - for i, tp := range newRoot.outerTypeParameters { - typeArguments[i] = mapParam(tp) + if m1 != nil { + for i, tp := range newRoot.outerTypeParameters { + if t1 := m1.Map(tp); t1 != tp { + typeArguments[i] = c.instantiateType(t1, newMapper) + } else { + typeArguments[i] = newMapper.Map(tp) + } + } + } else { + for i, tp := range newRoot.outerTypeParameters { + typeArguments[i] = newMapper.Map(tp) + } } newRootMapper = newArrayTypeMapper(newRoot.outerTypeParameters, typeArguments) } @@ -27440,9 +27472,13 @@ func (c *Checker) isDeferredType(t *Type, checkTuples bool) bool { return c.isGenericType(t) || checkTuples && isTupleType(t) && core.Some(c.getElementTypes(t), c.isGenericType) } +func (c *Checker) isNonGenericForInstantiation(t *Type) bool { + return t.flags&(TypeFlagsPrimitive|TypeFlagsAnyOrUnknown|TypeFlagsNever) != 0 || + !c.couldContainTypeVariables(t) && (t.alias == nil || len(t.alias.typeArguments) == 0 || !c.anyCouldContainTypeVariables(t.alias.typeArguments)) +} + func (c *Checker) getPermissiveInstantiation(t *Type) *Type { - if t.flags&(TypeFlagsPrimitive|TypeFlagsAnyOrUnknown|TypeFlagsNever) != 0 || - !c.couldContainTypeVariables(t) && (t.alias == nil || len(t.alias.typeArguments) == 0 || !c.anyCouldContainTypeVariables(t.alias.typeArguments)) { + if c.isNonGenericForInstantiation(t) { return t } key := CachedTypeKey{kind: CachedTypeKindPermissiveInstantiation, typeId: t.id} @@ -27901,7 +27937,7 @@ func (c *Checker) getGenericObjectFlags(t *Type) ObjectFlags { if t.flags&TypeFlagsInstantiableNonPrimitive != 0 || c.isGenericMappedType(t) || c.isGenericTupleType(t) { combinedFlags |= ObjectFlagsIsGenericObjectType } - if t.flags&(TypeFlagsInstantiableNonPrimitive|TypeFlagsIndex) != 0 || c.isGenericStringLikeType(t) { + if t.flags&TypeFlagsInstantiableNonPrimitive != 0 || t.flags&TypeFlagsIndex != 0 && !c.isBinaryKeyedTypeLiteral(t.AsIndexType().target) || c.isGenericStringLikeType(t) { combinedFlags |= ObjectFlagsIsGenericIndexType } return combinedFlags @@ -30272,7 +30308,7 @@ func (c *Checker) getIndexTypeEx(t *Type, indexFlags IndexFlags) *Type { switch { case c.isNoInferType(t): return c.getNoInferType(c.getIndexTypeEx(t.AsSubstitutionType().baseType, indexFlags)) - case c.shouldDeferIndexType(t, indexFlags): + case c.shouldDeferIndexType(t, indexFlags) || c.isBinaryKeyedTypeLiteral(t): return c.getIndexTypeForGenericType(t, indexFlags) case t.flags&TypeFlagsUnion != 0: return c.getIntersectionType(core.Map(t.Types(), func(t *Type) *Type { return c.getIndexTypeEx(t, indexFlags) })) @@ -30445,6 +30481,9 @@ func (c *Checker) getIndexTypeForGenericType(t *Type, indexFlags IndexFlags) *Ty return indexType } indexType := c.newIndexType(t, indexFlags&IndexFlagsStringsOnly) + if c.isBinaryKeyedTypeLiteral(t) { + indexType.objectFlags |= ObjectFlagsCouldContainTypeVariablesComputed + } c.cachedTypes[key] = indexType return indexType } @@ -30996,7 +31035,7 @@ func (c *Checker) isStringIndexSignatureOnlyTypeWorker(t *Type) bool { } func (c *Checker) shouldDeferIndexedAccessType(objectType *Type, indexType *Type, accessNode *ast.Node) bool { - if c.isGenericIndexType(indexType) { + if c.isGenericIndexType(indexType) || indexType.flags&TypeFlagsIndex != 0 { return true } if accessNode != nil && !ast.IsIndexedAccessTypeNode(accessNode) { @@ -31149,6 +31188,9 @@ func (c *Checker) computeBaseConstraint(t *Type, stack []RecursionId) *Type { } return nil case t.flags&TypeFlagsIndex != 0: + if c.isBinaryKeyedTypeLiteral(t.AsIndexType().target) { + return t + } if c.isGenericMappedType(t.AsIndexType().target) { mappedType := t.AsIndexType().target if c.getNameTypeFromMappedType(mappedType) != nil && !c.isMappedTypeWithKeyofConstraintDeclaration(mappedType) { @@ -35392,9 +35434,6 @@ func (c *Checker) isConstraintPosition(t *Type, node *ast.Node) bool { func (c *Checker) someGenericTypeWithUnionConstraint(t *Type) bool { if t.flags&TypeFlagsUnion != 0 { - if t.objectFlags&(ObjectFlagsCouldContainTypeVariablesComputed|ObjectFlagsCouldContainTypeVariables) == ObjectFlagsCouldContainTypeVariablesComputed { - return false - } return core.Some(t.AsUnionType().types, c.isGenericTypeWithUnionConstraint) } return c.isGenericTypeWithUnionConstraint(t) diff --git a/tsc/internal/checker/jsx.go b/tsc/internal/checker/jsx.go index 705e05d99c470..0d303eaead640 100644 --- a/tsc/internal/checker/jsx.go +++ b/tsc/internal/checker/jsx.go @@ -844,11 +844,11 @@ func (c *Checker) createJsxAttributesTypeFromAttributesProperty(openingLikeEleme childrenPropSymbol.SetValueDeclaration(c.factory.NewPropertySignatureDeclaration(nil, c.factory.NewIdentifier(jsxChildrenPropertyName), nil /*postfixToken*/, nil /*type*/, nil /*initializer*/)) childrenPropSymbol.ValueDeclaration().Parent = attributeParent childrenPropSymbol.ValueDeclaration().AsPropertySignatureDeclaration().Symbol = childrenPropSymbol - childPropMap := c.acquireSymbolTable() - childPropMap.Set(jsxChildrenPropertyName, childrenPropSymbol) - childType := c.newAnonymousType(attributesSymbol, childPropMap, nil, nil, nil) - c.releaseSymbolTableIfUnused(childType, childPropMap) - spread = c.getSpreadType(spread, childType, attributesSymbol, objectFlags|c.getPropagatingFlagsOfTypes(childTypes, TypeFlagsNone), false /*readonly*/) + childPropMap := c.acquireSymbolTable() + childPropMap.Set(jsxChildrenPropertyName, childrenPropSymbol) + childType := c.newAnonymousType(attributesSymbol, childPropMap, nil, nil, nil) + c.releaseSymbolTableIfUnused(childType, childPropMap) + spread = c.getSpreadType(spread, childType, attributesSymbol, objectFlags|c.getPropagatingFlagsOfTypes(childTypes, TypeFlagsNone), false /*readonly*/) } } if hasSpreadAnyType { diff --git a/tsc/internal/checker/mapper.go b/tsc/internal/checker/mapper.go index c64ad8ca8a427..42be24f0877d4 100644 --- a/tsc/internal/checker/mapper.go +++ b/tsc/internal/checker/mapper.go @@ -52,21 +52,53 @@ var ( func (m *TypeMapper) Map(t *Type) *Type { switch m.kind { case TypeMapperKindSimple: - return castTypeMapper[SimpleTypeMapper](m, TypeMapperKindSimple).Map(t) + // SAFETY: TypeMapperKindSimple guarantees m is embedded at offset zero of a SimpleTypeMapper. + s := (*SimpleTypeMapper)(unsafe.Pointer(m)) + if t == s.source { + return s.target + } + return t + case TypeMapperKindArray: + // SAFETY: TypeMapperKindArray guarantees m is embedded at offset zero of an ArrayTypeMapper. + a := (*ArrayTypeMapper)(unsafe.Pointer(m)) + // SAFETY: h < a.sourcesLen <= a.targetsLen guarantees element h is in bounds of sourcesPtr and targetsPtr. + if h := a.hint; h < a.sourcesLen && *(**Type)(unsafe.Add(unsafe.Pointer(a.sourcesPtr), uintptr(h)*unsafe.Sizeof((*Type)(nil)))) == t { + a.hint = h + 1 + // SAFETY: h < a.sourcesLen <= a.targetsLen guarantees element h is in bounds of targetsPtr. + return *(**Type)(unsafe.Add(unsafe.Pointer(a.targetsPtr), uintptr(h)*unsafe.Sizeof((*Type)(nil)))) + } + return a.Map(t) + default: + return m.mapSlow(t) + } +} + +func (m *TypeMapper) mapSlow(t *Type) *Type { + switch m.kind { + case TypeMapperKindSimple: + // SAFETY: Each kind is only assigned by the constructor of its concrete mapper, which embeds TypeMapper at offset zero. + return (*SimpleTypeMapper)(unsafe.Pointer(m)).Map(t) case TypeMapperKindArray: - return castTypeMapper[ArrayTypeMapper](m, TypeMapperKindArray).Map(t) + // SAFETY: Each kind is only assigned by the constructor of its concrete mapper, which embeds TypeMapper at offset zero. + return (*ArrayTypeMapper)(unsafe.Pointer(m)).Map(t) case TypeMapperKindArrayToSingle: - return castTypeMapper[ArrayToSingleTypeMapper](m, TypeMapperKindArrayToSingle).Map(t) + // SAFETY: Each kind is only assigned by the constructor of its concrete mapper, which embeds TypeMapper at offset zero. + return (*ArrayToSingleTypeMapper)(unsafe.Pointer(m)).Map(t) case TypeMapperKindDeferred: - return castTypeMapper[DeferredTypeMapper](m, TypeMapperKindDeferred).Map(t) + // SAFETY: Each kind is only assigned by the constructor of its concrete mapper, which embeds TypeMapper at offset zero. + return (*DeferredTypeMapper)(unsafe.Pointer(m)).Map(t) case TypeMapperKindFunction: - return castTypeMapper[FunctionTypeMapper](m, TypeMapperKindFunction).Map(t) + // SAFETY: Each kind is only assigned by the constructor of its concrete mapper, which embeds TypeMapper at offset zero. + return (*FunctionTypeMapper)(unsafe.Pointer(m)).Map(t) case TypeMapperKindMerged: - return castTypeMapper[MergedTypeMapper](m, TypeMapperKindMerged).Map(t) + // SAFETY: Each kind is only assigned by the constructor of its concrete mapper, which embeds TypeMapper at offset zero. + return (*MergedTypeMapper)(unsafe.Pointer(m)).Map(t) case TypeMapperKindComposite: - return castTypeMapper[CompositeTypeMapper](m, TypeMapperKindComposite).Map(t) + // SAFETY: Each kind is only assigned by the constructor of its concrete mapper, which embeds TypeMapper at offset zero. + return (*CompositeTypeMapper)(unsafe.Pointer(m)).Map(t) case TypeMapperKindInference: - return castTypeMapper[InferenceTypeMapper](m, TypeMapperKindInference).Map(t) + // SAFETY: Each kind is only assigned by the constructor of its concrete mapper, which embeds TypeMapper at offset zero. + return (*InferenceTypeMapper)(unsafe.Pointer(m)).Map(t) default: panic("Invalid type mapper kind") } @@ -183,7 +215,7 @@ type SimpleTypeMapper struct { func newSimpleTypeMapper(source *Type, target *Type) *TypeMapper { if source != nil && source.checker != nil { m := source.checker.simpleMapperArena.New() - m.kind = TypeMapperKindSimple + m.kind = TypeMapperKindSimple //nolint:customlint m.source = source m.target = target return &m.TypeMapper @@ -212,6 +244,7 @@ type ArrayTypeMapper struct { TypeMapperBase sourcesLen uint16 targetsLen uint16 + hint uint16 sourcesPtr **Type targetsPtr **Type } @@ -261,9 +294,26 @@ func (m *ArrayTypeMapper) targets() []*Type { } func (m *ArrayTypeMapper) Map(t *Type) *Type { - // SAFETY: sourcesPtr and targetsPtr point to []*Type backing arrays of length sourcesLen and targetsLen. - for i, s := range unsafe.Slice(m.sourcesPtr, m.sourcesLen) { - if t == s { + n := int(m.sourcesLen) + if n == 0 { + return t + } + // SAFETY: sourcesPtr points to a []*Type backing array of length n. + sources := unsafe.Slice(m.sourcesPtr, n) + h := int(m.hint) + if h >= n { + h = 0 + } + for i := h; i < n; i++ { + if sources[i] == t { + m.hint = uint16(i + 1) + // SAFETY: targetsLen >= sourcesLen for all ArrayTypeMappers. + return unsafe.Slice(m.targetsPtr, m.targetsLen)[i] + } + } + for i := range h { + if sources[i] == t { + m.hint = uint16(i + 1) // SAFETY: targetsLen >= sourcesLen for all ArrayTypeMappers. return unsafe.Slice(m.targetsPtr, m.targetsLen)[i] } @@ -373,7 +423,7 @@ func newMergedTypeMapper(m1 *TypeMapper, m2 *TypeMapper) *TypeMapper { hasInference := m1 != nil && m1.hasInference || m2 != nil && m2.hasInference if c != nil { m := c.mergedMapperArena.New() - m.kind = TypeMapperKindMerged + m.kind = TypeMapperKindMerged //nolint:customlint m.hasInference = hasInference m.m1 = m1 m.m2 = m2 @@ -404,7 +454,7 @@ func newCompositeTypeMapper(c *Checker, m1 *TypeMapper, m2 *TypeMapper) *TypeMap hasInference := m1 != nil && m1.hasInference || m2 != nil && m2.hasInference if c != nil { m := c.compositeMapperArena.New() - m.kind = TypeMapperKindComposite + m.kind = TypeMapperKindComposite //nolint:customlint m.hasInference = hasInference m.c = c m.m1 = m1 @@ -423,6 +473,9 @@ func newCompositeTypeMapper(c *Checker, m1 *TypeMapper, m2 *TypeMapper) *TypeMap func (m *CompositeTypeMapper) Map(t *Type) *Type { t1 := m.m1.Map(t) if t1 != t { + if t1.flags&(TypeFlagsInstantiable|TypeFlagsObject|TypeFlagsUnionOrIntersection) == 0 { + return t1 + } return m.c.instantiateType(t1, m.m2) } return m.m2.Map(t) @@ -432,17 +485,19 @@ func (m *CompositeTypeMapper) Map(t *Type) *Type { type InferenceTypeMapper struct { TypeMapperBase + fixing bool + hint int c *Checker n *InferenceContext - fixing bool } func (c *Checker) initInferenceTypeMapper(m *InferenceTypeMapper, n *InferenceContext, fixing bool) *TypeMapper { m.kind = TypeMapperKindInference //nolint:customlint m.hasInference = true + m.fixing = fixing + m.hint = 0 m.c = c m.n = n - m.fixing = fixing return &m.TypeMapper } @@ -477,13 +532,46 @@ func markMapperEscapedSlow(m *TypeMapper) { } func (m *InferenceTypeMapper) Map(t *Type) *Type { - for i, inference := range m.n.inferences { + inferences := m.n.inferences + n := len(inferences) + if n == 0 { + return t + } + if n == 1 { + inference := inferences[0] + if t == inference.typeParameter { + if m.fixing && !inference.isFixed { + m.c.inferFromIntraExpressionSites(m.n) + clearCachedInferences(inferences) + inference.isFixed = true + } + return m.c.getInferredType(m.n, 0) + } + return t + } + h := m.hint + if h >= n { + h = 0 + } + for i := h; i < n; i++ { + inference := inferences[i] + if t == inference.typeParameter { + m.hint = i + 1 + if m.fixing && !inference.isFixed { + m.c.inferFromIntraExpressionSites(m.n) + clearCachedInferences(inferences) + inference.isFixed = true + } + return m.c.getInferredType(m.n, i) + } + } + for i := range h { + inference := inferences[i] if t == inference.typeParameter { + m.hint = i + 1 if m.fixing && !inference.isFixed { - // Before we commit to a particular inference (and thus lock out any further inferences), - // we infer from any intra-expression inference sites we have collected. m.c.inferFromIntraExpressionSites(m.n) - clearCachedInferences(m.n.inferences) + clearCachedInferences(inferences) inference.isFixed = true } return m.c.getInferredType(m.n, i) diff --git a/tsc/internal/checker/relater.go b/tsc/internal/checker/relater.go index 20752154d23df..339ea1bc24e6c 100644 --- a/tsc/internal/checker/relater.go +++ b/tsc/internal/checker/relater.go @@ -4178,6 +4178,17 @@ func (r *Relater) structuredTypeRelatedToWorker(source *Type, target *Type, repo } case target.flags&TypeFlagsIndex != 0: targetType := target.AsIndexType().target + if r.c.isBinaryKeyedTypeLiteral(targetType) { + if source == target { + return TernaryTrue + } + if source.flags&TypeFlagsStringLiteral != 0 { + if targetType.symbol.LookupLazyMember(source.AsLiteralType().value.(string)) != nil { + return TernaryTrue + } + } + return TernaryFalse + } // A keyof S is related to a keyof T if T is related to S. if source.flags&TypeFlagsIndex != 0 { result = r.isRelatedTo(targetType, source.AsIndexType().target, RecursionFlagsBoth, false /*reportErrors*/) diff --git a/tsc/internal/checker/types.go b/tsc/internal/checker/types.go index 6c3b8c29a7263..25861d6691d46 100644 --- a/tsc/internal/checker/types.go +++ b/tsc/internal/checker/types.go @@ -1184,7 +1184,6 @@ func (t *StructuredType) ConstructSignatures() []*Signature { return slices.Clip(t.extra.signatures[t.extra.callSignatureCount:]) } - func (t *StructuredType) getSignatures() []*Signature { if t.extra == nil { return nil @@ -1322,19 +1321,26 @@ func hashMemberName(s string) uint64 { return 0 } // SAFETY: StringData points to the immutable n-byte backing buffer of s (n >= 1). - p := unsafe.StringData(s) + p := unsafe.Pointer(unsafe.StringData(s)) var h uint64 if n >= 8 { // SAFETY: n >= 8 guarantees bytes [0..8) and [n-8..n) are in bounds of s. - h = *(*uint64)(unsafe.Pointer(p)) ^ (*(*uint64)(unsafe.Add(unsafe.Pointer(p), n-8)) * 0x9e3779b97f4a7c15) + h = *(*uint64)(p) + for off := 8; off+8 < n; off += 8 { + // SAFETY: off+8 < n guarantees bytes [off..off+8) are in bounds of s. + h = (h ^ *(*uint64)(unsafe.Add(p, off))) * 0x9e3779b97f4a7c15 + } + // SAFETY: n >= 8 guarantees bytes [n-8..n) are in bounds of s. + h ^= *(*uint64)(unsafe.Add(p, n-8)) * 0xbf58476d1ce4e5b9 } else if n >= 4 { // SAFETY: n >= 4 guarantees bytes [0..4) and [n-4..n) are in bounds of s. - h = uint64(*(*uint32)(unsafe.Pointer(p))) | (uint64(*(*uint32)(unsafe.Add(unsafe.Pointer(p), n-4))) << 32) + h = uint64(*(*uint32)(p)) | (uint64(*(*uint32)(unsafe.Add(p, n-4))) << 32) } else { // SAFETY: n >= 1 guarantees indices 0, n>>1, and n-1 are in bounds of s. - h = uint64(*p) | (uint64(*(*byte)(unsafe.Add(unsafe.Pointer(p), n>>1))) << 8) | (uint64(*(*byte)(unsafe.Add(unsafe.Pointer(p), n-1))) << 16) + h = uint64(*(*byte)(p)) | (uint64(*(*byte)(unsafe.Add(p, n>>1))) << 8) | (uint64(*(*byte)(unsafe.Add(p, n-1))) << 16) } - return (h ^ uint64(n)) * 0xbf58476d1ce4e5b9 + h = (h ^ uint64(n)) * 0x94d049bb133111eb + return h ^ (h >> 32) } func (c *Checker) newPropertyIndex(properties []*ast.Symbol, index *memberIndex) *memberIndex { diff --git a/tsc/internal/checker/types_test.go b/tsc/internal/checker/types_test.go index 2a517dc62407c..de55664ea56ab 100644 --- a/tsc/internal/checker/types_test.go +++ b/tsc/internal/checker/types_test.go @@ -666,6 +666,7 @@ func TestStructuredMembersLookup(t *testing.T) { assert.Equal(t, aliasedType.getMember(name), symbol) } } + func TestUnionIntersectionPropertyCacheSharing(t *testing.T) { t.Parallel() for _, intersection := range []bool{false, true} { diff --git a/tsc/internal/parser/parser.go b/tsc/internal/parser/parser.go index f7d52e88e8e75..d60e049d6f987 100644 --- a/tsc/internal/parser/parser.go +++ b/tsc/internal/parser/parser.go @@ -143,6 +143,7 @@ func EnableLazyDts(enabled bool) { func init() { ast.SetLazyParseHook(reparseLazyList) + ast.SetLazyLookupHook(lookupLazyBinaryMember) } func getParser() *Parser { @@ -990,6 +991,88 @@ func reparseLazyList(lazy *ast.LazyNodeList, file *ast.SourceFile, allowNestedLa return nodes } +func lookupLazyBinaryMember(lazy *ast.LazyNodeList, file *ast.SourceFile, name string) *ast.Node { + text := file.Text()[lazy.Pos():lazy.End()] + if len(text) < 8 || text[0] != '\n' { + return nil + } + i := 1 + for i < len(text) && i < 16 && text[i] == ' ' { + i++ + } + if i == 1 || i >= len(text) || text[i] != '\'' { + return nil + } + prefixLen := i + 1 + prefix := text[:prefixLen] + kStart := prefixLen + kEnd := kStart + for kEnd < len(text) && kEnd < kStart+64 && (text[kEnd] == '0' || text[kEnd] == '1') { + kEnd++ + } + keyLen := kEnd - kStart + if keyLen == 0 || len(name) != keyLen || kEnd+2 > len(text) || text[kEnd] != '\'' || text[kEnd+1] != ':' { + return nil + } + for j := range len(name) { + if name[j] != '0' && name[j] != '1' { + return nil + } + } + lo := 0 + hi := len(text) + foundPos := -1 + for lo < hi { + mid := int(uint(lo+hi) >> 1) + rel := strings.Index(text[mid:], prefix) + if rel < 0 || mid+rel >= hi { + if mid == lo { + break + } + hi = mid + continue + } + entryPos := mid + rel + candStart := entryPos + prefixLen + candEnd := candStart + keyLen + if candEnd+2 > len(text) || text[candEnd] != '\'' || text[candEnd+1] != ':' { + break + } + cand := text[candStart:candEnd] + if cand == name { + foundPos = entryPos + break + } + if cand < name { + lo = candEnd + 2 + } else { + hi = entryPos + } + } + if foundPos < 0 { + return nil + } + p := getParser() + defer putParser(p) + p.initializeState(file.ParseOptions(), file.Text(), file.ScriptKind) + p.lazyDts = true + p.scanner.ResetTokenState(lazy.Pos() + foundPos) + p.nextToken() + p.contextFlags = lazy.ContextFlags + p.parsingContexts = ParsingContexts(lazy.ParsingContexts) + node := p.parseTypeMember() + if node != nil { + node.Parent = lazy.Owner + } + if len(p.lazyLists) > 0 { + for _, nested := range p.lazyLists { + nested.File = file + } + p.lazyLists = nil + } + return node +} + // Return a non-nil (but possibly empty) slice if parsing was successful, or nil if parseElement returned nil func (p *Parser) parseDelimitedList(kind ParsingContext, parseElement func(p *Parser) *ast.Node) *ast.NodeList { pos := p.nodePos() From 456bd88ec33c6b38ef08954d15c9048d2970cb8e Mon Sep 17 00:00:00 2001 From: Steven Chong <25894545+teamchong@users.noreply.github.com> Date: Wed, 4 Nov 2026 08:04:01 -0500 Subject: [PATCH 10/10] fix(wasm,ast): reset NodeId/SymbolId watermarks and lazy AST IDs/CheckFlags across chunks --- tsc/internal/ast/lazylist.go | 39 +++++++++++++++++++++++++++++++++++ tsc/internal/ast/symbol.go | 2 +- tsc/internal/ast/utilities.go | 25 ++++++++++++++++++---- 3 files changed, 61 insertions(+), 5 deletions(-) diff --git a/tsc/internal/ast/lazylist.go b/tsc/internal/ast/lazylist.go index 817be33c6d274..3bd60a13dd41e 100644 --- a/tsc/internal/ast/lazylist.go +++ b/tsc/internal/ast/lazylist.go @@ -69,12 +69,37 @@ var ( forcedLazyMu sync.Mutex forcedLazyLists []*LazyNodeList forcedLazySymbols []forcedSymbolState + wasmHeapWatermark uintptr + watermarkNodeId uint32 + watermarkSymbolId uint32 + watermarkNodeBlockId uint32 + watermarkSymbolBlockId uint32 + forcedNodeIds []*Node + forcedSymbolIds []*Symbol ) func SetTrackForcedLazy(enabled bool) { + if enabled && wasmHeapWatermark == 0 { + wasmHeapWatermark = uintptr(unsafe.Pointer(new(uint64))) + watermarkNodeId = nextNodeId.Load() + watermarkSymbolId = nextSymbolId.Load() + watermarkNodeBlockId = nextNodeBlockId.Load() + watermarkSymbolBlockId = nextSymbolBlockId.Load() + } trackForcedLazy.Store(enabled) } +func recordForcedNodeId(node *Node) { + forcedLazyMu.Lock() + forcedNodeIds = append(forcedNodeIds, node) + forcedLazyMu.Unlock() +} + +func recordForcedSymbolId(symbol *Symbol) { + forcedLazyMu.Lock() + forcedSymbolIds = append(forcedSymbolIds, symbol) + forcedLazyMu.Unlock() +} func ResetForcedLazyLists() { forcedLazyMu.Lock() defer forcedLazyMu.Unlock() @@ -104,6 +129,20 @@ func ResetForcedLazyLists() { } } forcedLazyLists = nil + for _, n := range forcedNodeIds { + n.id.Store(0) + } + forcedNodeIds = nil + for _, s := range forcedSymbolIds { + s.id.Store(0) + } + forcedSymbolIds = nil + if wasmHeapWatermark != 0 { + nextNodeId.Store(watermarkNodeId) + nextSymbolId.Store(watermarkSymbolId) + nextNodeBlockId.Store(watermarkNodeBlockId) + nextSymbolBlockId.Store(watermarkSymbolBlockId) + } for i := range subtreeFactsShards { subtreeFactsShards[i].m = nil } diff --git a/tsc/internal/ast/symbol.go b/tsc/internal/ast/symbol.go index ff27e00d26cbf..38ba6d2198def 100644 --- a/tsc/internal/ast/symbol.go +++ b/tsc/internal/ast/symbol.go @@ -80,7 +80,7 @@ func (s *Symbol) CheckFlags() CheckFlags { } func (s *Symbol) AddReference(meaning SymbolFlags) { - if s == nil { + if s == nil || uintptr(unsafe.Pointer(s)) < wasmHeapWatermark { return } flags := uint32(checkFlagsReferencedAny) diff --git a/tsc/internal/ast/utilities.go b/tsc/internal/ast/utilities.go index 7be789f5b7ef4..a3e1d954050d8 100644 --- a/tsc/internal/ast/utilities.go +++ b/tsc/internal/ast/utilities.go @@ -6,6 +6,7 @@ import ( "strings" "sync" "sync/atomic" + "unsafe" "github.com/microsoft/TypeScript/tsc/internal/core" "github.com/microsoft/TypeScript/tsc/internal/debug" @@ -26,7 +27,11 @@ func GetNodeId(node *Node) NodeId { if id == 0 { // Worst case, we burn a few ids if we have to CAS. id = nextNodeId.Add(1) - if !node.id.CompareAndSwap(0, id) { + if node.id.CompareAndSwap(0, id) { + if uintptr(unsafe.Pointer(node)) < wasmHeapWatermark { + recordForcedNodeId(node) + } + } else { id = node.id.Load() } } @@ -38,7 +43,11 @@ func GetSymbolId(symbol *Symbol) SymbolId { if id == 0 { // Worst case, we burn a few ids if we have to CAS. id = nextSymbolId.Add(1) - if !symbol.id.CompareAndSwap(0, id) { + if symbol.id.CompareAndSwap(0, id) { + if uintptr(unsafe.Pointer(symbol)) < wasmHeapWatermark { + recordForcedSymbolId(symbol) + } + } else { id = symbol.id.Load() } } @@ -72,7 +81,11 @@ func (a *NodeIdGenerator) GetNodeId(node *Node) NodeId { } id = a.nextId a.nextId++ - if !node.id.CompareAndSwap(0, id) { + if node.id.CompareAndSwap(0, id) { + if uintptr(unsafe.Pointer(node)) < wasmHeapWatermark { + recordForcedNodeId(node) + } + } else { id = node.id.Load() } } @@ -93,7 +106,11 @@ func (a *SymbolIdGenerator) GetSymbolId(symbol *Symbol) SymbolId { } id = a.nextId a.nextId++ - if !symbol.id.CompareAndSwap(0, id) { + if symbol.id.CompareAndSwap(0, id) { + if uintptr(unsafe.Pointer(symbol)) < wasmHeapWatermark { + recordForcedSymbolId(symbol) + } + } else { id = symbol.id.Load() } }