Type check and cast

Hej,

I’ms still a bit confused between “type check” and unsafe cast.

Let’s say I have a function like that :

public static function foo<T:A>( cl : Class<T> ){
    var classname = Type.getClassName( cl );
    var v                = cl.bar();
}

So I want to use Type.getClassName() because I know I pass a class to this function, but I also know that my class has a static function named bar().
Here Haxe compiler will complain that cl has not bar(), so I wanted to use type check like that :

var v = (cl:{ bar : Void->Map<String,String> }).bar();

But Haxe compiler still complains, so the only way it works is like that :

var v = (cast cl:{ getFieldnamesToDisplay : Void->Map<String,String> }).bar();

So my question is : why there is a “type check” synthax, if you have also to use unsafe cast in order to bypass compiler ?

Thanks for your attention,
Regards,

Type check is compile time. Thus can affect abstract casts for example, because abstracts only exist at compile time.
And in contrast cast is runtime.

I think safe cast is run-time, but both unsafe cast and type check are compile-time

Actual casting happens at runtime. Safe or unsafe.

Ok, anyway, I’m talking about compile time here, why using “type check” doesn’t work here but I have also to use unsafe cast in order to subvert the compiler ? Why is “type check” for so please ?

It’s a type “check”, not a real “cast” - the types have to be compatible already for it to work. The main use case is to trigger implicit conversions of abstract types without having to declare a temporary variable with that specific type hint first.

Thanks all for your answers.

So what should be the better way to do what I want please ? I just want to “subvert” the compiler on compile-time without any changes on run-time. By this I mean that I know what I’m doing and I just want to pass the compiler type checking. Untyped ?..

Of all the options untyped suppresses all type errors (typos etc.). So it’s best not to use it.

As for type checks and casts:

  1. (e:T) compiles only if e is of type T and the result is of type T
  2. cast e completely erases the type of e … it’s a way of telling the compiler “please ignore the type of this” … it does nothing at runtime (not 100% true on static platforms, but let’s not complicate things too much)
  3. therefore (cast e:T) always compiles and has the type T

Also, you can think of cast (e, T) as if (Std.is(e, T)) (cast e:T) else throw 'Cannot cast $e to $T'

Here’s an example:

class Test {
  static function main() {
    var s = 'hoho';
    $type(s);// String
    $type(cast s);// Unknown<0>
    {
      var o = (s:{ var length(default, null):Int; });// works
      $type(o);// { length : Int }
      trace(o.length);// 4
    }
    
    {
      var o = (cast s:{ function includes(s:String):Bool; });// only works with the cast
      $type(o);// { contains : (s : String) -> Bool }
      trace(o.includes('oh'));// true
    }
  }
}
4 Likes

Hej Juraj !

Thanks for your explanation, as always it’s deep and precise, many thanks for your time !

Yes I know unsafe is the worst but since I want to be sure to not have overhead on runtime and “I know what I’m doing” the only solution here is unsafe case. In JS, dynamic language it’s ok, but on static language it would add overhead code at runtime right ?

So sorry for insisting but why “type checking” can’t be used here ? Why is this “reserved” for abstracts ? It would really be great to have this “option” to tell punctually to the compiler to “let do this operation” which would be cleaner than using untyped and not add any overhead on runtime, isn’t it possible ?

Again, many thanks for all, for your answers,
Regards,
Michal

It is not reserved for abstracts in any way. It checks at compile time that the expression is compatible with the expected type and then converts it to that type (inserting implicit casts if necessary). Its primary role is to help the compiler with inference (and it was originally introduced only to be used with macros). In any case, you can think of (e:T) as {var v:T = e; v;} except that no extra variable is assigned at runtime. It is very close to a static casts in C/C++, although I’m sure experts in the field could name a whole number of differences ^^

Yes, it is. (cast e:T) does exactly that. On JavaScript this generates the code you want without further performance implications.

On static platforms, assigning an unknown type (which is the case here as cast e erases the type) to a concrete type may have performance implications (such as involving a runtime type check). It will still be orders of magnitude faster than untyped, which on static platforms generates reflection based code (leading to bigger binaries and slower execution).

The main use case for untyped is to access global variables/functions that you know are there. Other than that, I would advise staying clear of it.

1 Like

Thanks again Juraj.

