Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/hir-expand/src/builtin_fn_macro.rs')
-rw-r--r--crates/hir-expand/src/builtin_fn_macro.rs220
1 files changed, 174 insertions, 46 deletions
diff --git a/crates/hir-expand/src/builtin_fn_macro.rs b/crates/hir-expand/src/builtin_fn_macro.rs
index a9c5e1488a..c7643bd0a1 100644
--- a/crates/hir-expand/src/builtin_fn_macro.rs
+++ b/crates/hir-expand/src/builtin_fn_macro.rs
@@ -1,9 +1,13 @@
//! Builtin macro
+use std::mem;
+
+use ::tt::Ident;
use base_db::{AnchoredPath, Edition, FileId};
use cfg::CfgExpr;
use either::Either;
-use mbe::{parse_exprs_with_sep, parse_to_token_tree};
+use mbe::{parse_exprs_with_sep, parse_to_token_tree, TokenMap};
+use rustc_hash::FxHashMap;
use syntax::{
ast::{self, AstToken},
SmolStr,
@@ -67,7 +71,7 @@ macro_rules! register_builtin {
pub struct ExpandedEager {
pub(crate) subtree: tt::Subtree,
/// The included file ID of the include macro.
- pub(crate) included_file: Option<FileId>,
+ pub(crate) included_file: Option<(FileId, TokenMap)>,
}
impl ExpandedEager {
@@ -90,11 +94,6 @@ register_builtin! {
(module_path, ModulePath) => module_path_expand,
(assert, Assert) => assert_expand,
(stringify, Stringify) => stringify_expand,
- (format_args, FormatArgs) => format_args_expand,
- (const_format_args, ConstFormatArgs) => format_args_expand,
- // format_args_nl only differs in that it adds a newline in the end,
- // so we use the same stub expansion for now
- (format_args_nl, FormatArgsNl) => format_args_expand,
(llvm_asm, LlvmAsm) => asm_expand,
(asm, Asm) => asm_expand,
(global_asm, GlobalAsm) => global_asm_expand,
@@ -106,6 +105,9 @@ register_builtin! {
(trace_macros, TraceMacros) => trace_macros_expand,
EAGER:
+ (format_args, FormatArgs) => format_args_expand,
+ (const_format_args, ConstFormatArgs) => format_args_expand,
+ (format_args_nl, FormatArgsNl) => format_args_nl_expand,
(compile_error, CompileError) => compile_error_expand,
(concat, Concat) => concat_expand,
(concat_idents, ConcatIdents) => concat_idents_expand,
@@ -135,9 +137,8 @@ fn line_expand(
_tt: &tt::Subtree,
) -> ExpandResult<tt::Subtree> {
// dummy implementation for type-checking purposes
- let line_num = 0;
let expanded = quote! {
- #line_num
+ 0 as u32
};
ExpandResult::ok(expanded)
@@ -179,9 +180,8 @@ fn column_expand(
_tt: &tt::Subtree,
) -> ExpandResult<tt::Subtree> {
// dummy implementation for type-checking purposes
- let col_num = 0;
let expanded = quote! {
- #col_num
+ 0 as u32
};
ExpandResult::ok(expanded)
@@ -234,45 +234,173 @@ fn file_expand(
}
fn format_args_expand(
+ db: &dyn ExpandDatabase,
+ id: MacroCallId,
+ tt: &tt::Subtree,
+) -> ExpandResult<ExpandedEager> {
+ format_args_expand_general(db, id, tt, "")
+ .map(|x| ExpandedEager { subtree: x, included_file: None })
+}
+
+fn format_args_nl_expand(
+ db: &dyn ExpandDatabase,
+ id: MacroCallId,
+ tt: &tt::Subtree,
+) -> ExpandResult<ExpandedEager> {
+ format_args_expand_general(db, id, tt, "\\n")
+ .map(|x| ExpandedEager { subtree: x, included_file: None })
+}
+
+fn format_args_expand_general(
_db: &dyn ExpandDatabase,
_id: MacroCallId,
tt: &tt::Subtree,
+ end_string: &str,
) -> ExpandResult<tt::Subtree> {
- // We expand `format_args!("", a1, a2)` to
- // ```
- // $crate::fmt::Arguments::new_v1(&[], &[
- // $crate::fmt::Argument::new(&arg1,$crate::fmt::Display::fmt),
- // $crate::fmt::Argument::new(&arg2,$crate::fmt::Display::fmt),
- // ])
- // ```,
- // which is still not really correct, but close enough for now
- let mut args = parse_exprs_with_sep(tt, ',');
+ let args = parse_exprs_with_sep(tt, ',');
+
+ let expand_error =
+ ExpandResult::new(tt::Subtree::empty(), mbe::ExpandError::NoMatchingRule.into());
if args.is_empty() {
- return ExpandResult::with_err(
- tt::Subtree::empty(),
- mbe::ExpandError::NoMatchingRule.into(),
- );
+ return expand_error;
}
- for arg in &mut args {
+ let mut key_args = FxHashMap::default();
+ let mut args = args.into_iter().filter_map(|mut arg| {
// Remove `key =`.
if matches!(arg.token_trees.get(1), Some(tt::TokenTree::Leaf(tt::Leaf::Punct(p))) if p.char == '=')
{
// but not with `==`
- if !matches!(arg.token_trees.get(2), Some(tt::TokenTree::Leaf(tt::Leaf::Punct(p))) if p.char == '=' )
+ if !matches!(arg.token_trees.get(2), Some(tt::TokenTree::Leaf(tt::Leaf::Punct(p))) if p.char == '=')
{
- arg.token_trees.drain(..2);
+ let key = arg.token_trees.drain(..2).next().unwrap();
+ key_args.insert(key.to_string(), arg);
+ return None;
}
}
+ Some(arg)
+ }).collect::<Vec<_>>().into_iter();
+ // ^^^^^^^ we need this collect, to enforce the side effect of the filter_map closure (building the `key_args`)
+ let format_subtree = args.next().unwrap();
+ let format_string = (|| {
+ let token_tree = format_subtree.token_trees.get(0)?;
+ match token_tree {
+ tt::TokenTree::Leaf(l) => match l {
+ tt::Leaf::Literal(l) => {
+ if let Some(mut text) = l.text.strip_prefix('r') {
+ let mut raw_sharps = String::new();
+ while let Some(t) = text.strip_prefix('#') {
+ text = t;
+ raw_sharps.push('#');
+ }
+ text =
+ text.strip_suffix(&raw_sharps)?.strip_prefix('"')?.strip_suffix('"')?;
+ Some((text, l.span, Some(raw_sharps)))
+ } else {
+ let text = l.text.strip_prefix('"')?.strip_suffix('"')?;
+ let span = l.span;
+ Some((text, span, None))
+ }
+ }
+ _ => None,
+ },
+ tt::TokenTree::Subtree(_) => None,
+ }
+ })();
+ let Some((format_string, _format_string_span, raw_sharps)) = format_string else {
+ return expand_error;
+ };
+ let mut format_iter = format_string.chars().peekable();
+ let mut parts = vec![];
+ let mut last_part = String::new();
+ let mut arg_tts = vec![];
+ let mut err = None;
+ while let Some(c) = format_iter.next() {
+ // Parsing the format string. See https://doc.rust-lang.org/std/fmt/index.html#syntax for the grammar and more info
+ match c {
+ '{' => {
+ if format_iter.peek() == Some(&'{') {
+ format_iter.next();
+ last_part.push('{');
+ continue;
+ }
+ let mut argument = String::new();
+ while ![Some(&'}'), Some(&':')].contains(&format_iter.peek()) {
+ argument.push(match format_iter.next() {
+ Some(c) => c,
+ None => return expand_error,
+ });
+ }
+ let format_spec = match format_iter.next().unwrap() {
+ '}' => "".to_owned(),
+ ':' => {
+ let mut s = String::new();
+ while let Some(c) = format_iter.next() {
+ if c == '}' {
+ break;
+ }
+ s.push(c);
+ }
+ s
+ }
+ _ => unreachable!(),
+ };
+ parts.push(mem::take(&mut last_part));
+ let arg_tree = if argument.is_empty() {
+ match args.next() {
+ Some(x) => x,
+ None => {
+ err = Some(mbe::ExpandError::NoMatchingRule.into());
+ tt::Subtree::empty()
+ }
+ }
+ } else if let Some(tree) = key_args.get(&argument) {
+ tree.clone()
+ } else {
+ // FIXME: we should pick the related substring of the `_format_string_span` as the span. You
+ // can use `.char_indices()` instead of `.char()` for `format_iter` to find the substring interval.
+ let ident = Ident::new(argument, tt::TokenId::unspecified());
+ quote!(#ident)
+ };
+ let formatter = match &*format_spec {
+ "?" => quote!(::core::fmt::Debug::fmt),
+ "" => quote!(::core::fmt::Display::fmt),
+ _ => {
+ // FIXME: implement the rest and return expand error here
+ quote!(::core::fmt::Display::fmt)
+ }
+ };
+ arg_tts.push(quote! { ::core::fmt::Argument::new(&(#arg_tree), #formatter), });
+ }
+ '}' => {
+ if format_iter.peek() == Some(&'}') {
+ format_iter.next();
+ last_part.push('}');
+ } else {
+ return expand_error;
+ }
+ }
+ _ => last_part.push(c),
+ }
+ }
+ last_part += end_string;
+ if !last_part.is_empty() {
+ parts.push(last_part);
}
- let _format_string = args.remove(0);
- let arg_tts = args.into_iter().flat_map(|arg| {
- quote! { #DOLLAR_CRATE::fmt::Argument::new(&(#arg), #DOLLAR_CRATE::fmt::Display::fmt), }
- }.token_trees);
+ let part_tts = parts.into_iter().map(|x| {
+ let text = if let Some(raw) = &raw_sharps {
+ format!("r{raw}\"{}\"{raw}", x).into()
+ } else {
+ format!("\"{}\"", x).into()
+ };
+ let l = tt::Literal { span: tt::TokenId::unspecified(), text };
+ quote!(#l ,)
+ });
+ let arg_tts = arg_tts.into_iter().flat_map(|arg| arg.token_trees);
let expanded = quote! {
- #DOLLAR_CRATE::fmt::Arguments::new_v1(&[], &[##arg_tts])
+ ::core::fmt::Arguments::new_v1(&[##part_tts], &[##arg_tts])
};
- ExpandResult::ok(expanded)
+ ExpandResult { value: expanded, err }
}
fn asm_expand(
@@ -566,16 +694,16 @@ fn include_expand(
let path = parse_string(tt)?;
let file_id = relative_file(db, arg_id, &path, false)?;
- let subtree =
- parse_to_token_tree(&db.file_text(file_id)).ok_or(mbe::ExpandError::ConversionError)?.0;
- Ok((subtree, file_id))
+ let (subtree, map) =
+ parse_to_token_tree(&db.file_text(file_id)).ok_or(mbe::ExpandError::ConversionError)?;
+ Ok((subtree, map, file_id))
})();
match res {
- Ok((subtree, file_id)) => {
- ExpandResult::ok(ExpandedEager { subtree, included_file: Some(file_id) })
+ Ok((subtree, map, file_id)) => {
+ ExpandResult::ok(ExpandedEager { subtree, included_file: Some((file_id, map)) })
}
- Err(e) => ExpandResult::with_err(
+ Err(e) => ExpandResult::new(
ExpandedEager { subtree: tt::Subtree::empty(), included_file: None },
e,
),
@@ -588,7 +716,7 @@ fn include_bytes_expand(
tt: &tt::Subtree,
) -> ExpandResult<ExpandedEager> {
if let Err(e) = parse_string(tt) {
- return ExpandResult::with_err(
+ return ExpandResult::new(
ExpandedEager { subtree: tt::Subtree::empty(), included_file: None },
e,
);
@@ -613,7 +741,7 @@ fn include_str_expand(
let path = match parse_string(tt) {
Ok(it) => it,
Err(e) => {
- return ExpandResult::with_err(
+ return ExpandResult::new(
ExpandedEager { subtree: tt::Subtree::empty(), included_file: None },
e,
)
@@ -650,7 +778,7 @@ fn env_expand(
let key = match parse_string(tt) {
Ok(it) => it,
Err(e) => {
- return ExpandResult::with_err(
+ return ExpandResult::new(
ExpandedEager { subtree: tt::Subtree::empty(), included_file: None },
e,
)
@@ -686,16 +814,16 @@ fn option_env_expand(
let key = match parse_string(tt) {
Ok(it) => it,
Err(e) => {
- return ExpandResult::with_err(
+ return ExpandResult::new(
ExpandedEager { subtree: tt::Subtree::empty(), included_file: None },
e,
)
}
};
-
+ // FIXME: Use `DOLLAR_CRATE` when that works in eager macros.
let expanded = match get_env_inner(db, arg_id, &key) {
- None => quote! { #DOLLAR_CRATE::option::Option::None::<&str> },
- Some(s) => quote! { #DOLLAR_CRATE::option::Option::Some(#s) },
+ None => quote! { ::core::option::Option::None::<&str> },
+ Some(s) => quote! { ::core::option::Option::Some(#s) },
};
ExpandResult::ok(ExpandedEager::new(expanded))