diff options
| author | Marcus Klaas <mail@marcusklaas.nl> | 2015-08-14 14:09:19 +0200 |
|---|---|---|
| committer | Marcus Klaas <mail@marcusklaas.nl> | 2015-08-16 22:53:06 +0200 |
| commit | 41bca58100260323e54ff83e5bc74dd920beabcd (patch) | |
| tree | f3aa2bd2357772460166aebde44155454aaab3a4 /src/types.rs | |
| parent | 78b38c84c620a3019473463a5ff9120941604ec2 (diff) | |
Format paths
Diffstat (limited to 'src/types.rs')
| -rw-r--r-- | src/types.rs | 396 |
1 files changed, 350 insertions, 46 deletions
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) } } } |
