CosmWasm / sylvia

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

Generics in Interface allowed? #217

Closed taitruong closed 5 months ago

taitruong commented 1 year ago

I have something like this:

#[interface]
pub trait MyInterface<T>
where
    T: Serialize,
{
}

But when adding this to contract, I get this error:

error[E0107]: enum takes 1 generic argument but 0 generic arguments were supplied
  --> contracts/my-contract/src/contract.rs:20:1
   |
20 | #[contract]
   | ^^^^^^^^^^^ expected 1 generic argument
   |
note: enum defined here, with 1 generic parameter: `T`
  --> contracts/my-contract/src/my_interface.rs:12:1
   |
12 | #[interface]
   | ^^^^^^^^^^^^
13 | pub trait MyInterface<T>
   |                    -
   = note: this error originates in the attribute macro `contract` which comes from the expansion of the attribute macro `interface` (in Nightly builds, run with -Z macro-backtrace for more info)
help: add missing generic argument
   |
20 | #T[contract]
   |  +

Contract:

#[entry_points]
#[contract]
#[error(ContractError)]
#[messages(crate::my_interface as MyInterface)]
impl MyContract<'_> {
}
taitruong commented 1 year ago

I also tried removing Generics in interface by using type aliases:

#[interface]
pub trait MyInterface {
    type Error: From<StdError>;
    type Msg: Serialize;

    #[msg(exec)]
    fn foo(&self, ctx: ExecCtx, msg: Self::Msg) -> StdResult<Response> {
    }

But I get this error:

no variant named Msg found for enum MyInterfaceExecMsg

Seems like Self cant be used in param?

taitruong commented 1 year ago

Trying sylvia v0.7.1, seems like generics aren't supported at various places? Like here:

pub struct MyContract<'a, T> {
    pub(crate) msgs: Map<'a, String, T>,
}

#[entry_points]
#[contract]
impl MyContract<'static, ExecMsg> {
    pub const fn new() -> Self {
        Self {
            msgs: Map::new("msgs"),
        }
    }

    #[msg(instantiate)]
    pub fn instantiate(&self, _ctx: InstantiateCtx) -> StdResult<Response> {
        Ok(Response::default())
    }
}

Error:

error[E0107]: missing generics for struct contract::MyContract --> contracts/my-contract/src/contract.rs:15:6 15 impl MyContract<'static, ExecMsg> { ^^^^^^^^^^ expected 1 generic argument
note: struct defined here, with 1 generic parameter: T --> contracts/my-contract/src/contract.rs:8:12 8 pub struct MyContract<'a, T> { ^^^^^^^^^^ - help: add missing generic argument
15 impl MyContract<'static, ExecMsg> {
+++
jawoznia commented 1 year ago

Hi @taitruong could you share your code? Also did it work for you in previous versions of sylvia and failed while migrating to the 0.7.1?

hashedone commented 1 year ago

@taitruong generic traits should be valid interfaces, but they are tricky to handle. Even contracts should be generic, but at the end of the day, for the contract, there has to be a specific set of types that are passed as generic parameters so the final messages are well-defined.

I don't remember the implementation, but what I see in your question is:

#[messages(crate::my_interface as MyInterface)]

This sounds off to me - I don't see any generic argument list, so when contract messages are generated, Sylvia doesn't know what types should be used instead of generics. This is not well documented in Sylvia, but looking at the code, it should look like this:

#[messages(crate::my_interface: exec<T1, T2>, query<T3, T4> as MyInterface)]

The idea is that when Sylvia generates enum messages, it would include generics from the interface used by those messages, so as an example:

#[interface]
trait MyInterface<T: Serialize + Deserialize, U: Serialize + Deserialize, V: Serialize + Deserialize> {
  #[msg(exec)]
  fn my_exec(ctx: ExecCtx, t: T, v: V) -> StdResult<Response>;

  #[msg(query)]
  fn my_query(ctx: QueryCtx, u: U, v: V) -> StdResult<QueryResp>;
}

Should generate messages for this interface equivalent to:

enum ExecMsg<T: Serialize + Deserialize, V: Serialize + Deserialize> {
  MyExec { t: T, v: V }
}

enum QueryMsg<U: Serialize + Deserialize, V: Serialize + Deserialize> {
  MyQuery { u: U, v: V }
}

Note that only generics. which are used by enum are included in the message - this is to avoid errors with unused generics. The other option would be to add the additional variant like __Phantom(PhantomData<(T, U, V)>) just to use the generics - it might be a better approach to make usage cleaner (used don't need to know which generics are used where it makes API more stable) but honestly - we didn't put too much thought into that yet, as there we more urgent needs, so API here might change.

Anyway - for the contract implementation, the #[contract] macro has to know precisely how to monomorphise generics in the interface, and that is why there are exec<...> and query<...> additions to the #[message] attribute which would be used why naming the exec/query message types for the contract which are missing in your example.

The best would be if you provided us with minimal failing examples - it would help us verify if you met the case we missed. Right now, developing Sylvia, we prioritize cases used by actual developers, so if you have this issue, which we didn't need before, we can focus on improving this particular thing. Or if there is a case that all works fine, but documentation is not good enough, we are happy to improve it.

taitruong commented 1 year ago

Hi, I've create a simple example here: https://github.com/taitruong/sylvia-generics-example

First commit is without generics and it works. Second commit trying to add a simple generic - breaking code.

taitruong commented 11 months ago

So cool seeing Sylvia will support generics (#223). Once this is complete, I'll be happy to extensively test and use it. Imo this would be the missing part for using Sylvia 100% in production.

jawoznia commented 10 months ago

@taitruong sylvia 0.9.0 released featuring support for generics.

Please visit MIGRATING.md as we changed the way we generate the code.

Everything sylvia generates is put in the sv module.

kulikthebird commented 5 months ago

The article about assoc types vs. generics in traits can be found here: https://medium.com/confio/generics-vs-associated-types-in-sylvia-traits-design-689a16e09e56