]>
Commit | Line | Data |
---|---|---|
064997fb FG |
1 | //! Builtin macro |
2 | ||
3 | use base_db::{AnchoredPath, Edition, FileId}; | |
4 | use cfg::CfgExpr; | |
5 | use either::Either; | |
6 | use mbe::{parse_exprs_with_sep, parse_to_token_tree}; | |
7 | use syntax::{ | |
8 | ast::{self, AstToken}, | |
9 | SmolStr, | |
10 | }; | |
11 | ||
12 | use crate::{db::AstDatabase, name, quote, ExpandError, ExpandResult, MacroCallId, MacroCallLoc}; | |
13 | ||
14 | macro_rules! register_builtin { | |
15 | ( LAZY: $(($name:ident, $kind: ident) => $expand:ident),* , EAGER: $(($e_name:ident, $e_kind: ident) => $e_expand:ident),* ) => { | |
16 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | |
17 | pub enum BuiltinFnLikeExpander { | |
18 | $($kind),* | |
19 | } | |
20 | ||
21 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | |
22 | pub enum EagerExpander { | |
23 | $($e_kind),* | |
24 | } | |
25 | ||
26 | impl BuiltinFnLikeExpander { | |
27 | pub fn expand( | |
28 | &self, | |
29 | db: &dyn AstDatabase, | |
30 | id: MacroCallId, | |
31 | tt: &tt::Subtree, | |
32 | ) -> ExpandResult<tt::Subtree> { | |
33 | let expander = match *self { | |
34 | $( BuiltinFnLikeExpander::$kind => $expand, )* | |
35 | }; | |
36 | expander(db, id, tt) | |
37 | } | |
38 | } | |
39 | ||
40 | impl EagerExpander { | |
41 | pub fn expand( | |
42 | &self, | |
43 | db: &dyn AstDatabase, | |
44 | arg_id: MacroCallId, | |
45 | tt: &tt::Subtree, | |
46 | ) -> ExpandResult<ExpandedEager> { | |
47 | let expander = match *self { | |
48 | $( EagerExpander::$e_kind => $e_expand, )* | |
49 | }; | |
50 | expander(db, arg_id, tt) | |
51 | } | |
52 | } | |
53 | ||
54 | fn find_by_name(ident: &name::Name) -> Option<Either<BuiltinFnLikeExpander, EagerExpander>> { | |
55 | match ident { | |
56 | $( id if id == &name::name![$name] => Some(Either::Left(BuiltinFnLikeExpander::$kind)), )* | |
57 | $( id if id == &name::name![$e_name] => Some(Either::Right(EagerExpander::$e_kind)), )* | |
58 | _ => return None, | |
59 | } | |
60 | } | |
61 | }; | |
62 | } | |
63 | ||
64 | #[derive(Debug, Default)] | |
65 | pub struct ExpandedEager { | |
66 | pub(crate) subtree: tt::Subtree, | |
67 | /// The included file ID of the include macro. | |
68 | pub(crate) included_file: Option<FileId>, | |
69 | } | |
70 | ||
71 | impl ExpandedEager { | |
72 | fn new(subtree: tt::Subtree) -> Self { | |
73 | ExpandedEager { subtree, included_file: None } | |
74 | } | |
75 | } | |
76 | ||
77 | pub fn find_builtin_macro( | |
78 | ident: &name::Name, | |
79 | ) -> Option<Either<BuiltinFnLikeExpander, EagerExpander>> { | |
80 | find_by_name(ident) | |
81 | } | |
82 | ||
83 | register_builtin! { | |
84 | LAZY: | |
85 | (column, Column) => column_expand, | |
86 | (file, File) => file_expand, | |
87 | (line, Line) => line_expand, | |
88 | (module_path, ModulePath) => module_path_expand, | |
89 | (assert, Assert) => assert_expand, | |
90 | (stringify, Stringify) => stringify_expand, | |
91 | (format_args, FormatArgs) => format_args_expand, | |
92 | (const_format_args, ConstFormatArgs) => format_args_expand, | |
93 | // format_args_nl only differs in that it adds a newline in the end, | |
94 | // so we use the same stub expansion for now | |
95 | (format_args_nl, FormatArgsNl) => format_args_expand, | |
96 | (llvm_asm, LlvmAsm) => asm_expand, | |
97 | (asm, Asm) => asm_expand, | |
98 | (global_asm, GlobalAsm) => global_asm_expand, | |
99 | (cfg, Cfg) => cfg_expand, | |
100 | (core_panic, CorePanic) => panic_expand, | |
101 | (std_panic, StdPanic) => panic_expand, | |
102 | (unreachable, Unreachable) => unreachable_expand, | |
103 | (log_syntax, LogSyntax) => log_syntax_expand, | |
104 | (trace_macros, TraceMacros) => trace_macros_expand, | |
105 | ||
106 | EAGER: | |
107 | (compile_error, CompileError) => compile_error_expand, | |
108 | (concat, Concat) => concat_expand, | |
109 | (concat_idents, ConcatIdents) => concat_idents_expand, | |
110 | (concat_bytes, ConcatBytes) => concat_bytes_expand, | |
111 | (include, Include) => include_expand, | |
112 | (include_bytes, IncludeBytes) => include_bytes_expand, | |
113 | (include_str, IncludeStr) => include_str_expand, | |
114 | (env, Env) => env_expand, | |
115 | (option_env, OptionEnv) => option_env_expand | |
116 | } | |
117 | ||
118 | const DOLLAR_CRATE: tt::Ident = | |
119 | tt::Ident { text: SmolStr::new_inline("$crate"), id: tt::TokenId::unspecified() }; | |
120 | ||
121 | fn module_path_expand( | |
122 | _db: &dyn AstDatabase, | |
123 | _id: MacroCallId, | |
124 | _tt: &tt::Subtree, | |
125 | ) -> ExpandResult<tt::Subtree> { | |
126 | // Just return a dummy result. | |
127 | ExpandResult::ok(quote! { "module::path" }) | |
128 | } | |
129 | ||
130 | fn line_expand( | |
131 | _db: &dyn AstDatabase, | |
132 | _id: MacroCallId, | |
133 | _tt: &tt::Subtree, | |
134 | ) -> ExpandResult<tt::Subtree> { | |
135 | // dummy implementation for type-checking purposes | |
136 | let line_num = 0; | |
137 | let expanded = quote! { | |
138 | #line_num | |
139 | }; | |
140 | ||
141 | ExpandResult::ok(expanded) | |
142 | } | |
143 | ||
144 | fn log_syntax_expand( | |
145 | _db: &dyn AstDatabase, | |
146 | _id: MacroCallId, | |
147 | _tt: &tt::Subtree, | |
148 | ) -> ExpandResult<tt::Subtree> { | |
149 | ExpandResult::ok(quote! {}) | |
150 | } | |
151 | ||
152 | fn trace_macros_expand( | |
153 | _db: &dyn AstDatabase, | |
154 | _id: MacroCallId, | |
155 | _tt: &tt::Subtree, | |
156 | ) -> ExpandResult<tt::Subtree> { | |
157 | ExpandResult::ok(quote! {}) | |
158 | } | |
159 | ||
160 | fn stringify_expand( | |
161 | _db: &dyn AstDatabase, | |
162 | _id: MacroCallId, | |
163 | tt: &tt::Subtree, | |
164 | ) -> ExpandResult<tt::Subtree> { | |
165 | let pretty = tt::pretty(&tt.token_trees); | |
166 | ||
167 | let expanded = quote! { | |
168 | #pretty | |
169 | }; | |
170 | ||
171 | ExpandResult::ok(expanded) | |
172 | } | |
173 | ||
174 | fn column_expand( | |
175 | _db: &dyn AstDatabase, | |
176 | _id: MacroCallId, | |
177 | _tt: &tt::Subtree, | |
178 | ) -> ExpandResult<tt::Subtree> { | |
179 | // dummy implementation for type-checking purposes | |
180 | let col_num = 0; | |
181 | let expanded = quote! { | |
182 | #col_num | |
183 | }; | |
184 | ||
185 | ExpandResult::ok(expanded) | |
186 | } | |
187 | ||
188 | fn assert_expand( | |
189 | _db: &dyn AstDatabase, | |
190 | _id: MacroCallId, | |
191 | tt: &tt::Subtree, | |
192 | ) -> ExpandResult<tt::Subtree> { | |
193 | let args = parse_exprs_with_sep(tt, ','); | |
194 | let expanded = match &*args { | |
195 | [cond, panic_args @ ..] => { | |
196 | let comma = tt::Subtree { | |
197 | delimiter: None, | |
198 | token_trees: vec![tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { | |
199 | char: ',', | |
200 | spacing: tt::Spacing::Alone, | |
201 | id: tt::TokenId::unspecified(), | |
202 | }))], | |
203 | }; | |
204 | let cond = cond.clone(); | |
205 | let panic_args = itertools::Itertools::intersperse(panic_args.iter().cloned(), comma); | |
206 | quote! {{ | |
207 | if !#cond { | |
208 | #DOLLAR_CRATE::panic!(##panic_args); | |
209 | } | |
210 | }} | |
211 | } | |
212 | [] => quote! {{}}, | |
213 | }; | |
214 | ||
215 | ExpandResult::ok(expanded) | |
216 | } | |
217 | ||
218 | fn file_expand( | |
219 | _db: &dyn AstDatabase, | |
220 | _id: MacroCallId, | |
221 | _tt: &tt::Subtree, | |
222 | ) -> ExpandResult<tt::Subtree> { | |
223 | // FIXME: RA purposefully lacks knowledge of absolute file names | |
224 | // so just return "". | |
225 | let file_name = ""; | |
226 | ||
227 | let expanded = quote! { | |
228 | #file_name | |
229 | }; | |
230 | ||
231 | ExpandResult::ok(expanded) | |
232 | } | |
233 | ||
234 | fn format_args_expand( | |
235 | _db: &dyn AstDatabase, | |
236 | _id: MacroCallId, | |
237 | tt: &tt::Subtree, | |
238 | ) -> ExpandResult<tt::Subtree> { | |
239 | // We expand `format_args!("", a1, a2)` to | |
240 | // ``` | |
2b03887a FG |
241 | // $crate::fmt::Arguments::new_v1(&[], &[ |
242 | // $crate::fmt::ArgumentV1::new(&arg1,$crate::fmt::Display::fmt), | |
243 | // $crate::fmt::ArgumentV1::new(&arg2,$crate::fmt::Display::fmt), | |
064997fb FG |
244 | // ]) |
245 | // ```, | |
246 | // which is still not really correct, but close enough for now | |
247 | let mut args = parse_exprs_with_sep(tt, ','); | |
248 | ||
249 | if args.is_empty() { | |
250 | return ExpandResult::only_err(mbe::ExpandError::NoMatchingRule.into()); | |
251 | } | |
252 | for arg in &mut args { | |
253 | // Remove `key =`. | |
f2b60f7d | 254 | if matches!(arg.token_trees.get(1), Some(tt::TokenTree::Leaf(tt::Leaf::Punct(p))) if p.char == '=') |
064997fb | 255 | { |
f2b60f7d FG |
256 | // but not with `==` |
257 | if !matches!(arg.token_trees.get(2), Some(tt::TokenTree::Leaf(tt::Leaf::Punct(p))) if p.char == '=' ) | |
258 | { | |
259 | arg.token_trees.drain(..2); | |
260 | } | |
064997fb FG |
261 | } |
262 | } | |
263 | let _format_string = args.remove(0); | |
264 | let arg_tts = args.into_iter().flat_map(|arg| { | |
2b03887a | 265 | quote! { #DOLLAR_CRATE::fmt::ArgumentV1::new(&(#arg), #DOLLAR_CRATE::fmt::Display::fmt), } |
064997fb FG |
266 | }.token_trees); |
267 | let expanded = quote! { | |
2b03887a | 268 | #DOLLAR_CRATE::fmt::Arguments::new_v1(&[], &[##arg_tts]) |
064997fb FG |
269 | }; |
270 | ExpandResult::ok(expanded) | |
271 | } | |
272 | ||
273 | fn asm_expand( | |
274 | _db: &dyn AstDatabase, | |
275 | _id: MacroCallId, | |
276 | tt: &tt::Subtree, | |
277 | ) -> ExpandResult<tt::Subtree> { | |
278 | // We expand all assembly snippets to `format_args!` invocations to get format syntax | |
279 | // highlighting for them. | |
280 | ||
281 | let mut literals = Vec::new(); | |
282 | for tt in tt.token_trees.chunks(2) { | |
283 | match tt { | |
284 | [tt::TokenTree::Leaf(tt::Leaf::Literal(lit))] | |
285 | | [tt::TokenTree::Leaf(tt::Leaf::Literal(lit)), tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { char: ',', id: _, spacing: _ }))] => | |
286 | { | |
287 | let krate = DOLLAR_CRATE.clone(); | |
288 | literals.push(quote!(#krate::format_args!(#lit);)); | |
289 | } | |
290 | _ => break, | |
291 | } | |
292 | } | |
293 | ||
294 | let expanded = quote! {{ | |
295 | ##literals | |
296 | loop {} | |
297 | }}; | |
298 | ExpandResult::ok(expanded) | |
299 | } | |
300 | ||
301 | fn global_asm_expand( | |
302 | _db: &dyn AstDatabase, | |
303 | _id: MacroCallId, | |
304 | _tt: &tt::Subtree, | |
305 | ) -> ExpandResult<tt::Subtree> { | |
306 | // Expand to nothing (at item-level) | |
307 | ExpandResult::ok(quote! {}) | |
308 | } | |
309 | ||
310 | fn cfg_expand( | |
311 | db: &dyn AstDatabase, | |
312 | id: MacroCallId, | |
313 | tt: &tt::Subtree, | |
314 | ) -> ExpandResult<tt::Subtree> { | |
315 | let loc = db.lookup_intern_macro_call(id); | |
316 | let expr = CfgExpr::parse(tt); | |
317 | let enabled = db.crate_graph()[loc.krate].cfg_options.check(&expr) != Some(false); | |
318 | let expanded = if enabled { quote!(true) } else { quote!(false) }; | |
319 | ExpandResult::ok(expanded) | |
320 | } | |
321 | ||
322 | fn panic_expand( | |
323 | db: &dyn AstDatabase, | |
324 | id: MacroCallId, | |
325 | tt: &tt::Subtree, | |
326 | ) -> ExpandResult<tt::Subtree> { | |
327 | let loc: MacroCallLoc = db.lookup_intern_macro_call(id); | |
328 | // Expand to a macro call `$crate::panic::panic_{edition}` | |
329 | let mut call = if db.crate_graph()[loc.krate].edition >= Edition::Edition2021 { | |
330 | quote!(#DOLLAR_CRATE::panic::panic_2021!) | |
331 | } else { | |
332 | quote!(#DOLLAR_CRATE::panic::panic_2015!) | |
333 | }; | |
334 | ||
335 | // Pass the original arguments | |
336 | call.token_trees.push(tt::TokenTree::Subtree(tt.clone())); | |
337 | ExpandResult::ok(call) | |
338 | } | |
339 | ||
340 | fn unreachable_expand( | |
341 | db: &dyn AstDatabase, | |
342 | id: MacroCallId, | |
343 | tt: &tt::Subtree, | |
344 | ) -> ExpandResult<tt::Subtree> { | |
345 | let loc: MacroCallLoc = db.lookup_intern_macro_call(id); | |
346 | // Expand to a macro call `$crate::panic::unreachable_{edition}` | |
347 | let mut call = if db.crate_graph()[loc.krate].edition >= Edition::Edition2021 { | |
348 | quote!(#DOLLAR_CRATE::panic::unreachable_2021!) | |
349 | } else { | |
350 | quote!(#DOLLAR_CRATE::panic::unreachable_2015!) | |
351 | }; | |
352 | ||
353 | // Pass the original arguments | |
354 | call.token_trees.push(tt::TokenTree::Subtree(tt.clone())); | |
355 | ExpandResult::ok(call) | |
356 | } | |
357 | ||
358 | fn unquote_str(lit: &tt::Literal) -> Option<String> { | |
359 | let lit = ast::make::tokens::literal(&lit.to_string()); | |
360 | let token = ast::String::cast(lit)?; | |
361 | token.value().map(|it| it.into_owned()) | |
362 | } | |
363 | ||
f2b60f7d FG |
364 | fn unquote_char(lit: &tt::Literal) -> Option<char> { |
365 | let lit = ast::make::tokens::literal(&lit.to_string()); | |
366 | let token = ast::Char::cast(lit)?; | |
367 | token.value() | |
368 | } | |
369 | ||
064997fb FG |
370 | fn unquote_byte_string(lit: &tt::Literal) -> Option<Vec<u8>> { |
371 | let lit = ast::make::tokens::literal(&lit.to_string()); | |
372 | let token = ast::ByteString::cast(lit)?; | |
373 | token.value().map(|it| it.into_owned()) | |
374 | } | |
375 | ||
376 | fn compile_error_expand( | |
377 | _db: &dyn AstDatabase, | |
378 | _id: MacroCallId, | |
379 | tt: &tt::Subtree, | |
380 | ) -> ExpandResult<ExpandedEager> { | |
381 | let err = match &*tt.token_trees { | |
9c376795 FG |
382 | [tt::TokenTree::Leaf(tt::Leaf::Literal(it))] => match unquote_str(it) { |
383 | Some(unquoted) => ExpandError::Other(unquoted.into()), | |
384 | None => ExpandError::Other("`compile_error!` argument must be a string".into()), | |
385 | }, | |
064997fb FG |
386 | _ => ExpandError::Other("`compile_error!` argument must be a string".into()), |
387 | }; | |
388 | ||
389 | ExpandResult { value: ExpandedEager::new(quote! {}), err: Some(err) } | |
390 | } | |
391 | ||
392 | fn concat_expand( | |
393 | _db: &dyn AstDatabase, | |
394 | _arg_id: MacroCallId, | |
395 | tt: &tt::Subtree, | |
396 | ) -> ExpandResult<ExpandedEager> { | |
397 | let mut err = None; | |
398 | let mut text = String::new(); | |
399 | for (i, mut t) in tt.token_trees.iter().enumerate() { | |
400 | // FIXME: hack on top of a hack: `$e:expr` captures get surrounded in parentheses | |
401 | // to ensure the right parsing order, so skip the parentheses here. Ideally we'd | |
402 | // implement rustc's model. cc https://github.com/rust-lang/rust-analyzer/pull/10623 | |
403 | if let tt::TokenTree::Subtree(tt::Subtree { delimiter: Some(delim), token_trees }) = t { | |
404 | if let [tt] = &**token_trees { | |
405 | if delim.kind == tt::DelimiterKind::Parenthesis { | |
406 | t = tt; | |
407 | } | |
408 | } | |
409 | } | |
410 | ||
411 | match t { | |
412 | tt::TokenTree::Leaf(tt::Leaf::Literal(it)) if i % 2 == 0 => { | |
413 | // concat works with string and char literals, so remove any quotes. | |
414 | // It also works with integer, float and boolean literals, so just use the rest | |
415 | // as-is. | |
f2b60f7d FG |
416 | if let Some(c) = unquote_char(it) { |
417 | text.push(c); | |
418 | } else { | |
419 | let component = unquote_str(it).unwrap_or_else(|| it.text.to_string()); | |
420 | text.push_str(&component); | |
421 | } | |
064997fb FG |
422 | } |
423 | // handle boolean literals | |
424 | tt::TokenTree::Leaf(tt::Leaf::Ident(id)) | |
425 | if i % 2 == 0 && (id.text == "true" || id.text == "false") => | |
426 | { | |
427 | text.push_str(id.text.as_str()); | |
428 | } | |
429 | tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (), | |
430 | _ => { | |
431 | err.get_or_insert(mbe::ExpandError::UnexpectedToken.into()); | |
432 | } | |
433 | } | |
434 | } | |
435 | ExpandResult { value: ExpandedEager::new(quote!(#text)), err } | |
436 | } | |
437 | ||
438 | fn concat_bytes_expand( | |
439 | _db: &dyn AstDatabase, | |
440 | _arg_id: MacroCallId, | |
441 | tt: &tt::Subtree, | |
442 | ) -> ExpandResult<ExpandedEager> { | |
443 | let mut bytes = Vec::new(); | |
444 | let mut err = None; | |
445 | for (i, t) in tt.token_trees.iter().enumerate() { | |
446 | match t { | |
447 | tt::TokenTree::Leaf(tt::Leaf::Literal(lit)) => { | |
448 | let token = ast::make::tokens::literal(&lit.to_string()); | |
449 | match token.kind() { | |
450 | syntax::SyntaxKind::BYTE => bytes.push(token.text().to_string()), | |
451 | syntax::SyntaxKind::BYTE_STRING => { | |
9c376795 | 452 | let components = unquote_byte_string(lit).unwrap_or_default(); |
064997fb FG |
453 | components.into_iter().for_each(|x| bytes.push(x.to_string())); |
454 | } | |
455 | _ => { | |
456 | err.get_or_insert(mbe::ExpandError::UnexpectedToken.into()); | |
457 | break; | |
458 | } | |
459 | } | |
460 | } | |
461 | tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (), | |
462 | tt::TokenTree::Subtree(tree) | |
463 | if tree.delimiter_kind() == Some(tt::DelimiterKind::Bracket) => | |
464 | { | |
465 | if let Err(e) = concat_bytes_expand_subtree(tree, &mut bytes) { | |
466 | err.get_or_insert(e); | |
467 | break; | |
468 | } | |
469 | } | |
470 | _ => { | |
471 | err.get_or_insert(mbe::ExpandError::UnexpectedToken.into()); | |
472 | break; | |
473 | } | |
474 | } | |
475 | } | |
476 | let ident = tt::Ident { text: bytes.join(", ").into(), id: tt::TokenId::unspecified() }; | |
477 | ExpandResult { value: ExpandedEager::new(quote!([#ident])), err } | |
478 | } | |
479 | ||
480 | fn concat_bytes_expand_subtree( | |
481 | tree: &tt::Subtree, | |
482 | bytes: &mut Vec<String>, | |
483 | ) -> Result<(), ExpandError> { | |
484 | for (ti, tt) in tree.token_trees.iter().enumerate() { | |
485 | match tt { | |
486 | tt::TokenTree::Leaf(tt::Leaf::Literal(lit)) => { | |
487 | let lit = ast::make::tokens::literal(&lit.to_string()); | |
488 | match lit.kind() { | |
489 | syntax::SyntaxKind::BYTE | syntax::SyntaxKind::INT_NUMBER => { | |
490 | bytes.push(lit.text().to_string()) | |
491 | } | |
492 | _ => { | |
493 | return Err(mbe::ExpandError::UnexpectedToken.into()); | |
494 | } | |
495 | } | |
496 | } | |
497 | tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if ti % 2 == 1 && punct.char == ',' => (), | |
498 | _ => { | |
499 | return Err(mbe::ExpandError::UnexpectedToken.into()); | |
500 | } | |
501 | } | |
502 | } | |
503 | Ok(()) | |
504 | } | |
505 | ||
506 | fn concat_idents_expand( | |
507 | _db: &dyn AstDatabase, | |
508 | _arg_id: MacroCallId, | |
509 | tt: &tt::Subtree, | |
510 | ) -> ExpandResult<ExpandedEager> { | |
511 | let mut err = None; | |
512 | let mut ident = String::new(); | |
513 | for (i, t) in tt.token_trees.iter().enumerate() { | |
514 | match t { | |
515 | tt::TokenTree::Leaf(tt::Leaf::Ident(id)) => { | |
516 | ident.push_str(id.text.as_str()); | |
517 | } | |
518 | tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (), | |
519 | _ => { | |
520 | err.get_or_insert(mbe::ExpandError::UnexpectedToken.into()); | |
521 | } | |
522 | } | |
523 | } | |
524 | let ident = tt::Ident { text: ident.into(), id: tt::TokenId::unspecified() }; | |
525 | ExpandResult { value: ExpandedEager::new(quote!(#ident)), err } | |
526 | } | |
527 | ||
528 | fn relative_file( | |
529 | db: &dyn AstDatabase, | |
530 | call_id: MacroCallId, | |
531 | path_str: &str, | |
532 | allow_recursion: bool, | |
533 | ) -> Result<FileId, ExpandError> { | |
534 | let call_site = call_id.as_file().original_file(db); | |
535 | let path = AnchoredPath { anchor: call_site, path: path_str }; | |
536 | let res = db | |
537 | .resolve_path(path) | |
538 | .ok_or_else(|| ExpandError::Other(format!("failed to load file `{path_str}`").into()))?; | |
539 | // Prevent include itself | |
540 | if res == call_site && !allow_recursion { | |
541 | Err(ExpandError::Other(format!("recursive inclusion of `{path_str}`").into())) | |
542 | } else { | |
543 | Ok(res) | |
544 | } | |
545 | } | |
546 | ||
547 | fn parse_string(tt: &tt::Subtree) -> Result<String, ExpandError> { | |
548 | tt.token_trees | |
549 | .get(0) | |
550 | .and_then(|tt| match tt { | |
551 | tt::TokenTree::Leaf(tt::Leaf::Literal(it)) => unquote_str(it), | |
552 | _ => None, | |
553 | }) | |
554 | .ok_or(mbe::ExpandError::ConversionError.into()) | |
555 | } | |
556 | ||
557 | fn include_expand( | |
558 | db: &dyn AstDatabase, | |
559 | arg_id: MacroCallId, | |
560 | tt: &tt::Subtree, | |
561 | ) -> ExpandResult<ExpandedEager> { | |
562 | let res = (|| { | |
563 | let path = parse_string(tt)?; | |
564 | let file_id = relative_file(db, arg_id, &path, false)?; | |
565 | ||
566 | let subtree = | |
567 | parse_to_token_tree(&db.file_text(file_id)).ok_or(mbe::ExpandError::ConversionError)?.0; | |
568 | Ok((subtree, file_id)) | |
569 | })(); | |
570 | ||
571 | match res { | |
572 | Ok((subtree, file_id)) => { | |
573 | ExpandResult::ok(ExpandedEager { subtree, included_file: Some(file_id) }) | |
574 | } | |
575 | Err(e) => ExpandResult::only_err(e), | |
576 | } | |
577 | } | |
578 | ||
579 | fn include_bytes_expand( | |
580 | _db: &dyn AstDatabase, | |
581 | _arg_id: MacroCallId, | |
582 | tt: &tt::Subtree, | |
583 | ) -> ExpandResult<ExpandedEager> { | |
584 | if let Err(e) = parse_string(tt) { | |
585 | return ExpandResult::only_err(e); | |
586 | } | |
587 | ||
588 | // FIXME: actually read the file here if the user asked for macro expansion | |
589 | let res = tt::Subtree { | |
590 | delimiter: None, | |
591 | token_trees: vec![tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { | |
592 | text: r#"b"""#.into(), | |
593 | id: tt::TokenId::unspecified(), | |
594 | }))], | |
595 | }; | |
596 | ExpandResult::ok(ExpandedEager::new(res)) | |
597 | } | |
598 | ||
599 | fn include_str_expand( | |
600 | db: &dyn AstDatabase, | |
601 | arg_id: MacroCallId, | |
602 | tt: &tt::Subtree, | |
603 | ) -> ExpandResult<ExpandedEager> { | |
604 | let path = match parse_string(tt) { | |
605 | Ok(it) => it, | |
606 | Err(e) => return ExpandResult::only_err(e), | |
607 | }; | |
608 | ||
609 | // FIXME: we're not able to read excluded files (which is most of them because | |
610 | // it's unusual to `include_str!` a Rust file), but we can return an empty string. | |
611 | // Ideally, we'd be able to offer a precise expansion if the user asks for macro | |
612 | // expansion. | |
613 | let file_id = match relative_file(db, arg_id, &path, true) { | |
614 | Ok(file_id) => file_id, | |
615 | Err(_) => { | |
616 | return ExpandResult::ok(ExpandedEager::new(quote!(""))); | |
617 | } | |
618 | }; | |
619 | ||
620 | let text = db.file_text(file_id); | |
621 | let text = &*text; | |
622 | ||
623 | ExpandResult::ok(ExpandedEager::new(quote!(#text))) | |
624 | } | |
625 | ||
626 | fn get_env_inner(db: &dyn AstDatabase, arg_id: MacroCallId, key: &str) -> Option<String> { | |
627 | let krate = db.lookup_intern_macro_call(arg_id).krate; | |
628 | db.crate_graph()[krate].env.get(key) | |
629 | } | |
630 | ||
631 | fn env_expand( | |
632 | db: &dyn AstDatabase, | |
633 | arg_id: MacroCallId, | |
634 | tt: &tt::Subtree, | |
635 | ) -> ExpandResult<ExpandedEager> { | |
636 | let key = match parse_string(tt) { | |
637 | Ok(it) => it, | |
638 | Err(e) => return ExpandResult::only_err(e), | |
639 | }; | |
640 | ||
641 | let mut err = None; | |
642 | let s = get_env_inner(db, arg_id, &key).unwrap_or_else(|| { | |
643 | // The only variable rust-analyzer ever sets is `OUT_DIR`, so only diagnose that to avoid | |
644 | // unnecessary diagnostics for eg. `CARGO_PKG_NAME`. | |
645 | if key == "OUT_DIR" { | |
646 | err = Some(ExpandError::Other( | |
647 | r#"`OUT_DIR` not set, enable "build scripts" to fix"#.into(), | |
648 | )); | |
649 | } | |
650 | ||
651 | // If the variable is unset, still return a dummy string to help type inference along. | |
652 | // We cannot use an empty string here, because for | |
653 | // `include!(concat!(env!("OUT_DIR"), "/foo.rs"))` will become | |
654 | // `include!("foo.rs"), which might go to infinite loop | |
655 | "__RA_UNIMPLEMENTED__".to_string() | |
656 | }); | |
657 | let expanded = quote! { #s }; | |
658 | ||
659 | ExpandResult { value: ExpandedEager::new(expanded), err } | |
660 | } | |
661 | ||
662 | fn option_env_expand( | |
663 | db: &dyn AstDatabase, | |
664 | arg_id: MacroCallId, | |
665 | tt: &tt::Subtree, | |
666 | ) -> ExpandResult<ExpandedEager> { | |
667 | let key = match parse_string(tt) { | |
668 | Ok(it) => it, | |
669 | Err(e) => return ExpandResult::only_err(e), | |
670 | }; | |
671 | ||
672 | let expanded = match get_env_inner(db, arg_id, &key) { | |
2b03887a | 673 | None => quote! { #DOLLAR_CRATE::option::Option::None::<&str> }, |
9c376795 | 674 | Some(s) => quote! { #DOLLAR_CRATE::option::Option::Some(#s) }, |
064997fb FG |
675 | }; |
676 | ||
677 | ExpandResult::ok(ExpandedEager::new(expanded)) | |
678 | } |