diff options
Diffstat (limited to 'crates/hir_expand/src')
-rw-r--r-- | crates/hir_expand/src/builtin_macro.rs | 53 | ||||
-rw-r--r-- | crates/hir_expand/src/lib.rs | 70 | ||||
-rw-r--r-- | crates/hir_expand/src/test_db.rs | 6 |
3 files changed, 113 insertions, 16 deletions
diff --git a/crates/hir_expand/src/builtin_macro.rs b/crates/hir_expand/src/builtin_macro.rs index 44a5556b6..477192a09 100644 --- a/crates/hir_expand/src/builtin_macro.rs +++ b/crates/hir_expand/src/builtin_macro.rs | |||
@@ -4,7 +4,7 @@ use crate::{ | |||
4 | MacroDefId, MacroDefKind, TextSize, | 4 | MacroDefId, MacroDefKind, TextSize, |
5 | }; | 5 | }; |
6 | 6 | ||
7 | use base_db::FileId; | 7 | use base_db::{AnchoredPath, FileId}; |
8 | use either::Either; | 8 | use either::Either; |
9 | use mbe::{parse_to_token_tree, ExpandResult}; | 9 | use mbe::{parse_to_token_tree, ExpandResult}; |
10 | use parser::FragmentKind; | 10 | use parser::FragmentKind; |
@@ -245,6 +245,12 @@ fn format_args_expand( | |||
245 | if args.is_empty() { | 245 | if args.is_empty() { |
246 | return ExpandResult::only_err(mbe::ExpandError::NoMatchingRule); | 246 | return ExpandResult::only_err(mbe::ExpandError::NoMatchingRule); |
247 | } | 247 | } |
248 | for arg in &mut args { | ||
249 | // Remove `key =`. | ||
250 | if matches!(arg.get(1), Some(tt::TokenTree::Leaf(tt::Leaf::Punct(p))) if p.char == '=') { | ||
251 | arg.drain(..2); | ||
252 | } | ||
253 | } | ||
248 | let _format_string = args.remove(0); | 254 | let _format_string = args.remove(0); |
249 | let arg_tts = args.into_iter().flat_map(|arg| { | 255 | let arg_tts = args.into_iter().flat_map(|arg| { |
250 | quote! { std::fmt::ArgumentV1::new(&(##arg), std::fmt::Display::fmt), } | 256 | quote! { std::fmt::ArgumentV1::new(&(##arg), std::fmt::Display::fmt), } |
@@ -287,23 +293,34 @@ fn concat_expand( | |||
287 | _arg_id: EagerMacroId, | 293 | _arg_id: EagerMacroId, |
288 | tt: &tt::Subtree, | 294 | tt: &tt::Subtree, |
289 | ) -> ExpandResult<Option<(tt::Subtree, FragmentKind)>> { | 295 | ) -> ExpandResult<Option<(tt::Subtree, FragmentKind)>> { |
296 | let mut err = None; | ||
290 | let mut text = String::new(); | 297 | let mut text = String::new(); |
291 | for (i, t) in tt.token_trees.iter().enumerate() { | 298 | for (i, t) in tt.token_trees.iter().enumerate() { |
292 | match t { | 299 | match t { |
293 | tt::TokenTree::Leaf(tt::Leaf::Literal(it)) if i % 2 == 0 => { | 300 | tt::TokenTree::Leaf(tt::Leaf::Literal(it)) if i % 2 == 0 => { |
294 | text += &match unquote_str(&it) { | 301 | // concat works with string and char literals, so remove any quotes. |
295 | Some(s) => s, | 302 | // It also works with integer, float and boolean literals, so just use the rest |
296 | None => { | 303 | // as-is. |
297 | return ExpandResult::only_err(mbe::ExpandError::ConversionError); | 304 | |
298 | } | 305 | text += it |
299 | }; | 306 | .text |
307 | .trim_start_matches(|c| match c { | ||
308 | 'r' | '#' | '\'' | '"' => true, | ||
309 | _ => false, | ||
310 | }) | ||
311 | .trim_end_matches(|c| match c { | ||
312 | '#' | '\'' | '"' => true, | ||
313 | _ => false, | ||
314 | }); | ||
300 | } | 315 | } |
301 | tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (), | 316 | tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (), |
302 | _ => return ExpandResult::only_err(mbe::ExpandError::UnexpectedToken), | 317 | _ => { |
318 | err.get_or_insert(mbe::ExpandError::UnexpectedToken); | ||
319 | } | ||
303 | } | 320 | } |
304 | } | 321 | } |
305 | 322 | ||
306 | ExpandResult::ok(Some((quote!(#text), FragmentKind::Expr))) | 323 | ExpandResult { value: Some((quote!(#text), FragmentKind::Expr)), err } |
307 | } | 324 | } |
308 | 325 | ||
309 | fn relative_file( | 326 | fn relative_file( |
@@ -313,7 +330,8 @@ fn relative_file( | |||
313 | allow_recursion: bool, | 330 | allow_recursion: bool, |
314 | ) -> Option<FileId> { | 331 | ) -> Option<FileId> { |
315 | let call_site = call_id.as_file().original_file(db); | 332 | let call_site = call_id.as_file().original_file(db); |
316 | let res = db.resolve_path(call_site, path)?; | 333 | let path = AnchoredPath { anchor: call_site, path }; |
334 | let res = db.resolve_path(path)?; | ||
317 | // Prevent include itself | 335 | // Prevent include itself |
318 | if res == call_site && !allow_recursion { | 336 | if res == call_site && !allow_recursion { |
319 | None | 337 | None |
@@ -686,4 +704,19 @@ mod tests { | |||
686 | 704 | ||
687 | assert_eq!(expanded, r#"b"""#); | 705 | assert_eq!(expanded, r#"b"""#); |
688 | } | 706 | } |
707 | |||
708 | #[test] | ||
709 | fn test_concat_expand() { | ||
710 | let expanded = expand_builtin_macro( | ||
711 | r##" | ||
712 | #[rustc_builtin_macro] | ||
713 | macro_rules! concat {} | ||
714 | concat!("foo", 0, r#"bar"#); | ||
715 | "##, | ||
716 | ); | ||
717 | |||
718 | assert_eq!(expanded, r#""foo0bar""#); | ||
719 | |||
720 | // FIXME: `true`/`false` literals don't work. | ||
721 | } | ||
689 | } | 722 | } |
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, |
diff --git a/crates/hir_expand/src/test_db.rs b/crates/hir_expand/src/test_db.rs index fca501e1f..7168a9462 100644 --- a/crates/hir_expand/src/test_db.rs +++ b/crates/hir_expand/src/test_db.rs | |||
@@ -5,7 +5,7 @@ use std::{ | |||
5 | sync::{Arc, Mutex}, | 5 | sync::{Arc, Mutex}, |
6 | }; | 6 | }; |
7 | 7 | ||
8 | use base_db::{salsa, CrateId, FileId, FileLoader, FileLoaderDelegate}; | 8 | use base_db::{salsa, AnchoredPath, CrateId, FileId, FileLoader, FileLoaderDelegate}; |
9 | use rustc_hash::FxHashSet; | 9 | use rustc_hash::FxHashSet; |
10 | 10 | ||
11 | #[salsa::database( | 11 | #[salsa::database( |
@@ -40,8 +40,8 @@ impl FileLoader for TestDB { | |||
40 | fn file_text(&self, file_id: FileId) -> Arc<String> { | 40 | fn file_text(&self, file_id: FileId) -> Arc<String> { |
41 | FileLoaderDelegate(self).file_text(file_id) | 41 | FileLoaderDelegate(self).file_text(file_id) |
42 | } | 42 | } |
43 | fn resolve_path(&self, anchor: FileId, path: &str) -> Option<FileId> { | 43 | fn resolve_path(&self, path: AnchoredPath) -> Option<FileId> { |
44 | FileLoaderDelegate(self).resolve_path(anchor, path) | 44 | FileLoaderDelegate(self).resolve_path(path) |
45 | } | 45 | } |
46 | fn relevant_crates(&self, file_id: FileId) -> Arc<FxHashSet<CrateId>> { | 46 | fn relevant_crates(&self, file_id: FileId) -> Arc<FxHashSet<CrateId>> { |
47 | FileLoaderDelegate(self).relevant_crates(file_id) | 47 | FileLoaderDelegate(self).relevant_crates(file_id) |