Skip to content

Commit 452cc1e

Browse files
paulirwinclaude
andcommitted
Cache J2N numeric type lookups per-Compilation
Address Copilot review: GetJ2NNumericTypes was calling GetTypeByMetadataName for all 7 J2N numeric types on every numeric invocation/concat/interpolation node visited. The result is identical for the lifetime of a Compilation, so cache it in a ConditionalWeakTable keyed by Compilation. Also short-circuits IsJ2NNumericType: BCL primitives never live under J2N.Numerics, so checking the containing namespace's display string up front avoids the symbol-equality loop entirely on the hottest path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 2a4326a commit 452cc1e

1 file changed

Lines changed: 23 additions & 3 deletions

File tree

src/Lucene.Net.CodeAnalysis.Dev/Utility/NumericTypeHelper.cs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
*/
1919

2020
using System.Collections.Generic;
21+
using System.Collections.Immutable;
2122
using System.Linq;
23+
using System.Runtime.CompilerServices;
2224
using Microsoft.CodeAnalysis;
2325
using Microsoft.CodeAnalysis.CSharp;
2426
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -82,21 +84,39 @@ public static bool IsBclNumericSpecialType(ITypeSymbol? type)
8284
"J2N.Numerics.Double",
8385
};
8486

85-
public static IEnumerable<INamedTypeSymbol> GetJ2NNumericTypes(Compilation compilation)
87+
// Resolved J2N numeric types are cached per-Compilation so analyzers don't
88+
// re-run GetTypeByMetadataName for every numeric invocation/concat/interpolation node.
89+
// ConditionalWeakTable keeps the cache alive only as long as the Compilation is.
90+
private sealed class J2NTypeBox { public ImmutableArray<INamedTypeSymbol> Types; }
91+
private static readonly ConditionalWeakTable<Compilation, J2NTypeBox> J2NTypeCache = new();
92+
93+
public static ImmutableArray<INamedTypeSymbol> GetJ2NNumericTypes(Compilation compilation)
94+
=> J2NTypeCache.GetValue(compilation, ResolveJ2NTypes).Types;
95+
96+
private static J2NTypeBox ResolveJ2NTypes(Compilation compilation)
8697
{
98+
var builder = ImmutableArray.CreateBuilder<INamedTypeSymbol>(J2NNumericMetadataNames.Length);
8799
foreach (var name in J2NNumericMetadataNames)
88100
{
89101
var t = compilation.GetTypeByMetadataName(name);
90-
if (t is not null) yield return t;
102+
if (t is not null) builder.Add(t);
91103
}
104+
return new J2NTypeBox { Types = builder.ToImmutable() };
92105
}
93106

94107
public static bool IsJ2NNumericType(ITypeSymbol? type, Compilation compilation)
95108
{
96109
if (type is null) return false;
110+
if (type is not INamedTypeSymbol named) return false;
111+
112+
// Fast pre-filter: skip the symbol-equality loop unless the metadata name matches a J2N type.
113+
// (BCL primitives never sit under J2N.Numerics, so this short-circuits the hot path.)
114+
if (named.ContainingNamespace?.ToDisplayString() != "J2N.Numerics")
115+
return false;
116+
97117
foreach (var j2n in GetJ2NNumericTypes(compilation))
98118
{
99-
if (SymbolEqualityComparer.Default.Equals(type, j2n))
119+
if (SymbolEqualityComparer.Default.Equals(named, j2n))
100120
return true;
101121
}
102122
return false;

0 commit comments

Comments
 (0)