Ok, In JS I know it doesn’t add any overhead code at runtime it gives me exactly what I want, but imagine if it was a static target, so as you said it will add a runtime overhead, so my question is exactly for this case (if in the futur If I target a static language…) : How to “subvert” the type system at compile time without any runtime overhead ? Just to tell the compiler “let me do that, I’ll open my veins by myself if it fails at runtime” :slight_smile:

This isn’t generally possible, though it depends on the target. On JVM for example you will end up with a checkcast instruction, which may or may not have some cost after the JIT did its thing. C++ can use static_cast, but that requires the types to be compatible (like Haxe’s (e : T) without the cast).

Frankly, if you find yourself casting a lot, you will likely have to forget about runtime overhead because it’s not going to be super efficient either way. The problem is likely not how you cast things, but the fact that you’re casting things in the first place.

Thanks for your answer Simon,

Yes you’re right, I understand what you are talking about.
In fact, I don’t often use casting but when I do it I always ask myself the same questions so that’s why I asked these questions here.

The questions here were about 4 ways in Haxe to “bypass” compiler type checking (untyped, type check (e:T), unsafe cast and safe cast) and understand the differences. While I always knew about untyped, unsafe and safe cast, I wanted to know the difference with type checking (e:T) because I always thought that this was the real option to achieve what I’m talking about : tel Haxe compiler to not check this type AND not have overhead on run-time… But I was wrong. So we have 4 ways to bypass compiler type checking on compile time, but no one of these can do what I’m talking about (except untyped but it’s ugly…)

I’m quite never stopped by anything when coding but I sometimes want to get a more elegant way to do things or understand deeply how things works “under the hood”.

Sorry for this long post,
Have a nice day,
Michal

Wrong again. You cannot use untyped for this. On static targets it’s impossible to just invoke arbitrary methods through arbitrary types. Consider this piece of code:

class Test {
  static function main() {
    Test.foo();
    var c:Class<Test> = Test;
    untyped c.foo();
  }
  static function foo() {}
}

Now let’s take a look:

  • on C# the untyped line compiles to object __temp_expr1 = ((object) (global::haxe.lang.Runtime.callField(c, "foo", 5097222, null)) );as said before it falls back to reflection and that definitely has a runtime overhead
  • on C++ it’s even better: you tell the compiler you know what you’re doing, so gencpp happily just inserts c->foo(); and then the C++ compiler fails with: ./src/Test.cpp(35): error C2039: 'foo': is not a member of 'hx::Class_obj'

So for one last time: (cast e:T) is exactly what you want. You cannot possibly get a better result: on dynamic targets it vanishes to nothing, on static targets it compiles to the best possible valid code. In contrast, untyped yields code that at best somehow works. It’s fragile, it’s expensive, and it clashes with other compiler features even on JavaScript:

class Test {
  static function main() {
    var c:Class<Test> = Test;
    untyped c.foo();
  }
  static function foo() {
    trace('foo');
  }
}

Tadaaa:

> haxe -dce full -main Test -js out.js && node out.js

out.js:6
        c.foo();
          ^

TypeError: c.foo is not a function

This is a perfect waste of everyone’s time, including yours, which would be better spent on making your code be robust. The very thing you’re trying to do is incorrect and instead of dealing with that you burn time insisting that something perfectly working is not what you want and that something fundamentally impossible should be available instead.

As for the code above:

  1. If you’re concerned with overhead, don’t use Type.getClassName, because it forces the compiler to include class names for all classes (not just the ones you use)
  2. The code runs the risk of being broken by DCE. If you’re worried about overhead, you should always compile with DCE.
  3. Given T:A and cl : Class<T> it is incorrect to make any assumptions about methods of cl:
    class Test {
      static function main() {
        foo(A);// Test.hx:9: A, bar
        foo(B);// TypeError: cl.bar is not a function
      }
      public static function foo<T:A>( cl : Class<T> ){
        var classname = Type.getClassName( cl );
        var v         = (cast cl:{ function bar():String; }).bar();
        trace(classname, v);
      }
    }
    
    class A {
      @:keep static public function bar() {
        return 'bar';
      }
    }
    
    class B extends A {}
    

I would really advise you to focus your energy on fixing that, instead of obsessing over a non-issue. Good luck.

2 Likes

Juraj,

