// // argument.cs: Argument expressions // // Author: // Miguel de Icaza (miguel@ximain.com) // Marek Safar (marek.safar@gmail.com) // // Dual licensed under the terms of the MIT X11 or GNU GPL // Copyright 2003-2011 Novell, Inc. // Copyright 2011 Xamarin Inc // using System; using System.Collections.Generic; #if STATIC using IKVM.Reflection.Emit; #else using System.Reflection.Emit; #endif namespace Mono.CSharp { // // Argument expression used for invocation // public class Argument { public enum AType : byte { None = 0, Ref = 1, // ref modifier used Out = 2, // out modifier used Default = 3, // argument created from default parameter value DynamicTypeName = 4, // System.Type argument for dynamic binding ExtensionType = 5, // Instance expression inserted as the first argument // Conditional instance expression inserted as the first argument ExtensionTypeConditionalAccess = 5 | ConditionalAccessFlag, ConditionalAccessFlag = 1 << 7 } public readonly AType ArgType; public Expression Expr; public Argument (Expression expr, AType type) { this.Expr = expr; this.ArgType = type; } public Argument (Expression expr) { this.Expr = expr; } #region Properties public bool IsByRef { get { return ArgType == AType.Ref || ArgType == AType.Out; } } public bool IsDefaultArgument { get { return ArgType == AType.Default; } } public bool IsExtensionType { get { return (ArgType & AType.ExtensionType) == AType.ExtensionType; } } public Parameter.Modifier Modifier { get { switch (ArgType) { case AType.Out: return Parameter.Modifier.OUT; case AType.Ref: return Parameter.Modifier.REF; default: return Parameter.Modifier.NONE; } } } public TypeSpec Type { get { return Expr.Type; } } #endregion public Argument Clone (Expression expr) { Argument a = (Argument) MemberwiseClone (); a.Expr = expr; return a; } public Argument Clone (CloneContext clonectx) { return Clone (Expr.Clone (clonectx)); } public virtual Expression CreateExpressionTree (ResolveContext ec) { if (ArgType == AType.Default) ec.Report.Error (854, Expr.Location, "An expression tree cannot contain an invocation which uses optional parameter"); return Expr.CreateExpressionTree (ec); } public virtual void Emit (EmitContext ec) { if (!IsByRef) { if (ArgType == AType.ExtensionTypeConditionalAccess) { var ie = new InstanceEmitter (Expr, false); ie.Emit (ec, true); } else { Expr.Emit (ec); } return; } AddressOp mode = AddressOp.Store; if (ArgType == AType.Ref) mode |= AddressOp.Load; IMemoryLocation ml = (IMemoryLocation) Expr; ml.AddressOf (ec, mode); } public Argument EmitToField (EmitContext ec, bool cloneResult) { var res = Expr.EmitToField (ec); if (cloneResult && res != Expr) return new Argument (res, ArgType); Expr = res; return this; } public void FlowAnalysis (FlowAnalysisContext fc) { if (ArgType == AType.Out) { var vr = Expr as VariableReference; if (vr != null) { if (vr.VariableInfo != null) fc.SetVariableAssigned (vr.VariableInfo); return; } var fe = Expr as FieldExpr; if (fe != null) { fe.SetFieldAssigned (fc); return; } return; } Expr.FlowAnalysis (fc); } public string GetSignatureForError () { if (Expr.eclass == ExprClass.MethodGroup) return Expr.ExprClassName; return Expr.Type.GetSignatureForError (); } public bool ResolveMethodGroup (ResolveContext ec) { SimpleName sn = Expr as SimpleName; if (sn != null) Expr = sn.GetMethodGroup (); // FIXME: csc doesn't report any error if you try to use `ref' or // `out' in a delegate creation expression. Expr = Expr.Resolve (ec, ResolveFlags.VariableOrValue | ResolveFlags.MethodGroup); if (Expr == null) return false; return true; } public void Resolve (ResolveContext ec) { // Verify that the argument is readable if (ArgType != AType.Out) Expr = Expr.Resolve (ec); // Verify that the argument is writeable if (Expr != null && IsByRef) Expr = Expr.ResolveLValue (ec, EmptyExpression.OutAccess); if (Expr == null) Expr = ErrorExpression.Instance; } } public class MovableArgument : Argument { LocalTemporary variable; public MovableArgument (Argument arg) : this (arg.Expr, arg.ArgType) { } protected MovableArgument (Expression expr, AType modifier) : base (expr, modifier) { } public override void Emit (EmitContext ec) { // TODO: Should guard against multiple emits base.Emit (ec); // Release temporary variable when used if (variable != null) variable.Release (ec); } public void EmitToVariable (EmitContext ec) { var type = Expr.Type; if (IsByRef) { var ml = (IMemoryLocation) Expr; ml.AddressOf (ec, AddressOp.LoadStore); type = ReferenceContainer.MakeType (ec.Module, type); } else { Expr.Emit (ec); } variable = new LocalTemporary (type); variable.Store (ec); Expr = variable; } } public class NamedArgument : MovableArgument { public readonly string Name; readonly Location loc; public NamedArgument (string name, Location loc, Expression expr) : this (name, loc, expr, AType.None) { } public NamedArgument (string name, Location loc, Expression expr, AType modifier) : base (expr, modifier) { this.Name = name; this.loc = loc; } public override Expression CreateExpressionTree (ResolveContext ec) { ec.Report.Error (853, loc, "An expression tree cannot contain named argument"); return base.CreateExpressionTree (ec); } public Location Location { get { return loc; } } } public class Arguments { sealed class ArgumentsOrdered : Arguments { readonly List ordered; public ArgumentsOrdered (Arguments args) : base (args.Count) { AddRange (args); ordered = new List (); } public void AddOrdered (MovableArgument arg) { ordered.Add (arg); } public override void FlowAnalysis (FlowAnalysisContext fc, List movable = null) { foreach (var arg in ordered) { if (arg.ArgType != Argument.AType.Out) arg.FlowAnalysis (fc); } base.FlowAnalysis (fc, ordered); } public override Arguments Emit (EmitContext ec, bool dup_args, bool prepareAwait) { foreach (var a in ordered) { if (prepareAwait) a.EmitToField (ec, false); else a.EmitToVariable (ec); } return base.Emit (ec, dup_args, prepareAwait); } } // Try not to add any more instances to this class, it's allocated a lot List args; public Arguments (int capacity) { args = new List (capacity); } private Arguments (List args) { this.args = args; } public void Add (Argument arg) { args.Add (arg); } public void AddRange (Arguments args) { this.args.AddRange (args.args); } public bool ContainsEmitWithAwait () { foreach (var arg in args) { if (arg.Expr.ContainsEmitWithAwait ()) return true; } return false; } public ArrayInitializer CreateDynamicBinderArguments (ResolveContext rc) { Location loc = Location.Null; var all = new ArrayInitializer (args.Count, loc); MemberAccess binder = DynamicExpressionStatement.GetBinderNamespace (loc); foreach (Argument a in args) { Arguments dargs = new Arguments (2); // CSharpArgumentInfoFlags.None = 0 const string info_flags_enum = "CSharpArgumentInfoFlags"; Expression info_flags = new IntLiteral (rc.BuiltinTypes, 0, loc); if (a.Expr is Constant) { info_flags = new Binary (Binary.Operator.BitwiseOr, info_flags, new MemberAccess (new MemberAccess (binder, info_flags_enum, loc), "Constant", loc)); } else if (a.ArgType == Argument.AType.Ref) { info_flags = new Binary (Binary.Operator.BitwiseOr, info_flags, new MemberAccess (new MemberAccess (binder, info_flags_enum, loc), "IsRef", loc)); info_flags = new Binary (Binary.Operator.BitwiseOr, info_flags, new MemberAccess (new MemberAccess (binder, info_flags_enum, loc), "UseCompileTimeType", loc)); } else if (a.ArgType == Argument.AType.Out) { info_flags = new Binary (Binary.Operator.BitwiseOr, info_flags, new MemberAccess (new MemberAccess (binder, info_flags_enum, loc), "IsOut", loc)); info_flags = new Binary (Binary.Operator.BitwiseOr, info_flags, new MemberAccess (new MemberAccess (binder, info_flags_enum, loc), "UseCompileTimeType", loc)); } else if (a.ArgType == Argument.AType.DynamicTypeName) { info_flags = new Binary (Binary.Operator.BitwiseOr, info_flags, new MemberAccess (new MemberAccess (binder, info_flags_enum, loc), "IsStaticType", loc)); } var arg_type = a.Expr.Type; if (arg_type.BuiltinType != BuiltinTypeSpec.Type.Dynamic && arg_type != InternalType.NullLiteral) { MethodGroupExpr mg = a.Expr as MethodGroupExpr; if (mg != null) { rc.Report.Error (1976, a.Expr.Location, "The method group `{0}' cannot be used as an argument of dynamic operation. Consider using parentheses to invoke the method", mg.Name); } else if (arg_type == InternalType.AnonymousMethod) { rc.Report.Error (1977, a.Expr.Location, "An anonymous method or lambda expression cannot be used as an argument of dynamic operation. Consider using a cast"); } else if (arg_type.Kind == MemberKind.Void || arg_type == InternalType.Arglist || arg_type.IsPointer) { rc.Report.Error (1978, a.Expr.Location, "An expression of type `{0}' cannot be used as an argument of dynamic operation", arg_type.GetSignatureForError ()); } info_flags = new Binary (Binary.Operator.BitwiseOr, info_flags, new MemberAccess (new MemberAccess (binder, info_flags_enum, loc), "UseCompileTimeType", loc)); } string named_value; NamedArgument na = a as NamedArgument; if (na != null) { info_flags = new Binary (Binary.Operator.BitwiseOr, info_flags, new MemberAccess (new MemberAccess (binder, info_flags_enum, loc), "NamedArgument", loc)); named_value = na.Name; } else { named_value = null; } dargs.Add (new Argument (info_flags)); dargs.Add (new Argument (new StringLiteral (rc.BuiltinTypes, named_value, loc))); all.Add (new Invocation (new MemberAccess (new MemberAccess (binder, "CSharpArgumentInfo", loc), "Create", loc), dargs)); } return all; } public static Arguments CreateForExpressionTree (ResolveContext ec, Arguments args, params Expression[] e) { Arguments all = new Arguments ((args == null ? 0 : args.Count) + e.Length); for (int i = 0; i < e.Length; ++i) { if (e [i] != null) all.Add (new Argument (e[i])); } if (args != null) { foreach (Argument a in args.args) { Expression tree_arg = a.CreateExpressionTree (ec); if (tree_arg != null) all.Add (new Argument (tree_arg)); } } return all; } public void CheckArrayAsAttribute (CompilerContext ctx) { foreach (Argument arg in args) { // Type is undefined (was error 246) if (arg.Type == null) continue; if (arg.Type.IsArray) ctx.Report.Warning (3016, 1, arg.Expr.Location, "Arrays as attribute arguments are not CLS-compliant"); } } public Arguments Clone (CloneContext ctx) { Arguments cloned = new Arguments (args.Count); foreach (Argument a in args) cloned.Add (a.Clone (ctx)); return cloned; } public int Count { get { return args.Count; } } // // Emits a list of resolved Arguments // public void Emit (EmitContext ec) { Emit (ec, false, false); } // // if `dup_args' is true or any of arguments contains await. // A copy of all arguments will be returned to the caller // public virtual Arguments Emit (EmitContext ec, bool dup_args, bool prepareAwait) { List dups; if ((dup_args && Count != 0) || prepareAwait) dups = new List (Count); else dups = null; LocalTemporary lt; foreach (Argument a in args) { if (prepareAwait) { dups.Add (a.EmitToField (ec, true)); continue; } a.Emit (ec); if (!dup_args) { continue; } if (a.Expr.IsSideEffectFree) { // // No need to create a temporary variable for side effect free expressions. I assume // all side-effect free expressions are cheap, this has to be tweaked when we become // more aggressive on detection // dups.Add (a); } else { ec.Emit (OpCodes.Dup); // TODO: Release local temporary on next Emit // Need to add a flag to argument to indicate this lt = new LocalTemporary (a.Type); lt.Store (ec); dups.Add (new Argument (lt, a.ArgType)); } } if (dups != null) return new Arguments (dups); return null; } public virtual void FlowAnalysis (FlowAnalysisContext fc, List movable = null) { bool has_out = false; foreach (var arg in args) { if (arg.ArgType == Argument.AType.Out) { has_out = true; continue; } if (movable == null) { arg.FlowAnalysis (fc); continue; } var ma = arg as MovableArgument; if (ma != null && !movable.Contains (ma)) arg.FlowAnalysis (fc); } if (!has_out) return; foreach (var arg in args) { if (arg.ArgType != Argument.AType.Out) continue; arg.FlowAnalysis (fc); } } public List.Enumerator GetEnumerator () { return args.GetEnumerator (); } // // At least one argument is of dynamic type // public bool HasDynamic { get { foreach (Argument a in args) { if (a.Type.BuiltinType == BuiltinTypeSpec.Type.Dynamic && !a.IsByRef) return true; } return false; } } // // At least one argument is named argument // public bool HasNamed { get { foreach (Argument a in args) { if (a is NamedArgument) return true; } return false; } } public void Insert (int index, Argument arg) { args.Insert (index, arg); } public static System.Linq.Expressions.Expression[] MakeExpression (Arguments args, BuilderContext ctx) { if (args == null || args.Count == 0) return null; var exprs = new System.Linq.Expressions.Expression [args.Count]; for (int i = 0; i < exprs.Length; ++i) { Argument a = args.args [i]; exprs[i] = a.Expr.MakeExpression (ctx); } return exprs; } // // For named arguments when the order of execution is different // to order of invocation // public Arguments MarkOrderedArgument (NamedArgument a) { // // An expression has no effect on left-to-right execution // if (a.Expr.IsSideEffectFree) return this; ArgumentsOrdered ra = this as ArgumentsOrdered; if (ra == null) { ra = new ArgumentsOrdered (this); for (int i = 0; i < args.Count; ++i) { var la = args [i]; if (la == a) break; // // When the argument is filled later by default expression // if (la == null) continue; var ma = la as MovableArgument; if (ma == null) { ma = new MovableArgument (la); ra.args[i] = ma; } ra.AddOrdered (ma); } } ra.AddOrdered (a); return ra; } // // Returns dynamic when at least one argument is of dynamic type // public void Resolve (ResolveContext ec, out bool dynamic) { dynamic = false; foreach (Argument a in args) { a.Resolve (ec); if (a.Type.BuiltinType == BuiltinTypeSpec.Type.Dynamic && !a.IsByRef) dynamic = true; } } public void RemoveAt (int index) { args.RemoveAt (index); } public Argument this [int index] { get { return args [index]; } set { args [index] = value; } } } }