diff --git a/src/items.rs b/src/items.rs index 1c5eaa2a6319..8435de621475 100644 --- a/src/items.rs +++ b/src/items.rs @@ -22,7 +22,7 @@ use config::{Config, BlockIndentStyle, Density, ReturnIndent, BraceStyle, StructLitStyle}; use syntax::codemap; -use syntax::{ast, abi}; +use syntax::{ast, abi, ptr}; use syntax::codemap::{Span, BytePos, mk_sp}; use syntax::parse::token; use syntax::ast::ImplItem; @@ -607,6 +607,133 @@ pub fn format_struct(context: &RewriteContext, } } +pub fn format_trait(context: &RewriteContext, item: &ast::Item, offset: Indent) -> Option { + if let ast::ItemKind::Trait(unsafety, ref generics, ref type_param_bounds, ref trait_items) = + item.node { + let mut result = String::new(); + let header = format!("{}{}trait {}", + format_visibility(item.vis), + format_unsafety(unsafety), + item.ident); + + result.push_str(&header); + + let body_lo = context.codemap.span_after(item.span, "{"); + + let generics_str = try_opt!(rewrite_generics(context, + generics, + offset, + context.config.max_width, + offset + result.len(), + mk_sp(item.span.lo, body_lo))); + result.push_str(&generics_str); + + let trait_bound_str = try_opt!(rewrite_trait_bounds(context, + type_param_bounds, + offset, + context.config.max_width)); + // If the trait, generics, and trait bound cannot fit on the same line, + // put the trait bounds on an indented new line + if offset.width() + last_line_width(&result) + trait_bound_str.len() > + context.config.ideal_width { + result.push('\n'); + let width = context.block_indent.width() + context.config.tab_spaces; + let trait_indent = Indent::new(0, width); + result.push_str(&trait_indent.to_string(context.config)); + } + result.push_str(&trait_bound_str); + + let has_body = !trait_items.is_empty(); + + let where_density = if (context.config.where_density == Density::Compressed && + (!result.contains('\n') || + context.config.fn_args_layout == StructLitStyle::Block)) || + (context.config.fn_args_layout == StructLitStyle::Block && + result.is_empty()) || + (context.config.where_density == Density::CompressedIfEmpty && + !has_body && + !result.contains('\n')) { + Density::Compressed + } else { + Density::Tall + }; + + let where_budget = try_opt!(context.config + .max_width + .checked_sub(last_line_width(&result))); + let where_clause_str = try_opt!(rewrite_where_clause(context, + &generics.where_clause, + context.config, + context.config.item_brace_style, + context.block_indent, + where_budget, + where_density, + "{", + has_body, + None)); + // If the where clause cannot fit on the same line, + // put the where clause on a new line + if !where_clause_str.contains('\n') && + last_line_width(&result) + where_clause_str.len() + offset.width() > + context.config.ideal_width { + result.push('\n'); + let width = context.block_indent.width() + context.config.tab_spaces - 1; + let where_indent = Indent::new(0, width); + result.push_str(&where_indent.to_string(context.config)); + } + result.push_str(&where_clause_str); + + match context.config.item_brace_style { + BraceStyle::AlwaysNextLine => { + result.push('\n'); + result.push_str(&offset.to_string(context.config)); + } + BraceStyle::PreferSameLine => result.push(' '), + BraceStyle::SameLineWhere => { + if !where_clause_str.is_empty() && + (trait_items.len() > 0 || result.contains('\n')) { + result.push('\n'); + result.push_str(&offset.to_string(context.config)); + } else { + result.push(' '); + } + } + } + result.push('{'); + + let snippet = context.snippet(item.span); + let open_pos = try_opt!(snippet.find_uncommented("{")) + 1; + + if !trait_items.is_empty() || contains_comment(&snippet[open_pos..]) { + let mut visitor = FmtVisitor::from_codemap(context.parse_session, context.config); + visitor.block_indent = context.block_indent.block_indent(context.config); + visitor.last_pos = item.span.lo + BytePos(open_pos as u32); + + for item in trait_items { + visitor.visit_trait_item(&item); + } + + visitor.format_missing(item.span.hi - BytePos(1)); + + let inner_indent_str = visitor.block_indent.to_string(context.config); + let outer_indent_str = context.block_indent.to_string(context.config); + + result.push('\n'); + result.push_str(&inner_indent_str); + result.push_str(&trim_newlines(&visitor.buffer.to_string().trim())); + result.push('\n'); + result.push_str(&outer_indent_str); + } else if result.contains('\n') { + result.push('\n'); + } + + result.push('}'); + Some(result) + } else { + unreachable!(); + } +} + fn format_unit_struct(item_name: &str, ident: ast::Ident, vis: ast::Visibility) -> Option { let mut result = String::with_capacity(1024); @@ -894,7 +1021,7 @@ pub fn rewrite_static(prefix: &str, ident: ast::Ident, ty: &ast::Ty, mutability: ast::Mutability, - expr: &ast::Expr, + expr_opt: Option<&ptr::P>, context: &RewriteContext) -> Option { let prefix = format!("{}{} {}{}: ", @@ -907,11 +1034,54 @@ pub fn rewrite_static(prefix: &str, context.config.max_width - context.block_indent.width() - prefix.len() - 2, context.block_indent)); - let lhs = format!("{}{} =", prefix, ty_str); - // 1 = ; - let remaining_width = context.config.max_width - context.block_indent.width() - 1; - rewrite_assign_rhs(context, lhs, expr, remaining_width, context.block_indent).map(|s| s + ";") + if let Some(ref expr) = expr_opt { + let lhs = format!("{}{} =", prefix, ty_str); + // 1 = ; + let remaining_width = context.config.max_width - context.block_indent.width() - 1; + rewrite_assign_rhs(context, lhs, expr, remaining_width, context.block_indent) + .map(|s| s + ";") + } else { + let lhs = format!("{}{};", prefix, ty_str); + Some(lhs) + } +} + +pub fn rewrite_associated_type(ident: ast::Ident, + ty_opt: Option<&ptr::P>, + ty_param_bounds_opt: Option<&ast::TyParamBounds>, + context: &RewriteContext, + indent: Indent) + -> Option { + let prefix = format!("type {}", ident); + + let type_bounds_str = if let Some(ty_param_bounds) = ty_param_bounds_opt { + let bounds: &[_] = &ty_param_bounds.as_slice(); + let bound_str = bounds.iter() + .filter_map(|ty_bound| { + ty_bound.rewrite(context, context.config.max_width, indent) + }) + .collect::>() + .join(" + "); + if bounds.len() > 0 { + format!(": {}", bound_str) + } else { + String::new() + } + } else { + String::new() + }; + + if let Some(ty) = ty_opt { + let ty_str = try_opt!(ty.rewrite(context, + context.config.max_width - context.block_indent.width() - + prefix.len() - + 2, + context.block_indent)); + Some(format!("{} = {};", prefix, ty_str)) + } else { + Some(format!("{}{};", prefix, type_bounds_str)) + } } impl Rewrite for ast::FunctionRetTy { @@ -1482,6 +1652,28 @@ fn rewrite_generics(context: &RewriteContext, Some(format!("<{}>", list_str)) } +fn rewrite_trait_bounds(context: &RewriteContext, + type_param_bounds: &ast::TyParamBounds, + indent: Indent, + width: usize) + -> Option { + let bounds: &[_] = &type_param_bounds.as_slice(); + + if bounds.is_empty() { + return Some(String::new()); + } + + let bound_str = bounds.iter() + .filter_map(|ty_bound| ty_bound.rewrite(&context, width, indent)) + .collect::>() + .join(" + "); + + let mut result = String::new(); + result.push_str(": "); + result.push_str(&bound_str); + Some(result) +} + fn rewrite_where_clause(context: &RewriteContext, where_clause: &ast::WhereClause, config: &Config, diff --git a/src/visitor.rs b/src/visitor.rs index acff5086aa29..62fbfd7613fa 100644 --- a/src/visitor.rs +++ b/src/visitor.rs @@ -21,7 +21,7 @@ use rewrite::{Rewrite, RewriteContext}; use comment::rewrite_comment; use macros::rewrite_macro; -use items::{rewrite_static, rewrite_type_alias, format_impl}; +use items::{rewrite_static, rewrite_associated_type, rewrite_type_alias, format_impl, format_trait}; pub struct FmtVisitor<'a> { pub parse_session: &'a ParseSess, @@ -207,14 +207,14 @@ fn visit_item(&mut self, item: &ast::Item) { self.last_pos = item.span.hi; } } - // FIXME(#78): format traits. - ast::ItemKind::Trait(_, _, _, ref trait_items) => { + ast::ItemKind::Trait(..) => { self.format_missing_with_indent(item.span.lo); - self.block_indent = self.block_indent.block_indent(self.config); - for item in trait_items { - self.visit_trait_item(&item); + if let Some(trait_str) = format_trait(&self.get_context(), + item, + self.block_indent) { + self.buffer.push_str(&trait_str); + self.last_pos = item.span.hi; } - self.block_indent = self.block_indent.block_unindent(self.config); } ast::ItemKind::ExternCrate(_) => { self.format_missing_with_indent(item.span.lo); @@ -266,7 +266,7 @@ fn visit_item(&mut self, item: &ast::Item) { item.ident, ty, mutability, - expr, + Some(expr), &self.get_context()); self.push_rewrite(item.span, rewrite); } @@ -276,7 +276,7 @@ fn visit_item(&mut self, item: &ast::Item) { item.ident, ty, ast::Mutability::Immutable, - expr, + Some(expr), &self.get_context()); self.push_rewrite(item.span, rewrite); } @@ -308,14 +308,21 @@ fn visit_item(&mut self, item: &ast::Item) { } } - fn visit_trait_item(&mut self, ti: &ast::TraitItem) { + pub fn visit_trait_item(&mut self, ti: &ast::TraitItem) { if self.visit_attrs(&ti.attrs) { return; } match ti.node { - ast::TraitItemKind::Const(..) => { - // FIXME: Implement + ast::TraitItemKind::Const(ref ty, ref expr_opt) => { + let rewrite = rewrite_static("const", + ast::Visibility::Inherited, + ti.ident, + ty, + ast::Mutability::Immutable, + expr_opt.as_ref(), + &self.get_context()); + self.push_rewrite(ti.span, rewrite); } ast::TraitItemKind::Method(ref sig, None) => { let indent = self.block_indent; @@ -329,8 +336,13 @@ fn visit_trait_item(&mut self, ti: &ast::TraitItem) { ti.span, ti.id); } - ast::TraitItemKind::Type(..) => { - // FIXME: Implement + ast::TraitItemKind::Type(ref type_param_bounds, _) => { + let rewrite = rewrite_associated_type(ti.ident, + None, + Some(type_param_bounds), + &self.get_context(), + self.block_indent); + self.push_rewrite(ti.span, rewrite); } } } @@ -348,11 +360,23 @@ pub fn visit_impl_item(&mut self, ii: &ast::ImplItem) { ii.span, ii.id); } - ast::ImplItemKind::Const(..) => { - // FIXME: Implement + ast::ImplItemKind::Const(ref ty, ref expr) => { + let rewrite = rewrite_static("const", + ast::Visibility::Inherited, + ii.ident, + ty, + ast::Mutability::Immutable, + Some(expr), + &self.get_context()); + self.push_rewrite(ii.span, rewrite); } - ast::ImplItemKind::Type(_) => { - // FIXME: Implement + ast::ImplItemKind::Type(ref ty) => { + let rewrite = rewrite_associated_type(ii.ident, + Some(ty), + None, + &self.get_context(), + self.block_indent); + self.push_rewrite(ii.span, rewrite); } ast::ImplItemKind::Macro(ref mac) => { self.format_missing_with_indent(ii.span.lo); diff --git a/tests/source/impls.rs b/tests/source/impls.rs index d04b5ce1eebd..a3a8033b07f2 100644 --- a/tests/source/impls.rs +++ b/tests/source/impls.rs @@ -1,6 +1,10 @@ impl Foo for Bar { fn foo() { "hi" } } pub impl Foo for Bar { + // Associated Constants + const Baz: i32 = 16; + // Associated Types + type FooBar = usize; // Comment 1 fn foo() { "hi" } // Comment 2 diff --git a/tests/source/trait.rs b/tests/source/trait.rs index 5e7a0c78ba70..faac57179c20 100644 --- a/tests/source/trait.rs +++ b/tests/source/trait.rs @@ -35,3 +35,21 @@ trait TraitWithExpr { trait Test { fn read_struct(&mut self, s_name: &str, len: usize, f: F) -> Result where F: FnOnce(&mut Self) -> Result; } + +trait T<> {} + +trait Foo { type Bar: Baz;} + +trait ConstCheck:Foo where T: Baz { + const J: i32; +} + +trait Tttttttttttttttttttttttttttttttttttttttttttttttttttttttttt + where T: Foo {} + +trait Ttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt where T: Foo {} + +trait FooBar : Tttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt where J: Bar { fn test(); } + +trait WhereList where T: Foo, J: Bar {} + diff --git a/tests/source/where-trailing-comma.rs b/tests/source/where-trailing-comma.rs index 8f951d199e42..c2c1a3185bcb 100644 --- a/tests/source/where-trailing-comma.rs +++ b/tests/source/where-trailing-comma.rs @@ -20,7 +20,7 @@ struct Pair where T: P, S: P + Q { struct TupPair (S, T) where T: P, S: P + Q; -enum E where S: P, T: P { +enum E where S: P, T: P { A {a: T}, } @@ -30,7 +30,6 @@ enum E where S: P, T: P { fn f(x: T, y: S) -> T where T: P, S: Q; } -// Note: trait declarations are not fully formatted (issue #78) trait Q where T: P, S: R { fn f(self, x: T, y: S, z: U) -> Self where U: P, V: P; diff --git a/tests/target/impls.rs b/tests/target/impls.rs index 33bc74819050..c81de4d40fea 100644 --- a/tests/target/impls.rs +++ b/tests/target/impls.rs @@ -5,6 +5,10 @@ fn foo() { } pub impl Foo for Bar { + // Associated Constants + const Baz: i32 = 16; + // Associated Types + type FooBar = usize; // Comment 1 fn foo() { "hi" diff --git a/tests/target/trait.rs b/tests/target/trait.rs index 241f5d05b7c8..d8acc1ba229b 100644 --- a/tests/target/trait.rs +++ b/tests/target/trait.rs @@ -34,3 +34,35 @@ trait Test { fn read_struct(&mut self, s_name: &str, len: usize, f: F) -> Result where F: FnOnce(&mut Self) -> Result; } + +trait T {} + +trait Foo { + type Bar: Baz; +} + +trait ConstCheck: Foo + where T: Baz +{ + const J: i32; +} + +trait Tttttttttttttttttttttttttttttttttttttttttttttttttttttttttt where T: Foo {} + +trait Ttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt + where T: Foo +{ +} + +trait FooBar + : Tttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt + where J: Bar +{ + fn test(); +} + +trait WhereList + where T: Foo, + J: Bar +{ +} diff --git a/tests/target/where-trailing-comma.rs b/tests/target/where-trailing-comma.rs index bd7108f992a8..c8682237ae6a 100644 --- a/tests/target/where-trailing-comma.rs +++ b/tests/target/where-trailing-comma.rs @@ -48,8 +48,9 @@ fn f(x: T, y: S) -> T S: Q; } -// Note: trait declarations are not fully formatted (issue #78) -trait Q where T: P, S: R +trait Q + where T: P, + S: R, { fn f(self, x: T, y: S, z: U) -> Self where U: P,