#nullable enable
using System;
using System.Reflection;
namespace IlInterpreter.Interpreter
{
partial class ScriptInterpreter
{
///
/// The object behind a delegate whose target is an INTERPRETED method (a method group over a
/// script method, or a lambda's display-class method). The host holds an ordinary CLR delegate
/// of the exact requested type — System.Action, EventCallback<T>, a custom
/// delegate — and each invocation re-enters the VM, like does
/// for iterators.
///
/// No Reflection.Emit: the delegate is built with Delegate.CreateDelegate over a
/// shape-matched generic forwarder (V0..V4 for void, F0..F4 otherwise) closed
/// over the delegate's Invoke signature. Same AOT posture as HostBinding's typed fast paths;
/// on IL2CPP a value-typed signature relies on full generic sharing.
///
/// Lifetime: valid until the interpreter is re-Loaded or disposed — the same contract
/// as every other object that crosses the boundary.
///
internal sealed class ScriptDelegateAdapter
{
readonly ScriptInterpreter _owner;
readonly ParsedMethod _target;
readonly object? _receiver; // null for static targets; prepended as arg 0 otherwise
readonly object _generation; // the ParsedAssembly this target was lowered with
ScriptDelegateAdapter(ScriptInterpreter owner, ParsedMethod target, object? receiver)
{
_owner = owner;
_target = target;
_receiver = receiver;
_generation = owner._parsed!;
}
public static Delegate Create(
ScriptInterpreter owner, Type delegateType, ParsedMethod target, object? receiver)
{
var invoke = delegateType.GetMethod("Invoke")
?? throw new ScriptRuntimeException($"'{delegateType}' has no Invoke — not a delegate type");
var ps = invoke.GetParameters();
bool isVoid = invoke.ReturnType == typeof(void);
if (ps.Length > MaxArity)
throw new ScriptRuntimeException(
$"Delegate '{delegateType.Name}' has {ps.Length} parameters — method groups over " +
$"interpreted methods support up to {MaxArity}");
foreach (var p in ps)
if (p.ParameterType.IsByRef)
throw new ScriptRuntimeException(
$"Delegate '{delegateType.Name}' has a ref/out parameter — not supported " +
"for method groups over interpreted methods");
var adapter = new ScriptDelegateAdapter(owner, target, receiver);
var shape = typeof(ScriptDelegateAdapter).GetMethod(
(isVoid ? "V" : "F") + ps.Length, BindingFlags.Public | BindingFlags.Instance)!;
int typeArgCount = ps.Length + (isVoid ? 0 : 1);
if (typeArgCount > 0)
{
var typeArgs = new Type[typeArgCount];
for (int i = 0; i < ps.Length; i++) typeArgs[i] = ps[i].ParameterType;
if (!isVoid) typeArgs[ps.Length] = invoke.ReturnType;
shape = shape.MakeGenericMethod(typeArgs);
}
try
{
return Delegate.CreateDelegate(delegateType, adapter, shape);
}
catch (ArgumentException ex)
{
throw new ScriptRuntimeException(
$"Could not bind '{target.Name}' to delegate type '{delegateType.Name}': {ex.Message}");
}
}
const int MaxArity = 4;
object? Call(object?[] args)
{
if (_receiver == null)
return _owner.InvokeDelegateTarget(_target, args, _generation);
var withReceiver = new object?[args.Length + 1];
withReceiver[0] = _receiver;
Array.Copy(args, 0, withReceiver, 1, args.Length);
return _owner.InvokeDelegateTarget(_target, withReceiver, _generation);
}
static readonly object?[] s_NoArgs = Array.Empty