about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorMarcus Klaas <mail@marcusklaas.nl>2015-08-14 14:09:19 +0200
committerMarcus Klaas <mail@marcusklaas.nl>2015-08-16 22:53:06 +0200
commit41bca58100260323e54ff83e5bc74dd920beabcd (patch)
treef3aa2bd2357772460166aebde44155454aaab3a4 /src
parent78b38c84c620a3019473463a5ff9120941604ec2 (diff)
Format paths
Diffstat (limited to 'src')
-rw-r--r--src/expr.rs32
-rw-r--r--src/imports.rs27
-rw-r--r--src/items.rs40
-rw-r--r--src/lists.rs2
-rw-r--r--src/types.rs396
-rw-r--r--src/utils.rs10
-rw-r--r--src/visitor.rs16
7 files changed, 431 insertions, 92 deletions
diff --git a/src/expr.rs b/src/expr.rs
index 334c509c59d..4cfa5a0b879 100644
--- a/src/expr.rs
+++ b/src/expr.rs
@@ -12,14 +12,14 @@ use rewrite::{Rewrite, RewriteContext};
 use lists::{write_list, itemize_list, ListFormatting, SeparatorTactic, ListTactic};
 use string::{StringFormat, rewrite_string};
 use StructLitStyle;
-use utils::{span_after, make_indent};
+use utils::{span_after, make_indent, extra_offset};
 use visitor::FmtVisitor;
 use config::BlockIndentStyle;
 use comment::{FindUncommented, rewrite_comment};
+use types::rewrite_path;
 
 use syntax::{ast, ptr};
 use syntax::codemap::{Pos, Span, BytePos, mk_sp};
-use syntax::print::pprust;
 use syntax::visit::Visitor;
 
 impl Rewrite for ast::Expr {
@@ -99,6 +99,9 @@ impl Rewrite for ast::Expr {
                               width,
                               offset)
             }
+            ast::Expr_::ExprPath(ref qself, ref path) => {
+                rewrite_path(context, qself.as_ref(), path, width, offset)
+            }
             _ => context.codemap.span_to_snippet(self.span).ok()
         }
     }
@@ -320,11 +323,7 @@ fn rewrite_pat_expr(context: &RewriteContext,
     };
 
     // Consider only the last line of the pat string.
