Abstract macro op overloading


I played with abstract and macro, so I get a macro function for @:op(A.B).
It seemed to work fine, I suppose an operator macro function is like a static function and the first argument (I thought) it was the object on which the function apply (like using macro with static extension…)
But I realized that with a thing like myobj.foo += 5, the first argument of the macro function is no longer the object but another thing.
So my question is : Does anyone know exactly what is the 1st argument of an op overloading macro function please ? And why in the case of myobj.foo += 5 it becomes something else please ?

Here’s a complete example : Try Haxe ! (Take a look at Compiler Output please)

Here’s a fragment :

@:op(A.B) macro function set(obj:haxe.macro.Expr, prop:haxe.macro.Expr, val:haxe.macro.Expr) {
		trace(Context.getTypedExpr(Context.typeExpr(obj)).toString(), prop.toString(), Context.getTypedExpr(Context.typeExpr(val)).toString());
mydynobj.bar = "barry"; // macro trace : mydynobj,"bar","barry"
mydynobj.foo = 3;       // macro trace : mydynobj,"foo",3
mydynobj.foo += 5;      // macro trace : Test.ADyn_Impl_.get(mydynobj, "foo") + 5,"foo",Test.ADyn_Impl_.get(mydynobj, "foo") + 5

As you can see for the 2 first traces obj seems to be the object but in the 3rd, it becomes yet a combination … Maybe I just don’t know how the += op works on compiler side ? …

Thanks for reading !


that seems to be wrong, or we don’t understand how is it supposed to work. By simply returning obj as expression generates nothing, while returning binop explicitly produces error

return { expr:	EBinop(op,Context.getTypedExpr(e1),Context.getTypedExpr(e2)), pos: Context.currentPos() }
// MyTest.hx:88: characters 3-20 : Class<MyTest> has no field ADyn_Impl_



I don’t know why you’re switching against TBinop, in fact my question is exactly why obj becomes that, or in other words and with a minimalistic example we get that : Try Haxe ! (Take a look at Compiler Output)

import haxe.macro.Expr;
import haxe.macro.Context;

using haxe.macro.Tools;

abstract Foo(Dynamic) from Dynamic to Dynamic {
	@:op(A.B) function get(prop:String) {
		return Reflect.field(this, prop);

	@:op(A.B) macro function set(obj:Expr, prop:Expr, val:Expr) {
		trace(Context.getTypedExpr(Context.typeExpr(obj)).toString(), prop.toString(), Context.getTypedExpr(Context.typeExpr(val)).toString());
		return macro $val;

class Test {
	static function main() {
		var foo:Foo = {
			n: 5
		foo.n = 4;	// compiler trace => foo,"n",4
		foo.n += 4;	// compiler trace => Test.Foo_Impl_.get(foo, "n") + 4,"n",Test.Foo_Impl_.get(foo, "n") + 4 => So "obj" = Test.Foo_Impl_.get(foo, "n") + 4 ???

In my mind obj += X equals obj = obj + X so obj should always be obj and not become another thing.


I’m switching against TBinop because that’s what we get from Eval. Why we are getting this: that’s a question for the Haxe team.

Last and final test

Yes my main question here is why we get this, so I’ll be glad if someone from Haxe team could answer that.
Maybe A+=B op is not equal to A=A+B or something else, I don’t know…

Maybe A+=B op is not equal to A=A+B or something else, I don’t know…

This depends on the target you’re transpiling to, and whether or not the targetted compiler can resolve that syntax. For example, Lua has no concept of += whereas JavaScript does. For this reason, I imagine Haxe will always see += as Binop(OpAssignOp(OpAdd), expr1, expr2) so that each individual transpiler can interpret that expression according to its needs. (Edit: I also believe for certain types, Haxe can optimise out this expression entirely and replace it with the expected result at compile time).

In other words, never assume A += B resolves to A = A + B after compilation. I suppose this is why explicitly writing out A = A + B is why you don’t see a compiler error. Not yet sure why you’re seeing a different type because of it :slightly_frowning_face:

Thanks for your answer.
Yes, and here it occurs on Haxe AST not on the generated code, so I think the target has nothing to do but it’s interesting to know that some languages don’t have +=, I didn’t know

That’s reasonable. But brings more questions: why do we get

Test.ADyn_Impl_.get(mydynobj, "foo") + 5,"foo",Test.ADyn_Impl_.get(mydynobj, "foo") + 5

instead of

mydynobj,"foo",Reflect.field(mydynobj, "foo") + 5


Macro callback (see compiler output)

I’ve put an issue on Haxe Github Abstract Macro @:op(A.B) overloading weird · Issue #10793 · HaxeFoundation/haxe · GitHub
I hope they won’t kick me out :rofl: