├── .gitattributes ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.toml ├── README.md ├── README.tpl ├── derive_state_machine_future ├── CONTRIBUTING.md ├── Cargo.toml └── src │ ├── ast.rs │ ├── codegen.rs │ ├── lib.rs │ └── phases.rs ├── publish.sh ├── src ├── compile_fail_tests.rs ├── lib.rs └── transition.rs └── tests ├── call_to_context.rs ├── cargo_readme_up_to_date.rs ├── context_and_derives.rs ├── context_start_in_method.rs ├── context_start_method.rs ├── derives.rs ├── different_kinds_of_enums.rs ├── generics.rs ├── github_issue_25.rs ├── into_after.rs ├── name_collisions.rs ├── overlapping_states.rs ├── polling.rs ├── private_type_in_description.rs ├── ready_and_error.rs ├── start.rs ├── start_in.rs ├── state_conversions.rs ├── take_context_on_error.rs ├── take_context_on_ready.rs ├── take_context_value_on_ready.rs ├── take_in_poll_without_state_change.rs ├── transitions.rs └── visibility.rs /.gitattributes: -------------------------------------------------------------------------------- 1 | README.md -diff -merge 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /derive_state_machine_future/target/ 3 | **/*.rs.bk 4 | Cargo.lock 5 | default.nix 6 | .direnv 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | 3 | cache: cargo 4 | 5 | before_script: 6 | - cargo install -f cargo-readme 7 | 8 | rust: 9 | - 1.24.0 10 | - stable 11 | - beta 12 | - nightly 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Unreleased 2 | 3 | Released YYYY/MM/DD 4 | 5 | ## Added 6 | 7 | * TODO (or remove section if none) 8 | 9 | ## Changed 10 | 11 | * TODO (or remove section if none) 12 | 13 | ## Deprecated 14 | 15 | * TODO (or remove section if none) 16 | 17 | ## Removed 18 | 19 | * TODO (or remove section if none) 20 | 21 | ## Fixed 22 | 23 | * TODO (or remove section if none) 24 | 25 | ## Security 26 | 27 | * TODO (or remove section if none) 28 | 29 | -------------------------------------------------------------------------------- 30 | 31 | # 0.2.0 32 | 33 | Released 2018/11/10 34 | 35 | ## Added 36 | 37 | * Add `Context` functionality. Thanks [@thomaseizinger][]. [#27][] 38 | * State machine can be constructed in any state with `start_in`. Thanks [@thomaseizinger][]. [#31][] 39 | * Make generated state enum inherent visibility. Thanks [@thomaseizinger][]. [#30][] 40 | 41 | ## Changed 42 | 43 | * Derived traits are now only implemented for the state enum. Thanks [@thomaseizinger][]. [#27][] 44 | 45 | ## Fixed 46 | 47 | * Do not generate trait methods with anonymous parameters. [#34][] 48 | 49 | -------------------------------------------------------------------------------- 50 | 51 | # 0.1.8 52 | 53 | Released 2018/10/21 54 | 55 | ## Changed 56 | 57 | * Updated darling and syn to latest version. Thanks [@hcpl][]. [#26][] 58 | 59 | ## Fixed 60 | 61 | * Type parameter missing when used through a projection [#25][] 62 | 63 | -------------------------------------------------------------------------------- 64 | 65 | # 0.1.7 66 | 67 | Released 2018/06/04 68 | 69 | ## Added 70 | 71 | * Added [@bkchr][] to the team! \o/ 72 | 73 | ## Fixed 74 | 75 | * Fixed potential "field is never used" warning from inside the generated code. [#19][] 76 | 77 | [#19]: https://github.com/fitzgen/state_machine_future/pull/19 78 | [@bkchr]: https://github.com/bkchr 79 | 80 | -------------------------------------------------------------------------------- 81 | 82 | # 0.1.6 83 | 84 | Released 2018/02/12 85 | 86 | ## Added 87 | 88 | * Added the `transition!(..)` macro to make state transitions a little bit less 89 | boilerplate-y. [#16][] 90 | 91 | [#16]: https://github.com/fitzgen/state_machine_future/pull/16 92 | 93 | -------------------------------------------------------------------------------- 94 | 95 | # 0.1.5 96 | 97 | Released 2018/1/4 98 | 99 | ## Added 100 | 101 | * Support for heterogeneous usage of generic type- and lifetime-parameters 102 | across states. Previously, every state had to use every generic parameter 103 | (requiring `PhantomData` when it didn't need to use one). Now, states that 104 | don't need a generic parameter don't have to use it. [#10][] 105 | 106 | [#10]: https://github.com/fitzgen/state_machine_future/pull/10 107 | 108 | # 0.1.4 109 | 110 | Released 2017/12/19 111 | 112 | # Fixed 113 | 114 | * Using non-`pub` types within a state no longer causes compilation errors 115 | related to type visibility. [#6][] 116 | 117 | [#6]: https://github.com/fitzgen/state_machine_future/issues/6 118 | 119 | # 0.1.3 120 | 121 | Released 2017/12/12 122 | 123 | ## Fixed 124 | 125 | * Bounds on generic type parameters that were "inline", rather than in a `where` 126 | clause, work with `#[derive(StateMachineFuture)] now. [#4][] 127 | 128 | [#4]: https://github.com/fitzgen/state_machine_future/pull/4 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to `state_machine_future` 2 | 3 | Hi! We'd love to have your contributions! If you want help or mentorship, reach 4 | out to us in a GitHub issue, or ping `fitzgen` 5 | in [#rust on irc.mozilla.org](irc://irc.mozilla.org#rust) and introduce 6 | yourself. 7 | 8 | 9 | 10 | 11 | 12 | - [Code of Conduct](#code-of-conduct) 13 | - [Building](#building) 14 | - [Testing](#testing) 15 | - [Automatic code formatting](#automatic-code-formatting) 16 | 17 | 18 | 19 | ## Code of Conduct 20 | 21 | We abide by the [Rust Code of Conduct][coc] and ask that you do as well. 22 | 23 | [coc]: https://www.rust-lang.org/en-US/conduct.html 24 | 25 | ## Building 26 | 27 | ``` 28 | $ cargo build 29 | ``` 30 | 31 | ## Testing 32 | 33 | The tests require `cargo-readme` to be installed: 34 | ``` 35 | $ cargo install cargo-readme 36 | ``` 37 | 38 | To run all the tests: 39 | 40 | ``` 41 | $ cargo test 42 | ``` 43 | 44 | ## Automatic code formatting 45 | 46 | We use [`rustfmt`](https://github.com/rust-lang-nursery/rustfmt) to enforce a 47 | consistent code style across the whole code base. 48 | 49 | You can install the latest version of `rustfmt` with this command: 50 | 51 | ``` 52 | $ rustup component add rustfmt-preview --toolchain nightly 53 | ``` 54 | 55 | Ensure that `~/.rustup/toolchains/$YOUR_HOST_TARGET/bin/` is on your `$PATH`. 56 | 57 | Once that is taken care of, you can (re)format all code by running this command 58 | from the root of the repository: 59 | 60 | ``` 61 | $ cargo +nightly fmt 62 | ``` 63 | 64 | ## Pull Requests 65 | 66 | All pull requests must be reviewed and approved of by at least one [team](#team) 67 | member before merging. 68 | 69 | ## Team 70 | 71 | * `fitzgen` 72 | * `bkchr` 73 | 74 | Larger, more nuanced decisions about design, architecture, breaking changes, 75 | trade offs, etc are made by team consensus. In other words, decisions on things 76 | that aren't straightforward improvements or bug fixes to things that already 77 | exist in `state_machine_future`. If consensus can't be made, then `fitzgen` has 78 | the last word. 79 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Nick Fitzgerald ", "Bastian Köcher "] 3 | categories = ["asynchronous", "rust-patterns"] 4 | description = """ 5 | Easily create type-safe `Future`s from state machines — without the boilerplate. 6 | """ 7 | documentation = "https://docs.rs/state_machine_future" 8 | keywords = ["typestate", "derive", "state", "machine", "future"] 9 | license = "Apache-2.0/MIT" 10 | name = "state_machine_future" 11 | readme = "./README.md" 12 | repository = "https://github.com/fitzgen/state_machine_future" 13 | version = "0.2.0" 14 | 15 | [badges.travis-ci] 16 | repository = "fitzgen/state_machine_future" 17 | 18 | [dependencies] 19 | futures = "0.1.18" 20 | rent_to_own = "0.1.0" 21 | 22 | [dependencies.derive_state_machine_future] 23 | path = "./derive_state_machine_future/" 24 | version = "=0.2.0" 25 | 26 | [features] 27 | # For debugging purposes, print the generated code to stdout during 28 | # `derive(StateMachineFuture)` expansion. 29 | debug_code_generation = ["derive_state_machine_future/debug_code_generation"] 30 | 31 | [workspace] 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `state_machine_future` 2 | 3 | [![](https://docs.rs/state_machine_future/badge.svg)](https://docs.rs/state_machine_future/) [![](https://img.shields.io/crates/v/state_machine_future.svg)](https://crates.io/crates/state_machine_future) [![](https://img.shields.io/crates/d/state_machine_future.png)](https://crates.io/crates/state_machine_future) [![Build Status](https://travis-ci.org/fitzgen/state_machine_future.png?branch=master)](https://travis-ci.org/fitzgen/state_machine_future) 4 | 5 | Easily create type-safe `Future`s from state machines — without the boilerplate. 6 | 7 | `state_machine_future` type checks state machines and their state transitions, 8 | and then generates `Future` implementations and typestate[0][] 9 | boilerplate for you. 10 | 11 | * [Introduction](#introduction) 12 | * [Guide](#guide) 13 | * [Example](#example) 14 | * [Attributes](#attributes) 15 | * [Macro](#macro) 16 | * [Features](#features) 17 | * [License](#license) 18 | * [Contribution](#contribution) 19 | 20 | ### Introduction 21 | 22 | Most of the time, using `Future` combinators like `map` and `then` are a great 23 | way to describe an asynchronous computation. Other times, the most natural way 24 | to describe the process at hand is a state machine. 25 | 26 | When writing state machines in Rust, we want to *leverage the type system to 27 | enforce that only valid state transitions may occur*. To do that, we want 28 | *typestates*[0][]: types that represents each state in the state 29 | machine, and methods whose signatures only permit valid state transitions. But 30 | we *also* need an `enum` of every possible state, so we can treat the whole 31 | state machine as a single entity, and implement `Future` for it. But this is 32 | getting to be a *lot* of boilerplate... 33 | 34 | Enter `#[derive(StateMachineFuture)]`. 35 | 36 | With `#[derive(StateMachineFuture)]`, we describe the states and the possible 37 | transitions between them, and then the custom derive generates: 38 | 39 | * A typestate for each state in the state machine. 40 | 41 | * A type for the whole state machine that implements `Future`. 42 | 43 | * A concrete `start` method that constructs the state machine `Future` for you, 44 | initialized to its start state. 45 | 46 | * A state transition polling trait, with a `poll_zee_choo` method for each 47 | non-final state `ZeeChoo`. This trait describes the state machine's valid 48 | transitions, and its methods are called by `Future::poll`. 49 | 50 | Then, all *we* need to do is implement the generated state transition polling 51 | trait. 52 | 53 | Additionally, `#[derive(StateMachineFuture)]` will statically prevent against 54 | some footguns that can arise when writing state machines: 55 | 56 | * Every state is reachable from the start state: *there are no useless states.* 57 | 58 | * *There are no states which cannot reach a final state*. These states would 59 | otherwise lead to infinite loops. 60 | 61 | * *All state transitions are valid.* Attempting to make an invalid state 62 | transition fails to type check, thanks to the generated typestates. 63 | 64 | ### Guide 65 | 66 | Describe the state machine's states with an `enum` and add 67 | `#[derive(StateMachineFuture)]` to it: 68 | 69 | ```rust 70 | #[derive(StateMachineFuture)] 71 | enum MyStateMachine { 72 | // ... 73 | } 74 | ``` 75 | 76 | There must be one **start** state, which is the initial state upon construction; 77 | one **ready** state, which corresponds to `Future::Item`; and one **error** 78 | state, which corresponds to `Future::Error`. 79 | 80 | ```rust 81 | #[derive(StateMachineFuture)] 82 | enum MyStateMachine { 83 | #[state_machine_future(start)] 84 | Start, 85 | 86 | // ... 87 | 88 | #[state_machine_future(ready)] 89 | Ready(MyItem), 90 | 91 | #[state_machine_future(error)] 92 | Error(MyError), 93 | } 94 | ``` 95 | 96 | Any other variants of the `enum` are intermediate states. 97 | 98 | We define which state-to-state transitions are valid with 99 | `#[state_machine_future(transitions(...))]`. This attribute annotates a state 100 | variant, and lists which other states can be transitioned to immediately after 101 | this state. 102 | 103 | A final state (either **ready** or **error**) must be reachable from every 104 | intermediate state and the **start** state. Final states are not allowed to have 105 | transitions. 106 | 107 | ```rust 108 | #[derive(StateMachineFuture)] 109 | enum MyStateMachine { 110 | #[state_machine_future(start, transitions(Intermediate))] 111 | Start, 112 | 113 | #[state_machine_future(transitions(Start, Ready))] 114 | Intermediate { x: usize, y: usize }, 115 | 116 | #[state_machine_future(ready)] 117 | Ready(MyItem), 118 | 119 | #[state_machine_future(error)] 120 | Error(MyError), 121 | } 122 | ``` 123 | 124 | From this state machine description, the custom derive generates boilerplate for 125 | us. 126 | 127 | For each state, the custom derive creates: 128 | 129 | * A typestate for the state. The type's name matches the variant name, for 130 | example the `Intermediate` state variant's typestate is also named `Intermediate`. 131 | The kind of struct type generated matches the variant kind: a unit-style variant 132 | results in a unit struct, a tuple-style variant results in a tuple struct, and a 133 | struct-style variant results in a normal struct with fields. 134 | 135 | | State `enum` Variant | Generated Typestate | 136 | | ------------------------------------------------- | ------------------------------ | 137 | | `enum StateMachine { MyState, ... }` | `struct MyState;` | 138 | | `enum StateMachine { MyState(bool, usize), ... }` | `struct MyState(bool, usize);` | 139 | | `enum StateMachine { MyState { x: usize }, ... }` | `struct MyState { x: usize };` | 140 | 141 | * An `enum` for the possible states that can come after this state. This `enum` 142 | is named `AfterX` where `X` is the state's name. There is also a `From` 143 | implementation for each `Y` state that can be transitioned to after `X`. For 144 | example, the `Intermediate` state would get: 145 | 146 | ```rust 147 | enum AfterIntermediate { 148 | Start(Start), 149 | Ready(Ready), 150 | } 151 | 152 | impl From for AfterIntermediate { 153 | // ... 154 | } 155 | 156 | impl From for AfterIntermediate { 157 | // ... 158 | } 159 | ``` 160 | 161 | Next, for the state machine as a whole, the custom derive generates: 162 | 163 | * A state machine `Future` type, which is essentially an `enum` of all the 164 | different typestates. This type is named `BlahFuture` where `Blah` is the name 165 | of the state machine description `enum`. In this example, where the state 166 | machine description is named `MyStateMachine`, the generated state machine 167 | future type would be named `MyStateMachineFuture`. 168 | 169 | * A polling trait, `PollBordle` where `Bordle` is this state machine 170 | description's name. For each non-final state `TootWasabi`, this trait has a 171 | method, `poll_toot_wasabi`, which is like `Future::poll` but specialized to the 172 | current state. Each method takes conditional ownership of its state (via 173 | [`RentToOwn`][rent_to_own]) and returns a `futures::Poll` 174 | where `Error` is the state machine's error type. This signature *does not allow 175 | invalid state transitions*, which makes attempting an illegal state transition 176 | fail to type check. Here is the `MyStateMachine`'s polling trait, for example: 177 | 178 | ```rust 179 | trait PollMyStateMachine { 180 | fn poll_start<'a>( 181 | start: &'a mut RentToOwn<'a, Start>, 182 | ) -> Poll; 183 | 184 | fn poll_intermediate<'a>( 185 | intermediate: &'a mut RentToOwn<'a, Intermediate>, 186 | ) -> Poll; 187 | } 188 | ``` 189 | 190 | * An implementation of `Future` for that type. This implementation dispatches to 191 | the appropriate polling trait method depending on what state the future is 192 | in: 193 | 194 | * If the `Future` is in the `Start` state, then it uses `::poll_start`. 196 | 197 | * If it is in the `Intermediate` state, then it uses `::poll_intermediate`. 199 | 200 | * Etc... 201 | 202 | * A concrete `start` method for the description type (so `MyStateMachine::start` 203 | in this example) which constructs a new state machine `Future` type in its 204 | **start** state for you. This method has a parameter for each field in the 205 | **start** state variant. 206 | 207 | | Start `enum` Variant | Generated `start` Method | 208 | | ------------------------------- | ------------------------------------------------------------------- | 209 | | `MyStart,` | `fn start() -> MyStateMachineFuture { ... }` | 210 | | `MyStart(bool, usize),` | `fn start(arg0: bool, arg1: usize) -> MyStateMachineFuture { ... }` | 211 | | `MyStart { x: char, y: bool },` | `fn start(x: char, y: bool) -> MyStateMachineFuture { ... }` | 212 | 213 | Given all those generated types and traits, all we have to do is `impl PollBlah 214 | for Blah` for our state machine `Blah`. 215 | 216 | ```rust 217 | impl PollMyStateMachine for MyStateMachine { 218 | fn poll_start<'a>( 219 | start: &'a mut RentToOwn<'a, Start> 220 | ) -> Poll { 221 | // Call `try_ready!(start.inner.poll())` with any inner futures here. 222 | // 223 | // If we're ready to transition states, then we should return 224 | // `Ok(Async::Ready(AfterStart))`. If we are not ready to transition 225 | // states, return `Ok(Async::NotReady)`. If we encounter an error, 226 | // return `Err(...)`. 227 | } 228 | 229 | fn poll_intermediate<'a>( 230 | intermediate: &'a mut RentToOwn<'a, Intermediate> 231 | ) -> Poll { 232 | // Same deal as above... 233 | } 234 | } 235 | ``` 236 | 237 | ### Context 238 | 239 | The state machine also allows to pass in a context that is available in every `poll_*` method 240 | without having to explicitly include it in every one. 241 | 242 | The context can be specified through the `context` argument of the `state_machine_future` attribute. 243 | This will add parameters to the `start` method as well as to each `poll_*` method of the trait. 244 | 245 | ```rust 246 | #[macro_use] 247 | extern crate state_machine_future; 248 | extern crate futures; 249 | 250 | use futures::*; 251 | use state_machine_future::*; 252 | 253 | struct MyContext { 254 | 255 | } 256 | 257 | struct MyItem { 258 | 259 | } 260 | 261 | enum MyError { 262 | 263 | } 264 | 265 | #[derive(StateMachineFuture)] 266 | #[state_machine_future(context = "MyContext")] 267 | enum MyStateMachine { 268 | #[state_machine_future(start, transitions(Intermediate))] 269 | Start, 270 | 271 | #[state_machine_future(transitions(Start, Ready))] 272 | Intermediate { x: usize, y: usize }, 273 | 274 | #[state_machine_future(ready)] 275 | Ready(MyItem), 276 | 277 | #[state_machine_future(error)] 278 | Error(MyError), 279 | } 280 | 281 | impl PollMyStateMachine for MyStateMachine { 282 | fn poll_start<'s, 'c>( 283 | start: &'s mut RentToOwn<'s, Start>, 284 | context: &'c mut RentToOwn<'c, MyContext> 285 | ) -> Poll { 286 | 287 | // The `context` instance passed into `start` is available here. 288 | // It is a mutable reference, so are free to modify it. 289 | 290 | unimplemented!() 291 | } 292 | 293 | fn poll_intermediate<'s, 'c>( 294 | intermediate: &'s mut RentToOwn<'s, Intermediate>, 295 | context: &'c mut RentToOwn<'c, MyContext> 296 | ) -> Poll { 297 | 298 | // The `context` is available here as well. 299 | // It is the same instance. This means if `poll_start` modified it, those 300 | // changes will be visible to this method as well. 301 | 302 | unimplemented!() 303 | } 304 | } 305 | 306 | fn main() { 307 | let _ = MyStateMachine::start(MyContext { }); 308 | } 309 | ``` 310 | 311 | Same as for the state argument, the context can be taken through the `RentToOwn` type! 312 | However, be aware that once you take the context, the state machine will **always** return 313 | `Async::NotReady` **without** invoking the `poll_` methods anymore. The one exception to 314 | this is when the state machine is in a ready or error state, where it will resolve normally 315 | when polled if the context has been taken. 316 | 317 | That's it! 318 | 319 | ### Example 320 | 321 | Here is an example of a simple turn-based game played by two players over HTTP. 322 | 323 | ```rust 324 | #[macro_use] 325 | extern crate state_machine_future; 326 | 327 | #[macro_use] 328 | extern crate futures; 329 | 330 | use futures::{Async, Future, Poll}; 331 | use state_machine_future::RentToOwn; 332 | 333 | /// The result of a game. 334 | pub struct GameResult { 335 | winner: Player, 336 | loser: Player, 337 | } 338 | 339 | /// Some kind of simple turn based game. 340 | /// 341 | /// ```text 342 | /// Invite 343 | /// | 344 | /// | 345 | /// | accept invitation 346 | /// | 347 | /// | 348 | /// V 349 | /// WaitingForTurn --------+ 350 | /// | ^ | 351 | /// | | | receive turn 352 | /// | | | 353 | /// | +-------------+ 354 | /// game concludes | 355 | /// | 356 | /// | 357 | /// | 358 | /// V 359 | /// Finished 360 | /// ``` 361 | #[derive(StateMachineFuture)] 362 | enum Game { 363 | /// The game begins with an invitation to play from one player to another. 364 | /// 365 | /// Once the invited player accepts the invitation over HTTP, then we will 366 | /// switch states into playing the game, waiting to recieve each turn. 367 | #[state_machine_future(start, transitions(WaitingForTurn))] 368 | Invite { 369 | invitation: HttpInvitationFuture, 370 | from: Player, 371 | to: Player, 372 | }, 373 | 374 | // We are waiting on a turn. 375 | // 376 | // Upon receiving it, if the game is now complete, then we go to the 377 | // `Finished` state. Otherwise, we give the other player a turn. 378 | #[state_machine_future(transitions(WaitingForTurn, Finished))] 379 | WaitingForTurn { 380 | turn: HttpTurnFuture, 381 | active: Player, 382 | idle: Player, 383 | }, 384 | 385 | // The game is finished with a `GameResult`. 386 | // 387 | // The `GameResult` becomes the `Future::Item`. 388 | #[state_machine_future(ready)] 389 | Finished(GameResult), 390 | 391 | // Any state transition can implicitly go to this error state if we get an 392 | // `HttpError` while waiting on a turn or invitation acceptance. 393 | // 394 | // This `HttpError` is used as the `Future::Error`. 395 | #[state_machine_future(error)] 396 | Error(HttpError), 397 | } 398 | 399 | // Now, we implement the generated state transition polling trait for our state 400 | // machine description type. 401 | 402 | impl PollGame for Game { 403 | fn poll_invite<'a>( 404 | invite: &'a mut RentToOwn<'a, Invite> 405 | ) -> Poll { 406 | // See if the invitation has been accepted. If not, this will early 407 | // return with `Ok(Async::NotReady)` or propagate any HTTP errors. 408 | try_ready!(invite.invitation.poll()); 409 | 410 | // We're ready to transition into the `WaitingForTurn` state, so take 411 | // ownership of the `Invite` and then construct and return the new 412 | // state. 413 | let invite = invite.take(); 414 | let waiting = WaitingForTurn { 415 | turn: invite.from.request_turn(), 416 | active: invite.from, 417 | idle: invite.to, 418 | }; 419 | transition!(waiting) 420 | } 421 | 422 | fn poll_waiting_for_turn<'a>( 423 | waiting: &'a mut RentToOwn<'a, WaitingForTurn> 424 | ) -> Poll { 425 | // See if the next turn has arrived over HTTP. Again, this will early 426 | // return `Ok(Async::NotReady)` if the turn hasn't arrived yet, and 427 | // propagate any HTTP errors that we might encounter. 428 | let turn = try_ready!(waiting.turn.poll()); 429 | 430 | // Ok, we have a new turn. Take ownership of the `WaitingForTurn` state, 431 | // process the turn and if the game is over, then transition to the 432 | // `Finished` state, otherwise swap which player we need a new turn from 433 | // and request the turn over HTTP. 434 | let waiting = waiting.take(); 435 | if let Some(game_result) = process_turn(turn) { 436 | transition!(Finished(game_result)) 437 | } else { 438 | let next_waiting = WaitingForTurn { 439 | turn: waiting.idle.request_turn(), 440 | active: waiting.idle, 441 | idle: waiting.active, 442 | }; 443 | Ok(Async::Ready(next_waiting.into())) 444 | } 445 | } 446 | } 447 | 448 | // To spawn a new `Game` as a `Future` on whatever executor we're using (for 449 | // example `tokio`), we use `Game::start` to construct the `Future` in its start 450 | // state and then pass it to the executor. 451 | fn spawn_game(handle: TokioHandle) { 452 | let from = get_some_player(); 453 | let to = get_another_player(); 454 | let invitation = invite(&from, &to); 455 | let future = Game::start(invitation, from, to); 456 | handle.spawn(future) 457 | } 458 | ``` 459 | 460 | ### Attributes 461 | 462 | This is a list of all of the attributes used by `state_machine_future`: 463 | 464 | * `#[derive(StateMachineFuture)]`: Placed on an `enum` that describes a state 465 | machine. 466 | 467 | * `#[state_machine_future(derive(Clone, Debug, ...))]`: Placed on the `enum` 468 | that describes the state machine. This attribute describes which 469 | `#[derive(...)]`s to place on the generated `Future` type. 470 | 471 | * `#[state_machine_future(start)]`: Used on a variant of the state machine 472 | description `enum`. There must be exactly one variant with this attribute. This 473 | describes the initial starting state. The generated `start` method has a 474 | parameter for each field in this variant. 475 | 476 | * `#[state_machine_future(ready)]`: Used on a variant of the state machine 477 | description `enum`. There must be exactly one variant with this attribute. It 478 | must be a tuple-style variant with one field, for example `Ready(MyItemType)`. 479 | The generated `Future` implementation uses the field's type as `Future::Item`. 480 | 481 | * `#[state_machine_future(error)]`: Used on a variant of the state machine 482 | description `enum`. There must be exactly one variant with this attribute. It 483 | must be a tuple-style variant with one field, for example `Error(MyError)`. The 484 | generated `Future` implementation uses the field's type as `Future::Error`. 485 | 486 | * `#[state_machine_future(transitions(OtherState, AnotherState, ...))]`: Used on 487 | a variant of the state machine description `enum`. Describes the states that 488 | this one can transition to. 489 | 490 | ### Macro 491 | 492 | An auxiliary macro is provided that helps reducing boilerplate code for state 493 | transitions. So, the following code: 494 | 495 | ```Ok(Ready(NextState(1).into()))``` 496 | 497 | Can be reduced to: 498 | 499 | ```transition!(NextState(1))``` 500 | 501 | ### Features 502 | 503 | Here are the `cargo` features that you can enable: 504 | 505 | * `debug_code_generation`: Prints the code generated by 506 | `#[derive(StateMachineFuture)]` to `stdout` for debugging purposes. 507 | 508 | ### License 509 | 510 | Licensed under either of 511 | 512 | * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 513 | 514 | * [MIT license](http://opensource.org/licenses/MIT) 515 | 516 | at your option. 517 | 518 | ### Contribution 519 | 520 | See 521 | [CONTRIBUTING.md](https://github.com/fitzgen/state_machine_future/blob/master/CONTRIBUTING.md) 522 | for hacking. 523 | 524 | Unless you explicitly state otherwise, any contribution intentionally submitted 525 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 526 | dual licensed as above, without any additional terms or conditions. 527 | 528 | 529 | [0]: https://en.wikipedia.org/wiki/Typestate_analysis 530 | 531 | [rent_to_own]: https://crates.io/crates/rent_to_own 532 | 533 | -------------------------------------------------------------------------------- /README.tpl: -------------------------------------------------------------------------------- 1 | # `{{crate}}` 2 | {{readme}} 3 | -------------------------------------------------------------------------------- /derive_state_machine_future/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to `state_machine_future` 2 | 3 | Hi! We'd love to have your contributions! If you want help or mentorship, reach 4 | out to us in a GitHub issue, or ping `fitzgen` in 5 | [`#rust` on irc.mozilla.org](irc://irc.mozilla.org#rust) and introduce yourself. 6 | 7 | 8 | 9 | 10 | 11 | - [Code of Conduct](#code-of-conduct) 12 | - [Building](#building) 13 | - [Testing](#testing) 14 | - [Automatic code formatting](#automatic-code-formatting) 15 | 16 | 17 | 18 | ## Code of Conduct 19 | 20 | We abide by the [Rust Code of Conduct][coc] and ask that you do as well. 21 | 22 | [coc]: https://www.rust-lang.org/en-US/conduct.html 23 | 24 | ## Building 25 | 26 | ``` 27 | $ cargo build 28 | ``` 29 | 30 | ## Testing 31 | 32 | To run all the tests: 33 | 34 | ``` 35 | $ cargo test 36 | ``` 37 | 38 | ## Automatic code formatting 39 | 40 | We use [`rustfmt`](https://github.com/rust-lang-nursery/rustfmt) to enforce a 41 | consistent code style across the whole code base. 42 | 43 | You can install the latest version of `rustfmt` with this command: 44 | 45 | ``` 46 | $ cargo install -f rustfmt-nightly 47 | ``` 48 | 49 | Ensure that `~/.cargo/bin` is on your path. 50 | 51 | Once that is taken care of, you can (re)format all code by running this command: 52 | 53 | ``` 54 | $ cargo fmt 55 | ``` 56 | 57 | The code style is described in the `rustfmt.toml` file in top level of the repo. 58 | -------------------------------------------------------------------------------- /derive_state_machine_future/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Nick Fitzgerald ", "Bastian Köcher "] 3 | description = """ 4 | Custom derive implementation for the `state_machine_future` crate. Use that 5 | crate instead of this one directly. 6 | """ 7 | license = "Apache-2.0/MIT" 8 | name = "derive_state_machine_future" 9 | repository = "https://github.com/fitzgen/state_machine_future" 10 | version = "0.2.0" 11 | workspace = ".." 12 | 13 | [badges.travis-ci] 14 | repository = "fitzgen/state_machine_future" 15 | 16 | [dependencies] 17 | darling = "0.8.0" 18 | heck = "0.3.0" 19 | petgraph = "0.4.11" 20 | proc-macro2 = "0.4.21" 21 | quote = "0.6.10" 22 | syn = "0.15.0" 23 | 24 | [features] 25 | # For debugging purposes, print the generated code to stdout during 26 | # `derive(StateMachineFuture)` expansion. 27 | debug_code_generation = [] 28 | 29 | [lib] 30 | path = "./src/lib.rs" 31 | proc-macro = true 32 | -------------------------------------------------------------------------------- /derive_state_machine_future/src/ast.rs: -------------------------------------------------------------------------------- 1 | //! AST types for state machines and their states. 2 | 3 | use darling; 4 | use phases; 5 | use syn; 6 | 7 | /// A description of a state machine: its various states, which is the start 8 | /// state, ready state, and error state. 9 | #[derive(Debug, FromDeriveInput)] 10 | #[darling( 11 | attributes(state_machine_future), 12 | supports(enum_any), 13 | forward_attrs(allow, cfg) 14 | )] 15 | pub struct StateMachine { 16 | pub ident: syn::Ident, 17 | pub vis: syn::Visibility, 18 | pub generics: syn::Generics, 19 | pub data: darling::ast::Data, ()>, 20 | pub attrs: Vec, 21 | 22 | /// I guess we can't get other derives into `attrs` so we have to create our 23 | /// own derive list. 24 | #[darling(default)] 25 | pub derive: darling::util::IdentList, 26 | 27 | /// Extra per-phase data. 28 | #[darling(default)] 29 | pub extra: P::StateMachineExtra, 30 | 31 | #[darling(default)] 32 | pub context: Option, 33 | } 34 | 35 | /// In individual state in a state machine. 36 | #[derive(Debug, FromVariant)] 37 | #[darling( 38 | attributes(state_machine_future, transitions, start, ready, error), 39 | forward_attrs(allow, doc, cfg) 40 | )] 41 | pub struct State { 42 | pub ident: syn::Ident, 43 | pub attrs: Vec, 44 | pub fields: darling::ast::Fields, 45 | 46 | /// Whether this is the start state. 47 | #[darling(default)] 48 | pub start: bool, 49 | 50 | /// Whether this is the ready state. 51 | #[darling(default)] 52 | pub ready: bool, 53 | 54 | /// Whether this is the error state. 55 | #[darling(default)] 56 | pub error: bool, 57 | 58 | /// The set of other states that this one can transition to. 59 | #[darling(default)] 60 | pub transitions: darling::util::IdentList, 61 | 62 | /// Any extra per-phase data. 63 | #[darling(default)] 64 | pub extra: P::StateExtra, 65 | } 66 | 67 | impl

StateMachine

68 | where 69 | P: phases::Phase, 70 | { 71 | /// Split this state machine into its parts, and then create a state machine 72 | /// in another phase. 73 | pub fn and_then(self, mut f: F) -> StateMachine 74 | where 75 | Q: phases::Phase, 76 | F: FnMut( 77 | StateMachine, 78 | P::StateMachineExtra, 79 | Vec>, 80 | ) -> StateMachine, 81 | { 82 | let (state_machine, extra, states) = self.split(); 83 | f(state_machine, extra, states) 84 | } 85 | 86 | /// Split this state machine into its parts, separating per-phase data from 87 | /// the state machine. 88 | pub fn split( 89 | self, 90 | ) -> ( 91 | StateMachine, 92 | P::StateMachineExtra, 93 | Vec>, 94 | ) { 95 | let states = self.data.take_enum().unwrap(); 96 | let extra = self.extra; 97 | let machine = StateMachine { 98 | ident: self.ident, 99 | vis: self.vis, 100 | generics: self.generics, 101 | data: darling::ast::Data::Enum(vec![]), 102 | attrs: self.attrs, 103 | derive: self.derive, 104 | extra: (), 105 | context: self.context, 106 | }; 107 | (machine, extra, states) 108 | } 109 | 110 | /// Get this state machine's states. 111 | pub fn states(&self) -> &[State

] { 112 | match self.data { 113 | darling::ast::Data::Enum(ref states) => states, 114 | darling::ast::Data::Struct(_) => unreachable!(), 115 | } 116 | } 117 | } 118 | 119 | impl StateMachine { 120 | /// Join the state machine with the new phase's extra data, creating a state 121 | /// machine in the new phase. 122 | pub fn join

(self, extra: P::StateMachineExtra, states: Vec>) -> StateMachine

123 | where 124 | P: phases::Phase, 125 | { 126 | StateMachine { 127 | ident: self.ident, 128 | vis: self.vis, 129 | generics: self.generics, 130 | data: darling::ast::Data::Enum(states), 131 | attrs: self.attrs, 132 | derive: self.derive, 133 | extra, 134 | context: self.context, 135 | } 136 | } 137 | } 138 | 139 | impl

State

140 | where 141 | P: phases::Phase, 142 | { 143 | /// Split this state into its parts, and then construct a state in some new 144 | /// phase. 145 | pub fn and_then(self, mut f: F) -> State 146 | where 147 | F: FnMut(State, P::StateExtra) -> State, 148 | Q: phases::Phase, 149 | { 150 | let (state, extra) = self.split(); 151 | f(state, extra) 152 | } 153 | 154 | /// Split this state into its parts, separating its per-phase data out. 155 | pub fn split(self) -> (State, P::StateExtra) { 156 | let extra = self.extra; 157 | let state = State { 158 | ident: self.ident, 159 | attrs: self.attrs, 160 | fields: self.fields, 161 | start: self.start, 162 | ready: self.ready, 163 | error: self.error, 164 | transitions: self.transitions, 165 | extra: (), 166 | }; 167 | (state, extra) 168 | } 169 | } 170 | 171 | impl State { 172 | /// Join the state with the new phase's extra data, creating a state in the 173 | /// new phase. 174 | pub fn join

(self, extra: P::StateExtra) -> State

175 | where 176 | P: phases::Phase, 177 | { 178 | State { 179 | ident: self.ident, 180 | attrs: self.attrs, 181 | fields: self.fields, 182 | start: self.start, 183 | ready: self.ready, 184 | error: self.error, 185 | transitions: self.transitions, 186 | extra, 187 | } 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /derive_state_machine_future/src/codegen.rs: -------------------------------------------------------------------------------- 1 | //! Final AST -> tokens code generation. 2 | 3 | use darling; 4 | use heck::SnakeCase; 5 | use proc_macro2::{self, Ident, Span}; 6 | use quote::{ToTokens, TokenStreamExt}; 7 | use syn; 8 | 9 | use ast::{State, StateMachine}; 10 | use phases; 11 | 12 | fn doc_string>(s: S) -> proc_macro2::TokenStream { 13 | let s = s.as_ref(); 14 | 15 | let meta = syn::Meta::NameValue(syn::MetaNameValue { 16 | ident: Ident::new("doc", Span::call_site()), 17 | eq_token: ::default(), 18 | lit: syn::Lit::Str(syn::LitStr::new(s, Span::call_site())), 19 | }); 20 | 21 | quote!(#[#meta]) 22 | } 23 | 24 | fn to_var>(s: S) -> Ident { 25 | let s = s.as_ref().to_snake_case(); 26 | match s.as_str() { 27 | "abstract" | "alignof" | "as" | "become" | "box" | "break" | "const" | "continue" 28 | | "crate" | "do" | "else" | "enum" | "extern" | "false" | "final" | "fn" | "for" | "if" 29 | | "impl" | "in" | "let" | "loop" | "macro" | "match" | "mod" | "move" | "mut" 30 | | "offsetof" | "override" | "priv" | "proc" | "pub" | "pure" | "ref" | "return" 31 | | "Self" | "self" | "sizeof" | "static" | "struct" | "super" | "trait" | "true" 32 | | "type" | "typeof" | "unsafe" | "unsized" | "use" | "virtual" | "where" | "while" 33 | | "yield" | "bool" | "_" => { 34 | let mut var = String::from("var_"); 35 | var.push_str(&s); 36 | Ident::new(&var, Span::call_site()) 37 | } 38 | _ => Ident::new(&s, Span::call_site()), 39 | } 40 | } 41 | 42 | impl ToTokens for StateMachine { 43 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 44 | if cfg!(feature = "debug_code_generation") { 45 | println!("StateMachine::to_tokens: self = {:#?}", self); 46 | } 47 | 48 | let vis = &self.vis; 49 | let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); 50 | let states = self.states(); 51 | let total_states = states.len(); 52 | 53 | let derive = if self.derive.is_empty() { 54 | quote! {} 55 | } else { 56 | let derive = &*self.derive; 57 | quote! { 58 | #[derive( #( #derive ),* )] 59 | } 60 | }; 61 | 62 | let states_variants: Vec<_> = states 63 | .iter() 64 | .map(|s| { 65 | let ty_generics = s.extra.generics.split_for_impl().1; 66 | let ident = &s.ident; 67 | 68 | quote! { 69 | #ident(#ident #ty_generics) 70 | } 71 | }) 72 | .collect(); 73 | 74 | let start = &states[self.extra.start]; 75 | let start_state_ident = &start.ident; 76 | 77 | let ready = &states[self.extra.ready]; 78 | let ready_ident = &ready.ident; 79 | let ready_var = to_var(&ready_ident.to_string()); 80 | let future_item = &ready.fields.fields[0]; 81 | 82 | let error = &states[self.extra.error]; 83 | let error_ident = &error.ident; 84 | let error_var = to_var(&error_ident.to_string()); 85 | let future_error = &error.fields.fields[0]; 86 | 87 | let state_machine_attrs = &self.attrs; 88 | 89 | let state_machine_description_name = self.ident.to_string(); 90 | 91 | let mut state_machine_name = state_machine_description_name.clone(); 92 | state_machine_name.push_str("Future"); 93 | 94 | let ident = &self.ident; 95 | let state_machine_ident = Ident::new(state_machine_name.as_str(), Span::call_site()); 96 | let states_enum = &*self.extra.states_enum; 97 | 98 | let start_params = match start.fields.style { 99 | darling::ast::Style::Unit => vec![], 100 | darling::ast::Style::Tuple => start 101 | .fields 102 | .fields 103 | .iter() 104 | .cloned() 105 | .enumerate() 106 | .map(|(i, mut f)| { 107 | f.ident = Some(syn::Ident::new(&format!("arg{}", i), Span::call_site())); 108 | f 109 | }) 110 | .collect(), 111 | darling::ast::Style::Struct => start.fields.fields.clone(), 112 | }; 113 | let start_params = &start_params; 114 | 115 | let start_value = match start.fields.style { 116 | darling::ast::Style::Unit => quote! { 117 | #start_state_ident 118 | }, 119 | darling::ast::Style::Tuple => { 120 | let args = start_params.iter().map(|f| &f.ident); 121 | quote! { 122 | #start_state_ident( #( #args ),* ) 123 | } 124 | } 125 | darling::ast::Style::Struct => { 126 | let args = start_params.iter().map(|f| &f.ident); 127 | quote! { 128 | #start_state_ident { #( #args ),* } 129 | } 130 | } 131 | }; 132 | 133 | let poll_match_arms: Vec<_> = states 134 | .iter() 135 | .map(|state| state.future_poll_match_arm(&ty_generics, self.context.as_ref())) 136 | .collect(); 137 | 138 | let poll_trait = &*self.extra.poll_trait; 139 | let poll_trait_methods: Vec<_> = states 140 | .iter() 141 | .filter(|s| !s.ready && !s.error) 142 | .map(|state| state.poll_trait_method(&ty_generics, self.context.as_ref())) 143 | .collect(); 144 | 145 | let start_doc = doc_string(format!( 146 | "Start executing the `{}` state machine. This constructing its `Future` \ 147 | representation in its initial start state and returns it.", 148 | state_machine_name 149 | )); 150 | 151 | let smf_crate = &*self.extra.smf_crate; 152 | 153 | let mut quiet = "__smf_quiet_warnings_for_".to_string(); 154 | quiet += &state_machine_name.to_snake_case(); 155 | let quiet = Ident::new(&quiet, Span::call_site()); 156 | 157 | let quiet_constructions: Vec<_> = self 158 | .states() 159 | .iter() 160 | .map(|s| { 161 | let s_ident = &s.ident; 162 | match s.fields.style { 163 | darling::ast::Style::Unit => quote! { 164 | let _ = ::std::mem::replace(xxx, #ident::#s_ident); 165 | }, 166 | darling::ast::Style::Tuple => { 167 | let fields = s.fields.fields.iter().map(|_| { 168 | quote! { 169 | conjure() 170 | } 171 | }); 172 | quote! { 173 | let _ = ::std::mem::replace( 174 | xxx, 175 | #ident::#s_ident( #( #fields ),* ), 176 | ); 177 | } 178 | } 179 | darling::ast::Style::Struct => { 180 | let fields = s.fields.fields.iter().map(|f| { 181 | let f = &f.ident; 182 | quote! { 183 | #f: conjure() 184 | } 185 | }); 186 | 187 | let match_fields = s.fields.fields.iter().map(|f| { 188 | let f = &f.ident; 189 | quote! { 190 | ref #f 191 | } 192 | }); 193 | 194 | quote! { 195 | let _ = ::std::mem::replace( 196 | xxx, 197 | #ident::#s_ident { #( #fields ),* }, 198 | ); 199 | 200 | match *xxx { 201 | #ident::#s_ident { #( #match_fields ),* } => { unimplemented!() }, 202 | _ => { unimplemented!() }, 203 | } 204 | } 205 | } 206 | } 207 | }) 208 | .collect(); 209 | 210 | let has_no_start_parameters = start_params.len() == 0; 211 | 212 | let context_field = match self.context { 213 | Some(ref ident) => quote! { 214 | , context: Option<#ident #ty_generics> 215 | }, 216 | None => quote! {}, 217 | }; 218 | 219 | let context_start_arg_decl = match self.context { 220 | Some(ref ident) if has_no_start_parameters => quote! { 221 | context: #ident #ty_generics 222 | }, 223 | Some(ref ident) => quote! { 224 | , context: #ident #ty_generics 225 | }, 226 | None => quote! {}, 227 | }; 228 | 229 | let context_start_in_arg_decl = match self.context { 230 | Some(ref ident) => quote! { 231 | , context: #ident #ty_generics 232 | }, 233 | None => quote! {}, 234 | }; 235 | 236 | let context_start_arg = match self.context { 237 | Some(_) => quote! { 238 | , context: Some(context) 239 | }, 240 | None => quote! {}, 241 | }; 242 | 243 | let extract_context = match self.context { 244 | Some(_) => match total_states { 245 | // If there is only 1 state it is irrefutable that we are in the ready state. 246 | 1 => quote! { 247 | let context = match self.context.take() { 248 | Some(context) => context, 249 | None => { 250 | let #states_enum::#ready_ident(#ready_ident(#ready_var)) = state; 251 | return Ok(#smf_crate::export::Async::Ready(#ready_var)) 252 | } 253 | }; 254 | }, 255 | _ => quote! { 256 | let context = match self.context.take() { 257 | Some(context) => context, 258 | None => { 259 | return match state { 260 | #states_enum::#ready_ident(#ready_ident(#ready_var)) => Ok(#smf_crate::export::Async::Ready(#ready_var)), 261 | #states_enum::#error_ident(#error_ident(#error_var)) => Err(#error_var), 262 | _ => Ok(#smf_crate::export::Async::NotReady) 263 | } 264 | } 265 | }; 266 | }, 267 | }, 268 | None => quote! {}, 269 | }; 270 | 271 | tokens.append_all(quote! { 272 | extern crate state_machine_future as #smf_crate; 273 | 274 | #( #states )* 275 | 276 | #derive 277 | #[allow(dead_code)] 278 | #vis enum #states_enum #impl_generics #where_clause { 279 | #( #states_variants ),* 280 | } 281 | 282 | #( #state_machine_attrs )* 283 | #[must_use = "futures do nothing unless polled"] 284 | #vis struct #state_machine_ident #impl_generics #where_clause { 285 | current_state: Option<#states_enum #ty_generics> 286 | #context_field 287 | } 288 | 289 | impl #impl_generics #smf_crate::export::Future 290 | for #state_machine_ident #ty_generics #where_clause { 291 | type Item = #future_item; 292 | type Error = #future_error; 293 | 294 | #[allow(unreachable_code)] 295 | fn poll(&mut self) -> #smf_crate::export::Poll { 296 | loop { 297 | let state = match self.current_state.take() { 298 | Some(state) => state, 299 | None => return Ok(#smf_crate::export::Async::NotReady), 300 | }; 301 | #extract_context 302 | self.current_state = match state { 303 | #( #poll_match_arms )* 304 | }; 305 | } 306 | } 307 | } 308 | 309 | impl #impl_generics #smf_crate::StateMachineFuture 310 | for #ident #ty_generics #where_clause 311 | { 312 | type Future = #state_machine_ident #ty_generics; 313 | } 314 | 315 | #vis trait #poll_trait #impl_generics 316 | : #smf_crate::StateMachineFuture 317 | #where_clause 318 | { 319 | #( #poll_trait_methods )* 320 | } 321 | 322 | impl #impl_generics #ident #ty_generics #where_clause { 323 | #start_doc 324 | #[allow(dead_code)] 325 | #vis fn start( #( #start_params ),* #context_start_arg_decl ) -> #state_machine_ident #ty_generics { 326 | #state_machine_ident { 327 | current_state: Some( 328 | #states_enum::#start_state_ident( 329 | #start_value 330 | ) 331 | ) 332 | #context_start_arg 333 | } 334 | } 335 | 336 | #vis fn start_in>( state: STATE #context_start_in_arg_decl ) -> #state_machine_ident #ty_generics { 337 | #state_machine_ident { 338 | current_state: Some(state.into()) 339 | #context_start_arg 340 | } 341 | } 342 | } 343 | 344 | #[allow(warnings)] 345 | fn #quiet #impl_generics (xxx: &mut #ident #ty_generics) #where_clause { 346 | fn conjure() -> SmfAnyType { 347 | unreachable!() 348 | } 349 | #( 350 | #quiet_constructions; 351 | )* 352 | } 353 | }); 354 | 355 | let state_froms: Vec<_> = states 356 | .iter() 357 | .map(|s| { 358 | let state_ident = &s.ident; 359 | let state_ident_var = to_var(state_ident.to_string()); 360 | let state_generics = s.extra.generics.split_for_impl().1; 361 | 362 | quote! { 363 | impl #impl_generics From<#state_ident #state_generics> 364 | for #states_enum #ty_generics #where_clause { 365 | fn from(#state_ident_var: #state_ident #state_generics) -> Self { 366 | #states_enum::#state_ident(#state_ident_var) 367 | } 368 | } 369 | } 370 | }) 371 | .collect(); 372 | 373 | tokens.append_all(quote! { 374 | #( #state_froms )* 375 | }); 376 | 377 | if cfg!(feature = "debug_code_generation") { 378 | use std::io::Write; 379 | use std::process; 380 | 381 | println!(); 382 | println!("================================================================="); 383 | println!(); 384 | 385 | let mut child = process::Command::new("rustup") 386 | .args(&["run", "nightly", "rustfmt"]) 387 | .stdin(process::Stdio::piped()) 388 | .spawn() 389 | .unwrap_or_else(|_| { 390 | process::Command::new("rustfmt") 391 | .stdin(process::Stdio::piped()) 392 | .spawn() 393 | .unwrap() 394 | }); 395 | { 396 | let mut stdin = child.stdin.take().unwrap(); 397 | stdin.write_all(tokens.to_string().as_bytes()).unwrap(); 398 | } 399 | child.wait().unwrap(); 400 | 401 | println!(); 402 | println!("================================================================="); 403 | println!(); 404 | } 405 | } 406 | } 407 | 408 | impl State { 409 | fn future_poll_match_arm( 410 | &self, 411 | ty_generics: &syn::TypeGenerics, 412 | context: Option<&syn::Ident>, 413 | ) -> proc_macro2::TokenStream { 414 | let ident = &self.ident; 415 | let ident_string = ident.to_string(); 416 | let var = to_var(&ident_string); 417 | let states_enum = &*self.extra.states_enum; 418 | let poll_trait = &*self.extra.poll_trait; 419 | let smf_crate = &*self.extra.smf_crate; 420 | 421 | if self.ready { 422 | return quote! { 423 | #states_enum::#ident(#ident(#var)) => { 424 | return Ok(#smf_crate::export::Async::Ready(#var)); 425 | } 426 | }; 427 | } 428 | 429 | let error_ident = &*self.extra.error_ident; 430 | let error_var = to_var(error_ident.to_string()); 431 | 432 | if self.error { 433 | return quote! { 434 | #states_enum::#error_ident(#error_ident(#error_var)) => { 435 | return Err(#error_var); 436 | } 437 | }; 438 | } 439 | 440 | let after = &self.extra.after; 441 | let poll_method = &self.extra.poll_method; 442 | let description_ident = &*self.extra.description_ident; 443 | 444 | let ready = self.transitions.iter().map(|t| { 445 | let t_var = to_var(t.to_string()); 446 | quote! { 447 | Ok(#smf_crate::export::Async::Ready(#after::#t(#t_var))) => { 448 | Some(#states_enum::#t(#t_var)) 449 | } 450 | } 451 | }); 452 | 453 | let poll_method_call = match context { 454 | Some(_) => quote! { 455 | |state| { 456 | let (context, result) = 457 | #smf_crate::RentToOwn::with( 458 | context, 459 | move |context| { 460 | <#description_ident #ty_generics as #poll_trait #ty_generics>::#poll_method(state, context) 461 | } 462 | ); 463 | 464 | self.context = context; 465 | 466 | result 467 | } 468 | }, 469 | None => quote! { 470 | <#description_ident #ty_generics as #poll_trait #ty_generics>::#poll_method 471 | }, 472 | }; 473 | 474 | quote! { 475 | #states_enum::#ident(#var) => { 476 | let (#var, result) = 477 | #smf_crate::RentToOwn::with( 478 | #var, 479 | #poll_method_call 480 | ); 481 | match result { 482 | Err(e) => { 483 | Some(#states_enum::#error_ident(#error_ident(e))) 484 | } 485 | Ok(#smf_crate::export::Async::NotReady) => { 486 | self.current_state = #var.map(#states_enum::#ident); 487 | return Ok(#smf_crate::export::Async::NotReady); 488 | } 489 | #( #ready )* 490 | } 491 | } 492 | } 493 | } 494 | 495 | fn poll_doc_string(&self) -> proc_macro2::TokenStream { 496 | doc_string(format!( 497 | "Poll the future when it is in the `{}` state and see if it is ready \ 498 | to transition to a new state. If the future is ready to transition \ 499 | into a new state, return `Ok(Async::Ready({}))`. If the future is \ 500 | not ready to transition into a new state, return \ 501 | `Ok(Async::NotReady)`. If an error is encountered, return `Err({})`. \ 502 | The `RentToOwn` wrapper allows you to choose whether to take \ 503 | ownership of the current state or not.", 504 | self.ident.to_string(), 505 | self.extra.after, 506 | { 507 | let mut t = quote! {}; 508 | self.extra.error_type.to_tokens(&mut t); 509 | t.to_string() 510 | }, 511 | )) 512 | } 513 | 514 | fn poll_trait_method( 515 | &self, 516 | sm_ty_generics: &syn::TypeGenerics, 517 | context: Option<&syn::Ident>, 518 | ) -> proc_macro2::TokenStream { 519 | assert!(!self.ready && !self.error); 520 | 521 | let poll_method = &self.extra.poll_method; 522 | let poll_method_doc = self.poll_doc_string(); 523 | let me = &self.ident; 524 | let after = &self.extra.after; 525 | let ty_generics = self.extra.generics.split_for_impl().1; 526 | let (_, after_ty_generics, _) = self.extra.after_state_generics.split_for_impl(); 527 | let error_type = &*self.extra.error_type; 528 | let smf_crate = &*self.extra.smf_crate; 529 | 530 | let context_param = match context { 531 | Some(ident) => quote! { 532 | , _: &'smf_poll_context mut #smf_crate::RentToOwn<'smf_poll_context, #ident #sm_ty_generics> 533 | }, 534 | None => quote! {}, 535 | }; 536 | 537 | quote! { 538 | #poll_method_doc 539 | fn #poll_method<'smf_poll_state, 'smf_poll_context>( 540 | _: &'smf_poll_state mut #smf_crate::RentToOwn<'smf_poll_state, #me #ty_generics> 541 | #context_param 542 | ) -> #smf_crate::export::Poll<#after #after_ty_generics, #error_type>; 543 | } 544 | } 545 | } 546 | 547 | impl ToTokens for State { 548 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 549 | let vis = &*self.extra.vis; 550 | let ident_name = self.ident.to_string(); 551 | let ident = &self.ident; 552 | let attrs = &self.attrs; 553 | let (impl_generics, _, where_clause) = self.extra.generics.split_for_impl(); 554 | let (after_impl_generics, after_ty_generics, after_where_clause) = 555 | self.extra.after_state_generics.split_for_impl(); 556 | 557 | let derive = if self.extra.derive.is_empty() { 558 | quote! {} 559 | } else { 560 | let derive = &**self.extra.derive; 561 | quote! { 562 | #[derive( #( #derive ),* )] 563 | } 564 | }; 565 | 566 | let fields: Vec<_> = self 567 | .fields 568 | .fields 569 | .iter() 570 | .map(|f| { 571 | let mut f = f.clone(); 572 | f.vis = syn::VisPublic { 573 | pub_token: ::default(), 574 | } 575 | .into(); 576 | f 577 | }) 578 | .collect(); 579 | 580 | tokens.append_all(match self.fields.style { 581 | darling::ast::Style::Unit => quote! { 582 | #( #attrs )* 583 | #derive 584 | #vis struct #ident; 585 | }, 586 | darling::ast::Style::Tuple => quote! { 587 | #( #attrs )* 588 | #derive 589 | #vis struct #ident #impl_generics( #( #fields ),* ) #where_clause; 590 | }, 591 | darling::ast::Style::Struct => quote! { 592 | #( #attrs )* 593 | #derive 594 | #vis struct #ident #impl_generics #where_clause { 595 | #( #fields ),* 596 | } 597 | }, 598 | }); 599 | 600 | if self.ready || self.error { 601 | return; 602 | } 603 | 604 | let after_ident = &self.extra.after; 605 | 606 | let after_variants: Vec<_> = self 607 | .extra 608 | .transition_state_generics 609 | .iter() 610 | .map(|(s, g)| { 611 | let doc = doc_string(format!( 612 | "A transition from the `{}` state to the `{}` state.", 613 | ident_name, s 614 | )); 615 | let ty_generics = g.split_for_impl().1; 616 | quote! { 617 | #doc 618 | #s(#s #ty_generics) 619 | } 620 | }) 621 | .collect(); 622 | 623 | let after_froms: Vec<_> = self 624 | .extra 625 | .transition_state_generics 626 | .iter() 627 | .map(|(s, g)| { 628 | let s_var = to_var(s.to_string()); 629 | let trans_ty_generics = g.split_for_impl().1; 630 | 631 | quote! { 632 | impl #after_impl_generics From<#s #trans_ty_generics> 633 | for #after_ident #after_ty_generics #after_where_clause { 634 | fn from(#s_var: #s #trans_ty_generics) -> Self { 635 | #after_ident::#s(#s_var) 636 | } 637 | } 638 | } 639 | }) 640 | .collect(); 641 | 642 | let after_doc = doc_string(format!( 643 | "The states that the `{}` state can transition to.", 644 | ident_name 645 | )); 646 | 647 | tokens.append_all(quote! { 648 | #after_doc 649 | #vis enum #after_ident #after_impl_generics #after_where_clause { 650 | #( #after_variants ),* 651 | } 652 | #( #after_froms )* 653 | }); 654 | } 655 | } 656 | -------------------------------------------------------------------------------- /derive_state_machine_future/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! The compiler for `derive(StateMachineFuture)`. 2 | 3 | #![recursion_limit = "128"] 4 | 5 | #[macro_use] 6 | extern crate darling; 7 | extern crate heck; 8 | extern crate petgraph; 9 | extern crate proc_macro; 10 | extern crate proc_macro2; 11 | #[macro_use] 12 | extern crate quote; 13 | #[macro_use] 14 | extern crate syn; 15 | 16 | mod ast; 17 | mod codegen; 18 | mod phases; 19 | 20 | use darling::FromDeriveInput; 21 | use proc_macro::TokenStream; 22 | 23 | use ast::StateMachine; 24 | use phases::Pass; 25 | 26 | #[proc_macro_derive(StateMachineFuture, attributes(state_machine_future))] 27 | pub fn derive_state_machine_future(input: TokenStream) -> TokenStream { 28 | let derive_input = parse_macro_input!(input as syn::DeriveInput); 29 | 30 | let machine = match StateMachine::::from_derive_input(&derive_input) { 31 | Ok(sm) => sm, 32 | Err(e) => panic!("error in derive(StateMachineFuture): {}", e), 33 | }; 34 | 35 | let machine = phases::StartReadyError::pass(machine); 36 | let machine = phases::ValidTransitionEdges::pass(machine); 37 | let machine = phases::ValidPaths::pass(machine); 38 | let machine = phases::StateGenerics::pass(machine); 39 | let machine = phases::AfterStateGenerics::pass(machine); 40 | let machine = phases::ReadyForCodegen::pass(machine); 41 | 42 | quote!(#machine).into() 43 | } 44 | -------------------------------------------------------------------------------- /derive_state_machine_future/src/phases.rs: -------------------------------------------------------------------------------- 1 | //! Phases of our custom derive compiler, and passes that perform phase changes. 2 | 3 | use std::collections::{HashMap, HashSet}; 4 | use std::fmt; 5 | use std::hash::{BuildHasher, Hash}; 6 | use std::iter::FromIterator; 7 | use std::rc::Rc; 8 | 9 | use darling; 10 | use darling::usage::{ 11 | CollectLifetimes, CollectTypeParams, GenericsExt, IdentRefSet, LifetimeRefSet, Purpose, 12 | UsesTypeParams, 13 | }; 14 | use heck::SnakeCase; 15 | use petgraph; 16 | use petgraph::algo::has_path_connecting; 17 | use proc_macro2::{Ident, Span}; 18 | use syn; 19 | 20 | use ast::StateMachine; 21 | 22 | // Create a dummy `FromMeta` implementation for the given type. This is only 23 | // used because the way that `darling` emits bounds on generic items forces all 24 | // our extra state to implement a bunch of things that it won't ever use. 25 | macro_rules! dummy_from_meta { 26 | ( $t:ty ) => { 27 | impl darling::FromMeta for $t {} 28 | }; 29 | } 30 | 31 | // Same as above. 32 | macro_rules! dummy_default { 33 | ( $t:ty ) => { 34 | impl Default for $t { 35 | fn default() -> $t { 36 | panic!("dummy implementation") 37 | } 38 | } 39 | }; 40 | } 41 | 42 | /// A phase represents a state in the pipeline, and the extra data we've 43 | /// accumulated up to this point. 44 | pub trait Phase: fmt::Debug + darling::FromMeta { 45 | /// Extra data accumulated on the `StateMachine` at this phase. 46 | type StateMachineExtra: Default + fmt::Debug + darling::FromMeta; 47 | 48 | /// Extra data accumulated on each `State` in the `StateMachine` at this 49 | /// phase. 50 | type StateExtra: Default + fmt::Debug + darling::FromMeta; 51 | } 52 | 53 | /// A special phase representing the lack of a phase. 54 | /// 55 | /// This phase is used when we have split the state machine from its accumulated 56 | /// phase data and are operating on them independently. 57 | /// 58 | /// See `State::split` and `StateMachine::split` for usage. 59 | #[derive(FromMeta, Debug)] 60 | pub struct NoPhase; 61 | 62 | impl Phase for NoPhase { 63 | type StateMachineExtra = (); 64 | type StateExtra = (); 65 | } 66 | 67 | /// A pass is a single phase-to-phase translation step in our pipeline. 68 | /// 69 | /// When implementing the `From -> To` pass, this trait is implemented for `To` 70 | /// and this trait's associated type would be `From`. 71 | pub trait Pass: Phase { 72 | /// The current phase we are creating the next phase from. 73 | type FromPhase: Phase; 74 | 75 | /// The function to translate between these phases. 76 | fn pass(StateMachine) -> StateMachine; 77 | } 78 | 79 | /// The state machine AST has been parsed from the custom derive input. 80 | #[derive(FromMeta, Debug)] 81 | pub struct Parsed; 82 | 83 | impl Phase for Parsed { 84 | type StateMachineExtra = (); 85 | type StateExtra = (); 86 | } 87 | 88 | /// We've found the indices into `states` for the unique start, ready, and error 89 | /// states. 90 | #[derive(FromMeta, Debug, Default)] 91 | pub struct StartReadyError { 92 | pub start: usize, 93 | pub ready: usize, 94 | pub error: usize, 95 | } 96 | 97 | impl Phase for StartReadyError { 98 | type StateMachineExtra = Self; 99 | type StateExtra = (); 100 | } 101 | 102 | impl Pass for StartReadyError { 103 | type FromPhase = Parsed; 104 | 105 | fn pass(machine: StateMachine) -> StateMachine { 106 | machine.and_then(|machine, (), states| { 107 | let mut start = None; 108 | let mut ready = None; 109 | let mut error = None; 110 | 111 | let states = states 112 | .into_iter() 113 | .enumerate() 114 | .map(|(idx, state)| { 115 | if state.start { 116 | assert!(start.is_none(), "There must only be a single `start` state"); 117 | start = Some(idx); 118 | } 119 | 120 | if state.ready { 121 | assert!(ready.is_none(), "There must only be a single `ready` state"); 122 | assert!( 123 | state.fields.style.is_tuple(), 124 | "The `ready` state must be a tuple variant, for example: `Ready(Item)`" 125 | ); 126 | assert_eq!( 127 | state.fields.fields.len(), 128 | 1, 129 | "The `ready` state must only have one field, for example: `Ready(Item)`" 130 | ); 131 | assert!( 132 | state.transitions.is_empty(), 133 | "The `ready` state must not transition to any other states" 134 | ); 135 | ready = Some(idx); 136 | } 137 | 138 | if state.error { 139 | assert!(error.is_none(), "There must only be a single `error` state"); 140 | assert!( 141 | state.fields.style.is_tuple(), 142 | "The `error` state must be a tuple variant, for example: `Error(Item)`" 143 | ); 144 | assert_eq!( 145 | state.fields.fields.len(), 146 | 1, 147 | "The `error` state must only have one field, for example: `Error(Item)`" 148 | ); 149 | assert!( 150 | state.transitions.is_empty(), 151 | "The `error` state must not transition to any other states" 152 | ); 153 | error = Some(idx); 154 | } 155 | 156 | if !state.ready && !state.error { 157 | assert!( 158 | !state.transitions.is_empty(), 159 | "Non-{ready,error} states must have transitions" 160 | ); 161 | } 162 | 163 | state.and_then(|state, ()| state.join(())) 164 | }) 165 | .collect(); 166 | 167 | let start = start.expect("Must specify one `start` state"); 168 | let ready = ready.expect("Must specify one `ready` state"); 169 | let error = error.expect("Must specify one `error` state"); 170 | 171 | machine.join( 172 | StartReadyError { 173 | start, 174 | ready, 175 | error, 176 | }, 177 | states, 178 | ) 179 | }) 180 | } 181 | } 182 | 183 | /// A phase after which we know that all transitions are to valid states. That 184 | /// is, we will never get any "cannot find type `UnknownState` in this scope" 185 | /// compilation errors from any code we emit. 186 | #[derive(FromMeta, Debug)] 187 | pub struct ValidTransitionEdges; 188 | 189 | impl Phase for ValidTransitionEdges { 190 | type StateMachineExtra = ::StateMachineExtra; 191 | type StateExtra = (); 192 | } 193 | 194 | impl Pass for ValidTransitionEdges { 195 | type FromPhase = StartReadyError; 196 | 197 | fn pass(machine: StateMachine) -> StateMachine { 198 | machine.and_then(|machine, extra, states| { 199 | let state_idents: HashSet = 200 | HashSet::from_iter(states.iter().map(|s| s.ident.clone())); 201 | 202 | let states = states 203 | .into_iter() 204 | .map(|state| { 205 | state.and_then(|s, ()| { 206 | s.transitions.iter().for_each(|t| { 207 | assert!( 208 | state_idents.contains(t), 209 | "Transition to unknown state `{}` from state `{}`", 210 | t.to_string(), 211 | s.ident.to_string() 212 | ); 213 | }); 214 | s.join(()) 215 | }) 216 | }) 217 | .collect(); 218 | 219 | machine.join(extra, states) 220 | }) 221 | } 222 | } 223 | 224 | /// All paths through the state machine's states lead to the ready or error 225 | /// state, and no intermediate state is unreachable. 226 | #[derive(FromMeta, Debug)] 227 | pub struct ValidPaths; 228 | 229 | impl Phase for ValidPaths { 230 | type StateMachineExtra = ::StateMachineExtra; 231 | type StateExtra = (); 232 | } 233 | 234 | impl Pass for ValidPaths { 235 | type FromPhase = ValidTransitionEdges; 236 | 237 | fn pass(machine: StateMachine) -> StateMachine { 238 | machine.and_then(|machine, extra, states| { 239 | let mut nodes: HashMap> = HashMap::new(); 240 | let mut graph: petgraph::Graph = petgraph::Graph::new(); 241 | 242 | // First, create a node for each state and insert it into `nodes`. 243 | states.iter().for_each(|s| { 244 | let s = s.ident.to_string(); 245 | nodes.insert(s.clone(), graph.add_node(s)); 246 | }); 247 | 248 | // Second, construct the edges between states. 249 | graph.extend_with_edges(states.iter().flat_map(|s| { 250 | let s_name = s.ident.to_string(); 251 | s.transitions 252 | .iter() 253 | .map(|t| { 254 | let t = t.to_string(); 255 | let from = nodes[&s_name]; 256 | let to = nodes[&t]; 257 | (from, to) 258 | }) 259 | .collect::>() 260 | })); 261 | 262 | let start_name = states[extra.start].ident.to_string(); 263 | let ready_name = states[extra.ready].ident.to_string(); 264 | let error_name = states[extra.error].ident.to_string(); 265 | let start = nodes[&start_name]; 266 | let ready = nodes[&ready_name]; 267 | let error = nodes[&error_name]; 268 | 269 | // Check that every non-final state is 270 | // 271 | // 1. Reachable from the start state, or is the start state, and 272 | // 2. Has a path leading to a final state (ready or error). 273 | // 274 | // TODO: This would be a lot more efficient if we didn't throw away 275 | // the incremental results from each of these queries... But that 276 | // means not using `has_path_connecting` and rolling our own thing, 277 | // which is more work than I want to do this moment. 278 | let mut dfs_space = petgraph::algo::DfsSpace::new(&graph); 279 | states 280 | .iter() 281 | .filter(|s| !s.ready && !s.error) 282 | .for_each(|s| { 283 | let s_name = s.ident.to_string(); 284 | let s_node = nodes[&s_name]; 285 | assert!( 286 | has_path_connecting(&graph, s_node, ready, Some(&mut dfs_space)) 287 | || has_path_connecting(&graph, s_node, error, Some(&mut dfs_space)), 288 | "The `{}` state must have a transition path to either the ready \ 289 | state (`{}`) or the error state (`{}`) but it does not", 290 | s_name, 291 | ready_name, 292 | error_name 293 | ); 294 | 295 | assert!( 296 | s.start || has_path_connecting(&graph, start, s_node, Some(&mut dfs_space)), 297 | "The `{}` state must be reachable from the start state (`{}`) but \ 298 | it is not", 299 | s_name, 300 | start_name 301 | ); 302 | }); 303 | 304 | let states = states 305 | .into_iter() 306 | .map(|state| state.and_then(|s, ()| s.join(()))) 307 | .collect(); 308 | 309 | machine.join(extra, states) 310 | }) 311 | } 312 | } 313 | 314 | /// Builds the generics for all states, based on the generics of the state machine. 315 | #[derive(FromMeta, Debug)] 316 | pub struct StateGenerics; 317 | 318 | dummy_default!(StateGenerics); 319 | 320 | #[derive(Debug)] 321 | pub struct StateGenericsExtra { 322 | pub generics: Rc, 323 | } 324 | 325 | dummy_default!(StateGenericsExtra); 326 | dummy_from_meta!(StateGenericsExtra); 327 | 328 | impl Phase for StateGenerics { 329 | type StateMachineExtra = ::StateMachineExtra; 330 | type StateExtra = StateGenericsExtra; 331 | } 332 | 333 | impl Pass for StateGenerics { 334 | type FromPhase = ValidPaths; 335 | 336 | fn pass(machine: StateMachine) -> StateMachine { 337 | machine.and_then(|machine, extra, states| { 338 | let states = { 339 | let mgenerics = &machine.generics; 340 | 341 | // Return a `HashMap` of keys mapped to `Vec`s of values. Keys and values 342 | // are taken from `(Key, Value)` tuple pairs yielded by the input iterator. 343 | fn into_group_map(iter: I) -> HashMap, S> 344 | where 345 | I: Iterator, 346 | K: Hash + Eq, 347 | S: BuildHasher + Default, 348 | { 349 | let mut lookup = HashMap::default(); 350 | 351 | for (key, val) in iter { 352 | lookup.entry(key).or_insert_with(Vec::new).push(val); 353 | } 354 | 355 | lookup 356 | } 357 | 358 | // Return a `HashMap` of keys mapped to `Vec`s of values. Keys and values 359 | // are taken from `(IntoIterator, Value)` tuple pairs yielded by the 360 | // input iterator. 361 | fn into_group_map_multikey(iter: I) -> HashMap, S> 362 | where 363 | I: Iterator, 364 | J: IntoIterator, 365 | K: Hash + Eq, 366 | V: Clone, 367 | S: BuildHasher + Default, 368 | { 369 | let mut lookup = HashMap::default(); 370 | 371 | for (keys, val) in iter { 372 | for key in keys { 373 | lookup.entry(key).or_insert_with(Vec::new).push(val.clone()); 374 | } 375 | } 376 | 377 | lookup 378 | } 379 | 380 | let options = Purpose::Declare.into(); 381 | let declared_lifetimes = mgenerics.declared_lifetimes(); 382 | let declared_type_params = mgenerics.declared_type_params(); 383 | 384 | // Collect declared lifetimes with their definitions. 385 | // 386 | // E.g. for `'a: 'b + 'c` the hash map wil have: 387 | // * `'a` as key; 388 | // * `'a: 'b + 'c` as value. 389 | let lifetime_defs = mgenerics 390 | .lifetimes() 391 | .map(|ld| (&ld.lifetime, ld)) 392 | .collect::>(); 393 | 394 | // Collect declared type parameter identifiers with their definitions. 395 | // 396 | // E.g. for `T: 'r + fmt::Debug = String` the hash map will have: 397 | // * `T` as key; 398 | // * `T: 'r + fmt::Debug = String` as value. 399 | let type_param_defs = mgenerics 400 | .type_params() 401 | .map(|tp| (&tp.ident, tp)) 402 | .collect::>(); 403 | 404 | // Collect lifetimes in where clauses with respective lifetime predicates. 405 | // 406 | // E.g. for `'a: 'b + 'c` the hash map wil have: 407 | // * `'a` as key; 408 | // * `'a: 'b + 'c` as value. 409 | let where_clause_lifetimes = mgenerics 410 | .where_clause 411 | .as_ref() 412 | .map(|where_clause| { 413 | into_group_map(where_clause.predicates.iter().filter_map(|predicate| { 414 | match *predicate { 415 | syn::WherePredicate::Type(_) => None, 416 | syn::WherePredicate::Lifetime(ref lifetime_def) => { 417 | Some((&lifetime_def.lifetime, predicate)) 418 | } 419 | syn::WherePredicate::Eq(_) => None, 420 | } 421 | })) 422 | }) 423 | .unwrap_or_else(HashMap::new); 424 | 425 | // Collect type parameter identifiers in where clauses with respective type and 426 | // equality predicates. 427 | // 428 | // E.g. for `for<'c> Foo<'c, P>: Trait<'c, P>` the hash map will have: 429 | // * `{Foo, P}` as key; 430 | // * `for<'c> Foo<'c, P>: Trait<'c, P>` as value. 431 | // 432 | // Or e.g. for `X = Bar` the hash map will have: 433 | // * `X` as key; 434 | // * `X = Bar` as value. 435 | let where_clause_types = mgenerics 436 | .where_clause 437 | .as_ref() 438 | .map(|where_clause| { 439 | let declared_type_params_ref = &declared_type_params; 440 | into_group_map_multikey(where_clause.predicates.iter().filter_map( 441 | |predicate| match *predicate { 442 | syn::WherePredicate::Type(ref type_param) => { 443 | let idents = type_param 444 | .bounded_ty 445 | .uses_type_params(&options, declared_type_params_ref); 446 | Some((idents, predicate)) 447 | } 448 | syn::WherePredicate::Lifetime(_) => None, 449 | syn::WherePredicate::Eq(ref eq) => { 450 | let idents = eq 451 | .lhs_ty 452 | .uses_type_params(&options, declared_type_params_ref); 453 | Some((idents, predicate)) 454 | } 455 | }, 456 | )) 457 | }) 458 | .unwrap_or_else(HashMap::new); 459 | 460 | states 461 | .into_iter() 462 | .map(|state| { 463 | // Perform a breadth-first search across the graph of dependencies between 464 | // lifetimes and type parameters, then collect all reachable nodes as 465 | // generic parameters and where predicates. 466 | state.and_then(|state, ()| { 467 | // Lifetimes and type parameter identifiers for processing in any 468 | // current iteration. 469 | let mut current_lifetimes = state 470 | .fields 471 | .fields 472 | .collect_lifetimes(&options, &declared_lifetimes); 473 | let mut current_type_params = state 474 | .fields 475 | .fields 476 | .collect_type_params(&options, &declared_type_params); 477 | 478 | // Lifetimes and type parameter identifiers we already processed. 479 | let mut state_lifetimes = LifetimeRefSet::default(); 480 | let mut state_type_params = IdentRefSet::default(); 481 | 482 | // While we have new lifetimes and type parameter identifiers to 483 | // process... 484 | while !current_lifetimes.is_empty() || !current_type_params.is_empty() { 485 | // Collect elements for the next iteration from definitions and 486 | // where predicates. 487 | // 488 | // @hcpl: made as a macro because I have no idea how to generalize 489 | // this as a function in a readable way. 490 | macro_rules! new_elems_from_elem { 491 | ( 492 | $elem:ident, 493 | $elem_defs:ident, 494 | $where_clause_elems:ident, 495 | $collect_elems:ident, 496 | $declared_elems:ident, 497 | ) => {{ 498 | let from_def_bounds = $elem_defs 499 | .get($elem) 500 | .unwrap() 501 | .bounds 502 | .$collect_elems(&options, &$declared_elems) 503 | .into_iter(); 504 | let from_where_clause_bounds = $where_clause_elems 505 | .get($elem) 506 | .map(|predicates| predicates.iter().map(|p| *p)) 507 | .into_iter() 508 | .flat_map(|ps| ps) 509 | .$collect_elems(&options, &$declared_elems) 510 | .into_iter(); 511 | 512 | from_def_bounds.chain(from_where_clause_bounds) 513 | }}; 514 | } 515 | 516 | // Collect lifetimes for the next_iteration. 517 | let new_lifetimes = current_lifetimes 518 | .iter() 519 | .flat_map(|lifetime| { 520 | new_elems_from_elem!( 521 | lifetime, 522 | lifetime_defs, 523 | where_clause_lifetimes, 524 | collect_lifetimes, 525 | declared_lifetimes, 526 | ) 527 | }) 528 | .chain(current_type_params.iter().flat_map(|ident| { 529 | new_elems_from_elem!( 530 | ident, 531 | type_param_defs, 532 | where_clause_types, 533 | collect_lifetimes, 534 | declared_lifetimes, 535 | ) 536 | })) 537 | .filter(|lifetime| { 538 | !state_lifetimes.contains(lifetime) 539 | && !current_lifetimes.contains(lifetime) 540 | }) 541 | .collect(); 542 | 543 | // Collect type parameter identifiers for the next iteration. 544 | let new_type_params = current_type_params 545 | .iter() 546 | .flat_map(|ident| { 547 | new_elems_from_elem!( 548 | ident, 549 | type_param_defs, 550 | where_clause_types, 551 | collect_type_params, 552 | declared_type_params, 553 | ) 554 | }) 555 | .filter(|ident| { 556 | !state_type_params.contains(ident) 557 | && !current_type_params.contains(ident) 558 | }) 559 | .collect(); 560 | 561 | // Prepare the loop state for the next iteration. 562 | state_lifetimes.extend(current_lifetimes); 563 | state_type_params.extend(current_type_params); 564 | 565 | current_lifetimes = new_lifetimes; 566 | current_type_params = new_type_params; 567 | } 568 | 569 | // Collect generic parameters for this state. 570 | // The result will preserve the order they were declared for the state 571 | // machine enum. 572 | let params = { 573 | let param_lifetime_defs = mgenerics 574 | .lifetimes() 575 | .map(|lifetime_def| &lifetime_def.lifetime) 576 | .filter(|lifetime| state_lifetimes.contains(lifetime)) 577 | .map(|lifetime| { 578 | let lifetime_def = *lifetime_defs.get(lifetime).unwrap(); 579 | syn::GenericParam::Lifetime(lifetime_def.clone()) 580 | }); 581 | 582 | let param_type_param_defs = mgenerics 583 | .type_params() 584 | .map(|type_param| &type_param.ident) 585 | .filter(|ident| state_type_params.contains(ident)) 586 | .map(|ident| { 587 | let type_param = *type_param_defs.get(ident).unwrap(); 588 | syn::GenericParam::Type(type_param.clone()) 589 | }); 590 | 591 | param_lifetime_defs.chain(param_type_param_defs).collect() 592 | }; 593 | 594 | // Collect where predicates for this state. 595 | // The result will preserve the order they were declared for the state 596 | // machine enum. 597 | let where_preds = { 598 | let where_preds_lifetimes = state_lifetimes 599 | .iter() 600 | .filter_map(|lifetime| { 601 | where_clause_lifetimes 602 | .get(lifetime) 603 | .map(|predicates| predicates.iter().map(|&p| p.clone())) 604 | }) 605 | .flat_map(|ps| ps); 606 | 607 | let where_preds_types = state_type_params 608 | .iter() 609 | .filter_map(|ident| { 610 | where_clause_types 611 | .get(ident) 612 | .map(|predicates| predicates.iter().map(|&p| p.clone())) 613 | }) 614 | .flat_map(|ps| ps); 615 | 616 | where_preds_lifetimes.chain(where_preds_types).collect() 617 | }; 618 | 619 | let generics = Rc::new(syn::Generics { 620 | lt_token: Some(::default()), 621 | params, 622 | gt_token: Some(]>::default()), 623 | where_clause: Some(syn::WhereClause { 624 | where_token: ::default(), 625 | predicates: where_preds, 626 | }), 627 | }); 628 | 629 | state.join(StateGenericsExtra { generics }) 630 | }) 631 | }) 632 | .collect() 633 | }; 634 | 635 | machine.join(extra, states) 636 | }) 637 | } 638 | } 639 | 640 | /// This state builds the generic parameters for the after state enums. 641 | #[derive(FromMeta, Debug)] 642 | pub struct AfterStateGenerics; 643 | 644 | dummy_default!(AfterStateGenerics); 645 | 646 | #[derive(Debug)] 647 | pub struct AfterStateGenericsExtra { 648 | /// The generics for the state. 649 | pub generics: Rc, 650 | /// The generics for the after state enum. 651 | pub after_state_generics: Rc, 652 | /// The generics of the transition states. 653 | pub transition_state_generics: HashMap>, 654 | } 655 | 656 | dummy_default!(AfterStateGenericsExtra); 657 | dummy_from_meta!(AfterStateGenericsExtra); 658 | 659 | impl Phase for AfterStateGenerics { 660 | type StateMachineExtra = ::StateMachineExtra; 661 | type StateExtra = AfterStateGenericsExtra; 662 | } 663 | 664 | impl Pass for AfterStateGenerics { 665 | type FromPhase = StateGenerics; 666 | 667 | fn pass(machine: StateMachine) -> StateMachine { 668 | machine.and_then(|machine, extra, states| { 669 | let states = { 670 | let mgenerics = &machine.generics; 671 | 672 | // Build a HashMap that maps from ident to generics 673 | let ident_to_generics = states 674 | .iter() 675 | .map(|s| (s.ident.clone(), s.extra.generics.clone())) 676 | .collect::>(); 677 | 678 | states 679 | .into_iter() 680 | .map(|state| { 681 | state.and_then(|state, extra| { 682 | let params = { 683 | // Filter all generic_params in the order they appear in the machine 684 | // generics. 685 | let lifetimes = mgenerics 686 | .lifetimes() 687 | .filter(|l| { 688 | state.transitions.iter().any(|ident| { 689 | ident_to_generics 690 | .get(ident) 691 | .map(|v| v.lifetimes().any(|l_| l_ == *l)) 692 | .unwrap_or(false) 693 | }) 694 | }) 695 | .cloned(); 696 | 697 | let type_params = mgenerics 698 | .type_params() 699 | .filter(|t| { 700 | state.transitions.iter().any(|ident| { 701 | ident_to_generics 702 | .get(ident) 703 | .map(|v| v.type_params().any(|t_| t_ == *t)) 704 | .unwrap_or(false) 705 | }) 706 | }) 707 | .cloned(); 708 | 709 | type_params 710 | .map(syn::GenericParam::Type) 711 | .chain(lifetimes.map(syn::GenericParam::Lifetime)) 712 | .collect() 713 | }; 714 | 715 | let where_preds = mgenerics 716 | .where_clause 717 | .as_ref() 718 | .map(|clause| { 719 | clause 720 | .predicates 721 | .iter() 722 | .filter(|p| { 723 | state.transitions.iter().any(|ident| { 724 | ident_to_generics 725 | .get(ident) 726 | .map(|v| { 727 | v.where_clause 728 | .as_ref() 729 | .map(|clause| { 730 | clause 731 | .predicates 732 | .iter() 733 | .any(|p_| p_ == *p) 734 | }) 735 | .unwrap_or(false) 736 | }) 737 | .unwrap_or(false) 738 | }) 739 | }) 740 | .cloned() 741 | .collect() 742 | }) 743 | .unwrap_or_default(); 744 | 745 | let after_state_generics = Rc::new(syn::Generics { 746 | lt_token: Some(::default()), 747 | params, 748 | gt_token: Some(]>::default()), 749 | where_clause: Some(syn::WhereClause { 750 | where_token: ::default(), 751 | predicates: where_preds, 752 | }), 753 | }); 754 | 755 | let transition_state_generics = ident_to_generics 756 | .iter() 757 | .filter(|&(ident, _)| state.transitions.contains(ident)) 758 | .map(|(ref ident, ref generics)| { 759 | ((*ident).clone(), (*generics).clone()) 760 | }) 761 | .collect::>(); 762 | 763 | state.join(AfterStateGenericsExtra { 764 | generics: extra.generics, 765 | after_state_generics, 766 | transition_state_generics, 767 | }) 768 | }) 769 | }) 770 | .collect() 771 | }; 772 | 773 | machine.join(extra, states) 774 | }) 775 | } 776 | } 777 | 778 | /// The final state, where we have computed everything required for codegen. 779 | #[derive(Debug)] 780 | pub struct ReadyForCodegen { 781 | pub start: usize, 782 | pub ready: usize, 783 | pub error: usize, 784 | pub states_enum: Rc, 785 | pub poll_trait: Rc, 786 | pub smf_crate: Rc, 787 | } 788 | 789 | dummy_default!(ReadyForCodegen); 790 | dummy_from_meta!(ReadyForCodegen); 791 | 792 | #[derive(Debug)] 793 | pub struct CodegenStateExtra { 794 | pub vis: Rc, 795 | pub description_ident: Rc, 796 | pub states_enum: Rc, 797 | pub error_type: Rc, 798 | pub error_ident: Rc, 799 | pub after: Ident, 800 | pub derive: Rc, 801 | pub poll_trait: Rc, 802 | pub poll_method: Ident, 803 | pub smf_crate: Rc, 804 | pub generics: Rc, 805 | pub after_state_generics: Rc, 806 | pub transition_state_generics: HashMap>, 807 | } 808 | 809 | dummy_from_meta!(CodegenStateExtra); 810 | dummy_default!(CodegenStateExtra); 811 | 812 | impl Phase for ReadyForCodegen { 813 | type StateMachineExtra = Self; 814 | type StateExtra = CodegenStateExtra; 815 | } 816 | 817 | impl Pass for ReadyForCodegen { 818 | type FromPhase = AfterStateGenerics; 819 | 820 | fn pass(machine: StateMachine) -> StateMachine { 821 | machine.and_then(|machine, extra, states| { 822 | let StartReadyError { 823 | start, 824 | ready, 825 | error, 826 | } = extra; 827 | 828 | let vis = Rc::new(machine.vis.clone()); 829 | 830 | let description_ident = Rc::new(machine.ident.clone()); 831 | 832 | let error_ident = states[error].ident.clone(); 833 | let error_ident = Rc::new(error_ident); 834 | 835 | let error_type = states[error].fields.fields[0].ty.clone(); 836 | let error_type = Rc::new(error_type); 837 | 838 | let derive = Rc::new(machine.derive.clone()); 839 | 840 | let machine_name = machine.ident.to_string(); 841 | 842 | let mut states_enum = machine_name.clone(); 843 | states_enum += "States"; 844 | let states_enum = Rc::new(Ident::new(&states_enum, Span::call_site())); 845 | 846 | let mut poll_trait = String::from("Poll"); 847 | poll_trait += &machine_name; 848 | let poll_trait = Rc::new(Ident::new(&poll_trait, Span::call_site())); 849 | 850 | let mut smf_crate = String::from("__smf_"); 851 | smf_crate += machine_name.clone().to_snake_case().as_str(); 852 | smf_crate += "_state_machine_future"; 853 | let smf_crate = Rc::new(Ident::new(&smf_crate, Span::call_site())); 854 | 855 | let states = states 856 | .into_iter() 857 | .map(|state| { 858 | state.and_then(|state, extra| { 859 | let vis = vis.clone(); 860 | let description_ident = description_ident.clone(); 861 | let error_ident = error_ident.clone(); 862 | let error_type = error_type.clone(); 863 | let generics = extra.generics.clone(); 864 | let after_state_generics = extra.after_state_generics.clone(); 865 | let transition_state_generics = extra.transition_state_generics.clone(); 866 | let derive = derive.clone(); 867 | let states_enum = states_enum.clone(); 868 | let poll_trait = poll_trait.clone(); 869 | let smf_crate = smf_crate.clone(); 870 | 871 | let ident_name = state.ident.to_string(); 872 | 873 | let mut after = String::from("After"); 874 | after.push_str(&ident_name); 875 | let after = Ident::new(&after, Span::call_site()); 876 | 877 | let mut poll_method = String::from("poll_"); 878 | poll_method.push_str(&ident_name.to_snake_case()); 879 | let poll_method = Ident::new(&poll_method, Span::call_site()); 880 | 881 | state.join(CodegenStateExtra { 882 | vis, 883 | description_ident, 884 | states_enum, 885 | error_ident, 886 | error_type, 887 | after, 888 | derive, 889 | poll_trait, 890 | poll_method, 891 | smf_crate, 892 | generics, 893 | after_state_generics, 894 | transition_state_generics, 895 | }) 896 | }) 897 | }) 898 | .collect(); 899 | 900 | machine.join( 901 | ReadyForCodegen { 902 | start, 903 | ready, 904 | error, 905 | states_enum, 906 | poll_trait, 907 | smf_crate, 908 | }, 909 | states, 910 | ) 911 | }) 912 | } 913 | } 914 | -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eux 4 | 5 | cd $(dirname "$0") 6 | 7 | pushd ./derive_state_machine_future 8 | cargo publish --dry-run 9 | 10 | popd 11 | cargo publish --dry-run 12 | 13 | pushd ./derive_state_machine_future 14 | cargo publish 15 | 16 | popd 17 | cargo publish 18 | -------------------------------------------------------------------------------- /src/compile_fail_tests.rs: -------------------------------------------------------------------------------- 1 | //! Compile-Fail Tests 2 | 3 | mod no_start_state { 4 | /*! 5 | ```compile_fail 6 | #[macro_use] 7 | extern crate state_machine_future; 8 | extern crate futures; 9 | use futures::*; 10 | fn main() {} 11 | 12 | #[derive(StateMachineFuture)] 13 | pub enum Machine { 14 | #[state_machine_future(ready)] 15 | Ready(usize), 16 | 17 | #[state_machine_future(error)] 18 | Error(usize), 19 | } 20 | ``` 21 | */ 22 | } 23 | 24 | mod no_error_state { 25 | /*! 26 | ```compile_fail 27 | #[macro_use] 28 | extern crate state_machine_future; 29 | extern crate futures; 30 | use futures::*; 31 | fn main() {} 32 | impl PollMachine for Machine { 33 | fn poll_start<'a>( 34 | _: &'a mut state_machine_future::RentToOwn<'a, Start> 35 | ) -> Poll { 36 | unimplemented!() 37 | } 38 | } 39 | 40 | 41 | #[derive(StateMachineFuture)] 42 | pub enum Machine { 43 | #[state_machine_future(start)] 44 | #[state_machine_future(transitions(Ready))] 45 | Start, 46 | 47 | #[state_machine_future(ready)] 48 | Ready(usize), 49 | } 50 | ``` 51 | */ 52 | } 53 | 54 | mod no_ready_state { 55 | /*! 56 | ```compile_fail 57 | #[macro_use] 58 | extern crate state_machine_future; 59 | extern crate futures; 60 | use futures::*; 61 | fn main() {} 62 | impl PollMachine for Machine { 63 | fn poll_start<'a>( 64 | _: &'a mut state_machine_future::RentToOwn<'a, Start> 65 | ) -> Poll { 66 | unimplemented!() 67 | } 68 | } 69 | 70 | 71 | #[derive(StateMachineFuture)] 72 | pub enum Machine { 73 | #[state_machine_future(start)] 74 | #[state_machine_future(transitions(Error))] 75 | Start, 76 | 77 | #[state_machine_future(error)] 78 | Error(usize), 79 | } 80 | ``` 81 | */ 82 | } 83 | 84 | mod more_than_one_start_state { 85 | /*! 86 | ```compile_fail 87 | #[macro_use] 88 | extern crate state_machine_future; 89 | extern crate futures; 90 | use futures::*; 91 | fn main() {} 92 | impl PollMachine for Machine { 93 | fn poll_start1<'a>( 94 | _: &'a mut state_machine_future::RentToOwn<'a, Start1> 95 | ) -> Poll { 96 | unimplemented!() 97 | } 98 | fn poll_start2<'a>( 99 | _: &'a mut state_machine_future::RentToOwn<'a, Start2> 100 | ) -> Poll { 101 | unimplemented!() 102 | } 103 | } 104 | 105 | #[derive(StateMachineFuture)] 106 | pub enum Machine { 107 | #[state_machine_future(start)] 108 | #[state_machine_future(transitions(Ready))] 109 | Start1, 110 | 111 | #[state_machine_future(start)] 112 | #[state_machine_future(transitions(Ready))] 113 | Start2, 114 | 115 | #[state_machine_future(ready)] 116 | Ready(usize), 117 | 118 | #[state_machine_future(error)] 119 | Error(usize), 120 | } 121 | ``` 122 | */ 123 | } 124 | 125 | mod more_than_one_error_state { 126 | /*! 127 | ```compile_fail 128 | #[macro_use] 129 | extern crate state_machine_future; 130 | extern crate futures; 131 | use futures::*; 132 | fn main() {} 133 | impl PollMachine for Machine { 134 | fn poll_start<'a>( 135 | _: &'a mut state_machine_future::RentToOwn<'a, Start> 136 | ) -> Poll { 137 | unimplemented!() 138 | } 139 | } 140 | 141 | 142 | #[derive(StateMachineFuture)] 143 | pub enum Machine { 144 | #[state_machine_future(start)] 145 | #[state_machine_future(transitions(Ready))] 146 | Start, 147 | 148 | #[state_machine_future(ready)] 149 | Ready(usize), 150 | 151 | #[state_machine_future(error)] 152 | Error1(usize), 153 | 154 | #[state_machine_future(error)] 155 | Error2(usize), 156 | } 157 | ``` 158 | */ 159 | } 160 | 161 | mod more_than_one_ready_state { 162 | /*! 163 | ```compile_fail 164 | #[macro_use] 165 | extern crate state_machine_future; 166 | extern crate futures; 167 | use futures::*; 168 | fn main() {} 169 | impl PollMachine for Machine { 170 | fn poll_start<'a>( 171 | _: &'a mut state_machine_future::RentToOwn<'a, Start> 172 | ) -> Poll { 173 | unimplemented!() 174 | } 175 | } 176 | 177 | #[derive(StateMachineFuture)] 178 | pub enum Machine { 179 | #[state_machine_future(start)] 180 | #[state_machine_future(transitions(Ready1))] 181 | Start, 182 | 183 | #[state_machine_future(ready)] 184 | Ready1(usize), 185 | 186 | #[state_machine_future(ready)] 187 | Ready2(usize), 188 | 189 | #[state_machine_future(error)] 190 | Error(usize), 191 | } 192 | ``` 193 | */ 194 | } 195 | 196 | mod no_transitions_on_non_ready_or_error_state { 197 | /*! 198 | ```compile_fail 199 | #[macro_use] 200 | extern crate state_machine_future; 201 | extern crate futures; 202 | use futures::*; 203 | fn main() {} 204 | impl PollMachine for Machine { 205 | fn poll_start<'a>( 206 | _: &'a mut state_machine_future::RentToOwn<'a, Start> 207 | ) -> Poll { 208 | unimplemented!() 209 | } 210 | } 211 | 212 | #[derive(StateMachineFuture)] 213 | pub enum Machine { 214 | #[state_machine_future(start)] 215 | Start, 216 | 217 | #[state_machine_future(ready)] 218 | #[state_machine_future(error)] 219 | Ready(usize), 220 | } 221 | ``` 222 | */ 223 | } 224 | 225 | mod ready_state_with_multiple_fields { 226 | /*! 227 | ```compile_fail 228 | #[macro_use] 229 | extern crate state_machine_future; 230 | extern crate futures; 231 | use futures::*; 232 | fn main() {} 233 | impl PollMachine for Machine { 234 | fn poll_start<'a>( 235 | _: &'a mut state_machine_future::RentToOwn<'a, Start> 236 | ) -> Poll { 237 | unimplemented!() 238 | } 239 | } 240 | 241 | #[derive(StateMachineFuture)] 242 | pub enum Machine { 243 | #[state_machine_future(start)] 244 | #[state_machine_future(transitions(Ready))] 245 | Start, 246 | 247 | #[state_machine_future(ready)] 248 | Ready(usize, usize), 249 | 250 | #[state_machine_future(error)] 251 | Error(usize), 252 | } 253 | ``` 254 | */ 255 | } 256 | 257 | mod error_state_with_multiple_fields { 258 | /*! 259 | ```compile_fail 260 | #[macro_use] 261 | extern crate state_machine_future; 262 | extern crate futures; 263 | use futures::*; 264 | fn main() {} 265 | impl PollMachine for Machine { 266 | fn poll_start<'a>( 267 | _: &'a mut state_machine_future::RentToOwn<'a, Start> 268 | ) -> Poll { 269 | unimplemented!() 270 | } 271 | } 272 | 273 | #[derive(StateMachineFuture)] 274 | pub enum Machine { 275 | #[state_machine_future(start)] 276 | #[state_machine_future(transitions(Ready))] 277 | Start, 278 | 279 | #[state_machine_future(ready)] 280 | Ready(usize), 281 | 282 | #[state_machine_future(error)] 283 | Error(usize, usize), 284 | } 285 | ``` 286 | */ 287 | } 288 | 289 | mod ready_state_with_unit_style_variant { 290 | /*! 291 | ```compile_fail 292 | #[macro_use] 293 | extern crate state_machine_future; 294 | extern crate futures; 295 | use futures::*; 296 | fn main() {} 297 | impl PollMachine for Machine { 298 | fn poll_start<'a>( 299 | _: &'a mut state_machine_future::RentToOwn<'a, Start> 300 | ) -> Poll { 301 | unimplemented!() 302 | } 303 | } 304 | 305 | #[derive(StateMachineFuture)] 306 | pub enum Machine { 307 | #[state_machine_future(start)] 308 | #[state_machine_future(transitions(Ready))] 309 | Start, 310 | 311 | #[state_machine_future(ready)] 312 | Ready, 313 | 314 | #[state_machine_future(error)] 315 | Error(usize), 316 | } 317 | ``` 318 | */ 319 | } 320 | 321 | mod error_state_with_unit_style_variant { 322 | /*! 323 | ```compile_fail 324 | #[macro_use] 325 | extern crate state_machine_future; 326 | extern crate futures; 327 | use futures::*; 328 | fn main() {} 329 | impl PollMachine for Machine { 330 | fn poll_start<'a>( 331 | _: &'a mut state_machine_future::RentToOwn<'a, Start> 332 | ) -> Poll { 333 | unimplemented!() 334 | } 335 | } 336 | 337 | 338 | #[derive(StateMachineFuture)] 339 | pub enum Machine { 340 | #[state_machine_future(start)] 341 | #[state_machine_future(transitions(Ready))] 342 | Start, 343 | 344 | #[state_machine_future(ready)] 345 | Ready(usize), 346 | 347 | #[state_machine_future(error)] 348 | Error, 349 | } 350 | ``` 351 | */ 352 | } 353 | 354 | mod ready_state_with_struct_style_variant { 355 | /*! 356 | ```compile_fail 357 | #[macro_use] 358 | extern crate state_machine_future; 359 | extern crate futures; 360 | use futures::*; 361 | fn main() {} 362 | impl PollMachine for Machine { 363 | fn poll_start<'a>( 364 | _: &'a mut state_machine_future::RentToOwn<'a, Start> 365 | ) -> Poll { 366 | unimplemented!() 367 | } 368 | } 369 | 370 | #[derive(StateMachineFuture)] 371 | pub enum Machine { 372 | #[state_machine_future(start)] 373 | #[state_machine_future(transitions(Ready))] 374 | Start, 375 | 376 | #[state_machine_future(ready)] 377 | Ready { x: usize }, 378 | 379 | #[state_machine_future(error)] 380 | Error(usize), 381 | } 382 | ``` 383 | */ 384 | } 385 | 386 | mod error_state_with_struct_style_variant { 387 | /*! 388 | ```compile_fail 389 | #[macro_use] 390 | extern crate state_machine_future; 391 | extern crate futures; 392 | use futures::*; 393 | fn main() {} 394 | impl PollMachine for Machine { 395 | fn poll_start<'a>( 396 | _: &'a mut state_machine_future::RentToOwn<'a, Start> 397 | ) -> Poll { 398 | unimplemented!() 399 | } 400 | } 401 | 402 | #[derive(StateMachineFuture)] 403 | pub enum Machine { 404 | #[state_machine_future(start)] 405 | #[state_machine_future(transitions(Ready))] 406 | Start, 407 | 408 | #[state_machine_future(ready)] 409 | Ready(usize), 410 | 411 | #[state_machine_future(error)] 412 | Error { x: usize }, 413 | } 414 | ``` 415 | */ 416 | } 417 | 418 | mod ready_state_with_transitions { 419 | /*! 420 | ```compile_fail 421 | #[macro_use] 422 | extern crate state_machine_future; 423 | extern crate futures; 424 | use futures::*; 425 | fn main() {} 426 | impl PollMachine for Machine { 427 | fn poll_start<'a>( 428 | _: &'a mut state_machine_future::RentToOwn<'a, Start> 429 | ) -> Poll { 430 | unimplemented!() 431 | } 432 | } 433 | 434 | #[derive(StateMachineFuture)] 435 | pub enum Machine { 436 | #[state_machine_future(start)] 437 | #[state_machine_future(transitions(Ready))] 438 | Start, 439 | 440 | #[state_machine_future(ready)] 441 | #[state_machine_future(transitions(Error))] 442 | Ready(usize), 443 | 444 | #[state_machine_future(error)] 445 | Error(usize), 446 | } 447 | ``` 448 | */ 449 | } 450 | 451 | mod error_state_with_transitions { 452 | /*! 453 | ```compile_fail 454 | #[macro_use] 455 | extern crate state_machine_future; 456 | extern crate futures; 457 | use futures::*; 458 | fn main() {} 459 | impl PollMachine for Machine { 460 | fn poll_start<'a>( 461 | _: &'a mut state_machine_future::RentToOwn<'a, Start> 462 | ) -> Poll { 463 | unimplemented!() 464 | } 465 | } 466 | 467 | #[derive(StateMachineFuture)] 468 | pub enum Machine { 469 | #[state_machine_future(start)] 470 | #[state_machine_future(transitions(Ready))] 471 | Start, 472 | 473 | #[state_machine_future(ready)] 474 | Ready(usize), 475 | 476 | #[state_machine_future(error)] 477 | #[state_machine_future(transitions(Ready))] 478 | Error(usize), 479 | } 480 | ``` 481 | */ 482 | } 483 | 484 | mod transition_to_unknown_state { 485 | /*! 486 | ```compile_fail 487 | #[macro_use] 488 | extern crate state_machine_future; 489 | extern crate futures; 490 | use futures::*; 491 | fn main() {} 492 | impl PollMachine for Machine { 493 | fn poll_start<'a>( 494 | _: &'a mut state_machine_future::RentToOwn<'a, Start> 495 | ) -> Poll { 496 | unimplemented!() 497 | } 498 | } 499 | 500 | #[derive(StateMachineFuture)] 501 | pub enum Machine { 502 | #[state_machine_future(start)] 503 | #[state_machine_future(transitions(UnknownState))] 504 | Start, 505 | 506 | #[state_machine_future(ready)] 507 | Ready(usize), 508 | 509 | #[state_machine_future(error)] 510 | Error(usize), 511 | } 512 | ``` 513 | */ 514 | } 515 | 516 | mod derives_not_added_to_future { 517 | /*! 518 | ```compile_fail 519 | #[macro_use] 520 | extern crate state_machine_future; 521 | extern crate futures; 522 | use futures::*; 523 | use std::fmt::Debug; 524 | 525 | #[derive(StateMachineFuture)] 526 | #[state_machine_future(derive(Debug))] 527 | pub enum Debuggable { 528 | #[state_machine_future(start)] 529 | #[state_machine_future(ready)] 530 | #[state_machine_future(error)] 531 | OnlyState(()), 532 | } 533 | 534 | fn check_debug(_: D) {} 535 | 536 | fn main() { 537 | check_debug(Debuggable::start(())); 538 | } 539 | 540 | ``` 541 | */ 542 | } 543 | 544 | mod unreachable_intermediate_state { 545 | /*! 546 | ```compile_fail 547 | #[macro_use] 548 | extern crate state_machine_future; 549 | extern crate futures; 550 | use futures::*; 551 | fn main() {} 552 | impl PollMachine for Machine { 553 | fn poll_start<'a>( 554 | _: &'a mut state_machine_future::RentToOwn<'a, Start> 555 | ) -> Poll { 556 | unimplemented!() 557 | } 558 | } 559 | 560 | #[derive(StateMachineFuture)] 561 | pub enum Machine { 562 | #[state_machine_future(start)] 563 | #[state_machine_future(transitions(Ready))] 564 | Start, 565 | 566 | #[state_machine_future(transitions(Unreachable2))] 567 | Unreachable1, 568 | 569 | #[state_machine_future(transitions(Ready))] 570 | Unreachable2, 571 | 572 | #[state_machine_future(ready)] 573 | Ready(usize), 574 | 575 | #[state_machine_future(error)] 576 | Error(usize), 577 | } 578 | ``` 579 | */ 580 | } 581 | 582 | mod reachable_state_without_path_to_ready_or_error { 583 | /*! 584 | ```compile_fail 585 | #[macro_use] 586 | extern crate state_machine_future; 587 | extern crate futures; 588 | use futures::*; 589 | fn main() {} 590 | impl PollMachine for Machine { 591 | fn poll_start<'a>( 592 | _: &'a mut state_machine_future::RentToOwn<'a, Start> 593 | ) -> Poll { 594 | unimplemented!() 595 | } 596 | fn poll_never_ready1<'a>( 597 | _: &'a mut state_machine_future::RentToOwn<'a, NeverReady1> 598 | ) -> Poll { 599 | unimplemented!() 600 | } 601 | fn poll_never_ready2<'a>( 602 | _: &'a mut state_machine_future::RentToOwn<'a, NeverReady2> 603 | ) -> Poll { 604 | unimplemented!() 605 | } 606 | } 607 | 608 | #[derive(StateMachineFuture)] 609 | pub enum Machine { 610 | #[state_machine_future(start)] 611 | #[state_machine_future(transitions(NeverReady1))] 612 | Start, 613 | 614 | #[state_machine_future(transitions(NeverReady2))] 615 | NeverReady1, 616 | 617 | #[state_machine_future(transitions(NeverReady1))] 618 | NeverReady2, 619 | 620 | #[state_machine_future(ready)] 621 | Ready(usize), 622 | 623 | #[state_machine_future(error)] 624 | Error(usize), 625 | } 626 | ``` 627 | */ 628 | } 629 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | 3 | [![](https://docs.rs/state_machine_future/badge.svg)](https://docs.rs/state_machine_future/) [![](https://img.shields.io/crates/v/state_machine_future.svg)](https://crates.io/crates/state_machine_future) [![](https://img.shields.io/crates/d/state_machine_future.png)](https://crates.io/crates/state_machine_future) [![Build Status](https://travis-ci.org/fitzgen/state_machine_future.png?branch=master)](https://travis-ci.org/fitzgen/state_machine_future) 4 | 5 | Easily create type-safe `Future`s from state machines — without the boilerplate. 6 | 7 | `state_machine_future` type checks state machines and their state transitions, 8 | and then generates `Future` implementations and typestate[0][] 9 | boilerplate for you. 10 | 11 | * [Introduction](#introduction) 12 | * [Guide](#guide) 13 | * [Example](#example) 14 | * [Attributes](#attributes) 15 | * [Macro](#macro) 16 | * [Features](#features) 17 | * [License](#license) 18 | * [Contribution](#contribution) 19 | 20 | ## Introduction 21 | 22 | Most of the time, using `Future` combinators like `map` and `then` are a great 23 | way to describe an asynchronous computation. Other times, the most natural way 24 | to describe the process at hand is a state machine. 25 | 26 | When writing state machines in Rust, we want to *leverage the type system to 27 | enforce that only valid state transitions may occur*. To do that, we want 28 | *typestates*[0][]: types that represents each state in the state 29 | machine, and methods whose signatures only permit valid state transitions. But 30 | we *also* need an `enum` of every possible state, so we can treat the whole 31 | state machine as a single entity, and implement `Future` for it. But this is 32 | getting to be a *lot* of boilerplate... 33 | 34 | Enter `#[derive(StateMachineFuture)]`. 35 | 36 | With `#[derive(StateMachineFuture)]`, we describe the states and the possible 37 | transitions between them, and then the custom derive generates: 38 | 39 | * A typestate for each state in the state machine. 40 | 41 | * A type for the whole state machine that implements `Future`. 42 | 43 | * A concrete `start` method that constructs the state machine `Future` for you, 44 | initialized to its start state. 45 | 46 | * A state transition polling trait, with a `poll_zee_choo` method for each 47 | non-final state `ZeeChoo`. This trait describes the state machine's valid 48 | transitions, and its methods are called by `Future::poll`. 49 | 50 | Then, all *we* need to do is implement the generated state transition polling 51 | trait. 52 | 53 | Additionally, `#[derive(StateMachineFuture)]` will statically prevent against 54 | some footguns that can arise when writing state machines: 55 | 56 | * Every state is reachable from the start state: *there are no useless states.* 57 | 58 | * *There are no states which cannot reach a final state*. These states would 59 | otherwise lead to infinite loops. 60 | 61 | * *All state transitions are valid.* Attempting to make an invalid state 62 | transition fails to type check, thanks to the generated typestates. 63 | 64 | ## Guide 65 | 66 | Describe the state machine's states with an `enum` and add 67 | `#[derive(StateMachineFuture)]` to it: 68 | 69 | ```ignore 70 | #[derive(StateMachineFuture)] 71 | enum MyStateMachine { 72 | // ... 73 | } 74 | ``` 75 | 76 | There must be one **start** state, which is the initial state upon construction; 77 | one **ready** state, which corresponds to `Future::Item`; and one **error** 78 | state, which corresponds to `Future::Error`. 79 | 80 | ```ignore 81 | #[derive(StateMachineFuture)] 82 | enum MyStateMachine { 83 | #[state_machine_future(start)] 84 | Start, 85 | 86 | // ... 87 | 88 | #[state_machine_future(ready)] 89 | Ready(MyItem), 90 | 91 | #[state_machine_future(error)] 92 | Error(MyError), 93 | } 94 | ``` 95 | 96 | Any other variants of the `enum` are intermediate states. 97 | 98 | We define which state-to-state transitions are valid with 99 | `#[state_machine_future(transitions(...))]`. This attribute annotates a state 100 | variant, and lists which other states can be transitioned to immediately after 101 | this state. 102 | 103 | A final state (either **ready** or **error**) must be reachable from every 104 | intermediate state and the **start** state. Final states are not allowed to have 105 | transitions. 106 | 107 | ```ignore 108 | #[derive(StateMachineFuture)] 109 | enum MyStateMachine { 110 | #[state_machine_future(start, transitions(Intermediate))] 111 | Start, 112 | 113 | #[state_machine_future(transitions(Start, Ready))] 114 | Intermediate { x: usize, y: usize }, 115 | 116 | #[state_machine_future(ready)] 117 | Ready(MyItem), 118 | 119 | #[state_machine_future(error)] 120 | Error(MyError), 121 | } 122 | ``` 123 | 124 | From this state machine description, the custom derive generates boilerplate for 125 | us. 126 | 127 | For each state, the custom derive creates: 128 | 129 | * A typestate for the state. The type's name matches the variant name, for 130 | example the `Intermediate` state variant's typestate is also named `Intermediate`. 131 | The kind of struct type generated matches the variant kind: a unit-style variant 132 | results in a unit struct, a tuple-style variant results in a tuple struct, and a 133 | struct-style variant results in a normal struct with fields. 134 | 135 | | State `enum` Variant | Generated Typestate | 136 | | ------------------------------------------------- | ------------------------------ | 137 | | `enum StateMachine { MyState, ... }` | `struct MyState;` | 138 | | `enum StateMachine { MyState(bool, usize), ... }` | `struct MyState(bool, usize);` | 139 | | `enum StateMachine { MyState { x: usize }, ... }` | `struct MyState { x: usize };` | 140 | 141 | * An `enum` for the possible states that can come after this state. This `enum` 142 | is named `AfterX` where `X` is the state's name. There is also a `From` 143 | implementation for each `Y` state that can be transitioned to after `X`. For 144 | example, the `Intermediate` state would get: 145 | 146 | ```ignore 147 | enum AfterIntermediate { 148 | Start(Start), 149 | Ready(Ready), 150 | } 151 | 152 | impl From for AfterIntermediate { 153 | // ... 154 | } 155 | 156 | impl From for AfterIntermediate { 157 | // ... 158 | } 159 | ``` 160 | 161 | Next, for the state machine as a whole, the custom derive generates: 162 | 163 | * A state machine `Future` type, which is essentially an `enum` of all the 164 | different typestates. This type is named `BlahFuture` where `Blah` is the name 165 | of the state machine description `enum`. In this example, where the state 166 | machine description is named `MyStateMachine`, the generated state machine 167 | future type would be named `MyStateMachineFuture`. 168 | 169 | * A polling trait, `PollBordle` where `Bordle` is this state machine 170 | description's name. For each non-final state `TootWasabi`, this trait has a 171 | method, `poll_toot_wasabi`, which is like `Future::poll` but specialized to the 172 | current state. Each method takes conditional ownership of its state (via 173 | [`RentToOwn`][rent_to_own]) and returns a `futures::Poll` 174 | where `Error` is the state machine's error type. This signature *does not allow 175 | invalid state transitions*, which makes attempting an illegal state transition 176 | fail to type check. Here is the `MyStateMachine`'s polling trait, for example: 177 | 178 | ```ignore 179 | trait PollMyStateMachine { 180 | fn poll_start<'a>( 181 | start: &'a mut RentToOwn<'a, Start>, 182 | ) -> Poll; 183 | 184 | fn poll_intermediate<'a>( 185 | intermediate: &'a mut RentToOwn<'a, Intermediate>, 186 | ) -> Poll; 187 | } 188 | ``` 189 | 190 | * An implementation of `Future` for that type. This implementation dispatches to 191 | the appropriate polling trait method depending on what state the future is 192 | in: 193 | 194 | * If the `Future` is in the `Start` state, then it uses `::poll_start`. 196 | 197 | * If it is in the `Intermediate` state, then it uses `::poll_intermediate`. 199 | 200 | * Etc... 201 | 202 | * A concrete `start` method for the description type (so `MyStateMachine::start` 203 | in this example) which constructs a new state machine `Future` type in its 204 | **start** state for you. This method has a parameter for each field in the 205 | **start** state variant. 206 | 207 | | Start `enum` Variant | Generated `start` Method | 208 | | ------------------------------- | ------------------------------------------------------------------- | 209 | | `MyStart,` | `fn start() -> MyStateMachineFuture { ... }` | 210 | | `MyStart(bool, usize),` | `fn start(arg0: bool, arg1: usize) -> MyStateMachineFuture { ... }` | 211 | | `MyStart { x: char, y: bool },` | `fn start(x: char, y: bool) -> MyStateMachineFuture { ... }` | 212 | 213 | Given all those generated types and traits, all we have to do is `impl PollBlah 214 | for Blah` for our state machine `Blah`. 215 | 216 | ```ignore 217 | impl PollMyStateMachine for MyStateMachine { 218 | fn poll_start<'a>( 219 | start: &'a mut RentToOwn<'a, Start> 220 | ) -> Poll { 221 | // Call `try_ready!(start.inner.poll())` with any inner futures here. 222 | // 223 | // If we're ready to transition states, then we should return 224 | // `Ok(Async::Ready(AfterStart))`. If we are not ready to transition 225 | // states, return `Ok(Async::NotReady)`. If we encounter an error, 226 | // return `Err(...)`. 227 | } 228 | 229 | fn poll_intermediate<'a>( 230 | intermediate: &'a mut RentToOwn<'a, Intermediate> 231 | ) -> Poll { 232 | // Same deal as above... 233 | } 234 | } 235 | ``` 236 | 237 | ## Context 238 | 239 | The state machine also allows to pass in a context that is available in every `poll_*` method 240 | without having to explicitly include it in every one. 241 | 242 | The context can be specified through the `context` argument of the `state_machine_future` attribute. 243 | This will add parameters to the `start` method as well as to each `poll_*` method of the trait. 244 | 245 | ``` 246 | #[macro_use] 247 | extern crate state_machine_future; 248 | extern crate futures; 249 | 250 | use futures::*; 251 | use state_machine_future::*; 252 | 253 | struct MyContext { 254 | 255 | } 256 | 257 | struct MyItem { 258 | 259 | } 260 | 261 | enum MyError { 262 | 263 | } 264 | 265 | #[derive(StateMachineFuture)] 266 | #[state_machine_future(context = "MyContext")] 267 | enum MyStateMachine { 268 | #[state_machine_future(start, transitions(Intermediate))] 269 | Start, 270 | 271 | #[state_machine_future(transitions(Start, Ready))] 272 | Intermediate { x: usize, y: usize }, 273 | 274 | #[state_machine_future(ready)] 275 | Ready(MyItem), 276 | 277 | #[state_machine_future(error)] 278 | Error(MyError), 279 | } 280 | 281 | impl PollMyStateMachine for MyStateMachine { 282 | fn poll_start<'s, 'c>( 283 | start: &'s mut RentToOwn<'s, Start>, 284 | context: &'c mut RentToOwn<'c, MyContext> 285 | ) -> Poll { 286 | 287 | // The `context` instance passed into `start` is available here. 288 | // It is a mutable reference, so are free to modify it. 289 | 290 | unimplemented!() 291 | } 292 | 293 | fn poll_intermediate<'s, 'c>( 294 | intermediate: &'s mut RentToOwn<'s, Intermediate>, 295 | context: &'c mut RentToOwn<'c, MyContext> 296 | ) -> Poll { 297 | 298 | // The `context` is available here as well. 299 | // It is the same instance. This means if `poll_start` modified it, those 300 | // changes will be visible to this method as well. 301 | 302 | unimplemented!() 303 | } 304 | } 305 | 306 | fn main() { 307 | let _ = MyStateMachine::start(MyContext { }); 308 | } 309 | ``` 310 | 311 | Same as for the state argument, the context can be taken through the `RentToOwn` type! 312 | However, be aware that once you take the context, the state machine will **always** return 313 | `Async::NotReady` **without** invoking the `poll_` methods anymore. The one exception to 314 | this is when the state machine is in a ready or error state, where it will resolve normally 315 | when polled if the context has been taken. 316 | 317 | That's it! 318 | 319 | ## Example 320 | 321 | Here is an example of a simple turn-based game played by two players over HTTP. 322 | 323 | ``` 324 | #[macro_use] 325 | extern crate state_machine_future; 326 | 327 | #[macro_use] 328 | extern crate futures; 329 | 330 | use futures::{Async, Future, Poll}; 331 | use state_machine_future::RentToOwn; 332 | # pub struct Player; 333 | # impl Player { 334 | # fn request_turn(&self) -> HttpTurnFuture { unimplemented!() } 335 | # } 336 | # pub struct HttpError; 337 | # pub struct HttpInvitationFuture; 338 | # impl Future for HttpInvitationFuture { 339 | # type Item = (); 340 | # type Error = HttpError; 341 | # fn poll(&mut self) -> Poll<(), HttpError> { 342 | # unimplemented!() 343 | # } 344 | # } 345 | # pub struct HttpTurnFuture; 346 | # impl Future for HttpTurnFuture { 347 | # type Item = (); 348 | # type Error = HttpError; 349 | # fn poll(&mut self) -> Poll<(), HttpError> { 350 | # unimplemented!() 351 | # } 352 | # } 353 | # fn process_turn(_: ()) -> Option { unimplemented!() } 354 | # fn main() {} 355 | 356 | /// The result of a game. 357 | pub struct GameResult { 358 | winner: Player, 359 | loser: Player, 360 | } 361 | 362 | /// Some kind of simple turn based game. 363 | /// 364 | /// ```text 365 | /// Invite 366 | /// | 367 | /// | 368 | /// | accept invitation 369 | /// | 370 | /// | 371 | /// V 372 | /// WaitingForTurn --------+ 373 | /// | ^ | 374 | /// | | | receive turn 375 | /// | | | 376 | /// | +-------------+ 377 | /// game concludes | 378 | /// | 379 | /// | 380 | /// | 381 | /// V 382 | /// Finished 383 | /// ``` 384 | #[derive(StateMachineFuture)] 385 | enum Game { 386 | /// The game begins with an invitation to play from one player to another. 387 | /// 388 | /// Once the invited player accepts the invitation over HTTP, then we will 389 | /// switch states into playing the game, waiting to recieve each turn. 390 | #[state_machine_future(start, transitions(WaitingForTurn))] 391 | Invite { 392 | invitation: HttpInvitationFuture, 393 | from: Player, 394 | to: Player, 395 | }, 396 | 397 | // We are waiting on a turn. 398 | // 399 | // Upon receiving it, if the game is now complete, then we go to the 400 | // `Finished` state. Otherwise, we give the other player a turn. 401 | #[state_machine_future(transitions(WaitingForTurn, Finished))] 402 | WaitingForTurn { 403 | turn: HttpTurnFuture, 404 | active: Player, 405 | idle: Player, 406 | }, 407 | 408 | // The game is finished with a `GameResult`. 409 | // 410 | // The `GameResult` becomes the `Future::Item`. 411 | #[state_machine_future(ready)] 412 | Finished(GameResult), 413 | 414 | // Any state transition can implicitly go to this error state if we get an 415 | // `HttpError` while waiting on a turn or invitation acceptance. 416 | // 417 | // This `HttpError` is used as the `Future::Error`. 418 | #[state_machine_future(error)] 419 | Error(HttpError), 420 | } 421 | 422 | // Now, we implement the generated state transition polling trait for our state 423 | // machine description type. 424 | 425 | impl PollGame for Game { 426 | fn poll_invite<'a>( 427 | invite: &'a mut RentToOwn<'a, Invite> 428 | ) -> Poll { 429 | // See if the invitation has been accepted. If not, this will early 430 | // return with `Ok(Async::NotReady)` or propagate any HTTP errors. 431 | try_ready!(invite.invitation.poll()); 432 | 433 | // We're ready to transition into the `WaitingForTurn` state, so take 434 | // ownership of the `Invite` and then construct and return the new 435 | // state. 436 | let invite = invite.take(); 437 | let waiting = WaitingForTurn { 438 | turn: invite.from.request_turn(), 439 | active: invite.from, 440 | idle: invite.to, 441 | }; 442 | transition!(waiting) 443 | } 444 | 445 | fn poll_waiting_for_turn<'a>( 446 | waiting: &'a mut RentToOwn<'a, WaitingForTurn> 447 | ) -> Poll { 448 | // See if the next turn has arrived over HTTP. Again, this will early 449 | // return `Ok(Async::NotReady)` if the turn hasn't arrived yet, and 450 | // propagate any HTTP errors that we might encounter. 451 | let turn = try_ready!(waiting.turn.poll()); 452 | 453 | // Ok, we have a new turn. Take ownership of the `WaitingForTurn` state, 454 | // process the turn and if the game is over, then transition to the 455 | // `Finished` state, otherwise swap which player we need a new turn from 456 | // and request the turn over HTTP. 457 | let waiting = waiting.take(); 458 | if let Some(game_result) = process_turn(turn) { 459 | transition!(Finished(game_result)) 460 | } else { 461 | let next_waiting = WaitingForTurn { 462 | turn: waiting.idle.request_turn(), 463 | active: waiting.idle, 464 | idle: waiting.active, 465 | }; 466 | Ok(Async::Ready(next_waiting.into())) 467 | } 468 | } 469 | } 470 | 471 | # struct TokioHandle; 472 | # impl TokioHandle { fn spawn(&self, _: T) {} } 473 | # fn get_some_player() -> Player { unimplemented!() } 474 | # fn get_another_player() -> Player { unimplemented!() } 475 | # fn invite(_: &Player, _: &Player) -> HttpInvitationFuture { unimplemented!() } 476 | // To spawn a new `Game` as a `Future` on whatever executor we're using (for 477 | // example `tokio`), we use `Game::start` to construct the `Future` in its start 478 | // state and then pass it to the executor. 479 | fn spawn_game(handle: TokioHandle) { 480 | let from = get_some_player(); 481 | let to = get_another_player(); 482 | let invitation = invite(&from, &to); 483 | let future = Game::start(invitation, from, to); 484 | handle.spawn(future) 485 | } 486 | ``` 487 | 488 | ## Attributes 489 | 490 | This is a list of all of the attributes used by `state_machine_future`: 491 | 492 | * `#[derive(StateMachineFuture)]`: Placed on an `enum` that describes a state 493 | machine. 494 | 495 | * `#[state_machine_future(derive(Clone, Debug, ...))]`: Placed on the `enum` 496 | that describes the state machine. This attribute describes which 497 | `#[derive(...)]`s to place on the generated `Future` type. 498 | 499 | * `#[state_machine_future(start)]`: Used on a variant of the state machine 500 | description `enum`. There must be exactly one variant with this attribute. This 501 | describes the initial starting state. The generated `start` method has a 502 | parameter for each field in this variant. 503 | 504 | * `#[state_machine_future(ready)]`: Used on a variant of the state machine 505 | description `enum`. There must be exactly one variant with this attribute. It 506 | must be a tuple-style variant with one field, for example `Ready(MyItemType)`. 507 | The generated `Future` implementation uses the field's type as `Future::Item`. 508 | 509 | * `#[state_machine_future(error)]`: Used on a variant of the state machine 510 | description `enum`. There must be exactly one variant with this attribute. It 511 | must be a tuple-style variant with one field, for example `Error(MyError)`. The 512 | generated `Future` implementation uses the field's type as `Future::Error`. 513 | 514 | * `#[state_machine_future(transitions(OtherState, AnotherState, ...))]`: Used on 515 | a variant of the state machine description `enum`. Describes the states that 516 | this one can transition to. 517 | 518 | ## Macro 519 | 520 | An auxiliary macro is provided that helps reducing boilerplate code for state 521 | transitions. So, the following code: 522 | 523 | ```Ok(Ready(NextState(1).into()))``` 524 | 525 | Can be reduced to: 526 | 527 | ```transition!(NextState(1))``` 528 | 529 | ## Features 530 | 531 | Here are the `cargo` features that you can enable: 532 | 533 | * `debug_code_generation`: Prints the code generated by 534 | `#[derive(StateMachineFuture)]` to `stdout` for debugging purposes. 535 | 536 | ## License 537 | 538 | Licensed under either of 539 | 540 | * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 541 | 542 | * [MIT license](http://opensource.org/licenses/MIT) 543 | 544 | at your option. 545 | 546 | ## Contribution 547 | 548 | See 549 | [CONTRIBUTING.md](https://github.com/fitzgen/state_machine_future/blob/master/CONTRIBUTING.md) 550 | for hacking. 551 | 552 | Unless you explicitly state otherwise, any contribution intentionally submitted 553 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 554 | dual licensed as above, without any additional terms or conditions. 555 | 556 | 557 | [0]: https://en.wikipedia.org/wiki/Typestate_analysis 558 | 559 | [rent_to_own]: https://crates.io/crates/rent_to_own 560 | 561 | */ 562 | 563 | #![deny(missing_docs)] 564 | #![deny(missing_debug_implementations)] 565 | 566 | extern crate futures; 567 | extern crate rent_to_own; 568 | 569 | // Re-export the custom derive. This allows people to depend only on this crate 570 | // directly, not both this one and `derive_state_machine_future`. 571 | #[allow(unused_imports)] 572 | #[macro_use] 573 | extern crate derive_state_machine_future; 574 | pub use derive_state_machine_future::*; 575 | 576 | mod compile_fail_tests; 577 | #[macro_use] 578 | mod transition; 579 | 580 | // Helpers used by generated code. Not public API. 581 | #[doc(hidden)] 582 | pub mod export { 583 | pub use futures::{Async, Future, Poll}; 584 | } 585 | 586 | /// Re-export of `rent_to_own::RentToOwn`. 587 | pub type RentToOwn<'a, T> = rent_to_own::RentToOwn<'a, T>; 588 | 589 | /// A trait that links an `enum` with `#[derive(StateMachineFuture)]` to its 590 | /// generated type that implements `Future`. 591 | pub trait StateMachineFuture { 592 | /// The generated `Future` type for this state machine. 593 | type Future: futures::Future; 594 | } 595 | -------------------------------------------------------------------------------- /src/transition.rs: -------------------------------------------------------------------------------- 1 | /// Auxiliary macro for `poll_state_xy` functions to transition into a new state. 2 | #[macro_export] 3 | macro_rules! transition { 4 | ( $new_state:expr ) => { 5 | return Ok(::futures::Async::Ready($new_state.into())); 6 | }; 7 | } 8 | -------------------------------------------------------------------------------- /tests/call_to_context.rs: -------------------------------------------------------------------------------- 1 | //! Test that we can access context type. 2 | 3 | #[macro_use] 4 | extern crate futures; 5 | #[macro_use] 6 | extern crate state_machine_future; 7 | 8 | use futures::Async; 9 | use futures::Future; 10 | use futures::Poll; 11 | use state_machine_future::RentToOwn; 12 | 13 | pub struct ExternalSource { 14 | pub value: T, 15 | } 16 | 17 | pub struct Context { 18 | pub external_source: ExternalSource, 19 | pub lazy_future: Option>>, 20 | } 21 | 22 | impl Context { 23 | fn load_from_external_source(&mut self) -> &mut Box> { 24 | let value = &self.external_source.value; 25 | 26 | self.lazy_future 27 | .get_or_insert_with(|| Box::new(futures::future::ok(value.clone()))) 28 | } 29 | } 30 | 31 | #[derive(StateMachineFuture)] 32 | #[state_machine_future(context = "Context")] 33 | pub enum WithContext { 34 | #[state_machine_future(start, transitions(Ready))] 35 | Start(()), 36 | 37 | #[state_machine_future(ready)] 38 | Ready(T), 39 | 40 | #[state_machine_future(error)] 41 | Error(()), 42 | } 43 | 44 | impl PollWithContext for WithContext { 45 | fn poll_start<'s, 'c>( 46 | _: &'s mut RentToOwn<'s, Start>, 47 | context: &'c mut RentToOwn<'c, Context>, 48 | ) -> Poll, ()> { 49 | let value = try_ready!(context.load_from_external_source().poll()); 50 | 51 | transition!(Ready(value)) 52 | } 53 | } 54 | 55 | #[test] 56 | fn can_call_to_context() { 57 | let source = ExternalSource { 58 | value: String::from("foo"), 59 | }; 60 | 61 | let context = Context { 62 | external_source: source, 63 | lazy_future: None, 64 | }; 65 | 66 | let mut machine = WithContext::start((), context); 67 | 68 | assert_eq!(machine.poll(), Ok(Async::Ready(String::from("foo")))); 69 | } 70 | -------------------------------------------------------------------------------- /tests/cargo_readme_up_to_date.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::Read; 3 | use std::process::Command; 4 | 5 | #[test] 6 | fn cargo_readme_up_to_date() { 7 | println!("Checking that `cargo readme > README.md` is up to date..."); 8 | 9 | let output = Command::new("cargo") 10 | .arg("readme") 11 | .current_dir(env!("CARGO_MANIFEST_DIR")) 12 | .output() 13 | .expect("should run `cargo readme` OK"); 14 | 15 | assert!( 16 | output.status.success(), 17 | "Check if you have `cargo-readme` in $PATH" 18 | ); 19 | let expected = String::from_utf8_lossy(&output.stdout); 20 | 21 | let actual = { 22 | let mut file = File::open(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md")) 23 | .expect("should open README.md file"); 24 | let mut s = String::new(); 25 | file.read_to_string(&mut s) 26 | .expect("should read contents of file to string"); 27 | s 28 | }; 29 | 30 | if actual != expected { 31 | panic!("Run `cargo readme > README.md` to update README.md"); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/context_and_derives.rs: -------------------------------------------------------------------------------- 1 | //! Test that we can access context type. 2 | 3 | #[macro_use] 4 | extern crate state_machine_future; 5 | 6 | use std::fmt::Debug; 7 | 8 | pub struct Context {} 9 | 10 | #[derive(StateMachineFuture)] 11 | #[state_machine_future(context = "Context", derive(Debug))] 12 | pub enum WithContext { 13 | #[state_machine_future(start)] 14 | #[state_machine_future(ready)] 15 | #[state_machine_future(error)] 16 | OnlyState(()), 17 | } 18 | 19 | fn check_debug(_: D) {} 20 | 21 | #[test] 22 | fn given_sm_with_context_should_add_derives_to_states() { 23 | check_debug(OnlyState(())); 24 | } 25 | -------------------------------------------------------------------------------- /tests/context_start_in_method.rs: -------------------------------------------------------------------------------- 1 | //! Test that we can access context type. 2 | 3 | extern crate futures; 4 | #[macro_use] 5 | extern crate state_machine_future; 6 | 7 | use futures::Poll; 8 | use state_machine_future::RentToOwn; 9 | 10 | pub struct Context {} 11 | 12 | #[derive(StateMachineFuture)] 13 | #[state_machine_future(context = "Context")] 14 | pub enum WithContext { 15 | #[state_machine_future(start, transitions(Ready))] 16 | Start, 17 | 18 | #[state_machine_future(ready)] 19 | Ready(()), 20 | 21 | #[state_machine_future(error)] 22 | Error(()), 23 | } 24 | 25 | impl PollWithContext for WithContext { 26 | fn poll_start<'s, 'c>( 27 | _: &'s mut RentToOwn<'s, Start>, 28 | _: &'c mut RentToOwn<'c, Context>, 29 | ) -> Poll { 30 | unimplemented!() 31 | } 32 | } 33 | 34 | #[test] 35 | fn given_sm_with_no_start_args_only_takes_context() { 36 | let context = Context {}; 37 | 38 | let _ = WithContext::start_in(Ready(()), context); 39 | } 40 | -------------------------------------------------------------------------------- /tests/context_start_method.rs: -------------------------------------------------------------------------------- 1 | //! Test that we can access context type. 2 | 3 | extern crate futures; 4 | #[macro_use] 5 | extern crate state_machine_future; 6 | 7 | use futures::Poll; 8 | use state_machine_future::RentToOwn; 9 | 10 | pub struct Context {} 11 | 12 | #[derive(StateMachineFuture)] 13 | #[state_machine_future(context = "Context")] 14 | pub enum WithContext { 15 | #[state_machine_future(start, transitions(Ready))] 16 | Start, 17 | 18 | #[state_machine_future(ready)] 19 | Ready(()), 20 | 21 | #[state_machine_future(error)] 22 | Error(()), 23 | } 24 | 25 | impl PollWithContext for WithContext { 26 | fn poll_start<'s, 'c>( 27 | _: &'s mut RentToOwn<'s, Start>, 28 | _: &'c mut RentToOwn<'c, Context>, 29 | ) -> Poll { 30 | unimplemented!() 31 | } 32 | } 33 | 34 | #[test] 35 | fn given_sm_with_no_start_args_only_takes_context() { 36 | let context = Context {}; 37 | 38 | let _ = WithContext::start(context); 39 | } 40 | -------------------------------------------------------------------------------- /tests/derives.rs: -------------------------------------------------------------------------------- 1 | //! Test that we add derive traits properly. 2 | 3 | extern crate futures; 4 | #[macro_use] 5 | extern crate state_machine_future; 6 | 7 | use std::fmt::Debug; 8 | 9 | #[derive(StateMachineFuture)] 10 | #[state_machine_future(derive(Debug))] 11 | pub enum Debuggable { 12 | #[state_machine_future(start)] 13 | #[state_machine_future(ready)] 14 | #[state_machine_future(error)] 15 | OnlyState(()), 16 | } 17 | 18 | fn check_debug(_: D) {} 19 | 20 | #[test] 21 | fn state_derived_debug() { 22 | check_debug(OnlyState(())); 23 | } 24 | -------------------------------------------------------------------------------- /tests/different_kinds_of_enums.rs: -------------------------------------------------------------------------------- 1 | //! Test that we handle different kinds of state enums correctly. 2 | 3 | extern crate futures; 4 | #[macro_use] 5 | extern crate state_machine_future; 6 | 7 | use futures::Poll; 8 | use state_machine_future::RentToOwn; 9 | 10 | #[derive(StateMachineFuture)] 11 | pub enum Fsm { 12 | #[state_machine_future(start)] 13 | #[state_machine_future(transitions(Tuple))] 14 | Unit, 15 | 16 | #[state_machine_future(transitions(Struct))] 17 | Tuple(usize, bool), 18 | 19 | #[state_machine_future(transitions(Done))] 20 | Struct { x: usize, y: bool }, 21 | 22 | #[state_machine_future(ready)] 23 | #[state_machine_future(error)] 24 | Done(()), 25 | } 26 | 27 | impl PollFsm for Fsm { 28 | fn poll_unit<'a>(unit: &'a mut RentToOwn<'a, Unit>) -> Poll { 29 | match unit.take() { 30 | self::Unit => unimplemented!(), 31 | } 32 | } 33 | fn poll_tuple<'a>(tuple: &'a mut RentToOwn<'a, Tuple>) -> Poll { 34 | match tuple.take() { 35 | Tuple(3, true) | Tuple(_, _) => unimplemented!(), 36 | } 37 | } 38 | fn poll_struct<'a>(st: &'a mut RentToOwn<'a, Struct>) -> Poll { 39 | match st.take() { 40 | Struct { x: 3, y: true } | Struct { .. } => unimplemented!(), 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/generics.rs: -------------------------------------------------------------------------------- 1 | //! Test that we handle generics properly. 2 | //! 3 | //! Here's the deal: we don't figure out which generics are used in which 4 | //! variants, so it is up to you to add phantom datas as needed. 5 | 6 | extern crate futures; 7 | #[macro_use] 8 | extern crate state_machine_future; 9 | 10 | use futures::{Future, Poll}; 11 | use state_machine_future::RentToOwn; 12 | use std::fmt::Debug; 13 | use std::io; 14 | use std::marker::PhantomData; 15 | 16 | pub trait ComplexTrait<'a, T> { 17 | fn data(&self) -> &'a T; 18 | } 19 | 20 | pub trait AssociatedTypesTrait { 21 | type Type; 22 | } 23 | 24 | pub struct StartType<'a, 'c, 'd: 'a, T, C> 25 | where 26 | T: ComplexTrait<'c, C>, 27 | C: 'c, 28 | 'c: 'd, 29 | { 30 | _data: T, 31 | _phan: PhantomData<&'a C>, 32 | _phan2: PhantomData<&'c C>, 33 | _phan3: PhantomData<&'d C>, 34 | } 35 | 36 | #[derive(StateMachineFuture)] 37 | pub enum Fsm<'a, 'c, 'd: 'a, T: 'static, E, C, D> 38 | where 39 | T: ComplexTrait<'c, C>, 40 | E: Debug, 41 | C: 'c, 42 | 'c: 'd, 43 | D: AssociatedTypesTrait, 44 | { 45 | /// The start state. 46 | #[state_machine_future(start)] 47 | #[state_machine_future(transitions(Complex, AssociatedType, Ready, Error))] 48 | Start(StartType<'a, 'c, 'd, T, C>, D), 49 | 50 | #[state_machine_future(transitions(Ready, Error))] 51 | Complex(&'a i32, &'d u32), 52 | 53 | #[state_machine_future(transitions(Ready, Error))] 54 | AssociatedType(D), 55 | 56 | /// Some generic ready state. 57 | #[state_machine_future(ready)] 58 | Ready(i32), 59 | 60 | /// Some generic error state. 61 | #[state_machine_future(error)] 62 | Error(E), 63 | } 64 | 65 | impl<'a, 'c, 'd: 'a, T, E, C, D> PollFsm<'a, 'c, 'd, T, E, C, D> for Fsm<'a, 'c, 'd, T, E, C, D> 66 | where 67 | T: ComplexTrait<'c, C> + 'static, 68 | E: Debug, 69 | C: 'c, 70 | 'c: 'd, 71 | D: AssociatedTypesTrait, 72 | { 73 | fn poll_start<'b>( 74 | _: &'b mut RentToOwn<'b, Start<'a, 'c, 'd, T, E, C, D>>, 75 | ) -> Poll, E> { 76 | unimplemented!() 77 | } 78 | 79 | fn poll_complex<'b>(_: &'b mut RentToOwn<'b, Complex<'a, 'd>>) -> Poll, E> { 80 | unimplemented!() 81 | } 82 | 83 | fn poll_associated_type<'b>( 84 | _: &'b mut RentToOwn<'b, AssociatedType>, 85 | ) -> Poll, E> { 86 | unimplemented!() 87 | } 88 | } 89 | 90 | impl<'a> ComplexTrait<'a, i32> for i32 { 91 | fn data(&self) -> &'a i32 { 92 | unimplemented!() 93 | } 94 | } 95 | 96 | impl AssociatedTypesTrait for String { 97 | type Type = io::Error; 98 | } 99 | 100 | #[test] 101 | fn check_generic_start() { 102 | let test = String::from("test"); 103 | 104 | let _: Box> = Box::new(Fsm::start( 105 | StartType { 106 | _data: 0, 107 | _phan: Default::default(), 108 | _phan2: Default::default(), 109 | _phan3: Default::default(), 110 | }, 111 | test, 112 | )); 113 | } 114 | -------------------------------------------------------------------------------- /tests/github_issue_25.rs: -------------------------------------------------------------------------------- 1 | //! Test case taken from 2 | //! . 3 | 4 | extern crate futures; 5 | 6 | #[macro_use] 7 | extern crate state_machine_future; 8 | 9 | use futures::{Future, Poll}; 10 | use state_machine_future::RentToOwn; 11 | 12 | #[derive(StateMachineFuture)] 13 | enum Foo 14 | where 15 | T: Future, 16 | { 17 | #[state_machine_future(start, transitions(Finished))] 18 | Start(T), 19 | #[state_machine_future(ready)] 20 | Finished(T::Item), 21 | #[state_machine_future(error)] 22 | Failed(T::Error), 23 | } 24 | 25 | impl PollFoo for Foo 26 | where 27 | T: Future, 28 | { 29 | fn poll_start<'a>(_state: &'a mut RentToOwn<'a, Start>) -> Poll, T::Error> { 30 | panic!() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/into_after.rs: -------------------------------------------------------------------------------- 1 | //! Test that the `AfterBlah` types implement `From` for all 2 | //! `Successor` typestates that come after the `Blah` typestate. 3 | 4 | extern crate futures; 5 | #[macro_use] 6 | extern crate state_machine_future; 7 | 8 | use futures::{Async, Poll}; 9 | use state_machine_future::RentToOwn; 10 | 11 | #[derive(StateMachineFuture)] 12 | pub enum Machine { 13 | /// Choose which next state to go into depending on what start value is 14 | /// given. 15 | #[state_machine_future(start)] 16 | #[state_machine_future(transitions(Ready, TransitionMacro))] 17 | Start, 18 | 19 | #[state_machine_future(transitions(Ready))] 20 | TransitionMacro, 21 | 22 | #[state_machine_future(ready)] 23 | Ready(usize), 24 | 25 | #[state_machine_future(error)] 26 | Error(usize), 27 | } 28 | 29 | impl PollMachine for Machine { 30 | fn poll_start<'a>(_: &'a mut RentToOwn<'a, Start>) -> Poll { 31 | Ok(Async::Ready(Ready(1).into())) 32 | } 33 | 34 | fn poll_transition_macro<'a>( 35 | _: &'a mut RentToOwn<'a, TransitionMacro>, 36 | ) -> Poll { 37 | transition!(Ready(2)) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/name_collisions.rs: -------------------------------------------------------------------------------- 1 | //! Test that the generated code is somewhat robust in the face of states with 2 | //! names of types its using. 3 | 4 | extern crate futures; 5 | #[macro_use] 6 | extern crate state_machine_future; 7 | 8 | #[allow(dead_code)] 9 | fn futures() {} 10 | #[allow(dead_code)] 11 | fn state_machine_future() {} 12 | 13 | #[allow(unused_macros)] 14 | macro_rules! futures { 15 | () => {}; 16 | } 17 | #[allow(unused_macros)] 18 | macro_rules! state_machine_future { 19 | () => {}; 20 | } 21 | 22 | #[derive(StateMachineFuture)] 23 | pub enum Fsm { 24 | #[state_machine_future(start)] 25 | #[state_machine_future(transitions(Future))] 26 | Async, 27 | 28 | #[state_machine_future(transitions(Poll))] 29 | Future, 30 | 31 | #[state_machine_future(transitions(RentToOwn))] 32 | Poll, 33 | 34 | #[state_machine_future(transitions(StateMachineFuture))] 35 | RentToOwn, 36 | 37 | #[state_machine_future(ready)] 38 | #[state_machine_future(error)] 39 | StateMachineFuture(()), 40 | } 41 | 42 | impl PollFsm for Fsm { 43 | fn poll_async<'a>( 44 | _: &'a mut state_machine_future::RentToOwn<'a, Async>, 45 | ) -> futures::Poll { 46 | unimplemented!() 47 | } 48 | 49 | fn poll_future<'a>( 50 | _: &'a mut state_machine_future::RentToOwn<'a, Future>, 51 | ) -> futures::Poll { 52 | unimplemented!() 53 | } 54 | 55 | fn poll_poll<'a>( 56 | _: &'a mut state_machine_future::RentToOwn<'a, Poll>, 57 | ) -> futures::Poll { 58 | unimplemented!() 59 | } 60 | 61 | fn poll_rent_to_own<'a>( 62 | _: &'a mut state_machine_future::RentToOwn<'a, RentToOwn>, 63 | ) -> futures::Poll { 64 | unimplemented!() 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/overlapping_states.rs: -------------------------------------------------------------------------------- 1 | //! Test that we handle overlapping start/ready/error states properly. 2 | 3 | extern crate futures; 4 | #[macro_use] 5 | extern crate state_machine_future; 6 | 7 | use futures::Poll; 8 | use state_machine_future::RentToOwn; 9 | 10 | #[derive(StateMachineFuture)] 11 | pub enum AllOverlapping { 12 | #[state_machine_future(start)] 13 | #[state_machine_future(ready)] 14 | #[state_machine_future(error)] 15 | OnlyState(()), 16 | } 17 | 18 | #[derive(StateMachineFuture)] 19 | pub enum NotOverlapping { 20 | #[state_machine_future(start)] 21 | #[state_machine_future(transitions(Ready, Error))] 22 | Start, 23 | #[state_machine_future(ready)] 24 | Ready(()), 25 | #[state_machine_future(error)] 26 | Error(()), 27 | } 28 | 29 | impl PollNotOverlapping for NotOverlapping { 30 | fn poll_start<'a>(_: &'a mut RentToOwn<'a, Start>) -> Poll { 31 | unimplemented!() 32 | } 33 | } 34 | 35 | #[derive(StateMachineFuture)] 36 | pub enum ReadyErrorOverlapping { 37 | #[state_machine_future(start)] 38 | #[state_machine_future(transitions(ReadyError))] 39 | Init, 40 | 41 | #[state_machine_future(ready)] 42 | #[state_machine_future(error)] 43 | ReadyError(()), 44 | } 45 | 46 | impl PollReadyErrorOverlapping for ReadyErrorOverlapping { 47 | fn poll_init<'a>(_: &'a mut RentToOwn<'a, Init>) -> Poll { 48 | unimplemented!() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/polling.rs: -------------------------------------------------------------------------------- 1 | //! Test that we get the expected poll results. 2 | 3 | extern crate futures; 4 | #[macro_use] 5 | extern crate state_machine_future; 6 | 7 | use futures::{Async, Future, Poll}; 8 | use state_machine_future::RentToOwn; 9 | 10 | #[derive(StateMachineFuture)] 11 | pub enum Machine { 12 | /// Choose which next state to go into depending on what start value is 13 | /// given. 14 | #[state_machine_future(start)] 15 | #[state_machine_future(transitions(NeverReady, AlwaysReady, Error))] 16 | Start(Result, usize>), 17 | 18 | /// This always returns NotReady. 19 | #[state_machine_future(transitions(Error))] 20 | NeverReady, 21 | 22 | /// This always returns Ready. 23 | #[state_machine_future(transitions(Ready))] 24 | AlwaysReady, 25 | 26 | #[state_machine_future(ready)] 27 | Ready(usize), 28 | 29 | #[state_machine_future(error)] 30 | Error(usize), 31 | } 32 | 33 | impl PollMachine for Machine { 34 | fn poll_start<'a>(start: &'a mut RentToOwn<'a, Start>) -> Poll { 35 | Ok(Async::Ready(*start.take().0?)) 36 | } 37 | 38 | fn poll_never_ready<'a>(_: &'a mut RentToOwn<'a, NeverReady>) -> Poll { 39 | Ok(Async::NotReady) 40 | } 41 | 42 | fn poll_always_ready<'a>( 43 | _: &'a mut RentToOwn<'a, AlwaysReady>, 44 | ) -> Poll { 45 | Ok(Async::Ready(AfterAlwaysReady::Ready(Ready(1)))) 46 | } 47 | } 48 | 49 | #[test] 50 | fn direct_error() { 51 | let mut machine = Machine::start(Err(42)); 52 | assert_eq!(machine.poll(), Err(42)); 53 | 54 | // And its fused: never ready when polling again after we finished. 55 | assert_eq!(machine.poll(), Ok(Async::NotReady)); 56 | assert_eq!(machine.poll(), Ok(Async::NotReady)); 57 | assert_eq!(machine.poll(), Ok(Async::NotReady)); 58 | } 59 | 60 | #[test] 61 | fn indirect_error() { 62 | let mut machine = Machine::start(Ok(Box::new(AfterStart::Error(Error(42))))); 63 | assert_eq!(machine.poll(), Err(42)); 64 | 65 | // And its fused: never ready when polling again after we finished. 66 | assert_eq!(machine.poll(), Ok(Async::NotReady)); 67 | assert_eq!(machine.poll(), Ok(Async::NotReady)); 68 | assert_eq!(machine.poll(), Ok(Async::NotReady)); 69 | } 70 | 71 | #[test] 72 | fn never_ready() { 73 | let mut machine = Machine::start(Ok(Box::new(AfterStart::NeverReady(NeverReady)))); 74 | assert_eq!(machine.poll(), Ok(Async::NotReady)); 75 | assert_eq!(machine.poll(), Ok(Async::NotReady)); 76 | assert_eq!(machine.poll(), Ok(Async::NotReady)); 77 | assert_eq!(machine.poll(), Ok(Async::NotReady)); 78 | assert_eq!(machine.poll(), Ok(Async::NotReady)); 79 | } 80 | 81 | #[test] 82 | fn always_ready() { 83 | let mut machine = Machine::start(Ok(Box::new(AfterStart::AlwaysReady(AlwaysReady)))); 84 | assert_eq!(machine.poll(), Ok(Async::Ready(1))); 85 | 86 | // And its fused: never ready when polling again after we finished. 87 | assert_eq!(machine.poll(), Ok(Async::NotReady)); 88 | assert_eq!(machine.poll(), Ok(Async::NotReady)); 89 | assert_eq!(machine.poll(), Ok(Async::NotReady)); 90 | } 91 | -------------------------------------------------------------------------------- /tests/private_type_in_description.rs: -------------------------------------------------------------------------------- 1 | //! Test that we don't leak private types in public API. 2 | 3 | extern crate futures; 4 | #[macro_use] 5 | extern crate state_machine_future; 6 | 7 | use futures::Poll; 8 | use state_machine_future::RentToOwn; 9 | 10 | struct PrivateType; 11 | 12 | // Should not get this error: 13 | // 14 | // error[E0446]: private type `PrivateType` in public interface 15 | // --> tests/private_type_in_description.rs:12:10 16 | // | 17 | // 12 | #[derive(StateMachineFuture)] 18 | // | ^^^^^^^^^^^^^^^^^^ can't leak private type 19 | #[derive(StateMachineFuture)] 20 | enum Machine { 21 | #[state_machine_future(start)] 22 | #[state_machine_future(transitions(Ready))] 23 | Start(PrivateType), 24 | 25 | #[state_machine_future(ready)] 26 | Ready(usize), 27 | 28 | #[state_machine_future(error)] 29 | Error(usize), 30 | } 31 | 32 | impl PollMachine for Machine { 33 | fn poll_start<'a>(_: &'a mut RentToOwn<'a, Start>) -> Poll { 34 | unimplemented!() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/ready_and_error.rs: -------------------------------------------------------------------------------- 1 | //! Test that the generated code uses the right ready and error types. 2 | 3 | extern crate futures; 4 | #[macro_use] 5 | extern crate state_machine_future; 6 | 7 | use futures::{Async, Poll}; 8 | use state_machine_future::RentToOwn; 9 | 10 | pub struct MyReady; 11 | pub struct MyError; 12 | 13 | #[derive(StateMachineFuture)] 14 | pub enum Fsm { 15 | #[state_machine_future(start)] 16 | #[state_machine_future(transitions(Ready))] 17 | Start, 18 | 19 | #[state_machine_future(ready)] 20 | Ready(MyReady), 21 | 22 | #[state_machine_future(error)] 23 | Error(MyError), 24 | } 25 | 26 | impl PollFsm for Fsm { 27 | fn poll_start<'a>(_: &'a mut RentToOwn<'a, Start>) -> Poll { 28 | Ok(Async::Ready(AfterStart::Ready(Ready(MyReady)))) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/start.rs: -------------------------------------------------------------------------------- 1 | //! Test that the appropriate start method is generated for the original enum. 2 | 3 | extern crate futures; 4 | #[macro_use] 5 | extern crate state_machine_future; 6 | 7 | use futures::Poll; 8 | use state_machine_future::{RentToOwn, StateMachineFuture}; 9 | 10 | #[derive(StateMachineFuture)] 11 | pub enum Fsm { 12 | #[state_machine_future(start)] 13 | #[state_machine_future(transitions(Done))] 14 | Begin(usize, bool), 15 | 16 | #[state_machine_future(ready)] 17 | #[state_machine_future(error)] 18 | Done(()), 19 | } 20 | 21 | impl PollFsm for Fsm { 22 | fn poll_begin<'a>(_: &'a mut RentToOwn<'a, Begin>) -> Poll { 23 | unimplemented!() 24 | } 25 | } 26 | 27 | #[test] 28 | fn fsm_has_appropriate_start_function() { 29 | fn check(_: F) 30 | where 31 | S: StateMachineFuture, 32 | F: Fn(usize, bool) -> S::Future, 33 | { 34 | } 35 | 36 | check::(Fsm::start); 37 | } 38 | 39 | #[derive(StateMachineFuture)] 40 | pub enum Fsm2 { 41 | #[state_machine_future(start)] 42 | #[state_machine_future(transitions(Done2))] 43 | Begin2 { x: bool, y: usize }, 44 | 45 | #[state_machine_future(ready)] 46 | #[state_machine_future(error)] 47 | Done2(()), 48 | } 49 | 50 | impl PollFsm2 for Fsm2 { 51 | fn poll_begin2<'a>(_: &'a mut RentToOwn<'a, Begin2>) -> Poll { 52 | unimplemented!() 53 | } 54 | } 55 | 56 | #[test] 57 | fn fsm2_has_appropriate_start_function() { 58 | fn check(_: F) 59 | where 60 | S: StateMachineFuture, 61 | F: Fn(bool, usize) -> S::Future, 62 | { 63 | } 64 | 65 | check::(Fsm2::start); 66 | } 67 | 68 | #[derive(StateMachineFuture)] 69 | pub enum Fsm3 { 70 | #[state_machine_future(start)] 71 | #[state_machine_future(transitions(Done3))] 72 | Begin3, 73 | 74 | #[state_machine_future(ready)] 75 | #[state_machine_future(error)] 76 | Done3(()), 77 | } 78 | 79 | impl PollFsm3 for Fsm3 { 80 | fn poll_begin3<'a>(_: &'a mut RentToOwn<'a, Begin3>) -> Poll { 81 | unimplemented!() 82 | } 83 | } 84 | 85 | #[test] 86 | fn fsm3_has_appropriate_start_function() { 87 | fn check(_: F) 88 | where 89 | S: StateMachineFuture, 90 | F: Fn() -> S::Future, 91 | { 92 | } 93 | 94 | check::(Fsm3::start); 95 | } 96 | -------------------------------------------------------------------------------- /tests/start_in.rs: -------------------------------------------------------------------------------- 1 | //! Test that we can start the state machine in any state. 2 | 3 | extern crate futures; 4 | #[macro_use] 5 | extern crate state_machine_future; 6 | 7 | use futures::Poll; 8 | use state_machine_future::RentToOwn; 9 | 10 | #[derive(StateMachineFuture)] 11 | pub enum Fsm { 12 | #[state_machine_future(start)] 13 | #[state_machine_future(transitions(Middle1, Middle2, End))] 14 | Begin, 15 | 16 | #[state_machine_future(transitions(End))] 17 | Middle1 { number: u32 }, 18 | 19 | #[state_machine_future(transitions(End))] 20 | Middle2 { string: String }, 21 | 22 | #[state_machine_future(ready)] 23 | #[state_machine_future(error)] 24 | End(()), 25 | } 26 | 27 | #[allow(unused_must_use)] 28 | #[test] 29 | fn can_start_in_all_states() { 30 | Fsm::start_in(Begin); 31 | Fsm::start_in(Middle1 { number: 10 }); 32 | Fsm::start_in(Middle2 { 33 | string: String::from("Hello!"), 34 | }); 35 | Fsm::start_in(End(())); 36 | } 37 | 38 | impl PollFsm for Fsm { 39 | fn poll_begin<'a>(_: &'a mut RentToOwn<'a, Begin>) -> Poll { 40 | unimplemented!() 41 | } 42 | 43 | fn poll_middle1<'a>(_: &'a mut RentToOwn<'a, Middle1>) -> Poll { 44 | unimplemented!() 45 | } 46 | 47 | fn poll_middle2<'a>(_: &'a mut RentToOwn<'a, Middle2>) -> Poll { 48 | unimplemented!() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/state_conversions.rs: -------------------------------------------------------------------------------- 1 | //! Test that the generated states can be converted to the state enum. 2 | 3 | extern crate futures; 4 | #[macro_use] 5 | extern crate state_machine_future; 6 | 7 | use futures::Poll; 8 | use state_machine_future::RentToOwn; 9 | 10 | #[derive(StateMachineFuture)] 11 | pub enum Fsm { 12 | #[state_machine_future(start)] 13 | #[state_machine_future(transitions(Middle1, Middle2, End))] 14 | Begin, 15 | 16 | #[state_machine_future(transitions(End))] 17 | Middle1, 18 | 19 | #[state_machine_future(transitions(End))] 20 | Middle2, 21 | 22 | #[state_machine_future(ready)] 23 | #[state_machine_future(error)] 24 | End(()), 25 | } 26 | 27 | impl PollFsm for Fsm { 28 | fn poll_begin<'a>(_: &'a mut RentToOwn<'a, Begin>) -> Poll { 29 | unimplemented!() 30 | } 31 | 32 | fn poll_middle1<'a>(_: &'a mut RentToOwn<'a, Middle1>) -> Poll { 33 | unimplemented!() 34 | } 35 | 36 | fn poll_middle2<'a>(_: &'a mut RentToOwn<'a, Middle2>) -> Poll { 37 | unimplemented!() 38 | } 39 | } 40 | 41 | fn convert>(_state: S) {} 42 | 43 | #[test] 44 | fn convert_states() { 45 | convert(Begin); 46 | convert(Middle1); 47 | convert(Middle2); 48 | convert(End(())); 49 | } 50 | -------------------------------------------------------------------------------- /tests/take_context_on_error.rs: -------------------------------------------------------------------------------- 1 | //! Test that we can take context when transitioning to error. 2 | 3 | extern crate futures; 4 | #[macro_use] 5 | extern crate state_machine_future; 6 | 7 | use futures::Future; 8 | use futures::Poll; 9 | use state_machine_future::RentToOwn; 10 | 11 | pub struct Context {} 12 | 13 | #[derive(StateMachineFuture)] 14 | #[state_machine_future(context = "Context")] 15 | pub enum WithContext { 16 | #[state_machine_future(start, transitions(Ready))] 17 | Start, 18 | 19 | #[state_machine_future(ready)] 20 | Ready(()), 21 | 22 | #[state_machine_future(error)] 23 | Error(()), 24 | } 25 | 26 | impl PollWithContext for WithContext { 27 | fn poll_start<'s, 'c>( 28 | _: &'s mut RentToOwn<'s, Start>, 29 | context: &'c mut RentToOwn<'c, Context>, 30 | ) -> Poll { 31 | context.take(); 32 | 33 | Err(()) 34 | } 35 | } 36 | 37 | #[test] 38 | fn given_sm_with_context_can_take_context_on_error() { 39 | let context = Context {}; 40 | 41 | let mut machine = WithContext::start(context); 42 | 43 | assert_eq!(machine.poll(), Err(())); 44 | } 45 | -------------------------------------------------------------------------------- /tests/take_context_on_ready.rs: -------------------------------------------------------------------------------- 1 | //! Test that we can take context when transitioning to ready. 2 | 3 | extern crate futures; 4 | #[macro_use] 5 | extern crate state_machine_future; 6 | 7 | use futures::Async; 8 | use futures::Future; 9 | use futures::Poll; 10 | use state_machine_future::RentToOwn; 11 | 12 | pub struct Context {} 13 | 14 | #[derive(StateMachineFuture)] 15 | #[state_machine_future(context = "Context")] 16 | pub enum WithContext { 17 | #[state_machine_future(start, transitions(Ready))] 18 | Start, 19 | 20 | #[state_machine_future(ready)] 21 | Ready(()), 22 | 23 | #[state_machine_future(error)] 24 | Error(()), 25 | } 26 | 27 | impl PollWithContext for WithContext { 28 | fn poll_start<'s, 'c>( 29 | _: &'s mut RentToOwn<'s, Start>, 30 | context: &'c mut RentToOwn<'c, Context>, 31 | ) -> Poll { 32 | context.take(); 33 | 34 | transition!(Ready(())) 35 | } 36 | } 37 | 38 | #[test] 39 | fn given_sm_with_context_can_take_context_on_ready() { 40 | let context = Context {}; 41 | 42 | let mut machine = WithContext::start(context); 43 | 44 | assert_eq!(machine.poll(), Ok(Async::Ready(()))); 45 | } 46 | -------------------------------------------------------------------------------- /tests/take_context_value_on_ready.rs: -------------------------------------------------------------------------------- 1 | //! Test that we can take a value from context when transitioning to ready. 2 | 3 | extern crate futures; 4 | #[macro_use] 5 | extern crate state_machine_future; 6 | 7 | use futures::Async; 8 | use futures::Future; 9 | use futures::Poll; 10 | use state_machine_future::RentToOwn; 11 | 12 | pub struct Context { 13 | value: String, 14 | } 15 | 16 | #[derive(StateMachineFuture)] 17 | #[state_machine_future(context = "Context")] 18 | pub enum WithContext { 19 | #[state_machine_future(start, transitions(Ready))] 20 | Start, 21 | 22 | #[state_machine_future(ready)] 23 | Ready(String), 24 | 25 | #[state_machine_future(error)] 26 | Error(()), 27 | } 28 | 29 | impl PollWithContext for WithContext { 30 | fn poll_start<'s, 'c>( 31 | _: &'s mut RentToOwn<'s, Start>, 32 | context: &'c mut RentToOwn<'c, Context>, 33 | ) -> Poll { 34 | let context = context.take(); 35 | 36 | let value = context.value; 37 | 38 | transition!(Ready(value)) 39 | } 40 | } 41 | 42 | #[test] 43 | fn given_sm_with_context_can_take_context_value_on_ready() { 44 | let context = Context { 45 | value: String::from("foo"), 46 | }; 47 | 48 | let mut machine = WithContext::start(context); 49 | 50 | assert_eq!(machine.poll(), Ok(Async::Ready(String::from("foo")))); 51 | } 52 | -------------------------------------------------------------------------------- /tests/take_in_poll_without_state_change.rs: -------------------------------------------------------------------------------- 1 | //! Test that if a state transition function takes the `RentToOwn` without 2 | //! changing states, then we return `Ok(NotReady)` for perpetuity. 3 | 4 | extern crate futures; 5 | #[macro_use] 6 | extern crate state_machine_future; 7 | 8 | use futures::{Async, Future, Poll}; 9 | use state_machine_future::RentToOwn; 10 | 11 | #[derive(StateMachineFuture)] 12 | pub enum Machine { 13 | #[state_machine_future(start)] 14 | #[state_machine_future(transitions(Ready))] 15 | Start, 16 | 17 | #[state_machine_future(ready, error)] 18 | Ready(usize), 19 | } 20 | 21 | impl PollMachine for Machine { 22 | fn poll_start<'a>(start: &'a mut RentToOwn<'a, Start>) -> Poll { 23 | // Take the state. 24 | let _ = start.take(); 25 | 26 | // But don't transition to a new state. 27 | Ok(Async::NotReady) 28 | } 29 | } 30 | 31 | #[test] 32 | fn taken_without_state_transition_is_never_ready() { 33 | let mut machine = Machine::start(); 34 | 35 | assert_eq!(machine.poll(), Ok(Async::NotReady)); 36 | assert_eq!(machine.poll(), Ok(Async::NotReady)); 37 | assert_eq!(machine.poll(), Ok(Async::NotReady)); 38 | } 39 | -------------------------------------------------------------------------------- /tests/transitions.rs: -------------------------------------------------------------------------------- 1 | //! Test that the generated code has the right transition types. 2 | 3 | extern crate futures; 4 | #[macro_use] 5 | extern crate state_machine_future; 6 | 7 | use futures::Poll; 8 | use state_machine_future::RentToOwn; 9 | 10 | pub struct MyReady; 11 | pub struct MyError; 12 | 13 | #[derive(StateMachineFuture)] 14 | pub enum Fsm { 15 | #[state_machine_future(start)] 16 | #[state_machine_future(transitions(Middle, End))] 17 | Begin, 18 | 19 | #[state_machine_future(transitions(End))] 20 | Middle(()), 21 | 22 | #[state_machine_future(ready)] 23 | #[state_machine_future(error)] 24 | End(()), 25 | } 26 | 27 | pub fn check_begin_transitions(x: AfterBegin) { 28 | match x { 29 | AfterBegin::Middle(Middle(())) | AfterBegin::End(End(())) => unimplemented!(), 30 | } 31 | } 32 | 33 | pub fn check_middle_transitions(x: AfterMiddle) { 34 | match x { 35 | AfterMiddle::End(End(())) => unimplemented!(), 36 | } 37 | } 38 | 39 | impl PollFsm for Fsm { 40 | fn poll_begin<'a>(_: &'a mut RentToOwn<'a, Begin>) -> Poll { 41 | unimplemented!() 42 | } 43 | 44 | fn poll_middle<'a>(_: &'a mut RentToOwn<'a, Middle>) -> Poll { 45 | unimplemented!() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/visibility.rs: -------------------------------------------------------------------------------- 1 | //! Test that we handle `pub`, `pub(self)`, `pub(super)`, `pub(crate)`, 2 | //! `pub(in some::module)`, and non-`pub` state machines. 3 | 4 | #![allow(dead_code)] 5 | 6 | extern crate futures; 7 | #[macro_use] 8 | extern crate state_machine_future; 9 | 10 | #[derive(StateMachineFuture)] 11 | pub enum Pub { 12 | #[state_machine_future(start)] 13 | #[state_machine_future(ready)] 14 | #[state_machine_future(error)] 15 | PubState(()), 16 | } 17 | 18 | #[derive(StateMachineFuture)] 19 | pub(self) enum PubSelf { 20 | #[state_machine_future(start)] 21 | #[state_machine_future(ready)] 22 | #[state_machine_future(error)] 23 | PubSelfState(()), 24 | } 25 | 26 | mod namespace { 27 | #[derive(StateMachineFuture)] 28 | pub(super) enum PubSuper { 29 | #[state_machine_future(start)] 30 | #[state_machine_future(ready)] 31 | #[state_machine_future(error)] 32 | PubSuperState(()), 33 | } 34 | } 35 | 36 | #[derive(StateMachineFuture)] 37 | pub(crate) enum PubCrate { 38 | #[state_machine_future(start)] 39 | #[state_machine_future(ready)] 40 | #[state_machine_future(error)] 41 | PubCrateState(()), 42 | } 43 | 44 | mod some { 45 | mod module { 46 | mod inner { 47 | #[derive(StateMachineFuture)] 48 | pub(in some::module) enum PubInSomeModule { 49 | #[state_machine_future(start)] 50 | #[state_machine_future(ready)] 51 | #[state_machine_future(error)] 52 | PubInSomeModuleState(()), 53 | } 54 | } 55 | } 56 | } 57 | 58 | #[derive(StateMachineFuture)] 59 | enum NonPub { 60 | #[state_machine_future(start)] 61 | #[state_machine_future(ready)] 62 | #[state_machine_future(error)] 63 | NonPubState(()), 64 | } 65 | 66 | pub mod exported { 67 | #[derive(StateMachineFuture)] 68 | pub enum ExportedSM { 69 | #[state_machine_future(start)] 70 | #[state_machine_future(ready)] 71 | #[state_machine_future(error)] 72 | Exported(()), 73 | } 74 | } 75 | 76 | pub fn use_generated_states_enum_outside_of_module(_: exported::ExportedSMStates) { 77 | unimplemented!() 78 | } 79 | --------------------------------------------------------------------------------