haxe icon indicating copy to clipboard operation
haxe copied to clipboard

Overloads can't discriminate function types with optional arguments

Open kLabz opened this issue 3 years ago • 4 comments

This doesn't work: https://try.haxe.org/#c35B8161

class Test {
  static function main() {
    var foo:?Int->Void = (?foo:Int) -> trace(foo);
    var bar:?Int->Void = test(foo); // Ambiguous overload
  }
  
  extern inline overload static function test(f:?Int->Void):?Int->Void return f;
  extern inline overload static function test(f:Int->Void):Int->Void return f;
}

But this doesn't work either (using ?SomeType as T): https://try.haxe.org/#EB5c76aE

class Test {
  static function main() {
    var foo:?Int->Void = (?foo:Int) -> trace(foo);
    var bar:?Int->Void = test(foo); // error: Optional parameters can't be forced
  }
  
  static function test<T>(f:T->Void):T->Void return f;
}

Also note that @:overload was able to differentiate optional arguments, but if you need a different implementation for them it won't help much: https://try.haxe.org/#12f271C3

class Test {
  static function main() {
    var foo:?Int->Void = (?foo:Int) -> trace(foo);
    var bar:?Int->Void = test(foo);
    bar(42); // ok
    
    var foo:Int->Void = (foo:Int) -> trace(foo);
    var bar:Int->Void = test(foo);
    bar(42); // ok too
  }
  
  // Had to use the optional arguments one as main type to avoid the "Optional parameters can't be forced" error
  @:overload(function(f:Int->Void):Int->Void {})
  static function test(f:?Int->Void):?Int->Void return f;
}

kLabz avatar Mar 03 '22 08:03 kLabz

My current workaround:

class Test {
  static function main() {
    var foo:?Int->Void = function(?foo:Int) trace(foo);
    foo = test(foo);
    foo(); // Test.hx:14:,
    foo(42); // Test.hx:14:,42

    var foo:Int->Void = function(foo:Int) trace(foo);
    foo = test(foo);
    foo(42); // Test.hx:17:,42
  }
  
  extern inline overload static function test<T, T1:?T->Void>(f:T1):T1
    return cast ((?v:T) -> trace(v));

  extern inline overload static function test<T, T1:T->Void>(f:T1):T1
    return cast ((v:T) -> trace(v));
}

(but f can't be called without casting)

kLabz avatar Mar 03 '22 14:03 kLabz

I kind of already forgot what conclusion we reached here, but this is not as buggy as it might look due to function argument contravariance.

Simn avatar Apr 05 '22 05:04 Simn

Your last thoughts on this on slack (#general, from one month ago):

In summary, I think the only thing that we should maybe fix is this situation:

class Main {
	static function main() {
		var opt:?Int->Void = (?foo:Int) -> trace(foo);
		test(opt); // fails
	}

	extern inline overload static function test(f:?Int->Void)
		f();

	extern inline overload static function test(f:Int->Void)
		f(2);
}

Although we'll also have to make a special case for externs here. This would fail at the generator/run-time stage on targets where the type ends up being equal.

kLabz avatar Apr 05 '22 05:04 kLabz

Hmm, well, these overloads already don't compile to real overload targets because of this:

source/Main.hx:7: lines 7-8 : Another overloaded field of similar signature was already declared : test
source/Main.hx:7: lines 7-8 : ... The signatures are different in Haxe, but not in the target language

So that isn't exactly a new case.

Simn avatar Apr 05 '22 05:04 Simn