diff --git a/engine/Sandbox.Reflection/TypeLibrary/TypeDescription.cs b/engine/Sandbox.Reflection/TypeLibrary/TypeDescription.cs index 8135da46e..5d4be4d6b 100644 --- a/engine/Sandbox.Reflection/TypeLibrary/TypeDescription.cs +++ b/engine/Sandbox.Reflection/TypeLibrary/TypeDescription.cs @@ -647,6 +647,31 @@ public Type MakeGenericType( Type[] inargs ) if ( genericParams.Length != typeArgs.Length ) return null; + + // Make a map from generic type parameters to type arguments + var substitutions = new Dictionary(); + for ( int i = 0; i < genericParams.Length; i++ ) + { + var param = genericParams[i]; + var arg = typeArgs[i]; + substitutions.Add( param, arg ); + } + + // Substitutes generic type parameters in a type definition with type arguments + Type Substitute( Type type ) + { + if ( !type.IsGenericType ) + return type; + var genericDefinition = type.GetGenericTypeDefinition(); + var genericParams = type.GenericTypeArguments; + for ( int i = 0; i < genericParams.Length; i++ ) + { + if ( substitutions.TryGetValue( genericParams[i], out var substitutedType ) ) + genericParams[i] = Substitute( substitutedType ); + } + return genericDefinition.MakeGenericType( genericParams ); + } + for ( int i = 0; i < genericParams.Length; i++ ) { var param = genericParams[i]; @@ -691,7 +716,7 @@ public Type MakeGenericType( Type[] inargs ) var constraints = param.GetGenericParameterConstraints(); foreach ( var constraint in constraints ) { - if ( !constraint.IsAssignableFrom( arg ) ) + if ( !Substitute( constraint ).IsAssignableFrom( arg ) ) return null; } } diff --git a/engine/Sandbox.Test.Unit/Reflection/TypeLibraryTest.cs b/engine/Sandbox.Test.Unit/Reflection/TypeLibraryTest.cs index 43a560c1d..1e70a4311 100644 --- a/engine/Sandbox.Test.Unit/Reflection/TypeLibraryTest.cs +++ b/engine/Sandbox.Test.Unit/Reflection/TypeLibraryTest.cs @@ -340,6 +340,12 @@ public interface IConstraintBreaker void DoSomething( T value ); } + public interface IConstraintBreaker2 + { + void DoSomething( T value ); + void DoSomething2( T2 value ); + void DoSomething3( T3 value ); + } /// @@ -360,6 +366,25 @@ public void ObeyGenericConstraints() .CreateGeneric>( [typeof( TypeWrapper )] ); Assert.IsNull( badboy ); } + + + /// + /// + /// + [TestMethod] + public void ObeyRecursiveGenericConstraints() + { + var tl = new Sandbox.Internal.TypeLibrary(); + tl.AddAssembly( ThisAssembly, true ); + + var goodboy = tl.GetType( typeof( ConstraintBreaker2<> ) ) + .CreateGeneric( [typeof( ConstraintBreaker2Inner )] ); + Assert.IsNotNull( goodboy ); + + var badboy = tl.GetType( typeof( ConstraintBreaker2<> ) ) + .CreateGeneric( [typeof( TypeWrapper )] ); + Assert.IsNull( badboy ); + } } [Expose, MyType] @@ -379,3 +404,40 @@ public void DoSomething( T value ) } } + + +public class ConstraintBreaker2Inner : + IConstraintBreaker2< + ConstraintBreaker2Inner, + ConstraintBreaker2Inner, + ConstraintBreaker2Inner + > +{ + public void DoSomething( ConstraintBreaker2Inner value ) + { + + } + + public void DoSomething2( ConstraintBreaker2Inner value ) + { + + } + + public void DoSomething3( ConstraintBreaker2Inner value ) + { + + } +} + +public class ConstraintBreaker2TypeErased { } + +[Expose] +public class ConstraintBreaker2 : ConstraintBreaker2TypeErased where T : IConstraintBreaker2 +{ + public void DoSomething( T value ) + { + value.DoSomething( value ); + value.DoSomething2( value ); + value.DoSomething3( value ); + } +}