-    let extra_offset = match result.rfind('\n') {
-        // 1 for newline character
-        Some(idx) => result.len() - idx - 1 - offset,
-        None => result.len()
-    };
+    let extra_offset = extra_offset(&result, offset);
 
     // The expression may (partionally) fit on the current line.
     if width > extra_offset + 1 {
@@ -391,16 +390,19 @@ fn rewrite_call(context: &RewriteContext,
     debug!("rewrite_call, width: {}, offset: {}", width, offset);
 
     // TODO using byte lens instead of char lens (and probably all over the place too)
-    let callee_str = try_opt!(callee.rewrite(context, width, offset));
+    // 2 is for parens
+    let max_callee_width = try_opt!(width.checked_sub(2));
+    let callee_str = try_opt!(callee.rewrite(context, max_callee_width, offset));
     debug!("rewrite_call, callee_str: `{}`", callee_str);
 
     if args.len() == 0 {
         return Some(format!("{}()", callee_str));
     }
 
+    let extra_offset = extra_offset(&callee_str, offset);
     // 2 is for parens.
-    let remaining_width = try_opt!(width.checked_sub(callee_str.len() + 2));
-    let offset = callee_str.len() + 1 + offset;
+    let remaining_width = try_opt!(width.checked_sub(extra_offset + 2));
+    let offset = offset + extra_offset + 1;
     let block_indent = expr_block_indent(context, offset);
     let inner_context = &RewriteContext { block_indent: block_indent, ..*context };
 
@@ -425,7 +427,7 @@ fn rewrite_call(context: &RewriteContext,
         indent: offset,
         h_width: remaining_width,
         v_width: remaining_width,
-        ends_with_newline: true,
+        ends_with_newline: false,
     };
 
     Some(format!("{}({})", callee_str, write_list(&items, &fmt)))
@@ -468,7 +470,9 @@ fn rewrite_struct_lit<'a>(context: &RewriteContext,
         Base(&'a ast::Expr),
     }
 
-    let path_str = pprust::path_to_string(path);
+    // 2 = " {".len()
+    let path_str = try_opt!(path.rewrite(context, width - 2, offset));
+
     // Foo { a: Foo } - indent is +3, width is -5.
     let h_budget = width.checked_sub(path_str.len() + 5).unwrap_or(0);
     let (indent, v_budget) = match context.config.struct_lit_style {
@@ -537,7 +541,7 @@ fn rewrite_struct_lit<'a>(context: &RewriteContext,
         indent: indent,
         h_width: h_budget,
         v_width: v_budget,
-        ends_with_newline: true,
+        ends_with_newline: false,
     };
     let fields_str = write_list(&items, &fmt);
 
@@ -601,7 +605,7 @@ fn rewrite_tuple_lit(context: &RewriteContext,
         indent: indent,
         h_width: width - 2,
         v_width: width - 2,
-        ends_with_newline: true,
+        ends_with_newline: false,
     };
 
     Some(format!("({})", write_list(&items, &fmt)))
diff --git a/src/imports.rs b/src/imports.rs
index 958dfadff66..e3629410c6c 100644
--- a/src/imports.rs
+++ b/src/imports.rs
@@ -11,11 +11,9 @@
 use lists::{write_list, itemize_list, ListItem, ListFormatting, SeparatorTactic, ListTactic};
 use utils::span_after;
 use rewrite::{Rewrite, RewriteContext};
-use config::Config;
 
 use syntax::ast;
-use syntax::print::pprust;
-use syntax::codemap::{CodeMap, Span};
+use syntax::codemap::Span;
 
 // TODO (some day) remove unused imports, expand globs, compress many single imports into a list import
 
@@ -29,20 +27,21 @@ impl Rewrite for ast::ViewPath {
                                       path,
                                       path_list,
                                       self.span,
-                                      context.codemap,
-                                      context.config).unwrap_or("".to_owned()))
+                                      context).unwrap_or("".to_owned()))
             }
             ast::ViewPath_::ViewPathGlob(_) => {
                 // FIXME convert to list?
                 None
             }
             ast::ViewPath_::ViewPathSimple(ident, ref path) => {
-                let path_str = pprust::path_to_string(path);
+                let ident_str = ident.to_string();
+                // 4 = " as ".len()
+                let path_str = try_opt!(path.rewrite(context, width - ident_str.len() - 4, offset));
 
                 Some(if path.segments.last().unwrap().identifier == ident {
                          path_str
                      } else {
-                         format!("{} as {}", path_str, ident)
+                         format!("{} as {}", path_str, ident_str)
                      })
             }
         }
