Skip to content

Implement format_args #2489

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Dec 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions crates/ra_hir/src/source_binder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,13 @@ fn def_with_body_from_child_node(
db: &impl HirDatabase,
child: InFile<&SyntaxNode>,
) -> Option<DefWithBody> {
child.value.ancestors().find_map(|node| {
child.cloned().ancestors_with_macros(db).find_map(|node| {
let n = &node.value;
match_ast! {
match node {
ast::FnDef(def) => { return Function::from_source(db, child.with_value(def)).map(DefWithBody::from); },
ast::ConstDef(def) => { return Const::from_source(db, child.with_value(def)).map(DefWithBody::from); },
ast::StaticDef(def) => { return Static::from_source(db, child.with_value(def)).map(DefWithBody::from); },
match n {
ast::FnDef(def) => { return Function::from_source(db, node.with_value(def)).map(DefWithBody::from); },
ast::ConstDef(def) => { return Const::from_source(db, node.with_value(def)).map(DefWithBody::from); },
ast::StaticDef(def) => { return Static::from_source(db, node.with_value(def)).map(DefWithBody::from); },
_ => { None },
}
}
Expand Down Expand Up @@ -135,6 +136,7 @@ pub struct ReferenceDescriptor {
pub name: String,
}

#[derive(Debug)]
pub struct Expansion {
macro_file_kind: MacroFileKind,
macro_call_id: MacroCallId,
Expand Down
58 changes: 57 additions & 1 deletion crates/ra_hir_expand/src/builtin_macro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,11 @@ register_builtin! {
(COMPILE_ERROR_MACRO, CompileError) => compile_error_expand,
(FILE_MACRO, File) => file_expand,
(LINE_MACRO, Line) => line_expand,
(STRINGIFY_MACRO, Stringify) => stringify_expand
(STRINGIFY_MACRO, Stringify) => stringify_expand,
(FORMAT_ARGS_MACRO, FormatArgs) => 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_MACRO, FormatArgsNl) => format_args_expand
}

fn to_line_number(db: &dyn AstDatabase, file: HirFileId, pos: TextUnit) -> usize {
Expand Down Expand Up @@ -200,6 +204,41 @@ fn compile_error_expand(
Err(mbe::ExpandError::BindingError("Must be a string".into()))
}

fn format_args_expand(
_db: &dyn AstDatabase,
_id: MacroCallId,
tt: &tt::Subtree,
) -> Result<tt::Subtree, mbe::ExpandError> {
// We expand `format_args!("", arg1, arg2)` to
// `std::fmt::Arguments::new_v1(&[], &[&arg1, &arg2])`,
// which is still not really correct, but close enough for now
let mut args = Vec::new();
let mut current = Vec::new();
for tt in tt.token_trees.iter().cloned() {
match tt {
tt::TokenTree::Leaf(tt::Leaf::Punct(p)) if p.char == ',' => {
args.push(tt::Subtree { delimiter: tt::Delimiter::None, token_trees: current });
current = Vec::new();
}
_ => {
current.push(tt);
}
}
}
if !current.is_empty() {
args.push(tt::Subtree { delimiter: tt::Delimiter::None, token_trees: current });
}
if args.is_empty() {
return Err(mbe::ExpandError::NoMatchingRule);
}
let _format_string = args.remove(0);
let arg_tts = args.into_iter().flat_map(|arg| (quote! { & #arg , }).token_trees);
let expanded = quote! {
std::fmt::Arguments::new_v1(&[], &[##arg_tts])
};
Ok(expanded)
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -307,4 +346,21 @@ mod tests {

assert_eq!(expanded, r#"loop{"error!"}"#);
}

#[test]
fn test_format_args_expand() {
let expanded = expand_builtin_macro(
r#"
#[rustc_builtin_macro]
macro_rules! format_args {
($fmt:expr) => ({ /* compiler built-in */ });
($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ })
}
format_args!("{} {:?}", arg1(a, b, c), arg2);
"#,
BuiltinFnLikeExpander::FormatArgs,
);

assert_eq!(expanded, r#"std::fmt::Arguments::new_v1(&[] ,&[&arg1(a,b,c),&arg2,])"#);
}
}
4 changes: 2 additions & 2 deletions crates/ra_hir_expand/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ impl TokenExpander {
pub fn map_id_up(&self, id: tt::TokenId) -> (tt::TokenId, mbe::Origin) {
match self {
TokenExpander::MacroRules(it) => it.map_id_up(id),
TokenExpander::Builtin(..) => (id, mbe::Origin::Def),
TokenExpander::BuiltinDerive(..) => (id, mbe::Origin::Def),
TokenExpander::Builtin(..) => (id, mbe::Origin::Call),
TokenExpander::BuiltinDerive(..) => (id, mbe::Origin::Call),
}
}
}
Expand Down
39 changes: 39 additions & 0 deletions crates/ra_hir_expand/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,17 @@ impl HirFileId {
}
}

/// If this is a macro call, returns the syntax node of the call.
pub fn call_node(self, db: &dyn db::AstDatabase) -> Option<InFile<SyntaxNode>> {
match self.0 {
HirFileIdRepr::FileId(_) => None,
HirFileIdRepr::MacroFile(macro_file) => {
let loc = db.lookup_intern_macro(macro_file.macro_call_id);
Some(loc.kind.node(db))
}
}
}

/// Return expansion information if it is a macro-expansion file
pub fn expansion_info(self, db: &dyn db::AstDatabase) -> Option<ExpansionInfo> {
match self.0 {
Expand Down Expand Up @@ -176,6 +187,13 @@ impl MacroCallKind {
}
}

pub fn node(&self, db: &dyn db::AstDatabase) -> InFile<SyntaxNode> {
match self {
MacroCallKind::FnLike(ast_id) => ast_id.with_value(ast_id.to_node(db).syntax().clone()),
MacroCallKind::Attr(ast_id) => ast_id.with_value(ast_id.to_node(db).syntax().clone()),
}
}

pub fn arg(&self, db: &dyn db::AstDatabase) -> Option<SyntaxNode> {
match self {
MacroCallKind::FnLike(ast_id) => {
Expand Down Expand Up @@ -283,3 +301,24 @@ impl<T> InFile<T> {
db.parse_or_expand(self.file_id).expect("source created from invalid file")
}
}

impl<T: Clone> InFile<&T> {
pub fn cloned(&self) -> InFile<T> {
self.with_value(self.value.clone())
}
}

impl InFile<SyntaxNode> {
pub fn ancestors_with_macros<'a>(
self,
db: &'a impl crate::db::AstDatabase,
) -> impl Iterator<Item = InFile<SyntaxNode>> + 'a {
std::iter::successors(Some(self), move |node| match node.value.parent() {
Some(parent) => Some(node.with_value(parent)),
None => {
let parent_node = node.file_id.call_node(db)?;
Some(parent_node)
}
})
}
}
116 changes: 59 additions & 57 deletions crates/ra_hir_expand/src/name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ impl Name {
}

/// Shortcut to create inline plain text name
const fn new_inline_ascii(len: usize, text: &[u8]) -> Name {
Name::new_text(SmolStr::new_inline_from_ascii(len, text))
const fn new_inline_ascii(text: &[u8]) -> Name {
Name::new_text(SmolStr::new_inline_from_ascii(text.len(), text))
}

/// Resolve a name from the text of token.
Expand Down Expand Up @@ -105,68 +105,70 @@ impl AsName for ra_db::Dependency {
}

// Primitives
pub const ISIZE: Name = Name::new_inline_ascii(5, b"isize");
pub const I8: Name = Name::new_inline_ascii(2, b"i8");
pub const I16: Name = Name::new_inline_ascii(3, b"i16");
pub const I32: Name = Name::new_inline_ascii(3, b"i32");
pub const I64: Name = Name::new_inline_ascii(3, b"i64");
pub const I128: Name = Name::new_inline_ascii(4, b"i128");
pub const USIZE: Name = Name::new_inline_ascii(5, b"usize");
pub const U8: Name = Name::new_inline_ascii(2, b"u8");
pub const U16: Name = Name::new_inline_ascii(3, b"u16");
pub const U32: Name = Name::new_inline_ascii(3, b"u32");
pub const U64: Name = Name::new_inline_ascii(3, b"u64");
pub const U128: Name = Name::new_inline_ascii(4, b"u128");
pub const F32: Name = Name::new_inline_ascii(3, b"f32");
pub const F64: Name = Name::new_inline_ascii(3, b"f64");
pub const BOOL: Name = Name::new_inline_ascii(4, b"bool");
pub const CHAR: Name = Name::new_inline_ascii(4, b"char");
pub const STR: Name = Name::new_inline_ascii(3, b"str");
pub const ISIZE: Name = Name::new_inline_ascii(b"isize");
pub const I8: Name = Name::new_inline_ascii(b"i8");
pub const I16: Name = Name::new_inline_ascii(b"i16");
pub const I32: Name = Name::new_inline_ascii(b"i32");
pub const I64: Name = Name::new_inline_ascii(b"i64");
pub const I128: Name = Name::new_inline_ascii(b"i128");
pub const USIZE: Name = Name::new_inline_ascii(b"usize");
pub const U8: Name = Name::new_inline_ascii(b"u8");
pub const U16: Name = Name::new_inline_ascii(b"u16");
pub const U32: Name = Name::new_inline_ascii(b"u32");
pub const U64: Name = Name::new_inline_ascii(b"u64");
pub const U128: Name = Name::new_inline_ascii(b"u128");
pub const F32: Name = Name::new_inline_ascii(b"f32");
pub const F64: Name = Name::new_inline_ascii(b"f64");
pub const BOOL: Name = Name::new_inline_ascii(b"bool");
pub const CHAR: Name = Name::new_inline_ascii(b"char");
pub const STR: Name = Name::new_inline_ascii(b"str");

// Special names
pub const SELF_PARAM: Name = Name::new_inline_ascii(4, b"self");
pub const SELF_TYPE: Name = Name::new_inline_ascii(4, b"Self");
pub const MACRO_RULES: Name = Name::new_inline_ascii(11, b"macro_rules");
pub const SELF_PARAM: Name = Name::new_inline_ascii(b"self");
pub const SELF_TYPE: Name = Name::new_inline_ascii(b"Self");
pub const MACRO_RULES: Name = Name::new_inline_ascii(b"macro_rules");

// Components of known path (value or mod name)
pub const STD: Name = Name::new_inline_ascii(3, b"std");
pub const ITER: Name = Name::new_inline_ascii(4, b"iter");
pub const OPS: Name = Name::new_inline_ascii(3, b"ops");
pub const FUTURE: Name = Name::new_inline_ascii(6, b"future");
pub const RESULT: Name = Name::new_inline_ascii(6, b"result");
pub const BOXED: Name = Name::new_inline_ascii(5, b"boxed");
pub const STD: Name = Name::new_inline_ascii(b"std");
pub const ITER: Name = Name::new_inline_ascii(b"iter");
pub const OPS: Name = Name::new_inline_ascii(b"ops");
pub const FUTURE: Name = Name::new_inline_ascii(b"future");
pub const RESULT: Name = Name::new_inline_ascii(b"result");
pub const BOXED: Name = Name::new_inline_ascii(b"boxed");

// Components of known path (type name)
pub const INTO_ITERATOR_TYPE: Name = Name::new_inline_ascii(12, b"IntoIterator");
pub const ITEM_TYPE: Name = Name::new_inline_ascii(4, b"Item");
pub const TRY_TYPE: Name = Name::new_inline_ascii(3, b"Try");
pub const OK_TYPE: Name = Name::new_inline_ascii(2, b"Ok");
pub const FUTURE_TYPE: Name = Name::new_inline_ascii(6, b"Future");
pub const RESULT_TYPE: Name = Name::new_inline_ascii(6, b"Result");
pub const OUTPUT_TYPE: Name = Name::new_inline_ascii(6, b"Output");
pub const TARGET_TYPE: Name = Name::new_inline_ascii(6, b"Target");
pub const BOX_TYPE: Name = Name::new_inline_ascii(3, b"Box");
pub const RANGE_FROM_TYPE: Name = Name::new_inline_ascii(9, b"RangeFrom");
pub const RANGE_FULL_TYPE: Name = Name::new_inline_ascii(9, b"RangeFull");
pub const RANGE_INCLUSIVE_TYPE: Name = Name::new_inline_ascii(14, b"RangeInclusive");
pub const RANGE_TO_INCLUSIVE_TYPE: Name = Name::new_inline_ascii(16, b"RangeToInclusive");
pub const RANGE_TO_TYPE: Name = Name::new_inline_ascii(7, b"RangeTo");
pub const RANGE_TYPE: Name = Name::new_inline_ascii(5, b"Range");
pub const INTO_ITERATOR_TYPE: Name = Name::new_inline_ascii(b"IntoIterator");
pub const ITEM_TYPE: Name = Name::new_inline_ascii(b"Item");
pub const TRY_TYPE: Name = Name::new_inline_ascii(b"Try");
pub const OK_TYPE: Name = Name::new_inline_ascii(b"Ok");
pub const FUTURE_TYPE: Name = Name::new_inline_ascii(b"Future");
pub const RESULT_TYPE: Name = Name::new_inline_ascii(b"Result");
pub const OUTPUT_TYPE: Name = Name::new_inline_ascii(b"Output");
pub const TARGET_TYPE: Name = Name::new_inline_ascii(b"Target");
pub const BOX_TYPE: Name = Name::new_inline_ascii(b"Box");
pub const RANGE_FROM_TYPE: Name = Name::new_inline_ascii(b"RangeFrom");
pub const RANGE_FULL_TYPE: Name = Name::new_inline_ascii(b"RangeFull");
pub const RANGE_INCLUSIVE_TYPE: Name = Name::new_inline_ascii(b"RangeInclusive");
pub const RANGE_TO_INCLUSIVE_TYPE: Name = Name::new_inline_ascii(b"RangeToInclusive");
pub const RANGE_TO_TYPE: Name = Name::new_inline_ascii(b"RangeTo");
pub const RANGE_TYPE: Name = Name::new_inline_ascii(b"Range");

// Builtin Macros
pub const FILE_MACRO: Name = Name::new_inline_ascii(4, b"file");
pub const COLUMN_MACRO: Name = Name::new_inline_ascii(6, b"column");
pub const COMPILE_ERROR_MACRO: Name = Name::new_inline_ascii(13, b"compile_error");
pub const LINE_MACRO: Name = Name::new_inline_ascii(4, b"line");
pub const STRINGIFY_MACRO: Name = Name::new_inline_ascii(9, b"stringify");
pub const FILE_MACRO: Name = Name::new_inline_ascii(b"file");
pub const COLUMN_MACRO: Name = Name::new_inline_ascii(b"column");
pub const COMPILE_ERROR_MACRO: Name = Name::new_inline_ascii(b"compile_error");
pub const LINE_MACRO: Name = Name::new_inline_ascii(b"line");
pub const STRINGIFY_MACRO: Name = Name::new_inline_ascii(b"stringify");
pub const FORMAT_ARGS_MACRO: Name = Name::new_inline_ascii(b"format_args");
pub const FORMAT_ARGS_NL_MACRO: Name = Name::new_inline_ascii(b"format_args_nl");

// Builtin derives
pub const COPY_TRAIT: Name = Name::new_inline_ascii(4, b"Copy");
pub const CLONE_TRAIT: Name = Name::new_inline_ascii(5, b"Clone");
pub const DEFAULT_TRAIT: Name = Name::new_inline_ascii(7, b"Default");
pub const DEBUG_TRAIT: Name = Name::new_inline_ascii(5, b"Debug");
pub const HASH_TRAIT: Name = Name::new_inline_ascii(4, b"Hash");
pub const ORD_TRAIT: Name = Name::new_inline_ascii(3, b"Ord");
pub const PARTIAL_ORD_TRAIT: Name = Name::new_inline_ascii(10, b"PartialOrd");
pub const EQ_TRAIT: Name = Name::new_inline_ascii(2, b"Eq");
pub const PARTIAL_EQ_TRAIT: Name = Name::new_inline_ascii(9, b"PartialEq");
pub const COPY_TRAIT: Name = Name::new_inline_ascii(b"Copy");
pub const CLONE_TRAIT: Name = Name::new_inline_ascii(b"Clone");
pub const DEFAULT_TRAIT: Name = Name::new_inline_ascii(b"Default");
pub const DEBUG_TRAIT: Name = Name::new_inline_ascii(b"Debug");
pub const HASH_TRAIT: Name = Name::new_inline_ascii(b"Hash");
pub const ORD_TRAIT: Name = Name::new_inline_ascii(b"Ord");
pub const PARTIAL_ORD_TRAIT: Name = Name::new_inline_ascii(b"PartialOrd");
pub const EQ_TRAIT: Name = Name::new_inline_ascii(b"Eq");
pub const PARTIAL_EQ_TRAIT: Name = Name::new_inline_ascii(b"PartialEq");
30 changes: 30 additions & 0 deletions crates/ra_ide/src/goto_definition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -689,8 +689,38 @@ mod tests {
fo<|>o();
}
}
mod confuse_index { fn foo(); }
",
"foo FN_DEF FileId(1) [52; 63) [55; 58)",
);
}

#[should_panic] // currently failing because of expr mapping problems
#[test]
fn goto_through_format() {
check_goto(
"
//- /lib.rs
#[macro_export]
macro_rules! format {
($($arg:tt)*) => ($crate::fmt::format($crate::__export::format_args!($($arg)*)))
}
#[rustc_builtin_macro]
#[macro_export]
macro_rules! format_args {
($fmt:expr) => ({ /* compiler built-in */ });
($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ })
}
pub mod __export {
pub use crate::format_args;
fn foo() {} // for index confusion
}
fn foo() -> i8 {}
fn test() {
format!(\"{}\", fo<|>o())
}
",
"foo FN_DEF FileId(1) [359; 376) [362; 365)",
);
}
}