diff --git a/README.md b/README.md index c6c3c07e..edb9e536 100755 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Features: * Runs on IL2CPP converted code * No external dependencies, implemented in as few targets as possible * Easy and performant interop with CLR objects, with runtime code generation where supported -* Interop with methods, extension methods, overloads, fields, properties and indexers supported +* Interop with methods, generic methods, extension methods, overloads, fields, properties and indexers supported * Support for the complete Lua standard library with very few exceptions (mostly located on the 'debug' module) and a few extensions (in the string library, mostly) * Async method support * Supports dumping/loading bytecode for obfuscation and quicker parsing at runtime diff --git a/src/MoonSharp.Interpreter.Tests/EndToEnd/CoroutineTests.cs b/src/MoonSharp.Interpreter.Tests/EndToEnd/CoroutineTests.cs index c8402487..5d9f691f 100644 --- a/src/MoonSharp.Interpreter.Tests/EndToEnd/CoroutineTests.cs +++ b/src/MoonSharp.Interpreter.Tests/EndToEnd/CoroutineTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using MoonSharp.Interpreter.Interop; using NUnit.Framework; namespace MoonSharp.Interpreter.Tests.EndToEnd @@ -9,6 +10,48 @@ namespace MoonSharp.Interpreter.Tests.EndToEnd [TestFixture] class CoroutineTests { + private class YieldingIndexTarget + { + } + + private class YieldingIndexDescriptor : IUserDataDescriptor + { + public string Name + { + get { return "YieldingIndexTarget"; } + } + + public Type Type + { + get { return typeof(YieldingIndexTarget); } + } + + public DynValue Index(Script script, object obj, DynValue index, bool isDirectIndexing) + { + return DynValue.NewYieldReq(new[] { DynValue.NewString("yielded:" + index.String) }); + } + + public bool SetIndex(Script script, object obj, DynValue index, DynValue value, bool isDirectIndexing) + { + return false; + } + + public string AsString(object obj) + { + return null; + } + + public DynValue MetaIndex(Script script, object obj, string metaname) + { + return null; + } + + public bool IsTypeCompatible(Type type, object obj) + { + return type.IsInstanceOfType(obj); + } + } + [Test] public void Coroutine_Basic() { @@ -202,6 +245,38 @@ public void Coroutine_Direct_Resume() Assert.AreEqual("1234567", ret); } + [Test] + public void Coroutine_UserDataIndexCanYield() + { + string code = @" + return function(target) + state = 'before' + local value = target.value + state = 'after' + return value + end + "; + + Script script = new Script(); + DynValue function = script.DoString(code); + DynValue coroutine = script.CreateCoroutine(function); + DynValue target = UserData.Create(new YieldingIndexTarget(), new YieldingIndexDescriptor()); + + DynValue yielded = coroutine.Coroutine.Resume(target); + + Assert.AreEqual(CoroutineState.Suspended, coroutine.Coroutine.State); + Assert.AreEqual(DataType.String, yielded.Type); + Assert.AreEqual("yielded:value", yielded.String); + Assert.AreEqual("before", script.Globals.Get("state").String); + + DynValue returned = coroutine.Coroutine.Resume(DynValue.NewString("resumed")); + + Assert.AreEqual(CoroutineState.Dead, coroutine.Coroutine.State); + Assert.AreEqual(DataType.String, returned.Type); + Assert.AreEqual("resumed", returned.String); + Assert.AreEqual("after", script.Globals.Get("state").String); + } + [Test] public void Coroutine_Direct_AsEnumerable() diff --git a/src/MoonSharp.Interpreter.Tests/EndToEnd/UserDataMethodsTests.cs b/src/MoonSharp.Interpreter.Tests/EndToEnd/UserDataMethodsTests.cs index 09d4ebfc..73940aee 100755 --- a/src/MoonSharp.Interpreter.Tests/EndToEnd/UserDataMethodsTests.cs +++ b/src/MoonSharp.Interpreter.Tests/EndToEnd/UserDataMethodsTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Text; using MoonSharp.Interpreter.Compatibility; using MoonSharp.Interpreter.Interop; @@ -218,6 +219,31 @@ public static StringBuilder ConcatS(int p1, string p2, IComparable p3, bool p4, return p7; } + public static string GenericStaticDescribe(T value) + { + return typeof(T).Name + ":" + value; + } + + public static string GenericStaticDescribeTypes() + { + return typeof(T1).Name + "+" + typeof(T2).Name; + } + + public static string GenericStaticDescribeParams(params T[] values) + { + return typeof(T).Name + ":" + string.Join(",", values.Select(v => v == null ? "" : v.ToString()).ToArray()); + } + + public static string GenericOverloadStealing(string value) + { + return "nongeneric:" + value; + } + + public static string GenericOverloadStealing(T value) + { + return "generic:" + typeof(T).Name + ":" + value; + } + public string Format(string s, params object[] args) { return string.Format(s, args); @@ -230,6 +256,16 @@ public StringBuilder ConcatI(Script s, int p1, string p2, IComparable p3, bool p return ConcatS(p1, p2, p3, p4, p5, p6, p7, p8, this, p10); } + public string GenericInstanceDescribe(T value) + { + return ToString() + ":" + typeof(T).Name + ":" + value; + } + + public string GenericInstanceTypeName() + { + return ToString() + ":" + typeof(T).Name; + } + public override string ToString() { return "!SOMECLASS!"; @@ -250,6 +286,21 @@ public int CompareTo(object obj) } } + public class GenericMethodPayload + { + private readonly string m_Value; + + public GenericMethodPayload(string value) + { + m_Value = value; + } + + public override string ToString() + { + return m_Value; + } + } + public interface Interface1 { string Test1(); @@ -661,6 +712,130 @@ public void Test_DelegateMethod(InteropAccessMode opt) Assert.AreEqual("1%2", res.String); } + [Test] + public void Interop_GenericMethods() + { + Script S = new Script(); + SomeClass obj = new SomeClass(); + + UserData.UnregisterType(); + UserData.UnregisterType(); + UserData.UnregisterType(); + UserData.UnregisterType(); + UserData.RegisterType(); + UserData.RegisterType(); + UserData.RegisterType(); + UserData.RegisterType(); + + S.Globals.Set("static", UserData.CreateStatic()); + S.Globals.Set("obj", UserData.Create(obj)); + S.Globals["payloadType"] = typeof(GenericMethodPayload); + S.Globals["otherPayloadType"] = typeof(SomeOtherClass); + S.Globals["intType"] = typeof(int); + S.Globals.Set("payload", UserData.Create(new GenericMethodPayload("value"))); + + DynValue res = S.DoString(@" + a = static.GenericStaticDescribe(payloadType, payload); + b = obj:GenericInstanceDescribe(payloadType, payload); + c = static.GenericStaticDescribeTypes(payloadType, otherPayloadType); + return a .. '|' .. b .. '|' .. c;"); + + Assert.AreEqual(DataType.String, res.Type); + Assert.AreEqual("GenericMethodPayload:value|!SOMECLASS!:GenericMethodPayload:value|GenericMethodPayload+SomeOtherClass", res.String); + + res = S.DoString("return obj.GenericInstanceTypeName(payloadType);"); + + Assert.AreEqual(DataType.String, res.Type); + Assert.AreEqual("!SOMECLASS!:GenericMethodPayload", res.String); + + res = S.DoString("return static.GenericStaticDescribe(intType, 42);"); + + Assert.AreEqual(DataType.String, res.Type); + Assert.AreEqual("Int32:42", res.String); + } + + [Test] + public void Interop_GenericMethodDescriptor_InstanceDotCall_UsesFirstArgumentAsType() + { + Script S = new Script(); + + UserData.UnregisterType(); + UserData.UnregisterType(); + UserData.RegisterType(); + UserData.RegisterType(); + + MethodInfo mi = typeof(SomeClass).GetMethod("GenericInstanceTypeName"); + GenericMethodMemberDescriptor descriptor = new GenericMethodMemberDescriptor(mi); + CallbackArguments args = new CallbackArguments(new[] { UserData.CreateStatic() }, false); + + DynValue res = descriptor.Execute(S, new SomeClass(), null, args); + + Assert.AreEqual(DataType.String, res.Type); + Assert.AreEqual("!SOMECLASS!:GenericMethodPayload", res.String); + } + + [Test] + public void Interop_GenericMethodDescriptor_UsesConstructedParameterTypes() + { + Script S = new Script(); + + UserData.UnregisterType(); + UserData.UnregisterType(); + UserData.RegisterType(); + UserData.RegisterType(); + + MethodInfo mi = typeof(SomeClass).GetMethod("GenericStaticDescribe"); + GenericMethodMemberDescriptor descriptor = new GenericMethodMemberDescriptor(mi); + CallbackArguments args = new CallbackArguments(new[] { UserData.CreateStatic(), DynValue.NewNumber(42) }, false); + + DynValue res = descriptor.Execute(S, null, null, args); + + Assert.AreEqual(DataType.String, res.Type); + Assert.AreEqual("Int32:42", res.String); + } + + [Test] + public void Interop_GenericMethodDescriptor_PacksConstructedParamsArguments() + { + Script S = new Script(); + + UserData.UnregisterType(); + UserData.UnregisterType(); + UserData.RegisterType(); + UserData.RegisterType(); + + MethodInfo mi = typeof(SomeClass).GetMethod("GenericStaticDescribeParams"); + GenericMethodMemberDescriptor descriptor = new GenericMethodMemberDescriptor(mi); + CallbackArguments args = new CallbackArguments(new[] + { + UserData.CreateStatic(), + DynValue.NewNumber(1), + DynValue.NewNumber(2), + DynValue.NewNumber(3) + }, false); + + DynValue res = descriptor.Execute(S, null, null, args); + + Assert.AreEqual(DataType.String, res.Type); + Assert.AreEqual("Int32:1,2,3", res.String); + } + + [Test] + public void Interop_GenericMethodOverload_DoesNotStealNonGenericCall() + { + Script S = new Script(); + + UserData.UnregisterType(); + UserData.RegisterType(); + + S.Globals.Set("static", UserData.CreateStatic()); + + DynValue res = S.DoString("return static.GenericOverloadStealing('value');"); + + Assert.AreEqual(DataType.String, res.Type); + Assert.AreEqual("nongeneric:value", res.String); + } + public void Test_ListMethod(InteropAccessMode opt) { UserData.UnregisterType(); diff --git a/src/MoonSharp.Interpreter.Tests/EndToEnd/UserDataPropertiesTests.cs b/src/MoonSharp.Interpreter.Tests/EndToEnd/UserDataPropertiesTests.cs index 7a84aa72..3cc96653 100644 --- a/src/MoonSharp.Interpreter.Tests/EndToEnd/UserDataPropertiesTests.cs +++ b/src/MoonSharp.Interpreter.Tests/EndToEnd/UserDataPropertiesTests.cs @@ -48,6 +48,24 @@ public static IEnumerable Numbers } } + public class ConstructorCallClass + { + public ConstructorCallClass() + { + OptionalProp = 77; + } + + public ConstructorCallClass(int intProp) + : this() + { + IntProp = intProp; + } + + public int IntProp { get; set; } + public int OptionalProp { get; set; } + public string Name { get; set; } + } + public void Test_IntPropertyGetter(InteropAccessMode opt) { string script = @" @@ -852,6 +870,45 @@ public void Interop_IntPropertySetterWithSimplifiedSyntax() Assert.AreEqual(19, obj.IntProp); } + [Test] + public void Interop_TypeCallInvokesConstructor() + { + Script S = new Script(); + + UserData.UnregisterType(); + UserData.RegisterType(); + + S.Globals["mytype"] = typeof(ConstructorCallClass); + + DynValue res = S.DoString(@" + local obj = mytype(12); + return obj.IntProp, obj.OptionalProp;"); + + Assert.AreEqual(DataType.Tuple, res.Type); + Assert.AreEqual(12, res.Tuple[0].Number); + Assert.AreEqual(77, res.Tuple[1].Number); + } + + [Test] + public void Interop_TypeCallTableInitializerSetsProperties() + { + Script S = new Script(); + + UserData.UnregisterType(); + UserData.RegisterType(); + + S.Globals["mytype"] = typeof(ConstructorCallClass); + + DynValue res = S.DoString(@" + local obj = mytype{ IntProp = 12, OptionalProp = nil, Name = 'ok' }; + return obj.IntProp, obj.OptionalProp, obj.Name;"); + + Assert.AreEqual(DataType.Tuple, res.Type); + Assert.AreEqual(12, res.Tuple[0].Number); + Assert.AreEqual(77, res.Tuple[1].Number); + Assert.AreEqual("ok", res.Tuple[2].String); + } + [Test] public void Interop_OutOfRangeNumber() { diff --git a/src/MoonSharp.Interpreter.Tests/Units/InteropTests.cs b/src/MoonSharp.Interpreter.Tests/Units/InteropTests.cs index 2782788c..4e90ac99 100644 --- a/src/MoonSharp.Interpreter.Tests/Units/InteropTests.cs +++ b/src/MoonSharp.Interpreter.Tests/Units/InteropTests.cs @@ -26,5 +26,42 @@ public void Converter_FromObject() } + [Test] + public void Converter_ClrToScriptGenericTypeDefinition() + { + try + { + Script.GlobalOptions.CustomConverters.Clear(); + Script.GlobalOptions.CustomConverters.SetClrToScriptCustomConversion( + typeof(GenericConversionTarget<>), + (_s, obj) => DynValue.NewString(obj.GetType().GetGenericArguments()[0].Name + ":" + ((IGenericConversionTarget)obj).Value)); + + Script script = new Script(); + DynValue value = DynValue.FromObject(script, new GenericConversionTarget("ok")); + + Assert.AreEqual(DataType.String, value.Type); + Assert.AreEqual("Int32:ok", value.String); + } + finally + { + Script.GlobalOptions.CustomConverters.Clear(); + } + } + + private interface IGenericConversionTarget + { + string Value { get; } + } + + private class GenericConversionTarget : IGenericConversionTarget + { + public GenericConversionTarget(string value) + { + Value = value; + } + + public string Value { get; private set; } + } + } } diff --git a/src/MoonSharp.Interpreter/Execution/VM/Processor/DebugContext.cs b/src/MoonSharp.Interpreter/Execution/VM/Processor/DebugContext.cs index eabe0f9d..a62344f2 100644 --- a/src/MoonSharp.Interpreter/Execution/VM/Processor/DebugContext.cs +++ b/src/MoonSharp.Interpreter/Execution/VM/Processor/DebugContext.cs @@ -9,7 +9,7 @@ private class DebugContext { public bool DebuggerEnabled = true; public IDebugger DebuggerAttached = null; - public DebuggerAction.ActionType DebuggerCurrentAction = DebuggerAction.ActionType.None; + public DebuggerAction.ActionType DebuggerCurrentAction = DebuggerAction.ActionType.Run; public int DebuggerCurrentActionTarget = -1; public SourceRef LastHlRef = null; public int ExStackDepthAtStep = -1; diff --git a/src/MoonSharp.Interpreter/Execution/VM/Processor/Processor_InstructionLoop.cs b/src/MoonSharp.Interpreter/Execution/VM/Processor/Processor_InstructionLoop.cs index 91f4ba38..236b02c5 100644 --- a/src/MoonSharp.Interpreter/Execution/VM/Processor/Processor_InstructionLoop.cs +++ b/src/MoonSharp.Interpreter/Execution/VM/Processor/Processor_InstructionLoop.cs @@ -829,8 +829,11 @@ private int ExecRet(Instruction i) } if (csi.Continuation != null) + { m_ValueStack.Push(csi.Continuation.Invoke(new ScriptExecutionContext(this, csi.Continuation, i.SourceCodeRef), new DynValue[1] { m_ValueStack.Pop() })); + retpoint = Internal_CheckForTailRequests(i, retpoint); + } return retpoint; } @@ -1347,7 +1350,7 @@ private int ExecIndex(Instruction i, int instructionPtr) } m_ValueStack.Push(v.AsReadOnly()); - return instructionPtr; + return Internal_CheckForTailRequests(i, instructionPtr); } else { diff --git a/src/MoonSharp.Interpreter/Interop/BasicDescriptors/DispatchingUserDataDescriptor.cs b/src/MoonSharp.Interpreter/Interop/BasicDescriptors/DispatchingUserDataDescriptor.cs index 32975fe3..eae8cbb0 100644 --- a/src/MoonSharp.Interpreter/Interop/BasicDescriptors/DispatchingUserDataDescriptor.cs +++ b/src/MoonSharp.Interpreter/Interop/BasicDescriptors/DispatchingUserDataDescriptor.cs @@ -528,6 +528,8 @@ public virtual DynValue MetaIndex(Script script, object obj, string metaname) return MultiDispatchLessThanOrEqual(script, obj); case "__len": return TryDispatchLength(script, obj); + case "__call": + return TryDispatchStaticConstructor(script, obj); case "__tonumber": return TryDispatchToNumber(script, obj); case "__tobool": @@ -645,6 +647,55 @@ private DynValue TryDispatchToNumber(Script script, object obj) } + private DynValue TryDispatchStaticConstructor(Script script, object obj) + { + if (obj != null) + return null; + + IMemberDescriptor ctor = m_Members.GetOrDefault("__new").WithAccessOrNull(MemberDescriptorAccess.CanExecute); + if (ctor == null) + return null; + + return DynValue.NewCallback((context, args) => + { + if (args.Count == 2 && args[1].Type == DataType.Table) + { + DynValue result = ExecuteStaticConstructor(script, ctor, context, new DynValue[0]); + AssignTableInitializer(script, result, args[1].Table); + return result; + } + + return ExecuteStaticConstructor(script, ctor, context, args.GetArray(1)); + }, "__call"); + } + + private static DynValue ExecuteStaticConstructor(Script script, IMemberDescriptor ctor, ScriptExecutionContext context, IList args) + { + DynValue callback = ctor.GetValue(script, null); + if (callback.Type != DataType.ClrFunction) + throw new ScriptRuntimeException("a clr callback was expected in member {0}, while a {1} was found", ctor.Name, callback.Type); + + return callback.Callback.ClrCallback(context, new CallbackArguments(args, false)); + } + + private static void AssignTableInitializer(Script script, DynValue target, Table initializer) + { + if (target.Type != DataType.UserData) + throw new ScriptRuntimeException("constructor table initializer expected userdata, got {0}", target.Type); + + UserData ud = target.UserData; + + foreach (TablePair pair in initializer.Pairs) + { + if (pair.Value.IsNil()) + continue; + + if (!ud.Descriptor.SetIndex(script, ud.Object, pair.Key, pair.Value, pair.Key.Type == DataType.String)) + throw ScriptRuntimeException.UserDataMissingField(ud.Descriptor.Name, pair.Key.ToPrintString()); + } + } + + private DynValue TryDispatchToBool(Script script, object obj) { var name = typeof(bool).GetConversionMethodName(); diff --git a/src/MoonSharp.Interpreter/Interop/CustomConvertersCollection.cs b/src/MoonSharp.Interpreter/Interop/CustomConvertersCollection.cs index 70866ceb..b4e9b154 100644 --- a/src/MoonSharp.Interpreter/Interop/CustomConvertersCollection.cs +++ b/src/MoonSharp.Interpreter/Interop/CustomConvertersCollection.cs @@ -149,7 +149,17 @@ public void SetClrToScriptCustomConversion(Func converte /// The converter function, or null if not found public Func GetClrToScriptCustomConversion(Type clrDataType) { - return m_Clr2Script.GetOrDefault(clrDataType); + if (clrDataType == null) + return null; + + Func converter = m_Clr2Script.GetOrDefault(clrDataType); + if (converter != null) + return converter; + + if (clrDataType.IsGenericType) + return m_Clr2Script.GetOrDefault(clrDataType.GetGenericTypeDefinition()); + + return null; } /// Sets a custom converter from a CLR data type. Set null to remove a previous custom converter. diff --git a/src/MoonSharp.Interpreter/Interop/StandardDescriptors/ReflectionMemberDescriptors/GenericMethodMemberDescriptor.cs b/src/MoonSharp.Interpreter/Interop/StandardDescriptors/ReflectionMemberDescriptors/GenericMethodMemberDescriptor.cs new file mode 100644 index 00000000..7afec089 --- /dev/null +++ b/src/MoonSharp.Interpreter/Interop/StandardDescriptors/ReflectionMemberDescriptors/GenericMethodMemberDescriptor.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using MoonSharp.Interpreter.Compatibility; +using MoonSharp.Interpreter.Interop.BasicDescriptors; +using MoonSharp.Interpreter.Interop.Converters; + +namespace MoonSharp.Interpreter.Interop +{ + /// + /// Class providing marshalling of generic CLR functions that can be called from Lua by passing the type as the first parameter + /// + public class GenericMethodMemberDescriptor : FunctionMemberDescriptorBase + { + MethodInfo m_MethodInfo; + internal int GenericParameterCount { get; private set; } + + public GenericMethodMemberDescriptor(MethodInfo methodInfo) + { + m_MethodInfo = methodInfo; + + // Build parameters: first parameters are the generic argument types, then the actual method parameters + Type[] genericArguments = methodInfo.GetGenericArguments(); + GenericParameterCount = genericArguments.Length; + ParameterInfo[] actualParameter = methodInfo.GetParameters(); + ParameterDescriptor[] parameters = new ParameterDescriptor[genericArguments.Length + actualParameter.Length]; + + for (int i = 0; i < genericArguments.Length; i++) + parameters[i] = new ParameterDescriptor($"type{i}", typeof(Type)); + for (int i = 0; i < actualParameter.Length; i++) + { + ParameterInfo pi = actualParameter[i]; + parameters[i + genericArguments.Length] = !pi.ParameterType.ContainsGenericParameters + ? new ParameterDescriptor(pi) + : new ParameterDescriptor( + pi.Name, + typeof(object), + !Framework.Do.IsDbNull(pi.DefaultValue), + pi.DefaultValue, + pi.IsOut, + pi.ParameterType.IsByRef, + pi.ParameterType.IsArray && pi.GetCustomAttributes(typeof(ParamArrayAttribute), true).Any()); + } + + Initialize(methodInfo.Name, methodInfo.IsStatic, parameters, false); + } + + public override DynValue Execute(Script script, object obj, ScriptExecutionContext context, CallbackArguments args) + { + this.CheckAccess(MemberDescriptorAccess.CanExecute, obj); + + // Build generic type list + Type[] types = new Type[m_MethodInfo.GetGenericArguments().Length]; + int genericArgsIndex = args.IsMethodCall ? 1 : 0; + for (int i = 0; i < types.Length; i++) + { + DynValue typeArg = args.RawGet(genericArgsIndex + i, false); + if (typeArg == null || typeArg.Type != DataType.UserData) + throw new ScriptRuntimeException("Generic method {0} requires a Type as parameter {1}", m_MethodInfo.Name, i); + + types[i] = typeArg.UserData.Descriptor?.Type; + if (types[i] == null) + throw new ScriptRuntimeException("Generic method {0} requires a valid Type as parameter {1}", m_MethodInfo.Name, i); + } + + MethodInfo constructedMethod; + try + { + constructedMethod = m_MethodInfo.MakeGenericMethod(types); + } + catch (Exception ex) + { + throw new ScriptRuntimeException("Failed to construct generic method {0} with types {1}: {2}", m_MethodInfo.Name, string.Join(", ", types.Select(t => t.Name)), ex.Message); + } + + // Build argument list, skipping the type argument + List outParams = null; + ParameterInfo[] methodParams = constructedMethod.GetParameters(); + object[] pars = new object[methodParams.Length]; + int argsIndex = genericArgsIndex + types.Length; + for (int i = 0; i < pars.Length; i++) + { + if (methodParams[i].ParameterType.IsByRef) + { + if (outParams == null) + outParams = new List(); + outParams.Add(i); + } + + // fill special types (copied from base.BuildArgumentList) + if (methodParams[i].ParameterType == typeof(Script)) + pars[i] = script; + else if (methodParams[i].ParameterType == typeof(ScriptExecutionContext)) + pars[i] = context; + else if (methodParams[i].ParameterType == typeof(CallbackArguments)) + pars[i] = args.SkipMethodCall(); + else if (methodParams[i].IsOut) + pars[i] = null; + else if (i == methodParams.Length - 1 && methodParams[i].ParameterType.IsArray && methodParams[i].GetCustomAttributes(typeof(ParamArrayAttribute), true).Any()) + { + Type varArgsArrayType = methodParams[i].ParameterType; + Type varArgsElementType = varArgsArrayType.GetElementType(); + List extraArgs = new List(); + + while (true) + { + DynValue arg = args.RawGet(argsIndex, false); + argsIndex += 1; + if (arg != null) + extraArgs.Add(arg); + else + break; + } + + if (extraArgs.Count == 1) + { + DynValue arg = extraArgs[0]; + + if (arg.Type == DataType.UserData && arg.UserData.Object != null) + { + if (Framework.Do.IsAssignableFrom(varArgsArrayType, arg.UserData.Object.GetType())) + { + pars[i] = arg.UserData.Object; + continue; + } + } + } + + Array vararg = Array.CreateInstance(varArgsElementType, extraArgs.Count); + + for (int ii = 0; ii < extraArgs.Count; ii++) + { + vararg.SetValue(ScriptToClrConversions.DynValueToObjectOfType(extraArgs[ii], varArgsElementType, + null, false), ii); + } + + pars[i] = vararg; + } + else + { + DynValue arg = args.RawGet(argsIndex++, false); + if (arg != null) + pars[i] = ScriptToClrConversions.DynValueToObjectOfType(arg, methodParams[i].ParameterType, null, false); + else if (methodParams[i].HasDefaultValue) + pars[i] = methodParams[i].DefaultValue; + else + pars[i] = null; + } + } + + try + { + object retv = constructedMethod.Invoke(obj, pars); + return BuildReturnValue(script, outParams, pars, m_MethodInfo.ReturnType == typeof(void) ? DynValue.Void : retv); + } + catch (TargetInvocationException ex) + { + throw ex.InnerException ?? ex; + } + } + } +} diff --git a/src/MoonSharp.Interpreter/Interop/StandardDescriptors/ReflectionMemberDescriptors/MethodMemberDescriptor.cs b/src/MoonSharp.Interpreter/Interop/StandardDescriptors/ReflectionMemberDescriptors/MethodMemberDescriptor.cs index 1c1dde40..fde33491 100644 --- a/src/MoonSharp.Interpreter/Interop/StandardDescriptors/ReflectionMemberDescriptors/MethodMemberDescriptor.cs +++ b/src/MoonSharp.Interpreter/Interop/StandardDescriptors/ReflectionMemberDescriptors/MethodMemberDescriptor.cs @@ -109,13 +109,17 @@ public MethodMemberDescriptor(MethodBase methodBase, InteropAccessMode accessMod /// /// A new MethodMemberDescriptor or null. /// - public static MethodMemberDescriptor TryCreateIfVisible(MethodBase methodBase, InteropAccessMode accessMode, bool forceVisibility = false) + public static IMemberDescriptor TryCreateIfVisible(MethodBase methodBase, InteropAccessMode accessMode, bool forceVisibility = false) { if (!CheckMethodIsCompatible(methodBase, false)) return null; if (forceVisibility || (methodBase.GetVisibilityFromAttributes() ?? methodBase.IsPublic)) + { + if (methodBase.IsGenericMethodDefinition && methodBase is MethodInfo mi) + return new GenericMethodMemberDescriptor(mi); return new MethodMemberDescriptor(methodBase, accessMode); + } return null; } @@ -127,19 +131,10 @@ public static MethodMemberDescriptor TryCreateIfVisible(MethodBase methodBase, I /// if set to true an exception with the proper error message is thrown if not compatible. /// /// - /// Thrown if throwException is true and one of this applies: - /// The method contains unresolved generic parameters, or has an unresolved generic return type - /// or - /// The method contains pointer parameters, or has a pointer return type + /// Thrown if throwException is true and the method contains pointer parameters, or has a pointer return type /// public static bool CheckMethodIsCompatible(MethodBase methodBase, bool throwException) { - if (methodBase.ContainsGenericParameters) - { - if (throwException) throw new ArgumentException("Method cannot contain unresolved generic parameters"); - return false; - } - if (methodBase.GetParameters().Any(p => p.ParameterType.IsPointer)) { if (throwException) throw new ArgumentException("Method cannot contain pointer parameters"); diff --git a/src/MoonSharp.Interpreter/Interop/StandardDescriptors/ReflectionMemberDescriptors/OverloadedMethodMemberDescriptor.cs b/src/MoonSharp.Interpreter/Interop/StandardDescriptors/ReflectionMemberDescriptors/OverloadedMethodMemberDescriptor.cs index c1ad734c..9f7864f3 100644 --- a/src/MoonSharp.Interpreter/Interop/StandardDescriptors/ReflectionMemberDescriptors/OverloadedMethodMemberDescriptor.cs +++ b/src/MoonSharp.Interpreter/Interop/StandardDescriptors/ReflectionMemberDescriptors/OverloadedMethodMemberDescriptor.cs @@ -307,6 +307,7 @@ private int CalcScoreForOverload(ScriptExecutionContext context, CallbackArgumen int argsBase = args.IsMethodCall ? 1 : 0; int argsCnt = argsBase; bool varArgsUsed = false; + GenericMethodMemberDescriptor genericMethod = method as GenericMethodMemberDescriptor; for (int i = 0; i < method.Parameters.Length; i++) @@ -319,6 +320,18 @@ private int CalcScoreForOverload(ScriptExecutionContext context, CallbackArgumen Type parameterType = method.Parameters[i].Type; + if (genericMethod != null && i < genericMethod.GenericParameterCount) + { + DynValue arg = args.RawGet(argsCnt, false); + int score = IsGenericTypeArgument(arg) + ? ScriptToClrConversions.WEIGHT_EXACT_MATCH + : ScriptToClrConversions.WEIGHT_NO_MATCH; + + totalScore = Math.Min(totalScore, score); + argsCnt += 1; + continue; + } + if ((parameterType == typeof(Script)) || (parameterType == typeof(ScriptExecutionContext)) || (parameterType == typeof(CallbackArguments))) continue; @@ -401,6 +414,14 @@ private int CalcScoreForOverload(ScriptExecutionContext context, CallbackArgumen return totalScore; } + private static bool IsGenericTypeArgument(DynValue arg) + { + return arg != null + && arg.Type == DataType.UserData + && arg.UserData.Object == null + && arg.UserData.Descriptor != null; + } + private static int CalcScoreForSingleArgument(ParameterDescriptor desc, Type parameterType, DynValue arg, bool isOptional) { int score = ScriptToClrConversions.DynValueToObjectOfTypeWeight(arg, diff --git a/src/MoonSharp.Interpreter/Interop/StandardDescriptors/StandardUserDataDescriptor.cs b/src/MoonSharp.Interpreter/Interop/StandardDescriptors/StandardUserDataDescriptor.cs index 6eece605..882387d2 100755 --- a/src/MoonSharp.Interpreter/Interop/StandardDescriptors/StandardUserDataDescriptor.cs +++ b/src/MoonSharp.Interpreter/Interop/StandardDescriptors/StandardUserDataDescriptor.cs @@ -78,7 +78,7 @@ private void FillMemberList() { if (membersToIgnore.Contains(mi.Name)) continue; - MethodMemberDescriptor md = MethodMemberDescriptor.TryCreateIfVisible(mi, this.AccessMode); + IMemberDescriptor md = MethodMemberDescriptor.TryCreateIfVisible(mi, this.AccessMode); if (md != null) { diff --git a/src/MoonSharp.VsCodeDebugger/DebuggerLogic/ScriptDebugSession.cs b/src/MoonSharp.VsCodeDebugger/DebuggerLogic/ScriptDebugSession.cs index bc260ff8..cf0ccfa4 100644 --- a/src/MoonSharp.VsCodeDebugger/DebuggerLogic/ScriptDebugSession.cs +++ b/src/MoonSharp.VsCodeDebugger/DebuggerLogic/ScriptDebugSession.cs @@ -174,7 +174,6 @@ public int AddDebugger(AsyncDebugger debugger) debugger.Script.AttachDebugger(debugger); if (ClientConnected) { - debugger.PauseRequested = true; debugger.Client = state.ClientProxy; SendEvent(new ThreadEvent("started", threadId)); } @@ -234,7 +233,6 @@ public bool ReplaceDebugger(Script previousScript, AsyncDebugger debugger) debugger.Script.AttachDebugger(debugger); if (ClientConnected) { - debugger.PauseRequested = true; debugger.Client = state.ClientProxy; } @@ -556,7 +554,7 @@ public override void StackTrace(Response response, Table args) SourceCode sourceCode = state.Debugger.GetSource(sourceIdx); bool sourceAvailable = !sourceRef.IsClrLocation && sourceCode != null; - int sourceReference = sourceAvailable ? EncodeSourceReference(state.ThreadId, sourceIdx) : 0; + int sourceReference = sourceAvailable && (!Path.IsPathRooted(sourceFile) || !File.Exists(sourceFile)) ? EncodeSourceReference(state.ThreadId, sourceIdx) : 0; string sourcePath = sourceRef.IsClrLocation ? "(native)" : (sourceFile != null ? ConvertDebuggerPathToClient(sourceFile) : null); string sourceName = sourceRef.IsClrLocation ? sourcePath