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}