haxe icon indicating copy to clipboard operation
haxe copied to clipboard

[lua] Function assignments to class fields change their signatures (they behave as methods)

Open Frixuu opened this issue 3 years ago • 1 comments

Applies to both Haxe version 4.2.5 and development (f3c1a7d)

What I want to achieve

I'm trying to use the Lua target for modding a certain video game.

The API of this game allows mods to replace a chunk of its logic. All they need to do is provide a table with functions to be overridden:

{
  doStuff = function() --[[ ... ]]-- end,
  makePlayerOverpowered = function(playerId) --[[ ... ]]-- end,
  calculateManaCost = function(spellName, spellLevel) --[[ ... ]]-- end,
  -- imagine some more fields here...
}

What I expected

Since none of the functions on this object take self as an argument, I knew I could not model it with class methods. However, I felt that storing functions in fields would accurately enable the behavior I was going for:

class MagicCalculator {
    public var calculateManaCost: (spellName: String, spellLevel: Int) -> Int;

    public function new() {
        this.calculateManaCost = (spellName, spellLevel) -> {
            return spellName.length + spellLevel;
        };
    }
}

To help me test it outside of the game's environment, I've made the following my entrypoint:

class App {
    public static function main() {
        final calc = new MagicCalculator();
        lua.Lua.print(calc.calculateManaCost("Fireball", 10));
    }
}

What I got

Surprisingly for me, the generated .lua file rewrote my original function as if it was a method call - it now accepts self as its parameter. This would not be an issue if I only ever interacted with this object via Haxe, but unfortunately, the function does no longer match what the game's API expects:

MagicCalculator.super = function(self) 
  self.calculateManaCost = function(self,spellName,spellLevel) 
    do return #spellName + spellLevel end
   end;
end

Additionally, the generated call in main also uses the function as a method (note the colon):

App.main = function() 
  _G.print(MagicCalculator.new():calculateManaCost("Fireball", 10));
end

What else I have tried (unsuccessfully)

  • using dynamic function instead of var,
  • doing the assignment in place other than the constructor,
  • putting @:luaDotMethod in many different places: this changes the invocation of the function from : to ., but the actual definition remains the same (which I care about here)

What I have found

From my limited experimentation, it seems like any assign operation to a field will generate the self param, no matter if the classfield is a Var, Method, or whether it has dot access metadata:

https://github.com/HaxeFoundation/haxe/blob/f3c1a7d8f4/src/generators/genlua.ml#L1266-L1271

(disclaimer: I know nothing about OCaml, much less compiler architecture)

Other similar issues

  • #6411 talks only about accessing such fields on external, Lua-defined objects, not about creating them,
  • both #6426 and #10089 are similar to this issue with the caveat that those are about anonymous structures. However, I felt that comments to these might provide additional historical context to this codegen behavior.

Frixuu avatar Sep 18 '22 03:09 Frixuu

I think your analysis is correct. The code you point out makes no distinction between Var and Method, so any instance field that has a function type would be considered. The solution should then be to change the pattern to TField(e3, FInstance(_,_,{cf_kind = Method _})).

Simn avatar Sep 18 '22 04:09 Simn

So, I am having a very similar problem, that I'm not sure if will be fixed by the linked PR too. My specific problem is about storing functions in lua tables. They are also being wrapped by a function that "consumes" what it seems to be the self variable, preventing it from being passed down to the function. To give you an example, something like this

lua.Table.create({ foo: (a,b) -> a + b })

Will produce

local foo = function(a,b) return a + b end
{ foo: function(_,...) foo(...) end }

Which is obviously incorrect. @Frixuu does your PR fixes this? If not, I will open a separate issue

danielo515 avatar Dec 28 '22 12:12 danielo515