[Repo Assist] Perf: cache FullName, BaseType and GetInterfaces in TargetTypeDefinition#485
Conversation
FullName, BaseType and GetInterfaces() on TargetTypeDefinition are each computed from immutable input data (inp.Namespace/inp.Name, inp.Extends, inp.Implements) but were recomputed on every call. - FullName: allocates a new string on every call via string concatenation - BaseType: resolves the base type via txILType on every call - GetInterfaces(): resolves and allocates a new Type[] on every call For large type providers with many types (e.g. SwaggerProvider), where these properties are queried many times per type during compilation, this saves repeated allocations and type-resolution work. All three are now backed by lazy caches initialised on first access. F# lazy uses LazyThreadSafetyMode.ExecutionAndPublication by default so concurrent first-access from multiple compiler threads is safe. All 117 pre-existing tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR improves performance of TargetTypeDefinition in ProvidedTypes.fs by caching values that are derived from immutable ILTypeDef input and are frequently queried by the F# compiler during type-checking.
Changes:
- Cache
TargetTypeDefinition.FullNameusinglazyto avoid repeated string concatenations. - Cache
TargetTypeDefinition.BaseTypeusinglazyto avoid repeated type resolution. - Cache
TargetTypeDefinition.GetInterfaces()results usinglazyto avoid repeated type resolution and array allocations.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| override __.BaseType = inp.Extends |> Option.map (txILType (gps, [| |])) |> Option.toObj | ||
| override __.GetInterfaces() = inp.Implements |> Array.map (txILType (gps, [| |])) | ||
| override __.BaseType = baseType.Value | ||
| override __.GetInterfaces() = interfaces.Value |
There was a problem hiding this comment.
GetInterfaces() now returns the cached Type[] instance. Previously it allocated a fresh array on each call, so callers could (intentionally or accidentally) mutate the returned array without affecting subsequent calls. To avoid sharing a mutable array across callers, consider returning a copy (while still caching the resolved interface Types) or otherwise ensuring the returned collection can't be mutated by consumers.
| override __.GetInterfaces() = interfaces.Value | |
| override __.GetInterfaces() = interfaces.Value |> Array.copy |
🤖 This is an automated PR from Repo Assist, an AI assistant for this repository.
Summary
TargetTypeDefinition.FullName,BaseType, andGetInterfaces()each compute their result from immutable input data (inp.Namespace/inp.Name,inp.Extends,inp.Implements) but were recomputed on every call — allocating new strings/arrays and re-running type resolution each time.For large type providers with many types (e.g. SwaggerProvider), where the F# compiler queries these properties many times per type during type-checking, this saves repeated allocations and type-resolution work.
Changes
FullNamelazy— computed once, samestringreturned thereafterBaseTypetxILType(type-resolution) every calllazy— resolved onceGetInterfaces()Array.map txILType(allocates newType[]) every calllazy— resolved and allocated onceAll three caches use F#
lazywhich defaults toLazyThreadSafetyMode.ExecutionAndPublication, so concurrent first-access from multiple F# compiler threads is safe.This is complementary to PR #471 (which cached member-wrapper arrays) and does not touch the thread-safety areas being addressed by PRs #482/#483.
Test Status
All 117 pre-existing tests pass. The
netstandard2.0build target ran OOM on the CI machine (infrastructure issue, not caused by this change — the same issue affects master); thenet8.0build and tests both pass cleanly.