2.7.3 Operator Overloading

Abstracts allow overloading of unary and binary operators by adding the @:op metadata to class fields:

abstract MyAbstract(String) {
  public inline function new(s:String) {
    this = s;
  }

  @:op(A * B)
  public function repeat(rhs:Int):MyAbstract {
    var s:StringBuf = new StringBuf();
    for (i in 0...rhs)
      s.add(this);
    return new MyAbstract(s.toString());
  }
}

class Main {
  static public function main() {
    var a = new MyAbstract("foo");
    trace(a * 3); // foofoofoo
  }
}

By defining @:op(A * B), the function repeat serves as the operator method for the multiplication * operator when the type of the left value is MyAbstract and the type of the right value is Int. The usage is shown in line 17, which turns into the following code when compiled to JavaScript:

console.log(_AbstractOperatorOverload.
  MyAbstract_Impl_.repeat(a,3));

Similar to implicit casts with class fields, a call to the overload method is inserted where required.

The example repeat function is not commutative: while MyAbstract * Int works, Int * MyAbstract does not. The @:commutative metadata can be attached to the function to force it to accept the types in either order.

If the function should work only for Int * MyAbstract, but not for MyAbstract * Int, the overload method can be made static, accepting Int and MyAbstract as the first and second types respectively.

Overloading unary operators is similar:

abstract MyAbstract(String) {
  public inline function new(s:String) {
    this = s;
  }

  @:op(++A) public function pre() return "pre" + this;

  @:op(A++) public function post() return this + "post";
}

class Main {
  static public function main() {
    var a = new MyAbstract("foo");
    trace(++a); // prefoo
    trace(a++); // foopost
  }
}

Both binary and unary operator overloads can return any type.

since Haxe 4.0.0

The @:op syntax can be used to overload field access and array access on abstracts:

  • @:op([]) on a function with one argument overloads array read access.
  • @:op([]) on a function with two arguments overloads array write access, with the first argument being the index and the second one being the written value.
  • @:op(a.b) on a function with one argument overloads field read access.
  • @:op(a.b) on a function with two arguments overloads field write access.
abstract MyAbstract(String) from String {
  @:op([]) public function arrayRead(n:Int)
    return this.charAt(n);

  @:op([]) public function arrayWrite(n:Int, char:String)
    return this.substr(0, n) + char + this.substr(n + 1);

  @:op(a.b) public function fieldRead(name:String)
    return this.indexOf(name);

  @:op(a.b) public function fieldWrite(name:String, value:String)
    return this.split(name).join(value);
}

class Main {
  static public function main() {
    var s:MyAbstract = "example string";
    trace(s[1]); // "x"
    trace(s[2] = "*"); // "ex*mple string"
    trace(s.string); // 8
    trace(s.string = "code"); // "example code"
  }
}
Exposing underlying type operations

The method body of an @:op function can be omitted, but only if the underlying type of the abstract allows the operation in question and the resulting type can be assigned back to the abstract.

abstract MyAbstractInt(Int) from Int to Int {
  // The following line exposes the (A > B) operation from the underlying Int
  // type. Note that no function body is used:
  @:op(A > B) static function gt(a:MyAbstractInt, b:MyAbstractInt):Bool;
}

class Main {
  static function main() {
    var a:MyAbstractInt = 42;
    if (a > 0)
      trace('Works fine, > operation implemented!');

    // The < operator is not implemented.
    // This will cause an 'Cannot compare MyAbstractInt and Int' error:
    if (a < 100) {}
  }
}
since Haxe 4.3.0

The @:op(a()) syntax can be used to overload function calls on abstracts. The metadata is attached to a function, and the signature of that function determines the signature of the call to the abstract. Multiple functions with different signatures can be annotated this way to support overloading:

abstract MyAbstract(Int) from Int {
  @:op(a()) public function callNoArgs():Int
    return this;

  @:op(a()) public function callOneArg(x:Int):Int
    return x + this;
}

class Main {
  static public function main() {
    var s:MyAbstract = 42;
    trace(s()); // uses callNoArgs, outputs 42
    trace(s(1)); // uses callOneArg, ouputs 43
  }
}