Browse Source

Fix: metamethod call for TForCall

Akeit0 8 months ago
parent
commit
4a99a0b76e
2 changed files with 65 additions and 9 deletions
  1. 37 9
      src/Lua/Runtime/LuaVirtualMachine.cs
  2. 28 0
      tests/Lua.Tests/MetatableTests.cs

+ 37 - 9
src/Lua/Runtime/LuaVirtualMachine.cs

@@ -925,7 +925,7 @@ public static partial class LuaVirtualMachine
                 }
             }
 
-        End:
+            End:
             postOperation = PostOperationType.None;
             LuaValueArrayPool.Return1024(context.ResultsBuffer);
             return false;
@@ -1201,18 +1201,46 @@ public static partial class LuaVirtualMachine
         var instruction = context.Instruction;
         var stack = context.Stack;
         var RA = instruction.A + context.FrameBase;
-
+        bool isMetamethod = false;
         var iteratorRaw = stack.Get(RA);
         if (!iteratorRaw.TryReadFunction(out var iterator))
         {
-            LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(ref context), "call", iteratorRaw);
+            if (iteratorRaw.TryGetMetamethod(context.State, Metamethods.Call, out var metamethod) &&
+                metamethod.TryReadFunction(out iterator))
+            {
+                isMetamethod = true;
+            }
+            else
+            {
+                LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(ref context), "call", metamethod);
+            }
         }
 
         var newBase = RA + 3 + instruction.C;
-        stack.Get(newBase) = stack.Get(RA + 1);
-        stack.Get(newBase + 1) = stack.Get(RA + 2);
-        stack.NotifyTop(newBase + 2);
-        var newFrame = iterator.CreateNewFrame(ref context, newBase);
+
+        if (isMetamethod)
+        {
+            stack.Get(newBase) = iteratorRaw;
+            stack.Get(newBase + 1) = stack.Get(RA + 1);
+            stack.Get(newBase + 2) = stack.Get(RA + 2);
+            stack.NotifyTop(newBase + 3);
+        }
+        else
+        {
+            stack.Get(newBase) = stack.Get(RA + 1);
+            stack.Get(newBase + 1) = stack.Get(RA + 2);
+            stack.NotifyTop(newBase + 2);
+        }
+
+        var argumentCount = isMetamethod ? 3 : 2;
+        var variableArgumentCount = iterator.GetVariableArgumentCount(argumentCount);
+        if (variableArgumentCount != 0)
+        {
+            PrepareVariableArgument(stack, newBase, argumentCount, variableArgumentCount);
+            newBase += variableArgumentCount;
+        }
+
+        var newFrame = iterator.CreateNewFrame(ref context, newBase, variableArgumentCount);
         context.Thread.PushCallStackFrame(newFrame);
         if (iterator is Closure)
         {
@@ -1336,7 +1364,7 @@ public static partial class LuaVirtualMachine
             }
 
             table = metatableValue;
-        Function:
+            Function:
             if (table.TryReadFunction(out var function))
             {
                 return CallGetTableFunc(targetTable, function, key, ref context, out value, out doRestart);
@@ -1425,7 +1453,7 @@ public static partial class LuaVirtualMachine
 
             table = metatableValue;
 
-        Function:
+            Function:
             if (table.TryReadFunction(out var function))
             {
                 return CallSetTableFunc(targetTable, function, key, value, ref context, out doRestart);

+ 28 - 0
tests/Lua.Tests/MetatableTests.cs

@@ -106,6 +106,34 @@ function tail(a, b)
 end
 tail(a, 3)
 assert(tail(a, 3) == 4)
+";
+        await state.DoStringAsync(source);
+    }
+    
+    [Test]
+    public async Task Test_Metamethod_TForCall()
+    {
+        var source = @"
+local i =3
+function a(...)
+  local v ={...}
+   assert(v[1] ==t)
+   assert(v[2] == nil)
+   if i ==3 then
+       assert(v[3] == nil)
+    else
+      assert(v[3] == i)
+    end
+   
+   i  =i -1
+   if i ==0 then return nil end
+   return i
+end
+
+t =setmetatable({},{__call = a})
+
+for i in t do 
+end
 ";
         await state.DoStringAsync(source);
     }