X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Fmacros.rs;h=9ce5c913ca55f48a82cadc4c00923098d9025281;hb=3f6ea7788b5c65c00e995d04622533554a13dd38;hp=bb870e589eee51336557056501b7f1eaa580ada3;hpb=6ba7c34433ef62f25e566fe2f8729a674d57aa00;p=rust.git diff --git a/src/macros.rs b/src/macros.rs index bb870e589ee..9ce5c913ca5 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -22,35 +22,32 @@ use std::collections::HashMap; use config::lists::*; -use syntax::ast; -use syntax::codemap::{BytePos, Span}; 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 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, wrap_str}; +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, @@ -59,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 { @@ -82,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) => { @@ -108,18 +116,40 @@ macro_rules! parse_macro_arg { }; } - 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(path: &ast::Path, extra_ident: Option) -> String { +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!("{}!", path.segments[0].identifier) + format!("{}!", rewrite_ident(context, path.segments[0].ident)) } else { format!("{}!", path) }; @@ -129,6 +159,54 @@ fn rewrite_macro_name(path: &ast::Path, extra_ident: Option) -> Stri } } +// 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, @@ -136,21 +214,35 @@ 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 = rewrite_macro_name(&mac.node.path, extra_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 }; @@ -159,16 +251,16 @@ pub fn rewrite_macro( 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. - // TODO: Maybe add more macros? if macro_name == "lazy_static!" && !has_comment { if let success @ Some(..) = format_lazy_static(context, shape, &ts) { return success; @@ -180,11 +272,13 @@ pub fn rewrite_macro( 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 { @@ -204,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(); @@ -222,14 +323,26 @@ 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 => { + DelimToken::Paren => { // Format macro invocation as function call, preserve the trailing // comma because not all macros support them. overflow::rewrite_with_parens( context, ¯o_name, - &arg_vec.iter().map(|e| &*e).collect::>()[..], + arg_vec.iter(), shape, mac.span, context.config.width_heuristics().fn_call_width, @@ -238,71 +351,82 @@ pub fn rewrite_macro( } else { Some(SeparatorTactic::Never) }, - ).map(|rw| match position { + ) + .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!( - "{}{}{}{};{}{}{}{}", + "{}[{}{};{}{}{}]", macro_name, - lbr, nested_shape.indent.to_string_with_newline(context.config), lhs, nested_shape.indent.to_string_with_newline(context.config), rhs, shape.indent.to_string_with_newline(context.config), - rbr )) } } 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 - .snippet_provider - .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)?; + 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!("{}{}{}", macro_name, rewrite, comma)) + 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!(), } } @@ -329,11 +453,11 @@ 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; @@ -352,29 +476,35 @@ pub fn rewrite_macro_def( ";", |branch| branch.span.lo(), |branch| branch.span.hi(), - |branch| branch.rewrite(context, arm_shape, multi_branch_style), + |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()) + } + None => None, + }, context.snippet_provider.span_after(span, "{"), span.hi(), false, - ).collect::>(); - - let fmt = ListFormatting { - tactic: DefinitiveListTactic::Vertical, - separator: if def.legacy { ";" } else { "" }, - trailing_separator: SeparatorTactic::Always, - separator_place: SeparatorPlace::Back, - shape: arm_shape, - ends_with_newline: true, - preserve_newline: true, - config: context.config, - }; + ) + .collect::>(); + + let fmt = ListFormatting::new(arm_shape, context.config) + .separator(if def.legacy { ";" } else { "" }) + .trailing_separator(SeparatorTactic::Always) + .preserve_newline(true); 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_with_newline(context.config); @@ -384,6 +514,28 @@ pub fn rewrite_macro_def( 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. @@ -394,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); @@ -449,73 +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. -// FIXME: Use multi-line when every thing does not fit on one line. -fn format_macro_args(toks: ThinTokenStream, shape: Shape) -> 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), +} - for tok in (toks.into(): TokenStream).trees() { - match tok { - TokenTree::Token(_, t) => { - if !result.is_empty() && force_space_before(&t) { - insert_space = SpaceState::Always; +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)) + }; + + 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, shape)?; - 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) } +} - if result.len() <= shape.width { - Some(result) - } else { +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, @@ -524,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 @@ -541,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 @@ -572,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 @@ -584,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, } @@ -594,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(), @@ -609,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. @@ -729,20 +1174,21 @@ fn parse_branch(&mut self) -> Option { let tok = self.toks.next()?; let (lo, args_paren_kind) = match tok { TokenTree::Token(..) => return None, - TokenTree::Delimited(sp, ref d) => (sp.lo(), 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 (mut hi, 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(); + 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(), ) } }; @@ -755,6 +1201,7 @@ fn parse_branch(&mut self) -> Option { args_paren_kind, args, body, + whole_body, }) } } @@ -771,6 +1218,7 @@ struct MacroBranch { args_paren_kind: DelimToken, args: ThinTokenStream, body: Span, + whole_body: Span, } impl MacroBranch { @@ -787,12 +1235,18 @@ fn rewrite( } // 5 = " => {" - let mut result = format_macro_args(self.args.clone(), shape.sub_width(5)?)?; + let mut result = format_macro_args(context, self.args.clone(), shape.sub_width(5)?)?; 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 @@ -801,45 +1255,56 @@ fn rewrite( 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 has_block_body = old_body.starts_with('{'); - let body_indent = if has_block_body { shape.indent } else { - // We'll hack the indent below, take this into account when formatting, - let body_indent = shape.indent.block_indent(&config); - let new_width = config.max_width() - body_indent.width(); - config.set().max_width(new_width); - body_indent + 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 = match ::format_snippet(&body_str, &config) { + let new_body_snippet = 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, - }, + 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, config.max_width(), shape)?; + 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 = new_body - .trim_right() - .lines() - .fold(String::new(), |mut s, l| { - if !l.is_empty() { - s += &indent_str; - } - s + l + "\n" - }); + 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. @@ -869,7 +1334,7 @@ fn rewrite( /// /// # Expected syntax /// -/// ``` +/// ```ignore /// lazy_static! { /// [pub] static ref NAME_1: TYPE_1 = EXPR_1; /// [pub] static ref NAME_2: TYPE_2 = EXPR_2; @@ -880,32 +1345,36 @@ fn rewrite( 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()); + 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 parse_or($method:ident $(,)* $($arg:expr),* $(,)*) { - match parser.$method($($arg,)*) { - Ok(val) => { - if parser.sess.span_diagnostic.has_errors() { + 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; - } 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(&parse_or!(parse_visibility, false)); + 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); @@ -941,44 +1410,44 @@ fn format_lazy_static(context: &RewriteContext, shape: Shape, ts: &TokenStream) Some(result) } -#[cfg(test)] -mod test { - use super::*; - use syntax::parse::{parse_stream_from_source_str, ParseSess}; - use syntax::codemap::{FileName, FilePathMapping}; +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 => ";", + _ => "", + }; - 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, - ); - let shape = Shape { - width: 100, - indent: Indent::empty(), - offset: 0, + 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, }; - format_macro_args(input.into(), shape).unwrap() + visitor.visit_item(&item); } - #[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() - ); - } + 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) }