Skip to content

Format stable macro_rules #2393

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Feb 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/bin/cargo-fmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,14 @@ fn execute() -> i32 {
}

macro_rules! print_usage {
($print:ident, $opts:ident, $reason:expr) => ({
($print: ident, $opts: ident, $reason: expr) => {{
let msg = format!("{}\nusage: cargo fmt [options]", $reason);
$print!(
"{}\nThis utility formats all bin and lib files of the current crate using rustfmt. \
Arguments after `--` are passed to rustfmt.",
$opts.usage(&msg)
);
})
}};
}

fn print_usage_to_stdout(opts: &Options, reason: &str) {
Expand Down
2 changes: 1 addition & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ macro_rules! is_nightly_channel {
option_env!("CFG_RELEASE_CHANNEL")
.map(|c| c == "nightly")
.unwrap_or(true)
}
};
}

macro_rules! configuration_option_enum{
Expand Down
233 changes: 158 additions & 75 deletions src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ use syntax::util::ThinVec;
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 rewrite::{Rewrite, RewriteContext};
use shape::{Indent, Shape};
use utils::{format_visibility, mk_sp};
Expand Down Expand Up @@ -101,7 +103,7 @@ fn parse_macro_arg(parser: &mut Parser) -> Option<MacroArg> {
parser.sess.span_diagnostic.reset_err_count();
}
}
}
};
}

parse_macro_arg!(Expr, parse_expr);
Expand Down Expand Up @@ -283,6 +285,7 @@ pub fn rewrite_macro(

pub fn rewrite_macro_def(
context: &RewriteContext,
shape: Shape,
indent: Indent,
def: &ast::MacroDef,
ident: ast::Ident,
Expand All @@ -291,88 +294,67 @@ pub fn rewrite_macro_def(
) -> Option<String> {
let snippet = Some(remove_trailing_white_spaces(context.snippet(span)));

if def.legacy {
return snippet;
}

let mut parser = MacroParser::new(def.stream().into_trees());
let mut parsed_def = match parser.parse() {
let parsed_def = match parser.parse() {
Some(def) => def,
None => return snippet,
};

// Only attempt to format function-like macros.
if parsed_def.branches.len() != 1 || parsed_def.branches[0].args_paren_kind != DelimToken::Paren
{
// FIXME(#1539): implement for non-sugared macros.
return snippet;
}
let mut result = if def.legacy {
String::from("macro_rules!")
} else {
format!("{}macro", format_visibility(vis))
};

let branch = parsed_def.branches.remove(0);
let args_str = format_macro_args(branch.args)?;
result += " ";
result += &ident.name.as_str();

// 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 multi_branch_style = def.legacy || parsed_def.branches.len() != 1;

let old_body = context.snippet(branch.body).trim();
let (body_str, substs) = match replace_names(old_body) {
Some(result) => result,
None => return snippet,
let arm_shape = if multi_branch_style {
shape
.block_indent(context.config.tab_spaces())
.with_max_width(context.config)
} else {
shape
};

// We'll hack the indent below, take this into account when formatting,
let mut config = context.config.clone();
let new_width = config.max_width() - indent.block_indent(&config).width();
config.set().max_width(new_width);
config.set().hide_parse_errors(true);

// 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 snippet,
},
let branch_items = itemize_list(
context.codemap,
parsed_def.branches.iter(),
"}",
";",
|branch| branch.span.lo(),
|branch| branch.span.hi(),
|branch| branch.rewrite(context, arm_shape, multi_branch_style),
context.codemap.span_after(span, "{"),
span.hi(),
false,
).collect::<Vec<_>>();

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,
};

// Indent the body since it is in a block.
let indent_str = indent.block_indent(&config).to_string(&config);
let mut new_body = new_body
.lines()
.map(|l| {
if l.is_empty() {
l.to_owned()
} else {
format!("{}{}", indent_str, l)
}
})
.collect::<Vec<_>>()
.join("\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 snippet;
}
new_body = new_body.replace(new, old);
if multi_branch_style {
result += " {\n";
result += &arm_shape.indent.to_string(context.config);
}

let result = format!(
"{}macro {}({}) {{\n{}\n{}}}",
format_visibility(vis),
ident,
args_str,
new_body,
indent.to_string(context.config),
);
result += write_list(&branch_items, &fmt)?.as_str();

if multi_branch_style {
result += "\n";
result += &indent.to_string(context.config);
result += "}";
}

Some(result)
}
Expand Down Expand Up @@ -714,24 +696,34 @@ impl MacroParser {

// `(` ... `)` `=>` `{` ... `}`
fn parse_branch(&mut self) -> Option<MacroBranch> {
let (args_paren_kind, args) = match self.toks.next()? {
let tok = self.toks.next()?;
let (lo, args_paren_kind) = match tok {
TokenTree::Token(..) => return None,
TokenTree::Delimited(_, ref d) => (d.delim, d.tts.clone()),
TokenTree::Delimited(sp, ref d) => (sp.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) = 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)
(
data.hi,
Span::new(data.lo + BytePos(1), data.hi - BytePos(1), data.ctxt),
)
}
};
if let Some(TokenTree::Token(sp, Token::Semi)) = self.toks.look_ahead(0) {
self.toks.next();
hi = sp.hi();
}
Some(MacroBranch {
args,
span: mk_sp(lo, hi),
args_paren_kind,
args,
body,
})
}
Expand All @@ -745,11 +737,102 @@ 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 {
args: ThinTokenStream,
span: Span,
args_paren_kind: DelimToken,
args: ThinTokenStream,
body: Span,
}

impl MacroBranch {
fn rewrite(
&self,
context: &RewriteContext,
shape: Shape,
multi_branch_style: bool,
) -> Option<String> {
// Only attempt to format function-like macros.
if self.args_paren_kind != DelimToken::Paren {
// FIXME(#1539): implement for non-sugared macros.
return None;
}

let mut result = format_macro_args(self.args.clone())?;

if multi_branch_style {
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 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
};

// 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: `{}`", 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)
}
}

#[cfg(test)]
mod test {
use super::*;
Expand Down
24 changes: 11 additions & 13 deletions src/spanned.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,32 +20,30 @@ pub trait Spanned {
}

macro_rules! span_with_attrs_lo_hi {
($this:ident, $lo:expr, $hi:expr) => {
{
let attrs = outer_attributes(&$this.attrs);
if attrs.is_empty() {
mk_sp($lo, $hi)
} else {
mk_sp(attrs[0].span.lo(), $hi)
}
($this: ident, $lo: expr, $hi: expr) => {{
let attrs = outer_attributes(&$this.attrs);
if attrs.is_empty() {
mk_sp($lo, $hi)
} else {
mk_sp(attrs[0].span.lo(), $hi)
}
}
}};
}

macro_rules! span_with_attrs {
($this:ident) => {
($this: ident) => {
span_with_attrs_lo_hi!($this, $this.span.lo(), $this.span.hi())
}
};
}

macro_rules! implement_spanned {
($this:ty) => {
($this: ty) => {
impl Spanned for $this {
fn span(&self) -> Span {
span_with_attrs!(self)
}
}
}
};
}

// Implement `Spanned` for structs with `attrs` field.
Expand Down
Loading