From a1a283c8bb995ab10624721d60edd67ce44d9838 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sat, 17 Dec 2022 11:28:16 +0100 Subject: [PATCH] Add support for parameter attributes (#48) * 1 * 2 * 3 * . * x --- .../Builders/MethodParameterBuilder.cs | 25 +++++++++++++++++ .../Compatibility/IsExternalInit.cs | 7 ----- .../Compatibility/NullableAttributes.cs | 17 ----------- .../Constants/InternalClassNames.cs | 6 ++++ .../Extensions/ParameterSymbolExtensions.cs | 11 ++++++++ .../FileGenerators/BaseGenerator.cs | 13 +++++---- .../PartialInterfacesGenerator.cs | 7 ----- .../FileGenerators/ProxyClassesGenerator.cs | 14 +++++++--- .../Models/ProxyData.cs | 7 +++++ .../ProxyInterfaceCodeGenerator.cs | 7 ++++- .../ProxyInterfaceSourceGenerator.csproj | 14 ++++++---- .../Utils/MemberHelper.cs | 28 +++++++++++-------- .../AkkaTests.cs | 2 -- ...ceSourceGeneratorTests.Source.IPerson.g.cs | 6 +++- ...urceGeneratorTests.Source.PersonProxy.g.cs | 21 ++++++++++++-- .../ProxyInterfaceSourceGeneratorTests.csproj | 1 + .../Source/Person.cs | 15 +++++++++- 17 files changed, 136 insertions(+), 65 deletions(-) create mode 100644 src/ProxyInterfaceSourceGenerator/Builders/MethodParameterBuilder.cs delete mode 100644 src/ProxyInterfaceSourceGenerator/Compatibility/IsExternalInit.cs delete mode 100644 src/ProxyInterfaceSourceGenerator/Compatibility/NullableAttributes.cs create mode 100644 src/ProxyInterfaceSourceGenerator/Constants/InternalClassNames.cs diff --git a/src/ProxyInterfaceSourceGenerator/Builders/MethodParameterBuilder.cs b/src/ProxyInterfaceSourceGenerator/Builders/MethodParameterBuilder.cs new file mode 100644 index 0000000..e7cec8a --- /dev/null +++ b/src/ProxyInterfaceSourceGenerator/Builders/MethodParameterBuilder.cs @@ -0,0 +1,25 @@ +using System.Text; +using Microsoft.CodeAnalysis; +using ProxyInterfaceSourceGenerator.Extensions; + +namespace ProxyInterfaceSourceGenerator.Builders; + +internal static class MethodParameterBuilder +{ + public static string Build(IParameterSymbol parameterSymbol, string? type) + { + var stringBuilder = new StringBuilder(); + if (type is { }) + { + stringBuilder.Append(parameterSymbol.GetAttributesPrefix()); // "" or [NotNullWhen(true)] + stringBuilder.Append(parameterSymbol.GetParamsPrefix()); // "" or "params " + stringBuilder.Append(parameterSymbol.GetRefPrefix()); // "" or "out " + stringBuilder.AppendFormat("{0} ", type); // string or another type + } + + stringBuilder.Append(parameterSymbol.GetSanitizedName()); // "s" or "i" or ... + stringBuilder.Append(parameterSymbol.GetDefaultValue()); // "" or the value + + return stringBuilder.ToString(); + } +} \ No newline at end of file diff --git a/src/ProxyInterfaceSourceGenerator/Compatibility/IsExternalInit.cs b/src/ProxyInterfaceSourceGenerator/Compatibility/IsExternalInit.cs deleted file mode 100644 index 53b909b..0000000 --- a/src/ProxyInterfaceSourceGenerator/Compatibility/IsExternalInit.cs +++ /dev/null @@ -1,7 +0,0 @@ -// https://bartwullems.blogspot.com/2021/01/c-9use-record-types-in-net-standard-20.html -#if NETSTANDARD2_0 -namespace System.Runtime.CompilerServices -{ - public class IsExternalInit { } -} -#endif \ No newline at end of file diff --git a/src/ProxyInterfaceSourceGenerator/Compatibility/NullableAttributes.cs b/src/ProxyInterfaceSourceGenerator/Compatibility/NullableAttributes.cs deleted file mode 100644 index 5aceb18..0000000 --- a/src/ProxyInterfaceSourceGenerator/Compatibility/NullableAttributes.cs +++ /dev/null @@ -1,17 +0,0 @@ -// https://stackoverflow.com/questions/61573959/how-to-resolve-error-notnullwhen-attribute-is-inaccessible-due-to-its-protectio - -namespace System.Diagnostics.CodeAnalysis; - -/// Specifies that when a method returns , the parameter will not be null even if the corresponding type allows it. -[AttributeUsage(AttributeTargets.Parameter, Inherited = false)] -internal sealed class NotNullWhenAttribute : Attribute -{ - /// Initializes the attribute with the specified return value condition. - /// - /// The return value condition. If the method returns this value, the associated parameter will not be null. - /// - public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; - - /// Gets the return value condition. - public bool ReturnValue { get; } -} \ No newline at end of file diff --git a/src/ProxyInterfaceSourceGenerator/Constants/InternalClassNames.cs b/src/ProxyInterfaceSourceGenerator/Constants/InternalClassNames.cs new file mode 100644 index 0000000..1fbc5c1 --- /dev/null +++ b/src/ProxyInterfaceSourceGenerator/Constants/InternalClassNames.cs @@ -0,0 +1,6 @@ +namespace ProxyInterfaceSourceGenerator.Constants; + +internal static class InternalClassNames +{ + public const string NullableAttribute = "System.Runtime.CompilerServices.NullableAttribute"; +} \ No newline at end of file diff --git a/src/ProxyInterfaceSourceGenerator/Extensions/ParameterSymbolExtensions.cs b/src/ProxyInterfaceSourceGenerator/Extensions/ParameterSymbolExtensions.cs index bc2682c..df2c7b2 100644 --- a/src/ProxyInterfaceSourceGenerator/Extensions/ParameterSymbolExtensions.cs +++ b/src/ProxyInterfaceSourceGenerator/Extensions/ParameterSymbolExtensions.cs @@ -1,5 +1,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using ProxyInterfaceSourceGenerator.Constants; using ProxyInterfaceSourceGenerator.Enums; namespace ProxyInterfaceSourceGenerator.Extensions; @@ -8,6 +9,16 @@ internal static class ParameterSymbolExtensions { private const string ParameterValueNull = "null"; + public static string GetAttributesPrefix(this IParameterSymbol ps) + { + var attributes = ps.GetAttributes() + .Where(a => !string.Equals(a.AttributeClass?.GetFullType(), InternalClassNames.NullableAttribute, StringComparison.OrdinalIgnoreCase)) + .Select(a => $"[{a}]") + .ToArray(); + + return attributes.Any() ? $"{string.Join(" ", attributes)} " : string.Empty; + } + public static string GetRefPrefix(this IParameterSymbol ps) { return ps.RefKind switch diff --git a/src/ProxyInterfaceSourceGenerator/FileGenerators/BaseGenerator.cs b/src/ProxyInterfaceSourceGenerator/FileGenerators/BaseGenerator.cs index e913a4a..7937772 100644 --- a/src/ProxyInterfaceSourceGenerator/FileGenerators/BaseGenerator.cs +++ b/src/ProxyInterfaceSourceGenerator/FileGenerators/BaseGenerator.cs @@ -2,6 +2,7 @@ using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Text; using Microsoft.CodeAnalysis; +using ProxyInterfaceSourceGenerator.Builders; using ProxyInterfaceSourceGenerator.Enums; using ProxyInterfaceSourceGenerator.Extensions; using ProxyInterfaceSourceGenerator.Models; @@ -203,18 +204,18 @@ internal abstract class BaseGenerator return false; } - protected IList GetMethodParameters(ImmutableArray parameters, bool includeType) + protected IReadOnlyList GetMethodParameters(ImmutableArray parameterSymbols, bool includeType) { var methodParameters = new List(); - foreach (var ps in parameters) + foreach (var parameterSymbol in parameterSymbols) { - string t = string.Empty; + string? type = null; if (includeType) { - var type = ps.GetTypeEnum() == TypeEnum.Complex ? GetParameterType(ps, out _) : ps.Type.ToString(); - t = $"{ps.GetParamsPrefix()}{ps.GetRefPrefix()}{type} "; + type = parameterSymbol.GetTypeEnum() == TypeEnum.Complex ? GetParameterType(parameterSymbol, out _) : parameterSymbol.Type.ToString(); } - methodParameters.Add($"{t}{ps.GetSanitizedName()}{ps.GetDefaultValue()}"); + + methodParameters.Add(MethodParameterBuilder.Build(parameterSymbol, type)); } return methodParameters; diff --git a/src/ProxyInterfaceSourceGenerator/FileGenerators/PartialInterfacesGenerator.cs b/src/ProxyInterfaceSourceGenerator/FileGenerators/PartialInterfacesGenerator.cs index 964d8ff..6f681c2 100644 --- a/src/ProxyInterfaceSourceGenerator/FileGenerators/PartialInterfacesGenerator.cs +++ b/src/ProxyInterfaceSourceGenerator/FileGenerators/PartialInterfacesGenerator.cs @@ -123,13 +123,6 @@ using System; foreach (var method in MemberHelper.GetPublicMethods(targetClassSymbol, proxyBaseClasses)) { var methodParameters = GetMethodParameters(method.Parameters, true); - //var methodParameters = new List(); - //foreach (var ps in method.Parameters) - //{ - // var type = ps.GetTypeEnum() == TypeEnum.Complex ? GetParameterType(ps, out _) : ps.Type.ToString(); - // methodParameters.Add($"{ps.GetParamsPrefix()}{ps.GetRefPrefix()}{type} {ps.GetSanitizedName()}{ps.GetDefaultValue()}"); - //} - var whereStatement = GetWhereStatementFromMethod(method); str.AppendLine($" {GetReplacedType(method.ReturnType, out _)} {method.GetMethodNameWithOptionalTypeParameters()}({string.Join(", ", methodParameters)}){whereStatement};"); diff --git a/src/ProxyInterfaceSourceGenerator/FileGenerators/ProxyClassesGenerator.cs b/src/ProxyInterfaceSourceGenerator/FileGenerators/ProxyClassesGenerator.cs index 1849add..dce2703 100644 --- a/src/ProxyInterfaceSourceGenerator/FileGenerators/ProxyClassesGenerator.cs +++ b/src/ProxyInterfaceSourceGenerator/FileGenerators/ProxyClassesGenerator.cs @@ -1,6 +1,7 @@ using System.Diagnostics.CodeAnalysis; using System.Text; using Microsoft.CodeAnalysis; +using ProxyInterfaceSourceGenerator.Builders; using ProxyInterfaceSourceGenerator.Enums; using ProxyInterfaceSourceGenerator.Extensions; using ProxyInterfaceSourceGenerator.Models; @@ -188,15 +189,20 @@ using System; var str = new StringBuilder(); foreach (var method in MemberHelper.GetPublicMethods(targetClassSymbol, proxyBaseClasses)) { + if (method.Name == "TryParse") + { + int y = 0; + } + var methodParameters = new List(); var invokeParameters = new List(); - foreach (var ps in method.Parameters) + foreach (var parameterSymbol in method.Parameters) { - var type = GetParameterType(ps, out _); + var type = GetParameterType(parameterSymbol, out _); - methodParameters.Add($"{ps.GetParamsPrefix()}{ps.GetRefPrefix()}{type} {ps.GetSanitizedName()}{ps.GetDefaultValue()}"); - invokeParameters.Add($"{ps.GetRefPrefix()}{ps.GetSanitizedName()}_"); + methodParameters.Add(MethodParameterBuilder.Build(parameterSymbol, type)); + invokeParameters.Add($"{parameterSymbol.GetRefPrefix()}{parameterSymbol.GetSanitizedName()}_"); } string overrideOrVirtual = string.Empty; diff --git a/src/ProxyInterfaceSourceGenerator/Models/ProxyData.cs b/src/ProxyInterfaceSourceGenerator/Models/ProxyData.cs index cf175ac..ebe7855 100644 --- a/src/ProxyInterfaceSourceGenerator/Models/ProxyData.cs +++ b/src/ProxyInterfaceSourceGenerator/Models/ProxyData.cs @@ -3,11 +3,18 @@ namespace ProxyInterfaceSourceGenerator.Models; internal class ProxyData { public string Namespace { get; init; } + public string ShortInterfaceName { get; init; } + public string FullInterfaceName { get; init; } + public string FullRawTypeName { get; set; } + public string ShortTypeName { get; init; } + public string FullTypeName { get; init; } + public List Usings { get; init; } + public bool ProxyBaseClasses { get; init; } } \ No newline at end of file diff --git a/src/ProxyInterfaceSourceGenerator/ProxyInterfaceCodeGenerator.cs b/src/ProxyInterfaceSourceGenerator/ProxyInterfaceCodeGenerator.cs index 8e153c8..a3daa46 100644 --- a/src/ProxyInterfaceSourceGenerator/ProxyInterfaceCodeGenerator.cs +++ b/src/ProxyInterfaceSourceGenerator/ProxyInterfaceCodeGenerator.cs @@ -9,7 +9,12 @@ using ProxyInterfaceSourceGenerator.SyntaxReceiver; namespace ProxyInterfaceSourceGenerator; [Generator] -internal class ProxyInterfaceCodeGenerator : ISourceGenerator +#if DEBUG +public +#else +internal +#endif +class ProxyInterfaceCodeGenerator : ISourceGenerator { private readonly ProxyAttributeGenerator _proxyAttributeGenerator = new (); diff --git a/src/ProxyInterfaceSourceGenerator/ProxyInterfaceSourceGenerator.csproj b/src/ProxyInterfaceSourceGenerator/ProxyInterfaceSourceGenerator.csproj index a9bb283..5bd2082 100644 --- a/src/ProxyInterfaceSourceGenerator/ProxyInterfaceSourceGenerator.csproj +++ b/src/ProxyInterfaceSourceGenerator/ProxyInterfaceSourceGenerator.csproj @@ -42,6 +42,10 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + @@ -65,9 +69,9 @@ - + <_Parameter1>DynamicProxyGenAssembly2 diff --git a/src/ProxyInterfaceSourceGenerator/Utils/MemberHelper.cs b/src/ProxyInterfaceSourceGenerator/Utils/MemberHelper.cs index 1fc7f9e..2484bb0 100644 --- a/src/ProxyInterfaceSourceGenerator/Utils/MemberHelper.cs +++ b/src/ProxyInterfaceSourceGenerator/Utils/MemberHelper.cs @@ -7,7 +7,7 @@ internal static class MemberHelper { private static readonly string[] ExcludedMethods = { "ToString", "GetHashCode" }; - public static IEnumerable GetPublicProperties( + public static IReadOnlyList GetPublicProperties( ClassSymbol classSymbol, bool proxyBaseClasses, params Func[] filters) @@ -17,25 +17,28 @@ internal static class MemberHelper p => p.Kind == SymbolKind.Property }; - return GetPublicMembers(classSymbol, proxyBaseClasses, allFilters.ToArray()); + return GetPublicMembers(classSymbol, proxyBaseClasses, allFilters.ToArray()).ToArray(); } - public static IEnumerable GetPublicMethods( + public static IReadOnlyList GetPublicMethods( ClassSymbol classSymbol, bool proxyBaseClasses, Func? filter = null) { filter ??= _ => true; - return GetPublicMembers(classSymbol, - proxyBaseClasses, - m => m.Kind == SymbolKind.Method, - m => m.MethodKind == MethodKind.Ordinary, - m => !ExcludedMethods.Contains(m.Name), - filter); + return + GetPublicMembers( + classSymbol, + proxyBaseClasses, + m => m.Kind == SymbolKind.Method, + m => m.MethodKind == MethodKind.Ordinary, + m => !ExcludedMethods.Contains(m.Name), + filter) + .ToArray(); } - public static IEnumerable> GetPublicEvents( + public static IReadOnlyList> GetPublicEvents( ClassSymbol classSymbol, bool proxyBaseClasses, Func? filter = null) @@ -48,13 +51,14 @@ internal static class MemberHelper proxyBaseClasses, m => m.MethodKind is MethodKind.EventAdd or MethodKind.EventRemove/* || m.MethodKind == MethodKind.EventRaise*/, filter) - .GroupBy(e => e.AssociatedSymbol); + .GroupBy(e => e.AssociatedSymbol) + .ToArray(); #pragma warning restore RS1024 // Compare symbols correctly #pragma warning restore CS8619 // Nullability of reference types in value doesn't match target type. } // TODO : do we need also to check for "SanitizedName()" here? - private static IEnumerable GetPublicMembers( + private static IReadOnlyList GetPublicMembers( ClassSymbol classSymbol, bool proxyBaseClasses, params Func[] filters diff --git a/tests/ProxyInterfaceSourceGeneratorTests/AkkaTests.cs b/tests/ProxyInterfaceSourceGeneratorTests/AkkaTests.cs index a23d202..f2118ba 100644 --- a/tests/ProxyInterfaceSourceGeneratorTests/AkkaTests.cs +++ b/tests/ProxyInterfaceSourceGeneratorTests/AkkaTests.cs @@ -1,5 +1,3 @@ -using System.IO; -using System.Linq; using CSharp.SourceGenerators.Extensions; using CSharp.SourceGenerators.Extensions.Models; using CultureAwareTesting.xUnit; diff --git a/tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.Source.IPerson.g.cs b/tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.Source.IPerson.g.cs index a931497..3e6956f 100644 --- a/tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.Source.IPerson.g.cs +++ b/tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.Source.IPerson.g.cs @@ -60,7 +60,11 @@ namespace ProxyInterfaceSourceGeneratorTests.Source System.Threading.Tasks.Task Method3Async(); - void CreateInvokeHttpClient(int i = 5, string? appId = null, System.Collections.Generic.IReadOnlyDictionary metadata = null, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)); + void CreateInvokeHttpClient(int i = 5, string? appId = null, System.Collections.Generic.IReadOnlyDictionary? metadata = null, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)); + + bool TryParse(string s1, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] params int[]? ii); + + bool TryParse(string s2, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out int? i); diff --git a/tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.Source.PersonProxy.g.cs b/tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.Source.PersonProxy.g.cs index 5306c0a..0ffc2ea 100644 --- a/tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.Source.PersonProxy.g.cs +++ b/tests/ProxyInterfaceSourceGeneratorTests/Destination/ProxyInterfaceSourceGeneratorTests.Source.PersonProxy.g.cs @@ -135,15 +135,32 @@ namespace ProxyInterfaceSourceGeneratorTests.Source return result__57684656; } - public void CreateInvokeHttpClient(int i = 5, string? appId = null, System.Collections.Generic.IReadOnlyDictionary metadata = null, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) + public void CreateInvokeHttpClient(int i = 5, string? appId = null, System.Collections.Generic.IReadOnlyDictionary? metadata = null, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) { int i_ = i; string? appId_ = appId; - System.Collections.Generic.IReadOnlyDictionary metadata_ = metadata; + System.Collections.Generic.IReadOnlyDictionary? metadata_ = metadata; System.Threading.CancellationToken token_ = token; _Instance.CreateInvokeHttpClient(i_, appId_, metadata_, token_); } + public bool TryParse(string s1, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] params int[]? ii) + { + string s1_ = s1; + int[]? ii_ = ii; + var result__1226565302 = _Instance.TryParse(s1_, ii_); + return result__1226565302; + } + + public bool TryParse(string s2, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out int? i) + { + string s2_ = s2; + int? i_; + var result__1226565302 = _Instance.TryParse(s2_, out i_); + i = i_; + return result__1226565302; + } + diff --git a/tests/ProxyInterfaceSourceGeneratorTests/ProxyInterfaceSourceGeneratorTests.csproj b/tests/ProxyInterfaceSourceGeneratorTests/ProxyInterfaceSourceGeneratorTests.csproj index ef27c3e..6981157 100644 --- a/tests/ProxyInterfaceSourceGeneratorTests/ProxyInterfaceSourceGeneratorTests.csproj +++ b/tests/ProxyInterfaceSourceGeneratorTests/ProxyInterfaceSourceGeneratorTests.csproj @@ -6,6 +6,7 @@ 10.0 enable Debug;Release;DebugAttach + enable diff --git a/tests/ProxyInterfaceSourceGeneratorTests/Source/Person.cs b/tests/ProxyInterfaceSourceGeneratorTests/Source/Person.cs index 8857528..9f363cd 100644 --- a/tests/ProxyInterfaceSourceGeneratorTests/Source/Person.cs +++ b/tests/ProxyInterfaceSourceGeneratorTests/Source/Person.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; @@ -103,8 +104,20 @@ namespace ProxyInterfaceSourceGeneratorTests.Source return Task.FromResult((string?)""); } - public void CreateInvokeHttpClient(int i = 5, string? appId = null, IReadOnlyDictionary metadata = null, CancellationToken token = default) + public void CreateInvokeHttpClient(int i = 5, string? appId = null, IReadOnlyDictionary? metadata = null, CancellationToken token = default) { } + + public bool TryParse(string s1, [NotNullWhen(true)]params int[]? ii) + { + ii = null; + return false; + } + + public bool TryParse(string s2, [NotNullWhen(true)] out int? i) + { + i = 4; + return true; + } } } \ No newline at end of file