Skip to content

Track sibling-overload metadata closure in symbol-based mode (#146)#147

Merged
dfederm merged 1 commit intomainfrom
dfederm/fix-rt-issue-146
May 5, 2026
Merged

Track sibling-overload metadata closure in symbol-based mode (#146)#147
dfederm merged 1 commit intomainfrom
dfederm/fix-rt-issue-146

Conversation

@dfederm
Copy link
Copy Markdown
Owner

@dfederm dfederm commented May 5, 2026

Fixes #146.

Problem

When ReferenceTrimmerUseSymbolAnalysis=true (the experimental v3.5 symbol-based detection mode), RT was under-reporting references the C# compiler actually requires. Concretely: when source code performs name-based member lookup on a type — invocation, method-group conversion, indexer access, constructor call, attribute application — the compiler resolves the metadata of every member with that name on the receiving type and its base/interface chain in order to perform overload resolution. Every sibling overload's parameter/return type assemblies must therefore be metadata-resolvable, even ones source code never selects, otherwise CS0012 fires on the unused overload's parameter type.

The symbol analyzer tracked only the selected overload (IInvocationOperation.TargetMethod.ContainingAssembly etc.) and missed sibling-overload metadata-closure assemblies. Same family of issue as the override-chain gap fixed in #143, just specialized to per-call-site name resolution.

The original issue (#146) reports the constructor case (Provider.Class1 with Class1(params string[]) and Class1(ProviderDependency.Class1); consumer derives and calls base(""1""); RT incorrectly trims ProviderDependency). After empirically probing the compiler's behavior across name-resolution shapes, this PR addresses the broader pattern.

Fix

Inside InitializeSymbolBasedAnalysis:

  • TrackInstanceConstructorParameters(INamedTypeSymbol) — wired into the inheritance walk in TrackType; runs once per named type for the type itself and each BaseType in its chain. Covers : base(...), new T(...), and attribute application (since TrackAttribute feeds the attribute class through TrackType).
  • TrackOverloadSiblings(receiverType, name) — walks receiverType + BaseType chain + AllInterfaces, calling GetMembers(name) per type and tracking each method/property's parameter and return-type assemblies via TrackType. Wired into IInvocationOperation and the IMethodReferenceOperation branch of IMemberReferenceOperation.
  • TrackIndexerSiblings(receiverType) — same shape but scans for IsIndexer, since indexer lookup isn't keyed on a regular name. Wired into the IPropertyReferenceOperation branch when IsIndexer.
  • Extension method special case: when IsExtensionMethod is true, additionally walk siblings on TargetMethod.ContainingType (the static class), because the compiler's name lookup for extension overload resolution happens on the static class, not on the receiver type.
  • Two ConcurrentDictionary caches (memberLookupWalked, indexerLookupWalked) keyed with SymbolEqualityComparer.Default to dedup walks across hot call sites.

Tests

15 new regression tests in AnalyzerTests.cs covering each shape:

  • Constructor overloads on base type, attribute, multi-overload, multi-level chain.
  • Method overloads on instance methods, static methods, base-chain methods, and interface methods.
  • Method-group / delegate-target conversions.
  • Indexer overloads.
  • Extension methods (instance-style invocation and method-group form).
  • Negative tests confirming unrelated assemblies aren't credited and that the walk is correctly keyed by member name.

Each positive test was verified to fail without the analyzer change and pass with it. All 70 AnalyzerTests pass; 81 unit tests total green.

E2E validation

Reproduced the issue's exact scenario (Consumer/Provider/ProviderDependency with <DisableTransitiveProjectReferences>true</DisableTransitiveProjectReferences>) plus the expanded shapes (method overload, static method, indexer, method group). Without the fix, RT0002 fires on ProviderDependency; with the fix it doesn't. Removing ProviderDependency from the consumer csproj produces CS0012, confirming the fix is necessary and not over-conservative.

Out of scope

  • Operator and conversion sibling overloads. Empirically the C# compiler does not fire CS0012 on these (probably resolves via narrower lookup paths), so adding tracking would be over-eager today. Can be added later if real-world reports surface them.
  • Type forwarding edge cases (already partially addressed by Cover type forwarding and other gaps #138).

When ReferenceTrimmerUseSymbolAnalysis=true, RT was missing assemblies that the C# compiler eagerly loads during name-based member lookup for overload resolution. The selected overload's containing assembly was tracked, but sibling overloads' parameter/return types were not -- producing false-positive RT0001/RT0002 diagnostics whose application leaves the build broken with CS0012.

This fixes:
* Constructor overloads at base() / new T(...) sites (#146 primary case).
* Constructor overloads on attribute classes (#146 P.S. case).
* Method overloads at instance, static, and base-chain call sites.
* Method overloads through interfaces.
* Method-group / delegate-target conversions.
* Indexer overloads.
* Extension method overloads (instance-style and method-group form), where
  name lookup happens on the extension's containing static class rather
  than on the receiver type.

Adds 15 regression tests (positive + negative) covering each shape; each
positive test was verified to fail without the analyzer change and pass
with it. All 70 analyzer tests pass.

Same family as the override-chain gap (PR #143). Out of scope: operator
and conversion sibling overloads (compiler is empirically lazier here and
does not fire CS0012 on the cases tested).

Fixes #146

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dfederm dfederm enabled auto-merge (squash) May 5, 2026 21:39
@dfederm dfederm merged commit 8ee2bd8 into main May 5, 2026
2 checks passed
@dfederm dfederm deleted the dfederm/fix-rt-issue-146 branch May 5, 2026 21:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[ReferenceTrimmerUseSymbolAnalysis] Account for constructor overrides

1 participant