CosmWasm / sylvia

CosmWasm smart contract framework
Apache License 2.0
93 stars 14 forks source link

Generics in interface implementation #263

Closed jawoznia closed 7 months ago

jawoznia commented 10 months ago

With version 0.9.0 users are able to define generics in their contracts and interfaces. Although contracts generics can be set as any concrete types by users importing these contracts to their own it is impossible to forward these concrete types to the implemented interfaces.

Complexity

This functionality has to cover a lot of edge cases and will not be easy to implemented.

Generics passed to the interface implemented on non-generic contract

This should be fairly simple as there is no intersection between generics from the interface and the contract.

#[contract]
impl<ExecT, QueryT> Generic<ExecT, QueryT> for NonGeneric {}

Concrete types passed to the interface implemented on non-generic contract

At this stage we would have to detect which generic arguments are also generic parameters defined in impl block. Mechanism for that should be fairly simple, but good coverage is required to detect every place it should be appllied.

#[contract]
impl Generic<SvCustomMsg, SvCustomMsg> for NonGeneric {}

Generic types passed to the interface implemented on generic contract

Here generics might overlap in generated code. We have to filter the duplications. GlueMessage will for sure require change as it generates the wrapper message for exec and query.

#[contract]
impl<T1, T2, T3> Generic<T1, T2> for Generic<T1, T2, T3> {}

Concrete types passed to the interface implemented on generic contract

Same as in case of non-generic contract we have to detect the concrete types and not pass them to generated types as generics.

#[contract]
impl<T1, T2, T3> Generic<SvCustomMsg, SvCustomMsg> for Generic<T1, T2, T3> {}

Separate generic types passed to the interface

Some types passed to the interface might be generics and some concrete types. We should filter out the concrete types from the generics.

Behavior differ in execs and queries and even in parameters and return types and we have to cover all of these cases.

#[contract]
impl<T1, T2, T3> Generic<T1, SvCustomMsg> for Generic<T1, T2, T3> {}

Duplicated type in signatures

There is a possibility that message will have two parameters that are of the same generic type. While parsing used generics we should forward it only once to generated types and traits.

impl<ParamT>... {
    #[msg(..)]
    pub fn msg(&self, ctx: Ctx, param1: ParamT, param2: ParamT) -> ... {}
}

or

impl<ParamT>... {
    #[msg(..)]
    pub fn msg(&self, ctx: Ctx, param: ParamT) -> ... {}

    #[msg(..)]
    pub fn msg(&self, ctx: Ctx, param: ParamT) -> ... {}
}

Should generate

pub enum Msg<ParamT> {}

When implementing the interface instead of forwarding the generics we could use a concrete type. In current behavior only if types of two parameters were the same one would be omitted which in case of a single generic parameter ParamT like above would be good. However in the example below both <String, String> should be forwarded to the Querier and other types generated by the interface macro. It should be very hard to achieve support for both of these cases and solution should be to use the InterfaceApi and forward there all the generic arguments.

pub trait SomeTrait<Param1T, Param2T> {
    #[msg(..)]
    pub fn msg(&self, ctx: Ctx, param1: Param1T, param2: Param1T) -> ... {}
}

impl SomeTrait<String, String> for Contract {
    #[msg(..)]
    pub fn msg(&self, ctx: Ctx, param1: String, param2: String) -> ... {}
}

Multiple messages

Currently the examples consider single message per type. It should be confirmed that multiple messages are supported. I think two messages per type with one generic unique and one shared should cover all of the cases. Unfortunately this most likely will make the examples unreadable due to overflow of generic types.

impl<Param1T, Param2T, Param3T>... {
    #[msg(..)]
    pub fn msg(&self, ctx: Ctx, param1: Param1T, param2: Param2T) -> ... {}

    #[msg(..)]
    pub fn msg(&self, ctx: Ctx, param1: Param2T, param2: Param3T) -> ... {}
}
hashedone commented 7 months ago

Closing in favor of proper Assoc Types in interfaces - generic interfaces will not support.