From 52d6147d5078326d7496879d09f152f5327170db Mon Sep 17 00:00:00 2001 From: nth-commit Date: Tue, 29 Nov 2022 20:00:22 +1300 Subject: [PATCH] =?UTF-8?q?=F0=9F=8C=8D=20#371=20Support=20structs!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AboutGeneratingStructs.cs | 109 ++++++++++++++++++ .../AboutOverridingMembers.cs | 8 -- .../RendererTests/AboutUnaryExamples.cs | 10 ++ .../DefaultConstructorReflectedGenHandler.cs | 36 ++++-- 4 files changed, 144 insertions(+), 19 deletions(-) create mode 100644 src/GalaxyCheck.Tests/GenTests/ReflectedGenTests/AboutGeneratingStructs.cs diff --git a/src/GalaxyCheck.Tests/GenTests/ReflectedGenTests/AboutGeneratingStructs.cs b/src/GalaxyCheck.Tests/GenTests/ReflectedGenTests/AboutGeneratingStructs.cs new file mode 100644 index 00000000..132909d3 --- /dev/null +++ b/src/GalaxyCheck.Tests/GenTests/ReflectedGenTests/AboutGeneratingStructs.cs @@ -0,0 +1,109 @@ +using FluentAssertions; +using GalaxyCheck; +using NebulaCheck; +using System.Linq; +using Property = NebulaCheck.Property; +using Test = NebulaCheck.Test; + +namespace Tests.V2.GenTests.ReflectedGenTests +{ + public class AboutGeneratingStructs + { + private struct EmptyStruct + { + } + + [Property] + public NebulaCheck.IGen ItCanGenerateAnEmptyStruct() => + from value in DomainGen.Any() + from seed in DomainGen.Seed() + from size in DomainGen.Size() + select Property.ForThese(() => + { + var gen = GalaxyCheck.Gen.Create(); + + var instance = gen.SampleOne(seed: seed, size: size); + + instance.Should().NotBeNull(); + }); + + private struct StructWithOneProperty + { + public int Property { get; set; } + + } + + [Property] + public NebulaCheck.IGen ItCanGenerateAStructWithOneProperty() => + from seed in DomainGen.Seed() + from size in DomainGen.Size() + select Property.ForThese(() => + { + var gen = GalaxyCheck.Gen + .Factory() + .RegisterType(GalaxyCheck.Gen.Int32().Where(x => x != 0)) + .Create(); + + var instance = gen.SampleOne(seed: seed, size: size); + + instance.Should().NotBeNull(); + instance.Property.Should().NotBe(0); + }); + + private struct StructWithTwoProperties + { + public int Property1 { get; set; } + public int Property2 { get; set; } + } + + [Property] + public NebulaCheck.IGen ItCanGenerateAStructWithTwoProperties() => + from seed in DomainGen.Seed() + from size in DomainGen.Size() + select Property.ForThese(() => + { + var gen = GalaxyCheck.Gen + .Factory() + .RegisterType(GalaxyCheck.Gen.Int32().Where(x => x != 0)) + .Create(); + + var instance = gen.SampleOne(seed: 0); + + instance.Should().NotBeNull(); + instance.Property1.Should().NotBe(0); + instance.Property2.Should().NotBe(0); + }); + + + private struct StructWithOneConstructorArgument + { + public StructWithOneConstructorArgument(int property) + { + Property = property; + UsedConstructor = true; + } + + public int Property { get; set; } + + public bool UsedConstructor { get; set; } + } + + [Property] + public NebulaCheck.IGen ItCanGenerateAStructWithOneConstructorArgument() => + from seed in DomainGen.Seed() + from size in DomainGen.Size() + select Property.ForThese(() => + { + var gen = GalaxyCheck.Gen + .Factory() + .RegisterType(GalaxyCheck.Gen.Int32().Where(x => x != 0)) + .Create(); + + var instance = gen.SampleOne(seed: seed, size: size); + + instance.Should().NotBeNull(); + instance.Property.Should().NotBe(0); + instance.UsedConstructor.Should().BeTrue(); + }); + } +} diff --git a/src/GalaxyCheck.Tests/GenTests/ReflectedGenTests/AboutOverridingMembers.cs b/src/GalaxyCheck.Tests/GenTests/ReflectedGenTests/AboutOverridingMembers.cs index 8d7398ce..e09a0864 100644 --- a/src/GalaxyCheck.Tests/GenTests/ReflectedGenTests/AboutOverridingMembers.cs +++ b/src/GalaxyCheck.Tests/GenTests/ReflectedGenTests/AboutOverridingMembers.cs @@ -14,15 +14,7 @@ public class AboutOverridingMembers { private record RecordWithOneProperty(object Property); - private class ClassWithOneConstructorArgument - { - public object Property { get; } - public ClassWithOneConstructorArgument(object property) - { - Property = property; - } - } [Property] public NebulaCheck.IGen ItCanOverrideAnExternalInitProperty() => diff --git a/src/GalaxyCheck.Tests/RendererTests/AboutUnaryExamples.cs b/src/GalaxyCheck.Tests/RendererTests/AboutUnaryExamples.cs index a871e687..06b939ad 100644 --- a/src/GalaxyCheck.Tests/RendererTests/AboutUnaryExamples.cs +++ b/src/GalaxyCheck.Tests/RendererTests/AboutUnaryExamples.cs @@ -96,6 +96,7 @@ public void TupleExamples(object value, string expectedRendering) { new Action(() => { }), "System.Action" }, { Operations.Create, "Create" }, { new FaultyRecord(), "" }, + { new Struct { A = 1, B = 2, C = 3 }, "{ A = 1, B = 2, C = 3 }" } }; [Theory] @@ -142,5 +143,14 @@ public enum Operations Create, Read } + + public struct Struct + { + public int A { get; set; } + + public int B { get; set; } + + public int C { get; set; } + } } } diff --git a/src/GalaxyCheck/Gens/ReflectedGenHelpers/ReflectedGenHandlers/DefaultConstructorReflectedGenHandler.cs b/src/GalaxyCheck/Gens/ReflectedGenHelpers/ReflectedGenHandlers/DefaultConstructorReflectedGenHandler.cs index 7cd01d71..9ad5506a 100644 --- a/src/GalaxyCheck/Gens/ReflectedGenHelpers/ReflectedGenHandlers/DefaultConstructorReflectedGenHandler.cs +++ b/src/GalaxyCheck/Gens/ReflectedGenHelpers/ReflectedGenHandlers/DefaultConstructorReflectedGenHandler.cs @@ -14,8 +14,20 @@ public DefaultConstructorReflectedGenHandler(ContextualErrorFactory errorFactory _errorFactory = errorFactory; } - public bool CanHandleGen(Type type, ReflectedGenHandlerContext context) => - type.GetConstructors().Any(constructor => constructor.GetParameters().Any() == false); + public bool CanHandleGen(Type type, ReflectedGenHandlerContext context) + { + if (IsStruct(type)) + { + // Structs have slightly different constructor semantics. + return type.GetConstructors().Any() == false; + } + else + { + return type.GetConstructors().Any(constructor => constructor.GetParameters().Any() == false); + } + } + + private static bool IsStruct(Type type) => type.IsValueType && !type.IsPrimitive && !type.IsEnum; public IGen CreateGen(IReflectedGenHandler innerHandler, Type type, ReflectedGenHandlerContext context) { @@ -36,10 +48,10 @@ private static IGen CreateGenGeneric(IReflectedGenHandler innerHandler, Re Gen.Zip(CreateSetFieldActionGens(innerHandler, typeof(T), context))) .SelectMany((x) => { - T instance; + object instance; try { - instance = (T)Activator.CreateInstance(typeof(T))!; + instance = Activator.CreateInstance(typeof(T))!; } catch (TargetInvocationException ex) { @@ -52,7 +64,7 @@ private static IGen CreateGenGeneric(IReflectedGenHandler innerHandler, Re { try { - setPropertyAction(instance); + setPropertyAction(ref instance); } catch (TargetInvocationException ex) { @@ -64,14 +76,16 @@ private static IGen CreateGenGeneric(IReflectedGenHandler innerHandler, Re foreach (var setFieldAction in x.Item2) { - setFieldAction(instance); + setFieldAction(ref instance); } - return Gen.Constant(instance); + return Gen.Constant((T)instance); }); } + + private delegate void SetMemberAction(ref object instance); - private static IEnumerable>> CreateSetPropertyActionGens(IReflectedGenHandler innerHandler, Type type, ReflectedGenHandlerContext parentContext) + private static IEnumerable> CreateSetPropertyActionGens(IReflectedGenHandler innerHandler, Type type, ReflectedGenHandlerContext parentContext) { var nullabilityInfoContext = new NullabilityInfoContext(); return type @@ -84,11 +98,11 @@ private static IEnumerable>> CreateSetPropertyActionGens(IRe .CreateGen(property.PropertyType, context) .Cast() .Advanced.ReferenceRngWaypoint(rngWaypoint => rngWaypoint.Influence(context.CalculateStableSeed())) - .Select((Func>)(value => obj => property.SetValue(obj, value))); + .Select((Func)(value => (ref object obj) => property.SetValue(obj, value))); }); } - private static IEnumerable>> CreateSetFieldActionGens(IReflectedGenHandler innerHandler, Type type, ReflectedGenHandlerContext parentContext) + private static IEnumerable> CreateSetFieldActionGens(IReflectedGenHandler innerHandler, Type type, ReflectedGenHandlerContext parentContext) { var nullabilityInfoContext = new NullabilityInfoContext(); return type @@ -101,7 +115,7 @@ private static IEnumerable>> CreateSetFieldActionGens(IRefle .CreateGen(field.FieldType, context) .Cast() .Advanced.ReferenceRngWaypoint(rngWaypoint => rngWaypoint.Influence(context.CalculateStableSeed())) - .Select((Func>)(value => obj => field.SetValue(obj, value))); + .Select((Func)(value => (ref object obj) => field.SetValue(obj, value))); }); } }