@@ -74,10 +73,10 @@ pub fn rewrite_use_list(width: usize,
                         path: &ast::Path,
                         path_list: &[ast::PathListItem],
                         span: Span,
-                        codemap: &CodeMap,
-                        config: &Config)
+                        context: &RewriteContext)
                         -> Option<String> {
-    let path_str = pprust::path_to_string(path);
+    // 1 = {}
+    let path_str = try_opt!(path.rewrite(context, width - 1, offset));
 
     match path_list.len() {
         0 => return None,
@@ -106,10 +105,10 @@ pub fn rewrite_use_list(width: usize,
         // available
         // (loose 1 column (";"))
         v_width: remaining_width,
-        ends_with_newline: true,
+        ends_with_newline: false,
     };
 
-    let mut items = itemize_list(codemap,
+    let mut items = itemize_list(context.codemap,
                                  vec![ListItem::from_str("")], /* Dummy value, explanation
                                                                 * below */
                                  path_list.iter(),
@@ -125,7 +124,7 @@ pub fn rewrite_use_list(width: usize,
                                          "self".to_owned()
                                      }
                                  },
-                                 span_after(span, "{", codemap),
+                                 span_after(span, "{", context.codemap),
                                  span.hi);
 
     // We prefixed the item list with a dummy value so that we can
@@ -140,7 +139,7 @@ pub fn rewrite_use_list(width: usize,
         1
     };
 
-    if config.reorder_imports {
+    if context.config.reorder_imports {
         items[1..].sort_by(|a, b| a.item.cmp(&b.item));
     }
 
diff --git a/src/items.rs b/src/items.rs
index 55b55918131..bbe80bf1231 100644
--- a/src/items.rs
+++ b/src/items.rs
@@ -16,6 +16,8 @@ use utils::{format_mutability, format_visibility, make_indent, contains_skip, sp
 use lists::{write_list, itemize_list, ListItem, ListFormatting, SeparatorTactic, ListTactic};
 use comment::FindUncommented;
 use visitor::FmtVisitor;
+use rewrite::Rewrite;
+use config::Config;
 
 use syntax::{ast, abi};
 use syntax::codemap::{self, Span, BytePos};
@@ -215,6 +217,7 @@ impl<'a> FmtVisitor<'a> {
 
         // Where clause.
         result.push_str(&self.rewrite_where_clause(where_clause,
+                                                   self.config,
                                                    indent,
                                                    span.hi));
 
@@ -281,7 +284,7 @@ impl<'a> FmtVisitor<'a> {
             indent: arg_indent,
             h_width: one_line_budget,
             v_width: multi_line_budget,
-            ends_with_newline: true,
+            ends_with_newline: false,
         };
 
         write_list(&arg_items, &fmt)
@@ -429,7 +432,7 @@ impl<'a> FmtVisitor<'a> {
                         indent: indent,
                         h_width: budget,
                         v_width: budget,
-                        ends_with_newline: false,
+                        ends_with_newline: true,
                     };
                     result.push_str(&write_list(&items, &fmt));
                     result.push(')');
@@ -557,7 +560,7 @@ impl<'a> FmtVisitor<'a> {
             indent: offset + self.config.tab_spaces,
             h_width: self.config.max_width,
             v_width: budget,
-            ends_with_newline: false,
+            ends_with_newline: true,
         };
 
         result.push_str(&write_list(&items, &fmt));
@@ -608,6 +611,7 @@ impl<'a> FmtVisitor<'a> {
 
         if generics.where_clause.predicates.len() > 0 || result.contains('\n') {
             result.push_str(&self.rewrite_where_clause(&generics.where_clause,
+                                                       self.config,
                                                        self.block_indent,
                                                        span.hi));
             result.push_str(&make_indent(self.block_indent));
@@ -664,8 +668,15 @@ impl<'a> FmtVisitor<'a> {
         result.push('<');
 
         // Strings for the generics.
-        let lt_strs = lifetimes.iter().map(|l| self.rewrite_lifetime_def(l));
-        let ty_strs = tys.iter().map(|ty| self.rewrite_ty_param(ty));
+        // 1 = <
+        let context = self.get_context();
+        // FIXME: don't unwrap
+        let lt_strs = lifetimes.iter().map(|lt| {
+            lt.rewrite(&context, budget, offset + 1).unwrap()
+        });
+        let ty_strs = tys.iter().map(|ty_param| {
+            ty_param.rewrite(&context, budget, offset + 1).unwrap()
+        });
 
         // Extract comments between generics.
         let lt_spans = lifetimes.iter().map(|l| {
@@ -700,7 +711,7 @@ impl<'a> FmtVisitor<'a> {
             indent: offset + 1,
             h_width: budget,
             v_width: budget,
-            ends_with_newline: true,
+            ends_with_newline: false,
         };
         result.push_str(&write_list(&items, &fmt));
 
@@ -711,6 +722,7 @@ impl<'a> FmtVisitor<'a> {
 
     fn rewrite_where_clause(&self,
                             where_clause: &ast::WhereClause,
+                            config: &Config,
                             indent: usize,
                             span_end: BytePos)
                             -> String {
@@ -720,9 +732,13 @@ impl<'a> FmtVisitor<'a> {
         }
 
         result.push('\n');
-        result.push_str(&make_indent(indent + 4));
+        result.push_str(&make_indent(indent + config.tab_spaces));
         result.push_str("where ");
 
+        let context = self.get_context();
+        // 6 = "where ".len()
+        let offset = indent + config.tab_spaces + 6;
+        let budget = self.config.ideal_width + self.config.leeway - offset;
         let span_start = span_for_where_pred(&where_clause.predicates[0]).lo;
         let items = itemize_list(self.codemap,
                                  Vec::new(),
@@ -731,19 +747,21 @@ impl<'a> FmtVisitor<'a> {
                                  "{",
                                  |pred| span_for_where_pred(pred).lo,
                                  |pred| span_for_where_pred(pred).hi,
-                                 |pred| self.rewrite_pred(pred),
+                                 // FIXME: we should handle failure better
+                                 // this will be taken care of when write_list
+                                 // takes Rewrite object: see issue #133
+                                 |pred| pred.rewrite(&context, budget, offset).unwrap(),
                                  span_start,
                                  span_end);
 
-        let budget = self.config.ideal_width + self.config.leeway - indent - 10;
         let fmt = ListFormatting {
             tactic: ListTactic::Vertical,
             separator: ",",
             trailing_separator: SeparatorTactic::Never,
-            indent: indent + 10,
+            indent: offset,
             h_width: budget,
             v_width: budget,
-            ends_with_newline: true,
+            ends_with_newline: false,
         };
         result.push_str(&write_list(&items, &fmt));
 
diff --git a/src/lists.rs b/src/lists.rs
index 13c0cf763e3..6398c19c9ea 100644
--- a/src/lists.rs
+++ b/src/lists.rs
@@ -211,7 +211,7 @@ pub fn write_list<'b>(items: &[ListItem], formatting: &ListFormatting<'b>) -> St
             let offset = formatting.indent + item_width + 1;
             let comment = item.post_comment.as_ref().unwrap();
             // Use block-style only for the last item or multiline comments.
-            let block_style = formatting.ends_with_newline && last ||
+            let block_style = !formatting.ends_with_newline && last ||
                               comment.trim().contains('\n') || comment.trim().len() > width;
 
             let formatted_comment = rewrite_comment(comment, block_style, width, offset);
diff --git a/src/types.rs b/src/types.rs
index c2e27c31684..4917d6ac3c9 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -8,33 +8,311 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
-use visitor::FmtVisitor;
+use std::fmt;
 
 use syntax::ast;
 use syntax::print::pprust;
+use syntax::codemap::{self, Span, BytePos, CodeMap};
 
-impl<'a> FmtVisitor<'a> {
-    pub fn rewrite_pred(&self, predicate: &ast::WherePredicate) -> String {
-        // TODO dead spans
+use lists::{itemize_list, write_list, ListTactic, SeparatorTactic, ListFormatting};
+use rewrite::{Rewrite, RewriteContext};
+use utils::{extra_offset, span_after};
+
+impl Rewrite for ast::Path {
+    fn rewrite(&self, context: &RewriteContext, width: usize, offset: usize) -> Option<String> {
+        rewrite_path(context, None, self, width, offset)
+    }
+}
+
+// Does not wrap on simple segments.
+pub fn rewrite_path(context: &RewriteContext,
+                    qself: Option<&ast::QSelf>,
+                    path: &ast::Path,
+                    width: usize,
+                    offset: usize)
+                    -> Option<String> {
+    let skip_count = qself.map(|x| x.position).unwrap_or(0);
+
+    let mut result = if path.global {
+        "::".to_owned()
+    } else {
+        String::new()
+    };
+
+    let mut span_lo = path.span.lo;
+
+    if let Some(ref qself) = qself {
+        result.push('<');
+        result.push_str(&pprust::ty_to_string(&qself.ty));
+        result.push_str(" as ");
+
+        let extra_offset = extra_offset(&result, offset);
+        // 3 = ">::".len()
+        let budget = try_opt!(width.checked_sub(extra_offset)) - 3;
+
+        result = try_opt!(rewrite_path_segments(result,
+                                                path.segments.iter().take(skip_count),
+                                                span_lo,
+                                                path.span.hi,
+                                                context,
+                                                budget,
+                                                offset + extra_offset));
+
+        result.push_str(">::");
+        span_lo = qself.ty.span.hi + BytePos(1);
+    }
+
+    let extra_offset = extra_offset(&result, offset);
+    let budget = try_opt!(width.checked_sub(extra_offset));
+    rewrite_path_segments(result,
+                          path.segments.iter().skip(skip_count),
+                          span_lo,
+                          path.span.hi,
+                          context,
+                          budget,
+                          offset + extra_offset)
+}
+
+fn rewrite_path_segments<'a, I>(mut buffer: String,
+                                iter: I,
+                                mut span_lo: BytePos,
+                                span_hi: BytePos,
+                                context: &RewriteContext,
+                                width: usize,
+                                offset: usize)
+                                -> Option<String>
+    where I: Iterator<Item = &'a ast::PathSegment>
+{
+    let mut first = true;
+
+    for segment in iter {
+        let extra_offset = extra_offset(&buffer, offset);
+        let remaining_width = try_opt!(width.checked_sub(extra_offset));
+        let new_offset = offset + extra_offset;
+        let segment_string = try_opt!(rewrite_segment(segment,
+                                                      &mut span_lo,
+                                                      span_hi,
+                                                      context,
+                                                      remaining_width,
+                                                      new_offset));
+
+        if first {
+            first = false;
+        } else {
+            buffer.push_str("::");
+        }
+
+        buffer.push_str(&segment_string);
+    }
+
+    Some(buffer)
+}
+
+enum SegmentParam<'a> {
+    LifeTime(&'a ast::Lifetime),
+    Type(&'a ast::Ty),
+    Binding(&'a ast::TypeBinding),
+}
+
+impl<'a> SegmentParam<'a> {
+    fn get_span(&self) -> Span {
+        match *self {
+            SegmentParam::LifeTime(ref lt) => lt.span,
+            SegmentParam::Type(ref ty) => ty.span,
+            SegmentParam::Binding(ref binding) => binding.span,
+        }
+    }
+}
+
+impl<'a> fmt::Display for SegmentParam<'a> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match *self {
+            SegmentParam::LifeTime(ref lt) => {
+                write!(f, "{}", pprust::lifetime_to_string(lt))
+            }
+            SegmentParam::Type(ref ty) => {
+                write!(f, "{}", pprust::ty_to_string(ty))
+            }
+            SegmentParam::Binding(ref binding) => {
+                write!(f, "{} = {}", binding.ident, pprust::ty_to_string(&*binding.ty))
+            }
+        }
+    }
+}
+
+// This is a dirty hack to determine if we're in an expression or not. Generic
+// parameters are passed differently in expressions and items. We'd declare
+// a struct with Foo<A, B>, but call its functions with Foo::<A, B>::f().
+// We'd really rather not do this, but there doesn't seem to be an alternative
+// at this point.
+// FIXME: fails with spans containing comments with the characters < or :
+fn get_path_separator(codemap: &CodeMap,
+                      path_start: BytePos,
+                      segment_start: BytePos)
+                      -> &'static str {
+    let span = codemap::mk_sp(path_start, segment_start);
+    let snippet = codemap.span_to_snippet(span).unwrap();
+
+    for c in snippet.chars().rev() {
+        if c == ':' {
+            return "::"
+        } else if c.is_whitespace() || c == '<' {
+            continue;
+        } else {
+            return "";
+        }
+    }
+
+    unreachable!();
+}
+
+// Formats a path segment. There are some hacks involved to correctly determine
+// the segment's associated span since it's not part of the AST.
+//
+// The span_lo is assumed to be greater than the end of any previous segment's
+// parameters and lesser or equal than the start of current segment.
+//
+// span_hi is assumed equal to the end of the entire path.
+//
+// When the segment contains a positive number of parameters, we update span_lo
+// so that invariants described above will hold for the next segment.
+fn rewrite_segment(segment: &ast::PathSegment,
+                   span_lo: &mut BytePos,
+                   span_hi: BytePos,
+                   context: &RewriteContext,
+                   width: usize,
+                   offset: usize)
+                   -> Option<String> {
+    let ident_len = segment.identifier.to_string().len();
+    let width = try_opt!(width.checked_sub(ident_len));
+    let offset = offset + ident_len;
+
+    let params = match segment.parameters {
+        ast::PathParameters::AngleBracketedParameters(ref data) if data.lifetimes.len() > 0 ||
+                                                                   data.types.len() > 0 ||
+                                                                   data.bindings.len() > 0 => {
+            let param_list = data.lifetimes.iter()
+                                           .map(SegmentParam::LifeTime)
+                                           .chain(data.types.iter()
+                                                      .map(|x| SegmentParam::Type(&*x)))
+                                           .chain(data.bindings.iter()
+                                                      .map(|x| SegmentParam::Binding(&*x)))
+                                           .collect::<Vec<_>>();
+
+            let next_span_lo = param_list.last().unwrap().get_span().hi + BytePos(1);
+            let list_lo = span_after(codemap::mk_sp(*span_lo, span_hi), "<", context.codemap);
+            let separator = get_path_separator(context.codemap, *span_lo, list_lo);
+
+            let items = itemize_list(context.codemap,
+                                     Vec::new(),
+                                     param_list.into_iter(),
+                                     ",",
+                                     ">",
+                                     |param| param.get_span().lo,
+                                     |param| param.get_span().hi,
+                                     ToString::to_string,
+                                     list_lo,
+                                     span_hi);
+
+            // 1 for <
+            let extra_offset = 1 + separator.len();
+            // 1 for >
+            let list_width = try_opt!(width.checked_sub(extra_offset + 1));
+
+            let fmt = ListFormatting {
+                tactic: ListTactic::HorizontalVertical,
+                separator: ",",
+                trailing_separator: SeparatorTactic::Never,
+                indent: offset + extra_offset,
+                h_width: list_width,
+                v_width: list_width,
+                ends_with_newline: false,
+            };
+
+            // update pos
+            *span_lo = next_span_lo;
+
+            format!("{}<{}>", separator, write_list(&items, &fmt))
+        }
+        ast::PathParameters::ParenthesizedParameters(ref data) => {
+            let output = match data.output {
+                Some(ref ty) => format!(" -> {}", pprust::ty_to_string(&*ty)),
+                None => String::new()
+            };
+
+            let list_lo = span_after(codemap::mk_sp(*span_lo, span_hi), "(", context.codemap);
+            let items = itemize_list(context.codemap,
+                                     Vec::new(),
+                                     data.inputs.iter(),
+                                     ",",
+                                     ")",
+                                     |ty| ty.span.lo,
+                                     |ty| ty.span.hi,
+                                     |ty| pprust::ty_to_string(ty),
+                                     list_lo,
+                                     span_hi);
+
+            // 2 for ()
+            let budget = try_opt!(width.checked_sub(output.len() + 2));
+
+            let fmt = ListFormatting {
+                tactic: ListTactic::HorizontalVertical,
+                separator: ",",
+                trailing_separator: SeparatorTactic::Never,
+                // 1 for (
+                indent: offset + 1,
+                h_width: budget,
+                v_width: budget,
+                ends_with_newline: false,
+            };
+
+            // update pos
+            *span_lo = data.inputs.last().unwrap().span.hi + BytePos(1);
+
+            format!("({}){}", write_list(&items, &fmt), output)
+        }
+        _ => String::new()
+    };
+
+    Some(format!("{}{}", segment.identifier, params))
+}
+
+impl Rewrite for ast::WherePredicate {
+    fn rewrite(&self, context: &RewriteContext, width: usize, offset: usize) -> Option<String> {
+        // TODO dead spans?
         // TODO assumes we'll always fit on one line...
-        match predicate {
+        Some(match self {
             &ast::WherePredicate::BoundPredicate(ast::WhereBoundPredicate{ref bound_lifetimes,
                                                                           ref bounded_ty,
                                                                           ref bounds,
                                                                           ..}) => {
                 if bound_lifetimes.len() > 0 {
-                    format!("for<{}> {}: {}",
-                            bound_lifetimes.iter().map(|l| self.rewrite_lifetime_def(l))
-                                .collect::<Vec<_>>().join(", "),
-                            pprust::ty_to_string(bounded_ty),
-                            bounds.iter().map(|b| self.rewrite_ty_bound(b))
-                                .collect::<Vec<_>>().join(" + "))
+                    let lifetime_str = bound_lifetimes.iter().map(|lt| {
+                                           lt.rewrite(context, width, offset).unwrap()
+                                       }).collect::<Vec<_>>().join(", ");
+                    let type_str = pprust::ty_to_string(bounded_ty);
+                    // 8 = "for<> : ".len()
+                    let used_width = lifetime_str.len() + type_str.len() + 8;
+                    let bounds_str = bounds.iter().map(|ty_bound| {
+                                         ty_bound.rewrite(context,
+                                                          width - used_width,
+                                                          offset + used_width)
+                                                 .unwrap()
+                                     }).collect::<Vec<_>>().join(" + ");
 
+                    format!("for<{}> {}: {}", lifetime_str, type_str, bounds_str)
                 } else {
-                    format!("{}: {}",
-                            pprust::ty_to_string(bounded_ty),
-                            bounds.iter().map(|b| self.rewrite_ty_bound(b))
-                                .collect::<Vec<_>>().join(" + "))
+                    let type_str = pprust::ty_to_string(bounded_ty);
+                    // 2 = ": ".len()
+                    let used_width = type_str.len() + 2;
+                    let bounds_str = bounds.iter().map(|ty_bound| {
+                                         ty_bound.rewrite(context,
+                                                          width - used_width,
+                                                          offset + used_width)
+                                                 .unwrap()
+                                     }).collect::<Vec<_>>().join(" + ");
+
+                    format!("{}: {}", type_str, bounds_str)
                 }
             }
             &ast::WherePredicate::RegionPredicate(ast::WhereRegionPredicate{ref lifetime,
@@ -42,65 +320,91 @@ impl<'a> FmtVisitor<'a> {
                                                                             ..}) => {
                 format!("{}: {}",
                         pprust::lifetime_to_string(lifetime),
-                        bounds.iter().map(|l| pprust::lifetime_to_string(l))
-                            .collect::<Vec<_>>().join(" + "))
+                        bounds.iter().map(pprust::lifetime_to_string)
+                              .collect::<Vec<_>>().join(" + "))
             }
             &ast::WherePredicate::EqPredicate(ast::WhereEqPredicate{ref path, ref ty, ..}) => {
-                format!("{} = {}", pprust::path_to_string(path), pprust::ty_to_string(ty))
+                let ty_str = pprust::ty_to_string(ty);
+                // 3 = " = ".len()
+                let used_width = 3 + ty_str.len();
+                let path_str = try_opt!(path.rewrite(context,
+                                                     width - used_width,
+                                                     offset + used_width));
+                format!("{} = {}", path_str, ty_str)
             }
-        }
+        })
     }
+}
 
-    pub fn rewrite_lifetime_def(&self, lifetime: &ast::LifetimeDef) -> String {
-        if lifetime.bounds.len() == 0 {
-            return pprust::lifetime_to_string(&lifetime.lifetime);
+impl Rewrite for ast::LifetimeDef {
+    fn rewrite(&self, _: &RewriteContext, _: usize, _: usize) -> Option<String> {
+        if self.bounds.len() == 0 {
+            Some(pprust::lifetime_to_string(&self.lifetime))
+        } else {
+            Some(format!("{}: {}",
+                         pprust::lifetime_to_string(&self.lifetime),
+                         self.bounds.iter().map(pprust::lifetime_to_string)
+                                    .collect::<Vec<_>>().join(" + ")))
         }
-
-        format!("{}: {}",
-                pprust::lifetime_to_string(&lifetime.lifetime),
-                lifetime.bounds.iter().map(|l| pprust::lifetime_to_string(l))
-                    .collect::<Vec<_>>().join(" + "))
     }
+}
 
-    pub fn rewrite_ty_bound(&self, bound: &ast::TyParamBound) -> String {
-        match *bound {
+impl Rewrite for ast::TyParamBound {
+    fn rewrite(&self, context: &RewriteContext, width: usize, offset: usize) -> Option<String> {
+        match *self {
             ast::TyParamBound::TraitTyParamBound(ref tref, ast::TraitBoundModifier::None) => {
-                self.rewrite_poly_trait_ref(tref)
+                tref.rewrite(context, width, offset)
             }
             ast::TyParamBound::TraitTyParamBound(ref tref, ast::TraitBoundModifier::Maybe) => {
-                format!("?{}", self.rewrite_poly_trait_ref(tref))
+                Some(format!("?{}", try_opt!(tref.rewrite(context, width - 1, offset + 1))))
             }
             ast::TyParamBound::RegionTyParamBound(ref l) => {
-                pprust::lifetime_to_string(l)
+                Some(pprust::lifetime_to_string(l))
             }
         }
     }
+}
 
-    pub fn rewrite_ty_param(&self, ty_param: &ast::TyParam) -> String {
+// FIXME: this assumes everything will fit on one line
+impl Rewrite for ast::TyParam {
+    fn rewrite(&self, context: &RewriteContext, width: usize, offset: usize) -> Option<String> {
         let mut result = String::with_capacity(128);
-        result.push_str(&ty_param.ident.to_string());
-        if ty_param.bounds.len() > 0 {
+        result.push_str(&self.ident.to_string());
+        if self.bounds.len() > 0 {
             result.push_str(": ");
-            result.push_str(&ty_param.bounds.iter().map(|b| self.rewrite_ty_bound(b))
-                                .collect::<Vec<_>>().join(" + "));
+
+            let bounds = self.bounds.iter().map(|ty_bound| {
+                ty_bound.rewrite(context, width, offset).unwrap()
+            }).collect::<Vec<_>>().join(" + ");
+
+            result.push_str(&bounds);
         }
-        if let Some(ref def) = ty_param.default {
+        if let Some(ref def) = self.default {
             result.push_str(" = ");
             result.push_str(&pprust::ty_to_string(&def));
         }
 
-        result
+        Some(result)
     }
+}
 
-    fn rewrite_poly_trait_ref(&self, t: &ast::PolyTraitRef) -> String {
-        if t.bound_lifetimes.len() > 0 {
-            format!("for<{}> {}",
-                    t.bound_lifetimes.iter().map(|l| self.rewrite_lifetime_def(l))
-                        .collect::<Vec<_>>().join(", "),
-                    pprust::path_to_string(&t.trait_ref.path))
+// FIXME: this assumes everything will fit on one line
+impl Rewrite for ast::PolyTraitRef {
+    fn rewrite(&self, context: &RewriteContext, width: usize, offset: usize) -> Option<String> {
+        if self.bound_lifetimes.len() > 0 {
+            let lifetime_str = self.bound_lifetimes.iter().map(|lt| {
+                lt.rewrite(context, width, offset).unwrap()
+            }).collect::<Vec<_>>().join(", ");
+            // 6 is "for<> ".len()
+            let extra_offset = lifetime_str.len() + 6;
+            let max_path_width = try_opt!(width.checked_sub(extra_offset));
+            let path_str = try_opt!(self.trait_ref.path.rewrite(context,
+                                                                max_path_width,
+                                                                offset + extra_offset));
 
+            Some(format!("for<{}> {}", lifetime_str, path_str))
         } else {
-            pprust::path_to_string(&t.trait_ref.path)
+            self.trait_ref.path.rewrite(context, width, offset)
         }
     }
 }
diff --git a/src/utils.rs b/src/utils.rs
index 3ff30da9784..59f85b3ad27 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -15,6 +15,16 @@ use comment::FindUncommented;
 
 use SKIP_ANNOTATION;
 
+// Computes the length of a string's last line, minus offset.
+#[inline]
+pub fn extra_offset(text: &str, offset: usize) -> usize {
+    match text.rfind('\n') {
+        // 1 for newline character
+        Some(idx) => text.len() - idx - 1 - offset,
+        None => text.len()
+    }
+}
+
 #[inline]
 pub fn span_after(original: Span, needle: &str, codemap: &CodeMap) -> BytePos {
     let snippet = codemap.span_to_snippet(original).unwrap();
diff --git a/src/visitor.rs b/src/visitor.rs
index 23908edf8d0..e107a32abf8 100644
--- a/src/visitor.rs
+++ b/src/visitor.rs
@@ -33,13 +33,9 @@ impl<'a, 'v> visit::Visitor<'v> for FmtVisitor<'a> {
                self.codemap.lookup_char_pos(ex.span.lo),
                self.codemap.lookup_char_pos(ex.span.hi));
         self.format_missing(ex.span.lo);
+
         let offset = self.buffer.cur_offset();
-        let context = RewriteContext {
-            codemap: self.codemap,
-            config: self.config,
-            block_indent: self.block_indent,
-        };
-        let rewrite = ex.rewrite(&context, self.config.max_width - offset, offset);
+        let rewrite = ex.rewrite(&self.get_context(), self.config.max_width - offset, offset);
 
         if let Some(new_str) = rewrite {
             self.buffer.push_str(&new_str);
@@ -372,4 +368,12 @@ impl<'a> FmtVisitor<'a> {
             }
         }
     }
+
+    pub fn get_context(&self) -> RewriteContext {
+        RewriteContext {
+            codemap: self.codemap,
+            config: self.config,
+            block_indent: self.block_indent,
+        }
+    }
 }