Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support converting js function into any delegate. #864

Merged
merged 1 commit into from
Apr 19, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 42 additions & 100 deletions Jint/Runtime/Interop/DefaultTypeConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,122 +78,64 @@ public virtual object Convert(object value, Type type, IFormatProvider formatPro
{
var function = (Func<JsValue, JsValue[], JsValue>)value;

if (type.IsGenericType)
if (typeof(Delegate).IsAssignableFrom(type) && !type.IsAbstract)
{
var genericType = type.GetGenericTypeDefinition();
var method = type.GetMethod("Invoke");
var arguments = method.GetParameters();

// create the requested Delegate
if (genericType.Name.StartsWith("Action"))
var @params = new ParameterExpression[arguments.Length];
for (var i = 0; i < @params.Length; i++)
{
var genericArguments = type.GetGenericArguments();
@params[i] = Expression.Parameter(arguments[i].ParameterType, arguments[i].Name);
}

var @params = new ParameterExpression[genericArguments.Length];
for (var i = 0; i < @params.Length; i++)
var initializers = new MethodCallExpression[@params.Length];
for (int i = 0; i < @params.Length; i++)
{
var param = @params[i];
if (param.Type.IsValueType)
{
@params[i] = Expression.Parameter(genericArguments[i], genericArguments[i].Name + i);
var boxing = Expression.Convert(param, objectType);
initializers[i] = Expression.Call(null, jsValueFromObject, Expression.Constant(_engine, engineType), boxing);
}
var tmpVars = new Expression[@params.Length];
for (var i = 0; i < @params.Length; i++)
else
{
var param = @params[i];
if (param.Type.IsValueType)
{
var boxing = Expression.Convert(param, objectType);
tmpVars[i] = Expression.Call(null, jsValueFromObject, Expression.Constant(_engine, engineType), boxing);
}
else
{
tmpVars[i] = Expression.Call(null, jsValueFromObject, Expression.Constant(_engine, engineType), param);
}
initializers[i] = Expression.Call(null, jsValueFromObject, Expression.Constant(_engine, engineType), param);
}
var @vars = Expression.NewArrayInit(jsValueType, tmpVars);

var callExpresion = Expression.Block(Expression.Call(
Expression.Call(Expression.Constant(function.Target),
function.Method,
Expression.Constant(JsValue.Undefined, jsValueType),
@vars),
jsValueToObject), Expression.Empty());

return Expression.Lambda(callExpresion, new ReadOnlyCollection<ParameterExpression>(@params)).Compile();
}
else if (genericType.Name.StartsWith("Func"))
{
var genericArguments = type.GetGenericArguments();
var returnType = genericArguments[genericArguments.Length - 1];

var @params = new ParameterExpression[genericArguments.Length - 1];
for (var i = 0; i < @params.Length; i++)
{
@params[i] = Expression.Parameter(genericArguments[i], genericArguments[i].Name + i);
}
var @vars = Expression.NewArrayInit(jsValueType, initializers);

var initializers = new MethodCallExpression[@params.Length];
for (int i = 0; i < @params.Length; i++)
{
var boxingExpression = Expression.Convert(@params[i], objectType);
initializers[i]= Expression.Call(null, jsValueFromObject, Expression.Constant(_engine, engineType), boxingExpression);
}
var @vars = Expression.NewArrayInit(jsValueType, initializers);

// the final result's type needs to be changed before casting,
// for instance when a function returns a number (double) but C# expects an integer

var callExpresion = Expression.Convert(
Expression.Call(null,
convertChangeType,
Expression.Call(
Expression.Call(Expression.Constant(function.Target),
function.Method,
Expression.Constant(JsValue.Undefined, jsValueType),
@vars),
jsValueToObject),
Expression.Constant(returnType, typeType),
Expression.Constant(System.Globalization.CultureInfo.InvariantCulture, typeof(IFormatProvider))
),
returnType);

return Expression.Lambda(callExpresion, new ReadOnlyCollection<ParameterExpression>(@params)).Compile();
}
}
else
{
if (type == typeof(Action))
var callExpression = Expression.Call(
Expression.Constant(function.Target),
function.Method,
Expression.Constant(JsValue.Undefined, jsValueType),
@vars);

if (method.ReturnType != typeof(void))
{
return (Action)(() => function(JsValue.Undefined, System.Array.Empty<JsValue>()));
return Expression.Lambda(
type,
Expression.Convert(
Expression.Call(
null,
convertChangeType,
Expression.Call(callExpression, jsValueToObject),
Expression.Constant(method.ReturnType),
Expression.Constant(System.Globalization.CultureInfo.InvariantCulture, typeof(IFormatProvider))
),
method.ReturnType
),
new ReadOnlyCollection<ParameterExpression>(@params)).Compile();
}
else if (typeof(MulticastDelegate).IsAssignableFrom(type))
else
{
var method = type.GetMethod("Invoke");
var arguments = method.GetParameters();

var @params = new ParameterExpression[arguments.Length];
for (var i = 0; i < @params.Length; i++)
{
@params[i] = Expression.Parameter(objectType, arguments[i].Name);
}

var initializers = new MethodCallExpression[@params.Length];
for (int i = 0; i < @params.Length; i++)
{
initializers[i] = Expression.Call(null, jsValueType.GetMethod("FromObject"), Expression.Constant(_engine, engineType), @params[i]);
}

var @vars = Expression.NewArrayInit(jsValueType, initializers);

var callExpression = Expression.Call(
Expression.Call(Expression.Constant(function.Target),
function.Method,
Expression.Constant(JsValue.Undefined, jsValueType),
@vars),
jsValueType.GetMethod("ToObject"));

var dynamicExpression = Expression.Invoke(Expression.Lambda(callExpression, new ReadOnlyCollection<ParameterExpression>(@params)), new ReadOnlyCollection<ParameterExpression>(@params));

return Expression.Lambda(type, dynamicExpression, new ReadOnlyCollection<ParameterExpression>(@params)).Compile();
return Expression.Lambda(
type,
callExpression,
new ReadOnlyCollection<ParameterExpression>(@params)).Compile();
}
}

}

if (type.IsArray)
Expand Down