Support single-line functions

By default, places functions with empty bodies on one line.
If the function has only one expression or statement that fits on one line, the 'fn_single_line' option can be used.
This commit is contained in:
Kevin Yeh 2015-11-17 22:53:06 -06:00
parent f09aa85798
commit 4d7de5a16e
17 changed files with 298 additions and 96 deletions

View File

@ -269,6 +269,7 @@ fn default() -> Config {
newline_style: NewlineStyle, NewlineStyle::Unix, "Unix or Windows line endings";
fn_brace_style: BraceStyle, BraceStyle::SameLineWhere, "Brace style for functions";
item_brace_style: BraceStyle, BraceStyle::SameLineWhere, "Brace style for structs and enums";
fn_single_line: bool, false, "Put single-expression functions on a single line";
fn_return_indent: ReturnIndent, ReturnIndent::WithArgs,
"Location of return type in function declaration";
fn_args_paren_newline: bool, true, "If function argument parenthesis goes on a newline";

View File

@ -12,17 +12,17 @@
use Indent;
use utils::{format_mutability, format_visibility, contains_skip, span_after, end_typaram,
wrap_str, last_line_width};
wrap_str, last_line_width, semicolon_for_expr, semicolon_for_stmt};
use lists::{write_list, itemize_list, ListItem, ListFormatting, SeparatorTactic,
DefinitiveListTactic, definitive_tactic, format_item_list};
use expr::rewrite_assign_rhs;
use comment::FindUncommented;
use comment::{FindUncommented, contains_comment};
use visitor::FmtVisitor;
use rewrite::{Rewrite, RewriteContext};
use config::{Config, BlockIndentStyle, Density, ReturnIndent, BraceStyle, StructLitStyle};
use syntax::{ast, abi};
use syntax::codemap::{Span, BytePos, mk_sp};
use syntax::codemap::{Span, BytePos, CodeMap, mk_sp};
use syntax::print::pprust;
use syntax::parse::token;
@ -447,6 +447,81 @@ fn rewrite_fn_base(&mut self,
Some((result, force_new_line_for_brace))
}
pub fn rewrite_single_line_fn(&self,
fn_rewrite: &Option<String>,
block: &ast::Block)
-> Option<String> {
let fn_str = match *fn_rewrite {
Some(ref s) if !s.contains('\n') => s,
_ => return None,
};
let codemap = self.get_context().codemap;
if is_empty_block(block, codemap) &&
self.block_indent.width() + fn_str.len() + 3 <= self.config.max_width {
return Some(format!("{}{{ }}", fn_str));
}
if self.config.fn_single_line && is_simple_block_stmt(block, codemap) {
let rewrite = {
if let Some(ref e) = block.expr {
let suffix = if semicolon_for_expr(e) {
";"
} else {
""
};
e.rewrite(&self.get_context(),
self.config.max_width - self.block_indent.width(),
self.block_indent)
.map(|s| s + suffix)
.or_else(|| Some(self.snippet(e.span)))
} else if let Some(ref stmt) = block.stmts.first() {
self.rewrite_stmt(stmt)
} else {
None
}
};
if let Some(res) = rewrite {
let width = self.block_indent.width() + fn_str.len() + res.len() + 3;
if !res.contains('\n') && width <= self.config.max_width {
return Some(format!("{}{{ {} }}", fn_str, res));
}
}
}
None
}
pub fn rewrite_stmt(&self, stmt: &ast::Stmt) -> Option<String> {
match stmt.node {
ast::Stmt_::StmtDecl(ref decl, _) => {
if let ast::Decl_::DeclLocal(ref local) = decl.node {
let context = self.get_context();
local.rewrite(&context, self.config.max_width, self.block_indent)
} else {
None
}
}
ast::Stmt_::StmtExpr(ref ex, _) | ast::Stmt_::StmtSemi(ref ex, _) => {
let suffix = if semicolon_for_stmt(stmt) {
";"
} else {
""
};
ex.rewrite(&self.get_context(),
self.config.max_width - self.block_indent.width() - suffix.len(),
self.block_indent)
.map(|s| s + suffix)
}
ast::Stmt_::StmtMac(..) => None,
}
}
fn rewrite_args(&self,
args: &[ast::Arg],
explicit_self: Option<&ast::ExplicitSelf>,
@ -1317,3 +1392,23 @@ fn span_for_where_pred(pred: &ast::WherePredicate) -> Span {
ast::WherePredicate::EqPredicate(ref p) => p.span,
}
}
// Checks whether a block contains at most one statement or expression, and no comments.
fn is_simple_block_stmt(block: &ast::Block, codemap: &CodeMap) -> bool {
if (!block.stmts.is_empty() && block.expr.is_some()) ||
(block.stmts.len() != 1 && block.expr.is_none()) {
return false;
}
let snippet = codemap.span_to_snippet(block.span).unwrap();
!contains_comment(&snippet)
}
fn is_empty_block(block: &ast::Block, codemap: &CodeMap) -> bool {
if !block.stmts.is_empty() || block.expr.is_some() {
return false;
}
let snippet = codemap.span_to_snippet(block.span).unwrap();
!contains_comment(&snippet)
}

