Do not redefine interfaces. (#67)

* Do not redefine interfaces.

* Improve code style, readability and performance after initial code review

* Implemented unit tests for the interface inheritance

* Cleaned up unit tests.

* Completely remove output helper.
This commit is contained in:
David
2024-04-23 10:52:52 +02:00
committed by GitHub
parent d2d1115753
commit 6b6e5d04a0
15 changed files with 340 additions and 11 deletions
@@ -60,4 +60,41 @@ internal static class NamedTypeSymbolExtensions
$"{namedTypeSymbol}Proxy" :
$"{namedTypeSymbol}Proxy<{string.Join(", ", namedTypeSymbol.TypeArguments.Select(ta => ta.Name))}>";
}
public static List<INamedTypeSymbol> ResolveImplementedInterfaces(this INamedTypeSymbol symbol, bool proxyBaseClasses)
{
//Members implemented by us or base classes should go here.
var publicMembers = symbol.GetMembers().Where(m => m.DeclaredAccessibility == Accessibility.Public).ToList();
//Direct interfaces, recursive interfaces or base class interfaces should go here.
var interfaces = new List<INamedTypeSymbol>(symbol.Interfaces);
var baseType = symbol.BaseType;
while (proxyBaseClasses && baseType != null && baseType.SpecialType != SpecialType.System_Object)
{
publicMembers.AddRange(baseType.GetMembers().Where(m => m.DeclaredAccessibility == Accessibility.Public));
interfaces.AddRange(baseType.Interfaces);
baseType = baseType.BaseType;
}
//Filter explicitly implemented interfaces.
var realizedInterfaces = new List<INamedTypeSymbol>();
foreach (var iface in interfaces)
{
var isRealized = true;
var allMembers = iface.AllInterfaces.Aggregate(iface.GetMembers(), (xs, x) => xs.AddRange(x.GetMembers()));
foreach (var member in allMembers)
{
var implementation = symbol.FindImplementationForInterfaceMember(member);
if (!publicMembers.Contains(implementation!))
{
isRealized = false;
break;
}
}
if (isRealized)
{
realizedInterfaces.Add(iface);
}
}
return realizedInterfaces;
}
}
@@ -11,6 +11,7 @@ namespace ProxyInterfaceSourceGenerator.FileGenerators;
internal class PartialInterfacesGenerator : BaseGenerator, IFilesGenerator
{
private IReadOnlyCollection<INamedTypeSymbol> ImplementedInterfaces = new List<INamedTypeSymbol>();
public PartialInterfacesGenerator(Context context, bool supportsNullable) :
base(context, supportsNullable)
{
@@ -58,6 +59,9 @@ internal class PartialInterfacesGenerator : BaseGenerator, IFilesGenerator
ProxyData proxyData)
{
var extendsProxyClasses = GetExtendsProxyData(proxyData, classSymbol);
ImplementedInterfaces = classSymbol.Symbol.ResolveImplementedInterfaces(proxyData.ProxyBaseClasses);
var implementedInterfacesNames = ImplementedInterfaces.Select(i => i.ToDisplayString(NullableFlowState.None, SymbolDisplayFormat.FullyQualifiedFormat));
var implements = implementedInterfacesNames.Any() ? $" : {string.Join(", ", implementedInterfacesNames)}" : string.Empty;
var @new = extendsProxyClasses.Any() ? "new " : string.Empty;
var (namespaceStart, namespaceEnd) = NamespaceBuilder.Build(ns);
@@ -74,7 +78,7 @@ internal class PartialInterfacesGenerator : BaseGenerator, IFilesGenerator
using System;
{namespaceStart}
public partial interface {interfaceName}
public partial interface {interfaceName}{implements}
{{
{@new}{classSymbol.Symbol} _Instance {{ get; }}
@@ -88,11 +92,26 @@ using System;
{SupportsNullable.IIf("#nullable restore")}";
}
private Func<T, bool> InterfaceFilter<T>() where T : ISymbol
{
var hashSet = new HashSet<string>();
foreach (var iface in ImplementedInterfaces)
{
var members = iface.AllInterfaces.Aggregate(iface.GetMembers(), (xs, x) => xs.AddRange(x.GetMembers()));
foreach (var member in members)
{
hashSet.Add(member.Name);
}
}
//Member is not already implemented in another interface.
return (T t) => !hashSet.Contains(t.Name);
}
private string GenerateProperties(ClassSymbol targetClassSymbol, bool proxyBaseClasses)
{
var str = new StringBuilder();
foreach (var property in MemberHelper.GetPublicProperties(targetClassSymbol, proxyBaseClasses))
foreach (var property in MemberHelper.GetPublicProperties(targetClassSymbol, proxyBaseClasses, InterfaceFilter<IPropertySymbol>()))
{
var type = GetPropertyType(property, out var isReplaced);
@@ -125,7 +144,7 @@ using System;
private string GenerateMethods(ClassSymbol targetClassSymbol, bool proxyBaseClasses)
{
var str = new StringBuilder();
foreach (var method in MemberHelper.GetPublicMethods(targetClassSymbol, proxyBaseClasses))
foreach (var method in MemberHelper.GetPublicMethods(targetClassSymbol, proxyBaseClasses, InterfaceFilter<IMethodSymbol>()))
{
var methodParameters = GetMethodParameters(method.Parameters, true);
var whereStatement = GetWhereStatementFromMethod(method);
@@ -145,7 +164,7 @@ using System;
private string GenerateEvents(ClassSymbol targetClassSymbol, bool proxyBaseClasses)
{
var str = new StringBuilder();
foreach (var @event in MemberHelper.GetPublicEvents(targetClassSymbol, proxyBaseClasses))
foreach (var @event in MemberHelper.GetPublicEvents(targetClassSymbol, proxyBaseClasses, InterfaceFilter<IMethodSymbol>()))
{
var ps = @event.First().Parameters.First();
var type = ps.GetTypeEnum() == TypeEnum.Complex ? GetParameterType(ps, out _) : ps.Type.ToString();
@@ -82,7 +82,7 @@ internal partial class ProxyClassesGenerator : BaseGenerator, IFilesGenerator
var operators = GenerateOperators(targetClassSymbol, pd.ProxyBaseClasses);
var configurationForMapster = string.Empty;
if (Context.ReplacedTypes.Any())
if (Context.ReplacedTypes.Count > 0)
{
configurationForMapster = GenerateMapperConfigurationForMapster();
}