Rhino required fixes (#9)

* add fixes for structs

* handle null non-nullables to nullable nulls

* test fix

* add enum test even if it's gross looking

* generate nullable adapt when needed

* fix adapter tests

* don't generate static events

* fmt

* mark for release
This commit is contained in:
Adam Hathcock
2024-06-18 13:04:08 +01:00
committed by GitHub
parent 4c8bb894c6
commit c71fc31132
16 changed files with 476 additions and 68 deletions
@@ -7,30 +7,4 @@ namespace Speckle.ProxyGenerator.Extensions;
internal static class PropertySymbolExtensions
{
public static TypeEnum GetTypeEnum(this IPropertySymbol p) => p.Type.GetTypeEnum();
public static (string PropertyType, string? PropertyName, string GetSet)? ToPropertyDetails(
this IPropertySymbol property,
string? overrideType = null
)
{
var getIsPublic = property.GetMethod.IsPublic();
var setIsPublic = property.SetMethod.IsPublic();
if (!getIsPublic && !setIsPublic)
{
return null;
}
var get = getIsPublic ? "get; " : string.Empty;
var set = setIsPublic ? "set; " : string.Empty;
var type = !string.IsNullOrEmpty(overrideType)
? overrideType
: BaseGenerator.FixType(
property.Type.ToFullyQualifiedDisplayString(),
property.NullableAnnotation
);
return (type!, property.GetSanitizedName(), $"{{ {get}{set}}}");
}
}
@@ -21,14 +21,14 @@ internal abstract class BaseGenerator
SupportsNullable = supportsNullable;
}
protected string GetPropertyType(IPropertySymbol property, out bool isReplaced)
protected FixedType GetPropertyType(IPropertySymbol property, out bool isReplaced)
{
return GetReplacedTypeAsString(property.Type, out isReplaced);
return GetReplacedTypeAsString(property.Type, null, out isReplaced);
}
protected string GetParameterType(IParameterSymbol property, out bool isReplaced)
protected FixedType GetParameterType(IParameterSymbol property, out bool isReplaced)
{
return GetReplacedTypeAsString(property.Type, out isReplaced);
return GetReplacedTypeAsString(property.Type, property.GetDefaultValue(), out isReplaced);
}
protected bool TryFindProxyDataByTypeName(
@@ -119,7 +119,8 @@ internal abstract class BaseGenerator
{
if (replaceIt)
{
constraints.Add(GetReplacedTypeAsString(namedTypeSymbol, out _));
var (_, type) = GetReplacedTypeAsString(namedTypeSymbol, null, out _);
constraints.Add(type);
}
else
{
@@ -152,7 +153,11 @@ internal abstract class BaseGenerator
| SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier
);
protected string GetReplacedTypeAsString(ITypeSymbol typeSymbol, out bool isReplaced)
protected FixedType GetReplacedTypeAsString(
ITypeSymbol typeSymbol,
string? defaultValue,
out bool isReplaced
)
{
isReplaced = false;
@@ -170,7 +175,7 @@ internal abstract class BaseGenerator
}
isReplaced = true;
return FixType(existing.FullInterfaceName, typeSymbol.NullableAnnotation);
return FixType(existing.FullInterfaceName, typeSymbol.NullableAnnotation, defaultValue);
}
ITypeSymbol[] typeArguments;
@@ -184,7 +189,7 @@ internal abstract class BaseGenerator
}
else
{
return FixType(typeSymbolAsString, typeSymbol.NullableAnnotation);
return FixType(typeSymbolAsString, typeSymbol.NullableAnnotation, defaultValue);
}
var propertyTypeAsStringToBeModified = nullableTypeSymbolAsString;
@@ -211,7 +216,11 @@ internal abstract class BaseGenerator
}
}
return FixType(propertyTypeAsStringToBeModified, typeSymbol.NullableAnnotation);
return FixType(
propertyTypeAsStringToBeModified,
typeSymbol.NullableAnnotation,
defaultValue
);
}
protected bool TryGetNamedTypeSymbolByFullName(
@@ -272,13 +281,14 @@ internal abstract class BaseGenerator
{
if (parameterSymbol.GetTypeEnum() == TypeEnum.Complex)
{
type = GetParameterType(parameterSymbol, out _);
(_, type) = GetParameterType(parameterSymbol, out _);
}
else
{
type = FixType(
(_, type) = FixType(
parameterSymbol.Type.ToFullyQualifiedDisplayString(),
parameterSymbol.NullableAnnotation
parameterSymbol.NullableAnnotation,
parameterSymbol.GetDefaultValue()
);
}
}
@@ -316,15 +326,23 @@ internal abstract class BaseGenerator
return extendsProxyClasses;
}
internal static string FixType(string type, NullableAnnotation nullableAnnotation)
{
if (
nullableAnnotation == NullableAnnotation.Annotated
&& !type.EndsWith("?", StringComparison.Ordinal)
internal FixedType FixType(
string type,
NullableAnnotation nullableAnnotation,
string? defaultValue
)
{
return $"{type}?";
var na = nullableAnnotation;
if (SupportsNullable && defaultValue == " = null")
{
na = NullableAnnotation.Annotated;
}
return type;
if (na == NullableAnnotation.Annotated && !type.EndsWith("?", StringComparison.Ordinal))
{
return new(true, $"{type}?");
}
return new(false, type);
}
}
internal record FixedType(bool Fixed, string Type);
@@ -151,6 +151,18 @@ namespace Speckle.ProxyGenerator
public static object CreateProxy(Type type, object toWrap) => s_proxyFactory[type](toWrap);
public static T CreateProxy<T>(object toWrap) => (T)CreateProxy(typeof(T), toWrap);
}}
public static class MapsterAdapter
{{
public static TDestination? AdaptNull<TDestination>(object? source)
{{
if (source is null)
{{
return default;
}}
return Mapster.TypeAdapter.Adapt<TDestination>(source);
}}
}}
{supportsNullable.IIf("#nullable restore")}
}}"
);
@@ -191,11 +191,11 @@ methods}
)
)
{
var type = GetPropertyType(property, out var isReplaced);
var (_, type) = GetPropertyType(property, out var isReplaced);
var getterSetter = isReplaced
? property.ToPropertyDetails(type)
: property.ToPropertyDetails();
? ToPropertyDetails(property, type)
: ToPropertyDetails(property);
if (getterSetter is null)
{
continue;
@@ -241,8 +241,9 @@ methods}
str.AppendLine($" {attribute}");
}
var (_, type) = GetReplacedTypeAsString(method.ReturnType, null, out _);
str.AppendLine(
$" {GetReplacedTypeAsString(method.ReturnType, out _)} {method.GetMethodNameWithOptionalTypeParameters()}({string.Join(", ", methodParameters)}){whereStatement};"
$" {type} {method.GetMethodNameWithOptionalTypeParameters()}({string.Join(", ", methodParameters)}){whereStatement};"
);
str.AppendLine();
}
@@ -262,10 +263,16 @@ methods}
)
{
var ps = @event.First().Parameters.First();
var type =
ps.GetTypeEnum() == TypeEnum.Complex
? GetParameterType(ps, out _)
: ps.Type.ToString();
string? type;
if (ps.GetTypeEnum() == TypeEnum.Complex)
{
(_, type) = GetParameterType(ps, out _);
}
else
{
type = ps.Type.ToString();
}
foreach (var attribute in ps.GetAttributesAsList())
{
@@ -278,4 +285,37 @@ methods}
return str.ToString();
}
public (string PropertyType, string? PropertyName, string GetSet)? ToPropertyDetails(
IPropertySymbol property,
string? overrideType = null
)
{
var getIsPublic = property.GetMethod.IsPublic();
var setIsPublic = property.SetMethod.IsPublic();
if (!getIsPublic && !setIsPublic)
{
return null;
}
var get = getIsPublic ? "get; " : string.Empty;
var set = setIsPublic ? "set; " : string.Empty;
string? type;
if (!string.IsNullOrEmpty(overrideType))
{
type = overrideType;
}
else
{
(_, type) = FixType(
property.Type.ToFullyQualifiedDisplayString(),
property.NullableAnnotation,
null
);
}
return (type!, property.GetSanitizedName(), $"{{ {get}{set}}}");
}
}
@@ -182,7 +182,7 @@ using System;
foreach (var property in MemberHelper.GetPublicProperties(targetClassSymbol, proxyData))
{
var type = GetPropertyType(property, out var isReplaced);
var (_, type) = GetPropertyType(property, out var isReplaced);
var instance = !property.IsStatic ? "_Instance" : $"{targetClassSymbol.Symbol}";
@@ -261,7 +261,7 @@ using System;
foreach (var parameterSymbol in method.Parameters)
{
var type = GetParameterType(parameterSymbol, out _);
var (_, type) = GetParameterType(parameterSymbol, out _);
methodParameters.Add(MethodParameterBuilder.Build(parameterSymbol, type));
@@ -285,8 +285,9 @@ using System;
overrideOrVirtual = "virtual ";
}
string returnTypeAsString = GetReplacedTypeAsString(
var (_, returnTypeAsString) = GetReplacedTypeAsString(
method.ReturnType,
null,
out var returnIsReplaced
);
@@ -304,9 +305,10 @@ using System;
foreach (var ps in method.Parameters.Where(p => !p.IsRef()))
{
var type = FixType(
var (wasFixed, type) = FixType(
ps.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
ps.Type.NullableAnnotation
ps.Type.NullableAnnotation,
ps.GetDefaultValue()
);
string normalOrMap = $" = {ps.GetSanitizedName()}";
if (ps.RefKind == RefKind.Out)
@@ -317,11 +319,19 @@ using System;
{
_ = GetParameterType(ps, out var isReplaced); // TODO : response is not used?
if (isReplaced)
{
if (wasFixed)
{
normalOrMap =
$" = Speckle.ProxyGenerator.MapsterAdapter.AdaptNull<{type}>({ps.GetSanitizedName()})";
}
else
{
normalOrMap =
$" = Mapster.TypeAdapter.Adapt<{type}>({ps.GetSanitizedName()})";
}
}
}
str.AppendLine($" {type} {ps.GetSanitizedName()}_{normalOrMap};");
}
@@ -350,7 +360,7 @@ using System;
foreach (var ps in method.Parameters.Where(p => p.RefKind == RefKind.Out))
{
string normalOrMap = $" = {ps.GetSanitizedName()}_";
var type = GetParameterType(ps, out var isReplaced);
var (_, type) = GetParameterType(ps, out var isReplaced);
if (isReplaced)
{
normalOrMap = $" = Mapster.TypeAdapter.Adapt<{type}>({ps.GetSanitizedName()}_)";
@@ -385,12 +395,21 @@ using System;
var str = new StringBuilder();
foreach (var @event in MemberHelper.GetPublicEvents(targetClassSymbol, proxyData))
{
if (@event.Key.IsStatic)
{
continue;
}
var name = @event.Key.GetSanitizedName();
var ps = @event.First().Parameters.First();
var type =
ps.GetTypeEnum() == TypeEnum.Complex
? GetParameterType(ps, out _)
: ps.Type.ToString();
var type = string.Empty;
if (ps.GetTypeEnum() == TypeEnum.Complex)
{
(_, type) = GetParameterType(ps, out _);
}
else
{
type = ps.Type.ToString();
}
foreach (var attribute in ps.GetAttributesAsList())
{
@@ -417,6 +436,10 @@ using System;
private string GenerateOperators(ClassSymbol targetClassSymbol, ProxyData proxyData)
{
if (targetClassSymbol.Symbol.TypeKind != TypeKind.Class)
{
return string.Empty;
}
var str = new StringBuilder();
foreach (
var @operator in MemberHelper.GetPublicStaticOperators(targetClassSymbol, proxyData)
@@ -438,7 +461,11 @@ using System;
var operatorType = @operator.Name.ToLowerInvariant().Replace("op_", string.Empty);
if (operatorType == "explicit")
{
var returnTypeAsString = GetReplacedTypeAsString(@operator.ReturnType, out _);
var (_, returnTypeAsString) = GetReplacedTypeAsString(
@operator.ReturnType,
null,
out _
);
str.AppendLine(
$" public static explicit operator {returnTypeAsString}({proxyClassName} {parameter.Name})"
@@ -451,7 +478,7 @@ using System;
}
else
{
var returnTypeAsString = GetReplacedTypeAsString(parameter.Type, out _);
var (_, returnTypeAsString) = GetReplacedTypeAsString(parameter.Type, null, out _);
str.AppendLine(
$" public static implicit operator {proxyClassName}({returnTypeAsString} {parameter.Name})"
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Version>0.1.9</Version>
<Version>0.1.10</Version>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>Latest</LangVersion>
<Nullable>enable</Nullable>
@@ -207,6 +207,18 @@ namespace Speckle.ProxyGenerator
public static object CreateProxy(Type type, object toWrap) => s_proxyFactory[type](toWrap);
public static T CreateProxy<T>(object toWrap) => (T)CreateProxy(typeof(T), toWrap);
}
public static class MapsterAdapter
{
public static TDestination? AdaptNull<TDestination>(object? source)
{
if (source is null)
{
return default;
}
return Mapster.TypeAdapter.Adapt<TDestination>(source);
}
}
#nullable restore
}
}
@@ -0,0 +1,216 @@
[
{
HintName: ProxyInterfaceSourceGeneratorTests.Source.IFooNotNullable.g.cs,
Source:
//----------------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by https://github.com/specklesystems/ProxyGenerator.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//----------------------------------------------------------------------------------------
#nullable enable
using System;
namespace ProxyInterfaceSourceGeneratorTests.Source
{
public partial interface IFooNotNullable
{
global::ProxyInterfaceSourceGeneratorTests.Source.FooNotNullable _Instance { get; }
void Test(global::ProxyInterfaceSourceGeneratorTests.Source.FooEnum z = 1, global::System.Collections.Generic.IEnumerable<string>? v = null);
}
}
#nullable restore
},
{
HintName: ProxyInterfaceSourceGeneratorTests.Source.FooNotNullableProxy.g.cs,
Source:
//----------------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by https://github.com/specklesystems/ProxyGenerator.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//----------------------------------------------------------------------------------------
#nullable enable
using System;
namespace ProxyInterfaceSourceGeneratorTests.Source
{
public partial class FooNotNullableProxy : global::ProxyInterfaceSourceGeneratorTests.Source.IFooNotNullable
{
public global::ProxyInterfaceSourceGeneratorTests.Source.FooNotNullable _Instance { get; }
public void Test(global::ProxyInterfaceSourceGeneratorTests.Source.FooEnum z = 1, global::System.Collections.Generic.IEnumerable<string>? v = null)
{
global::ProxyInterfaceSourceGeneratorTests.Source.FooEnum z_ = z;
global::System.Collections.Generic.IEnumerable<string>? v_ = v;
_Instance.Test(z_, v_);
}
public FooNotNullableProxy(global::ProxyInterfaceSourceGeneratorTests.Source.FooNotNullable instance)
{
_Instance = instance;
}
}
}
#nullable restore
},
{
HintName: Speckle.ProxyGenerator.Extra.g.cs,
Source:
//----------------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by https://github.com/specklesystems/ProxyGenerator
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//----------------------------------------------------------------------------------------
#nullable enable
using System;
namespace Speckle.ProxyGenerator
{
[AttributeUsage(AttributeTargets.Interface)]
internal sealed class ProxyAttribute : Attribute
{
public Type Type { get; }
public ImplementationOptions Options { get; }
public ProxyClassAccessibility Accessibility { get; }
public string[]? MembersToIgnore { get; }
public ProxyAttribute(Type type) : this(type, ImplementationOptions.None, ProxyClassAccessibility.Public)
{
}
public ProxyAttribute(Type type, ImplementationOptions options) : this(type, options, ProxyClassAccessibility.Public)
{
}
public ProxyAttribute(Type type, ProxyClassAccessibility accessibility) : this(type, ImplementationOptions.None, accessibility)
{
}
public ProxyAttribute(Type type, ImplementationOptions options, ProxyClassAccessibility accessibility) : this(type, options, accessibility, null)
{
}
public ProxyAttribute(Type type, string[]? membersToIgnore) : this(type, ImplementationOptions.None, ProxyClassAccessibility.Public, null)
{
}
public ProxyAttribute(Type type, ImplementationOptions options, string[]? membersToIgnore) : this(type, options, ProxyClassAccessibility.Public, null)
{
}
public ProxyAttribute(Type type, ImplementationOptions options, ProxyClassAccessibility accessibility, string[]? membersToIgnore)
{
Type = type;
Options = options;
Accessibility = accessibility;
MembersToIgnore = membersToIgnore;
}
}
[Flags]
internal enum ProxyClassAccessibility
{
Public = 0,
Internal = 1
}
[Flags]
internal enum ImplementationOptions
{
None = 0,
ProxyBaseClasses = 1,
ProxyInterfaces = 2,
UseExtendedInterfaces = 4,
ProxyForBaseInterface = 8
}
public static class ProxyMap
{
private static readonly global::System.Collections.Concurrent.ConcurrentDictionary<Type, Type> s_revitToInterfaceMap = new();
private static readonly global::System.Collections.Concurrent.ConcurrentDictionary<Type, Type> s_proxyToInterfaceMap = new();
private static readonly global::System.Collections.Concurrent.ConcurrentDictionary<Type, Type> s_interfaceToRevit = new();
private static readonly global::System.Collections.Concurrent.ConcurrentDictionary<Type, Func<object, object>> s_proxyFactory = new();
static ProxyMap()
{
Add<ProxyInterfaceSourceGeneratorTests.Source.FooNotNullable, global::ProxyInterfaceSourceGeneratorTests.Source.IFooNotNullable, ProxyInterfaceSourceGeneratorTests.Source.FooNotNullableProxy>(x => new ProxyInterfaceSourceGeneratorTests.Source.FooNotNullableProxy(x));
}
private static void Add<T, TInterface, TProxy>(Func<T, TProxy> f)
where TInterface : notnull
where TProxy : TInterface
{
s_revitToInterfaceMap.TryAdd(typeof(T), typeof(TInterface));
s_proxyToInterfaceMap.TryAdd(typeof(TProxy), typeof(TInterface));
s_proxyFactory.TryAdd(typeof(TInterface), w => f((T)w));
s_interfaceToRevit.TryAdd(typeof(TInterface), typeof(T));
}
public static Type? GetMappedTypeFromHostType(Type type)
{
if (s_revitToInterfaceMap.TryGetValue(type, out var t))
{
return t;
}
return null;
}
public static Type? GetMappedTypeFromProxyType(Type type)
{
if (s_proxyToInterfaceMap.TryGetValue(type, out var t))
{
return t;
}
return null;
}
public static Type? GetHostTypeFromMappedType(Type type)
{
if (s_interfaceToRevit.TryGetValue(type, out var t))
{
return t;
}
return null;
}
public static object CreateProxy(Type type, object toWrap) => s_proxyFactory[type](toWrap);
public static T CreateProxy<T>(object toWrap) => (T)CreateProxy(typeof(T), toWrap);
}
public static class MapsterAdapter
{
public static TDestination? AdaptNull<TDestination>(object? source)
{
if (source is null)
{
return default;
}
return Mapster.TypeAdapter.Adapt<TDestination>(source);
}
}
#nullable restore
}
}
]
@@ -215,6 +215,18 @@ namespace Speckle.ProxyGenerator
public static object CreateProxy(Type type, object toWrap) => s_proxyFactory[type](toWrap);
public static T CreateProxy<T>(object toWrap) => (T)CreateProxy(typeof(T), toWrap);
}
public static class MapsterAdapter
{
public static TDestination? AdaptNull<TDestination>(object? source)
{
if (source is null)
{
return default;
}
return Mapster.TypeAdapter.Adapt<TDestination>(source);
}
}
#nullable restore
}
}
@@ -217,6 +217,18 @@ namespace Speckle.ProxyGenerator
public static object CreateProxy(Type type, object toWrap) => s_proxyFactory[type](toWrap);
public static T CreateProxy<T>(object toWrap) => (T)CreateProxy(typeof(T), toWrap);
}
public static class MapsterAdapter
{
public static TDestination? AdaptNull<TDestination>(object? source)
{
if (source is null)
{
return default;
}
return Mapster.TypeAdapter.Adapt<TDestination>(source);
}
}
#nullable restore
}
}
@@ -268,6 +268,18 @@ Add<ProxyInterfaceSourceGeneratorTests.Source.Bar3, global::ProxyInterfaceSource
public static object CreateProxy(Type type, object toWrap) => s_proxyFactory[type](toWrap);
public static T CreateProxy<T>(object toWrap) => (T)CreateProxy(typeof(T), toWrap);
}
public static class MapsterAdapter
{
public static TDestination? AdaptNull<TDestination>(object? source)
{
if (source is null)
{
return default;
}
return Mapster.TypeAdapter.Adapt<TDestination>(source);
}
}
#nullable restore
}
}
@@ -280,6 +280,18 @@ Add<ProxyInterfaceSourceGeneratorTests.Source.MyStruct2, global::ProxyInterfaceS
public static object CreateProxy(Type type, object toWrap) => s_proxyFactory[type](toWrap);
public static T CreateProxy<T>(object toWrap) => (T)CreateProxy(typeof(T), toWrap);
}
public static class MapsterAdapter
{
public static TDestination? AdaptNull<TDestination>(object? source)
{
if (source is null)
{
return default;
}
return Mapster.TypeAdapter.Adapt<TDestination>(source);
}
}
#nullable restore
}
}
@@ -73,6 +73,40 @@ public class ProxyInterfaceSourceGeneratorTest
return Verify(results);
}
[Fact]
public Task GenerateFiles_ForClassWithArray_Should_GenerateCorrectFiles_NotNullable()
{
// Arrange
var fileNames = new[]
{
"ProxyInterfaceSourceGeneratorTests.Source.IFooNotNullable.g.cs",
"ProxyInterfaceSourceGeneratorTests.Source.FooNotNullableProxy.g.cs"
};
var path = "./Source/IFooNotNullable.cs";
var sourceFile = new SourceFile
{
Path = path,
Text = File.ReadAllText(path),
AttributeToAddToInterface = new ExtraAttribute
{
Name = "Speckle.ProxyGenerator.Proxy",
ArgumentList = "typeof(ProxyInterfaceSourceGeneratorTests.Source.FooNotNullable)"
}
};
// Act
var result = _sut.Execute(new[] { sourceFile });
// Assert
result.Valid.Should().BeTrue();
result.Files.Should().HaveCount(fileNames.Length + 1);
// Verify
var results = result.GeneratorDriver.GetRunResult().Results.First().GeneratedSources;
return Verify(results);
}
[Fact]
public Task GenerateFiles_ForClassWithArray_Should_GenerateCorrectFiles()
{
@@ -0,0 +1,7 @@
namespace ProxyInterfaceSourceGeneratorTests.Source;
public enum FooEnumImplement
{
X,
Y
}
@@ -0,0 +1,14 @@
namespace ProxyInterfaceSourceGeneratorTests.Source;
#nullable disable
public class FooNotNullable
{
public void Test(FooEnum z = FooEnum.Y, IEnumerable<string> v = null) { }
}
public enum FooEnum
{
X,
Y
}
#nullable restore
@@ -0,0 +1,6 @@
namespace ProxyInterfaceSourceGeneratorTests.Source;
public partial interface IFooNotNullable
{
public void Test(FooEnumImplement z = FooEnumImplement.Y, IEnumerable<string>? v = null) { }
}