SomeRanDev / reflaxe.CSharp

A remake of the Haxe/C# target written entirely within Haxe using Reflaxe.
MIT License
34 stars 2 forks source link

Null<T> #3

Open Simn opened 1 year ago

Simn commented 1 year ago

One of the biggest challenges when it comes to Haxe code generation is the handling of Null<T>. My experience is that it's good to get this out of the way early because otherwise it's going to come back with a vengeance.

The problem with C# Nullable is that it is constrained to struct. This means that it cannot be used with reference types, which in turn means that it cannot be "blindly" used in place of Haxe's Null<T>. Unfortunately, I don't remember what exactly the problem was with an approach that would emit Nullable only if the parameter is not a reference type.

The C# target has its own Null implementation that doesn't have the struct restriction. However, this has always caused some friction and doesn't seem ideal either.

I'm interested in knowing what the best approach is in the C# world!

SomeRanDev commented 1 year ago

Ahh, true! I can see how this would become a headache 🤔

Unfortunately, I don't remember what exactly the problem was with an approach that would emit Nullable only if the parameter is not a reference type.

Reference types can already be assigned null in C# right? Perhaps the problem that was being solved is @:struct class types can be assigned null in Haxe(?), but not in C#. So instances are wrapped with Nullable?

But it would probably suck to just wrap all the custom value types in a reference type, removes the whole point of their optimization. Hmmmm... guess @:struct types can just throw an error if found being assigned a null during generation (without wrapping in Null<T>), but would be nice if null usage could be tracked better.

@Simn Sorry if this has been asked before, but I've been meaning to inquire: is having null-safety always "on" on the table for Haxe 5? Outside of the safety stuff, feels like it would help with optimizing generation for static targets for situations like this. But would probably break everything... so idkkk...

jeremyfa commented 1 year ago

As far as I can tell, I would see the generated code like this:

But yes, that won't solve all cases, like if in the original Haxe code there is some generic class or function literally taking Null<T> as argument, which mean it could theoretically accept both value types and reference types as T, then, yes, in that case we might need wrap the value into some custom Nullable type...

If there is no going away from this, I guess we should try our best to use a custom Nullable type only in places where we can't do otherwise (like haxe generic Null<T>), and stick to C# Nullable value types or plain references in all other situations when possible. Can imagine that will require the exporter to be smart about that and can definitely see this as a challenge 😅. But not sure there is a better option...

Simn commented 1 year ago

Regarding default null-safety, I don't know yet. It might become a thing like DCE that is enabled by default for user-code. But at the end of the day, the problem here isn't going to go away.

jeremyfa commented 1 year ago

The C# target has its own Null implementation that doesn't have the struct restriction. However, this has always caused some friction and doesn't seem ideal either.

@Simn Do you have more details about the problems/friction you had when using that custom Null<T> implementation for C# target?

Simn commented 1 year ago

I never used it much, but from my understanding it makes native interop a mess. Maybe @klabz has some insight, he's the expert on Haxe/C# annoyances.