HaxeFoundation / haxe

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

Reflection on primitive types and values seems to be broken/inconsistent #5202

Open sebthom opened 8 years ago

sebthom commented 8 years ago
  1. Std.is() inconsistent when testing primitive types

    The haxe doc states basic types are no classes: http://haxe.org/manual/types-basic-types.html However:

    Std.is(Bool, Class);  // returns false
    Std.is(Int, Class);  // returns true  => should return false?
    Std.is(Float, Class);  // returns true  => should return false?
    
    Std.is(1, Int); // returns true
    Std.is(1, Float); // returns true => should return false
    Std.is(1.0, Float); // returns true
    Std.is(1.0, Int); // returns true => should return false
  2. Type.getClass() does not return null for primitive values

    The haxedoc of Type.getClass() states:

    Returns the class of o, if o is a class instance. If o is null or of a different type, null is returned.

    However:

    Type.getClass(true);  // returns some Boolean{} object => should return null per haxedoc
    Type.getClass(1);  // returns some Number{} object => should return null per haxedoc
    Type.getClass(1.0); // returns some Number{} object => should return null per haxedoc
  3. Bool behaves like a broken enum

    Std.is(Bool, Enum);  // returns true
    Type.enumEq(true, true); // works

    However:

    var e1:Enum<Dynamic> = Bool;  // doesn't compile
    var e2:Enum<Bool> = Bool;  // doesn't compile
    
    Type.allEnums(Bool);  // doesn't compile
    Type.enumIndex(true);  // doesn't compile 
    Type.enumParameters(true);  // doesn't compile 
    Type.getEnum(true);  // doesn't compile 
    Type.getEnumName(Bool);  // doesn't compile 
ousado commented 8 years ago

On many/most targets there's no sane way to distinguish certain primitive types from each other (or e.g. pointers) at runtime, and the performance overhead of any "working" implementation would render the respective target pretty much useless. I think the only answer is don't ever do this, don't ever rely on this. And I think maybe Haxe shouldn't pretend it can give correct results here, but that's the nature of any API involving Dynamic.

nadako commented 8 years ago

Yeah, this is totally unspecified. I'm leaving this open for 3.4, since we may want to look into making it a bit more consistent, but that's not a priority at all.

Simn commented 6 years ago

I see nothing actionable here because as mentioned, this is unspecified.

sebthom commented 6 years ago

Is it documented that some of the behavior for basic types is unspecified? If not maybe that would be the action.

Also for Int and Float it actually is specified that basic types are no classes, yet Std.is(Int/Float,Class) returns true.

jdonaldson commented 6 years ago

As an aside, this test is problematic:

Std.is(1.0, Int); // returns true => should return false

Some platforms (lua) don't have run time integers, so there's no way to detect them properly. I think that kills that test case.

jdonaldson commented 6 years ago

This test is also problematic, for the previous reason:

Std.is(1, Float); // returns true => should return false

Additionally : Ints "extend" Floats according to Haxe. Std.is respects inheritance, so 1 is treated as both a Float and an Int. Ints are abstracts with a "to Float" capability.

jdonaldson commented 6 years ago

It seems that there are a few ways forward here. I'll leave them on a few separate comments so folks can thumbs up/down each one.

jdonaldson commented 6 years ago

We change the documentation making Std.is on a value type unspecified.

jdonaldson commented 6 years ago

We throw errors on run time platforms when called against a value type that they can't disambiguate.

jdonaldson commented 6 years ago

We give warnings on run time platforms when called against a value type that they can't disambiguate.

Simn commented 5 years ago

Two things to do here:

  1. Check if enumEq is missing the :EnumValue constraints on some targets. Also check why @:coreApi doesn't check that.
  2. Document that "is a class instance" on Type.getClass refers to the run-time type. For instance, calling it with a primitive value like 1 will wrap it into a java.lang.Integer on the Java target, which is a fixed necessity. It's impossible for the run-time to distinguish this from an actual java.lang.Integer instance (see #8065).