From 0f6f78365db030eca2bcd8ae2d4ca36d2b797c0b Mon Sep 17 00:00:00 2001 From: Simon McKenzie Date: Fri, 14 Jun 2024 11:48:46 +1000 Subject: [PATCH 1/2] Prevent duplicates when overriding or shadowing methods The current code will insert multiple definitions of a method when that method has been overridden or shadowed. The approach is the same as that already used for property deduplication, which is to group by the method definition. --- .../AutomaticInterface/Builder.cs | 8 ++ AutomaticInterface/Tests/GeneratorTests.cs | 98 +++++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/AutomaticInterface/AutomaticInterface/Builder.cs b/AutomaticInterface/AutomaticInterface/Builder.cs index dd04845..26932eb 100644 --- a/AutomaticInterface/AutomaticInterface/Builder.cs +++ b/AutomaticInterface/AutomaticInterface/Builder.cs @@ -11,6 +11,12 @@ public static class Builder { private const string InheritDoc = "/// "; // we use inherit doc because that should be able to fetch documentation from base classes. + private static readonly SymbolDisplayFormat MethodDisplayFormatForDeduplication = + new( + memberOptions: SymbolDisplayMemberOptions.IncludeParameters, + parameterOptions: SymbolDisplayParameterOptions.IncludeType + ); + public static string BuildInterfaceFor(ITypeSymbol typeSymbol) { if ( @@ -72,6 +78,8 @@ InterfaceBuilder codeGenerator .Where(x => x.MethodKind == MethodKind.Ordinary) .Where(x => !x.IsStatic) .Where(x => x.ContainingType.Name != nameof(Object)) + .GroupBy(x => x.ToDisplayString(MethodDisplayFormatForDeduplication)) + .Select(g => g.First()) .ToList() .ForEach(method => { diff --git a/AutomaticInterface/Tests/GeneratorTests.cs b/AutomaticInterface/Tests/GeneratorTests.cs index fe3ff9f..a168a74 100644 --- a/AutomaticInterface/Tests/GeneratorTests.cs +++ b/AutomaticInterface/Tests/GeneratorTests.cs @@ -2211,4 +2211,102 @@ public partial interface ISecondClass """; GenerateCode(code).Should().Be(expected); } + + [Fact] + public void WorksWithMethodOverrides() + { + const string code = """ + + using AutomaticInterfaceAttribute; + + namespace AutomaticInterfaceExample; + + public class BaseClass + { + public virtual bool AMethod(); + } + + [GenerateAutomaticInterface] + public class DemoClass : BaseClass + { + public override bool AMethod() => return true; + } + + """; + + const string expected = """ + //-------------------------------------------------------------------------------------------------- + // + // This code was generated by a tool. + // + // Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + // + //-------------------------------------------------------------------------------------------------- + + using System.CodeDom.Compiler; + using AutomaticInterfaceAttribute; + + namespace AutomaticInterfaceExample + { + [GeneratedCode("AutomaticInterface", "")] + public partial interface IDemoClass + { + /// + bool AMethod(); + + } + } + + """; + GenerateCode(code).Should().Be(expected); + } + + [Fact] + public void WorksWithMethodShadowing() + { + const string code = """ + + using AutomaticInterfaceAttribute; + + namespace AutomaticInterfaceExample; + + public class BaseClass + { + public bool AMethod(); + } + + [GenerateAutomaticInterface] + public class DemoClass : BaseClass + { + public new bool AMethod() => return true; + } + + """; + + const string expected = """ + //-------------------------------------------------------------------------------------------------- + // + // This code was generated by a tool. + // + // Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + // + //-------------------------------------------------------------------------------------------------- + + using System.CodeDom.Compiler; + using AutomaticInterfaceAttribute; + + namespace AutomaticInterfaceExample + { + [GeneratedCode("AutomaticInterface", "")] + public partial interface IDemoClass + { + /// + bool AMethod(); + + } + } + + """; + GenerateCode(code).Should().Be(expected); + } } From 668ccfed7e61dc6782eb2ebc23107bf741e1f122 Mon Sep 17 00:00:00 2001 From: Simon McKenzie Date: Sat, 15 Jun 2024 20:32:14 +1000 Subject: [PATCH 2/2] Update deduplication so it won't merge overloads that only differ by parameter direction --- .../AutomaticInterface/Builder.cs | 5 +- AutomaticInterface/Tests/GeneratorTests.cs | 49 +++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/AutomaticInterface/AutomaticInterface/Builder.cs b/AutomaticInterface/AutomaticInterface/Builder.cs index 26932eb..b5fa56c 100644 --- a/AutomaticInterface/AutomaticInterface/Builder.cs +++ b/AutomaticInterface/AutomaticInterface/Builder.cs @@ -11,10 +11,11 @@ public static class Builder { private const string InheritDoc = "/// "; // we use inherit doc because that should be able to fetch documentation from base classes. - private static readonly SymbolDisplayFormat MethodDisplayFormatForDeduplication = + private static readonly SymbolDisplayFormat MethodSignatureDisplayFormat = new( memberOptions: SymbolDisplayMemberOptions.IncludeParameters, parameterOptions: SymbolDisplayParameterOptions.IncludeType + | SymbolDisplayParameterOptions.IncludeParamsRefOut ); public static string BuildInterfaceFor(ITypeSymbol typeSymbol) @@ -78,7 +79,7 @@ InterfaceBuilder codeGenerator .Where(x => x.MethodKind == MethodKind.Ordinary) .Where(x => !x.IsStatic) .Where(x => x.ContainingType.Name != nameof(Object)) - .GroupBy(x => x.ToDisplayString(MethodDisplayFormatForDeduplication)) + .GroupBy(x => x.ToDisplayString(MethodSignatureDisplayFormat)) .Select(g => g.First()) .ToList() .ForEach(method => diff --git a/AutomaticInterface/Tests/GeneratorTests.cs b/AutomaticInterface/Tests/GeneratorTests.cs index a168a74..ec4de03 100644 --- a/AutomaticInterface/Tests/GeneratorTests.cs +++ b/AutomaticInterface/Tests/GeneratorTests.cs @@ -2309,4 +2309,53 @@ public partial interface IDemoClass """; GenerateCode(code).Should().Be(expected); } + + [Fact] + public void WorksWithParameterDirectionOverloads() + { + const string code = """ + + using AutomaticInterfaceAttribute; + + namespace AutomaticInterfaceExample; + + [GenerateAutomaticInterface] + public class DemoClass + { + public void AMethod(int val) => return true; + + public void AMethod(ref int val) => return true; + } + + """; + + const string expected = """ + //-------------------------------------------------------------------------------------------------- + // + // This code was generated by a tool. + // + // Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + // + //-------------------------------------------------------------------------------------------------- + + using System.CodeDom.Compiler; + using AutomaticInterfaceAttribute; + + namespace AutomaticInterfaceExample + { + [GeneratedCode("AutomaticInterface", "")] + public partial interface IDemoClass + { + /// + void AMethod(int val); + + /// + void AMethod(ref int val); + + } + } + + """; + GenerateCode(code).Should().Be(expected); + } }