diff --git a/Source/Mockolate.SourceGenerators/Entities/Class.cs b/Source/Mockolate.SourceGenerators/Entities/Class.cs index 62f59ece..614710f7 100644 --- a/Source/Mockolate.SourceGenerators/Entities/Class.cs +++ b/Source/Mockolate.SourceGenerators/Entities/Class.cs @@ -48,9 +48,9 @@ public Class(ITypeSymbol type, List events = ToListExcept(type.GetMembers().OfType() .Where(x => !x.IsSealed) .Where(x => IsInterface || x.IsVirtual || x.IsAbstract) - .Select(x => (x, (x.Type as INamedTypeSymbol)?.DelegateInvokeMethod)) - .Where(x => x.DelegateInvokeMethod is not null) - .Select(x => new Event(x.x, x.DelegateInvokeMethod!, alreadyDefinedEvents)) + .Select(x => (x, x.Type as INamedTypeSymbol)) + .Where(x => x.Item2?.DelegateInvokeMethod is not null) + .Select(x => new Event(x.x, x.Item2!.DelegateInvokeMethod!, alreadyDefinedEvents)) .Distinct(), exceptEvents, Event.ContainingTypeIndependentEqualityComparer); Events = new EquatableArray(events.ToArray()); @@ -66,9 +66,9 @@ public Class(ITypeSymbol type, .ToList(); exceptEvents ??= type.GetMembers().OfType() .Where(x => x.IsSealed) - .Select(x => (x, (x.Type as INamedTypeSymbol)?.DelegateInvokeMethod)) - .Where(x => x.DelegateInvokeMethod is not null) - .Select(x => new Event(x.x, x.DelegateInvokeMethod!, null)) + .Select(x => (x, x.Type as INamedTypeSymbol)) + .Where(x => x.Item2?.DelegateInvokeMethod is not null) + .Select(x => new Event(x.x, x.Item2!.DelegateInvokeMethod!, null)) .Distinct() .ToList(); @@ -146,7 +146,6 @@ private static bool TryExtractSpecialName(INamedTypeSymbol namedType, [NotNullWh { (specialName, bool hasSpecialType) = namedType.SpecialType switch { - SpecialType.System_Void => ("void", true), SpecialType.System_Object => ("object", true), SpecialType.System_Boolean => ("bool", true), SpecialType.System_String => ("string", true), diff --git a/Source/Mockolate.SourceGenerators/Entities/GenericParameter.cs b/Source/Mockolate.SourceGenerators/Entities/GenericParameter.cs index bdaea145..16f67d46 100644 --- a/Source/Mockolate.SourceGenerators/Entities/GenericParameter.cs +++ b/Source/Mockolate.SourceGenerators/Entities/GenericParameter.cs @@ -9,10 +9,10 @@ internal readonly record struct GenericParameter public GenericParameter(ITypeParameterSymbol typeSymbol) { Name = typeSymbol.Name; + IsUnmanaged = typeSymbol.HasUnmanagedTypeConstraint; IsClass = typeSymbol.HasReferenceTypeConstraint; IsStruct = typeSymbol.HasValueTypeConstraint; IsNotNull = typeSymbol.HasNotNullConstraint; - IsUnmanaged = typeSymbol.HasUnmanagedTypeConstraint; HasConstructor = typeSymbol.HasConstructorConstraint; AllowsRefStruct = typeSymbol.AllowsRefLikeType; NullableAnnotation = typeSymbol.ReferenceTypeConstraintNullableAnnotation; @@ -43,23 +43,20 @@ public void AppendWhereConstraint(StringBuilder sb, string prefix) int count = 0; sb.AppendLine().Append(prefix).Append("where ").Append(Name).Append(" : "); - if (IsStruct) - { - if (count++ > 0) - { - sb.Append(", "); - } + if (IsUnmanaged) + { + count++; + sb.Append("unmanaged"); + } + else if (IsStruct) + { + count++; sb.Append("struct"); } - - if (IsClass) + else if (IsClass) { - if (count++ > 0) - { - sb.Append(", "); - } - + count++; sb.Append("class"); if (NullableAnnotation == NullableAnnotation.Annotated) { @@ -77,16 +74,6 @@ public void AppendWhereConstraint(StringBuilder sb, string prefix) sb.Append("notnull"); } - if (IsUnmanaged) - { - if (count++ > 0) - { - sb.Append(", "); - } - - sb.Append("unmanaged"); - } - if (AllowsRefStruct) { if (count++ > 0) @@ -105,10 +92,6 @@ public void AppendWhereConstraint(StringBuilder sb, string prefix) } sb.Append(constraintType.Fullname); - if (NullableAnnotation == NullableAnnotation.Annotated) - { - sb.Append('?'); - } } if (HasConstructor) diff --git a/Source/Mockolate.SourceGenerators/Entities/Type.cs b/Source/Mockolate.SourceGenerators/Entities/Type.cs index dc59f08d..13fb6019 100644 --- a/Source/Mockolate.SourceGenerators/Entities/Type.cs +++ b/Source/Mockolate.SourceGenerators/Entities/Type.cs @@ -15,13 +15,6 @@ internal Type(ITypeSymbol typeSymbol) // Removes '*' from multi-dimensional array types Fullname = typeSymbol.ToDisplayString().Replace("*", ""); Namespace = typeSymbol.ContainingNamespace?.ToString(); - IsArray = typeSymbol.TypeKind == TypeKind.Array; - if (typeSymbol is IArrayTypeSymbol arrayType) - { - ElementType = new Type(arrayType.ElementType); - } - - IsTypeParameter = typeSymbol.TypeKind == TypeKind.TypeParameter; if (typeSymbol is INamedTypeSymbol namedTypeSymbol) { if (typeSymbol.IsTupleType) @@ -40,9 +33,6 @@ internal Type(ITypeSymbol typeSymbol) } public SpecialGenericType SpecialGenericType { get; } - - public bool IsArray { get; } - public bool IsTypeParameter { get; } public EquatableArray? TupleTypes { get; } public EquatableArray? GenericTypeParameters { get; } public string? Namespace { get; } @@ -50,7 +40,5 @@ internal Type(ITypeSymbol typeSymbol) internal static Type Void { get; } = new("void"); public string Fullname { get; } - public Type? ElementType { get; } - public override string ToString() => Fullname; } diff --git a/Tests/Mockolate.SourceGenerators.Tests/GeneralTests.cs b/Tests/Mockolate.SourceGenerators.Tests/GeneralTests.cs index 61b08089..db62bbcb 100644 --- a/Tests/Mockolate.SourceGenerators.Tests/GeneralTests.cs +++ b/Tests/Mockolate.SourceGenerators.Tests/GeneralTests.cs @@ -95,6 +95,65 @@ await That(result.Sources) .ContainsKey("MockForIMyInterface3.g.cs"); } + [Fact] + public async Task MockOfGenericTypes_ShouldHandleSpecialGenericParameters() + { + GeneratorResult result = Generator + .Run(""" + using System; + using System.Collections.Generic; + using Mockolate; + + namespace MyCode + { + public class Program + { + public static void Main(string[] args) + { + _ = Mockolate.Mock.Create(); + } + } + + public interface IMyInterface + { + IEnumerable EnumerableOfObject { get; } + IEnumerable EnumerableOfBool { get; } + IEnumerable EnumerableOfString { get; } + IEnumerable EnumerableOfChar { get; } + IEnumerable EnumerableOfByte { get; } + IEnumerable EnumerableOfSbyte { get; } + IEnumerable EnumerableOfShort { get; } + IEnumerable EnumerableOfUshort { get; } + IEnumerable EnumerableOfInt { get; } + IEnumerable EnumerableOfUint { get; } + IEnumerable EnumerableOfLong { get; } + IEnumerable EnumerableOfUlong { get; } + IEnumerable EnumerableOfFloat { get; } + IEnumerable EnumerableOfDouble { get; } + IEnumerable EnumerableOfDecimal { get; } + } + } + + """, typeof(IEnumerable<>)); + + await That(result.Sources).ContainsKey("MockForIMyInterface.g.cs").WhoseValue + .Contains("public System.Collections.Generic.IEnumerable EnumerableOfObject").And + .Contains("public System.Collections.Generic.IEnumerable EnumerableOfBool").And + .Contains("public System.Collections.Generic.IEnumerable EnumerableOfString").And + .Contains("public System.Collections.Generic.IEnumerable EnumerableOfChar").And + .Contains("public System.Collections.Generic.IEnumerable EnumerableOfByte").And + .Contains("public System.Collections.Generic.IEnumerable EnumerableOfSbyte").And + .Contains("public System.Collections.Generic.IEnumerable EnumerableOfShort").And + .Contains("public System.Collections.Generic.IEnumerable EnumerableOfUshort").And + .Contains("public System.Collections.Generic.IEnumerable EnumerableOfInt").And + .Contains("public System.Collections.Generic.IEnumerable EnumerableOfUint").And + .Contains("public System.Collections.Generic.IEnumerable EnumerableOfLong").And + .Contains("public System.Collections.Generic.IEnumerable EnumerableOfUlong").And + .Contains("public System.Collections.Generic.IEnumerable EnumerableOfFloat").And + .Contains("public System.Collections.Generic.IEnumerable EnumerableOfDouble").And + .Contains("public System.Collections.Generic.IEnumerable EnumerableOfDecimal"); + } + [Fact] public async Task MockOfHttpMessageHandler_ShouldContainGetEnumeratorFromIEnumerableAndIEnumerableOfT() { @@ -405,7 +464,7 @@ public interface IMyService string SomeProperty { get; set; } [Localizable(false)] string MyMethod(string message); - [CustomAttribute( + [CustomAttributeWithWrongName( true, (byte)42, 'X', @@ -422,6 +481,7 @@ public interface IMyService typeof(string), MyEnum.Value2, new int[] { 1, 2, 3 }, + null, BoolParam = false, ByteParam = (byte)99, CharParam = 'Y', @@ -438,7 +498,8 @@ public interface IMyService ObjectParam = 42, TypeParam = typeof(int), EnumParam = MyFlagEnum.Value1 | MyFlagEnum.Value2, - ArrayParam = new string[] { "a", "b" } + ArrayParam = new string[] { "a", "b" }, + OptionalIntParam = null )] event EventHandler MyEvent; } @@ -457,7 +518,7 @@ public enum MyFlagEnum } [AttributeUsage(AttributeTargets.All)] - public class CustomAttribute : Attribute + public class CustomAttributeWithWrongName : Attribute { public CustomAttribute( bool boolArg, @@ -475,7 +536,8 @@ public CustomAttribute( ushort ushortArg, Type typeArg, MyEnum enumArg, - int[] arrayArg) + int[] arrayArg, + int? optionalIntArg) { } @@ -496,6 +558,7 @@ public CustomAttribute( public Type TypeParam { get; set; } public MyFlagEnum EnumParam { get; set; } public string[] ArrayParam { get; set; } + public int? OptionalIntParam { get; set; } } """, typeof(AllowNullAttribute), typeof(IDataParameter), typeof(LocalizableAttribute), typeof(AttributeUsageAttribute)); @@ -541,7 +604,7 @@ public string MyMethod(string message) """).IgnoringNewlineStyle().And .Contains(""" /// - [MyCode.Custom(true, (byte)42, 'X', 3.14, 2.71F, 100, 999L, (sbyte)-10, (short)500, "test", 123u, 456uL, (ushort)789, typeof(string), (MyCode.MyEnum)2, new int[]{1, 2, 3}, BoolParam = false, ByteParam = (byte)99, CharParam = 'Y', DoubleParam = 1.23, FloatParam = 4.56F, IntParam = 200, LongParam = 888L, SByteParam = (sbyte)-5, ShortParam = (short)300, StringParam = "named", UIntParam = 111u, ULongParam = 222uL, UShortParam = (ushort)333, ObjectParam = 42, TypeParam = typeof(int), EnumParam = (MyCode.MyFlagEnum)3, ArrayParam = new string[]{"a", "b"})] + [MyCode.CustomAttributeWithWrongName(true, (byte)42, 'X', 3.14, 2.71F, 100, 999L, (sbyte)-10, (short)500, "test", 123u, 456uL, (ushort)789, typeof(string), (MyCode.MyEnum)2, new int[]{1, 2, 3}, null, BoolParam = false, ByteParam = (byte)99, CharParam = 'Y', DoubleParam = 1.23, FloatParam = 4.56F, IntParam = 200, LongParam = 888L, SByteParam = (sbyte)-5, ShortParam = (short)300, StringParam = "named", UIntParam = 111u, ULongParam = 222uL, UShortParam = (ushort)333, ObjectParam = 42, TypeParam = typeof(int), EnumParam = (MyCode.MyFlagEnum)3, ArrayParam = new string[]{"a", "b"}, OptionalIntParam = null)] public event System.EventHandler? MyEvent { add diff --git a/Tests/Mockolate.SourceGenerators.Tests/Sources/ForMockTests.DelegateTests.cs b/Tests/Mockolate.SourceGenerators.Tests/Sources/ForMockTests.DelegateTests.cs index b2d7b00d..0b58e2e8 100644 --- a/Tests/Mockolate.SourceGenerators.Tests/Sources/ForMockTests.DelegateTests.cs +++ b/Tests/Mockolate.SourceGenerators.Tests/Sources/ForMockTests.DelegateTests.cs @@ -76,6 +76,53 @@ public IVoidMethodSetup Delegate(IParameter? x, IRefParamete """).IgnoringNewlineStyle(); } + [Fact] + public async Task CustomDelegates_ShouldSupportSpanAndReadOnlySpanParameters() + { + GeneratorResult result = Generator + .Run(""" + using System; + using Mockolate; + + namespace MyCode; + + public class Program + { + public static void Main(string[] args) + { + _ = Mock.Create(); + _ = Mock.Create(); + } + + public delegate Span DoSomething1(int x); + public delegate ReadOnlySpan DoSomething2(int x); + } + """); + + await That(result.Sources) + .ContainsKey("MockForProgramDoSomething1.g.cs").WhoseValue + .Contains(""" + public MyCode.Program.DoSomething1 Object => new(Invoke); + private System.Span Invoke(int x) + { + var result = _mock.Registrations.InvokeMethod>("MyCode.Program.DoSomething1.Invoke", p => _mock.Registrations.Behavior.DefaultValue.Generate(default(SpanWrapper)!, () => _mock.Registrations.Behavior.DefaultValue.Generate(default(char)!), p), x); + result.TriggerCallbacks(x); + return result.Result; + } + """).IgnoringNewlineStyle(); + await That(result.Sources) + .ContainsKey("MockForProgramDoSomething2.g.cs").WhoseValue + .Contains(""" + public MyCode.Program.DoSomething2 Object => new(Invoke); + private System.ReadOnlySpan Invoke(int x) + { + var result = _mock.Registrations.InvokeMethod>("MyCode.Program.DoSomething2.Invoke", p => _mock.Registrations.Behavior.DefaultValue.Generate(default(ReadOnlySpanWrapper)!, () => _mock.Registrations.Behavior.DefaultValue.Generate(default(char)!), p), x); + result.TriggerCallbacks(x); + return result.Result; + } + """).IgnoringNewlineStyle(); + } + [Fact] public async Task Delegates_ShouldCreateDelegateInsteadOfMockSubject() { diff --git a/Tests/Mockolate.SourceGenerators.Tests/Sources/ForMockTests.ImplementClassTests.cs b/Tests/Mockolate.SourceGenerators.Tests/Sources/ForMockTests.ImplementClassTests.cs index 5aa19b1d..45e75574 100644 --- a/Tests/Mockolate.SourceGenerators.Tests/Sources/ForMockTests.ImplementClassTests.cs +++ b/Tests/Mockolate.SourceGenerators.Tests/Sources/ForMockTests.ImplementClassTests.cs @@ -625,12 +625,15 @@ await That(result.Sources).ContainsKey("MockForIMyService.g.cs").WhoseValue [InlineData("class, T")] [InlineData("struct")] [InlineData("class")] + [InlineData("class, notnull")] [InlineData("notnull")] + [InlineData("unmanaged")] [InlineData("class?")] [InlineData("MyCode.IMyInterface")] [InlineData("new()")] [InlineData("MyCode.IMyInterface?")] [InlineData("allows ref struct")] + [InlineData("class, allows ref struct")] [InlineData("MyCode.IMyInterface, new()")] public async Task Methods_Generic_ShouldApplyAllConstraints(string constraint) {