From a1eee5fe6c49407fc555ce2c5e6d400c78d70134 Mon Sep 17 00:00:00 2001 From: Christian Sauer Date: Wed, 31 Jan 2024 12:03:27 +0100 Subject: [PATCH 1/2] Support for nullable generics --- AutomaticInterface/AutomaticInterface.sln | 1 + .../AutomaticInterface.csproj | 2 +- .../AutomaticInterface/Builder.cs | 64 ++++- .../AutomaticInterfaceExample/DemoClass.cs | 8 + AutomaticInterface/Tests/GeneratorTests.cs | 220 ++++++++++++++++++ README.md | 8 + 6 files changed, 293 insertions(+), 10 deletions(-) diff --git a/AutomaticInterface/AutomaticInterface.sln b/AutomaticInterface/AutomaticInterface.sln index b153c83..f6d30ce 100644 --- a/AutomaticInterface/AutomaticInterface.sln +++ b/AutomaticInterface/AutomaticInterface.sln @@ -15,6 +15,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject Directory.Build.targets = Directory.Build.targets ..\.github\workflows\action.yml = ..\.github\workflows\action.yml + ..\README.md = ..\README.md EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestNuget", "TestNuget\TestNuget.csproj", "{E7CF6623-54D8-4A40-A1B1-CE078551EFE1}" diff --git a/AutomaticInterface/AutomaticInterface/AutomaticInterface.csproj b/AutomaticInterface/AutomaticInterface/AutomaticInterface.csproj index bfbe783..7359f8a 100644 --- a/AutomaticInterface/AutomaticInterface/AutomaticInterface.csproj +++ b/AutomaticInterface/AutomaticInterface/AutomaticInterface.csproj @@ -25,7 +25,7 @@ MIT True latest-Recommended - 2.1.0 + 2.1.1 README.md true 1701;1702;NU5128 diff --git a/AutomaticInterface/AutomaticInterface/Builder.cs b/AutomaticInterface/AutomaticInterface/Builder.cs index 796931b..2649b8f 100644 --- a/AutomaticInterface/AutomaticInterface/Builder.cs +++ b/AutomaticInterface/AutomaticInterface/Builder.cs @@ -84,15 +84,7 @@ private static void AddMethod(InterfaceBuilder codeGenerator, IMethodSymbol meth var returnType = method.ReturnType; var name = method.Name; - var hasNullableParameter = method.Parameters.Any(x => - x.NullableAnnotation == NullableAnnotation.Annotated - ); - var hasNullableReturn = - method.ReturnType.NullableAnnotation == NullableAnnotation.Annotated; - if (hasNullableParameter || hasNullableReturn) - { - codeGenerator.HasNullable = true; - } + ActivateNullableIfNeeded(codeGenerator, method); var paramResult = new HashSet(); method.Parameters.Select(GetMethodSignature).ToList().ForEach(x => paramResult.Add(x)); @@ -100,6 +92,7 @@ private static void AddMethod(InterfaceBuilder codeGenerator, IMethodSymbol meth var typedArgs = method .TypeParameters.Select(arg => (arg.ToDisplayString(), arg.GetWhereStatement())) .ToList(); + codeGenerator.AddMethodToInterface( name, returnType.ToDisplayString(), @@ -109,6 +102,55 @@ private static void AddMethod(InterfaceBuilder codeGenerator, IMethodSymbol meth ); } + private static void ActivateNullableIfNeeded( + InterfaceBuilder codeGenerator, + ITypeSymbol typeSymbol + ) + { + if (IsNullable(typeSymbol)) + { + codeGenerator.HasNullable = true; + } + } + + private static void ActivateNullableIfNeeded( + InterfaceBuilder codeGenerator, + IMethodSymbol method + ) + { + var hasNullableParameter = method.Parameters.Any(x => IsNullable(x.Type)); + + var hasNullableReturn = IsNullable(method.ReturnType); + + if (hasNullableParameter || hasNullableReturn) + { + codeGenerator.HasNullable = true; + } + } + + private static bool IsNullable(ITypeSymbol typeSymbol) + { + if (typeSymbol.NullableAnnotation == NullableAnnotation.Annotated) + { + return true; + } + + if (typeSymbol is not INamedTypeSymbol named) + { + return false; + } + + foreach (var param in named.TypeArguments) + { + if (IsNullable(param)) + { + return true; + } + } + + return false; + } + private static void AddEventsToInterface( ITypeSymbol classSymbol, InterfaceBuilder codeGenerator @@ -126,6 +168,8 @@ InterfaceBuilder codeGenerator var type = evt.Type; var name = evt.Name; + ActivateNullableIfNeeded(codeGenerator, type); + codeGenerator.AddEventToInterface(name, type.ToDisplayString(), InheritDoc); }); } @@ -169,6 +213,8 @@ InterfaceBuilder interfaceGenerator var hasGet = prop.GetMethod?.DeclaredAccessibility == Accessibility.Public; var hasSet = prop.SetMethod?.DeclaredAccessibility == Accessibility.Public; + ActivateNullableIfNeeded(interfaceGenerator, type); + interfaceGenerator.AddPropertyToInterface( name, type.ToDisplayString(), diff --git a/AutomaticInterface/AutomaticInterfaceExample/DemoClass.cs b/AutomaticInterface/AutomaticInterfaceExample/DemoClass.cs index 7c3742f..af4950d 100644 --- a/AutomaticInterface/AutomaticInterfaceExample/DemoClass.cs +++ b/AutomaticInterface/AutomaticInterfaceExample/DemoClass.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using AutomaticInterfaceAttribute; namespace AutomaticInterfaceExample @@ -45,6 +46,11 @@ public string CMethod(string? x, string y) // included return "Ok"; } + public Task ASync(string x, string y) + { + return Task.FromResult(""); + } + public static string StaticProperty => "abc"; // static property, ignored public static string StaticMethod() // static method, ignored @@ -67,6 +73,8 @@ public static string StaticMethod() // static method, ignored public event EventHandler? ShapeChangedNullable; // included + public event EventHandler ShapeChangedNullable2; // included + private readonly int[] arr = new int[100]; public int this[int index] // currently ignored diff --git a/AutomaticInterface/Tests/GeneratorTests.cs b/AutomaticInterface/Tests/GeneratorTests.cs index 1335313..21da44b 100644 --- a/AutomaticInterface/Tests/GeneratorTests.cs +++ b/AutomaticInterface/Tests/GeneratorTests.cs @@ -1648,6 +1648,65 @@ public partial interface IDemoClass GenerateCode(code).Should().Be(expected); } + [Fact] + public void NullableEvent2() + { + const string code = """ + + using AutomaticInterfaceAttribute; + using System; + + namespace AutomaticInterfaceExample + { + /// + /// Bla bla + /// + [GenerateAutomaticInterface] + class DemoClass + { + + /// + /// Bla bla + /// + public event EventHandler ShapeChangedNullable; + } + } + + """; + + 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. + // + //-------------------------------------------------------------------------------------------------- + + #nullable enable + using System.CodeDom.Compiler; + using AutomaticInterfaceAttribute; + using System; + + namespace AutomaticInterfaceExample + { + /// + /// Bla bla + /// + [GeneratedCode("AutomaticInterface", "")] + public partial interface IDemoClass + { + /// + event System.EventHandler ShapeChangedNullable; + + } + } + #nullable restore + + """; + GenerateCode(code).Should().Be(expected); + } + [Fact] public void NullableProperty() { @@ -1705,6 +1764,67 @@ public partial interface IDemoClass GenerateCode(code).Should().Be(expected); } + [Fact] + public void NullableProperty2() + { + const string code = """ + + using AutomaticInterfaceAttribute; + using System; + using System.Threading.Tasks; + + namespace AutomaticInterfaceExample + { + /// + /// Bla bla + /// + [GenerateAutomaticInterface] + class DemoClass + { + + /// + /// Bla bla + /// + public Task NullableProperty { get; set; } + } + } + + """; + + 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. + // + //-------------------------------------------------------------------------------------------------- + + #nullable enable + using System.CodeDom.Compiler; + using AutomaticInterfaceAttribute; + using System; + using System.Threading.Tasks; + + namespace AutomaticInterfaceExample + { + /// + /// Bla bla + /// + [GeneratedCode("AutomaticInterface", "")] + public partial interface IDemoClass + { + /// + System.Threading.Tasks.Task NullableProperty { get; set; } + + } + } + #nullable restore + + """; + GenerateCode(code).Should().Be(expected); + } + [Fact] public void BooleanWithNonNull() { @@ -1842,4 +1962,104 @@ public partial interface IDemoClass """; GenerateCode(code).Should().Be(expected); } + + [Fact] + public void WorksWithNullableGeneric() + { + const string code = """ + + using AutomaticInterfaceAttribute; + using System.Threading.Tasks; + + namespace AutomaticInterfaceExample; + [GenerateAutomaticInterface] + public class DemoClass + { + public Task AMethodAsync(DemoClass x, string y) + { + return "Ok"; + } + } + + """; + + 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. + // + //-------------------------------------------------------------------------------------------------- + + #nullable enable + using System.CodeDom.Compiler; + using AutomaticInterfaceAttribute; + using System.Threading.Tasks; + + namespace AutomaticInterfaceExample + { + [GeneratedCode("AutomaticInterface", "")] + public partial interface IDemoClass + { + /// + System.Threading.Tasks.Task AMethodAsync(AutomaticInterfaceExample.DemoClass x, string y); + + } + } + #nullable restore + + """; + GenerateCode(code).Should().Be(expected); + } + + [Fact] + public void WorksWithNullableGeneric2() + { + const string code = """ + + using AutomaticInterfaceAttribute; + using System.Threading.Tasks; + + namespace AutomaticInterfaceExample; + [GenerateAutomaticInterface] + public class DemoClass + { + public string AMethod(Task x, string y) + { + return "Ok"; + } + } + + """; + + 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. + // + //-------------------------------------------------------------------------------------------------- + + #nullable enable + using System.CodeDom.Compiler; + using AutomaticInterfaceAttribute; + using System.Threading.Tasks; + + namespace AutomaticInterfaceExample + { + [GeneratedCode("AutomaticInterface", "")] + public partial interface IDemoClass + { + /// + string AMethod(System.Threading.Tasks.Task x, string y); + + } + } + #nullable restore + + """; + GenerateCode(code).Should().Be(expected); + } } diff --git a/README.md b/README.md index 2f6c10d..0c32da4 100644 --- a/README.md +++ b/README.md @@ -162,18 +162,26 @@ Should be simply a build and run Tests ## Changelog +### 2.0.1 + +- Better support for nullable like Task, previously only top level generic where considered + ### 2.0.0 + - Major rework to Incremental Source generator - Fixed some outstanding bugs - Removed logging, b/c not really used - Increased coverage ### 1.6.1 + - Minor bug fixes ### 1.5.0 + - Add support nullable context ### 1.4.0 + - Add support for overloaded methods. - Add support for optional parameters in method `void test(string x = null)` should now work. From 23f0cc066d6d08915d202cde8ccd6a51172db8cf Mon Sep 17 00:00:00 2001 From: Christian Sauer Date: Wed, 31 Jan 2024 12:04:55 +0100 Subject: [PATCH 2/2] Readme --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0c32da4..c33f66c 100644 --- a/README.md +++ b/README.md @@ -162,8 +162,9 @@ Should be simply a build and run Tests ## Changelog -### 2.0.1 +### 2.1.1 +- Fix bug where multiple automatic interfaces caused issues - Better support for nullable like Task, previously only top level generic where considered ### 2.0.0