Skip to content

Support embedding of helper methods inside #[cgp_impl] #184

@soareschen

Description

@soareschen

Support embedding of methods inside #[cgp_impl]

Overview

Similar to #183, to simplify the developer experience of defining blanket implementations in CGP, we need to support embedding getter and helper methods inside #[cgp_impl] in similar ways as #[cgp_auto_impl].

For example, given the following Greeter trait:

#[cgp_component(Greeter)]
pub trait CanGreet {
    fn greet(&self);
}

We should be able to define a GreetName provider as follows:

#[cgp_impl(GreetName)]
impl Greeter {
    #[getter]
    fn name(&self) -> &str;

    #[helper]
    fn capitalize(value: &str) -> String { ... }

    fn greet(&self) {
        println!("Hello, {}!", Self::capitalize(self.name()));
    }
}

There are two simplifications inside the new #[cgp_impl]. First, the generic Context type can be completely omitted, so that it look less like blanket implementation and more like class implementation. Secondly, the #[getter] and #[helper] attributes allow us to embed a helper methods directly inside the trait impl.

Generated Code

Behind the scene, since Rust does not allow additional methods to be defined within a trait impl, we need to first desugar it as a separate trait. We can do this by using #[cgp_auto_impl], and then including that auto trait inside the provider implementation. So it would become something like:

trait __Impl_Greeter_GreetName {
    fn name(&self) -> &str;

    fn capitalize(value: &str) -> String;

    fn greet(&self);
}

impl<Context> __Impl_Greeter_GreetName for Context
where
    Context: HasField<Symbol!("name"), Value = String>,
{
    fn name(&self) -> &str {
        self.get_field(PhantomData).as_ref()
    }

    fn capitalize(value: &str) -> String { ... }

    fn greet(&self) {
        println!("Hello, {}!", Self::capitalize(self.name()));
    }
}

impl<Context> Greeter<Context> for GreetName
where
    Context: __Impl_Greeter_GreetName,
{
    fn greet(context: &Context) {
        __Impl_Greeter_GreetName::greet(context)
    }
}

Currently, it is not very clear whether we should also include the original provider implementation within the blanket implementation, or put it directly inside the provider implementation. My intuition is that it is probably better to have everything inside the helper trait, as this makes it possible the helper methods to call the provider methods.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions