Closed nadako closed 1 year ago
Note that if we remove the String
constraint, f
will be outputted as:
public virtual global::haxe.IMap<T, int> f() {
@Simn this fails too on cpp. It seems that Map<T,Int>
when T : String
is inferred as ObjectMap
instead of StringMap
I reduced example to a small @:multiType
abstract and it seems it's somehow related to type param/constraint being used in the @:to
function:
interface Base<T> {}
@:multiType(T)
abstract A<T>(Base<T>) {
public function new();
@:to static function toS(t:Base<String>):S return null;
@:to static function toO<T:{}>(t:Base<T>):O return null;
}
class S implements Base<String> {}
class O implements Base<{}> {}
class B<T:String> {
var v:A<T>;
}
generates:
public class B<T> : global::haxe.lang.HxObject, global::B {
public global::O v;
}
That might be the most confusing issue reduction I have ever seen. :P
But yes, the @:to
resolution has always used type_eq
instead of unify
so T:String
is not considered compatible with String
. I don't want to change that either because it would allow using IntMap with Float and other horrible things.
But why does it ends up using ObjectMap
instead of IMap
when dealing with constrained type param?
Because String can be assigned to {}.
right...
But even if it can be assigned to {}, type_eq shouldn't be true. Should it? I feel like if we had a better variance idiom, these kinds of issues would be more clear.
Here's what happens inside unify_to_field
:
toStringMap
type_eq IMap<test.T, String> IMap<String, Unknown<0>>
type_eq test.T String
fail
toIntMap
type_eq IMap<test.T, String> IMap<Int, Unknown<0>>
type_eq test.T Int
fail
toEnumValueMapMap
type_eq IMap<test.T, String> IMap<Unknown<0>, Unknown<1>>
beginning constraint checks
unify test.T EnumValue
fail
toObjectMap
type_eq IMap<test.T, String> IMap<Unknown<0>, Unknown<1>>
beginning constraint checks
unify test.T { }
done constraint checks
As you see the constraints are checked using unify
.
We could try changing toStringMap to toStringMap<K:String, V>(t:IMap<K,V>):StringMap<V>
.
I guess that solves it. Which makes me think - why do you have T:String
anyway, @nadako ? :P
I've been wondering about the semantics of T:String
as well...
I have a base class for a functionality that works with strings, but i wanted to be able to constraint those strings in a subclass using @:enum abstract.
E.g.
typedef ServiceHandler = {} -> Void;
class ServicesBase<TServiceType:String> {
var handlers:Map<TServiceType,ServiceHandler>;
public function new() {
handlers = initHandlers();
}
function initHandlers():Map<TServiceType,ServiceHandler> {
throw "not implemented";
}
public function useService(type:TServiceType, data:{}) {
handlers[type](data);
}
}
@:enum abstract MyServiceType(String) to String {
var Gift = "gift";
var Matchmaking = "matchmaking";
}
class MyServices extends ServicesBase<MyServiceType> {
override function initHandlers() {
return [
Gift => handleGifts,
Matchmaking => handleMatchMaking
];
}
function handleGifts(data:{}) {}
function handleMatchMaking(data:{}) {}
}
Anyway, feel free to make the change, I don't want to be responsible. :P
The original test for this started failing when I moved Map to haxe.ds and aliased it from toplevel. I "fixed" the test by using haxe.ds.Map explicitly, but there must be something wrong here. Maybe a missing follow somewhere in the C# area or something...
Closing old C# issue that won't be addressed.
I'm not sure if it's gencommon issue or general (ping @Simn), but it's reproduced easily with C# target:
this fails with
src\Main.cs(140,12): error CS0029: Cannot implicitly convert type "haxe.ds.StringMap<int>" to "haxe.ds.ObjectMap<object,int>"
and that's becausef
is typed asObjectMap<T,int>
: