/* **************************************************************************** * * Copyright (c) Microsoft Corporation. * * This source code is subject to terms and conditions of the Microsoft Public License. A * copy of the license can be found in the License.html file at the root of this distribution. If * you cannot locate the Microsoft Public License, please send an email to * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound * by the terms of the Microsoft Public License. * * You must not remove this notice, or any other, from this software. * * * ***************************************************************************/ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using System.Dynamic; using Microsoft.Scripting.Generation; using Microsoft.Scripting.Runtime; using Microsoft.Scripting.Utils; using Microsoft.Scripting.Actions.Calls; namespace Microsoft.Scripting.Actions { using Ast = System.Linq.Expressions.Expression; public partial class DefaultBinder : ActionBinder { /// /// Provides default binding for performing a call on the specified meta objects. /// /// The signature describing the call /// The object to be called /// /// Additional meta objects are the parameters for the call as specified by the CallSignature in the CallAction. /// /// A MetaObject representing the call or the error. public MetaObject Call(CallSignature signature, MetaObject target, params MetaObject[] args) { return Call(signature, new ParameterBinder(this), target, args); } /// /// Provides default binding for performing a call on the specified meta objects. /// /// The signature describing the call /// The meta object to be called. /// /// Additional meta objects are the parameters for the call as specified by the CallSignature in the CallAction. /// /// ParameterBinder used to map arguments to parameters. /// A MetaObject representing the call or the error. public MetaObject Call(CallSignature signature, ParameterBinder parameterBinder, MetaObject target, params MetaObject[] args) { ContractUtils.RequiresNotNullItems(args, "args"); ContractUtils.RequiresNotNull(parameterBinder, "parameterBinder"); TargetInfo targetInfo = GetTargetInfo(signature, target, args); if (targetInfo != null) { // we're calling a well-known MethodBase return MakeMetaMethodCall(signature, parameterBinder, targetInfo); } else { // we can't call this object return MakeCannotCallRule(target, target.LimitType); } } #region Method Call Rule private MetaObject MakeMetaMethodCall(CallSignature signature, ParameterBinder parameterBinder, TargetInfo targetInfo) { Restrictions restrictions = Restrictions.Combine(targetInfo.Arguments).Merge(targetInfo.Restrictions); if (targetInfo.Instance != null) { restrictions = targetInfo.Instance.Restrictions.Merge(restrictions); } if (targetInfo.Instance != null) { return CallInstanceMethod( parameterBinder, targetInfo.Targets, targetInfo.Instance, targetInfo.Arguments, signature, restrictions ); } return CallMethod( parameterBinder, targetInfo.Targets, targetInfo.Arguments, signature, restrictions); } #endregion #region Target acquisition /// /// Gets a TargetInfo object for performing a call on this object. /// /// If this object is a delegate we bind to the Invoke method. /// If this object is a MemberGroup or MethodGroup we bind to the methods in the member group. /// If this object is a BoundMemberTracker we bind to the methods with the bound instance. /// If the underlying type has defined an operator Call method we'll bind to that method. /// private TargetInfo GetTargetInfo(CallSignature signature, MetaObject target, MetaObject[] args) { Debug.Assert(target.HasValue); object objTarget = target.Value; return TryGetDelegateTargets(target, args, objTarget as Delegate) ?? TryGetMemberGroupTargets(target, args, objTarget as MemberGroup) ?? TryGetMethodGroupTargets(target, args, objTarget as MethodGroup) ?? TryGetBoundMemberTargets(target, args, objTarget as BoundMemberTracker) ?? TryGetOperatorTargets(target, args, target, signature); } /// /// Binds to the methods in a method group. /// private static TargetInfo TryGetMethodGroupTargets(MetaObject target, MetaObject[] args, MethodGroup mthgrp) { if (mthgrp != null) { List foundTargets = new List(); foreach (MethodTracker mt in mthgrp.Methods) { foundTargets.Add(mt.Method); } return new TargetInfo(null, ArrayUtils.Insert(target, args), Restrictions.GetInstanceRestriction(target.Expression, mthgrp), foundTargets.ToArray()); } return null; } /// /// Binds to the methods in a member group. /// /// TODO: We should really only have either MemberGroup or MethodGroup, not both. /// private static TargetInfo TryGetMemberGroupTargets(MetaObject target, MetaObject[] args, MemberGroup mg) { if (mg != null) { MethodBase[] targets; List foundTargets = new List(); foreach (MemberTracker mt in mg) { if (mt.MemberType == TrackerTypes.Method) { foundTargets.Add(((MethodTracker)mt).Method); } } targets = foundTargets.ToArray(); return new TargetInfo(null, ArrayUtils.Insert(target, args), targets); } return null; } /// /// Binds to the BoundMemberTracker and uses the instance in the tracker and restricts /// based upon the object instance type. /// private TargetInfo TryGetBoundMemberTargets(MetaObject self, MetaObject[] args, BoundMemberTracker bmt) { if (bmt != null) { Debug.Assert(bmt.Instance == null); // should be null for trackers that leak to user code MethodBase[] targets; // instance is pulled from the BoundMemberTracker and restricted to the correct // type. MetaObject instance = new MetaObject( Ast.Convert( Ast.Property( Ast.Convert(self.Expression, typeof(BoundMemberTracker)), typeof(BoundMemberTracker).GetProperty("ObjectInstance") ), bmt.BoundTo.DeclaringType ), self.Restrictions ).Restrict(CompilerHelpers.GetType(bmt.ObjectInstance)); // we also add a restriction to make sure we're going to the same BoundMemberTracker Restrictions restrictions = Restrictions.GetExpressionRestriction( Ast.Equal( Ast.Property( Ast.Convert(self.Expression, typeof(BoundMemberTracker)), typeof(BoundMemberTracker).GetProperty("BoundTo") ), Ast.Constant(bmt.BoundTo) ) ); switch (bmt.BoundTo.MemberType) { case TrackerTypes.MethodGroup: targets = ((MethodGroup)bmt.BoundTo).GetMethodBases(); break; case TrackerTypes.Method: targets = new MethodBase[] { ((MethodTracker)bmt.BoundTo).Method }; break; default: throw new InvalidOperationException(); // nothing else binds yet } return new TargetInfo(instance, args, restrictions, targets); } return null; } /// /// Binds to the Invoke method on a delegate if this is a delegate type. /// private static TargetInfo TryGetDelegateTargets(MetaObject target, MetaObject[] args, Delegate d) { if (d != null) { return new TargetInfo(target, args, d.GetType().GetMethod("Invoke")); } return null; } /// /// Attempts to bind to an operator Call method. /// private TargetInfo TryGetOperatorTargets(MetaObject self, MetaObject[] args, object target, CallSignature signature) { MethodBase[] targets; Type targetType = CompilerHelpers.GetType(target); MemberGroup callMembers = GetMember(OldCallAction.Make(this, signature), targetType, "Call"); List callTargets = new List(); foreach (MemberTracker mi in callMembers) { if (mi.MemberType == TrackerTypes.Method) { MethodInfo method = ((MethodTracker)mi).Method; if (method.IsSpecialName) { callTargets.Add(method); } } } Expression instance = null; if (callTargets.Count > 0) { targets = callTargets.ToArray(); instance = Ast.Convert(self.Expression, CompilerHelpers.GetType(target)); return new TargetInfo(null, ArrayUtils.Insert(self, args), targets); } return null; } #endregion #region Error support private MetaObject MakeCannotCallRule(MetaObject self, Type type) { return MakeError( ErrorInfo.FromException( Ast.New( typeof(ArgumentTypeException).GetConstructor(new Type[] { typeof(string) }), Ast.Constant(type.Name + " is not callable") ) ), self.Restrictions.Merge(Restrictions.GetTypeRestriction(self.Expression, type)) ); } #endregion /// /// Encapsulates information about the target of the call. This includes an implicit instance for the call, /// the methods that we'll be calling as well as any restrictions required to perform the call. /// class TargetInfo { public readonly MetaObject Instance; public readonly MetaObject[] Arguments; public readonly MethodBase[] Targets; public readonly Restrictions Restrictions; public TargetInfo(MetaObject instance, MetaObject[] arguments, params MethodBase[] args) : this(instance, arguments, Restrictions.Empty, args) { } public TargetInfo(MetaObject instance, MetaObject[] arguments, Restrictions restrictions, params MethodBase[] targets) { Assert.NotNullItems(targets); Assert.NotNull(restrictions); Instance = instance; Arguments = arguments; Targets = targets; Restrictions = restrictions; } } } }