Skip to content

[Repo Assist] Perf: cache FullName, BaseType and GetInterfaces in TargetTypeDefinition#485

Open
github-actions[bot] wants to merge 2 commits intomasterfrom
repo-assist/perf-cache-targettype-props-20260321-b76dfcd6be6d4c79
Open

[Repo Assist] Perf: cache FullName, BaseType and GetInterfaces in TargetTypeDefinition#485
github-actions[bot] wants to merge 2 commits intomasterfrom
repo-assist/perf-cache-targettype-props-20260321-b76dfcd6be6d4c79

Conversation

@github-actions
Copy link
Contributor

🤖 This is an automated PR from Repo Assist, an AI assistant for this repository.

Summary

TargetTypeDefinition.FullName, BaseType, and GetInterfaces() 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

Property Before After
FullName String concatenation every call Cached lazy — computed once, same string returned thereafter
BaseType txILType (type-resolution) every call Cached lazy — resolved once
GetInterfaces() Array.map txILType (allocates new Type[]) every call Cached lazy — resolved and allocated once

All three caches use F# lazy which defaults to LazyThreadSafetyMode.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

Passed!  - Failed: 0, Passed: 117, Skipped: 0, Total: 117 (net8.0)

All 117 pre-existing tests pass. The netstandard2.0 build target ran OOM on the CI machine (infrastructure issue, not caused by this change — the same issue affects master); the net8.0 build and tests both pass cleanly.

Generated by Repo Assist ·

To install this agentic workflow, run

gh aw add githubnext/agentics/workflows/repo-assist.md@d1d884596e62351dd652ae78465885dd32f0dd7d

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>
@sergey-tihon sergey-tihon marked this pull request as ready for review March 22, 2026 06:19
Copilot AI review requested due to automatic review settings March 22, 2026 06:19
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.FullName using lazy to avoid repeated string concatenations.
  • Cache TargetTypeDefinition.BaseType using lazy to avoid repeated type resolution.
  • Cache TargetTypeDefinition.GetInterfaces() results using lazy to 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
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
override __.GetInterfaces() = interfaces.Value
override __.GetInterfaces() = interfaces.Value |> Array.copy

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant