1#![warn(missing_docs)]
2extern crate proc_macro2;
8
9use std::collections::HashMap;
10
11use darling::{FromDeriveInput, FromVariant, ast};
12use proc_macro::TokenStream;
13use proc_macro2::Span;
14use quote::quote;
15
16use crate::scope::Scope;
17
18#[cfg(feature = "hierarchy")]
19mod hierarchy;
20
21mod scope;
22
23#[derive(FromDeriveInput)]
25#[darling(attributes(scope), supports(enum_unit))]
26struct ScopeOpts {
27 ident: syn::Ident,
28
29 #[darling(default = || ".".to_string())]
30 separator: String,
31
32 #[darling(default)]
33 prefix: String,
34
35 #[cfg(feature = "hierarchy")]
36 #[darling(default = || true)]
37 hierarchy: bool,
38
39 data: ast::Data<ScopeVariantOpts, ()>,
40}
41
42#[derive(Debug, FromVariant)]
44#[darling(attributes(scope))]
45struct ScopeVariantOpts {
46 ident: syn::Ident,
47
48 rename: Option<String>,
49}
50
51#[proc_macro_derive(Scope, attributes(scope))]
63pub fn derive_into_scope(item: TokenStream) -> TokenStream {
64
65 let input = syn::parse_macro_input!(item as syn::DeriveInput);
66
67 let opts = match ScopeOpts::from_derive_input(&input) {
68 Ok(opts) => opts,
69 Err(err) => {
70 return err.write_errors().into()
71 },
72 };
73
74 derive_into_scope_impl(&opts)
75}
76
77fn derive_into_scope_impl(opts: &ScopeOpts) -> TokenStream {
78
79 let enum_ident = &opts.ident;
80
81 let variants = match &opts.data {
83 ast::Data::Enum(items) => items,
84 ast::Data::Struct(_) => {
85 return syn::Error::new(Span::call_site(), "The Scope derive macro only accepts enums").into_compile_error().into();
86 },
87 };
88
89 let mut scopes: HashMap<String, Scope> = HashMap::with_capacity(variants.len());
91 let mut error: Option<syn::Error> = None;
92
93 for variant in variants {
94
95 let scope = Scope::from_variant(variant, &opts);
96 let scope_full_name = scope.full_name();
97
98 if let Some(other_scope) = scopes.get(&scope_full_name) {
100 let mut err = syn::Error::new(
101 variant.ident.span(),
102 format!("Conflicting scope name '{}' (conflicting with variant {}::{})", scope.name(), enum_ident, &other_scope.ident)
103 );
104
105 err.combine(syn::Error::new(
106 other_scope.ident.span(),
107 format!("Conflicting scope name '{}' (conflicting with variant {}::{})", other_scope.name(), enum_ident, variant.ident)
108 ));
109
110 if let Some(error) = error.as_mut() {
111 error.combine(err);
112 } else {
113 error = Some(err)
114 }
115
116 } else {
117 scopes.insert(scope_full_name, scope);
118 }
119
120 }
121
122 if let Some(err) = error {
123 return err.into_compile_error().into();
124 }
125
126 let (scopes_full_names, scopes_ident): (Vec<_>, Vec<_>) = scopes
129 .iter()
130 .map(|(k, v)| (k, &v.ident))
131 .unzip();
132
133 let fromstr_impl = quote! {
134 impl ::std::str::FromStr for #enum_ident {
135 type Err = ::scopes_rs::error::ScopeParseError;
136
137 fn from_str(s: &str) -> ::std::result::Result<Self, Self::Err> {
138 match s {
139 #(#scopes_full_names => Ok(#enum_ident::#scopes_ident),)*
140 _ => Err(::scopes_rs::error::ScopeParseError(s.to_string())),
141 }
142 }
143 }
144 };
145
146 let scope_impl = quote! {
147 impl ::scopes_rs::scope::Scope for #enum_ident {}
148 };
149
150 let scope_impl = quote! {
151 #fromstr_impl
152 #scope_impl
153 };
154
155 #[cfg(feature = "hierarchy")]
156 let mut scope_impl = scope_impl;
157
158 #[cfg(feature = "hierarchy")]
160 if opts.hierarchy {
161 use quote::TokenStreamExt;
162
163 use crate::hierarchy::implement_hierarchized;
164
165 scope_impl.append_all(implement_hierarchized(enum_ident, &scopes));
166 }
167
168 scope_impl.into()
169}