@@ -74,6 +74,7 @@ public int Run()
7474 Can_round_trip_more_expression_kinds_through_system_expressions ( ) ;
7575 Can_compile_type_is_and_type_equal_the_same_as_system ( ) ;
7676 IsLifted_and_IsLiftedToNull_are_the_same_as_in_system_expressions ( ) ;
77+ Lifted_comparisons_with_null_operand_and_other_nullable_types_are_the_same_as_system ( ) ;
7778 Lifted_comparisons_have_the_same_type_and_result_as_system ( ) ;
7879 And_AndAlso_OrElse_take_the_type_from_the_left_operand ( ) ;
7980 Can_compile_nullable_boolean_and_also ( ) ;
@@ -87,7 +88,7 @@ public int Run()
8788 Can_print_reference_comparison ( ) ;
8889 Can_round_trip_new_expression_kinds_through_system_expressions ( ) ;
8990
90- return 68 ;
91+ return 69 ;
9192 }
9293
9394 private static int _sideEffectCalls ;
@@ -767,6 +768,113 @@ public void And_AndAlso_OrElse_take_the_type_from_the_left_operand()
767768 Asserts . AreEqual ( typeof ( bool ? ) , OrElse ( nb , nb ) . Type ) ;
768769 }
769770
771+ public struct LiftMoney
772+ {
773+ public int V ;
774+ public LiftMoney ( int v ) { V = v ; }
775+ public static bool operator == ( LiftMoney a , LiftMoney b ) => a . V == b . V ;
776+ public static bool operator != ( LiftMoney a , LiftMoney b ) => a . V != b . V ;
777+ public static bool operator < ( LiftMoney a , LiftMoney b ) => a . V < b . V ;
778+ public static bool operator > ( LiftMoney a , LiftMoney b ) => a . V > b . V ;
779+ public static bool operator <= ( LiftMoney a , LiftMoney b ) => a . V <= b . V ;
780+ public static bool operator >= ( LiftMoney a , LiftMoney b ) => a . V >= b . V ;
781+ public override bool Equals ( object o ) => o is LiftMoney m && m . V == V ;
782+ public override int GetHashCode ( ) => V ;
783+ }
784+ public enum LiftEnum { A , B , C }
785+
786+ public void Lifted_comparisons_with_null_operand_and_other_nullable_types_are_the_same_as_system ( )
787+ {
788+ var names = new [ ] { "Equal" , "NotEqual" , "GreaterThan" , "GreaterThanOrEqual" , "LessThan" , "LessThanOrEqual" } ;
789+ var cases = new ( Type , object , object ) [ ]
790+ {
791+ ( typeof ( int ? ) , 1 , 2 ) , ( typeof ( long ? ) , 1L , 2L ) , ( typeof ( double ? ) , 1.0 , 2.0 ) , ( typeof ( uint ? ) , 1u , 2u ) ,
792+ ( typeof ( char ? ) , 'a' , 'b' ) , ( typeof ( LiftEnum ? ) , LiftEnum . A , LiftEnum . B ) , ( typeof ( bool ? ) , true , false ) ,
793+ ( typeof ( decimal ? ) , 1m , 2m ) , ( typeof ( DateTime ? ) , new DateTime ( 2000 , 1 , 1 ) , new DateTime ( 2001 , 1 , 1 ) ) ,
794+ ( typeof ( LiftMoney ? ) , new LiftMoney ( 1 ) , new LiftMoney ( 2 ) ) ,
795+ } ;
796+ var sig = new [ ] { typeof ( SysExpr ) , typeof ( SysExpr ) , typeof ( bool ) , typeof ( MethodInfo ) } ;
797+ var lsig = new [ ] { typeof ( Expression ) , typeof ( Expression ) , typeof ( bool ) , typeof ( MethodInfo ) } ;
798+ var checkedCount = 0 ;
799+ foreach ( var ( t , v1 , v2 ) in cases )
800+ foreach ( var name in names )
801+ foreach ( var lift in new [ ] { false , true } )
802+ foreach ( var nullSide in new [ ] { 0 , 1 , 2 } ) // none, left, right
803+ {
804+ var what = $ "{ t . Name } { name } lift={ lift } nullSide={ nullSide } ";
805+ Delegate sysD ;
806+ try
807+ {
808+ var sa = SysExpr . Parameter ( t , "a" ) ; var sb = SysExpr . Parameter ( t , "b" ) ;
809+ var sl = nullSide == 1 ? SysExpr . Constant ( null , t ) : ( SysExpr ) sa ;
810+ var sr = nullSide == 2 ? SysExpr . Constant ( null , t ) : ( SysExpr ) sb ;
811+ var se = ( SysExpr ) typeof ( SysExpr ) . GetMethod ( name , sig ) . Invoke ( null , new object [ ] { sl , sr , lift , null } ) ;
812+ sysD = SysExpr . Lambda ( typeof ( Func < , , > ) . MakeGenericType ( t , t , se . Type ) , se , sa , sb ) . Compile ( ) ;
813+ }
814+ catch ( System . Reflection . TargetInvocationException ) { continue ; } // the operator is not defined for the type
815+
816+ var la = Parameter ( t , "a" ) ; var lb = Parameter ( t , "b" ) ;
817+ var ll = nullSide == 1 ? Constant ( null , t ) : ( Expression ) la ;
818+ var lr = nullSide == 2 ? Constant ( null , t ) : ( Expression ) lb ;
819+ var le = ( Expression ) typeof ( Expression ) . GetMethod ( name , lsig ) . Invoke ( null , new object [ ] { ll , lr , lift , null } ) ;
820+ var fastD = Lambda ( typeof ( Func < , , > ) . MakeGenericType ( t , t , le . Type ) , le , la , lb ) . CompileFast ( true ) ;
821+ Asserts . IsNotNull ( fastD , what ) ;
822+ ++ checkedCount ;
823+
824+ foreach ( var x in new [ ] { null , v1 , v2 } )
825+ foreach ( var y in new [ ] { null , v1 , v2 } )
826+ Asserts . AreEqual ( sysD . DynamicInvoke ( x , y ) , fastD . DynamicInvoke ( x , y ) , $ "{ what } ({ x } , { y } )") ;
827+ }
828+ Asserts . IsTrue ( checkedCount > 100 ) ;
829+
830+ // both operands are constants, as in the ported dotnet/runtime tests
831+ foreach ( var ( t , v1 , v2 ) in cases )
832+ foreach ( var name in names )
833+ foreach ( var lift in new [ ] { false , true } )
834+ foreach ( var x in new [ ] { null , v1 , v2 } )
835+ foreach ( var y in new [ ] { null , v1 , v2 } )
836+ {
837+ object expected ;
838+ try
839+ {
840+ var se = ( SysExpr ) typeof ( SysExpr ) . GetMethod ( name , sig ) . Invoke ( null , new object [ ] { SysExpr . Constant ( x , t ) , SysExpr . Constant ( y , t ) , lift , null } ) ;
841+ expected = SysExpr . Lambda ( typeof ( Func < > ) . MakeGenericType ( se . Type ) , se ) . Compile ( ) . DynamicInvoke ( ) ;
842+ }
843+ catch ( System . Reflection . TargetInvocationException ) { continue ; }
844+
845+ var le = ( Expression ) typeof ( Expression ) . GetMethod ( name , lsig ) . Invoke ( null , new object [ ] { Constant ( x , t ) , Constant ( y , t ) , lift , null } ) ;
846+ var fastD = Lambda ( typeof ( Func < > ) . MakeGenericType ( le . Type ) , le ) . CompileFast ( true ) ;
847+ var what = $ "{ t . Name } { name } lift={ lift } ({ x } , { y } )";
848+ Asserts . IsNotNull ( fastD , what ) ;
849+ Asserts . AreEqual ( expected , fastD . DynamicInvoke ( ) , what ) ;
850+ }
851+
852+ // unsigned operands above the signed range
853+ var ua = Parameter ( typeof ( uint ? ) , "ua" ) ; var ub = Parameter ( typeof ( uint ? ) , "ub" ) ;
854+ var ult = Lambda < Func < uint ? , uint ? , bool > > ( LessThan ( ua , ub ) , ua , ub ) . CompileFast ( true ) ;
855+ Asserts . AreEqual ( true , ult ( 1u , uint . MaxValue ) ) ;
856+ Asserts . AreEqual ( false , ult ( uint . MaxValue , 1u ) ) ;
857+ var ugt = Lambda < Func < uint ? , uint ? , bool ? > > ( GreaterThan ( ua , ub , true , null ) , ua , ub ) . CompileFast ( true ) ;
858+ Asserts . AreEqual ( true , ugt ( uint . MaxValue , 1u ) ) ;
859+ Asserts . AreEqual ( null , ugt ( null , 1u ) ) ;
860+ var pu = Parameter ( typeof ( ulong ) , "pu" ) ; var pv = Parameter ( typeof ( ulong ) , "pv" ) ;
861+ Asserts . AreEqual ( true , Lambda < Func < ulong , ulong , bool > > ( GreaterThan ( pu , pv ) , pu , pv ) . CompileFast ( true ) ( ulong . MaxValue , 1UL ) ) ;
862+ // the other operand is still evaluated when the result is known to be null
863+ var calls = 0 ;
864+ Func < int ? > sideEffect = ( ) => { ++ calls ; return 1 ; } ;
865+ var lt = LessThan ( Invoke ( Constant ( sideEffect ) ) , Constant ( null , typeof ( int ? ) ) , true , null ) ;
866+ Asserts . AreEqual ( typeof ( bool ? ) , lt . Type ) ;
867+ Asserts . AreEqual ( null , Lambda < Func < bool ? > > ( lt ) . CompileFast ( true ) ( ) ) ;
868+ var gt = GreaterThan ( Constant ( null , typeof ( int ? ) ) , Invoke ( Constant ( sideEffect ) ) , true , null ) ;
869+ Asserts . AreEqual ( null , Lambda < Func < bool ? > > ( gt ) . CompileFast ( true ) ( ) ) ;
870+ Asserts . AreEqual ( 2 , calls ) ;
871+
872+ // and the result is usable as a nested operand
873+ var nb = Parameter ( typeof ( bool ? ) , "nb" ) ;
874+ var withOr = Lambda < Func < bool ? , bool ? > > ( Or ( nb , lt ) , nb ) . CompileFast ( true ) ;
875+ Asserts . AreEqual ( true , withOr ( true ) ) ;
876+ Asserts . AreEqual ( null , withOr ( false ) ) ;
877+ }
770878 public void Lifted_comparisons_have_the_same_type_and_result_as_system ( )
771879 {
772880 var ip = Parameter ( typeof ( int ) , "a" ) ; var iq = Parameter ( typeof ( int ) , "b" ) ;
0 commit comments