Skip to content

Commit

Permalink
Merge pull request #58 from simonmckenzie/fix/handle-generic-types-in…
Browse files Browse the repository at this point in the history
…-method-deduplication

Handle generic type parameters in method deduplication
  • Loading branch information
ChristianSauer authored Nov 29, 2024
2 parents f30a718 + 1ffa50b commit edc3965
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<EnableNETAnalyzers>True</EnableNETAnalyzers>
<AnalysisLevel>latest-Recommended</AnalysisLevel>
<Version>5.0.1</Version>
<Version>5.0.2</Version>
<PackageReadmeFile>README.md</PackageReadmeFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NoWarn>1701;1702;NU5128</NoWarn>
Expand Down
31 changes: 15 additions & 16 deletions AutomaticInterface/AutomaticInterface/Builder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,14 @@ public static class Builder
{
private const string InheritDoc = "/// <inheritdoc />"; // we use inherit doc because that should be able to fetch documentation from base classes.

private static readonly SymbolDisplayFormat MethodSignatureDisplayFormat =
private static readonly SymbolDisplayFormat FullyQualifiedDisplayFormat =
new(
genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters,
memberOptions: SymbolDisplayMemberOptions.IncludeParameters,
parameterOptions: SymbolDisplayParameterOptions.IncludeType
| SymbolDisplayParameterOptions.IncludeParamsRefOut
);

private static readonly SymbolDisplayFormat TypeDisplayFormat =
new(
globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Included,
| SymbolDisplayParameterOptions.IncludeParamsRefOut,
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters,
globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Included,
miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes
| SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier
);
Expand Down Expand Up @@ -89,7 +85,7 @@ InterfaceBuilder codeGenerator
.Where(x => !HasIgnoreAttribute(x))
.Where(x => x.ContainingType.Name != nameof(Object))
.Where(x => !HasIgnoreAttribute(x))
.GroupBy(x => x.ToDisplayString(MethodSignatureDisplayFormat))
.GroupBy(x => x.ToDisplayString(FullyQualifiedDisplayFormat))
.Select(g => g.First())
.ToList()
.ForEach(method =>
Expand All @@ -110,13 +106,16 @@ private static void AddMethod(InterfaceBuilder codeGenerator, IMethodSymbol meth

var typedArgs = method
.TypeParameters.Select(arg =>
(arg.ToDisplayString(TypeDisplayFormat), arg.GetWhereStatement(TypeDisplayFormat))
(
arg.ToDisplayString(FullyQualifiedDisplayFormat),
arg.GetWhereStatement(FullyQualifiedDisplayFormat)
)
)
.ToList();

codeGenerator.AddMethodToInterface(
name,
returnType.ToDisplayString(TypeDisplayFormat),
returnType.ToDisplayString(FullyQualifiedDisplayFormat),
InheritDoc,
paramResult,
typedArgs
Expand Down Expand Up @@ -184,7 +183,7 @@ InterfaceBuilder codeGenerator
.Where(x => x.DeclaredAccessibility == Accessibility.Public)
.Where(x => !x.IsStatic)
.Where(x => !HasIgnoreAttribute(x))
.GroupBy(x => x.ToDisplayString(MethodSignatureDisplayFormat))
.GroupBy(x => x.ToDisplayString(FullyQualifiedDisplayFormat))
.Select(g => g.First())
.ToList()
.ForEach(evt =>
Expand All @@ -196,7 +195,7 @@ InterfaceBuilder codeGenerator

codeGenerator.AddEventToInterface(
name,
type.ToDisplayString(TypeDisplayFormat),
type.ToDisplayString(FullyQualifiedDisplayFormat),
InheritDoc
);
});
Expand All @@ -208,7 +207,7 @@ private static string GetMethodSignature(IParameterSymbol x)
var refKindText = GetRefKind(x);
var optionalValue = GetMethodOptionalValue(x);

return $"{refKindText}{x.Type.ToDisplayString(TypeDisplayFormat)} {name}{optionalValue}";
return $"{refKindText}{x.Type.ToDisplayString(FullyQualifiedDisplayFormat)} {name}{optionalValue}";
}

private static string GetMethodOptionalValue(IParameterSymbol x)
Expand All @@ -224,7 +223,7 @@ private static string GetMethodOptionalValue(IParameterSymbol x)
bool value => $" = {(value ? "true" : "false")}",
// struct
null when x.Type.IsValueType
=> $" = default({x.Type.ToDisplayString(TypeDisplayFormat)})",
=> $" = default({x.Type.ToDisplayString(FullyQualifiedDisplayFormat)})",
null => " = null",
_ => $" = {x.ExplicitDefaultValue}"
};
Expand Down Expand Up @@ -281,7 +280,7 @@ InterfaceBuilder interfaceGenerator

interfaceGenerator.AddPropertyToInterface(
name,
type.ToDisplayString(TypeDisplayFormat),
type.ToDisplayString(FullyQualifiedDisplayFormat),
hasGet,
hasSet,
isRef,
Expand Down
101 changes: 101 additions & 0 deletions AutomaticInterface/Tests/GeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1787,6 +1787,107 @@ public partial interface IDemoClass
GenerateCode(code).Should().Be(expected);
}

[Fact]
public void WorksWithGenericParameterOverloads()
{
const string code = """
using AutomaticInterface;
namespace AutomaticInterfaceExample;
[GenerateAutomaticInterface]
public class DemoClass
{
public void AMethod(Func<Task<int>> getValue) {}
public void AMethod(Func<Task<float>> getValue) {}
}
""";

const string expected = """
//--------------------------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
// </auto-generated>
//--------------------------------------------------------------------------------------------------
namespace AutomaticInterfaceExample
{
[global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
public partial interface IDemoClass
{
/// <inheritdoc />
void AMethod(Func<Task<int>> getValue);
/// <inheritdoc />
void AMethod(Func<Task<float>> getValue);
}
}
""";
GenerateCode(code).Should().Be(expected);
}

[Fact]
public void WorksWithGenericParameterOverloadsWithIdenticalTypeNames()
{
const string code = """
using AutomaticInterface;
namespace AutomaticInterfaceExample
{
namespace Types1 {
public class Model;
}
namespace Types2 {
public class Model;
}
[GenerateAutomaticInterface]
public class DemoClass
{
public void AMethod(Func<Task<Types1.Model>> getValue) {}
public void AMethod(Func<Task<Types2.Model>> getValue) {}
}
}
""";

const string expected = """
//--------------------------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
// </auto-generated>
//--------------------------------------------------------------------------------------------------
namespace AutomaticInterfaceExample
{
[global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
public partial interface IDemoClass
{
/// <inheritdoc />
void AMethod(Func<Task<global::AutomaticInterfaceExample.Types1.Model>> getValue);
/// <inheritdoc />
void AMethod(Func<Task<global::AutomaticInterfaceExample.Types2.Model>> getValue);
}
}
""";
GenerateCode(code).Should().Be(expected);
}

[Fact]
public void WorksWithEventOverrides()
{
Expand Down

0 comments on commit edc3965

Please sign in to comment.