1#![doc = include_str!("../README.md")]
2
3use proc_macro::TokenStream;
4use proc_macro2::Span as Span2;
5use proc_macro2::TokenStream as TokenStream2;
6use quote::quote;
7use quote::quote_spanned;
8use syn::parse_macro_input;
9
10#[proc_macro_attribute]
12pub fn layers(_attr: TokenStream, input: TokenStream) -> TokenStream {
13 let input = parse_macro_input!(input as syn::ItemMod);
14 match layered_crate_expand(input) {
15 Ok(expanded) => expanded,
16 Err(err) => err.to_compile_error().into(),
17 }
18}
19
20fn layered_crate_expand(input: syn::ItemMod) -> syn::Result<TokenStream> {
21 let (_, content) = match input.content {
22 None => {
23 return Ok(quote! { #input }.into());
25 }
26 Some(content) => content,
27 };
28
29 let mut before_tokens = TokenStream2::new();
30 let mut has_doc_hidden = false;
31
32 for attr in input.attrs {
35 if attr.path().is_ident("doc") {
36 if let Ok(x) = attr
37 .meta
38 .require_list()
39 .and_then(|m| m.parse_args::<syn::Ident>())
40 {
41 if x == "hidden" {
42 has_doc_hidden = true;
43 }
44 }
45 }
46 before_tokens.extend(quote! { #attr });
47 }
48 if !has_doc_hidden {
49 before_tokens.extend(quote! { #[doc(hidden)] });
50 }
51
52 let mut graph = graph::DepsGraph::default();
54 let mut transformed_src_content = TokenStream2::new();
55 let mut error_tokens = TokenStream2::new();
56
57 for item in content {
58 let (attrs, vis, ident, extra_tokens) = match item {
63 syn::Item::ExternCrate(item) => {
67 let mut extra_tokens = TokenStream2::new();
68 if let Some(rename) = item.rename {
69 let e = syn::Error::new_spanned(
70 &rename.1,
71 "rename syntax (as ...) is not supported when using #[layers]",
72 );
73 extra_tokens.extend(e.to_compile_error());
74 }
75 let semi = item.semi_token;
76 extra_tokens.extend(quote! { #semi });
77
78 (item.attrs, item.vis, item.ident, extra_tokens)
79 }
80 syn::Item::Mod(item) => {
81 let mut extra_tokens = TokenStream2::new();
82 if let Some((_, content)) = item.content {
83 extra_tokens.extend(quote! { { #(#content)* } });
84 }
85 if let Some(semi) = item.semi {
86 extra_tokens.extend(quote! { #semi });
87 }
88
89 (item.attrs, item.vis, item.ident, extra_tokens)
90 }
91 _ => {
92 transformed_src_content.extend(quote! { #item });
94 continue;
95 }
96 };
97
98 let mut edges = Vec::with_capacity(attrs.len());
100 let mut docs = TokenStream2::new();
101 let mut cfg = TokenStream2::new();
102 for attr in attrs {
103 if attr.path().is_ident("depends_on") {
104 let ident = match attr
105 .meta
106 .require_list()
107 .and_then(|m| m.parse_args::<syn::Ident>())
108 {
109 Ok(x) => x,
110 Err(e) => {
111 error_tokens.extend(e.to_compile_error());
112 continue;
113 }
114 };
115 edges.push(graph::DepEdge {
116 name: ident.to_string(),
117 attr,
118 ident,
119 });
120 continue;
121 }
122
123 if attr.path().is_ident("doc") {
124 docs.extend(quote! { #attr });
125 }
126 if attr.path().is_ident("cfg") {
127 cfg.extend(quote! { #attr });
128 }
129
130 transformed_src_content.extend(quote! { #attr });
132 }
133
134 transformed_src_content.extend(quote! {
135 pub mod #ident #extra_tokens
136 });
137 graph.add(graph::ModuleDecl::new(
138 matches!(vis, syn::Visibility::Public(_)),
139 ident,
140 docs,
141 cfg,
142 edges,
143 ));
144 }
145
146 error_tokens.extend(graph.check());
150
151 let src_ident = syn::Ident::new(&input.ident.to_string(), Span2::call_site());
154 let mod_tokens = graph.generate_impl(&src_ident);
155
156 let expanded = quote! {
157 #before_tokens
158 pub(crate) mod #src_ident {
159 #transformed_src_content
160 }
161 #mod_tokens
162 #error_tokens
163 };
164
165 Ok(expanded.into())
166}
167
168mod graph;
169
170impl graph::DepsGraph {
171 fn generate_impl(&self, src_mod: &syn::Ident) -> TokenStream2 {
172 let mut mod_tokens = TokenStream2::new();
173 for entry in self.graph.values() {
174 mod_tokens.extend(self.generate_mod_impl(entry, src_mod, self.has_circular_deps));
175 }
176 mod_tokens
177 }
178 fn generate_mod_impl(
179 &self,
180 module: &graph::ModuleDecl,
181 src_mod: &syn::Ident,
182 has_circular_deps: bool,
183 ) -> TokenStream2 {
184 let vis = if module.is_pub {
185 quote! { pub }
186 } else {
187 quote! { pub(crate) }
188 };
189 let doc = &module.docs;
190 let cfg = &module.cfg;
191 let deps_ident = &module.ident;
192
193 if module.edges.is_empty() {
194 return quote_spanned! {
195 module.ident.span() =>
196 #cfg
197 #[doc(inline)]
198 #vis use #src_mod::#deps_ident;
199 };
200 }
201
202 let mut suppress_lints = TokenStream2::new();
203 if has_circular_deps {
204 suppress_lints.extend(quote! {
207 #[allow(unused_imports)]
208 });
209 }
210
211 let mut dep_tokens = TokenStream2::new();
212 for edge in &module.edges {
213 let dep_module = self.graph.get(&edge.name).unwrap();
214 let dep_cfg = &dep_module.cfg;
215 let dep_ident = &edge.ident;
216 dep_tokens.extend(quote_spanned! {
217 dep_ident.span() =>
218 #dep_cfg
219 pub use crate::#src_mod::#dep_ident;
220 });
221 }
222
223 quote_spanned! {
224 module.ident.span() =>
225 #doc
226 #cfg
227 #vis mod #deps_ident {
228 #[doc(inline)]
229 pub use crate::#src_mod::#deps_ident::*;
230 #[doc(hidden)]
231 #suppress_lints
232 pub(crate) mod crate_ {
233 #dep_tokens
234 }
235 }
236 }
237 }
238}