Implement predefined binary enum operators involving an implicit user conversion
authorMarek Safar <marek.safar@gmail.com>
Fri, 17 Sep 2010 10:06:40 +0000 (11:06 +0100)
committerMarek Safar <marek.safar@gmail.com>
Fri, 17 Sep 2010 10:17:31 +0000 (11:17 +0100)
mcs/errors/gcs0458-8.cs [new file with mode: 0644]
mcs/mcs/expression.cs
mcs/mcs/nullable.cs
mcs/tests/test-801.cs [new file with mode: 0644]
mcs/tests/ver-il-gmcs.xml

diff --git a/mcs/errors/gcs0458-8.cs b/mcs/errors/gcs0458-8.cs
new file mode 100644 (file)
index 0000000..e945f5e
--- /dev/null
@@ -0,0 +1,17 @@
+// CS0458: The result of the expression is always `null' of type `E?'
+// Line: 15
+// Compiler options: -warnaserror -warn:2
+
+enum E
+{
+       V
+}
+
+public class C
+{
+       public static void Main ()
+       {
+               E e = E.V;
+               object o = null + e;
+       }
+}
index 5c8a27b2fdbe22e73dc0352768e517b81265311f..0fd91dfae68b8073bc2a83273c3c16aa1b0398b9 100644 (file)
@@ -1977,9 +1977,17 @@ namespace Mono.CSharp {
                        RelationalMask  = 1 << 13
                }
 
+               protected enum State
+               {
+                       None = 0,
+                       Compound = 1 << 1,
+                       LeftNullLifted = 1 << 2,
+                       RightNullLifted = 1 << 3
+               }
+
                readonly Operator oper;
                protected Expression left, right;
-               readonly bool is_compound;
+               protected State state;
                Expression enum_conversion;
 
                static PredefinedOperator[] standard_operators;
