Commit: cb948f3
Parent: dd59603

Mark implementations of foreign interfaces and abstract classes as "probably reachable"

Mårten Åsberg committed on 2026-03-14 at 19:40
Core/ISymbolExtensions.cs +8 -4
diff --git a/Core/ISymbolExtensions.cs b/Core/ISymbolExtensions.cs
index d151ab7..c20c410 100644
@@ -5,14 +5,18 @@ namespace Reacher;
internal static class ISymbolExtensions
{
public static HashSet<ISymbol> ExplicitOrImplicitInterfaceImplementations(this ISymbol member) =>
[.. member.ExplicitInterfaceImplementations(), .. member.ImplicitInterfaceImplementations()];
public static HashSet<ISymbol> ExplicitOrImplicitInterfaceImplementations(this ISymbol member)
{
var implementations = member.ExplicitInterfaceImplementations();
implementations.UnionWith(member.ImplicitInterfaceImplementations());
return implementations;
}
public static HashSet<ISymbol> ExplicitInterfaceImplementations(this ISymbol member) =>
member switch
{
IMethodSymbol method => [.. method.ExplicitInterfaceImplementations],
IPropertySymbol property => [.. property.ExplicitInterfaceImplementations],
IMethodSymbol method => new(method.ExplicitInterfaceImplementations, SymbolEqualityComparer.Default),
IPropertySymbol property => new(property.ExplicitInterfaceImplementations, SymbolEqualityComparer.Default),
_ => [],
};
Core/MemberCollectors/MembersCollector.cs +1 -1
diff --git a/Core/MemberCollectors/MembersCollector.cs b/Core/MemberCollectors/MembersCollector.cs
index 85e7a55..e9ad4b1 100644
@@ -6,7 +6,7 @@ namespace Reacher.MemberCollectors;
internal class MembersCollector : SymbolVisitor
{
public IReadOnlySet<ISymbol> Members => members;
private readonly HashSet<ISymbol> members = [];
private readonly HashSet<ISymbol> members = new(SymbolEqualityComparer.Default);
public override void VisitAssembly(IAssemblySymbol symbol)
{
Core/ReachabilityAnalyzer.cs +8 -6
diff --git a/Core/ReachabilityAnalyzer.cs b/Core/ReachabilityAnalyzer.cs
index 5e6cd33..717be3c 100644
@@ -13,9 +13,9 @@ namespace Reacher;
internal sealed class ReachabilityAnalyzer(IReadOnlyCollection<Compilation> compilations)
{
private readonly Dictionary<ISymbol, ReachabilityConfidence> reachableMembers = [];
private readonly Dictionary<ISymbol, ReachabilityConfidence> reachableMembers = new(SymbolEqualityComparer.Default);
private readonly HashSet<ISymbol> implementableMembers = [];
private readonly HashSet<ISymbol> implementableMembers = new(SymbolEqualityComparer.Default);
public void Analyze(ISymbol member, CancellationToken cancellationToken) =>
Analyze(member, ReachabilityConfidence.DefinitelyReachable, cancellationToken);
@@ -57,8 +57,10 @@ internal sealed class ReachabilityAnalyzer(IReadOnlyCollection<Compilation> comp
_ => member,
};
private static bool IsUserDefined(ISymbol member) =>
!member.IsImplicitlyDeclared && (member.CanBeReferencedByName || member.Name is "<Main>$");
private bool IsUserDefined(ISymbol member) =>
!member.IsImplicitlyDeclared
&& (member.CanBeReferencedByName || member.Name is "<Main>$")
&& compilations.Any(c => SymbolEqualityComparer.Default.Equals(c.SourceModule, member.ContainingModule));
private bool TryGetAssociatedCompilation(ISymbol member, [NotNullWhen(true)] out Compilation? compilation)
{
@@ -124,7 +126,7 @@ internal sealed class ReachabilityAnalyzer(IReadOnlyCollection<Compilation> comp
{
var collector = new PredicateMembersCollector(m => IsUserDefined(m) && !reachableMembers.ContainsKey(m));
compilations.Accept(collector);
return [.. collector.Members];
return new(collector.Members, SymbolEqualityComparer.Default);
}
private void AnalyzeProbablyReachableMembers(
@@ -148,6 +150,6 @@ internal sealed class ReachabilityAnalyzer(IReadOnlyCollection<Compilation> comp
{
possibleTransientTargets.Add(overriddenMember);
}
return possibleTransientTargets.Any(reachableMembers.ContainsKey);
return possibleTransientTargets.Any(m => reachableMembers.ContainsKey(m) || !IsUserDefined(m));
}
}