Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[cs] Wrong generic type definition #5434

Closed
Neverbirth opened this issue Jul 5, 2016 · 18 comments
Closed

[cs] Wrong generic type definition #5434

Neverbirth opened this issue Jul 5, 2016 · 18 comments
Assignees
Labels
bug platform-cs Everything related to c#

Comments

@Neverbirth
Copy link

Neverbirth commented Jul 5, 2016

Having for example the following piece of code:

interface ITest<K, V>
{
    function keys():Array<K>;
    function values():Array<V>;
}

interface ISubTest extends ITest<String, String>
{
}

Generates the following C# code for ISubTest:

public interface ISubTest : global::haxe.lang.IHxObject, global::ITest<object, object> {

}

As you can see it completely loses the types we want to use. I tried different types, and I was always getting object for the generic type. Furthermore, this one:

interface ITest<K, V>
{
    function keys():Array<K>;
    function values():Array<V>;
}

interface ISubTest extends ITest<String, Dynamic>
{
}

class TestClass implements ISubTest
{
    public function keys():Array<String>
    {
        return null;
    }

    public function values():Array<Dynamic>
    {
        return null;
    }
}

Doesn't compile at all, because it generates some invalid implementation of the interface in TestClass:

    global::Array global::ITest<object, object>.keys() {
        return ((global::Array) (this.keys()) );
    }


    public virtual global::Array<object> keys() {
        return null;
    }


    public virtual global::Array values() {
        return null;
    }

I would say this is a rather important issue.

@nadako
Copy link
Member

nadako commented Jul 5, 2016

The second one is a real issue.

As for the object - that's how the c# target currently works. As @waneck explained before, since the memory layout is the same, there's no real benefit of working around the haxe/c# type system differences to make the code look more "native", and by some tests it even worked faster. I'd like to review/check that again though, because this topic is brought quite often.

@nadako nadako added bug platform-cs Everything related to c# labels Jul 5, 2016
@Neverbirth
Copy link
Author

Neverbirth commented Jul 6, 2016

? Would love to see those tests. While what you say is true for some cases, it's not for all of them. Think for example of a generic that in the end is creating an array for ints, with the code used for c# you'll end creating an array of objects, in that case the memory layout is not the same and haxe code will be much slower for some uses. Also, what if you use constraints for the generic type? I didn't check this use case, but do you rely on reflection to access known methods and properties? in that case it's clear it will also be slower.

There are other cases too, but I'd say that in a lot of cases the produced code will be slower (admittedly in a lot of them the difference will be minimal), maybe those tests were not taking into account GC garbage, starting up overload and other things that can take an effect into the final results?

EDIT: Tested it, I see that constraints are not converted over to C# code, and that in fact reflection is used unless we define the exact type to be used in the interface or class declaration.

@nadako
Copy link
Member

nadako commented Jul 7, 2016

Well, for basic types (int,double,bool) it generates proper type, it's just all others that are pointers anyway, but yeah, we should look into it at some point in future.

@Neverbirth
Copy link
Author

I see, I thought the object replacement was used everywhere, my bad. There may be some cases where it can still be a problem, like arrays of primitive types, but I think this one would be very uncommon.

At any rate, that bug when using Dynamic is way more important, it's for example making hexMachina not usable when targetting C#, a real shame as it's already working for a few targets.

@vroad
Copy link
Contributor

vroad commented Aug 15, 2016

Is there any way to avoid this issue? Haxe falls back to reflection when iterating through Map values like this.

https://github.com/openfl/starling/blob/8d3f19b9594870db8f406442ba5cff476395393a/starling/rendering/BatchProcessor.hx#L211

@vroad
Copy link
Contributor

vroad commented Nov 13, 2016

I'm not sure why losing generic parameter makes program faster either. That will cause lots of casts, and makes code size bigger. Compilers cannot generate optimized code from such big code.

@vroad
Copy link
Contributor

vroad commented Nov 13, 2016

Even when reflection is not used it could be bad for performance because of lots of casts.

Also, losing type information is bad if you want to make a library for existing .NET application, or if you want to use that library from Haxe with -net-lib. You need to do cast everywhere to get an instance of correct type from arrays/maps.

@Neverbirth
Copy link
Author

Also, losing type information is bad if you want to make a library for existing .NET application, or if you want to use that library from Haxe with -net-lib. You need to do cast everywhere to get an instance of correct type from arrays/maps.

That's a good point... I didn't try it, but what if you have a library that requires to get for example some GenericClass<CustomClass> instance? AFAIK GenericClass<Object> won't work in the cases where variance is not defined, will it?

@vroad
Copy link
Contributor

vroad commented Nov 14, 2016

You mean we should support casting to GenericClass<object> from GenericClass<CustomClass> in some way?
Then, should all generic classes have wrapper object like arrays/maps to support variance? That may work, but makes generated code complex, and creates many objects for casting.