@@ -1989,7 +1997,8 @@ namespace Mono.CSharp {
                public Binary (Operator oper, Expression left, Expression right, bool isCompound, Location loc)
                        : this (oper, left, right, loc)
                {
-                       this.is_compound = isCompound;
+                       if (isCompound)
+                               state |= State.Compound;
                }
 
                public Binary (Operator oper, Expression left, Expression right, Location loc)
@@ -2000,12 +2009,22 @@ namespace Mono.CSharp {
                        this.loc = loc;
                }
 
+               #region Properties
+
+               public bool IsCompound {
+                       get {
+                               return (state & State.Compound) != 0;
+                       }
+               }
+
                public Operator Oper {
                        get {
                                return oper;
                        }
                }
-               
+
+               #endregion
+
                /// <summary>
                ///   Returns a stringified representation of the Operator
                /// </summary>
@@ -2072,7 +2091,7 @@ namespace Mono.CSharp {
                                break;
                        }
 
-                       if (is_compound)
+                       if (IsCompound)
                                return s + "=";
 
                        return s;
@@ -2105,15 +2124,15 @@ namespace Mono.CSharp {
                {
                        switch (oper) {
                        case Operator.Addition:
-                               return is_compound ? "AddAssign" : "Add";
+                               return IsCompound ? "AddAssign" : "Add";
                        case Operator.BitwiseAnd:
-                               return is_compound ? "AndAssign" : "And";
+                               return IsCompound ? "AndAssign" : "And";
                        case Operator.BitwiseOr:
-                               return is_compound ? "OrAssign" : "Or";
+                               return IsCompound ? "OrAssign" : "Or";
                        case Operator.Division:
-                               return is_compound ? "DivideAssign" : "Divide";
+                               return IsCompound ? "DivideAssign" : "Divide";
                        case Operator.ExclusiveOr:
-                               return is_compound ? "ExclusiveOrAssign" : "ExclusiveOr";
+                               return IsCompound ? "ExclusiveOrAssign" : "ExclusiveOr";
                        case Operator.Equality:
                                return "Equal";
                        case Operator.GreaterThan:
@@ -2123,7 +2142,7 @@ namespace Mono.CSharp {
                        case Operator.Inequality:
                                return "NotEqual";
                        case Operator.LeftShift:
-                               return is_compound ? "LeftShiftAssign" : "LeftShift";
+                               return IsCompound ? "LeftShiftAssign" : "LeftShift";
                        case Operator.LessThan:
                                return "LessThan";
                        case Operator.LessThanOrEqual:
@@ -2133,13 +2152,13 @@ namespace Mono.CSharp {
                        case Operator.LogicalOr:
                                return "Or";
                        case Operator.Modulus:
-                               return is_compound ? "ModuloAssign" : "Modulo";
+                               return IsCompound ? "ModuloAssign" : "Modulo";
                        case Operator.Multiply:
-                               return is_compound ? "MultiplyAssign" : "Multiply";
+                               return IsCompound ? "MultiplyAssign" : "Multiply";
                        case Operator.RightShift:
-                               return is_compound ? "RightShiftAssign" : "RightShift";
+                               return IsCompound ? "RightShiftAssign" : "RightShift";
                        case Operator.Subtraction:
-                               return is_compound ? "SubtractAssign" : "Subtract";
+                               return IsCompound ? "SubtractAssign" : "Subtract";
                        default:
                                throw new NotImplementedException ("Unknown expression type operator " + oper.ToString ());
                        }
@@ -2364,12 +2383,11 @@ namespace Mono.CSharp {
                                        return ResolveOperatorPointer (ec, l, r);
 
                                // Enums
-                               bool lenum = TypeManager.IsEnumType (l);
-                               bool renum = TypeManager.IsEnumType (r);
+                               bool lenum = l.IsEnum;
+                               bool renum = r.IsEnum;
                                if (lenum || renum) {
                                        expr = ResolveOperatorEnum (ec, lenum, renum, l, r);
 
-                                       // TODO: Can this be ambiguous
                                        if (expr != null)
                                                return expr;
                                }
@@ -2860,88 +2878,133 @@ namespace Mono.CSharp {
                        //
                        // U operator - (E e, E f)
                        // E operator - (E e, U x)
+                       // E operator - (U x, E e)      // LAMESPEC: Not covered by the specification
                        //
-                       // E operator + (U x, E e)
                        // E operator + (E e, U x)
+                       // E operator + (U x, E e)
                        //
-                       if (!((oper & (Operator.ComparisonMask | Operator.BitwiseMask)) != 0 ||
-                               (oper == Operator.Subtraction && lenum) ||
-                               (oper == Operator.Addition && (lenum != renum || type != null))))       // type != null for lifted null
-                               return null;
-
                        Expression ltemp = left;
                        Expression rtemp = right;
                        TypeSpec underlying_type;
+                       TypeSpec underlying_type_result;
+                       TypeSpec res_type;
                        Expression expr;
                        
+                       //
+                       // LAMESPEC: There is never ambiguous conversion between enum operators
+                       // the one which contains more enum parameters always wins even if there
+                       // is an implicit conversion involved
+                       //
                        if ((oper & (Operator.ComparisonMask | Operator.BitwiseMask)) != 0) {
                                if (renum) {
+                                       underlying_type = EnumSpec.GetUnderlyingType (rtype);
                                        expr = Convert.ImplicitConversion (ec, left, rtype, loc);
-                                       if (expr != null) {
-                                               left = expr;
-                                               ltype = expr.Type;
-                                       }
+                                       if (expr == null)
+                                               return null;
+
+                                       left = expr;
+                                       ltype = expr.Type;
                                } else if (lenum) {
+                                       underlying_type = EnumSpec.GetUnderlyingType (ltype);
                                        expr = Convert.ImplicitConversion (ec, right, ltype, loc);
-                                       if (expr != null) {
-                                               right = expr;
-                                               rtype = expr.Type;
-                                       }
+                                       if (expr == null)
+                                               return null;
+
+                                       right = expr;
+                                       rtype = expr.Type;
+                               } else {
+                                       return null;
                                }
-                       }                       
 
-                       if (ltype == rtype) {
-                               underlying_type = EnumSpec.GetUnderlyingType (ltype);
+                               if ((oper & Operator.BitwiseMask) != 0) {
+                                       res_type = ltype;
+                                       underlying_type_result = underlying_type;
+                               } else {
+                                       res_type = null;
+                                       underlying_type_result = null;
+                               }
+                       } else if (oper == Operator.Subtraction) {
+                               if (renum) {
+                                       underlying_type = EnumSpec.GetUnderlyingType (rtype);
+                                       if (ltype != rtype) {
+                                               expr = Convert.ImplicitConversion (ec, left, rtype, left.Location);
+                                               if (expr == null) {
+                                                       expr = Convert.ImplicitConversion (ec, left, underlying_type, left.Location);
+                                                       if (expr == null)
+                                                               return null;
+
+                                                       res_type = rtype;
+                                               } else {
+                                                       res_type = underlying_type;
+                                               }
 
-                               if (left is Constant)
-                                       left = ((Constant) left).ConvertExplicitly (false, underlying_type).Resolve (ec);
-                               else
-                                       left = EmptyCast.Create (left, underlying_type);
+                                               left = expr;
+                                       } else {
+                                               res_type = underlying_type;
+                                       }
 
-                               if (right is Constant)
-                                       right = ((Constant) right).ConvertExplicitly (false, underlying_type).Resolve (ec);
-                               else
-                                       right = EmptyCast.Create (right, underlying_type);
-                       } else if (lenum) {
-                               underlying_type = EnumSpec.GetUnderlyingType (ltype);
+                                       underlying_type_result = underlying_type;
+                               } else if (lenum) {
+                                       underlying_type = EnumSpec.GetUnderlyingType (ltype);
+                                       expr = Convert.ImplicitConversion (ec, right, ltype, right.Location);
+                                       if (expr == null) {
+                                               expr = Convert.ImplicitConversion (ec, right, underlying_type, right.Location);
+                                               if (expr == null)
+                                                       return null;
 
-                               if (oper != Operator.Subtraction && oper != Operator.Addition) {
-                                       Constant c = right as Constant;
-                                       if (c == null || !c.IsDefaultValue)
-                                               return null;
+                                               res_type = ltype;
+                                       } else {
+                                               res_type = underlying_type;
+                                       }
+
+                                       right = expr;
+                                       underlying_type_result = underlying_type;
                                } else {
-                                       if (!Convert.ImplicitStandardConversionExists (right, underlying_type))
-                                               return null;
+                                       return null;
+                               }
+                       } else if (oper == Operator.Addition) {
+                               if (lenum) {
+                                       underlying_type = EnumSpec.GetUnderlyingType (ltype);
+                                       res_type = ltype;
 
-                                       right = Convert.ImplicitConversionStandard (ec, right, underlying_type, right.Location);
+                                       if (rtype != underlying_type && (state & (State.RightNullLifted | State.LeftNullLifted)) == 0) {
+                                               expr = Convert.ImplicitConversion (ec, right, underlying_type, right.Location);
+                                               if (expr == null)
+                                                       return null;
+
+                                               right = expr;
+                                       }
+                               } else {
+                                       underlying_type = EnumSpec.GetUnderlyingType (rtype);
+                                       res_type = rtype;
+                                       if (ltype != underlying_type) {
+                                               expr = Convert.ImplicitConversion (ec, left, underlying_type, left.Location);
+                                               if (expr == null)
+                                                       return null;
+
+                                               left = expr;
+                                       }
                                }
 
+                               underlying_type_result = underlying_type;
+                       } else {
+                               return null;
+                       }
+
+                       // Unwrap the constant correctly, so DoBinaryOperatorPromotion can do the magic
+                       // with constants and expressions
+                       if (left.Type != underlying_type) {
                                if (left is Constant)
                                        left = ((Constant) left).ConvertExplicitly (false, underlying_type).Resolve (ec);
                                else
                                        left = EmptyCast.Create (left, underlying_type);
+                       }
 
-                       } else if (renum) {
-                               underlying_type = EnumSpec.GetUnderlyingType (rtype);
-
-                               if (oper != Operator.Addition) {
-                                       Constant c = left as Constant;
-                                       if (c == null || !c.IsDefaultValue)
-                                               return null;
-                               } else {
-                                       if (!Convert.ImplicitStandardConversionExists (left, underlying_type))
-                                               return null;
-
-                                       left = Convert.ImplicitConversionStandard (ec, left, underlying_type, left.Location);
-                               }
-
+                       if (right.Type != underlying_type) {
                                if (right is Constant)
                                        right = ((Constant) right).ConvertExplicitly (false, underlying_type).Resolve (ec);
                                else
                                        right = EmptyCast.Create (right, underlying_type);
-
-                       } else {
-                               return null;
                        }
 
                        //
@@ -2954,22 +3017,15 @@ namespace Mono.CSharp {
                                return null;
                        }
 
-                       TypeSpec res_type = null;
-                       if ((oper & Operator.BitwiseMask) != 0 || oper == Operator.Subtraction || oper == Operator.Addition) {
-                               TypeSpec promoted_type = lenum ? left.Type : right.Type;
-                               enum_conversion = Convert.ExplicitNumericConversion (
-                                       new EmptyExpression (promoted_type), underlying_type);
-
-                               if (oper == Operator.Subtraction && renum && lenum)
-                                       res_type = underlying_type;
-                               else if (oper == Operator.Addition && renum)
-                                       res_type = rtype;
-                               else
-                                       res_type = ltype;
+                       if (underlying_type_result != null && left.Type != underlying_type_result) {
+                               enum_conversion = Convert.ExplicitNumericConversion (new EmptyExpression (left.Type), underlying_type_result);
                        }
-                       
+
                        expr = ResolveOperatorPredefined (ec, standard_operators, true, res_type);
-                       if (!is_compound || expr == null)
+                       if (expr == null)
+                               return null;
+
+                       if (!IsCompound)
                                return expr;
 
                        //
index b834121dc96d1774ae1efea96a6ca6d5d9562509..1f4df2e824f75df3260016457e2426862c07bf44 100644 (file)
@@ -562,7 +562,6 @@ namespace Mono.CSharp.Nullable
        public class LiftedBinaryOperator : Binary
        {
                Unwrap left_unwrap, right_unwrap;
-               bool left_null_lifted, right_null_lifted;
                Expression left_orig, right_orig;
                Expression user_operator;
                MethodSpec wrap_ctor;
@@ -572,6 +571,25 @@ namespace Mono.CSharp.Nullable
                {
                }
 
+               bool IsBitwiseBoolean {
+                       get {
+                               return (Oper & Operator.BitwiseMask) != 0 && left_unwrap != null && right_unwrap != null &&
+                               left_unwrap.Type == TypeManager.bool_type && right_unwrap.Type == TypeManager.bool_type;
+                       }
+               }
+
+               bool IsLeftNullLifted {
+                       get {
+                               return (state & State.LeftNullLifted) != 0;
+                       }
+               }
+
+               bool IsRightNullLifted {
+                       get {
+                               return (state & State.RightNullLifted) != 0;
+                       }
+               }
+
                public override Expression CreateExpressionTree (ResolveContext ec)
                {
                        if (user_operator != null)
@@ -630,13 +648,13 @@ namespace Mono.CSharp.Nullable
                        //      
                        if (left_orig.IsNull) {
                                left = right;
-                               left_null_lifted = true;
+                               state |= State.LeftNullLifted;
                                type = TypeManager.bool_type;
                        }
 
                        if (right_orig.IsNull) {
                                right = left;
-                               right_null_lifted = true;
+                               state |= State.RightNullLifted;
                                type = TypeManager.bool_type;
                        }
 
@@ -685,7 +703,7 @@ namespace Mono.CSharp.Nullable
                        //
                        // Either left or right is null
                        //
-                       if (left_unwrap != null && (right_null_lifted || right.IsNull)) {
+                       if (left_unwrap != null && (IsRightNullLifted || right.IsNull)) {
                                left_unwrap.EmitCheck (ec);
                                if (Oper == Binary.Operator.Equality) {
                                        ec.Emit (OpCodes.Ldc_I4_0);
@@ -694,7 +712,7 @@ namespace Mono.CSharp.Nullable
                                return;
                        }
 
-                       if (right_unwrap != null && (left_null_lifted || left.IsNull)) {
+                       if (right_unwrap != null && (IsLeftNullLifted || left.IsNull)) {
                                right_unwrap.EmitCheck (ec);
                                if (Oper == Binary.Operator.Equality) {
                                        ec.Emit (OpCodes.Ldc_I4_0);
@@ -815,13 +833,6 @@ namespace Mono.CSharp.Nullable
                        base.EmitOperator (ec, l);
                }
 
-               bool IsBitwiseBoolean {
-                       get {
-                               return (Oper & Operator.BitwiseMask) != 0 && left_unwrap != null && right_unwrap != null &&
-                               left_unwrap.Type == TypeManager.bool_type && right_unwrap.Type == TypeManager.bool_type;
-                       }
-               }
-
                Expression LiftResult (ResolveContext ec, Expression res_expr)
                {
                        TypeExpr lifted_type;
@@ -829,7 +840,7 @@ namespace Mono.CSharp.Nullable
                        //
                        // Avoid double conversion
                        //
-                       if (left_unwrap == null || left_null_lifted || left_unwrap.Type != left.Type || (left_unwrap != null && right_null_lifted)) {
+                       if (left_unwrap == null || IsLeftNullLifted || left_unwrap.Type != left.Type || (left_unwrap != null && IsRightNullLifted)) {
                                lifted_type = new NullableType (left.Type, loc);
                                lifted_type = lifted_type.ResolveAsTypeTerminal (ec, false);
                                if (lifted_type == null)
@@ -841,7 +852,7 @@ namespace Mono.CSharp.Nullable
                                        left = EmptyCast.Create (left, lifted_type.Type);
                        }
 
-                       if (left != right && (right_unwrap == null || right_null_lifted || right_unwrap.Type != right.Type || (right_unwrap != null && left_null_lifted))) {
+                       if (left != right && (right_unwrap == null || IsRightNullLifted || right_unwrap.Type != right.Type || (right_unwrap != null && IsLeftNullLifted))) {
                                lifted_type = new NullableType (right.Type, loc);
                                lifted_type = lifted_type.ResolveAsTypeTerminal (ec, false);
                                if (lifted_type == null)
@@ -863,7 +874,7 @@ namespace Mono.CSharp.Nullable
                                type = res_expr.Type = lifted_type.Type;
                        }
 
-                       if (left_null_lifted) {
+                       if (IsLeftNullLifted) {
                                left = LiftedNull.Create (right.Type, left.Location);
 
                                if ((Oper & (Operator.ArithmeticMask | Operator.ShiftMask | Operator.BitwiseMask)) != 0)
@@ -876,7 +887,7 @@ namespace Mono.CSharp.Nullable
                                        return CreateNullConstant (ec, right_orig).Resolve (ec);
                        }
 
-                       if (right_null_lifted) {
+                       if (IsRightNullLifted) {
                                right = LiftedNull.Create (left.Type, right.Location);
 
                                if ((Oper & (Operator.ArithmeticMask | Operator.ShiftMask | Operator.BitwiseMask)) != 0)
@@ -907,7 +918,7 @@ namespace Mono.CSharp.Nullable
                        // (in unlifted or lifted form) exists for the operation.
                        //
                        if (e == null && (Oper & Operator.EqualityMask) != 0) {
-                               if ((left_null_lifted && right_unwrap != null) || (right_null_lifted && left_unwrap != null))
+                               if ((IsLeftNullLifted && right_unwrap != null) || (IsRightNullLifted && left_unwrap != null))
                                        return LiftResult (ec, this);
                        }
 
diff --git a/mcs/tests/test-801.cs b/mcs/tests/test-801.cs
new file mode 100644 (file)
index 0000000..0c20484
--- /dev/null
@@ -0,0 +1,56 @@
+using System;
+
+class C
+{
+       public enum E
+       {
+               V_0 = 10,
+               V_1     = 50,
+               V_2 = 80
+       }
+       
+       public static implicit operator E (C x)
+       {
+               return E.V_2;
+       }
+
+       public static implicit operator int (C x)
+       {
+               return 1;
+       }
+
+       public static int Main ()
+       {
+               var v = new C ();
+               int i = E.V_1 - v;
+               if (i != -30)
+                       return 1;
+               
+               i = v - E.V_1;
+               if (i != 30)
+                       return 10;
+               
+               E e = E.V_1 + v;
+               if (e != (E) 51)
+                       return 2;
+               
+               e = v + E.V_0;
+               if (e != (E) 11)
+                       return 3;
+               
+               bool b = E.V_2 > v;
+               if (b)
+                       return 4;
+               
+               int iv = 900;
+               e = iv - E.V_1;
+               if (e != (E)850)
+                       return 5;
+               
+               i = v - E.V_1;
+               if (i != (int) 30)
+                       return 6;
+
+               return 0;
+       }
+}
index 38a0e1c21f3369f22812cc0bd72ebdc27a7c27b7..5082da96d00f533e6f27f4424d3e07fdefc857b1 100644 (file)
       </method>
     </type>
   </test>
+  <test name="test-801.cs">
+    <type name="C">
+      <method name="Int32 Main()">
+        <size>154</size>
+      </method>
+      <method name="E op_Implicit(C)">
+        <size>3</size>
+      </method>
+      <method name="Int32 op_Implicit(C)">
+        <size>2</size>
+      </method>
+      <method name="Void .ctor()">
+        <size>7</size>
+      </method>
+    </type>
+  </test>
   <test name="test-81.cs">
     <type name="N1.A">
       <method name="Void .ctor()">