Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incorrect type is generated when using generics #27

Open
jquesada2016 opened this issue Dec 14, 2021 · 2 comments
Open

Incorrect type is generated when using generics #27

jquesada2016 opened this issue Dec 14, 2021 · 2 comments

Comments

@jquesada2016
Copy link

jquesada2016 commented Dec 14, 2021

Using generics in any of the states causes the automaton's type to be stripped away. Take the following example:

use typestate::typestate;

#[typestate]
mod my_state {
    #[automaton]
    pub struct Automaton;

    #[state]
    pub struct State1<T>(T);

    pub trait State1 {
        fn new<T>() -> State1<T>
        where
            T: Default;

        fn to_state_2(self) -> State2;
    }

    impl<T> State1State for Automaton<State1<T>> {
        fn new<U>() -> State1<U>
        where
            U: Default,
        {
            State1(U::default())
        }

        fn to_state_2(self) -> Automaton<State2> {
            Automaton { state: State2 }
        }
    }

    #[state]
    pub struct State2;

    pub trait State2 {
        fn done(self);
    }

    impl State2State for Automaton<State2> {
        fn done(self) {}
    }
}

fn t() {
    use my_state::*;
    let s1: State1<()> = Automaton::<State1<()>>::new();
}

fn main() {}

cargo expand output:

#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2018::*;
#[macro_use]
extern crate std;
use typestate::typestate;
///```mermaid
///stateDiagram-v2
///[*] --> State1 : new
///State1 --> State2 : to_state_2
///State2 --> [*] : done
///```
mod my_state {
    pub struct Automaton<State: AutomatonState> {
        pub state: State,
    }
    pub struct State1<T>(T);
    pub trait State1State {
        fn new<T>() -> State1<T>
        where
            T: Default;
        #[must_use]
        fn to_state_2(self) -> Automaton<State2>;
    }
    impl<T> State1State for Automaton<State1<T>> {
        fn new<U>() -> State1<U>
        where
            U: Default,
        {
            State1(U::default())
        }
        fn to_state_2(self) -> Automaton<State2> {
            Automaton { state: State2 }
        }
    }
    pub struct State2;
    pub trait State2State {
        fn done(self);
    }
    impl State2State for Automaton<State2> {
        fn done(self) {}
    }
    #[doc(hidden)]
    mod __private {
        pub trait AutomatonState {}
    }
    pub trait AutomatonState: __private::AutomatonState {}
    impl<__T: ?::core::marker::Sized> AutomatonState for __T where __T: __private::AutomatonState {}
    impl<T> __private::AutomatonState for State1<T> {}
    impl __private::AutomatonState for State2 {}
}
fn t() {
    use my_state::*;
    let s1: State1<()> = Automaton::<State1<()>>::new();
}
fn main() {}

We see that Automaton::<State1<()>>::new() yields us a type State1<()> instead of the correct type Automaton<State1<()>>. If we remove the generic parameters to State1, all is well again.

@jmg-duarte
Copy link
Contributor

I found the issue. Consider the following minimal example:

use typestate::typestate;

#[typestate]
mod my_state {
    #[automaton]
    pub struct Automaton;

    #[state]
    pub struct S<T>(T);

    pub trait S {
        fn new<T>() -> S<T>;
        fn end(self);
    }
}

fn main() {
}

Using cargo expand we get the following output (removing some of the fluff):

mod my_state {
    pub struct Automaton<State: AutomatonState> {
        pub state: State,
    }
    pub struct State1<T>(T);
    pub trait State1State {
        fn new<T>() -> State1<T>;
        fn end(self);
    }
    #[doc(hidden)]
    mod __private {
        pub trait AutomatonState {}
    }
    pub trait AutomatonState: __private::AutomatonState {}
    impl<__T: ?::core::marker::Sized> AutomatonState for __T where __T: __private::AutomatonState {}
    impl<T> __private::AutomatonState for State1<T> {}
}
fn main() {}

The problem lies in the expanded output for the new declaration:

fn new<T>() -> State1<T>;

Which should instead be:

fn new<T>() -> Automaton<State1<T>>;

@jmg-duarte
Copy link
Contributor

On another note, traits (currently) are not implemented inside the module, as the module only serves as a declaration of the typestate.
This limitation is due to the amount of extra code the macro needs to process and the consistency between the declaration and implementation DSL. Outside the module you need to be more verbose about the states, which usually means make use of Automaton<...> returns and others.
If you have any idea on how to merge both please open an issue!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants