Search code examples
haxe

Forward operators in haxe


I'm trying to write my own boolean "abstract" with some additional functions.

@forward
abstract MyBool(Bool) {
  public inline function new(b:Bool) {
    this = b;
  }
  @:from
  public static inline function fromBool(b:Bool):MyBool {
    return new MyBool(b);
  }
  @:to
  public inline function toBool():Bool {
    return this;
  }
  // some additional functions
}

In principal this works fine:

var t:T = true;
if(t) {
  trace("1");
}
t.someStrangeMethod();

However @:forward does not forward basic boolean-operators like "!":

var f:T = false;
if(!f) { // fails here, because "!" is not defined as an operator for MyBool ...
  trace("2");
}

The error message is "MyBool should be Bool", which I find quite strange because MyBool is an abstract of a Bool with @:forward annotation and there is a @:to-method.

Of course there are some easy workarounds. One could either use:

if(!f.toBool()) {
  trace("2");
}

and/or add a function annotated with @:op(!A) to the abstract:

@:op(!A)
public inline function notOp():Bool {
  return !this;
}

However I do not like both methods:

  • I dislike adding @:op(...) to MyBool, because creating a method for each possible operator would require much code (Maybe not with a boolean, but e.g. with an Int, Float, ...).
  • I dislike using !var.toBool(). If someone has already written quite some code (s)he does not want to go through all of it, when (s)he simply wants to change Bool to a MyBool ... I mean of course (s)he could also cast Bool to MyBool whenever adding new code, but that can be horrible too.

So I was wondering if anyone has a better idea? Is there maybe another "@:forward"-like compiling metadata, I do not know about yet?


Solution

  • There's an open feature request regarding this:

    Can @:forward also forward underlying operator overloads? (#5035)

    One way to make your code example work is to allow implicit conversions with to Bool. I'm not entirely sure why the equivalent @:to function doesn't work here, as the Haxe Manual states that "Class field casts have the same semantics".

    abstract MyBool(Bool) to Bool {
    

    Apart from that, I think the only options is to declare an @:op function for each operator you want to support. If declared without a body, the underlying type's operator will be forwarded:

    @:op(!A) function notOp():MyBool;