aboutsummaryrefslogtreecommitdiff
path: root/crates/hir_expand/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/hir_expand/src')
-rw-r--r--crates/hir_expand/src/builtin_macro.rs53
-rw-r--r--crates/hir_expand/src/lib.rs70
-rw-r--r--crates/hir_expand/src/test_db.rs6
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
7use base_db::FileId; 7use base_db::{AnchoredPath, FileId};
8use either::Either; 8use either::Either;
9use mbe::{parse_to_token_tree, ExpandResult}; 9use mbe::{parse_to_token_tree, ExpandResult};
10use parser::FragmentKind; 10use 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
309fn relative_file( 326fn 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};
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,
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
8use base_db::{salsa, CrateId, FileId, FileLoader, FileLoaderDelegate}; 8use base_db::{salsa, AnchoredPath, CrateId, FileId, FileLoader, FileLoaderDelegate};
9use rustc_hash::FxHashSet; 9use 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)