HaxeFoundation / haxe

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

[hl] Don't know how to cast void to #Example #5358

Closed sebthom closed 7 years ago

sebthom commented 8 years ago

The following code doesn't compile when targeting HL but throws the error characters 25-51 : Don't know how to cast void to #Example.

abstract Either2<A,B>(haxe.ds.Either<A,B>) {
    public function new(value:haxe.ds.Either<A,B>) this = value;
    @:from static function fromA<A,B>(value:A):Either2<A,B> return new Either2(Left(value));
}

class Example {

    inline
    public static function createExample(value:Either2<String, Int> = null):Example {
        if (value == null) return null;
        return new Example();
    }

    function new() {}
}

class Test {

    static var example = Example.createExample("g");

    public static function main() {
        trace(example);
    }
}

The example contains only the minimum number of statements from my project to reproduce the error.

It compiles if I remove

When targeting C# and Java the Compiler Warning Type Example is being cast to the unrelated type Void is reported but compilation proceeds and the code works. All dynamic targets work fine too.

Targeting CPP fails with

./src/Test.cpp: In static member function 'static void hx::strings::TestRunner_obj::__boot()':
./src/Test.cpp:390:25: error: expected primary-expression before 'if'
 HXDLIN(  43)  example = if (_hx_tmp) {
                         ^
./src/Test.cpp:393:15: error: 'else' without a previous 'if'
               else {
               ^
Error: Build failed
ncannasse commented 8 years ago

Seems like some AST typing issue wrt inlining or analyzer

Simn commented 8 years ago

That compiles fine for me if I call createExample("foo"). How are you invoking it?

sebthom commented 8 years ago

I updated the example above. Basically it happens when the return value is directly assigned to a static variable. Works fine with non-static.

Simn commented 8 years ago
class Main {
    static var example = if (Math.random() > 0.5) 1 else 2;

    public static function main() {
        trace(example);
    }
}

Static inits are annoying. From the expression's point of view this is a block-level if, so having it typed as Void is correct. It is only due to the fact that the block is assigned to the field that it should be different here.

This leads to back our static_init discussion from... somewhere else. Right now I'm not even sure how to temp-fix this for hxcpp.

Simn commented 8 years ago

Would it be acceptable to use self-calling-functions as field initialization on targets that have problems with this (C++, HL)?

ncannasse commented 8 years ago

shouldn't the expression be typed as returning a value in the first place?

Simn commented 8 years ago

Not really because the assignment to the field is implicit.

Simn commented 8 years ago

@hughsando: Do you have an opinion on this (or remember where we talked about it before)?

hughsando commented 8 years ago

I think we talked about generating the "static init block" per class (including meta and rtti?), rather than limiting ourselves to a series of field.cf_exprs. Or even just a helper "create_static_init_block." This would avoid code duplication between targets, and allow arbitrary code ordering/tmp variables.

Simn commented 8 years ago

I tried working on this but ran into a problem with DCE or rather post-process order in general. We want to apply all post-process operations to the static inits, but we don't want to do that for statics which would be DCE'd otherwise.

Basically collecting them before DCE subverts DCE, collecting them after misses the required post-processing.

hughsando commented 8 years ago

Yep, sounds like fun. I guess the other way to do it would be make the expression an arbitrary void expression (ie, could be a block) which includes (possibly conditional and multiple) "name = value" statements.

Then instead of outputting "name = genExpr(cf_expr)" just generate "genExpr(cf_expr)".

This would be less code per target (unless they optimize somehow?), but would involve changing every target.

ncannasse commented 8 years ago

I'm don't agree that the AST expression should be Void. Even if the assignment is implicit, we are actually typing it with WithType if there's an explicit type (I think) and with Value either, so it should be typed as an expression which eval to a value.

Simn commented 8 years ago

Sure we type it with WithType, just as we do something like this:

var x = if (e1) e2 else e3

This is transformed:

var x;
if (e1) x = e2
else x = e3

At this stage the if-expression is typed as Void, so we effectively discard the type that originally came from the WithType. Depending on the complexity of e1, e2 and e3 we might then reconstruct the original expression.

Now imagine the same thing without the assignment. We obviously end up with this:

if (e1) e2
else e3

And again the if-expression is typed as Void. This case is indistinguishable from the former, the only difference is the context because the latter is actually assigned to a static field.

Simn commented 8 years ago

I'm using self-calling functions now for Cpp so the regression part of this can be considered resolved. I might look into a better approach in the future, but this should be good enough for 3.3.

sebthom commented 7 years ago

Works with HL1.1 and Haxe 3.4.