orchestra_proc_macro/subsystem.rs
1// Copyright (C) 2022 Parity Technologies (UK) Ltd.
2// SPDX-License-Identifier: Apache-2.0
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16//! Generates the bounds for a particular subsystem `Context` and associate `type Sender`.
17//!
18//!
19//! ## Implement `trait Subsystem<Context, Error>` via `subsystem`
20//!
21//! ```ignore
22//! # use orchestra_proc_macro::subsystem;
23//! # mod somewhere {
24//! # use orchestra_proc_macro::orchestra;
25//! # pub use orchestra::*;
26//! #
27//! # #[derive(Debug, thiserror::Error)]
28//! # #[error("Yikes!")]
29//! # pub struct Yikes;
30//! # impl From<OrchestraError> for Yikes {
31//! # fn from(_: OrchestraError) -> Yikes { Yikes }
32//! # }
33//! # impl From<mpsc::SendError> for Yikes {
34//! # fn from(_: mpsc::SendError) -> Yikes { Yikes }
35//! # }
36//! #
37//! # #[derive(Debug)]
38//! # pub struct Eve;
39//! #
40//! # #[derive(Debug, Clone)]
41//! # pub struct Sig;
42//! #
43//! # #[derive(Debug, Clone, Copy)]
44//! # pub struct A;
45//! # #[derive(Debug, Clone, Copy)]
46//! # pub struct B;
47//! #
48//! # #[orchestra(signal=Sig, gen=AllOfThem, event=Eve, error=Yikes)]
49//! # pub struct Wonderland {
50//! # #[subsystem(A, sends: [B])]
51//! # foo: Foo,
52//! # #[subsystem(B, sends: [A])]
53//! # bar: Bar,
54//! # }
55//! # }
56//! # use somewhere::{Yikes, SpawnedSubsystem};
57//! #
58//! # struct FooSubsystem;
59//! #
60//! #[subsystem(Foo, error = Yikes, prefix = somewhere)]
61//! impl<Context> FooSubsystem {
62//! fn start(self, context: Context) -> SpawnedSubsystem<Yikes> {
63//! // ..
64//! # let _ = context;
65//! # unimplemented!()
66//! }
67//! }
68//! ```
69//!
70//! expands to
71//!
72//! ```ignore
73//! # use orchestra_proc_macro::subsystem;
74//! # mod somewhere {
75//! # use orchestra_proc_macro::orchestra;
76//! # pub use orchestra::*;
77//! #
78//! # #[derive(Debug, thiserror::Error)]
79//! # #[error("Yikes!")]
80//! # pub struct Yikes;
81//! # impl From<OrchestraError> for Yikes {
82//! # fn from(_: OrchestraError) -> Yikes { Yikes }
83//! # }
84//! # impl From<mpsc::SendError> for Yikes {
85//! # fn from(_: mpsc::SendError) -> Yikes { Yikes }
86//! # }
87//! #
88//! # #[derive(Debug)]
89//! # pub struct Eve;
90//! #
91//! # #[derive(Debug, Clone)]
92//! # pub struct Sig;
93//! #
94//! # #[derive(Debug, Clone, Copy)]
95//! # pub struct A;
96//! # #[derive(Debug, Clone, Copy)]
97//! # pub struct B;
98//! #
99//! # #[orchestra(signal=Sig, gen=AllOfThem, event=Eve, error=Yikes)]
100//! # pub struct Wonderland {
101//! # #[subsystem(A, sends: [B])]
102//! # foo: Foo,
103//! # #[subsystem(B, sends: [A])]
104//! # bar: Bar,
105//! # }
106//! # }
107//! # use somewhere::{Yikes, SpawnedSubsystem};
108//! # use orchestra as support_crate;
109//! #
110//! # struct FooSubsystem;
111//! #
112//! impl<Context> support_crate::Subsystem<Context, Yikes> for FooSubsystem
113//! where
114//! Context: somewhere::FooContextTrait,
115//! Context: support_crate::SubsystemContext,
116//! <Context as somewhere::FooContextTrait>::Sender: somewhere::FooSenderTrait,
117//! <Context as support_crate::SubsystemContext>::Sender: somewhere::FooSenderTrait,
118//! {
119//! fn start(self, context: Context) -> SpawnedSubsystem<Yikes> {
120//! // ..
121//! # let _ = context;
122//! # unimplemented!()
123//! }
124//! }
125//! ```
126//!
127//! where `support_crate` is either equivalent to `somewhere` or derived from the cargo manifest.
128//!
129//!
130//! ## Add additional trait bounds for a generic `Context` via `contextbounds`
131//!
132//! ### To an `ImplItem`
133//!
134//! ```ignore
135//! #[contextbounds(Foo, prefix = somewhere)]
136//! impl<Context> X {
137//! ..
138//! }
139//! ```
140//!
141//! expands to
142//!
143//! ```ignore
144//! impl<Context> X
145//! where
146//! Context: somewhere::FooSubsystemTrait,
147//! Context: support_crate::SubsystemContext,
148//! <Context as somewhere::FooContextTrait>::Sender: somewhere::FooSenderTrait,
149//! <Context as support_crate::SubsystemContext>::Sender: somewhere::FooSenderTrait,
150//! {
151//! }
152//! ```
153//!
154//! ### To a free standing `Fn` (not a method, that's covered by the above)
155//!
156//! ```ignore
157//! #[contextbounds(Foo, prefix = somewhere)]
158//! fn do_smth<Context>(context: &mut Context) {
159//! ..
160//! }
161//! ```
162//!
163//! expands to
164//!
165//! ```ignore
166//! fn do_smth<Context>(context: &mut Context)
167//! where
168//! Context: somewhere::FooSubsystemTrait,
169//! Context: support_crate::SubsystemContext,
170//! <Context as somewhere::FooContextTrait>::Sender: somewhere::FooSenderTrait,
171//! <Context as support_crate::SubsystemContext>::Sender: somewhere::FooSenderTrait,
172//! {
173//! }
174//! ```
175use proc_macro2::TokenStream;
176use quote::{format_ident, ToTokens};
177use syn::{parse2, parse_quote, punctuated::Punctuated, Result};
178
179use super::{parse::*, *};
180
181#[derive(Debug, Clone, Copy, PartialEq, Eq)]
182pub(crate) enum MakeSubsystem {
183 /// Implements `trait Subsystem` and apply the trait bounds to the `Context` generic.
184 ///
185 /// Relevant to `impl Item` only.
186 ImplSubsystemTrait,
187 /// Only apply the trait bounds to the context.
188 AddContextTraitBounds,
189}
190
191pub(crate) fn impl_subsystem_context_trait_bounds(
192 attr: TokenStream,
193 orig: TokenStream,
194 make_subsystem: MakeSubsystem,
195) -> Result<proc_macro2::TokenStream> {
196 let args = parse2::<SubsystemAttrArgs>(attr.clone())?;
197 let span = args.span();
198 let SubsystemAttrArgs { error_path, subsystem_ident, trait_prefix_path, .. } = args;
199
200 let mut item = parse2::<syn::Item>(orig)?;
201
202 // always prefer the direct usage, if it's not there, let's see if there is
203 // a `prefix=*` provided. Either is ok.
204
205 // Technically this is two different things:
206 // The place where the `#[orchestra]` is annotated is where all `trait *SenderTrait` and
207 // `trait *ContextTrait` types exist.
208 // The other usage is the true support crate `orchestra`, where the static ones
209 // are declared.
210 // Right now, if the `support_crate` is not included, it falls back silently to the `trait_prefix_path`.
211 let support_crate = support_crate()
212 .or_else(|_e| {
213 trait_prefix_path.clone().ok_or_else(|| {
214 syn::Error::new(attr.span(), "Couldn't find `orchestra` in manifest, but also missing a `prefix=` to help trait bound resolution")
215 })
216 })?;
217
218 let trait_prefix_path = trait_prefix_path.unwrap_or_else(|| parse_quote! { self });
219 if trait_prefix_path.segments.trailing_punct() {
220 return Err(syn::Error::new(trait_prefix_path.span(), "Must not end with `::`"))
221 }
222
223 let subsystem_ctx_trait = format_ident!("{}ContextTrait", subsystem_ident);
224 let subsystem_sender_trait = format_ident!("{}SenderTrait", subsystem_ident);
225
226 let extra_where_predicates: Punctuated<syn::WherePredicate, syn::Token![,]> = parse_quote! {
227 Context: #trait_prefix_path::#subsystem_ctx_trait,
228 Context: #support_crate::SubsystemContext,
229 <Context as #trait_prefix_path::#subsystem_ctx_trait>::Sender: #trait_prefix_path::#subsystem_sender_trait,
230 <Context as #support_crate::SubsystemContext>::Sender: #trait_prefix_path::#subsystem_sender_trait,
231 };
232
233 let apply_ctx_bound_if_present = move |generics: &mut syn::Generics| -> bool {
234 if generics
235 .params
236 .iter()
237 .find(|generic| match generic {
238 syn::GenericParam::Type(ty) if ty.ident == "Context" => true,
239 _ => false,
240 })
241 .is_some()
242 {
243 let where_clause = generics.make_where_clause();
244 where_clause.predicates.extend(extra_where_predicates.clone());
245 true
246 } else {
247 false
248 }
249 };
250
251 match item {
252 syn::Item::Impl(ref mut struktured_impl) => {
253 if make_subsystem == MakeSubsystem::ImplSubsystemTrait {
254 let error_path = error_path.ok_or_else(|| {
255 syn::Error::new(
256 span,
257 "Must annotate the identical orchestra error type via `error=..`.",
258 )
259 })?;
260 // Only replace the subsystem trait if it's desired.
261 struktured_impl.trait_.replace((
262 None,
263 parse_quote! {
264 #support_crate::Subsystem<Context, #error_path>
265 },
266 syn::token::For::default(),
267 ));
268 }
269
270 apply_ctx_bound_if_present(&mut struktured_impl.generics);
271 for item in struktured_impl.items.iter_mut() {
272 match item {
273 syn::ImplItem::Method(method) => {
274 apply_ctx_bound_if_present(&mut method.sig.generics);
275 },
276 _others => {
277 // don't error, just nop
278 },
279 }
280 }
281 },
282 syn::Item::Fn(ref mut struktured_fn) => {
283 if make_subsystem == MakeSubsystem::ImplSubsystemTrait {
284 return Err(syn::Error::new(struktured_fn.span(), "Cannot make a free function a subsystem, did you mean to apply `contextbound` instead?"));
285 }
286 apply_ctx_bound_if_present(&mut struktured_fn.sig.generics);
287 },
288 other =>
289 return Err(syn::Error::new(
290 other.span(),
291 "Macro can only be annotated on functions or struct implementations",
292 )),
293 };
294
295 Ok(item.to_token_stream())
296}
297
298#[cfg(test)]
299mod tests {
300 use super::*;
301
302 #[test]
303 fn is_path() {
304 let _p: Path = parse_quote! { self };
305 let _p: Path = parse_quote! { crate };
306 let _p: Path = parse_quote! { ::foo };
307 let _p: Path = parse_quote! { bar };
308 }
309}