Skip to content

Commit

Permalink
Support single-line functions
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
kyeah committed Nov 19, 2015
1 parent f09aa85 commit 4d7de5a
Show file tree
Hide file tree
Showing 17 changed files with 298 additions and 96 deletions.
1 change: 1 addition & 0 deletions src/config.rs
Expand Up @@ -269,6 +269,7 @@ create_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";
Expand Down
101 changes: 98 additions & 3 deletions src/items.rs
Expand Up @@ -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;

Expand Down Expand Up @@ -447,6 +447,81 @@ impl<'a> FmtVisitor<'a> {
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>,
Expand Down Expand Up @@ -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)
}
27 changes: 27 additions & 0 deletions src/utils.rs
Expand Up @@ -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
Expand Down
61 changes: 15 additions & 46 deletions src/visitor.rs
Expand Up @@ -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) => {
Expand Down Expand Up @@ -101,7 +88,7 @@ impl<'a> FmtVisitor<'a> {
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(";");
}
}
Expand Down Expand Up @@ -161,6 +148,13 @@ impl<'a> FmtVisitor<'a> {
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);
Expand Down Expand Up @@ -501,31 +495,6 @@ impl<'a> FmtVisitor<'a> {
}
}

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();
Expand Down
74 changes: 74 additions & 0 deletions tests/source/fn-single-line.rs
@@ -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) {
}
}
9 changes: 3 additions & 6 deletions tests/target/attrib.rs
Expand Up @@ -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.
Expand All @@ -28,8 +27,7 @@ impl Bar {
}

#[another_attribute]
fn f3(self) -> Dog {
}
fn f3(self) -> Dog { }

/// Blah blah bing.
#[attrib1]
Expand All @@ -38,6 +36,5 @@ impl Bar {
// Another comment that needs rewrite because it's tooooooooooooooooooooooooooooooo
// loooooooooooong.
/// Blah blah bing.
fn f4(self) -> Cat {
}
fn f4(self) -> Cat { }
}
3 changes: 1 addition & 2 deletions tests/target/comment.rs
Expand Up @@ -32,8 +32,7 @@ fn test() {
}

/// test123
fn doc_comment() {
}
fn doc_comment() { }

fn chains() {
foo.bar(|| {
Expand Down
6 changes: 2 additions & 4 deletions tests/target/comments-fn.rs
Expand Up @@ -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 */ { }

0 comments on commit 4d7de5a

Please sign in to comment.