rust-typescript-type-def icon indicating copy to clipboard operation
rust-typescript-type-def copied to clipboard

Support for custom bounds

Open nappa85 opened this issue 2 years ago • 4 comments

I need to be able to define custom bounds, exactly like #[serde(bounds)]. E.g.:

trait EntityKind {
    type KeyKind;
}

#[derive(serde::Serialize, typescript_type_def::TypeDef)]
pub struct EntityKey<Kind> {
    some_accessory_field: usize,
    kind: Kind,
}

#[derive(serde::Serialize, typescript_type_def::TypeDef)]
pub struct Entity<Kind> {
    some_accessory_field: usize,
    kind: Kind,
}

#[derive(serde::Serialize, typescript_type_def::TypeDef)]
#[serde(bound = "Kind: serde::Serialize, Kind::KeyKind: serde::Serialize")]
#[type_def(bound = "Kind: typescript_type_def::TypeDef, Kind::KeyKind: typescript_type_def::TypeDef")]
struct ConcreteEntity<Kind: EntityKind> {
    key: EntityKey<Kind::KeyKind>,
    value: Entity<Kind>,
}

This code won't work, you get a duplicate field error on second bound definition.

Doing everything in a single bound, like

#[derive(serde::Serialize, typescript_type_def::TypeDef)]
#[serde(
    bound = "Kind: serde::Serialize + typescript_type_def::TypeDef, Kind::KeyKind: serde::Serialize + typescript_type_def::TypeDef"
)]
struct ConcreteEntity<Kind: EntityKind> {
    key: EntityKey<Kind::KeyKind>,
    value: Entity<Kind>,
}

doesn't work anyway, because bound is ignored by TypeDef proc-macro.

At the moment the only workaround I've found is to write TypeDef trait implementation by hand.

nappa85 avatar Oct 26 '23 11:10 nappa85

Maybe I'm missing something, but these bounds seem unnecessary? The TypeDef derive macro already adds TypeDef bounds to all the generic arguments, and the Serialize bound isn't needed for implementing TypeDef. Does this work?

#[derive(serde::Serialize, typescript_type_def::TypeDef)]
#[serde(bound = "Kind: serde::Serialize, Kind::KeyKind: serde::Serialize")]
struct ConcreteEntity<Kind: EntityKind> {
    key: EntityKey<Kind::KeyKind>,
    value: Entity<Kind>,
}

dbeckwith avatar Oct 26 '23 21:10 dbeckwith

Tried it myself, I see the issue, the Kind::KeyKind type needs a TypeDef bound. Yeah this seems like an analogous problem to what the bound option for serde is solving. I can work on adding this.

dbeckwith avatar Oct 26 '23 21:10 dbeckwith

Actually, this brings up an interesting problem with how associated types are handled. How would you write a Typescript generic type for ConcreteEntity that's equivalent to the Rust one?

type ConcreteEntity<Kind> = {
  key: EntityKey<?>,
  value: Entity<Kind>,
}

Typescript has no equivalent for Kind::EntityKind since it does not have associated types.

I'm curious what the hand-written type definition you had looked like.

dbeckwith avatar Oct 26 '23 21:10 dbeckwith

That's a good question... I'm kinda alien to the typescript world, I'm just trying to expose data from a WASM module and let frontend people live happily with it...

In facts this code generates a wrong definition:

trait EntityKind {
    type KeyKind;
}

#[derive(serde::Serialize, typescript_type_def::TypeDef)]
pub struct EntityKey<Kind> {
    some_accessory_field: usize,
    kind: Kind,
}

#[derive(serde::Serialize, typescript_type_def::TypeDef)]
pub struct Entity<Kind> {
    some_accessory_field: usize,
    kind: Kind,
}

#[derive(serde::Serialize, typescript_type_def::TypeDef)]
#[serde(bound = "Kind: serde::Serialize, Kind::KeyKind: serde::Serialize")]
struct ConcreteEntity<Kind: EntityKind>
where
    Kind::KeyKind: typescript_type_def::TypeDef,
{
    key: EntityKey<Kind::KeyKind>,
    value: Entity<Kind>,
}

#[derive(serde::Serialize, typescript_type_def::TypeDef)]
struct ConcreteEntityKind1 {}

impl EntityKind for ConcreteEntityKind1 {
    type KeyKind = usize;
}

#[derive(serde::Serialize, typescript_type_def::TypeDef)]
struct ConcreteEntityKind2 {}

impl EntityKind for ConcreteEntityKind2 {
    type KeyKind = String;
}

generated definition:

// AUTO-GENERATED by typescript-type-def

export default types;
export namespace types {
    export type Usize = number;
    export type EntityKey<Kind> = {
        "some_accessory_field": types.Usize;
        "kind": Kind;
    };
    export type Entity<Kind> = {
        "some_accessory_field": types.Usize;
        "kind": Kind;
    };
    export type ConcreteEntityKind1 = {
    };
    export type ConcreteEntity<Kind> = {
        "key": types.EntityKey<types.Usize>;
        "value": types.Entity<Kind>;
    };
    export type ConcreteEntityKind2 = {
    };
}

nappa85 avatar Oct 27 '23 08:10 nappa85