2489: Implement `format_args` r=flodiebold a=flodiebold

This fixes a huge amount of type mismatches (because every format call was a type mismatch so far); I also hoped to get go to def working within `format!` etc., and the test says it should, but in practice it still doesn't seem to...

Also remove the `len` parameter from `Name::new_inline_ascii`, which I'm assuming was only there because of `const fn` limitations?

cc @edwin0cheng 

Co-authored-by: Florian Diebold <flodiebold@gmail.com>
This commit is contained in:
bors[bot] 2019-12-06 20:59:51 +00:00 committed by GitHub
commit 431836f4a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 194 additions and 65 deletions

View File

@ -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 },
}
}
@ -135,6 +136,7 @@ pub struct ReferenceDescriptor {
pub name: String,
}
#[derive(Debug)]
pub struct Expansion {
macro_file_kind: MacroFileKind,
macro_call_id: MacroCallId,

View File

@ -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 {
@ -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::*;
@ -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,])"#);
}
}

View File

@ -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),
}
}
}

View File

@ -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 {
@ -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) => {
@ -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)
}
})
}
}

View File

@ -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.
@ -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");

View File

@ -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)",
);
}
}