aspclassic-core/AspClassic.VBScript/Binders/VBScriptInvokeMemberBinder.cs
Jelle Luteijn 484dbfc9d9 progress
2022-05-15 11:19:49 +02:00

215 lines
8.9 KiB
C#

using System;
using System.Collections.Generic;
//using System.Text;
using System.Reflection;
using System.Dynamic;
#if USE35
using AspClassic.Scripting.Ast;
using AspClassic.Scripting.ComInterop;
#else
using System.Linq;
using System.Linq.Expressions;
using AspClassic.Scripting.ComInterop;
#endif
using Dlrsoft.VBScript.Runtime;
using Debug = System.Diagnostics.Debug;
namespace Dlrsoft.VBScript.Binders
{
// VBScriptInvokeMemberBinder is used for general dotted expressions in function
// calls for invoking members.
//
public class VBScriptInvokeMemberBinder : InvokeMemberBinder
{
public VBScriptInvokeMemberBinder(string name, CallInfo callinfo)
: base(name, true, callinfo)
{ // true = ignoreCase
}
public override DynamicMetaObject FallbackInvokeMember(
DynamicMetaObject targetMO, DynamicMetaObject[] args,
DynamicMetaObject errorSuggestion)
{
#if !SILVERLIGHT
// First try COM binding.
DynamicMetaObject result;
if (ComBinder.TryBindInvokeMember(this, targetMO, args, out result))
{
return result;
}
#endif
// Defer if any object has no value so that we evaulate their
// Expressions and nest a CallSite for the InvokeMember.
if (!targetMO.HasValue || args.Any((a) => !a.HasValue))
{
var deferArgs = new DynamicMetaObject[args.Length + 1];
for (int i = 0; i < args.Length; i++)
{
deferArgs[i + 1] = args[i];
}
deferArgs[0] = targetMO;
return Defer(deferArgs);
}
// Find our own binding.
// Could consider allowing invoking static members from an instance.
var flags = BindingFlags.IgnoreCase | BindingFlags.Instance |
BindingFlags.Public;
var members = targetMO.LimitType.GetMember(this.Name, flags);
var restrictions = RuntimeHelpers.GetTargetArgsRestrictions(
targetMO, args, false);
// Assigned a Null
if (targetMO.HasValue && targetMO.Value == null)
{
return RuntimeHelpers.CreateThrow(targetMO,
args,
restrictions,
typeof(NullReferenceException),
"Object reference not set to an instance of an object.");
}
if ((members.Length == 1) && (members[0] is PropertyInfo ||
members[0] is FieldInfo))
{
// NEED TO TEST, should check for delegate value too
var mem = members[0];
var target = new DynamicMetaObject(
RuntimeHelpers.EnsureObjectResult(
Expression.MakeMemberAccess(
Expression.Convert(targetMO.Expression,
members[0].DeclaringType),
members[0])),
// Don't need restriction test for name since this
// rule is only used where binder is used, which is
// only used in sites with this binder.Name.
BindingRestrictions.GetTypeRestriction(targetMO.Expression,
targetMO.LimitType));
//If no arguments, to allow scenario like Request.QueryString()
if (args == null || args.Length == 0)
{
return target;
}
return new DynamicMetaObject(
RuntimeHelpers.GetIndexingExpression(target, args),
restrictions
);
// Don't test for eventinfos since we do nothing with them now.
}
else
{
bool isExtension = false;
// Get MethodInfos with right arg counts.
var mi_mems = members.
Select(m => m as MethodInfo).
Where(m => m is MethodInfo &&
((MethodInfo)m).GetParameters().Length ==
args.Length);
// Get MethodInfos with param types that work for args. This works
// except for value args that need to pass to reftype params.
// We could detect that to be smarter and then explicitly StrongBox
// the args.
List<MethodInfo> res = new List<MethodInfo>();
foreach (var mem in mi_mems)
{
if (RuntimeHelpers.ParametersMatchArguments(
mem.GetParameters(), args))
{
res.Add(mem);
}
}
List<DynamicMetaObject> argList = new List<DynamicMetaObject>(args);
//Try extension methods if no methods found
if (res.Count == 0)
{
isExtension = true;
argList.Insert(0, targetMO);
res = RuntimeHelpers.GetExtensionMethods(this.Name, targetMO, argList.ToArray());
}
// False below means generate a type restriction on the MO.
// We are looking at the members targetMO's Type.
if (res.Count == 0)
{
return errorSuggestion ??
RuntimeHelpers.CreateThrow(
targetMO, args, restrictions,
typeof(MissingMemberException),
string.Format("Can't bind member invoke {0}.{1}({2})", targetMO.RuntimeType.Name, this.Name, args.ToString()));
}
//If more than one results found, attempt overload resolution
MethodInfo mi = null;
if (res.Count > 1)
{
mi = RuntimeHelpers.ResolveOverload(res, argList.ToArray());
}
else
{
mi = res[0];
}
// restrictions and conversion must be done consistently.
var callArgs = RuntimeHelpers.ConvertArguments(
argList.ToArray(), mi.GetParameters());
if (isExtension)
{
return new DynamicMetaObject(
RuntimeHelpers.EnsureObjectResult(
Expression.Call(
null,
mi, callArgs)),
restrictions);
}
else
{
return new DynamicMetaObject(
RuntimeHelpers.EnsureObjectResult(
Expression.Call(
Expression.Convert(targetMO.Expression,
targetMO.LimitType),
mi, callArgs)),
restrictions);
}
// Could hve tried just letting Expr.Call factory do the work,
// but if there is more than one applicable method using just
// assignablefrom, Expr.Call throws. It does not pick a "most
// applicable" method or any method.
}
}
public override DynamicMetaObject FallbackInvoke(
DynamicMetaObject targetMO, DynamicMetaObject[] args,
DynamicMetaObject errorSuggestion)
{
var argexprs = new Expression[args.Length + 1];
for (int i = 0; i < args.Length; i++)
{
argexprs[i + 1] = args[i].Expression;
}
argexprs[0] = targetMO.Expression;
// Just "defer" since we have code in SymplInvokeBinder that knows
// what to do, and typically this fallback is from a language like
// Python that passes a DynamicMetaObject with HasValue == false.
return new DynamicMetaObject(
Expression.Dynamic(
// This call site doesn't share any L2 caching
// since we don't call GetInvokeBinder from Sympl.
// We aren't plumbed to get the runtime instance here.
new VBScriptInvokeBinder(new CallInfo(args.Length)),
typeof(object), // ret type
argexprs),
// No new restrictions since SymplInvokeBinder will handle it.
targetMO.Restrictions.Merge(
BindingRestrictions.Combine(args)));
}
}
}