aboutsummaryrefslogtreecommitdiff
path: root/crates/hir_expand
diff options
context:
space:
mode:
Diffstat (limited to 'crates/hir_expand')
-rw-r--r--crates/hir_expand/src/builtin_macro.rs42
-rw-r--r--crates/hir_expand/src/lib.rs70
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
309fn relative_file( 320fn 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};
20use std::hash::Hash; 20use std::hash::Hash;
21use std::sync::Arc; 21use std::sync::Arc;
22 22
23use base_db::{impl_intern_key, salsa, CrateId, FileId}; 23use base_db::{impl_intern_key, salsa, CrateId, FileId, FileRange};
24use syntax::{ 24use 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
30use crate::ast_id_map::FileAstId; 30use crate::ast_id_map::FileAstId;
@@ -445,6 +445,70 @@ impl InFile<SyntaxNode> {
445 } 445 }
446} 446}
447 447
448impl<'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
472fn 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
497fn 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
448impl InFile<SyntaxToken> { 512impl InFile<SyntaxToken> {
449 pub fn ancestors_with_macros( 513 pub fn ancestors_with_macros(
450 self, 514 self,