View File

@ -102,6 +102,33 @@ pub fn end_typaram(typaram: &ast::TyParam) -> BytePos {
.hi
}
#[inline]
pub fn semicolon_for_expr(expr: &ast::Expr) -> bool {
match expr.node {
ast::Expr_::ExprRet(..) |
ast::Expr_::ExprAgain(..) |
ast::Expr_::ExprBreak(..) => true,
_ => false,
}
}
#[inline]
pub fn semicolon_for_stmt(stmt: &ast::Stmt) -> bool {
match stmt.node {
ast::Stmt_::StmtSemi(ref expr, _) => {
match expr.node {
ast::Expr_::ExprWhile(..) |
ast::Expr_::ExprWhileLet(..) |
ast::Expr_::ExprLoop(..) |
ast::Expr_::ExprForLoop(..) => false,
_ => true,
}
}
ast::Stmt_::StmtExpr(..) => false,
_ => true,
}
}
#[inline]
#[cfg(target_pointer_width="64")]
// Based on the trick layed out at

View File

@ -38,28 +38,15 @@ impl<'a> FmtVisitor<'a> {
fn visit_stmt(&mut self, stmt: &ast::Stmt) {
match stmt.node {
ast::Stmt_::StmtDecl(ref decl, _) => {
match decl.node {
ast::Decl_::DeclLocal(ref local) => {
let rewrite = {
let context = self.get_context();
local.rewrite(&context, self.config.max_width, self.block_indent)
};
self.push_rewrite(stmt.span, rewrite);
}
ast::Decl_::DeclItem(ref item) => self.visit_item(item),
if let ast::Decl_::DeclItem(ref item) = decl.node {
self.visit_item(item);
} else {
let rewrite = self.rewrite_stmt(stmt);
self.push_rewrite(stmt.span, rewrite);
}
}
ast::Stmt_::StmtExpr(ref ex, _) | ast::Stmt_::StmtSemi(ref ex, _) => {
let suffix = if semicolon_for_stmt(stmt) {
";"
} else {
""
};
let rewrite = ex.rewrite(&self.get_context(),
self.config.max_width - self.block_indent.width() -
suffix.len(),
self.block_indent)
.map(|s| s + suffix);
ast::Stmt_::StmtExpr(..) | ast::Stmt_::StmtSemi(..) => {
let rewrite = self.rewrite_stmt(stmt);
self.push_rewrite(stmt.span, rewrite);
}
ast::Stmt_::StmtMac(ref mac, _macro_style) => {
@ -101,7 +88,7 @@ pub fn visit_block(&mut self, b: &ast::Block) {
self.buffer.push_str(&rewrite);
self.last_pos = e.span.hi;
if semicolon_for_expr(e) {
if utils::semicolon_for_expr(e) {
self.buffer.push_str(";");
}
}
@ -161,6 +148,13 @@ fn visit_fn(&mut self,
visit::FnKind::Closure => None,
};
if let Some(ref single_line_fn) = self.rewrite_single_line_fn(&rewrite, &b) {
self.format_missing_with_indent(s.lo);
self.buffer.push_str(single_line_fn);
self.last_pos = b.span.hi;
return;
}
if let Some(fn_str) = rewrite {
self.format_missing_with_indent(s.lo);
self.buffer.push_str(&fn_str);
@ -501,31 +495,6 @@ pub fn get_context(&self) -> RewriteContext {
}
}
fn semicolon_for_stmt(stmt: &ast::Stmt) -> bool {
match stmt.node {
ast::Stmt_::StmtSemi(ref expr, _) => {
match expr.node {
ast::Expr_::ExprWhile(..) |
ast::Expr_::ExprWhileLet(..) |
ast::Expr_::ExprLoop(..) |
ast::Expr_::ExprForLoop(..) => false,
_ => true,
}
}
ast::Stmt_::StmtExpr(..) => false,
_ => true,
}
}
fn semicolon_for_expr(expr: &ast::Expr) -> bool {
match expr.node {
ast::Expr_::ExprRet(..) |
ast::Expr_::ExprAgain(..) |
ast::Expr_::ExprBreak(..) => true,
_ => false,
}
}
impl<'a> Rewrite for [ast::Attribute] {
fn rewrite(&self, context: &RewriteContext, _: usize, offset: Indent) -> Option<String> {
let mut result = String::new();

View File

@ -0,0 +1,74 @@
// rustfmt-fn_single_line: true
// Test single-line functions.
fn foo_expr() {
1
}
fn foo_stmt() {
foo();
}
fn foo_decl_local() {
let z = 5;
}
fn foo_decl_item(x: &mut i32) {
x = 3;
}
fn empty() {
}
fn foo_return() -> String {
"yay"
}
fn foo_where() -> T where T: Sync {
let x = 2;
}
fn fooblock() {
{
"inner-block"
}
}
fn fooblock2(x: i32) {
let z = match x {
_ => 2,
};
}
fn comment() {
// this is a test comment
1
}
fn comment2() {
// multi-line comment
let z = 2;
1
}
fn only_comment() {
// Keep this here
}
fn aaaaaaaaaaaaaaaaa_looooooooooooooooooooooong_name() {
let z = "aaaaaaawwwwwwwwwwwwwwwwwwwwwwwwwwww";
}
fn lots_of_space () {
1
}
trait CoolTypes {
fn dummy(&self) {
}
}
trait CoolerTypes { fn dummy(&self) {
}
}

View File

@ -13,8 +13,7 @@ impl Bar {
/// Blah blah blooo.
/// Blah blah blooo.
#[an_attribute]
fn foo(&mut self) -> isize {
}
fn foo(&mut self) -> isize { }
/// Blah blah bing.
/// Blah blah bing.
@ -28,8 +27,7 @@ pub fn f2(self) {
}
#[another_attribute]
fn f3(self) -> Dog {
}
fn f3(self) -> Dog { }
/// Blah blah bing.
#[attrib1]
@ -38,6 +36,5 @@ fn f3(self) -> Dog {
// Another comment that needs rewrite because it's tooooooooooooooooooooooooooooooo
// loooooooooooong.
/// Blah blah bing.
fn f4(self) -> Cat {
}
fn f4(self) -> Cat { }
}

View File

@ -32,8 +32,7 @@ fn test() {
}
/// test123
fn doc_comment() {
}
fn doc_comment() { }
fn chains() {
foo.bar(|| {

View File

@ -16,8 +16,6 @@ fn foo<F, G>(a: aaaaaaaaaaaaa, // A comment
}
fn bar<F /* comment on F */, G /* comment on G */>() {
}
fn bar<F /* comment on F */, G /* comment on G */>() { }
fn baz() -> Baz /* Comment after return type */ {
}
fn baz() -> Baz /* Comment after return type */ { }

View File

@ -28,15 +28,13 @@ fn generic<T>(arg: T) -> &SomeType
arg(a, b, c, d, e)
}
fn foo() -> ! {
}
fn foo() -> ! { }
pub fn http_fetch_async(listener: Box<AsyncCORSResponseListener + Send>,
script_chan: Box<ScriptChan + Send>) {
}
fn some_func<T: Box<Trait + Bound>>(val: T) {
}
fn some_func<T: Box<Trait + Bound>>(val: T) { }
fn zzzzzzzzzzzzzzzzzzzz<Type, NodeType>(selff: Type,
mut handle: node::Handle<IdRef<'id, Node<K, V>>,

View File

@ -0,0 +1,61 @@
// rustfmt-fn_single_line: true
// Test single-line functions.
fn foo_expr() { 1 }
fn foo_stmt() { foo(); }
fn foo_decl_local() { let z = 5; }
fn foo_decl_item(x: &mut i32) { x = 3; }
fn empty() { }
fn foo_return() -> String { "yay" }
fn foo_where() -> T
where T: Sync
{
let x = 2;
}
fn fooblock() {
{
"inner-block"
}
}
fn fooblock2(x: i32) {
let z = match x {
_ => 2,
};
}
fn comment() {
// this is a test comment
1
}
fn comment2() {
// multi-line comment
let z = 2;
1
}
fn only_comment() {
// Keep this here
}
fn aaaaaaaaaaaaaaaaa_looooooooooooooooooooooong_name() {
let z = "aaaaaaawwwwwwwwwwwwwwwwwwwwwwwwwwww";
}
fn lots_of_space() { 1 }
trait CoolTypes {
fn dummy(&self) { }
}
trait CoolerTypes {
fn dummy(&self) { }
}

View File

@ -1,8 +1,6 @@
// Tests different fns
fn foo(a: AAAA, b: BBB, c: CCC) -> RetType {
}
fn foo(a: AAAA, b: BBB, c: CCC) -> RetType { }
fn foo(a: AAAA, b: BBB /* some, weird, inline comment */, c: CCC) -> RetType
where T: Blah
@ -34,8 +32,7 @@ fn foo<U, T>(a: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,
}
fn foo<U: Fn(A) -> B /* paren inside generics */>() {
}
fn foo<U: Fn(A) -> B /* paren inside generics */>() { }
impl Foo {
fn with_no_errors<T, F>(&mut self, f: F) -> T
@ -43,11 +40,9 @@ fn with_no_errors<T, F>(&mut self, f: F) -> T
{
}
fn foo(mut self, mut bar: u32) {
}
fn foo(mut self, mut bar: u32) { }
fn bar(self, mut bazz: u32) {
}
fn bar(self, mut bazz: u32) { }
}
pub fn render<'a,
@ -75,12 +70,9 @@ const fn foo() {
}
}
fn homura<T: Deref<Target = i32>>(_: T) {
fn homura<T: Deref<Target = i32>>(_: T) { }
}
fn issue377() -> (Box<CompositorProxy + Send>, Box<CompositorReceiver>) {
}
fn issue377() -> (Box<CompositorProxy + Send>, Box<CompositorReceiver>) { }
fn main() {
let _ = function(move || 5);

View File

@ -26,9 +26,7 @@
// sfdgfffffffffffffffffffffffffffffffffffffffffffffffffffffff
// ffffffffffffffffffffffffffffffffffffffffff
fn foo(a: isize, b: u32 /* blah blah */, c: f64) {
}
fn foo(a: isize, b: u32 /* blah blah */, c: f64) { }
fn foo() -> Box<Write + 'static>
where 'a: 'b,
@ -77,8 +75,7 @@ pub fn f2(self) {
}
#[an_attribute]
fn f3(self) -> Dog {
}
fn f3(self) -> Dog { }
}
/// The `nodes` and `edges` method each return instantiations of
@ -118,8 +115,7 @@ pub struct Foo<'a, Y: Baz>
f: SomeType, // Comment beside a field
}
fn foo(ann: &'a (PpAnn + 'a)) {
}
fn foo(ann: &'a (PpAnn + 'a)) { }
fn main() {
for i in 0i32..4 {

View File

@ -1,4 +1,3 @@
// A standard mod
fn a() {
}
fn a() { }

View File

@ -1,3 +1,2 @@
// Another mod
fn a() {
}
fn a() { }

View File

@ -3,5 +3,4 @@
use c::a;
fn foo() {
}
fn foo() { }

View File

@ -1,2 +1 @@
fn main() {
}
fn main() { }

View File

@ -19,5 +19,4 @@ fn main() {
let x: Foo<A>;
}
fn op(foo: Bar, key: &[u8], upd: Fn(Option<&memcache::Item>, Baz) -> Result) -> MapResult {
}
fn op(foo: Bar, key: &[u8], upd: Fn(Option<&memcache::Item>, Baz) -> Result) -> MapResult { }