CosmWasm / sylvia

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

Generics limitations #274

Closed jawoznia closed 6 months ago

jawoznia commented 9 months ago

Concrete uniqueness

Contract call on interface implementation generates implementation of interface::Querier on contract::BoundQuerier. This requires us to determine which generics should be forwarded to the interface::Querier. In case of generic values it's easy as we only have to determine which ones are used in the query methods. We are only interested in unique types and can use the same logic from interface macro.

Problem appears when we try to use concrete types. If for below interface messages with four generic types [QueryT1, QueryT2, QueryT3, RetT]

    #[msg(query)]
    fn custom_generic_query_one(
        &self,
        ctx: QueryCtx<CustomQueryT>,
        msg1: Query1T,
        msg2: Query2T,
    ) -> Result<RetT, Self::Error>;

    #[msg(query)]
    fn custom_generic_query_two(
        &self,
        ctx: QueryCtx<CustomQueryT>,
        msg1: Query2T,
        msg2: Query3T,
    ) -> Result<RetT, Self::Error>;

we will use concrete types as below

    #[msg(query)]
    fn custom_generic_query_one(
        &self,
        _ctx: QueryCtx<SvCustomQuery>,
        _msg1: sylvia::types::SvCustomMsg,
        _msg2: sylvia::types::SvCustomMsg,
    ) -> StdResult<SvCustomMsg> {
        Ok(SvCustomMsg {})
    }

    #[msg(query)]
    fn custom_generic_query_two(
        &self,
        _ctx: QueryCtx<SvCustomQuery>,
        _msg1: sylvia::types::SvCustomMsg,
        _msg2: sylvia::types::SvCustomMsg,
    ) -> StdResult<SvCustomMsg> {
        Ok(SvCustomMsg {})
    }

Sylvia will think that there are only two generic types and will try to forward them to the Querier.

    impl<'a, C: sylvia::cw_std::CustomQuery>
        custom_and_generic::sv::Querier<sylvia::types::SvCustomMsg, SvCustomMsg>
        for crate::contract::sv::BoundQuerier<'a, C>
    {
        fn custom_generic_query_one(
            &self,
            _msg1: sylvia::types::SvCustomMsg,
            _msg2: sylvia::types::SvCustomMsg,
        ) -> Result<SvCustomMsg, sylvia::cw_std::StdError> {
            let query = custom_and_generic::sv::QueryMsg:: <sylvia::types::SvCustomMsg,SvCustomMsg, > ::custom_generic_query_one(_msg1,_msg2);
            self.querier().query_wasm_smart(self.contract(), &query)
        }
        fn custom_generic_query_two(
            &self,
            _msg1: sylvia::types::SvCustomMsg,
            _msg2: sylvia::types::SvCustomMsg,
        ) -> Result<SvCustomMsg, sylvia::cw_std::StdError> {
            let query = custom_and_generic::sv::QueryMsg:: <sylvia::types::SvCustomMsg,SvCustomMsg, > ::custom_generic_query_two(_msg1,_msg2);
            self.querier().query_wasm_smart(self.contract(), &query)
        }
    }

If we would use the same path here f.e. SvCustomMsg in place of sylvia::types::SvCustomMsg only a single type would be forwarded as generic.

This provides us an workaround allowing user to work with aliases/different versions of paths, but it's a bad solution and we should find a better one.

Solution here might be to drop unique filtering for generic arguments, but it will cause more troubles as in above example we would jump from two generics to six (while expecting four).

Another way would be to provide a new attribute here, but it would only serve as a way to provide generics defined for a trait and I don't see as a much better solution then using type aliases.

We also can't distribute proper generics to Querier in the InterfaceApi as this is a trait.

At this moment I don't see a proper way to fix it and we most likely will have to fix it after the 1.0.0 release.

jawoznia commented 9 months ago

Found while implementing #263

hashedone commented 9 months ago

Ok, I was thinking through that slowly. The question here is - why the querier is not generic over all the generic types? The answer is - because some of the types are not used at all, which causes compilation error. If the querier trait would always be generic over exactly the same types the contract is, there would not really be a problem. So what is the reason we dont use some generics? The reason is - because querier is not actually using all the generics. It uses only those used by query functions, but if you have some generics used only by execs or sudos, you do not need those for queriers. I was looking at the solutions, and then I got hit by one fact - how many implementations of this querier can exist on the single contract type (monomorphized one)? Obviously - exactly one. It is due to a fact, that we would have couple functions for the same query message. This is invalid. We do that for traits for couple reasons, and none of the reasons are good I think. You can never have a contract properly implementing multiple interfaces. Contract can be generic, for extensibility purposes, but for interfaces we would always need to use associated types. Ill explain it more on call I think

hashedone commented 6 months ago

Closed, generics not to be supported: #303