derive_deftly_macros/
check.rs1use crate::prelude::*;
4
5#[derive(Debug, Clone, Copy, Eq, PartialEq, EnumString, Display)]
7#[allow(non_camel_case_types)]
8pub enum Target {
9 items,
10 expr,
11}
12
13struct Checking<'t> {
15 ctx: &'t framework::Context<'t>,
16 output: &'t mut TokenStream,
17 target: DdOptVal<Target>,
18}
19
20pub fn check_expected_target_syntax(
31 ctx: &framework::Context,
32 output: &mut TokenStream,
33 target: DdOptVal<Target>,
34) {
35 check::Checking {
36 ctx,
37 output,
38 target,
39 }
40 .check();
41}
42
43pub fn check_expect_opcontext(
44 op: &DdOptVal<Target>,
45 context: OpContext,
46) -> syn::Result<()> {
47 use OpContext as OC;
48 match (context, op.value) {
49 (OC::TemplateDefinition, Target::items) => Ok(()),
50 (OC::TemplateDefinition, _) => {
51 Err(op.span.error(
52 "predefined templates must always expand to items", ))
54 }
55 _ => Ok(()),
56 }
57}
58
59impl Target {
60 fn perform_check(self, ts: TokenStream) -> Option<syn::Error> {
62 fn chk<T: Parse>(ts: TokenStream) -> Option<syn::Error> {
63 syn::parse2::<Discard<T>>(ts).err()
64 }
65
66 use Target::*;
67 match self {
68 items => chk::<Concatenated<Discard<syn::Item>>>(ts),
69 expr => chk::<syn::Expr>(ts),
70 }
71 }
72
73 fn include_syntax(self, file: &str) -> TokenStream {
75 use Target::*;
76 match self {
77 items => quote! { include!{ #file } },
78 expr => quote! { include!( #file ) },
79 }
80 }
81
82 fn combine_outputs(
87 self,
88 mut err: TokenStream,
89 expansion: TokenStream,
90 ) -> TokenStream {
91 use Target::*;
92 match self {
93 items => {
94 err.extend(expansion);
95 err
96 }
97 expr => quote!( ( #err, #expansion ) ),
98 }
99 }
100}
101
102impl Checking<'_> {
103 fn check(self) {
107 let err = self.target.value.perform_check(self.output.clone());
108
109 let err = match err {
110 Some(err) => err,
111 None => return,
112 };
113
114 let broken = mem::take(self.output);
115 let err = err.into_compile_error();
116
117 let expansion = expand_via_file(self.ctx, self.target.value, broken)
118 .map_err(|e| {
119 Span::call_site()
120 .error(format!(
121 "derive-deftly was unable to write out the expansion to a file for fuller syntax error reporting: {}",
122 e
123 ))
124 .into_compile_error()
125 })
126 .unwrap_or_else(|e| e);
127
128 *self.output = self.target.value.combine_outputs(err, expansion);
129 }
130}
131
132fn massage_dollar_crate(input: TokenStream) -> TokenStream {
134 input
135 .into_iter()
136 .update(|tt| match tt {
137 TT::Group(g) => {
138 *g = group_clone_set_stream(
139 &g,
140 massage_dollar_crate(g.stream()),
141 )
142 }
143 TT::Ident(i) if i == "$crate" => {
144 *i = Ident::new("crate", i.span())
145 }
146 _other => {}
147 })
148 .collect()
149}
150
151fn expand_via_file(
157 ctx: &framework::Context,
158 target: Target,
159 broken: TokenStream,
160) -> Result<TokenStream, String> {
161 use sha3::{Digest as _, Sha3_256};
162 use std::{fs, io, io::Write as _, path::PathBuf};
163
164 let broken = massage_dollar_crate(broken);
174
175 let text = format!(
176 "// {}, should have been {}:\n{}\n",
177 ctx.expansion_description(),
178 target,
179 broken,
180 );
181
182 let hash: String = {
183 let mut hasher = Sha3_256::new();
184 hasher.update(&text);
185 let hash = hasher.finalize();
186 const HASH_LEN_BYTES: usize = 12;
187 hash[0..HASH_LEN_BYTES].iter().fold(
188 String::with_capacity(HASH_LEN_BYTES * 2),
189 |mut s, b| {
190 write!(s, "{:02x}", b).expect("write to String failed");
191 s
192 },
193 )
194 };
195
196 let dir: PathBuf = [env!("OUT_DIR"), "derive-deftly~expansions~"]
197 .iter()
198 .collect();
199
200 match fs::create_dir(&dir) {
201 Ok(()) => {}
202 Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {}
203 Err(e) => return Err(format!("create dir {:?}: {}", &dir, e)),
204 };
205
206 let leaf = format!("dd-{}.rs", hash);
207 let some_file = |leaf: &str| {
208 let mut file = dir.clone();
209 file.push(leaf);
210 file
211 };
212 let file = some_file(&leaf);
213 let file = file
214 .to_str()
215 .ok_or_else(|| format!("non UTF-8 path? from env var! {:?}", file))?;
216
217 let mut fh = fs::OpenOptions::new()
232 .write(true)
233 .create(true)
234 .truncate(false)
235 .open(file)
236 .map_err(|e| format!("create/open {:?}: {}", &file, e))?;
237 fh.write_all(text.as_ref())
238 .map_err(|e| format!("write {:?}: {}", &file, e))?;
239
240 Ok(target.include_syntax(file))
241}