]> git.lizzy.rs Git - rust.git/blob - crates/ra_hir_expand/src/builtin_macro.rs
Minimize FileLoader interface
[rust.git] / crates / ra_hir_expand / src / builtin_macro.rs
1 //! Builtin macro
2 use crate::db::AstDatabase;
3 use crate::{
4     ast::{self, AstToken, HasStringValue},
5     name, AstId, CrateId, MacroDefId, MacroDefKind, TextSize,
6 };
7
8 use crate::{quote, EagerMacroId, LazyMacroId, MacroCallId};
9 use either::Either;
10 use mbe::parse_to_token_tree;
11 use ra_db::FileId;
12 use ra_parser::FragmentKind;
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: LazyMacroId,
31                 tt: &tt::Subtree,
32             ) -> Result<tt::Subtree, mbe::ExpandError> {
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: EagerMacroId,
45                 tt: &tt::Subtree,
46             ) -> Result<(tt::Subtree, FragmentKind), mbe::ExpandError> {
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 pub fn find_builtin_macro(
65     ident: &name::Name,
66     krate: CrateId,
67     ast_id: AstId<ast::MacroCall>,
68 ) -> Option<MacroDefId> {
69     let kind = find_by_name(ident)?;
70
71     match kind {
72         Either::Left(kind) => Some(MacroDefId {
73             krate: Some(krate),
74             ast_id: Some(ast_id),
75             kind: MacroDefKind::BuiltIn(kind),
76             local_inner: false,
77         }),
78         Either::Right(kind) => Some(MacroDefId {
79             krate: Some(krate),
80             ast_id: Some(ast_id),
81             kind: MacroDefKind::BuiltInEager(kind),
82             local_inner: false,
83         }),
84     }
85 }
86
87 register_builtin! {
88     LAZY:
89     (column, Column) => column_expand,
90     (compile_error, CompileError) => compile_error_expand,
91     (file, File) => file_expand,
92     (line, Line) => line_expand,
93     (assert, Assert) => assert_expand,
94     (stringify, Stringify) => stringify_expand,
95     (format_args, FormatArgs) => format_args_expand,
96     // format_args_nl only differs in that it adds a newline in the end,
97     // so we use the same stub expansion for now
98     (format_args_nl, FormatArgsNl) => format_args_expand,
99
100     EAGER:
101     (concat, Concat) => concat_expand,
102     (include, Include) => include_expand,
103     (env, Env) => env_expand,
104     (option_env, OptionEnv) => option_env_expand
105 }
106
107 fn line_expand(
108     _db: &dyn AstDatabase,
109     _id: LazyMacroId,
110     _tt: &tt::Subtree,
111 ) -> Result<tt::Subtree, mbe::ExpandError> {
112     // dummy implementation for type-checking purposes
113     let line_num = 0;
114     let expanded = quote! {
115         #line_num
116     };
117
118     Ok(expanded)
119 }
120
121 fn stringify_expand(
122     db: &dyn AstDatabase,
123     id: LazyMacroId,
124     _tt: &tt::Subtree,
125 ) -> Result<tt::Subtree, mbe::ExpandError> {
126     let loc = db.lookup_intern_macro(id);
127
128     let macro_content = {
129         let arg = loc.kind.arg(db).ok_or_else(|| mbe::ExpandError::UnexpectedToken)?;
130         let macro_args = arg;
131         let text = macro_args.text();
132         let without_parens = TextSize::of('(')..text.len() - TextSize::of(')');
133         text.slice(without_parens).to_string()
134     };
135
136     let expanded = quote! {
137         #macro_content
138     };
139
140     Ok(expanded)
141 }
142
143 fn column_expand(
144     _db: &dyn AstDatabase,
145     _id: LazyMacroId,
146     _tt: &tt::Subtree,
147 ) -> Result<tt::Subtree, mbe::ExpandError> {
148     // dummy implementation for type-checking purposes
149     let col_num = 0;
150     let expanded = quote! {
151         #col_num
152     };
153
154     Ok(expanded)
155 }
156
157 fn assert_expand(
158     _db: &dyn AstDatabase,
159     _id: LazyMacroId,
160     tt: &tt::Subtree,
161 ) -> Result<tt::Subtree, mbe::ExpandError> {
162     // A hacky implementation for goto def and hover
163     // We expand `assert!(cond, arg1, arg2)` to
164     // ```
165     // {(cond, &(arg1), &(arg2));}
166     // ```,
167     // which is wrong but useful.
168
169     let mut args = Vec::new();
170     let mut current = Vec::new();
171     for tt in tt.token_trees.iter().cloned() {
172         match tt {
173             tt::TokenTree::Leaf(tt::Leaf::Punct(p)) if p.char == ',' => {
174                 args.push(current);
175                 current = Vec::new();
176             }
177             _ => {
178                 current.push(tt);
179             }
180         }
181     }
182     if !current.is_empty() {
183         args.push(current);
184     }
185
186     let arg_tts = args.into_iter().flat_map(|arg| {
187         quote! { &(##arg), }
188     }.token_trees).collect::<Vec<_>>();
189
190     let expanded = quote! {
191         { { (##arg_tts); } }
192     };
193     Ok(expanded)
194 }
195
196 fn file_expand(
197     _db: &dyn AstDatabase,
198     _id: LazyMacroId,
199     _tt: &tt::Subtree,
200 ) -> Result<tt::Subtree, mbe::ExpandError> {
201     // FIXME: RA purposefully lacks knowledge of absolute file names
202     // so just return "".
203     let file_name = "";
204
205     let expanded = quote! {
206         #file_name
207     };
208
209     Ok(expanded)
210 }
211
212 fn compile_error_expand(
213     _db: &dyn AstDatabase,
214     _id: LazyMacroId,
215     tt: &tt::Subtree,
216 ) -> Result<tt::Subtree, mbe::ExpandError> {
217     if tt.count() == 1 {
218         if let tt::TokenTree::Leaf(tt::Leaf::Literal(it)) = &tt.token_trees[0] {
219             let s = it.text.as_str();
220             if s.contains('"') {
221                 return Ok(quote! { loop { #it }});
222             }
223         };
224     }
225
226     Err(mbe::ExpandError::BindingError("Must be a string".into()))
227 }
228
229 fn format_args_expand(
230     _db: &dyn AstDatabase,
231     _id: LazyMacroId,
232     tt: &tt::Subtree,
233 ) -> Result<tt::Subtree, mbe::ExpandError> {
234     // We expand `format_args!("", a1, a2)` to
235     // ```
236     // std::fmt::Arguments::new_v1(&[], &[
237     //   std::fmt::ArgumentV1::new(&arg1,std::fmt::Display::fmt),
238     //   std::fmt::ArgumentV1::new(&arg2,std::fmt::Display::fmt),
239     // ])
240     // ```,
241     // which is still not really correct, but close enough for now
242     let mut args = Vec::new();
243     let mut current = Vec::new();
244     for tt in tt.token_trees.iter().cloned() {
245         match tt {
246             tt::TokenTree::Leaf(tt::Leaf::Punct(p)) if p.char == ',' => {
247                 args.push(current);
248                 current = Vec::new();
249             }
250             _ => {
251                 current.push(tt);
252             }
253         }
254     }
255     if !current.is_empty() {
256         args.push(current);
257     }
258     if args.is_empty() {
259         return Err(mbe::ExpandError::NoMatchingRule);
260     }
261     let _format_string = args.remove(0);
262     let arg_tts = args.into_iter().flat_map(|arg| {
263         quote! { std::fmt::ArgumentV1::new(&(##arg), std::fmt::Display::fmt), }
264     }.token_trees).collect::<Vec<_>>();
265     let expanded = quote! {
266         std::fmt::Arguments::new_v1(&[], &[##arg_tts])
267     };
268     Ok(expanded)
269 }
270
271 fn unquote_str(lit: &tt::Literal) -> Option<String> {
272     let lit = ast::make::tokens::literal(&lit.to_string());
273     let token = ast::String::cast(lit)?;
274     token.value()
275 }
276
277 fn concat_expand(
278     _db: &dyn AstDatabase,
279     _arg_id: EagerMacroId,
280     tt: &tt::Subtree,
281 ) -> Result<(tt::Subtree, FragmentKind), mbe::ExpandError> {
282     let mut text = String::new();
283     for (i, t) in tt.token_trees.iter().enumerate() {
284         match t {
285             tt::TokenTree::Leaf(tt::Leaf::Literal(it)) if i % 2 == 0 => {
286                 text += &unquote_str(&it).ok_or_else(|| mbe::ExpandError::ConversionError)?;
287             }
288             tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (),
289             _ => return Err(mbe::ExpandError::UnexpectedToken),
290         }
291     }
292
293     Ok((quote!(#text), FragmentKind::Expr))
294 }
295
296 fn relative_file(db: &dyn AstDatabase, call_id: MacroCallId, path: &str) -> Option<FileId> {
297     let call_site = call_id.as_file().original_file(db);
298     let res = db.resolve_path(call_site, path)?;
299     // Prevent include itself
300     if res == call_site {
301         None
302     } else {
303         Some(res)
304     }
305 }
306
307 fn parse_string(tt: &tt::Subtree) -> Result<String, mbe::ExpandError> {
308     tt.token_trees
309         .get(0)
310         .and_then(|tt| match tt {
311             tt::TokenTree::Leaf(tt::Leaf::Literal(it)) => unquote_str(&it),
312             _ => None,
313         })
314         .ok_or_else(|| mbe::ExpandError::ConversionError)
315 }
316
317 fn include_expand(
318     db: &dyn AstDatabase,
319     arg_id: EagerMacroId,
320     tt: &tt::Subtree,
321 ) -> Result<(tt::Subtree, FragmentKind), mbe::ExpandError> {
322     let path = parse_string(tt)?;
323     let file_id =
324         relative_file(db, arg_id.into(), &path).ok_or_else(|| mbe::ExpandError::ConversionError)?;
325
326     // FIXME:
327     // Handle include as expression
328     let res = parse_to_token_tree(&db.file_text(file_id))
329         .ok_or_else(|| mbe::ExpandError::ConversionError)?
330         .0;
331
332     Ok((res, FragmentKind::Items))
333 }
334
335 fn get_env_inner(db: &dyn AstDatabase, arg_id: EagerMacroId, key: &str) -> Option<String> {
336     let call_id: MacroCallId = arg_id.into();
337     let original_file = call_id.as_file().original_file(db);
338
339     let krate = *db.relevant_crates(original_file).get(0)?;
340     db.crate_graph()[krate].env.get(key)
341 }
342
343 fn env_expand(
344     db: &dyn AstDatabase,
345     arg_id: EagerMacroId,
346     tt: &tt::Subtree,
347 ) -> Result<(tt::Subtree, FragmentKind), mbe::ExpandError> {
348     let key = parse_string(tt)?;
349
350     // FIXME:
351     // If the environment variable is not defined int rustc, then a compilation error will be emitted.
352     // We might do the same if we fully support all other stuffs.
353     // But for now on, we should return some dummy string for better type infer purpose.
354     // However, we cannot use an empty string here, because for
355     // `include!(concat!(env!("OUT_DIR"), "/foo.rs"))` will become
356     // `include!("foo.rs"), which might go to infinite loop
357     let s = get_env_inner(db, arg_id, &key).unwrap_or_else(|| "__RA_UNIMPLEMENTED__".to_string());
358     let expanded = quote! { #s };
359
360     Ok((expanded, FragmentKind::Expr))
361 }
362
363 fn option_env_expand(
364     db: &dyn AstDatabase,
365     arg_id: EagerMacroId,
366     tt: &tt::Subtree,
367 ) -> Result<(tt::Subtree, FragmentKind), mbe::ExpandError> {
368     let key = parse_string(tt)?;
369     let expanded = match get_env_inner(db, arg_id, &key) {
370         None => quote! { std::option::Option::None::<&str> },
371         Some(s) => quote! { std::option::Some(#s) },
372     };
373
374     Ok((expanded, FragmentKind::Expr))
375 }
376
377 #[cfg(test)]
378 mod tests {
379     use super::*;
380     use crate::{
381         name::AsName, test_db::TestDB, AstNode, EagerCallLoc, MacroCallId, MacroCallKind,
382         MacroCallLoc,
383     };
384     use ra_db::{fixture::WithFixture, SourceDatabase};
385     use ra_syntax::ast::NameOwner;
386     use std::sync::Arc;
387
388     fn expand_builtin_macro(ra_fixture: &str) -> String {
389         let (db, file_id) = TestDB::with_single_file(&ra_fixture);
390         let parsed = db.parse(file_id);
391         let macro_calls: Vec<_> =
392             parsed.syntax_node().descendants().filter_map(ast::MacroCall::cast).collect();
393
394         let ast_id_map = db.ast_id_map(file_id.into());
395
396         let expander = find_by_name(&macro_calls[0].name().unwrap().as_name()).unwrap();
397
398         let file_id = match expander {
399             Either::Left(expander) => {
400                 // the first one should be a macro_rules
401                 let def = MacroDefId {
402                     krate: Some(CrateId(0)),
403                     ast_id: Some(AstId::new(file_id.into(), ast_id_map.ast_id(&macro_calls[0]))),
404                     kind: MacroDefKind::BuiltIn(expander),
405                     local_inner: false,
406                 };
407
408                 let loc = MacroCallLoc {
409                     def,
410                     kind: MacroCallKind::FnLike(AstId::new(
411                         file_id.into(),
412                         ast_id_map.ast_id(&macro_calls[1]),
413                     )),
414                 };
415
416                 let id: MacroCallId = db.intern_macro(loc).into();
417                 id.as_file()
418             }
419             Either::Right(expander) => {
420                 // the first one should be a macro_rules
421                 let def = MacroDefId {
422                     krate: Some(CrateId(0)),
423                     ast_id: Some(AstId::new(file_id.into(), ast_id_map.ast_id(&macro_calls[0]))),
424                     kind: MacroDefKind::BuiltInEager(expander),
425                     local_inner: false,
426                 };
427
428                 let args = macro_calls[1].token_tree().unwrap();
429                 let parsed_args = mbe::ast_to_token_tree(&args).unwrap().0;
430
431                 let arg_id = db.intern_eager_expansion({
432                     EagerCallLoc {
433                         def,
434                         fragment: FragmentKind::Expr,
435                         subtree: Arc::new(parsed_args.clone()),
436                         file_id: file_id.into(),
437                     }
438                 });
439
440                 let (subtree, fragment) = expander.expand(&db, arg_id, &parsed_args).unwrap();
441                 let eager = EagerCallLoc {
442                     def,
443                     fragment,
444                     subtree: Arc::new(subtree),
445                     file_id: file_id.into(),
446                 };
447
448                 let id: MacroCallId = db.intern_eager_expansion(eager).into();
449                 id.as_file()
450             }
451         };
452
453         db.parse_or_expand(file_id).unwrap().to_string()
454     }
455
456     #[test]
457     fn test_column_expand() {
458         let expanded = expand_builtin_macro(
459             r#"
460             #[rustc_builtin_macro]
461             macro_rules! column {() => {}}
462             column!()
463             "#,
464         );
465
466         assert_eq!(expanded, "0");
467     }
468
469     #[test]
470     fn test_line_expand() {
471         let expanded = expand_builtin_macro(
472             r#"
473             #[rustc_builtin_macro]
474             macro_rules! line {() => {}}
475             line!()
476             "#,
477         );
478
479         assert_eq!(expanded, "0");
480     }
481
482     #[test]
483     fn test_stringify_expand() {
484         let expanded = expand_builtin_macro(
485             r#"
486             #[rustc_builtin_macro]
487             macro_rules! stringify {() => {}}
488             stringify!(a b c)
489             "#,
490         );
491
492         assert_eq!(expanded, "\"a b c\"");
493     }
494
495     #[test]
496     fn test_env_expand() {
497         let expanded = expand_builtin_macro(
498             r#"
499             #[rustc_builtin_macro]
500             macro_rules! env {() => {}}
501             env!("TEST_ENV_VAR")
502             "#,
503         );
504
505         assert_eq!(expanded, "\"__RA_UNIMPLEMENTED__\"");
506     }
507
508     #[test]
509     fn test_option_env_expand() {
510         let expanded = expand_builtin_macro(
511             r#"
512             #[rustc_builtin_macro]
513             macro_rules! option_env {() => {}}
514             option_env!("TEST_ENV_VAR")
515             "#,
516         );
517
518         assert_eq!(expanded, "std::option::Option::None:: < &str>");
519     }
520
521     #[test]
522     fn test_file_expand() {
523         let expanded = expand_builtin_macro(
524             r#"
525             #[rustc_builtin_macro]
526             macro_rules! file {() => {}}
527             file!()
528             "#,
529         );
530
531         assert_eq!(expanded, "\"\"");
532     }
533
534     #[test]
535     fn test_assert_expand() {
536         let expanded = expand_builtin_macro(
537             r#"
538             #[rustc_builtin_macro]
539             macro_rules! assert {
540                 ($cond:expr) => ({ /* compiler built-in */ });
541                 ($cond:expr, $($args:tt)*) => ({ /* compiler built-in */ })
542             }
543             assert!(true, "{} {:?}", arg1(a, b, c), arg2);
544             "#,
545         );
546
547         assert_eq!(expanded, "{{(&(true), &(\"{} {:?}\"), &(arg1(a,b,c)), &(arg2),);}}");
548     }
549
550     #[test]
551     fn test_compile_error_expand() {
552         let expanded = expand_builtin_macro(
553             r#"
554             #[rustc_builtin_macro]
555             macro_rules! compile_error {
556                 ($msg:expr) => ({ /* compiler built-in */ });
557                 ($msg:expr,) => ({ /* compiler built-in */ })
558             }
559             compile_error!("error!");
560             "#,
561         );
562
563         assert_eq!(expanded, r#"loop{"error!"}"#);
564     }
565
566     #[test]
567     fn test_format_args_expand() {
568         let expanded = expand_builtin_macro(
569             r#"
570             #[rustc_builtin_macro]
571             macro_rules! format_args {
572                 ($fmt:expr) => ({ /* compiler built-in */ });
573                 ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ })
574             }
575             format_args!("{} {:?}", arg1(a, b, c), arg2);
576             "#,
577         );
578
579         assert_eq!(
580             expanded,
581             r#"std::fmt::Arguments::new_v1(&[], &[std::fmt::ArgumentV1::new(&(arg1(a,b,c)),std::fmt::Display::fmt),std::fmt::ArgumentV1::new(&(arg2),std::fmt::Display::fmt),])"#
582         );
583     }
584 }