haxe
haxe copied to clipboard
[lua] Function assignments to class fields change their signatures (they behave as methods)
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 functioninstead ofvar, - doing the assignment in place other than the constructor,
- putting
@:luaDotMethodin 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.
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 _})).
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