I truly apologize if I waste precious time of people. I thought it was a community forum and asking questions wouldn’t create a space-time crack like that :slight_smile:
There was a time in the past, (2007, I think you didn’t know Haxe exists, but maybe I’m wrong, I don’t remember, it’s possible because I’m obviously quite often wrong…) when Haxe was born and focused a lot on the Flash target, I was quite “good” targeting Flash, I had time to explore and dig in Haxe deeply to understand things and I also used to answer people when they asked questions, sometimes quite stupid questions, like mine can be sometimes too…

But you know you’re not forced to answer me, not even read me. And at the end, community can also banish me (like I’ve already been banished from discord in the past by some weak people…) even if I think I’m always really polite in my posts and comments and always very thanksful for the attention people give for reading and answering me, so you can just “bypass Michal’s questions on reading-time” :slight_smile:

That’s said, I admit my bad english doesn’t help and maybe sometimes I don’t compose my questions as it should be.
For example, in this thread, I should maybe ask my question this way :

  • I want to build a helper function that I know it takes a class as argument.
  • In this helper function, how can I call a static function that I’m sure is in these classes without using Reflection
  • In fact, I’m looking for a way to bypass punctually Haxe type checking system on compile-time that would let me do unsafe things, is there a way for that please ?

My “atomic” question here is “Is there a way to bypass Haxe compiler type checking system on compile-time ?” Maybe something linked to these options “untyped, unsafe and safe cating, type checking” ?

Yes, I think I should improve my english, so once again I apologize for the pain this thread gives.

And at the end, you must know you are for me one of the best Haxe coders, from who I’ve learned a lot and once again I thank you for all the knowledge you gave me.

NB : In your sample, you extends B from A, but statics doesn’t inherite if I remember well ?

Thanks all for reading me, I hope you won’t take my comment badly, I wish you a nice day ! :slight_smile:

2 Likes

To your question: you should learn macro, which will fulfill all your requirements.

Hi Kevin,

Thanks for your answer.
I use macros sometimes, I have yet some helpful macro tools.
But can you advice me how to do here with macros please ? Don’t give me result, just hint me the direction please.

Thanks !

The following is assuming you are passing a compile time class reference to the function:

You can type the expression, then make sure it has a field bar, then make sure bar is a function, then emit an expression that calls bar.

You can also get the class name at compile time.

Of course yes, thanks Kevin.
But after thinking, I find it’s too much work for nothing. You’re right it works like that and in fact I already use something like that for another thing in my macro tools.
No, I come back to my first wish to just ponctually subvert Haxe type check system and ok I know now it’s not possible. That’s ok.
What would be great if there is no keyword to bypass Haxe type check, to do it by macros but I think it’s not possible. What I will produce with macros must still pass the type check, so I give up, I will do with a cast or use Reflection that’s nothing.

Thanks for your interest :wink:

It’s a perfectly valid solution and could be implemented in a fraction of the time spent debating this. If you actually want to solve anything, why not take any advice? If you just want to talk, let’s meet on gitter and find something more fun to talk about :wink:

An unsafe cast is exactly what you want. It erases an expression’s type and then you can operate on it as you wish. I don’t know how else to explain that or what the problem is here. Do not use untyped. Do not use reflection. Use an unsafe cast.


As for your actual problem, it’s probably better to avoid attempts at abstracting over class objects. If you must, this would work:

class Test {
  static function main() {
    foo(Test);
  }
  
  static public function bar()
    return new Map<String, String>();
  
  static function foo<T:Class<Dynamic> & { function bar():Map<String, String>; }>(cl:T){
    var classname = Type.getClassName(cl);
    var v         = cl.bar();
  }
}

Note that in Haxe 4.2+, this is currently incompatible with DCE.

In any case, I would still advise to use at least this:

  static function foo(cl:{ var classname(default, null):String; function bar():Map<String, String>; }){
    var classname = cl.classname;
    var v         = cl.bar();
  }

Any class you want to use with that will have to have a static var classname. You can do this by hand or with a global build macro. This doesn’t require subverting the type system and as a result it doesn’t cause problems with DCE. It also means smaller output size, since the compiler doesn’t have to include class names for all classes.

There are probably still far better solutions, but it’s hard to make any suggestions for foo and bar ^^

3 Likes