this.loc = loc;
}
- public override TypeSpec ResolveAsType (IMemberContext ec)
+ public override TypeSpec ResolveAsType (IMemberContext ec, bool allowUnboundTypeArguments = false)
{
eclass = ExprClass.Type;
{
}
+ public override bool IsNull {
+ get {
+ return expr.IsNull;
+ }
+ }
+
public override bool ContainsEmitWithAwait ()
{
return unwrap.ContainsEmitWithAwait ();
//
// Both operands are bool? types
//
- if (UnwrapLeft != null && UnwrapRight != null) {
+ if ((UnwrapLeft != null && !Left.IsNull) && (UnwrapRight != null && !Right.IsNull)) {
if (ec.HasSet (BuilderContext.Options.AsyncBody) && Binary.Right.ContainsEmitWithAwait ()) {
Left = Left.EmitToField (ec);
Right = Right.EmitToField (ec);
LiftedNull.Create (type, loc).Emit (ec);
} else {
Left.Emit (ec);
+ UnwrapRight.Store (ec);
+
ec.Emit (or ? OpCodes.Brfalse_S : OpCodes.Brtrue_S, load_right);
ec.EmitInt (or ? 1 : 0);
ec.Emit (OpCodes.Br_S, end_label);
ec.MarkLabel (load_right);
- UnwrapRight.Original.Emit (ec);
+ UnwrapRight.Load (ec);
}
} else {
//
LiftedNull.Create (type, loc).Emit (ec);
} else {
Right.Emit (ec);
- ec.Emit (or ? OpCodes.Brfalse_S : OpCodes.Brtrue_S, load_right);
+ ec.Emit (or ? OpCodes.Brfalse_S : OpCodes.Brtrue_S, load_left);
ec.EmitInt (or ? 1 : 0);
ec.Emit (OpCodes.Newobj, NullableInfo.GetConstructor (type));
ec.Emit (OpCodes.Br_S, end_label);
- ec.MarkLabel (load_right);
+ ec.MarkLabel (load_left);
UnwrapLeft.Load (ec);
}
{
Expression left, right;
Unwrap unwrap;
+ bool user_conversion_left;
public NullCoalescingOperator (Expression left, Expression right)
{
if (right.IsNull)
return ReducedExpression.Create (left, this);
- if (Convert.ImplicitConversionExists (ec, right, unwrap.Type)) {
- left = unwrap;
- ltype = left.Type;
+ Expression conv;
+ if (right.Type.IsNullableType) {
+ conv = right.Type == ltype ? right : Convert.ImplicitNulableConversion (ec, right, ltype);
+ if (conv != null) {
+ right = conv;
+ type = ltype;
+ return this;
+ }
+ } else {
+ conv = Convert.ImplicitConversion (ec, right, unwrap.Type, loc);
+ if (conv != null) {
+ left = unwrap;
+ ltype = left.Type;
- //
- // If right is a dynamic expression, the result type is dynamic
- //
- if (right.Type.BuiltinType == BuiltinTypeSpec.Type.Dynamic) {
- type = right.Type;
+ //
+ // If right is a dynamic expression, the result type is dynamic
+ //
+ if (right.Type.BuiltinType == BuiltinTypeSpec.Type.Dynamic) {
+ type = right.Type;
+
+ // Need to box underlying value type
+ left = Convert.ImplicitBoxingConversion (left, ltype, type);
+ return this;
+ }
- // Need to box underlying value type
- left = Convert.ImplicitBoxingConversion (left, ltype, type);
+ right = conv;
+ type = ltype;
return this;
}
-
- right = Convert.ImplicitConversion (ec, right, ltype, loc);
- type = ltype;
- return this;
}
} else if (TypeSpec.IsReferenceType (ltype)) {
if (Convert.ImplicitConversionExists (ec, right, ltype)) {
return ReducedExpression.Create (right, this, false).Resolve (ec);
left = Convert.ImplicitConversion (ec, unwrap ?? left, rtype, loc);
+ user_conversion_left = left is UserCast;
type = rtype;
return this;
}
return;
}
- left.Emit (ec);
- ec.Emit (OpCodes.Dup);
+ //
+ // Null check is done on original expression not after expression is converted to
+ // result type. This is in most cases same but when user conversion is involved
+ // we can end up in situation when user operator does the null handling which is
+ // not what the operator is supposed to do.
+ // There is tricky case where cast of left expression is meant to be cast of
+ // whole source expression (null check is done on it) and cast from right-to-left
+ // conversion needs to do null check on unconverted source expression.
+ //
+ if (user_conversion_left) {
+ var op_expr = (UserCast) left;
+
+ op_expr.Source.Emit (ec);
+ LocalTemporary temp;
+
+ // TODO: More load kinds can be special cased
+ if (!(op_expr.Source is VariableReference)) {
+ temp = new LocalTemporary (op_expr.Source.Type);
+ temp.Store (ec);
+ temp.Emit (ec);
+ op_expr.Source = temp;
+ } else {
+ temp = null;
+ }
- // Only to make verifier happy
- if (left.Type.IsGenericParameter)
- ec.Emit (OpCodes.Box, left.Type);
+ var right_label = ec.DefineLabel ();
+ ec.Emit (OpCodes.Brfalse_S, right_label);
+ left.Emit (ec);
+ ec.Emit (OpCodes.Br, end_label);
+ ec.MarkLabel (right_label);
- ec.Emit (OpCodes.Brtrue, end_label);
+ if (temp != null)
+ temp.Release (ec);
+ } else {
+ //
+ // Common case where expression is not modified before null check and
+ // we generate better/smaller code
+ //
+ left.Emit (ec);
+ ec.Emit (OpCodes.Dup);
+
+ // Only to make verifier happy
+ if (left.Type.IsGenericParameter)
+ ec.Emit (OpCodes.Box, left.Type);
+
+ ec.Emit (OpCodes.Brtrue, end_label);
+
+ ec.Emit (OpCodes.Pop);
+ }
- ec.Emit (OpCodes.Pop);
right.Emit (ec);
ec.MarkLabel (end_label);