Could that be the other way around?
Just create class instances with type parameter(SomeList<CustomClass>), and automatically upcast to CustomClass if you want to push some element, and downcast to `object' when you get some element from object.
This way we don't need wrappers, but list cannot contain any kind of object anymore.

Should a List be able to contain any kind of object? Then it's not possible to support variance in this way.
but you can't insert objects to Array<Int> already, can you?

@Neverbirth
Copy link
Author

Neverbirth commented Nov 14, 2016

You mean we should support casting to GenericClass from GenericClass in some way?

Not exactly. I mean if you have some external assembly expecting a GenericClass<CustomClass> instance, but Haxe is generating for your code GenericClass<object> your code won't work. I don't know if right now there is some workaround for this in place (you could use LINQ Cast() or OfType() functions for example), but if so I'd guess it would fail when using reflection for example.

I guess the same happens if you generate public API with Haxe? You want to generate an assembly that accepts GenericClass<CustomClass>, but Haxe exposes GenericClass<object>, is this so? Not very nice :S.

@vroad
Copy link
Contributor

vroad commented Nov 14, 2016

Not so nice, but it seems impossible to GenericClass<CustomClass> without knowing about underlying type. You'll need to cast to GenericClass<CustomClass> in some way.

@vroad
Copy link
Contributor

vroad commented Nov 14, 2016

But it's not possible to cast GenericClass<int> to GenericClass<object> either. Then is it meaningful to suppot GenericClass<CustomClass> to GenericClass<object>?
So, this is not for supporting cast to GenericClass<object>??

I've tried writing a function that returns System.Collections.Generic.List<CustomClass>. In this case the function returned List<CustomClass>. So this only applies to Haxe-created classes?

@Neverbirth
Copy link
Author

I'm not sure to follow you :/. Having some fictitious 3rdparty.dll that you reference in your HxCS project, and 3rdparty.dll has a class NameSpace.ClassA that you need to use, with a IComparable<ClassA> Comparer property. You may have in your Haxe project something like class Test<T> implements IComparable_1<T>.

When you do new ClassA().Comparer = new Test<ClassA>() it gets converted to something similar to:

new global::NameSpace.ClassA().Comparer = ((global::System.IComparable<global::NameSpace.ClassA>) (new global::_Main.Test<object>()) );

Which AFAIK, should fail.

@Neverbirth
Copy link
Author

Just tested it. Indeed it fails. Note that my sample is not 100% valid tho, because I was using IComparable, and from .NET 4 onward it's using variance (it's defined as public interface IComparable<in T> in there)... if we use a normal interface that isn't defining any variance (like I said some comments above) it's failing. I could upload a sample if desired.

@vroad
Copy link
Contributor

vroad commented Nov 17, 2016

I've found the place where this conversion is done.

dynamic_anon

Changing dynamic_anon to t disabled type parameter conversion. However, as I mentioned , this will cause problems with GenericClass<Dynamic> because you cannot convert GenricClass<CustomClass> to GenericClass<Dynamic>.

@Neverbirth your test class is just a haxe-generated class that implements builtin .NET interface, so haxe still converts type parameters to object I guess.

Should we be able to convert GenricClass<CustomClass> to GenericClass<Dynamic>, in the first place? I rarely use this technique, but probably we should support this in some way because other targets support this.

@vroad
Copy link
Contributor

vroad commented Nov 17, 2016

Could this be fixed without converting all type parameters to object when #4475 is implemented?

@Neverbirth
Copy link
Author

Neverbirth commented Nov 18, 2016

Oh, I didn't consider how GenericClass<Dynamic> works in Haxe. Tricky indeed... I can think of workarounds for a lot of cases, but not to all of them... never had to thought of a case like this... Personally, if I would have to choose between this and keeping class definitions in generics I would choose the second tho.

EDIT: I guess typedefs can also become a bit tricky... didn't check the generated code for a case like this.

Anyway, all this topic is not a showstopper like the second bug I mentioned in my original report.

@Simn Simn modified the milestone: 4.0 Jan 9, 2017
@Simn Simn modified the milestones: Release 4.0, Bugs Apr 17, 2018
kLabz added a commit to kLabz/haxe that referenced this issue Jun 8, 2019
Simn pushed a commit that referenced this issue Jun 9, 2019
* Add failing tests

* [cs] Consider String as basic type too

* HxObject.getParams() returns an Array<{}>

* Add tests for #5434
@kLabz
Copy link
Contributor

kLabz commented Jun 14, 2019

Seems to be fixed by #8387 (or is there something else to do here?)

@kLabz kLabz closed this as completed Jun 26, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug platform-cs Everything related to c#
Projects
None yet
Development

No branches or pull requests

6 participants