test(scripts): pin null-result and definition-agnostic caching; review polish

This commit is contained in:
Joseph Doherty
2026-08-12 16:42:27 -04:00
parent b1c3783578
commit 702de910ad
2 changed files with 82 additions and 3 deletions
@@ -56,13 +56,25 @@ public sealed class CachingScriptMetadataResolver : MetadataReferenceResolver
private readonly MetadataReferenceResolver _inner;
// Keyed on the assembly identity display name, compared case-insensitively to
// match .NET assembly-name binding, which treats simple names case-insensitively.
// A null VALUE is cached too: "not found" is a legitimate, sticky result.
private readonly ConcurrentDictionary<string, PortableExecutableReference?> _missingByIdentity =
new(StringComparer.OrdinalIgnoreCase);
// Deliberately case-SENSITIVE (the tuple's default comparer): baseFilePath is a
// filesystem path, and on the Linux containers these nodes run in, two paths
// differing only in case are two different files.
private readonly ConcurrentDictionary<(string Reference, string? BaseFilePath, MetadataReferenceProperties Properties),
ImmutableArray<PortableExecutableReference>> _referencesByPath = new();
ImmutableArray<PortableExecutableReference>> _referencesByArgs = new();
/// <summary>Creates a decorator over the given inner resolver. Exposed for tests; production uses <see cref="Instance"/>.</summary>
/// <summary>
/// Creates a decorator over the given inner resolver. Exposed for tests;
/// production code must NEVER call this constructor directly — always attach
/// <see cref="Instance"/>. The cache lives on the instance, so a second
/// instance has a second (empty) cache and silently reintroduces the
/// per-compile native-metadata leak on whichever surface uses it.
/// </summary>
/// <param name="inner">The resolver whose results are memoized.</param>
public CachingScriptMetadataResolver(MetadataReferenceResolver inner) => _inner = inner;
@@ -79,7 +91,7 @@ public sealed class CachingScriptMetadataResolver : MetadataReferenceResolver
/// <inheritdoc />
public override ImmutableArray<PortableExecutableReference> ResolveReference(
string reference, string? baseFilePath, MetadataReferenceProperties properties)
=> _referencesByPath.GetOrAdd(
=> _referencesByArgs.GetOrAdd(
(reference, baseFilePath, properties),
_ => _inner.ResolveReference(reference, baseFilePath, properties));