orchestra_proc_macro/parse/
parse_subsystem_attr.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
16use super::kw;
17use proc_macro2::Span;
18use quote::{quote, ToTokens};
19use std::collections::{hash_map::RandomState, HashMap};
20use syn::{
21	parse::{Parse, ParseBuffer},
22	punctuated::Punctuated,
23	spanned::Spanned,
24	Error, Ident, Path, Result, Token,
25};
26
27#[derive(Clone, Debug)]
28enum SubsystemAttrItem {
29	/// Error type provided by the user.
30	Error { tag: kw::error, eq_token: Token![=], value: Path },
31	/// For which slot in the orchestra this should be plugged.
32	///
33	/// The subsystem implementation can and should have a different name
34	/// from the declared parameter type in the orchestra.
35	Subsystem { tag: Option<kw::subsystem>, eq_token: Option<Token![=]>, value: Ident },
36	/// The prefix to apply when a subsystem is implemented in a different file/crate
37	/// than the orchestra itself.
38	///
39	/// Important for `#[subsystem(..)]` to reference the traits correctly.
40	TraitPrefix { tag: kw::prefix, eq_token: Token![=], value: Path },
41}
42
43impl ToTokens for SubsystemAttrItem {
44	fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
45		let ts = match self {
46			Self::TraitPrefix { tag, eq_token, value } => {
47				quote! { #tag #eq_token, #value }
48			},
49			Self::Error { tag, eq_token, value } => {
50				quote! { #tag #eq_token, #value }
51			},
52			Self::Subsystem { tag, eq_token, value } => {
53				quote! { #tag #eq_token, #value }
54			},
55		};
56		tokens.extend(ts.into_iter());
57	}
58}
59
60impl Parse for SubsystemAttrItem {
61	fn parse(input: &ParseBuffer) -> Result<Self> {
62		let lookahead = input.lookahead1();
63		if lookahead.peek(kw::error) {
64			Ok(SubsystemAttrItem::Error {
65				tag: input.parse::<kw::error>()?,
66				eq_token: input.parse()?,
67				value: input.parse()?,
68			})
69		} else if lookahead.peek(kw::prefix) {
70			Ok(SubsystemAttrItem::TraitPrefix {
71				tag: input.parse::<kw::prefix>()?,
72				eq_token: input.parse()?,
73				value: input.parse()?,
74			})
75		} else if lookahead.peek(kw::subsystem) {
76			Ok(SubsystemAttrItem::Subsystem {
77				tag: Some(input.parse::<kw::subsystem>()?),
78				eq_token: Some(input.parse()?),
79				value: input.parse()?,
80			})
81		} else {
82			Ok(SubsystemAttrItem::Subsystem { tag: None, eq_token: None, value: input.parse()? })
83		}
84	}
85}
86
87/// Attribute arguments `$args` in `#[subsystem( $args )]`.
88#[derive(Clone, Debug)]
89pub(crate) struct SubsystemAttrArgs {
90	span: Span,
91	pub(crate) error_path: Option<Path>,
92	pub(crate) subsystem_ident: Ident,
93	pub(crate) trait_prefix_path: Option<Path>,
94}
95
96impl Spanned for SubsystemAttrArgs {
97	fn span(&self) -> Span {
98		self.span.clone()
99	}
100}
101
102macro_rules! extract_variant {
103	($unique:expr, $variant:ident ; default = $fallback:expr) => {
104		extract_variant!($unique, $variant).unwrap_or_else(|| $fallback)
105	};
106	($unique:expr, $variant:ident ; err = $err:expr) => {
107		extract_variant!($unique, $variant).ok_or_else(|| Error::new(Span::call_site(), $err))
108	};
109	($unique:expr, $variant:ident) => {
110		$unique.values().find_map(|item| match item {
111			SubsystemAttrItem::$variant { value, .. } => Some(value.clone()),
112			_ => None,
113		})
114	};
115}
116
117impl Parse for SubsystemAttrArgs {
118	fn parse(input: &ParseBuffer) -> Result<Self> {
119		let span = input.span();
120		let items: Punctuated<SubsystemAttrItem, Token![,]> =
121			input.parse_terminated(SubsystemAttrItem::parse)?;
122
123		let mut unique = HashMap::<
124			std::mem::Discriminant<SubsystemAttrItem>,
125			SubsystemAttrItem,
126			RandomState,
127		>::default();
128		for item in items {
129			if let Some(first) = unique.insert(std::mem::discriminant(&item), item.clone()) {
130				let mut e = Error::new(
131					item.span(),
132					format!("Duplicate definition of subsystem generation type found"),
133				);
134				e.combine(Error::new(first.span(), "previously defined here."));
135				return Err(e)
136			}
137		}
138		let error_path = extract_variant!(unique, Error);
139		let subsystem_ident = extract_variant!(unique, Subsystem; err = "Must annotate the identical orchestra error type via `subsystem=..` or plainly as `Subsystem` as specified in the orchestra declaration.")?;
140		let trait_prefix_path = extract_variant!(unique, TraitPrefix);
141		Ok(SubsystemAttrArgs { span, error_path, subsystem_ident, trait_prefix_path })
142	}
143}