diff options
Diffstat (limited to 'crates/hir_expand')
-rw-r--r-- | crates/hir_expand/src/builtin_macro.rs | 42 | ||||
-rw-r--r-- | crates/hir_expand/src/lib.rs | 70 |
2 files changed, 101 insertions, 11 deletions
diff --git a/crates/hir_expand/src/builtin_macro.rs b/crates/hir_expand/src/builtin_macro.rs index 44a5556b6..79b970850 100644 --- a/crates/hir_expand/src/builtin_macro.rs +++ b/crates/hir_expand/src/builtin_macro.rs | |||
@@ -287,23 +287,34 @@ fn concat_expand( | |||
287 | _arg_id: EagerMacroId, | 287 | _arg_id: EagerMacroId, |
288 | tt: &tt::Subtree, | 288 | tt: &tt::Subtree, |
289 | ) -> ExpandResult<Option<(tt::Subtree, FragmentKind)>> { | 289 | ) -> ExpandResult<Option<(tt::Subtree, FragmentKind)>> { |
290 | let mut err = None; | ||
290 | let mut text = String::new(); | 291 | let mut text = String::new(); |
291 | for (i, t) in tt.token_trees.iter().enumerate() { | 292 | for (i, t) in tt.token_trees.iter().enumerate() { |
292 | match t { | 293 | match t { |
293 | tt::TokenTree::Leaf(tt::Leaf::Literal(it)) if i % 2 == 0 => { | 294 | tt::TokenTree::Leaf(tt::Leaf::Literal(it)) if i % 2 == 0 => { |
294 | text += &match unquote_str(&it) { | 295 | // concat works with string and char literals, so remove any quotes. |
295 | Some(s) => s, | 296 | // It also works with integer, float and boolean literals, so just use the rest |
296 | None => { | 297 | // as-is. |
297 | return ExpandResult::only_err(mbe::ExpandError::ConversionError); | 298 | |
298 | } | 299 | text += it |
299 | }; | 300 | .text |
301 | .trim_start_matches(|c| match c { | ||
302 | 'r' | '#' | '\'' | '"' => true, | ||
303 | _ => false, | ||
304 | }) | ||
305 | .trim_end_matches(|c| match c { | ||
306 | '#' | '\'' | '"' => true, | ||
307 | _ => false, | ||
308 | }); | ||
300 | } | 309 | } |
301 | tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (), | 310 | tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (), |
302 | _ => return ExpandResult::only_err(mbe::ExpandError::UnexpectedToken), | 311 | _ => { |
312 | err.get_or_insert(mbe::ExpandError::UnexpectedToken); | ||
313 | } | ||
303 | } | 314 | } |
304 | } | 315 | } |
305 | 316 | ||
306 | ExpandResult::ok(Some((quote!(#text), FragmentKind::Expr))) | 317 | ExpandResult { value: Some((quote!(#text), FragmentKind::Expr)), err } |
307 | } | 318 | } |
308 | 319 | ||
309 | fn relative_file( | 320 | fn relative_file( |
@@ -686,4 +697,19 @@ mod tests { | |||
686 | 697 | ||
687 | assert_eq!(expanded, r#"b"""#); | 698 | assert_eq!(expanded, r#"b"""#); |
688 | } | 699 | } |
700 | |||
701 | #[test] | ||
702 | fn test_concat_expand() { | ||
703 | let expanded = expand_builtin_macro( | ||
704 | r##" | ||
705 | #[rustc_builtin_macro] | ||
706 | macro_rules! concat {} | ||
707 | concat!("foo", 0, r#"bar"#); | ||
708 | "##, | ||
709 | ); | ||
710 | |||
711 | assert_eq!(expanded, r#""foo0bar""#); | ||
712 | |||
713 | // FIXME: `true`/`false` literals don't work. | ||
714 | } | ||
689 | } | 715 | } |
diff --git a/crates/hir_expand/src/lib.rs b/crates/hir_expand/src/lib.rs index 2633fd8f7..1a9428514 100644 --- a/crates/hir_expand/src/lib.rs +++ b/crates/hir_expand/src/lib.rs | |||
@@ -20,11 +20,11 @@ pub use mbe::{ExpandError, ExpandResult}; | |||
20 | use std::hash::Hash; | 20 | use std::hash::Hash; |
21 | use std::sync::Arc; | 21 | use std::sync::Arc; |
22 | 22 | ||
23 | use base_db::{impl_intern_key, salsa, CrateId, FileId}; | 23 | use base_db::{impl_intern_key, salsa, CrateId, FileId, FileRange}; |
24 | use syntax::{ | 24 | use syntax::{ |
25 | algo, | 25 | algo::{self, skip_trivia_token}, |
26 | ast::{self, AstNode}, | 26 | ast::{self, AstNode}, |
27 | SyntaxNode, SyntaxToken, TextSize, | 27 | Direction, SyntaxNode, SyntaxToken, TextRange, TextSize, |
28 | }; | 28 | }; |
29 | 29 | ||
30 | use crate::ast_id_map::FileAstId; | 30 | use crate::ast_id_map::FileAstId; |
@@ -445,6 +445,70 @@ impl InFile<SyntaxNode> { | |||
445 | } | 445 | } |
446 | } | 446 | } |
447 | 447 | ||
448 | impl<'a> InFile<&'a SyntaxNode> { | ||
449 | pub fn original_file_range(self, db: &dyn db::AstDatabase) -> FileRange { | ||
450 | if let Some(range) = original_range_opt(db, self) { | ||
451 | let original_file = range.file_id.original_file(db); | ||
452 | if range.file_id == original_file.into() { | ||
453 | return FileRange { file_id: original_file, range: range.value }; | ||
454 | } | ||
455 | |||
456 | log::error!("Fail to mapping up more for {:?}", range); | ||
457 | return FileRange { file_id: range.file_id.original_file(db), range: range.value }; | ||
458 | } | ||
459 | |||
460 | // Fall back to whole macro call. | ||
461 | let mut node = self.cloned(); | ||
462 | while let Some(call_node) = node.file_id.call_node(db) { | ||
463 | node = call_node; | ||
464 | } | ||
465 | |||
466 | let orig_file = node.file_id.original_file(db); | ||
467 | assert_eq!(node.file_id, orig_file.into()); | ||
468 | FileRange { file_id: orig_file, range: node.value.text_range() } | ||
469 | } | ||
470 | } | ||
471 | |||
472 | fn original_range_opt( | ||
473 | db: &dyn db::AstDatabase, | ||
474 | node: InFile<&SyntaxNode>, | ||
475 | ) -> Option<InFile<TextRange>> { | ||
476 | let expansion = node.file_id.expansion_info(db)?; | ||
477 | |||
478 | // the input node has only one token ? | ||
479 | let single = skip_trivia_token(node.value.first_token()?, Direction::Next)? | ||
480 | == skip_trivia_token(node.value.last_token()?, Direction::Prev)?; | ||
481 | |||
482 | Some(node.value.descendants().find_map(|it| { | ||
483 | let first = skip_trivia_token(it.first_token()?, Direction::Next)?; | ||
484 | let first = ascend_call_token(db, &expansion, node.with_value(first))?; | ||
485 | |||
486 | let last = skip_trivia_token(it.last_token()?, Direction::Prev)?; | ||
487 | let last = ascend_call_token(db, &expansion, node.with_value(last))?; | ||
488 | |||
489 | if (!single && first == last) || (first.file_id != last.file_id) { | ||
490 | return None; | ||
491 | } | ||
492 | |||
493 | Some(first.with_value(first.value.text_range().cover(last.value.text_range()))) | ||
494 | })?) | ||
495 | } | ||
496 | |||
497 | fn ascend_call_token( | ||
498 | db: &dyn db::AstDatabase, | ||
499 | expansion: &ExpansionInfo, | ||
500 | token: InFile<SyntaxToken>, | ||
501 | ) -> Option<InFile<SyntaxToken>> { | ||
502 | let (mapped, origin) = expansion.map_token_up(token.as_ref())?; | ||
503 | if origin != Origin::Call { | ||
504 | return None; | ||
505 | } | ||
506 | if let Some(info) = mapped.file_id.expansion_info(db) { | ||
507 | return ascend_call_token(db, &info, mapped); | ||
508 | } | ||
509 | Some(mapped) | ||
510 | } | ||
511 | |||
448 | impl InFile<SyntaxToken> { | 512 | impl InFile<SyntaxToken> { |
449 | pub fn ancestors_with_macros( | 513 | pub fn ancestors_with_macros( |
450 | self, | 514 | self, |