using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Unity.Burst; using Unity.Collections.LowLevel.Unsafe; using Unity.Jobs; using Unity.Mathematics; namespace Burst.Compiler.IL.Tests { // TODO: We should add a lot more tests for generics // - instance methods with generics // - instance methods with generics and outer generics from declaring type // - check generic name collisions // - ...etc. internal partial class TestGenerics { [TestCompiler] public static int StructNestedGenerics() { var value = new GenericStruct>(); return value.FieldMixed1.Field4.FieldMixed1.Field4; } private unsafe struct DataOutput2 where TType : struct { #pragma warning disable 0649 internal TType m_Value; #pragma warning restore 0649 } [TestCompiler] public static int CheckSizeOfWithGenerics() { return UnsafeUtility.SizeOf>() + UnsafeUtility.SizeOf>>>() * 10; } [TestCompiler] public static float TestOuterInnerGenerics() { var yoyo = new GenericStructOuter.GenericStructInner { Field1 = { Value = 1.0f }, Field2 = { Value = 11.0f } }; Executor.GenericStructInner>.Execute(ref yoyo); return yoyo.Result; } [TestCompiler] public static float TestOuterInnerGenerics2() { #pragma warning disable 0649 var yoyo = new GenericStructOuter2.GenericStructInner { Field1 = { Value = 1.0f }, Field2 = { Value = 11.0f }, Field3 = { Value = 106.0f } }; #pragma warning restore 0649 Executor.GenericStructInner>.Execute(ref yoyo); return yoyo.Result; } [TestCompiler] public static float TestActivator() { var yoyo = new MyActivator(); var result = yoyo.Create(1.0f); return result.Value; } [TestCompiler] public static float TestActivatorIndirect() { var yoyo = new MyActivatorIndirect(); var result = yoyo.Create(1.0f); return result.Value; } [TestCompiler] public static float TestStaticMethodGeneric() { var v1 = new MyValueData1() { Value = 10.0f }; var v2 = new MyValueData2() { Value = 100.0f }; var result = ComputeData(v1, v2); return result.Value; } public interface IMyActivator where T : IMyData, new() { T Create(float value); } public struct MyActivator : IMyActivator where T : IMyData, new() { public T Create(float value) { var data = new T { Value = value + 2.0f }; return data; } } public struct MyActivatorIndirect : IMyActivator where T : IMyData, new() { public T Create(float value) { return CreateActivator(value); } } public interface IMyData { float Value { get; set; } } public struct MyValueData1 : IMyData { public float Value { get; set; } } public struct MyValueData2 : IMyData { public float Value { get; set; } } private struct GenericStructOuter where T1 : IMyData where T2 : IMyData { public struct GenericStructInner : IJob { #pragma warning disable 0649 public T1 Field1; public T2 Field2; #pragma warning restore 0649 public float Result; public void Execute() { Result = Field1.Value + Field2.Value; } } } private struct GenericStructOuter2 where T1 : IMyData where T2 : IMyData { public struct GenericStructInner : IJob where T3 : IMyData { #pragma warning disable 0649 public T1 Field1; public T2 Field2; public T3 Field3; public float Result; #pragma warning restore 0649 public void Execute() { Result = Field1.Value + Field2.Value + Field3.Value; } } } private struct Executor where T : IJob { public static void Execute(ref T job) { job.Execute(); } } private struct GenericStruct { #pragma warning disable 0649 public GenericSubStruct FieldMixed1; public GenericSubStruct FieldMixed2; #pragma warning restore 0649 } private struct GenericSubStruct { #pragma warning disable 0649 public T3 Field3; public T4 Field4; #pragma warning restore 0649 } public interface IRotation { float Value { get; set; } } public struct SimpleRotation : IRotation { public float Value { get; set; } } public struct SimpleRotation2 : IRotation { public float Value { get; set; } } private static TNew CreateActivator(float value) where TNew : IMyData, new() { var data = new TNew { Value = value + 5.0f }; return data; } private static TResult ComputeData(TLeft left, TRight right) where TLeft : IMyData where TRight : IMyData where TResult : IMyData, new() { var result = new TResult(); result.Value = 5.0f; result.Value += left.Value; result.Value += right.Value; return result; } [TestCompiler] public static void TestCrossConstraints() { var job = new ReproBurstError(); job.Execute(); } struct ReproBurstError : IJob { #pragma warning disable 0649 public FirstLevel, int> first; public SecondLevel second; #pragma warning restore 0649 public void Execute() { first.First(second, 0); } } [StructLayout(LayoutKind.Sequential, Size = 1)] struct FirstLevel where T1 : struct, ISecondLevel { public void First(T1 t1, T2 t2) { t1.Second(t2); } } interface ISecondLevel { void Second(T2 x); } [StructLayout(LayoutKind.Sequential, Size = 1)] struct SecondLevel : ISecondLevel { public void Second(T x) { } } [TestCompiler] public static float TestCrossAndGenericArgumentsInGenericInterface() { var value = new CaseMixedGenerics.Check(); return value.Execute(); } public struct CaseMixedGenerics where T1 : IRotation { public interface MyInterface where T2 : IRotation { // Here we have a test with generics coming from interface but also coming from parameters // through an interface method call float MyMethod(T2 t2, T value) where T : IRotation; } public struct Check where T3 : MyInterface where T4 : IRotation { #pragma warning disable 0649 private T3 t3Value; private T4 t4Value; #pragma warning restore 0649 public float Execute() { return t3Value.MyMethod(t4Value, t4Value); } public static float Run(T1 t1, Check t3t4) { return t1.Value + t3t4.Execute(); } } } [StructLayout(LayoutKind.Sequential, Size = 1)] public struct CaseMixedImplem : CaseMixedGenerics.MyInterface { public float MyMethod(SimpleRotation t2, T value) where T : IRotation { return t2.Value + value.Value; } } [TestCompiler] public static int TestCase_1059355() { var job = new ReproBurstError2(); job.Execute(); return job.Result; } [TestCompiler] public static void ExplicitInterfaceImplementation() { ExplicitRunner.RunJob(new ExplicitInterfaceStruct()); } struct ReproBurstError2 : IJob { #pragma warning disable 0649 Simplified.Foo> solver; public int Result; #pragma warning restore 0649 public void Execute() { Result = solver.Run(default(BugRepro.Foo)); } } struct Variant { } struct BugRepro { public struct Foo : IFoo { public void Bug() { } } } interface IFoo { void Bug(); } [StructLayout(LayoutKind.Sequential, Size = 1)] struct Simplified where T : IFoo { public int Run(T foo) { foo.Bug(); foo.Bug(); return 1; } } struct ExplicitInterfaceStruct : IJob { void IJob.Execute() { } } struct ExplicitRunner { public static void RunJob(T job) where T : IJob { job.Execute(); } } // case devirtualizer not working for a Physics Job [TestCompiler] public static int ExecutePhysicsJob() { var job = new PhysicsJob(); job.Execute(0); return job.result ? 1 : 0; } public interface IQueryResult { float Fraction { get; set; } } // The output of ray cast queries public struct RayCastResult : IQueryResult { public float Fraction { get; set; } public float3 SurfaceNormal; public int RigidBodyIndex; } public interface ICollector where T : struct, IQueryResult { float MaxFraction { get; } bool HasHit { get; } int NumHits { get; } void AddHit(T hit); } public struct AnyHitCollector : ICollector where T : struct, IQueryResult { public float MaxFraction { get; private set; } public bool HasHit { get; private set; } public int NumHits { get { return HasHit ? 1 : 0; } } public void AddHit(T hit) { HasHit = true; } } public struct ClosestHitCollector : ICollector where T : struct, IQueryResult { public float MaxFraction { get { return ClosestHit.Fraction; } } public bool HasHit { get; private set; } public int NumHits { get { return HasHit ? 1 : 0; } } public T ClosestHit; public void AddHit(T hit) { ClosestHit = hit; HasHit = true; } } public interface IRaycastLeafProcessor { // Cast a ray against a leaf node of the bounding volume hierarchy. void RayLeaf(int leafData, ref T collector) where T : struct, ICollector; } static void castRay(int data, ref T collector) where T : struct, ICollector { RayCastResult result = new RayCastResult(); result.Fraction = 0.5f; collector.AddHit(result); } private struct RayLeafProcessor : IRaycastLeafProcessor { public void RayLeaf(int leafData, ref T collector) where T : struct, ICollector { castRay(leafData, ref collector); } } static void processLeaves(ref T processor, ref U collector) where T : struct, IRaycastLeafProcessor where U : struct, ICollector { for (int i = 0; i < 10; i++) { if (collector.MaxFraction > 0.5f) { processor.RayLeaf(i, ref collector); } } } static void castRayMesh(ref T collector) where T : struct, ICollector { RayLeafProcessor processor; processLeaves(ref processor, ref collector); } [BurstCompile] protected struct PhysicsJob : IJobParallelFor { public bool result; public unsafe void Execute(int index) { ClosestHitCollector collector = new ClosestHitCollector(); castRayMesh(ref collector); result = collector.HasHit; } } [TestCompiler] public static float TestGenericIssueWithIJobProcessComponentData() { var jobProcess = new JobStruct_Process_DD, Translation>(); jobProcess.DataU0.Value = 5.0f; jobProcess.DataU1.Value = 22.0f; JobStruct_Process_DD, Translation>.Execute(ref jobProcess); return jobProcess.DataU0.Value + jobProcess.DataU1.Value; } public interface IComponentData { } internal struct JobStruct_Process_DD where T : struct, IJobProcessComponentData where U0 : struct, IComponentData where U1 : struct, IComponentData { public T Data; public U0 DataU0; public U1 DataU1; public static unsafe void Execute(ref JobStruct_Process_DD jobData) { jobData.Data.Execute(ref jobData.DataU0, ref jobData.DataU1); } } public interface IJobProcessComponentData where U0 : struct, IComponentData where U1 : struct, IComponentData { void Execute(ref U0 c0, ref U1 c1); } public struct GenericComponent : IComponentData { public T Value; } public struct Translation : IComponentData { public float Value; } struct MyReadJob : IJobProcessComponentData, Translation> { public void Execute(ref GenericComponent c0, ref Translation c1) { c1.Value = c0.Value; } } public struct GenericTypeContainer where TType : struct { public TType Value; } [TestCompiler] public static int TestSizeOfWithGenericType() { return UnsafeUtility.SizeOf>(); } public class GenericContainerOuter where T : struct { public struct GenericContainerInner where TType : struct { public TType Value; public T Value2; } } [TestCompiler] public static int TestSizeOfWithNestedGenericTypes() { return UnsafeUtility.SizeOf.GenericContainerInner>(); } [TestCompiler] public static int CheckInterfaceCallsThroughGenericsOfGenerics() { var job = MyOuterStructWithGenerics.GetJob(); job.Value1.Component.Value = 1; job.Value1.Component.Value = 2; job.Execute(); return job.Result; } private interface IComponentDataOrdered { int Order { get; } } private struct EntityInChunkWithComponent where TComponent : struct, IComponentData { public TComponent Component; public EntityInChunkWithComponent(TComponent component) { Component = component; } } private struct EntityInChunkWithComponentComparer : IComparer> where TComponent : unmanaged, IComponentData, IComparable { public int Compare(EntityInChunkWithComponent x, EntityInChunkWithComponent y) { return x.Component.CompareTo(y.Component); } } private struct MyOuterStructWithGenerics where TComponent : unmanaged, IComponentData, IComparable { public struct InnerWithComparer : IJob where T : struct where TComparer : struct, IComparer { public T Value1; #pragma warning disable 0649 public T Value2; #pragma warning restore 0649 public int Result; public void Execute() { var comparer = new TComparer(); Result = comparer.Compare(Value1, Value2); } } public static InnerWithComparer, EntityInChunkWithComponentComparer> GetJob() { return new InnerWithComparer, EntityInChunkWithComponentComparer>(); } } private struct MyComponentData : IComponentData, IComparable { public int Value; public MyComponentData(int value) { Value = value; } public int CompareTo(MyComponentData other) { return Value.CompareTo(other.Value); } } [TestCompiler] public static long TestNestedGenericsWithStaticAndSameName() { return TypeIndexCache.GetValue(); } private class TypeIndexCache { public static long GetValue() { return InnerIndex.Create(); } } private struct InnerIndex { public static long Create() { var value = BurstRuntime.GetHashCode64(); value *= BurstRuntime.GetHashCode64(); return value; } } // Set this to no-inlining so the compiler can't fold the branch away with anything other than type deduction. [MethodImpl(MethodImplOptions.NoInlining)] private static int GenericResolutionBranchTrick() { if (default(T) is null) { return 42; } else { return 13; } } [TestCompiler] public static int TestGenericResolutionBranchTrickInt() { return GenericResolutionBranchTrick(); } private struct SomeStruct { } [TestCompiler] public static int TestGenericResolutionBranchTrickStruct() { return GenericResolutionBranchTrick(); } private class SomeClass { } [TestCompiler(ExpectCompilerException = true, ExpectedDiagnosticId = DiagnosticId.ERR_InstructionBoxNotSupported)] public static unsafe int TestGenericResolutionBranchTrickClass() { return GenericResolutionBranchTrick(); } // TODO: Burst does not yet resolve the correct method // [TestCompiler] public static int TestStructImplementingGenericInterfaceWithSourceOrderDependentResolution() { var value = new StructImplementingGenericInterfaceWithSourceOrderDependentResolution(); return CallStructImplementingGenericInterfaceWithSourceOrderDependentResolutionHelper(value, 0); } private static int CallStructImplementingGenericInterfaceWithSourceOrderDependentResolutionHelper(T value, U u) where T : IGenericInterfaceWithSourceOrderDependentResolution { return value.Foo(u); } private interface IGenericInterfaceWithSourceOrderDependentResolution { int Foo(int i); int Foo(T t); } private struct StructImplementingGenericInterfaceWithSourceOrderDependentResolution : IGenericInterfaceWithSourceOrderDependentResolution { #pragma warning disable CS0473 // Explicit interface implementation matches more than one interface member int IGenericInterfaceWithSourceOrderDependentResolution.Foo(int i) => 1; #pragma warning restore CS0473 // Explicit interface implementation matches more than one interface member public int Foo(int i) => 2; } [TestCompiler] public static int TestStructImplementingGenericInterfaceWithSourceOrderDependentResolution2() { var value = new StructImplementingGenericInterfaceWithSourceOrderDependentResolution2(); return CallStructImplementingGenericInterfaceWithSourceOrderDependentResolution2Helper(value, 0); } private static int CallStructImplementingGenericInterfaceWithSourceOrderDependentResolution2Helper(T value, U u) where T : IGenericInterfaceWithSourceOrderDependentResolution2 { return value.Foo(u); } private interface IGenericInterfaceWithSourceOrderDependentResolution2 { // Inverted order from IGenericInterfaceWithSourceOrderDependentResolution above int Foo(T t); int Foo(int i); } private struct StructImplementingGenericInterfaceWithSourceOrderDependentResolution2 : IGenericInterfaceWithSourceOrderDependentResolution2 { #pragma warning disable CS0473 // Explicit interface implementation matches more than one interface member int IGenericInterfaceWithSourceOrderDependentResolution2.Foo(int i) => 1; #pragma warning restore CS0473 // Explicit interface implementation matches more than one interface member public int Foo(int i) => 2; } [TestCompiler] #if BURST_TESTS_ONLY [TestHash] #endif public static int CallGenericStructImplementingGenericInterfaceWithOverloads() { var value = new GenericStructImplementingGenericInterfaceWithOverloads(); return CallGenericStructImplementingGenericInterfaceWithOverloadsHelper(value); } private static int CallGenericStructImplementingGenericInterfaceWithOverloadsHelper(T value) where T : IGenericInterfaceWithOverloads { return value.Foo(0u) + value.Foo(0); } private interface IGenericInterfaceWithOverloads { T Foo(uint u); T Foo(int i); } private struct GenericStructImplementingGenericInterfaceWithOverloads : IGenericInterfaceWithOverloads { public T UIntValue; public T IntValue; public T Foo(uint u) => UIntValue; public T Foo(int i) => IntValue; } [TestCompiler] #if BURST_TESTS_ONLY [TestHash] #endif public static int CallGenericStructImplementingGenericInterfaceWithOverloads2() { var value = new GenericStructImplementingGenericInterfaceWithOverloads { UIntValue = 42, IntValue = 43, }; CallGenericStructImplementingGenericInterfaceWithOverloadsHelper2(value, out int result1, out int result2); return result1 + result2; } private static void CallGenericStructImplementingGenericInterfaceWithOverloadsHelper2(T value, out U result1, out U result2) where T : IGenericInterfaceWithOverloads { result1 = value.Foo(0u); result2 = value.Foo(0); } [TestCompiler] #if BURST_TESTS_ONLY [TestHash] #endif public static int CallGenericStructImplementingGenericInterfaceWithOverloadsWrapper() { var value = new GenericStructImplementingGenericInterfaceWithOverloadsWrapper { UIntValue = new GenericStructImplementingGenericInterfaceWithOverloads { UIntValue = 42, IntValue = 43, }, IntValue = new GenericStructImplementingGenericInterfaceWithOverloads { UIntValue = 44, IntValue = 45, }, }; return CallGenericStructImplementingGenericInterfaceWithOverloadsHelperWrapper(value); } private static int CallGenericStructImplementingGenericInterfaceWithOverloadsHelperWrapper(T value) where T : IGenericInterfaceWithOverloadsWrapper { return value.Bar(0u).Foo(0u) + value.Bar(0u).Foo(0) + value.Bar(0).Foo(0u) + value.Bar(0).Foo(0); } [TestCompiler] #if BURST_TESTS_ONLY [TestHash] #endif public static int CallGenericStructImplementingGenericInterfaceWithOverloadsWrapper2() { var value = new GenericStructImplementingGenericInterfaceWithOverloadsWrapper(); CallGenericStructImplementingGenericInterfaceWithOverloadsHelperWrapper2( value, out int result1, out int result2, out int result3, out int result4); return result1 + result2 + result3 + result4; } private static void CallGenericStructImplementingGenericInterfaceWithOverloadsHelperWrapper2( T value, out U result1, out U result2, out U result3, out U result4) where T : IGenericInterfaceWithOverloadsWrapper { result1 = value.Bar(0u).Foo(0u); result2 = value.Bar(0u).Foo(0); result3 = value.Bar(0).Foo(0u); result4 = value.Bar(0).Foo(0); } private interface IGenericInterfaceWithOverloadsWrapper { GenericStructImplementingGenericInterfaceWithOverloads Bar(uint index); GenericStructImplementingGenericInterfaceWithOverloads Bar(int index); } private struct GenericStructImplementingGenericInterfaceWithOverloadsWrapper : IGenericInterfaceWithOverloadsWrapper { public GenericStructImplementingGenericInterfaceWithOverloads UIntValue; public GenericStructImplementingGenericInterfaceWithOverloads IntValue; public GenericStructImplementingGenericInterfaceWithOverloads Bar(uint index) => UIntValue; public GenericStructImplementingGenericInterfaceWithOverloads Bar(int index) => IntValue; } // TODO: Burst does not yet resolve the correct method // [TestCompiler] #if BURST_TESTS_ONLY [TestHash] #endif public static int CallStructImplementingGenericInterfaceWithMoreSpecificOverload() { var value = new StructImplementingGenericInterfaceWithMoreSpecificOverload(); return CallStructImplementingGenericInterfaceWithMoreSpecificOverloadHelper(value); } private static int CallStructImplementingGenericInterfaceWithMoreSpecificOverloadHelper(T value) where T : IGenericInterfaceWithMoreSpecificOverload { return value.Foo(0); } private interface IGenericInterfaceWithMoreSpecificOverload { int Foo(T t); int Foo(int i); } private struct StructImplementingGenericInterfaceWithMoreSpecificOverload : IGenericInterfaceWithMoreSpecificOverload { public int Foo(T t) => 1; public int Foo(int i) => 2; } [TestCompiler] #if BURST_TESTS_ONLY [TestHash] #endif public static int CallStructImplementingGenericInterfaceWithMoreSpecificOverload2() { var value = new StructImplementingGenericInterfaceWithMoreSpecificOverload2(); return CallStructImplementingGenericInterfaceWithMoreSpecificOverload2Helper(value); } private static int CallStructImplementingGenericInterfaceWithMoreSpecificOverload2Helper(T value) where T : IGenericInterfaceWithMoreSpecificOverload { return value.Foo(0); } private struct StructImplementingGenericInterfaceWithMoreSpecificOverload2 : IGenericInterfaceWithMoreSpecificOverload { public int Foo(int i) => 1; } [TestCompiler] public static int CallGenericStructImplementingGenericInterfaceWithPrivateOverload() { var value = new GenericStructImplementingGenericInterfaceWithPrivateOverload(); return CallGenericStructImplementingGenericInterfaceWithPrivateOverloadHelper(value); } private interface IGenericInterface { T Get(int idx); } private static int CallGenericStructImplementingGenericInterfaceWithPrivateOverloadHelper(T value) where T : IGenericInterface { return value.Get(0); } private struct GenericStructImplementingGenericInterfaceWithPrivateOverload : IGenericInterface { private int Get(T idx) => 42; public T Get(int idx) => default; } [TestCompiler] public static int CallGenericStructImplementingGenericInterfaceDerived() { var value = new GenericStructImplementingGenericInterfaceDerived(); return CallGenericStructImplementingGenericInterfaceDerivedHelper(value); } private static int CallGenericStructImplementingGenericInterfaceDerivedHelper(T value) where T : IGenericInterfaceDerived { return value.Foo(0); } private interface IGenericInterfaceBase { int Foo(T t); int Foo(double d); } private interface IGenericInterfaceDerived : IGenericInterfaceBase { int Foo(U u); } private struct GenericStructImplementingGenericInterfaceDerived : IGenericInterfaceDerived { public int Foo(T u) => 1; public int Foo(double d) => (int)d; } [TestCompiler] #if BURST_TESTS_ONLY [TestHash] #endif public static int CallBaseInterfaceMethodOnGenericStruct() { var value = new GenericStructImplementingGenericInterfaceDerived(); return CallBaseInterfaceMethodOnGenericStructHelper(value); } private static int CallBaseInterfaceMethodOnGenericStructHelper(T value) where T : IGenericInterfaceBase { return value.Foo(0); } // TODO: Burst does not yet resolve the correct method // [TestCompiler] public static int CallGenericStructImplementingGenericInterfaceDerived2() { var value = new GenericStructImplementingGenericInterfaceDerived2(); return CallGenericStructImplementingGenericInterfaceDerived2Helper, int>(value); } private static int CallGenericStructImplementingGenericInterfaceDerived2Helper(T value) where T : IGenericInterfaceDerived { return value.Foo(default); } private struct GenericStructImplementingGenericInterfaceDerived2 : IGenericInterfaceDerived { int IGenericInterfaceBase.Foo(T t) => 2; int IGenericInterfaceBase.Foo(double d) => (int)d; public int Foo(T u) => 1; } [TestCompiler] #if BURST_TESTS_ONLY [TestHash] #endif public static int CallGetHashCodeViaInterface() { return CallGetHashCodeViaInterfaceHelper(new CallGetHashCodeViaInterfaceStruct { Value = 42 }); } public static int CallGetHashCodeViaInterfaceHelper(T value) { return value.GetHashCode(); } public struct CallGetHashCodeViaInterfaceStruct { public int Value; public override int GetHashCode() => Value.GetHashCode(); public int GetHashCode(int x) => x; } [TestCompiler(ExpectCompilerException = true, ExpectedDiagnosticId = DiagnosticId.ERR_UnableToAccessManagedMethod)] #if BURST_TESTS_ONLY [TestHash] #endif public static int CallGetHashCodeViaInterface2() { return CallGetHashCodeViaInterfaceHelper(new CallGetHashCodeViaInterfaceStruct2 { Value = 42 }); } public struct CallGetHashCodeViaInterfaceStruct2 { public int Value; // This struct doesn't override GetHashCode, so a Burst compiler error is expected. // (but hashing should still succeed regardless). public int GetHashCode(int x) => x; public double GetHashCode(double d) => d; } [TestCompiler(ExpectCompilerException = true, ExpectedDiagnosticId = DiagnosticId.ERR_UnableToAccessManagedMethod)] #if BURST_TESTS_ONLY [TestHash] #endif public static int CallGetHashCodeViaInterface3() { return CallGetHashCodeViaInterfaceHelper(new CallGetHashCodeViaInterfaceStruct3 { Value = 42 }); } public struct CallGetHashCodeViaInterfaceStruct3 { public int Value; // This struct doesn't override GetHashCode and has no other methods with that name. } } }