HaxeFoundation / haxe

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

Dynamically named field on extern #9825

Open zabojad opened 4 years ago

zabojad commented 4 years ago

I'm currently writing an extern for some js lib and I'm facing the following case:

type WrapOptions {
    injectedPropName:String,
}
function wrapComponent(component:ReactType,?options:WrapOptions){
    // ...
}
type WrappedComponentProps {
    [injectedPropName]: InjectedShape
}

How could I type a prop with a dynamic name with Haxe 4.1? With a default value would be even better...

RealyUniqueName commented 4 years ago

Something like this?

abstract MyDynamicStruct(Dynamic) {
    public inline function new(knownField:String) {
        this = {knownField:knownField};
    }

    public var knownField(get,never):String;
    inline function get_knownField():String
        return this.knownField;

    @:arrayAccess inline function get(name:String):InjectedShape
        return Reflect.field(this, name);
    @:arrayAccess inline function set(name:String, v:InjectedShape):InjectedShape {
        Reflect.setField(this, name, v);
        return v;
    }
}

usage

var o = new MyDynamicStruct('hello');
trace(o.knownField); // hello
o['dynamicField'] = new InjectedShape();
trace(o['dynamicField']); // stringified InjectedShape instance
RealyUniqueName commented 4 years ago

We can probably implement something like

typedef MyDynamicStruct = { knownField:String } & Dynamic<Int>;

Opinions?

kLabz commented 4 years ago

Yes please! (with support for Dynamic<Any> too of course)

zabojad commented 4 years ago

@RealyUniqueName thank you for your replies.

Just for illustrating my need, I was wondering how to translate this typescript interface into a haxe extern: react-intl's injectIntl

Your first proposition doesn't help me in my particular case as I'm never actually building the MyDynamicStruct object as it is the extern lib which does that.

haxe typedef MyDynamicStruct = { knownField:String } & Dynamic<Int>;

I'm sorry but I do not understand this notation :

What I need would probably look more like how we declare type parameters, however not for declaring a generic type but rather for declaring a property name instead:

typedef WrappedComponentProps<DynPropName = 'defaultPropName'> = {
  [DynPropName]: SomeKnownShape;
};

... that we could extend from in other typedef:

typedef MyCompProps = {
  > WrappedComponentProps<'intl'>,
  myCompPublicProp:String,
  // ...
}

That could be limited to externs only if that makes it more feasible :D...

kLabz commented 4 years ago

There's not much you can do without making wrapComponent a macro; you cannot use a runtime value from options argument to build a compile-time type otherwise

zabojad commented 4 years ago

Yhea obviously, it's too tricky...

And what does Dynamic<Int> means? For me and until now, Dynamic was simply Dynamic :)... Does it "reduce" the Dynamic to allow it to have only unknown number of Int fields?

zabojad commented 4 years ago

@kLabz it's too tricky but thinking about it, it feasible with typescript and there's no runtime checking with ts neither... However the way it's feasible is very verbose I agree:

type WrappedComponentProps<IntlPropName extends string = 'intl'> = {
  [k in IntlPropName]: IntlShape;
};

type WithIntlProps<P> = Omit<P, keyof WrappedComponentProps> & {
  forwardedRef?: React.Ref<any>;
};

function injectIntl<
  IntlPropName extends string = 'intl',
  P extends WrappedComponentProps<IntlPropName> = WrappedComponentProps<any>
>(
  WrappedComponent: React.ComponentType<P>,
  options?: Opts<IntlPropName>
): React.ComponentType<WithIntlProps<P>> & {
  WrappedComponent: typeof WrappedComponent;
};

Then I also think it is anyway an edge case and we can probably live without such feature anyway...

kLabz commented 4 years ago

Dynamic<T> has been around for a long time, you can find out more about it on https://haxe.org/manual/types-dynamic-with-type-parameter.html

zabojad commented 4 years ago

Yhea, I rarely used Dynamic (as less as I can) and never realized there was a type parameter...