HaxeFoundation / haxe

Haxe - The Cross-Platform Toolkit
https://haxe.org
6.03k stars 648 forks source link

Uncorrect resolving Unknown<?> type returned from function #11593

Closed ze0nni closed 4 months ago

ze0nni commented 4 months ago

Look like haxe resolve type Unknown<?> after first comparisons for all branches of code.

enum abstract GameType<TBody>(String) {
    final Unit: GameType<{ hp: Float }>;
    final Box: GameType<{ size: Float }>;
}

@: forward()
abstract GameObject < TBody = Void > ({
  final type: GameType<TBody>;
  final body: TBody;
}) {
  inline public function new<T>(type: GameType<T>, body: T) {
    this = cast { type: type, body: body };
  }
  inline public function resolve<TResolvedBody>(): GameObject<TResolvedBody> {
    return cast this;
  }
}

class Test {
  static function main() {
    final objects: Array<GameObject> = [
      new GameObject(Unit, { hp: 10 }),
      new GameObject(Box, { size: 5 })
    ];

    for (i in objects) {
      final o = i.resolve();
      switch (o.type) {
        case Unit:
        //o.body.hp -= 1;

        case Box: //<< 36
        //o.body.size += 1;
      }
    }
  }
}

[ERROR] Test.hx:36: characters 14-17

 36 |         case Box:
    |              ^^^
    | error: { hp : Float } has no field size
    | have: GameType<{ size }>
    | want: GameType<{ hp }>

But it's work good here:

static function resolve<TBody>(type: GameType<TBody>, body: TBody) {
    switch(type) {
        case Unit:
                body.hp -= 1;        
        case Box:
                body.size -= 1;
}

Actually I would like to be able to use the Unknown type in my code too. For cases when the type is really unknown is this possible?

Simn commented 4 months ago

I'm not sure what exactly you're reporting here. The first example doesn't look unexpected because TResolvedBody is never unified with anything.

ze0nni commented 4 months ago

I expect behavior like this

enum abstract GameType<TBody>(String) {
    final Unit: GameType<{ hp: Float }>;
    final Box: GameType<{ size: Float }>;
}

typedef GameObject<TBody> = {
  final type: GameType<TBody>;
  final body: TBody;
}

final objects: object: Array<GameObject<Unknown>> = [];

for (o in objects) {
    switch (o.type) {
       case Unit: o.body.hp -= 1;
       case Box: o.body.size -= 1;
    }
}
kLabz commented 4 months ago

You can achieve it with one more step:

class Test {
    static function main() {
        final objects:Array<GameObject<Any>> = [
            {type: Unit, body: {hp: 42}},
            {type: Box, body: {size: 4.2}}
        ];

        inline function foo<T>(o:GameObject<T>) {
            switch (o.type) {
                case Unit:
                    $type(o); // GameObject<{ hp : Float }>
                case Box:
                    $type(o); // GameObject<{ size : Float }>
            }
        }

        for (o in objects)
            foo(o);
    }
}

enum abstract GameType<TBody>(String) {
    final Unit:GameType<{hp:Float}>;
    final Box:GameType<{size:Float}>;
}

typedef GameObject<TBody> = {
    final type:GameType<TBody>;
    final body:TBody;
}

https://try.haxe.org/#A80ca1Da

ze0nni commented 4 months ago

Right. But why doesn't the same work for return-types?

Simn commented 4 months ago

I don't know if we want to support this kind of typing for monomorphs in general. Type inference is already tricky enough as it is, and changing it to support a corner case like here doesn't have a good risk/reward ratio in my opinion.

ze0nni commented 4 months ago

Sad to hear. I find it feature powerful when haxe-Enum cannot be used