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