X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Fmacros.rs;h=9ce5c913ca55f48a82cadc4c00923098d9025281;hb=3f6ea7788b5c65c00e995d04622533554a13dd38;hp=1c9ca3057f017976c5ff7dfaf90b80f6bdae480a;hpb=70e77162621c7cde2435224cf1decdee088be27e;p=rust.git diff --git a/src/macros.rs b/src/macros.rs index 1c9ca3057f0..9ce5c913ca5 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -20,34 +20,34 @@ // and those with brackets will be formatted as array literals. use std::collections::HashMap; -use syntax::ast; -use syntax::codemap::{BytePos, Span}; + +use config::lists::*; use syntax::parse::new_parser_from_tts; use syntax::parse::parser::Parser; use syntax::parse::token::{BinOpToken, DelimToken, Token}; use syntax::print::pprust; +use syntax::source_map::{BytePos, Span}; use syntax::symbol; use syntax::tokenstream::{Cursor, ThinTokenStream, TokenStream, TokenTree}; -use syntax::util::ThinVec; +use syntax::ThinVec; +use syntax::{ast, parse, ptr}; -use codemap::SpanUtils; -use comment::{contains_comment, remove_trailing_white_spaces, FindUncommented}; -use expr::{rewrite_array, rewrite_call_inner}; -use lists::{itemize_list, write_list, DefinitiveListTactic, ListFormatting, SeparatorPlace, SeparatorTactic}; +use comment::{contains_comment, CharClasses, FindUncommented, FullCodeCharKind, LineClasses}; +use expr::rewrite_array; +use lists::{itemize_list, write_list, ListFormatting}; +use overflow; use rewrite::{Rewrite, RewriteContext}; use shape::{Indent, Shape}; -use utils::{format_visibility, mk_sp}; +use source_map::SpanUtils; +use spanned::Spanned; +use utils::{ + format_visibility, is_empty_line, mk_sp, remove_trailing_white_spaces, rewrite_ident, + trim_left_preserve_layout, wrap_str, NodeIdExt, +}; +use visitor::FmtVisitor; const FORCED_BRACKET_MACROS: &[&str] = &["vec!"]; -// FIXME: use the enum from libsyntax? -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -enum MacroStyle { - Parens, - Brackets, - Braces, -} - #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum MacroPosition { Item, @@ -56,21 +56,31 @@ pub enum MacroPosition { Pat, } -impl MacroStyle { - fn opener(&self) -> &'static str { - match *self { - MacroStyle::Parens => "(", - MacroStyle::Brackets => "[", - MacroStyle::Braces => "{", +#[derive(Debug)] +pub enum MacroArg { + Expr(ptr::P), + Ty(ptr::P), + Pat(ptr::P), + Item(ptr::P), +} + +impl MacroArg { + fn is_item(&self) -> bool { + match self { + MacroArg::Item(..) => true, + _ => false, } } } -#[derive(Debug)] -pub enum MacroArg { - Expr(ast::Expr), - Ty(ast::Ty), - Pat(ast::Pat), +impl Rewrite for ast::Item { + fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option { + let mut visitor = ::visitor::FmtVisitor::from_context(context); + visitor.block_indent = shape.indent; + visitor.last_pos = self.span().lo(); + visitor.visit_item(self); + Some(visitor.buffer.to_owned()) + } } impl Rewrite for MacroArg { @@ -79,22 +89,23 @@ fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option { MacroArg::Expr(ref expr) => expr.rewrite(context, shape), MacroArg::Ty(ref ty) => ty.rewrite(context, shape), MacroArg::Pat(ref pat) => pat.rewrite(context, shape), + MacroArg::Item(ref item) => item.rewrite(context, shape), } } } -fn parse_macro_arg(parser: &mut Parser) -> Option { +fn parse_macro_arg<'a, 'b: 'a>(parser: &'a mut Parser<'b>) -> Option { macro_rules! parse_macro_arg { - ($macro_arg: ident, $parser: ident) => { + ($macro_arg:ident, $parser:expr, $f:expr) => { let mut cloned_parser = (*parser).clone(); - match cloned_parser.$parser() { + match $parser(&mut cloned_parser) { Ok(x) => { if parser.sess.span_diagnostic.has_errors() { parser.sess.span_diagnostic.reset_err_count(); } else { // Parsing succeeded. *parser = cloned_parser; - return Some(MacroArg::$macro_arg((*x).clone())); + return Some(MacroArg::$macro_arg($f(x)?)); } } Err(mut e) => { @@ -102,16 +113,100 @@ macro_rules! parse_macro_arg { parser.sess.span_diagnostic.reset_err_count(); } } - } + }; } - parse_macro_arg!(Expr, parse_expr); - parse_macro_arg!(Ty, parse_ty); - parse_macro_arg!(Pat, parse_pat); + parse_macro_arg!( + Expr, + |parser: &mut parse::parser::Parser<'b>| parser.parse_expr(), + |x: ptr::P| Some(x) + ); + parse_macro_arg!( + Ty, + |parser: &mut parse::parser::Parser<'b>| parser.parse_ty(), + |x: ptr::P| Some(x) + ); + parse_macro_arg!( + Pat, + |parser: &mut parse::parser::Parser<'b>| parser.parse_pat(None), + |x: ptr::P| Some(x) + ); + // `parse_item` returns `Option>`. + parse_macro_arg!( + Item, + |parser: &mut parse::parser::Parser<'b>| parser.parse_item(), + |x: Option>| x + ); None } +/// Rewrite macro name without using pretty-printer if possible. +fn rewrite_macro_name( + context: &RewriteContext, + path: &ast::Path, + extra_ident: Option, +) -> String { + let name = if path.segments.len() == 1 { + // Avoid using pretty-printer in the common case. + format!("{}!", rewrite_ident(context, path.segments[0].ident)) + } else { + format!("{}!", path) + }; + match extra_ident { + Some(ident) if ident != symbol::keywords::Invalid.ident() => format!("{} {}", name, ident), + _ => name, + } +} + +// Use this on failing to format the macro call. +fn return_macro_parse_failure_fallback( + context: &RewriteContext, + indent: Indent, + span: Span, +) -> Option { + // Mark this as a failure however we format it + context.macro_rewrite_failure.replace(true); + + // Heuristically determine whether the last line of the macro uses "Block" style + // rather than using "Visual" style, or another indentation style. + let is_like_block_indent_style = context + .snippet(span) + .lines() + .last() + .map(|closing_line| { + closing_line.trim().chars().all(|ch| match ch { + '}' | ')' | ']' => true, + _ => false, + }) + }) + .unwrap_or(false); + if is_like_block_indent_style { + return trim_left_preserve_layout(context.snippet(span), indent, &context.config); + } + + // Return the snippet unmodified if the macro is not block-like + Some(context.snippet(span).to_owned()) +} + +struct InsideMacroGuard<'a> { + context: &'a RewriteContext<'a>, + is_nested: bool, +} + +impl<'a> InsideMacroGuard<'a> { + fn inside_macro_context(context: &'a RewriteContext) -> InsideMacroGuard<'a> { + let is_nested = context.inside_macro.replace(true); + InsideMacroGuard { context, is_nested } + } +} + +impl<'a> Drop for InsideMacroGuard<'a> { + fn drop(&mut self) { + self.context.inside_macro.replace(self.is_nested); + } +} + pub fn rewrite_macro( mac: &ast::Mac, extra_ident: Option, @@ -119,56 +214,71 @@ pub fn rewrite_macro( shape: Shape, position: MacroPosition, ) -> Option { - let context = &mut context.clone(); - context.inside_macro = true; + let guard = InsideMacroGuard::inside_macro_context(context); + let result = rewrite_macro_inner(mac, extra_ident, context, shape, position, guard.is_nested); + if result.is_none() { + context.macro_rewrite_failure.replace(true); + } + result +} + +pub fn rewrite_macro_inner( + mac: &ast::Mac, + extra_ident: Option, + context: &RewriteContext, + shape: Shape, + position: MacroPosition, + is_nested_macro: bool, +) -> Option { if context.config.use_try_shorthand() { if let Some(expr) = convert_try_mac(mac, context) { - context.inside_macro = false; + context.inside_macro.replace(false); return expr.rewrite(context, shape); } } let original_style = macro_style(mac, context); - let macro_name = match extra_ident { - None => format!("{}!", mac.node.path), - Some(ident) => { - if ident == symbol::keywords::Invalid.ident() { - format!("{}!", mac.node.path) - } else { - format!("{}! {}", mac.node.path, ident) - } - } - }; + let macro_name = rewrite_macro_name(context, &mac.node.path, extra_ident); - let style = if FORCED_BRACKET_MACROS.contains(&¯o_name[..]) { - MacroStyle::Brackets + let style = if FORCED_BRACKET_MACROS.contains(&¯o_name[..]) && !is_nested_macro { + DelimToken::Bracket } else { original_style }; let ts: TokenStream = mac.node.stream(); - if ts.is_empty() && !contains_comment(context.snippet(mac.span)) { + let has_comment = contains_comment(context.snippet(mac.span)); + if ts.is_empty() && !has_comment { return match style { - MacroStyle::Parens if position == MacroPosition::Item => { + DelimToken::Paren if position == MacroPosition::Item => { Some(format!("{}();", macro_name)) } - MacroStyle::Parens => Some(format!("{}()", macro_name)), - MacroStyle::Brackets => Some(format!("{}[]", macro_name)), - MacroStyle::Braces => Some(format!("{}{{}}", macro_name)), + DelimToken::Paren => Some(format!("{}()", macro_name)), + DelimToken::Bracket => Some(format!("{}[]", macro_name)), + DelimToken::Brace => Some(format!("{} {{}}", macro_name)), + _ => unreachable!(), }; } + // Format well-known macros which cannot be parsed as a valid AST. + if macro_name == "lazy_static!" && !has_comment { + if let success @ Some(..) = format_lazy_static(context, shape, &ts) { + return success; + } + } let mut parser = new_parser_from_tts(context.parse_session, ts.trees().collect()); let mut arg_vec = Vec::new(); let mut vec_with_semi = false; let mut trailing_comma = false; - if MacroStyle::Braces != style { + if DelimToken::Brace != style { loop { match parse_macro_arg(&mut parser) { Some(arg) => arg_vec.push(arg), - None => return Some(context.snippet(mac.span).to_owned()), + None => { + return return_macro_parse_failure_fallback(context, shape.indent, mac.span); + } } match parser.token { @@ -188,13 +298,20 @@ pub fn rewrite_macro( break; } } - None => return Some(context.snippet(mac.span).to_owned()), + None => { + return return_macro_parse_failure_fallback( + context, + shape.indent, + mac.span, + ); + } } } } - return Some(context.snippet(mac.span).to_owned()); + return return_macro_parse_failure_fallback(context, shape.indent, mac.span); } - _ => return Some(context.snippet(mac.span).to_owned()), + _ if arg_vec.last().map_or(false, MacroArg::is_item) => continue, + _ => return return_macro_parse_failure_fallback(context, shape.indent, mac.span), } parser.bump(); @@ -206,79 +323,110 @@ pub fn rewrite_macro( } } + if !arg_vec.is_empty() && arg_vec.iter().all(MacroArg::is_item) { + return rewrite_macro_with_items( + context, + &arg_vec, + ¯o_name, + shape, + style, + position, + mac.span, + ); + } + match style { - MacroStyle::Parens => { - // Format macro invocation as function call, forcing no trailing + DelimToken::Paren => { + // Format macro invocation as function call, preserve the trailing // comma because not all macros support them. - rewrite_call_inner( + overflow::rewrite_with_parens( context, ¯o_name, - &arg_vec.iter().map(|e| &*e).collect::>()[..], - mac.span, + arg_vec.iter(), shape, + mac.span, context.config.width_heuristics().fn_call_width, - trailing_comma, - ).map(|rw| match position { + if trailing_comma { + Some(SeparatorTactic::Always) + } else { + Some(SeparatorTactic::Never) + }, + ) + .map(|rw| match position { MacroPosition::Item => format!("{};", rw), _ => rw, }) } - MacroStyle::Brackets => { - let mac_shape = shape.offset_left(macro_name.len())?; + DelimToken::Bracket => { // Handle special case: `vec![expr; expr]` if vec_with_semi { - let (lbr, rbr) = if context.config.spaces_within_parens_and_brackets() { - ("[ ", " ]") - } else { - ("[", "]") - }; - // 6 = `vec!` + `; ` - let total_overhead = lbr.len() + rbr.len() + 6; + let mac_shape = shape.offset_left(macro_name.len())?; + // 8 = `vec![]` + `; ` + let total_overhead = 8; let nested_shape = mac_shape.block_indent(context.config.tab_spaces()); let lhs = arg_vec[0].rewrite(context, nested_shape)?; let rhs = arg_vec[1].rewrite(context, nested_shape)?; - if !lhs.contains('\n') && !rhs.contains('\n') + if !lhs.contains('\n') + && !rhs.contains('\n') && lhs.len() + rhs.len() + total_overhead <= shape.width { - Some(format!("{}{}{}; {}{}", macro_name, lbr, lhs, rhs, rbr)) + Some(format!("{}[{}; {}]", macro_name, lhs, rhs)) } else { Some(format!( - "{}{}\n{}{};\n{}{}\n{}{}", + "{}[{}{};{}{}{}]", macro_name, - lbr, - nested_shape.indent.to_string(context.config), + nested_shape.indent.to_string_with_newline(context.config), lhs, - nested_shape.indent.to_string(context.config), + nested_shape.indent.to_string_with_newline(context.config), rhs, - shape.indent.to_string(context.config), - rbr + shape.indent.to_string_with_newline(context.config), )) } } else { // If we are rewriting `vec!` macro or other special macros, // then we can rewrite this as an usual array literal. // Otherwise, we must preserve the original existence of trailing comma. - if FORCED_BRACKET_MACROS.contains(¯o_name.as_str()) { - context.inside_macro = false; - trailing_comma = false; + let macro_name = ¯o_name.as_str(); + let mut force_trailing_comma = if trailing_comma { + Some(SeparatorTactic::Always) + } else { + Some(SeparatorTactic::Never) + }; + if FORCED_BRACKET_MACROS.contains(macro_name) && !is_nested_macro { + context.inside_macro.replace(false); + if context.use_block_indent() { + force_trailing_comma = Some(SeparatorTactic::Vertical); + }; } - // Convert `MacroArg` into `ast::Expr`, as `rewrite_array` only accepts the latter. - let sp = mk_sp( - context - .codemap - .span_after(mac.span, original_style.opener()), - mac.span.hi() - BytePos(1), - ); - let arg_vec = &arg_vec.iter().map(|e| &*e).collect::>()[..]; - let rewrite = rewrite_array(arg_vec, sp, context, mac_shape, trailing_comma)?; - - Some(format!("{}{}", macro_name, rewrite)) + let rewrite = rewrite_array( + macro_name, + arg_vec.iter(), + mac.span, + context, + shape, + force_trailing_comma, + Some(original_style), + )?; + let comma = match position { + MacroPosition::Item => ";", + _ => "", + }; + + Some(format!("{}{}", rewrite, comma)) } } - MacroStyle::Braces => { - // Skip macro invocations with braces, for now. - indent_macro_snippet(context, context.snippet(mac.span), shape.indent) + DelimToken::Brace => { + // For macro invocations with braces, always put a space between + // the `macro_name!` and `{ /* macro_body */ }` but skip modifying + // anything in between the braces (for now). + let snippet = context.snippet(mac.span); + let macro_raw = snippet.split_at(snippet.find('!')? + 1).1.trim_start(); + match trim_left_preserve_layout(macro_raw, shape.indent, &context.config) { + Some(macro_body) => Some(format!("{} {}", macro_name, macro_body)), + None => Some(format!("{} {}", macro_name, macro_raw)), + } } + _ => unreachable!(), } } @@ -292,6 +440,9 @@ pub fn rewrite_macro_def( span: Span, ) -> Option { let snippet = Some(remove_trailing_white_spaces(context.snippet(span))); + if snippet.as_ref().map_or(true, |s| s.ends_with(';')) { + return snippet; + } let mut parser = MacroParser::new(def.stream().into_trees()); let parsed_def = match parser.parse() { @@ -302,158 +453,89 @@ pub fn rewrite_macro_def( let mut result = if def.legacy { String::from("macro_rules!") } else { - format!("{}macro", format_visibility(vis)) + format!("{}macro", format_visibility(context, vis)) }; result += " "; - result += &ident.name.as_str(); + result += rewrite_ident(context, ident); let multi_branch_style = def.legacy || parsed_def.branches.len() != 1; - let mac_indent = if multi_branch_style { - result += " {"; - indent.block_indent(context.config) + let arm_shape = if multi_branch_style { + shape + .block_indent(context.config.tab_spaces()) + .with_max_width(context.config) } else { - indent + shape }; - let mac_indent_str = mac_indent.to_string(context.config); - let branch_items = itemize_list( - context.codemap, + context.snippet_provider, parsed_def.branches.iter(), - "", - "", - |branch| branch.args_span.lo(), - |branch| branch.body.hi(), - |branch| { - let mut result = String::new(); - - // Only attempt to format function-like macros. - if branch.args_paren_kind != DelimToken::Paren { - // FIXME(#1539): implement for non-sugared macros. - return None; - } - - let args = format_macro_args(branch.args.clone())?; - - if multi_branch_style { - result += "\n"; - result += &mac_indent_str; - result += &args; - result += " =>"; - } else { - result += &args; + "}", + ";", + |branch| branch.span.lo(), + |branch| branch.span.hi(), + |branch| match branch.rewrite(context, arm_shape, multi_branch_style) { + Some(v) => Some(v), + // if the rewrite returned None because a macro could not be rewritten, then return the + // original body + None if *context.macro_rewrite_failure.borrow() => { + Some(context.snippet(branch.body).trim().to_string()) } - - // The macro body is the most interesting part. It might end up as various - // AST nodes, but also has special variables (e.g, `$foo`) which can't be - // parsed as regular Rust code (and note that these can be escaped using - // `$$`). We'll try and format like an AST node, but we'll substitute - // variables for new names with the same length first. - - let old_body = context.snippet(branch.body).trim(); - let (body_str, substs) = match replace_names(old_body) { - Some(result) => result, - None => return snippet, - }; - - let mut config = context.config.clone(); - config.set().hide_parse_errors(true); - - result += " {"; - - let has_block_body = old_body.starts_with('{'); - - let body_indent = if has_block_body { - mac_indent - } else { - // We'll hack the indent below, take this into account when formatting, - let body_indent = mac_indent.block_indent(&config); - let new_width = config.max_width() - body_indent.width(); - config.set().max_width(new_width); - body_indent - }; - - // First try to format as items, then as statements. - let new_body = match ::format_snippet(&body_str, &config) { - Some(new_body) => new_body, - None => match ::format_code_block(&body_str, &config) { - Some(new_body) => new_body, - None => return None, - }, - }; - - // Indent the body since it is in a block. - let indent_str = body_indent.to_string(&config); - let mut new_body = new_body - .trim_right() - .lines() - .fold(String::new(), |mut s, l| { - if !l.is_empty() { - s += &indent_str; - } - s + l + "\n" - }); - - // Undo our replacement of macro variables. - // FIXME: this could be *much* more efficient. - for (old, new) in &substs { - if old_body.find(new).is_some() { - debug!( - "rewrite_macro_def: bailing matching variable: `{}` in `{}`", - new, ident - ); - return None; - } - new_body = new_body.replace(new, old); - } - - if has_block_body { - result += new_body.trim(); - } else if !new_body.is_empty() { - result += "\n"; - result += &new_body; - result += &mac_indent_str; - } - - result += "}"; - if def.legacy { - result += ";"; - } - result += "\n"; - Some(result) + None => None, }, - span.lo(), + context.snippet_provider.span_after(span, "{"), span.hi(), - false - ).collect::>(); + false, + ) + .collect::>(); - let arm_shape = shape - .block_indent(context.config.tab_spaces()) - .with_max_width(context.config); + let fmt = ListFormatting::new(arm_shape, context.config) + .separator(if def.legacy { ";" } else { "" }) + .trailing_separator(SeparatorTactic::Always) + .preserve_newline(true); - let fmt = ListFormatting { - tactic: DefinitiveListTactic::Vertical, - separator: "", - trailing_separator: SeparatorTactic::Never, - separator_place: SeparatorPlace::Back, - shape: arm_shape, - ends_with_newline: false, - preserve_newline: true, - config: context.config, - }; + if multi_branch_style { + result += " {"; + result += &arm_shape.indent.to_string_with_newline(context.config); + } - result += write_list(&branch_items, &fmt)?.as_str(); + match write_list(&branch_items, &fmt) { + Some(ref s) => result += s, + None => return snippet, + } if multi_branch_style { - result += &indent.to_string(context.config); + result += &indent.to_string_with_newline(context.config); result += "}"; } Some(result) } +fn register_metavariable( + map: &mut HashMap, + result: &mut String, + name: &str, + dollar_count: usize, +) { + let mut new_name = String::new(); + let mut old_name = String::new(); + + old_name.push('$'); + for _ in 0..(dollar_count - 1) { + new_name.push('$'); + old_name.push('$'); + } + new_name.push('z'); + new_name.push_str(&name); + old_name.push_str(&name); + + result.push_str(&new_name); + map.insert(old_name, new_name); +} + // Replaces `$foo` with `zfoo`. We must check for name overlap to ensure we // aren't causing problems. // This should also work for escaped `$` variables, where we leave earlier `$`s. @@ -464,54 +546,30 @@ fn replace_names(input: &str) -> Option<(String, HashMap)> { let mut dollar_count = 0; let mut cur_name = String::new(); - for c in input.chars() { - if c == '$' { + for (kind, c) in CharClasses::new(input.chars()) { + if kind != FullCodeCharKind::Normal { + result.push(c); + } else if c == '$' { dollar_count += 1; } else if dollar_count == 0 { result.push(c); } else if !c.is_alphanumeric() && !cur_name.is_empty() { // Terminates a name following one or more dollars. - let mut new_name = String::new(); - let mut old_name = String::new(); - old_name.push('$'); - for _ in 0..(dollar_count - 1) { - new_name.push('$'); - old_name.push('$'); - } - new_name.push('z'); - new_name.push_str(&cur_name); - old_name.push_str(&cur_name); - - result.push_str(&new_name); - substs.insert(old_name, new_name); + register_metavariable(&mut substs, &mut result, &cur_name, dollar_count); result.push(c); - dollar_count = 0; - cur_name = String::new(); + cur_name.clear(); } else if c == '(' && cur_name.is_empty() { // FIXME: Support macro def with repeat. return None; - } else if c.is_alphanumeric() { + } else if c.is_alphanumeric() || c == '_' { cur_name.push(c); } } - // FIXME: duplicate code if !cur_name.is_empty() { - let mut new_name = String::new(); - let mut old_name = String::new(); - old_name.push('$'); - for _ in 0..(dollar_count - 1) { - new_name.push('$'); - old_name.push('$'); - } - new_name.push('z'); - new_name.push_str(&cur_name); - old_name.push_str(&cur_name); - - result.push_str(&new_name); - substs.insert(old_name, new_name); + register_metavariable(&mut substs, &mut result, &cur_name, dollar_count); } debug!("replace_names `{}` {:?}", result, substs); @@ -519,68 +577,477 @@ fn replace_names(input: &str) -> Option<(String, HashMap)> { Some((result, substs)) } -// This is a bit sketchy. The token rules probably need tweaking, but it works -// for some common cases. I hope the basic logic is sufficient. Note that the -// meaning of some tokens is a bit different here from usual Rust, e.g., `*` -// and `(`/`)` have special meaning. -// -// We always try and format on one line. -fn format_macro_args(toks: ThinTokenStream) -> Option { - let mut result = String::with_capacity(128); - let mut insert_space = SpaceState::Never; +#[derive(Debug, Clone)] +enum MacroArgKind { + /// e.g. `$x: expr`. + MetaVariable(ast::Ident, String), + /// e.g. `$($foo: expr),*` + Repeat( + /// `()`, `[]` or `{}`. + DelimToken, + /// Inner arguments inside delimiters. + Vec, + /// Something after the closing delimiter and the repeat token, if available. + Option>, + /// The repeat token. This could be one of `*`, `+` or `?`. + Token, + ), + /// e.g. `[derive(Debug)]` + Delimited(DelimToken, Vec), + /// A possible separator. e.g. `,` or `;`. + Separator(String, String), + /// Other random stuff that does not fit to other kinds. + /// e.g. `== foo` in `($x: expr == foo)`. + Other(String, String), +} + +fn delim_token_to_str( + context: &RewriteContext, + delim_token: DelimToken, + shape: Shape, + use_multiple_lines: bool, + inner_is_empty: bool, +) -> (String, String) { + let (lhs, rhs) = match delim_token { + DelimToken::Paren => ("(", ")"), + DelimToken::Bracket => ("[", "]"), + DelimToken::Brace => { + if inner_is_empty || use_multiple_lines { + ("{", "}") + } else { + ("{ ", " }") + } + } + DelimToken::NoDelim => ("", ""), + }; + if use_multiple_lines { + let indent_str = shape.indent.to_string_with_newline(context.config); + let nested_indent_str = shape + .indent + .block_indent(context.config) + .to_string_with_newline(context.config); + ( + format!("{}{}", lhs, nested_indent_str), + format!("{}{}", indent_str, rhs), + ) + } else { + (lhs.to_owned(), rhs.to_owned()) + } +} + +impl MacroArgKind { + fn starts_with_brace(&self) -> bool { + match *self { + MacroArgKind::Repeat(DelimToken::Brace, _, _, _) + | MacroArgKind::Delimited(DelimToken::Brace, _) => true, + _ => false, + } + } + + fn starts_with_dollar(&self) -> bool { + match *self { + MacroArgKind::Repeat(..) | MacroArgKind::MetaVariable(..) => true, + _ => false, + } + } + + fn ends_with_space(&self) -> bool { + match *self { + MacroArgKind::Separator(..) => true, + _ => false, + } + } + + fn has_meta_var(&self) -> bool { + match *self { + MacroArgKind::MetaVariable(..) => true, + MacroArgKind::Repeat(_, ref args, _, _) => args.iter().any(|a| a.kind.has_meta_var()), + _ => false, + } + } + + fn rewrite( + &self, + context: &RewriteContext, + shape: Shape, + use_multiple_lines: bool, + ) -> Option { + let rewrite_delimited_inner = |delim_tok, args| -> Option<(String, String, String)> { + let inner = wrap_macro_args(context, args, shape)?; + let (lhs, rhs) = delim_token_to_str(context, delim_tok, shape, false, inner.is_empty()); + if lhs.len() + inner.len() + rhs.len() <= shape.width { + return Some((lhs, inner, rhs)); + } + + let (lhs, rhs) = delim_token_to_str(context, delim_tok, shape, true, false); + let nested_shape = shape + .block_indent(context.config.tab_spaces()) + .with_max_width(context.config); + let inner = wrap_macro_args(context, args, nested_shape)?; + Some((lhs, inner, rhs)) + }; - for tok in (toks.into(): TokenStream).trees() { - match tok { - TokenTree::Token(_, t) => { - if !result.is_empty() && force_space_before(&t) { - insert_space = SpaceState::Always; + match *self { + MacroArgKind::MetaVariable(ty, ref name) => { + Some(format!("${}:{}", name, ty.name.as_str())) + } + MacroArgKind::Repeat(delim_tok, ref args, ref another, ref tok) => { + let (lhs, inner, rhs) = rewrite_delimited_inner(delim_tok, args)?; + let another = another + .as_ref() + .and_then(|a| a.rewrite(context, shape, use_multiple_lines)) + .unwrap_or_else(|| "".to_owned()); + let repeat_tok = pprust::token_to_string(tok); + + Some(format!("${}{}{}{}{}", lhs, inner, rhs, another, repeat_tok)) + } + MacroArgKind::Delimited(delim_tok, ref args) => { + rewrite_delimited_inner(delim_tok, args) + .map(|(lhs, inner, rhs)| format!("{}{}{}", lhs, inner, rhs)) + } + MacroArgKind::Separator(ref sep, ref prefix) => Some(format!("{}{} ", prefix, sep)), + MacroArgKind::Other(ref inner, ref prefix) => Some(format!("{}{}", prefix, inner)), + } + } +} + +#[derive(Debug, Clone)] +struct ParsedMacroArg { + kind: MacroArgKind, + span: Span, +} + +impl ParsedMacroArg { + pub fn rewrite( + &self, + context: &RewriteContext, + shape: Shape, + use_multiple_lines: bool, + ) -> Option { + self.kind.rewrite(context, shape, use_multiple_lines) + } +} + +/// Parses macro arguments on macro def. +struct MacroArgParser { + /// Holds either a name of the next metavariable, a separator or a junk. + buf: String, + /// The start position on the current buffer. + lo: BytePos, + /// The first token of the current buffer. + start_tok: Token, + /// Set to true if we are parsing a metavariable or a repeat. + is_meta_var: bool, + /// The position of the last token. + hi: BytePos, + /// The last token parsed. + last_tok: Token, + /// Holds the parsed arguments. + result: Vec, +} + +fn last_tok(tt: &TokenTree) -> Token { + match *tt { + TokenTree::Token(_, ref t) => t.clone(), + TokenTree::Delimited(_, ref d) => d.close_token(), + } +} + +impl MacroArgParser { + pub fn new() -> MacroArgParser { + MacroArgParser { + lo: BytePos(0), + hi: BytePos(0), + buf: String::new(), + is_meta_var: false, + last_tok: Token::Eof, + start_tok: Token::Eof, + result: vec![], + } + } + + fn set_last_tok(&mut self, tok: &TokenTree) { + self.hi = tok.span().hi(); + self.last_tok = last_tok(tok); + } + + fn add_separator(&mut self) { + let prefix = if self.need_space_prefix() { + " ".to_owned() + } else { + "".to_owned() + }; + self.result.push(ParsedMacroArg { + kind: MacroArgKind::Separator(self.buf.clone(), prefix), + span: mk_sp(self.lo, self.hi), + }); + self.buf.clear(); + } + + fn add_other(&mut self) { + let prefix = if self.need_space_prefix() { + " ".to_owned() + } else { + "".to_owned() + }; + self.result.push(ParsedMacroArg { + kind: MacroArgKind::Other(self.buf.clone(), prefix), + span: mk_sp(self.lo, self.hi), + }); + self.buf.clear(); + } + + fn add_meta_variable(&mut self, iter: &mut Cursor) -> Option<()> { + match iter.next() { + Some(TokenTree::Token(sp, Token::Ident(ref ident, _))) => { + self.result.push(ParsedMacroArg { + kind: MacroArgKind::MetaVariable(*ident, self.buf.clone()), + span: mk_sp(self.lo, sp.hi()), + }); + + self.buf.clear(); + self.is_meta_var = false; + Some(()) + } + _ => None, + } + } + + fn add_delimited(&mut self, inner: Vec, delim: DelimToken, span: Span) { + self.result.push(ParsedMacroArg { + kind: MacroArgKind::Delimited(delim, inner), + span, + }); + } + + // $($foo: expr),? + fn add_repeat( + &mut self, + inner: Vec, + delim: DelimToken, + iter: &mut Cursor, + span: Span, + ) -> Option<()> { + let mut buffer = String::new(); + let mut first = false; + let mut lo = span.lo(); + let mut hi = span.hi(); + + // Parse '*', '+' or '?. + for tok in iter { + self.set_last_tok(&tok); + if first { + first = false; + lo = tok.span().lo(); + } + + match tok { + TokenTree::Token(_, Token::BinOp(BinOpToken::Plus)) + | TokenTree::Token(_, Token::Question) + | TokenTree::Token(_, Token::BinOp(BinOpToken::Star)) => { + break; } - if force_no_space_before(&t) { - insert_space = SpaceState::Never; + TokenTree::Token(sp, ref t) => { + buffer.push_str(&pprust::token_to_string(t)); + hi = sp.hi(); } - match (insert_space, ident_like(&t)) { - (SpaceState::Always, _) - | (SpaceState::Punctuation, false) - | (SpaceState::Ident, true) => { - result.push(' '); + _ => return None, + } + } + + // There could be some random stuff between ')' and '*', '+' or '?'. + let another = if buffer.trim().is_empty() { + None + } else { + Some(Box::new(ParsedMacroArg { + kind: MacroArgKind::Other(buffer, "".to_owned()), + span: mk_sp(lo, hi), + })) + }; + + self.result.push(ParsedMacroArg { + kind: MacroArgKind::Repeat(delim, inner, another, self.last_tok.clone()), + span: mk_sp(self.lo, self.hi), + }); + Some(()) + } + + fn update_buffer(&mut self, lo: BytePos, t: &Token) { + if self.buf.is_empty() { + self.lo = lo; + self.start_tok = t.clone(); + } else { + let needs_space = match next_space(&self.last_tok) { + SpaceState::Ident => ident_like(t), + SpaceState::Punctuation => !ident_like(t), + SpaceState::Always => true, + SpaceState::Never => false, + }; + if force_space_before(t) || needs_space { + self.buf.push(' '); + } + } + + self.buf.push_str(&pprust::token_to_string(t)); + } + + fn need_space_prefix(&self) -> bool { + if self.result.is_empty() { + return false; + } + + let last_arg = self.result.last().unwrap(); + if let MacroArgKind::MetaVariable(..) = last_arg.kind { + if ident_like(&self.start_tok) { + return true; + } + if self.start_tok == Token::Colon { + return true; + } + } + + if force_space_before(&self.start_tok) { + return true; + } + + false + } + + /// Returns a collection of parsed macro def's arguments. + pub fn parse(mut self, tokens: ThinTokenStream) -> Option> { + let stream: TokenStream = tokens.into(); + let mut iter = stream.trees(); + + while let Some(ref tok) = iter.next() { + match tok { + TokenTree::Token(sp, Token::Dollar) => { + // We always want to add a separator before meta variables. + if !self.buf.is_empty() { + self.add_separator(); } - _ => {} + + // Start keeping the name of this metavariable in the buffer. + self.is_meta_var = true; + self.lo = sp.lo(); + self.start_tok = Token::Dollar; } - result.push_str(&pprust::token_to_string(&t)); - insert_space = next_space(&t); - } - TokenTree::Delimited(_, d) => { - if let SpaceState::Always = insert_space { - result.push(' '); + TokenTree::Token(_, Token::Colon) if self.is_meta_var => { + self.add_meta_variable(&mut iter)?; } - let formatted = format_macro_args(d.tts)?; - match d.delim { - DelimToken::Paren => { - result.push_str(&format!("({})", formatted)); - insert_space = SpaceState::Always; - } - DelimToken::Bracket => { - result.push_str(&format!("[{}]", formatted)); - insert_space = SpaceState::Always; - } - DelimToken::Brace => { - result.push_str(&format!(" {{ {} }}", formatted)); - insert_space = SpaceState::Always; + TokenTree::Token(sp, ref t) => self.update_buffer(sp.lo(), t), + TokenTree::Delimited(delimited_span, delimited) => { + if !self.buf.is_empty() { + if next_space(&self.last_tok) == SpaceState::Always { + self.add_separator(); + } else { + self.add_other(); + } } - DelimToken::NoDelim => { - result.push_str(&format!("{}", formatted)); - insert_space = SpaceState::Always; + + // Parse the stuff inside delimiters. + let mut parser = MacroArgParser::new(); + parser.lo = delimited_span.open.lo(); + let delimited_arg = parser.parse(delimited.tts.clone())?; + + let span = delimited_span.entire(); + if self.is_meta_var { + self.add_repeat(delimited_arg, delimited.delim, &mut iter, span)?; + self.is_meta_var = false; + } else { + self.add_delimited(delimited_arg, delimited.delim, span); } } } + + self.set_last_tok(tok); } + + // We are left with some stuff in the buffer. Since there is nothing + // left to separate, add this as `Other`. + if !self.buf.is_empty() { + self.add_other(); + } + + Some(self.result) } +} - Some(result) +fn wrap_macro_args( + context: &RewriteContext, + args: &[ParsedMacroArg], + shape: Shape, +) -> Option { + wrap_macro_args_inner(context, args, shape, false) + .or_else(|| wrap_macro_args_inner(context, args, shape, true)) +} + +fn wrap_macro_args_inner( + context: &RewriteContext, + args: &[ParsedMacroArg], + shape: Shape, + use_multiple_lines: bool, +) -> Option { + let mut result = String::with_capacity(128); + let mut iter = args.iter().peekable(); + let indent_str = shape.indent.to_string_with_newline(context.config); + + while let Some(ref arg) = iter.next() { + result.push_str(&arg.rewrite(context, shape, use_multiple_lines)?); + + if use_multiple_lines + && (arg.kind.ends_with_space() || iter.peek().map_or(false, |a| a.kind.has_meta_var())) + { + if arg.kind.ends_with_space() { + result.pop(); + } + result.push_str(&indent_str); + } else if let Some(ref next_arg) = iter.peek() { + let space_before_dollar = + !arg.kind.ends_with_space() && next_arg.kind.starts_with_dollar(); + let space_before_brace = next_arg.kind.starts_with_brace(); + if space_before_dollar || space_before_brace { + result.push(' '); + } + } + } + + if !use_multiple_lines && result.len() >= shape.width { + None + } else { + Some(result) + } +} + +// This is a bit sketchy. The token rules probably need tweaking, but it works +// for some common cases. I hope the basic logic is sufficient. Note that the +// meaning of some tokens is a bit different here from usual Rust, e.g., `*` +// and `(`/`)` have special meaning. +// +// We always try and format on one line. +// FIXME: Use multi-line when every thing does not fit on one line. +fn format_macro_args( + context: &RewriteContext, + toks: ThinTokenStream, + shape: Shape, +) -> Option { + if !context.config.format_macro_matchers() { + let token_stream: TokenStream = toks.into(); + let span = span_for_token_stream(&token_stream); + return Some(match span { + Some(span) => context.snippet(span).to_owned(), + None => String::new(), + }); + } + let parsed_args = MacroArgParser::new().parse(toks)?; + wrap_macro_args(context, &parsed_args, shape) +} + +fn span_for_token_stream(token_stream: &TokenStream) -> Option { + token_stream.trees().next().map(|tt| tt.span()) } // We should insert a space if the next token is a: -#[derive(Copy, Clone)] +#[derive(Copy, Clone, PartialEq)] enum SpaceState { Never, Punctuation, @@ -589,7 +1056,9 @@ enum SpaceState { } fn force_space_before(tok: &Token) -> bool { - match *tok { + debug!("tok: force_space_before {:?}", tok); + + match tok { Token::Eq | Token::Lt | Token::Le @@ -606,30 +1075,26 @@ fn force_space_before(tok: &Token) -> bool { | Token::RArrow | Token::LArrow | Token::FatArrow + | Token::BinOp(_) | Token::Pound | Token::Dollar => true, - Token::BinOp(bot) => bot != BinOpToken::Star, _ => false, } } -fn force_no_space_before(tok: &Token) -> bool { - match *tok { - Token::Semi | Token::Comma | Token::Dot => true, - Token::BinOp(bot) => bot == BinOpToken::Star, - _ => false, - } -} fn ident_like(tok: &Token) -> bool { - match *tok { - Token::Ident(_) | Token::Literal(..) | Token::Lifetime(_) => true, + match tok { + Token::Ident(..) | Token::Literal(..) | Token::Lifetime(_) => true, _ => false, } } fn next_space(tok: &Token) -> SpaceState { - match *tok { + debug!("next_space: {:?}", tok); + + match tok { Token::Not + | Token::BinOp(BinOpToken::And) | Token::Tilde | Token::At | Token::Comma @@ -637,10 +1102,7 @@ fn next_space(tok: &Token) -> SpaceState { | Token::DotDot | Token::DotDotDot | Token::DotDotEq - | Token::DotEq - | Token::Question - | Token::Underscore - | Token::BinOp(_) => SpaceState::Punctuation, + | Token::Question => SpaceState::Punctuation, Token::ModSep | Token::Pound @@ -649,7 +1111,7 @@ fn next_space(tok: &Token) -> SpaceState { | Token::CloseDelim(_) | Token::Whitespace => SpaceState::Never, - Token::Literal(..) | Token::Ident(_) | Token::Lifetime(_) => SpaceState::Ident, + Token::Literal(..) | Token::Ident(..) | Token::Lifetime(_) => SpaceState::Ident, _ => SpaceState::Always, } @@ -659,12 +1121,12 @@ fn next_space(tok: &Token) -> SpaceState { /// when the macro is not an instance of try! (or parsing the inner expression /// failed). pub fn convert_try_mac(mac: &ast::Mac, context: &RewriteContext) -> Option { - if &format!("{}", mac.node.path)[..] == "try" { + if &mac.node.path.to_string() == "try" { let ts: TokenStream = mac.node.tts.clone().into(); let mut parser = new_parser_from_tts(context.parse_session, ts.trees().collect()); Some(ast::Expr { - id: ast::NodeId::new(0), // dummy value + id: ast::NodeId::root(), // dummy value node: ast::ExprKind::Try(parser.parse_expr().ok()?), span: mac.span, // incorrect span, but shouldn't matter too much attrs: ThinVec::new(), @@ -674,101 +1136,19 @@ pub fn convert_try_mac(mac: &ast::Mac, context: &RewriteContext) -> Option MacroStyle { +fn macro_style(mac: &ast::Mac, context: &RewriteContext) -> DelimToken { let snippet = context.snippet(mac.span); let paren_pos = snippet.find_uncommented("(").unwrap_or(usize::max_value()); let bracket_pos = snippet.find_uncommented("[").unwrap_or(usize::max_value()); let brace_pos = snippet.find_uncommented("{").unwrap_or(usize::max_value()); if paren_pos < bracket_pos && paren_pos < brace_pos { - MacroStyle::Parens + DelimToken::Paren } else if bracket_pos < brace_pos { - MacroStyle::Brackets + DelimToken::Bracket } else { - MacroStyle::Braces - } -} - -/// Indent each line according to the specified `indent`. -/// e.g. -/// ```rust -/// foo!{ -/// x, -/// y, -/// foo( -/// a, -/// b, -/// c, -/// ), -/// } -/// ``` -/// will become -/// ```rust -/// foo!{ -/// x, -/// y, -/// foo( -/// a, -/// b, -/// c, -// ), -/// } -/// ``` -fn indent_macro_snippet( - context: &RewriteContext, - macro_str: &str, - indent: Indent, -) -> Option { - let mut lines = macro_str.lines(); - let first_line = lines.next().map(|s| s.trim_right())?; - let mut trimmed_lines = Vec::with_capacity(16); - - let min_prefix_space_width = lines - .filter_map(|line| { - let prefix_space_width = if is_empty_line(line) { - None - } else { - Some(get_prefix_space_width(context, line)) - }; - trimmed_lines.push((line.trim(), prefix_space_width)); - prefix_space_width - }) - .min()?; - - Some( - String::from(first_line) + "\n" - + &trimmed_lines - .iter() - .map(|&(line, prefix_space_width)| match prefix_space_width { - Some(original_indent_width) => { - let new_indent_width = indent.width() - + original_indent_width - .checked_sub(min_prefix_space_width) - .unwrap_or(0); - let new_indent = Indent::from_width(context.config, new_indent_width); - format!("{}{}", new_indent.to_string(context.config), line.trim()) - } - None => String::new(), - }) - .collect::>() - .join("\n"), - ) -} - -fn get_prefix_space_width(context: &RewriteContext, s: &str) -> usize { - let mut width = 0; - for c in s.chars() { - match c { - ' ' => width += 1, - '\t' => width += context.config.tab_spaces(), - _ => return width, - } + DelimToken::Brace } - width -} - -fn is_empty_line(s: &str) -> bool { - s.is_empty() || s.chars().all(char::is_whitespace) } // A very simple parser that just parses a macros 2.0 definition into its branches. @@ -792,30 +1172,36 @@ fn parse(&mut self) -> Option { // `(` ... `)` `=>` `{` ... `}` fn parse_branch(&mut self) -> Option { let tok = self.toks.next()?; - let (args_span, args_paren_kind) = match tok { + let (lo, args_paren_kind) = match tok { TokenTree::Token(..) => return None, - TokenTree::Delimited(sp, ref d) => (sp, d.delim), + TokenTree::Delimited(delimited_span, ref d) => (delimited_span.open.lo(), d.delim), }; let args = tok.joint().into(); match self.toks.next()? { TokenTree::Token(_, Token::FatArrow) => {} _ => return None, } - let body = match self.toks.next()? { + let (mut hi, body, whole_body) = match self.toks.next()? { TokenTree::Token(..) => return None, - TokenTree::Delimited(sp, _) => { - let data = sp.data(); - Span::new(data.lo + BytePos(1), data.hi - BytePos(1), data.ctxt) + TokenTree::Delimited(delimited_span, _) => { + let data = delimited_span.entire().data(); + ( + data.hi, + Span::new(data.lo + BytePos(1), data.hi - BytePos(1), data.ctxt), + delimited_span.entire(), + ) } }; - if let Some(TokenTree::Token(_, Token::Semi)) = self.toks.look_ahead(0) { + if let Some(TokenTree::Token(sp, Token::Semi)) = self.toks.look_ahead(0) { self.toks.next(); + hi = sp.hi(); } Some(MacroBranch { + span: mk_sp(lo, hi), args_paren_kind, - args_span, args, body, + whole_body, }) } } @@ -828,45 +1214,240 @@ struct Macro { // FIXME: it would be more efficient to use references to the token streams // rather than clone them, if we can make the borrowing work out. struct MacroBranch { + span: Span, args_paren_kind: DelimToken, - args_span: Span, args: ThinTokenStream, body: Span, + whole_body: Span, } -#[cfg(test)] -mod test { - use super::*; - use syntax::parse::{parse_stream_from_source_str, ParseSess}; - use syntax::codemap::{FileName, FilePathMapping}; +impl MacroBranch { + fn rewrite( + &self, + context: &RewriteContext, + shape: Shape, + multi_branch_style: bool, + ) -> Option { + // Only attempt to format function-like macros. + if self.args_paren_kind != DelimToken::Paren { + // FIXME(#1539): implement for non-sugared macros. + return None; + } + + // 5 = " => {" + let mut result = format_macro_args(context, self.args.clone(), shape.sub_width(5)?)?; - fn format_macro_args_str(s: &str) -> String { - let input = parse_stream_from_source_str( - FileName::Custom("stdin".to_owned()), - s.to_owned(), - &ParseSess::new(FilePathMapping::empty()), - None, - ); - format_macro_args(input.into()).unwrap() + if multi_branch_style { + result += " =>"; + } + + if !context.config.format_macro_bodies() { + result += " "; + result += context.snippet(self.whole_body); + return Some(result); + } + + // The macro body is the most interesting part. It might end up as various + // AST nodes, but also has special variables (e.g, `$foo`) which can't be + // parsed as regular Rust code (and note that these can be escaped using + // `$$`). We'll try and format like an AST node, but we'll substitute + // variables for new names with the same length first. + + let old_body = context.snippet(self.body).trim(); + let (body_str, substs) = replace_names(old_body)?; + let has_block_body = old_body.starts_with('{'); + + let mut config = context.config.clone(); + config.set().hide_parse_errors(true); + + result += " {"; + + let body_indent = if has_block_body { + shape.indent + } else { + shape.indent.block_indent(&config) + }; + let new_width = config.max_width() - body_indent.width(); + config.set().max_width(new_width); + + // First try to format as items, then as statements. + let new_body_snippet = match ::format_snippet(&body_str, &config) { + Some(new_body) => new_body, + None => { + let new_width = new_width + config.tab_spaces(); + config.set().max_width(new_width); + match ::format_code_block(&body_str, &config) { + Some(new_body) => new_body, + None => return None, + } + } + }; + let new_body = wrap_str( + new_body_snippet.snippet.to_string(), + config.max_width(), + shape, + )?; + + // Indent the body since it is in a block. + let indent_str = body_indent.to_string(&config); + let mut new_body = LineClasses::new(new_body.trim_end()) + .enumerate() + .fold( + (String::new(), true), + |(mut s, need_indent), (i, (kind, ref l))| { + if !is_empty_line(l) + && need_indent + && !new_body_snippet.is_line_non_formatted(i + 1) + { + s += &indent_str; + } + (s + l + "\n", !kind.is_string() || l.ends_with('\\')) + }, + ) + .0; + + // Undo our replacement of macro variables. + // FIXME: this could be *much* more efficient. + for (old, new) in &substs { + if old_body.find(new).is_some() { + debug!("rewrite_macro_def: bailing matching variable: `{}`", new); + return None; + } + new_body = new_body.replace(new, old); + } + + if has_block_body { + result += new_body.trim(); + } else if !new_body.is_empty() { + result += "\n"; + result += &new_body; + result += &shape.indent.to_string(&config); + } + + result += "}"; + + Some(result) } +} - #[test] - fn test_format_macro_args() { - assert_eq!(format_macro_args_str(""), "".to_owned()); - assert_eq!(format_macro_args_str("$ x : ident"), "$x: ident".to_owned()); - assert_eq!( - format_macro_args_str("$ m1 : ident , $ m2 : ident , $ x : ident"), - "$m1: ident, $m2: ident, $x: ident".to_owned() - ); - assert_eq!( - format_macro_args_str("$($beginning:ident),*;$middle:ident;$($end:ident),*"), - "$($beginning: ident),*; $middle: ident; $($end: ident),*".to_owned() - ); - assert_eq!( - format_macro_args_str( - "$ name : ident ( $ ( $ dol : tt $ var : ident ) * ) $ ( $ body : tt ) *" - ), - "$name: ident($($dol: tt $var: ident)*) $($body: tt)*".to_owned() - ); +/// Format `lazy_static!` from https://crates.io/crates/lazy_static. +/// +/// # Expected syntax +/// +/// ```ignore +/// lazy_static! { +/// [pub] static ref NAME_1: TYPE_1 = EXPR_1; +/// [pub] static ref NAME_2: TYPE_2 = EXPR_2; +/// ... +/// [pub] static ref NAME_N: TYPE_N = EXPR_N; +/// } +/// ``` +fn format_lazy_static(context: &RewriteContext, shape: Shape, ts: &TokenStream) -> Option { + let mut result = String::with_capacity(1024); + let mut parser = new_parser_from_tts(context.parse_session, ts.trees().collect()); + let nested_shape = shape + .block_indent(context.config.tab_spaces()) + .with_max_width(context.config); + + result.push_str("lazy_static! {"); + result.push_str(&nested_shape.indent.to_string_with_newline(context.config)); + + macro_rules! parse_or { + ($method:ident $(,)* $($arg:expr),* $(,)*) => { + match parser.$method($($arg,)*) { + Ok(val) => { + if parser.sess.span_diagnostic.has_errors() { + parser.sess.span_diagnostic.reset_err_count(); + return None; + } else { + val + } + } + Err(mut err) => { + err.cancel(); + parser.sess.span_diagnostic.reset_err_count(); + return None; + } + } + } + } + + while parser.token != Token::Eof { + // Parse a `lazy_static!` item. + let vis = ::utils::format_visibility(context, &parse_or!(parse_visibility, false)); + parser.eat_keyword(symbol::keywords::Static); + parser.eat_keyword(symbol::keywords::Ref); + let id = parse_or!(parse_ident); + parser.eat(&Token::Colon); + let ty = parse_or!(parse_ty); + parser.eat(&Token::Eq); + let expr = parse_or!(parse_expr); + parser.eat(&Token::Semi); + + // Rewrite as a static item. + let mut stmt = String::with_capacity(128); + stmt.push_str(&format!( + "{}static ref {}: {} =", + vis, + id, + ty.rewrite(context, nested_shape)? + )); + result.push_str(&::expr::rewrite_assign_rhs( + context, + stmt, + &*expr, + nested_shape.sub_width(1)?, + )?); + result.push(';'); + if parser.token != Token::Eof { + result.push_str(&nested_shape.indent.to_string_with_newline(context.config)); + } } + + result.push_str(&shape.indent.to_string_with_newline(context.config)); + result.push('}'); + + Some(result) +} + +fn rewrite_macro_with_items( + context: &RewriteContext, + items: &[MacroArg], + macro_name: &str, + shape: Shape, + style: DelimToken, + position: MacroPosition, + span: Span, +) -> Option { + let (opener, closer) = match style { + DelimToken::Paren => ("(", ")"), + DelimToken::Bracket => ("[", "]"), + DelimToken::Brace => (" {", "}"), + _ => return None, + }; + let trailing_semicolon = match style { + DelimToken::Paren | DelimToken::Bracket if position == MacroPosition::Item => ";", + _ => "", + }; + + let mut visitor = FmtVisitor::from_context(context); + visitor.block_indent = shape.indent.block_indent(context.config); + visitor.last_pos = context.snippet_provider.span_after(span, opener.trim()); + for item in items { + let item = match item { + MacroArg::Item(item) => item, + _ => return None, + }; + visitor.visit_item(&item); + } + + let mut result = String::with_capacity(256); + result.push_str(¯o_name); + result.push_str(opener); + result.push_str(&visitor.block_indent.to_string_with_newline(context.config)); + result.push_str(visitor.buffer.trim()); + result.push_str(&shape.indent.to_string_with_newline(context.config)); + result.push_str(closer); + result.push_str(trailing_semicolon); + Some(result) }