context: //
  // nullable.cs: Nullable types support
  //
  // Authors: Martin Baulig (martin@ximian.com)
  //          Miguel de Icaza (miguel@ximian.com)
  //          Marek Safar (marek.safar@gmail.com)
  //
  // Dual licensed under the terms of the MIT X11 or GNU GPL
  //
  // Copyright 2001, 2002, 2003 Ximian, Inc (http://www.ximian.com)
  // Copyright 2004-2008 Novell, Inc
  // Copyright 2011 Xamarin Inc
  //
  
  using System;
  using SLE = System.Linq.Expressions;
  
  #if STATIC
  using IKVM.Reflection.Emit;
  #else
  using System.Reflection.Emit;
  #endif
  	
  namespace Mono.CSharp.Nullable
  {
  	public class NullableType : TypeExpr
  	{
  		readonly TypeSpec underlying;
  
  		public NullableType (TypeSpec type, Location loc)
  		{
  			this.underlying = type;
  			this.loc = loc;
  		}
  
  		public override TypeSpec ResolveAsType (IMemberContext ec)
  		{
  			eclass = ExprClass.Type;
  
  			var otype = ec.Module.PredefinedTypes.Nullable.Resolve ();
  			if (otype == null)
  				return null;
  
  			TypeArguments args = new TypeArguments (new TypeExpression (underlying, loc));
  			GenericTypeExpr ctype = new GenericTypeExpr (otype, args, loc);
  			
  			type = ctype.ResolveAsType (ec);
  			return type;
  		}
  	}
  
  	static class NullableInfo
  	{
  		public static MethodSpec GetConstructor (TypeSpec nullableType)
  		{
  			return (MethodSpec) MemberCache.FindMember (nullableType,
  				MemberFilter.Constructor (ParametersCompiled.CreateFullyResolved (GetUnderlyingType (nullableType))), BindingRestriction.DeclaredOnly);
  		}
  
  		public static MethodSpec GetHasValue (TypeSpec nullableType)
  		{
  			return (MethodSpec) MemberCache.FindMember (nullableType,
  				MemberFilter.Method ("get_HasValue", 0, ParametersCompiled.EmptyReadOnlyParameters, null), BindingRestriction.None);
  		}
  
  		public static MethodSpec GetGetValueOrDefault (TypeSpec nullableType)
  		{
  			return (MethodSpec) MemberCache.FindMember (nullableType,
  				MemberFilter.Method ("GetValueOrDefault", 0, ParametersCompiled.EmptyReadOnlyParameters, null), BindingRestriction.None);
  		}
  
  		//
  		// Don't use unless really required for correctness, see Unwrap::Emit
  		//
  		public static MethodSpec GetValue (TypeSpec nullableType)
  		{
  			return (MethodSpec) MemberCache.FindMember (nullableType,
  				MemberFilter.Method ("get_Value", 0, ParametersCompiled.EmptyReadOnlyParameters, null), BindingRestriction.None);
  		}
  
  		public static TypeSpec GetUnderlyingType (TypeSpec nullableType)
  		{
  			return ((InflatedTypeSpec) nullableType).TypeArguments[0];
  		}
  
  		public static TypeSpec GetEnumUnderlyingType (ModuleContainer module, TypeSpec nullableEnum)
  		{
  			return module.PredefinedTypes.Nullable.TypeSpec.MakeGenericType (module,
  				new[] { EnumSpec.GetUnderlyingType (GetUnderlyingType (nullableEnum)) });
  		}
  	}
  
  	public class Unwrap : Expression, IMemoryLocation
  	{
  		Expression expr;
  
  		LocalTemporary temp;
  		Expression temp_field;
  		readonly bool useDefaultValue;
  
  		public Unwrap (Expression expr, bool useDefaultValue = true)
  		{
  			this.expr = expr;
  			this.loc = expr.Location;
  			this.useDefaultValue = useDefaultValue;
  
  			type = NullableInfo.GetUnderlyingType (expr.Type);
  			eclass = expr.eclass;
  		}
  
  		public override bool ContainsEmitWithAwait ()
  		{
  			return expr.ContainsEmitWithAwait ();
  		}
  
  		// TODO: REMOVE
  		public static Expression Create (Expression expr)
  		{
  			//
  			// Avoid unwraping and wraping of same type
  			//
  			Wrap wrap = expr as Wrap;
  			if (wrap != null)
  				return wrap.Child;
  
  			return Create (expr, false);
  		}
  
  		public static Expression CreateUnwrapped (Expression expr)
  		{
  			//
  			// Avoid unwraping and wraping of same type
  			//
  			Wrap wrap = expr as Wrap;
  			if (wrap != null)
  				return wrap.Child;
  
  			return Create (expr, true);
  		}
  
  		public static Unwrap Create (Expression expr, bool useDefaultValue)
  		{
  			return new Unwrap (expr, useDefaultValue);
  		}
  		
  		public override Expression CreateExpressionTree (ResolveContext ec)
  		{
  			return expr.CreateExpressionTree (ec);
  		}
  
  		protected override Expression DoResolve (ResolveContext ec)
  		{
  			return this;
  		}
  
  		public override Expression DoResolveLValue (ResolveContext ec, Expression right_side)
  		{
  			expr = expr.DoResolveLValue (ec, right_side);
  			return this;
  		}
  
  		public override void Emit (EmitContext ec)
  		{
  			Store (ec);
  
  			var call = new CallEmitter ();
  			call.InstanceExpression = this;
  
  			//
  			// Using GetGetValueOrDefault is prefered because JIT can possibly
  			// inline it whereas Value property contains a throw which is very
  			// unlikely to be inlined
  			//
  			if (useDefaultValue)
  				call.EmitPredefined (ec, NullableInfo.GetGetValueOrDefault (expr.Type), null);
  			else
  				call.EmitPredefined (ec, NullableInfo.GetValue (expr.Type), null);
  		}
  
  		public void EmitCheck (EmitContext ec)
  		{
  			Store (ec);
  
  			var call = new CallEmitter ();
  			call.InstanceExpression = this;
  
  			call.EmitPredefined (ec, NullableInfo.GetHasValue (expr.Type), null);
  		}
  
  		public override Expression EmitToField (EmitContext ec)
  		{
  			if (temp_field == null)
  				temp_field = this.expr.EmitToField (ec);
  			
  			return this;
  		}
  
  		public override bool Equals (object obj)
  		{
  			Unwrap uw = obj as Unwrap;
  			return uw != null && expr.Equals (uw.expr);
  		}
  
  		public override void FlowAnalysis (FlowAnalysisContext fc)
  		{
  			expr.FlowAnalysis (fc);
  		}
  
  		public Expression Original {
  			get {
  				return expr;
  			}
  		}
  		
  		public override int GetHashCode ()
  		{
  			return expr.GetHashCode ();
  		}
  
  		public override bool IsNull {
  			get {
  				return expr.IsNull;
  			}
  		}
  
  		public void Store (EmitContext ec)
  		{
  			if (temp != null || temp_field != null)
  				return;
  
  			if (expr is VariableReference)
  				return;
  
  			expr.Emit (ec);
  			LocalVariable.Store (ec);
  		}
  
  		public void Load (EmitContext ec)
  		{
  			if (temp_field != null)
  				temp_field.Emit (ec);
  			else if (expr is VariableReference)
  				expr.Emit (ec);
  			else
  				LocalVariable.Emit (ec);
  		}
  
  		public override SLE.Expression MakeExpression (BuilderContext ctx)
  		{
  			return expr.MakeExpression (ctx);
  		}
  
  		public void AddressOf (EmitContext ec, AddressOp mode)
  		{
  			IMemoryLocation ml;
  
  			if (temp_field != null) {
  				ml = temp_field as IMemoryLocation;
  				if (ml == null) {
  					var lt = new LocalTemporary (temp_field.Type);
  					temp_field.Emit (ec);
  					lt.Store (ec);
  					ml = lt;
  				}
  			} else {
  				ml = expr as VariableReference;
  			}
  
  			if (ml != null)
  				ml.AddressOf (ec, mode);
  			else
  				LocalVariable.AddressOf (ec, mode);
  		}
  
  		//
  		// Keeps result of non-variable expression
  		//
  		LocalTemporary LocalVariable {
  			get {
  				if (temp == null && temp_field == null)
  					temp = new LocalTemporary (expr.Type);
  				return temp;
  			}
  		}
  	}
  
  	//
  	// Calls get_Value method on nullable expression
  	//
  	public class UnwrapCall : CompositeExpression
  	{
  		public UnwrapCall (Expression expr)
  			: base (expr)
  		{
  		}
  
  		protected override Expression DoResolve (ResolveContext rc)
  		{
  			base.DoResolve (rc);
  
  			if (type != null)
  				type = NullableInfo.GetUnderlyingType (type);
  
  			return this;
  		}
  
  		public override void Emit (EmitContext ec)
  		{
  			var call = new CallEmitter ();
  			call.InstanceExpression = Child;
  			call.EmitPredefined (ec, NullableInfo.GetValue (Child.Type), null);
  		}
  	}
  
  	public class Wrap : TypeCast
  	{
  		private Wrap (Expression expr, TypeSpec type)
  			: base (expr, type)
  		{
  			eclass = ExprClass.Value;
  		}
  
  		public override Expression CreateExpressionTree (ResolveContext ec)
  		{
  			TypeCast child_cast = child as TypeCast;
  			if (child_cast != null) {
  				child.Type = type;
  				return child_cast.CreateExpressionTree (ec);
  			}
  
  			var user_cast = child as UserCast;
  			if (user_cast != null) {
  				child.Type = type;
  				return user_cast.CreateExpressionTree (ec);
  			}
  
  			return base.CreateExpressionTree (ec);
  		}
  
  		public static Expression Create (Expression expr, TypeSpec type)
  		{
  			//
  			// Avoid unwraping and wraping of the same type
  			//
  			Unwrap unwrap = expr as Unwrap;
  			if (unwrap != null && expr.Type == NullableInfo.GetUnderlyingType (type))
  				return unwrap.Original;
  		
  			return new Wrap (expr, type);
  		}
  		
  		public override void Emit (EmitContext ec)
  		{
  			child.Emit (ec);
  			ec.Emit (OpCodes.Newobj, NullableInfo.GetConstructor (type));
  		}
  	}
  
  	//
  	// Represents null literal lifted to nullable type
  	//
  	public class LiftedNull : NullConstant, IMemoryLocation
  	{
  		private LiftedNull (TypeSpec nullable_type, Location loc)
  			: base (nullable_type, loc)
  		{
  			eclass = ExprClass.Value;
  		}
  
  		public static Constant Create (TypeSpec nullable, Location loc)
  		{
  			return new LiftedNull (nullable, loc);
  		}
  
  		public static Constant CreateFromExpression (ResolveContext rc, Expression e)
  		{
  			if (!rc.HasSet (ResolveContext.Options.ExpressionTreeConversion)) {
  				rc.Report.Warning (458, 2, e.Location, "The result of the expression is always `null' of type `{0}'",
  					e.Type.GetSignatureForError ());
  			}
  
  			return ReducedExpression.Create (Create (e.Type, e.Location), e);
  		}
  
  		public override void Emit (EmitContext ec)
  		{
  			// TODO: generate less temporary variables
  			LocalTemporary value_target = new LocalTemporary (type);
  
  			value_target.AddressOf (ec, AddressOp.Store);
  			ec.Emit (OpCodes.Initobj, type);
  			value_target.Emit (ec);
  			value_target.Release (ec);
  		}
  
  		public void AddressOf (EmitContext ec, AddressOp Mode)
  		{
  			LocalTemporary value_target = new LocalTemporary (type);
  				
  
gt: 			value_target.AddressOf (ec, AddressOp.Store);