diff --git a/src/comment.rs b/src/comment.rs index 6222c34b1a2..3adeeea0fc6 100644 --- a/src/comment.rs +++ b/src/comment.rs @@ -1789,7 +1789,7 @@ mod test { * test3 */"#, false, - Shape::legacy(100, Indent::new(0, 0)), + Shape::legacy(100, Indent::empty()), &wrap_normalize_config).unwrap(); assert_eq!("/// test1\n/// test2\n// test3", comment); @@ -1798,7 +1798,7 @@ mod test { // test2"#, false, - Shape::legacy(100, Indent::new(0, 0)), + Shape::legacy(100, Indent::empty()), &wrap_normalize_config).unwrap(); assert_eq!("// test1\n\n// test2", comment); @@ -1807,7 +1807,7 @@ mod test { //@ test2"#, false, - Shape::legacy(100, Indent::new(0, 0)), + Shape::legacy(100, Indent::empty()), &wrap_normalize_config).unwrap(); assert_eq!("//@ test1\n\n//@ test2", comment); @@ -1819,7 +1819,7 @@ mod test { another bare line! */"#, false, - Shape::legacy(100, Indent::new(0, 0)), + Shape::legacy(100, Indent::empty()), &wrap_config).unwrap(); assert_eq!("// test1\n/*\n a bare line!\n\n another bare line!\n*/", comment); } diff --git a/src/items.rs b/src/items.rs index d3937bbdb31..20b0cda0cf8 100644 --- a/src/items.rs +++ b/src/items.rs @@ -990,20 +990,32 @@ pub(crate) fn format_trait( rewrite_generics(context, rewrite_ident(context, item.ident), generics, shape)?; result.push_str(&generics_str); - // FIXME(#2055): rustfmt fails to format when there are comments between trait bounds. if !generic_bounds.is_empty() { - let ident_hi = context - .snippet_provider - .span_after(item.span, &item.ident.as_str()); - let bound_hi = generic_bounds.last().unwrap().span().hi(); - let snippet = context.snippet(mk_sp(ident_hi, bound_hi)); - if contains_comment(snippet) { - return None; - } - + let comment = if context.config.version() == Version::Two { + let comment_span = mk_sp(generics.span.hi(), generic_bounds[0].span().lo()); + let after_colon = context.snippet_provider.span_after(comment_span, ":"); + recover_missing_comment_in_span( + mk_sp(after_colon, comment_span.hi()), + shape, + context, + // 1 = ":" + last_line_width(&result) + 1, + ) + .unwrap_or_default() + } else { + let ident_hi = context + .snippet_provider + .span_after(item.span, &item.ident.as_str()); + let bound_hi = generic_bounds.last().unwrap().span().hi(); + let snippet = context.snippet(mk_sp(ident_hi, bound_hi)); + if contains_comment(snippet) { + return None; + } + String::new() + }; result = rewrite_assign_rhs_with( context, - result + ":", + format!("{}:{}", result, comment), generic_bounds, shape, RhsTactics::ForceNextLineWithoutIndent, @@ -1048,7 +1060,11 @@ pub(crate) fn format_trait( if let Some(lo) = item_snippet.find('/') { // 1 = `{` let comment_hi = body_lo - BytePos(1); - let comment_lo = item.span.lo() + BytePos(lo as u32); + let comment_lo = if generic_bounds.is_empty() { + item.span.lo() + BytePos(lo as u32) + } else { + generic_bounds[generic_bounds.len() - 1].span().hi() + }; if comment_lo < comment_hi { match recover_missing_comment_in_span( mk_sp(comment_lo, comment_hi), diff --git a/src/lists.rs b/src/lists.rs index ac83d7082c8..14ec18a1219 100644 --- a/src/lists.rs +++ b/src/lists.rs @@ -5,7 +5,7 @@ use std::iter::Peekable; use syntax::source_map::BytePos; -use crate::comment::{find_comment_end, rewrite_comment, FindUncommented}; +use crate::comment::{find_comment_end, is_last_comment_block, rewrite_comment, FindUncommented}; use crate::config::lists::*; use crate::config::{Config, IndentStyle}; use crate::rewrite::RewriteContext; @@ -22,16 +22,20 @@ pub(crate) struct ListFormatting<'a> { trailing_separator: SeparatorTactic, separator_place: SeparatorPlace, shape: Shape, - // Non-expressions, e.g., items, will have a new line at the end of the list. - // Important for comment styles. + /// Non-expressions, e.g., items, will have a new line at the end of the list. + /// Important for comment styles. ends_with_newline: bool, - // Remove newlines between list elements for expressions. + /// Remove newlines between list elements for expressions. preserve_newline: bool, - // Nested import lists get some special handling for the "Mixed" list type + /// Nested import lists get some special handling for the "Mixed" list type. nested: bool, - // Whether comments should be visually aligned. + /// Whether comments should be visually aligned. align_comments: bool, config: &'a Config, + /// The decision of putting an item on a newline is determined by the caller. + custom_list_tactic: Vec, + /// Whether whitespaces should be added around the separator. + padding: bool, } impl<'a> ListFormatting<'a> { @@ -47,9 +51,21 @@ impl<'a> ListFormatting<'a> { nested: false, align_comments: true, config, + custom_list_tactic: Vec::new(), + padding: true, } } + pub(crate) fn padding(mut self, padding: bool) -> Self { + self.padding = padding; + self + } + + pub(crate) fn custom_list_tactic(mut self, custom_list_tactic: Vec) -> Self { + self.custom_list_tactic = custom_list_tactic; + self + } + pub(crate) fn tactic(mut self, tactic: DefinitiveListTactic) -> Self { self.tactic = tactic; self @@ -259,58 +275,298 @@ where } } -// Format a list of commented items into a string. -pub(crate) fn write_list(items: I, formatting: &ListFormatting<'_>) -> Option +/// Stores the state of the ongoing stringification of a list item. +struct WriteListItemState { + /// The offset of the item in the list. + ith: usize, + /// Whether or not the item is the first in the list. + first: bool, + /// Whether or not the item is the last in the list. + last: bool, + /// Whether or not the item is the first to be written on the current line. + first_item_on_line: bool, + /// The placement of the separator + sep_place: SeparatorPlace, + /// Whether or not the separator should be written. + separate: bool, + /// Whether or not the separator can be trailing. + trailing_separator: bool, +} + +impl WriteListItemState { + fn new(formatting: &ListFormatting<'_>) -> WriteListItemState { + WriteListItemState { + ith: 0, + first: false, + last: false, + first_item_on_line: true, + sep_place: SeparatorPlace::from_tactic( + formatting.separator_place, + formatting.tactic, + formatting.separator, + ), + separate: false, + // Now that we know how we will layout, we can decide for sure if there + // will be a trailing separator. + trailing_separator: formatting.needs_trailing_separator(), + } + } + + /// Set if a separator should be written for the current item. + fn should_separate(&mut self) { + self.separate = match self.sep_place { + SeparatorPlace::Front => !self.first, + SeparatorPlace::Back => !self.last || self.trailing_separator, + }; + } + + /// Returns true if a separtor should be placed in front of the current item. + fn should_separate_if_front(&self) -> bool { + self.separate && self.sep_place.is_front() + } + + /// Returns true if a separtor should be placed at the back of the current item. + fn should_separate_if_back(&self) -> bool { + self.separate && self.sep_place.is_back() + } +} + +/// Align comments written after items. +struct PostCommentAlignment { + /// The largest item width in a group of items. + item_max_width: Option, + /// The length of the first item. + first_item_len: usize, + /// Whether or not an item that is not the first has the same length as the first item. + middle_item_same_len_first: bool, + /// The length of the last item. + last_item_len: usize, + /// Whether or not an item that is not the last has the same length as the last item. + middle_item_same_len_last: bool, +} + +impl PostCommentAlignment { + fn new>(items: &[T]) -> PostCommentAlignment { + let first_item_len = if let Some(item) = items.iter().nth(0) { + item.as_ref().inner_as_ref().len() + } else { + 0 + }; + let last_item_len = if let Some(item) = items.iter().last() { + item.as_ref().inner_as_ref().len() + } else { + 0 + }; + let middle_item_same_len_first = items + .iter() + // all but first item + .skip(1) + // check if any of the intermediate items share the size of the first item + .map(|item| item.as_ref().inner_as_ref().len()) + .any(|len| len == first_item_len); + let middle_item_same_len_last = items + .iter() + // all but last item + .take(items.len().saturating_sub(1)) + // check if any of the intermediate items share the size of the last item + .map(|item| item.as_ref().inner_as_ref().len()) + .any(|len| len == last_item_len); + PostCommentAlignment { + item_max_width: None, + first_item_len, + middle_item_same_len_first, + last_item_len, + middle_item_same_len_last, + } + } + + /// Get the length of the largest item starting at the ith-item. + fn update_item_max_width>( + &mut self, + items: &[T], + formatting: &ListFormatting<'_>, + item_state: &WriteListItemState, + inner_item: &str, + overhead: usize, + ) { + if self.item_max_width.is_none() && !item_state.last && !inner_item.contains('\n') { + self.item_max_width = Some(PostCommentAlignment::max_width_of_item_with_post_comment( + &items, + item_state.ith, + overhead, + formatting.config.max_width(), + )); + } + } + + fn max_width_of_item_with_post_comment( + items: &[T], + i: usize, + overhead: usize, + max_budget: usize, + ) -> usize + where + T: AsRef, + { + let mut max_width = 0; + let mut first = true; + for item in items.iter().skip(i) { + let item = item.as_ref(); + let inner_item_width = item.inner_as_ref().len(); + if !first + && (item.is_different_group() + || item.post_comment.is_none() + || inner_item_width + overhead > max_budget) + { + return max_width; + } + if max_width < inner_item_width { + max_width = inner_item_width; + } + if item.new_lines { + return max_width; + } + first = false; + } + max_width + } + + /// Computes the number whitespaces to insert after an item so that its post comment is aligned + /// with others. + /// + /// The alignment logic is complicated by the fact that the alignment is based on the longest + /// item, without the additional space taken by the separator. For the first and last items + /// where the separator may be missing, some compensation needs to be computed. + fn alignment( + &self, + item_state: &WriteListItemState, + inner_item_len: usize, + fmt: &ListFormatting<'_>, + ) -> usize { + // 1 = whitespace before the post_comment + if self.item_max_width.is_none() { + return 1; + } + let item_max_width = self.item_max_width.unwrap(); + let alignment = item_max_width.saturating_sub(inner_item_len) + 1; + let first_item_longest = item_max_width == self.first_item_len; + let last_item_longest = item_max_width == self.last_item_len; + + let alignment = match item_state.sep_place { + /* + * Front separator: first item is missing the separator and needs to be compensated + */ + SeparatorPlace::Front => { + match (item_state.first, first_item_longest) { + // in case a middle item has the same length as the first, then all items have + // the correct alignment: only the first item needs to account for the separator + _ if self.middle_item_same_len_first => { + alignment + + if item_state.first { + fmt.separator.len() + } else { + 0 + } + } + // first item is the longest: others need to minus the (separator + padding) + // to the alignment + (false, true) => alignment + .saturating_sub(if item_state.first_item_on_line { + fmt.separator.trim_start().len() + } else { + fmt.separator.len() + }) + .saturating_sub(if fmt.padding { 1 } else { 0 }), + // first item is not the longest: first needs to add (searator + padding) + // to the alignment + (true, false) => { + alignment + + fmt.separator.trim_start().len() + + if fmt.padding { 1 } else { 0 } + } + _ => alignment, + } + } + /* + * Back separator: last item is missing the separator (if it is not trailing) + * and needs to be compensated + */ + SeparatorPlace::Back => { + match (item_state.last, last_item_longest) { + _ if self.middle_item_same_len_last => { + alignment + + if item_state.last && !item_state.separate { + fmt.separator.len() + } else { + 0 + } + } + // last item is the longest: others need to minus the sep to the alignment + (false, true) if !fmt.needs_trailing_separator() => { + alignment.saturating_sub(fmt.separator.len()) + } + // last item is not the longest: last needs to add sep to the alignment + (true, false) if !item_state.separate => alignment + fmt.separator.len(), + _ => alignment, + } + } + }; + // at least 1 for the whitespace before the post_comment + if alignment == 0 { 1 } else { alignment } + } +} + +/// Format a list of commented items into a string. +pub(crate) fn write_list(items: &[T], formatting: &ListFormatting<'_>) -> Option where - I: IntoIterator + Clone, T: AsRef, { let tactic = formatting.tactic; - let sep_len = formatting.separator.len(); + let indent_str = &formatting.shape.indent.to_string(formatting.config); - // Now that we know how we will layout, we can decide for sure if there - // will be a trailing separator. - let mut trailing_separator = formatting.needs_trailing_separator(); let mut result = String::with_capacity(128); - let cloned_items = items.clone(); - let mut iter = items.into_iter().enumerate().peekable(); - let mut item_max_width: Option = None; - let sep_place = - SeparatorPlace::from_tactic(formatting.separator_place, tactic, formatting.separator); + let mut iter = items.iter().enumerate().peekable(); + + // Mixed tactic state let mut prev_item_had_post_comment = false; let mut prev_item_is_nested_import = false; - let mut line_len = 0; - let indent_str = &formatting.shape.indent.to_string(formatting.config); + + let mut item_state = WriteListItemState::new(formatting); + let mut pca = PostCommentAlignment::new(items); + while let Some((i, item)) = iter.next() { let item = item.as_ref(); let inner_item = item.item.as_ref()?; - let first = i == 0; - let last = iter.peek().is_none(); - let mut separate = match sep_place { - SeparatorPlace::Front => !first, - SeparatorPlace::Back => !last || trailing_separator, - }; - let item_sep_len = if separate { sep_len } else { 0 }; - - // Item string may be multi-line. Its length (used for block comment alignment) - // should be only the length of the last line. - let item_last_line = if item.is_multiline() { - inner_item.lines().last().unwrap_or("") - } else { - inner_item.as_ref() - }; - let mut item_last_line_width = item_last_line.len() + item_sep_len; - if item_last_line.starts_with(&**indent_str) { - item_last_line_width -= indent_str.len(); - } if !item.is_substantial() { continue; } + item_state.ith = i; + item_state.first = i == 0; + item_state.last = iter.peek().is_none(); + item_state.should_separate(); + let item_sep_len = if item_state.separate { + formatting.separator.len() + } else { + 0 + }; match tactic { - DefinitiveListTactic::Horizontal if !first => { + _ if !formatting.custom_list_tactic.is_empty() => { + if *formatting + .custom_list_tactic + .get(i) + .expect("invalid custom_list_tactic formatting option") + { + result.push('\n'); + result.push_str(indent_str); + item_state.first_item_on_line = true; + } else if formatting.padding && !item_state.first_item_on_line { + result.push(' '); + } + } + DefinitiveListTactic::Horizontal if !item_state.first && formatting.padding => { result.push(' '); } DefinitiveListTactic::SpecialMacro(num_args_before) => { @@ -326,8 +582,9 @@ where } } DefinitiveListTactic::Vertical - if !first && !inner_item.is_empty() && !result.is_empty() => + if !item_state.first && !inner_item.is_empty() && !result.is_empty() => { + item_state.first_item_on_line = true; result.push('\n'); result.push_str(indent_str); } @@ -338,24 +595,28 @@ where if (line_len > 0 && line_len + 1 + total_width > formatting.shape.width) || prev_item_had_post_comment || (formatting.nested - && (prev_item_is_nested_import || (!first && inner_item.contains("::")))) + && (prev_item_is_nested_import + || (!item_state.first && inner_item.contains("::")))) { result.push('\n'); result.push_str(indent_str); line_len = 0; + item_state.first_item_on_line = true; if formatting.ends_with_newline { - trailing_separator = true; + item_state.trailing_separator = true; } - } else if line_len > 0 { + } else if formatting.padding && line_len > 0 { result.push(' '); line_len += 1; } - if last && formatting.ends_with_newline { - separate = formatting.trailing_separator != SeparatorTactic::Never; + if item_state.last && formatting.ends_with_newline { + item_state.separate = formatting.trailing_separator != SeparatorTactic::Never; } line_len += total_width; + prev_item_had_post_comment = item.post_comment.is_some(); + prev_item_is_nested_import = inner_item.contains("::"); } _ => {} } @@ -395,12 +656,18 @@ where result.push(' '); } } - item_max_width = None; + pca.item_max_width = None; } - if separate && sep_place.is_front() && !first { - result.push_str(formatting.separator.trim()); - result.push(' '); + if item_state.should_separate_if_front() && !item_state.first { + if formatting.padding { + result.push_str(formatting.separator.trim()); + result.push(' '); + } else if item_state.first_item_on_line { + result.push_str(formatting.separator.trim_start()); + } else { + result.push_str(formatting.separator); + } } result.push_str(inner_item); @@ -414,11 +681,16 @@ where formatting.config, )?; - result.push(' '); + if is_last_comment_block(&formatted_comment) { + result.push(' '); + } else { + result.push('\n'); + result.push_str(indent_str); + } result.push_str(&formatted_comment); } - if separate && sep_place.is_back() { + if item_state.should_separate_if_back() { result.push_str(formatting.separator); } @@ -426,20 +698,24 @@ where let comment = item.post_comment.as_ref().unwrap(); let overhead = last_line_width(&result) + first_line_width(comment.trim()); - let rewrite_post_comment = |item_max_width: &mut Option| { - if item_max_width.is_none() && !last && !inner_item.contains('\n') { - *item_max_width = Some(max_width_of_item_with_post_comment( - &cloned_items, - i, - overhead, - formatting.config.max_width(), - )); - } + let rewrite_post_comment = |item_max_width: Option| { let overhead = if starts_with_newline(comment) { 0 - } else if let Some(max_width) = *item_max_width { + } else if let Some(max_width) = item_max_width { max_width + 2 } else { + // Item string may be multi-line. Its length (used for block comment alignment) + // should be only the length of the last line. + let item_last_line = if item.is_multiline() { + inner_item.lines().last().unwrap_or("") + } else { + inner_item.as_ref() + }; + let mut item_last_line_width = item_last_line.len() + item_sep_len; + if item_last_line.starts_with(&**indent_str) { + item_last_line_width -= indent_str.len(); + } + // 1 = space between item and comment. item_last_line_width + 1 }; @@ -448,7 +724,7 @@ where let comment_shape = Shape::legacy(width, offset); // 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 && item_state.last || comment.trim().contains('\n') || comment.trim().len() > width; @@ -460,35 +736,32 @@ where ) }; - let mut formatted_comment = rewrite_post_comment(&mut item_max_width)?; + pca.update_item_max_width(items, formatting, &item_state, inner_item, overhead); + let mut formatted_comment = rewrite_post_comment(pca.item_max_width)?; if !starts_with_newline(comment) { if formatting.align_comments { let mut comment_alignment = - post_comment_alignment(item_max_width, inner_item.len()); + pca.alignment(&item_state, inner_item.len(), formatting); if first_line_width(&formatted_comment) + last_line_width(&result) + comment_alignment - + 1 > formatting.config.max_width() { - item_max_width = None; - formatted_comment = rewrite_post_comment(&mut item_max_width)?; + pca.item_max_width = None; + pca.update_item_max_width( + items, + formatting, + &item_state, + inner_item, + overhead, + ); + formatted_comment = rewrite_post_comment(pca.item_max_width)?; comment_alignment = - post_comment_alignment(item_max_width, inner_item.len()); - } - for _ in 0..=comment_alignment { - result.push(' '); + pca.alignment(&item_state, inner_item.len(), formatting); } - } - // An additional space for the missing trailing separator (or - // if we skipped alignment above). - if !formatting.align_comments - || (last - && item_max_width.is_some() - && !separate - && !formatting.separator.is_empty()) - { + result.push_str(&" ".repeat(comment_alignment)); + } else { result.push(' '); } } else { @@ -496,66 +769,28 @@ where result.push_str(indent_str); } if formatted_comment.contains('\n') { - item_max_width = None; + pca.item_max_width = None; } result.push_str(&formatted_comment); } else { - item_max_width = None; + pca.item_max_width = None; } if formatting.preserve_newline - && !last + && !item_state.last && tactic == DefinitiveListTactic::Vertical && item.new_lines { - item_max_width = None; + pca.item_max_width = None; result.push('\n'); } - prev_item_had_post_comment = item.post_comment.is_some(); - prev_item_is_nested_import = inner_item.contains("::"); + item_state.first_item_on_line = false; } Some(result) } -fn max_width_of_item_with_post_comment( - items: &I, - i: usize, - overhead: usize, - max_budget: usize, -) -> usize -where - I: IntoIterator + Clone, - T: AsRef, -{ - let mut max_width = 0; - let mut first = true; - for item in items.clone().into_iter().skip(i) { - let item = item.as_ref(); - let inner_item_width = item.inner_as_ref().len(); - if !first - && (item.is_different_group() - || item.post_comment.is_none() - || inner_item_width + overhead > max_budget) - { - return max_width; - } - if max_width < inner_item_width { - max_width = inner_item_width; - } - if item.new_lines { - return max_width; - } - first = false; - } - max_width -} - -fn post_comment_alignment(item_max_width: Option, inner_item_len: usize) -> usize { - item_max_width.unwrap_or(0).saturating_sub(inner_item_len) -} - pub(crate) struct ListItems<'a, I, F1, F2, F3> where I: Iterator, @@ -599,25 +834,29 @@ pub(crate) fn extract_pre_comment(pre_snippet: &str) -> (Option, ListIte } } -pub(crate) fn extract_post_comment( - post_snippet: &str, - comment_end: usize, - separator: &str, -) -> Option { +fn extract_post_comment(post_snippet: &str, comment_end: usize, separator: &str) -> Option { + // leading newlines are important but not when they are trailing let white_space: &[_] = &[' ', '\t']; // Cleanup post-comment: strip separators and whitespace. let post_snippet = post_snippet[..comment_end].trim(); let post_snippet_trimmed = if post_snippet.starts_with(|c| c == ',' || c == ':') { - post_snippet[1..].trim_matches(white_space) + post_snippet[1..] + .trim_matches(white_space) + .trim_end_matches('\n') + } else if post_snippet.ends_with(separator) { + // the separator is in front of the next item + post_snippet[..post_snippet.len() - separator.len()] + .trim_matches(white_space) + .trim_end_matches('\n') } else if post_snippet.starts_with(separator) { - post_snippet[separator.len()..].trim_matches(white_space) - } - // not comment or over two lines - else if post_snippet.ends_with(',') - && (!post_snippet.trim().starts_with("//") || post_snippet.trim().contains('\n')) - { - post_snippet[..(post_snippet.len() - 1)].trim_matches(white_space) + post_snippet[separator.len()..] + .trim_matches(white_space) + .trim_end_matches('\n') + } else if post_snippet.ends_with(',') && !post_snippet.trim_start().starts_with("//") { + post_snippet[..post_snippet.len() - 1] + .trim_matches(white_space) + .trim_end_matches('\n') } else { post_snippet }; @@ -633,12 +872,7 @@ pub(crate) fn extract_post_comment( } } -pub(crate) fn get_comment_end( - post_snippet: &str, - separator: &str, - terminator: &str, - is_last: bool, -) -> usize { +fn get_comment_end(post_snippet: &str, separator: &str, terminator: &str, is_last: bool) -> usize { if is_last { return post_snippet .find_uncommented(terminator) @@ -686,7 +920,7 @@ pub(crate) fn get_comment_end( // Account for extra whitespace between items. This is fiddly // because of the way we divide pre- and post- comments. -pub(crate) fn has_extra_newline(post_snippet: &str, comment_end: usize) -> bool { +fn has_extra_newline(post_snippet: &str, comment_end: usize) -> bool { if post_snippet.is_empty() || comment_end == 0 { return false; } @@ -767,7 +1001,17 @@ where } #[allow(clippy::too_many_arguments)] -// Creates an iterator over a list's items with associated comments. +/// Creates an iterator over a list's items with associated comments. +/// +/// - inner is the iterator over items +/// - terminator is a string that closes the list, used to get comments after the last item +/// - separator is a string that separates the items +/// - get_lo is a closure to get the lower bound of an item's span +/// - get_hi is a closure to get the upper bound of an item's span +/// - get_item_string is a closure to get the rewritten item as a string +/// - prev_span_end is the BytePos before the first item +/// - next_span_start is the BytePos after the last item +/// - leave_last is a boolean whether or not to rewrite the last item pub(crate) fn itemize_list<'a, T, I, F1, F2, F3>( snippet_provider: &'a SnippetProvider<'_>, inner: I, @@ -918,5 +1162,257 @@ pub(crate) fn struct_lit_formatting<'a>( nested: false, align_comments: true, config: context.config, + custom_list_tactic: Vec::new(), + padding: true, + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::config::Config; + use crate::shape::{Indent, Shape}; + + #[test] + fn post_comment_alignment() { + let config: Config = Default::default(); + + let data = [ + //separator at the front and first item is the longest + ( + { + let shape = Shape::legacy(config.max_width(), Indent::empty()); + ListFormatting::new(shape, &config) + .separator("+") + .trailing_separator(SeparatorTactic::Always) + .separator_place(SeparatorPlace::Front) + }, + [ + ("item1aaaaaaaaaaaaaaa", "// len20"), + ("item2aaaaa", "// len10"), + ("item3aaaaaaaaaa", "// len15"), + ], + r#"item1aaaaaaaaaaaaaaa // len20 ++ item2aaaaa // len10 ++ item3aaaaaaaaaa // len15"#, + ), + // separator at the front and second item is the longest + ( + { + let shape = Shape::legacy(config.max_width(), Indent::empty()); + ListFormatting::new(shape, &config) + .separator("+") + .trailing_separator(SeparatorTactic::Always) + .separator_place(SeparatorPlace::Front) + }, + [ + ("item1aaaaa", "// len10"), + ("item2aaaaaaaaaaaaaaa", "// len20"), + ("item3aaaaaaaaaa", "// len15"), + ], + r#"item1aaaaa // len10 ++ item2aaaaaaaaaaaaaaa // len20 ++ item3aaaaaaaaaa // len15"#, + ), + ( + { + let shape = Shape::legacy(config.max_width(), Indent::empty()); + ListFormatting::new(shape, &config) + .separator("+") + .separator_place(SeparatorPlace::Front) + }, + [ + ("item1aaaaaaaaaaaaaaa", "// len20"), + ("item2aaaaa", "// len10"), + ("item3aaaaaaaaaa", "// len15"), + ], + r#"item1aaaaaaaaaaaaaaa // len20 ++ item2aaaaa // len10 ++ item3aaaaaaaaaa // len15"#, + ), + // font separator and middle/last items are the longest + ( + { + let shape = Shape::legacy(config.max_width(), Indent::empty()); + ListFormatting::new(shape, &config) + .separator("+") + .separator_place(SeparatorPlace::Front) + }, + [ + ("item1aaaaa", "// len10"), + ("item2aaaaaaaaaaaaaaa", "// len20"), + ("item3aaaaaaaaaaaaaaa", "// len20"), + ], + r#"item1aaaaa // len10 ++ item2aaaaaaaaaaaaaaa // len20 ++ item3aaaaaaaaaaaaaaa // len20"#, + ), + // font separator and first/middle items are the longest + ( + { + let shape = Shape::legacy(config.max_width(), Indent::empty()); + ListFormatting::new(shape, &config) + .separator(" +") + .separator_place(SeparatorPlace::Front) + }, + [ + ("item1aaaaaaaaaaaaaaa", "// len20"), + ("item2aaaaaaaaaaaaaaa", "// len20"), + ("item3aaaaaaaaaa", "// len15"), + ], + r#"item1aaaaaaaaaaaaaaa // len20 ++ item2aaaaaaaaaaaaaaa // len20 ++ item3aaaaaaaaaa // len15"#, + ), + // back separator and first item is the longest + ( + { + let shape = Shape::legacy(config.max_width(), Indent::empty()); + ListFormatting::new(shape, &config) + .separator(" +") + .separator_place(SeparatorPlace::Back) + }, + [ + ("item1aaaaaaaaaaaaaaa", "// len20"), + ("item2aaaaa", "// len10"), + ("item3aaaaaaaaaa", "// len15"), + ], + r#"item1aaaaaaaaaaaaaaa + // len20 +item2aaaaa + // len10 +item3aaaaaaaaaa // len15"#, + ), + // back separator and last item is the longest + ( + { + let shape = Shape::legacy(config.max_width(), Indent::empty()); + ListFormatting::new(shape, &config) + .separator(" +") + .separator_place(SeparatorPlace::Back) + }, + [ + ("item1aaaaa", "// len10"), + ("item2aaaaaaaaaa", "// len15"), + ("item3aaaaaaaaaaaaaaa", "// len20"), + ], + r#"item1aaaaa + // len10 +item2aaaaaaaaaa + // len15 +item3aaaaaaaaaaaaaaa // len20"#, + ), + // back separator and middle item is the longest + ( + { + let shape = Shape::legacy(config.max_width(), Indent::empty()); + ListFormatting::new(shape, &config) + .separator(" +") + .separator_place(SeparatorPlace::Back) + }, + [ + ("item1aaaaa", "// len10"), + ("item2aaaaaaaaaaaaaaa", "// len20"), + ("item3aaaaaaaaaa", "// len15"), + ], + r#"item1aaaaa + // len10 +item2aaaaaaaaaaaaaaa + // len20 +item3aaaaaaaaaa // len15"#, + ), + // back separator and middle/last items are the longest + ( + { + let shape = Shape::legacy(config.max_width(), Indent::empty()); + ListFormatting::new(shape, &config) + .separator(" +") + .separator_place(SeparatorPlace::Back) + }, + [ + ("item1aaaaa", "// len10"), + ("item2aaaaaaaaaaaaaaa", "// len20"), + ("item3aaaaaaaaaaaaaaa", "// len20"), + ], + r#"item1aaaaa + // len10 +item2aaaaaaaaaaaaaaa + // len20 +item3aaaaaaaaaaaaaaa // len20"#, + ), + // back separator and first/middle items are the longest + ( + { + let shape = Shape::legacy(config.max_width(), Indent::empty()); + ListFormatting::new(shape, &config) + .separator(" +") + .separator_place(SeparatorPlace::Back) + }, + [ + ("item1aaaaaaaaaaaaaaa", "// len20"), + ("item2aaaaaaaaaaaaaaa", "// len20"), + ("item3aaaaaaaaaa", "// len15"), + ], + r#"item1aaaaaaaaaaaaaaa + // len20 +item2aaaaaaaaaaaaaaa + // len20 +item3aaaaaaaaaa // len15"#, + ), + ]; + + for (i, (fmt, items, expected)) in data.into_iter().enumerate() { + let items = items + .into_iter() + .map(|(inner_item, post_comment)| ListItem { + pre_comment_style: ListItemCommentStyle::SameLine, + pre_comment: None, + item: Some(inner_item.to_string()), + post_comment: Some(post_comment.to_string()), + new_lines: false, + }) + .collect::>(); + assert_eq!( + write_list(&items, &fmt), + Some(expected.to_string()), + "failed on item {}", + i + ); + } + } + + #[test] + fn test_extract_post_comment() { + let data = [ + ( + ", // a comment", + ", // a comment".len(), + ",", + "// a comment", + ), + ( + ": // a comment", + ": // a comment".len(), + ":", + "// a comment", + ), + ( + "// a comment\n +", + "// a comment\n +".len(), + "+", + "// a comment", + ), + ( + "+ // a comment\n ", + "+ // a comment\n ".len(), + "+", + "// a comment", + ), + ( + "/* a comment */ ,", + "/* a comment */ ,".len(), + "+", + "/* a comment */", + ), + ]; + + for (i, (post_snippet, comment_end, separator, expected)) in data.iter().enumerate() { + assert_eq!( + extract_post_comment(post_snippet, *comment_end, separator), + Some(expected.to_string()), + "Failed on input {}", + i + ); + } } } diff --git a/src/types.rs b/src/types.rs index 012cdba3192..4fb745f06bf 100644 --- a/src/types.rs +++ b/src/types.rs @@ -499,7 +499,11 @@ fn rewrite_bounded_lifetime( "{}{}{}", result, colon, - join_bounds(context, shape.sub_width(overhead)?, bounds, true)? + if context.config.version() == Version::One { + join_bounds_v1(context, shape.sub_width(overhead)?, bounds, true)? + } else { + join_bounds_v2(context, shape.sub_width(overhead)?, bounds, true)? + } ); Some(result) } @@ -542,7 +546,11 @@ impl Rewrite for ast::GenericBounds { return Some(String::new()); } - join_bounds(context, shape, self, true) + if context.config.version() == Version::One { + join_bounds_v1(context, shape, self, true) + } else { + join_bounds_v2(context, shape, self, true) + } } } @@ -748,7 +756,7 @@ impl Rewrite for ast::Ty { let rw = if context.config.version() == Version::One { it.rewrite(context, shape) } else { - join_bounds(context, shape, it, false) + join_bounds_v2(context, shape, it, false) }; rw.map(|it_str| { let space = if it_str.is_empty() { "" } else { " " }; @@ -827,7 +835,7 @@ fn is_generic_bounds_in_order(generic_bounds: &[ast::GenericBound]) -> bool { } } -fn join_bounds( +fn join_bounds_v1( context: &RewriteContext<'_>, shape: Shape, items: &[ast::GenericBound], @@ -886,6 +894,94 @@ fn join_bounds( Some(result) } +fn join_bounds_v2( + context: &RewriteContext<'_>, + shape: Shape, + items: &[ast::GenericBound], + need_indent: bool, +) -> Option { + debug_assert!(!items.is_empty()); + + // Try to join types in a single line + let type_strs: Vec<_> = itemize_list( + context.snippet_provider, + items.iter(), + "", + "+", + |item| item.span().lo(), + |item| item.span().hi(), + |item| item.rewrite(context, shape), + items[0].span().lo(), + items[items.len() - 1].span().hi(), + false, + ) + .collect(); + let joiner = match context.config.type_punctuation_density() { + TypeDensity::Compressed => "+", + TypeDensity::Wide => " + ", + }; + let fmt = ListFormatting::new(shape, context.config) + .padding(false) + .separator(joiner) + .tactic(DefinitiveListTactic::Horizontal); + let result = write_list(&type_strs, &fmt)?; + + if items.len() <= 1 || (!result.contains('\n') && result.len() <= shape.width) { + return Some(result); + } + + // We need to use multiple lines. + let (type_strs, shape) = if need_indent { + // Rewrite with additional indentation. + let nested_shape = shape + .block_indent(context.config.tab_spaces()) + .with_max_width(context.config); + let type_strs: Vec<_> = itemize_list( + context.snippet_provider, + items.iter(), + "", + "+", + |item| item.span().lo(), + |item| item.span().hi(), + |item| item.rewrite(context, nested_shape), + items[0].span().lo(), + items[items.len() - 1].span().hi(), + false, + ) + .collect(); + (type_strs, nested_shape) + } else { + (type_strs, shape) + }; + let is_bound_extendable = |s: &str, b: &ast::GenericBound| match b { + ast::GenericBound::Outlives(..) => true, + ast::GenericBound::Trait(..) => last_line_extendable(s), + }; + let generic_bounds_in_order = is_generic_bounds_in_order(items); + let fmt = ListFormatting::new(shape, context.config) + .padding(false) + .separator(joiner) + .trailing_separator(SeparatorTactic::Always) + .separator_place(SeparatorPlace::Front); + let fmt = if generic_bounds_in_order { + let custom_list_tactic = std::iter::once(false) // no newline before the first bound + .chain( + items + .iter() + .zip(type_strs.iter()) + .map(|(bound, bound_str)| { + // putting a newline before the current bound depends on the previous bound + !is_bound_extendable(bound_str.inner_as_ref(), bound) + }), + ) + .collect::>(); + fmt.custom_list_tactic(custom_list_tactic) + } else { + fmt.tactic(DefinitiveListTactic::Vertical) + }; + write_list(&type_strs, &fmt) +} + pub(crate) fn can_be_overflowed_type( context: &RewriteContext<'_>, ty: &ast::Ty, diff --git a/tests/source/issue-3669/type_compressed-one.rs b/tests/source/issue-3669/type_compressed-one.rs new file mode 100644 index 00000000000..a6a4a9180a0 --- /dev/null +++ b/tests/source/issue-3669/type_compressed-one.rs @@ -0,0 +1,8 @@ +// rustfmt-version: One +// rustfmt-type_punctuation_density: Compressed + +pub fn do_something<'a, T: Trait1 + Trait2 + 'a>( + &fooo: u32, +) -> impl Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + 'a + 'a + 'a + 'a + 'a + { +} diff --git a/tests/source/issue-3669/type_compressed-two.rs b/tests/source/issue-3669/type_compressed-two.rs new file mode 100644 index 00000000000..ce4a5c8920e --- /dev/null +++ b/tests/source/issue-3669/type_compressed-two.rs @@ -0,0 +1,8 @@ +// rustfmt-version: Two +// rustfmt-type_punctuation_density: Compressed + +pub fn do_something<'a, T: Trait1 + Trait2 + 'a>( + &fooo: u32, +) -> impl Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + Foo + 'a + 'a + 'a + 'a + 'a + { +} diff --git a/tests/source/issue-3669/version-one.rs b/tests/source/issue-3669/version-one.rs new file mode 100644 index 00000000000..f2f1956b066 --- /dev/null +++ b/tests/source/issue-3669/version-one.rs @@ -0,0 +1,76 @@ +// rustfmt-version: One + +pub trait PCG: self::sealed::Sealed // comment1 + + Sized // comment2 + + Eq // comment3 + + Hash // comment4 + + Debug // comment5 + + Clone // comment6 + + Default // comment7 + + Serialize // comment8 + + for<'a> Deserialize<'a> // comment9 +{ + type DoubleState: Copy // Note(Evrey): Because Rust is drunk. 1 + + ShrAssign // Note(Evrey): Because Rust is drunk. 2 + + Shl // Note(Evrey): Because Rust is drunk. 3 ++ BitAnd // Note(Evrey): Because Rust is drunk. 4 ++ BitOrAssign // Note(Evrey): Because Rust is drunk. 5 + + Sub // Note(Evrey): Because Rust is drunk. 6 + + Into // Note(Evrey): Because Rust is drunk. 7 + + Debug // Note(Evrey): Because Rust is drunk. 8 + + Eq // Note(Evrey): Because Rust is drunk. 9 + + Hash // Note(Evrey): Because Rust is drunk. 10 + + Default // Note(Evrey): Because Rust is drunk. 11 + + Serialize // Note(Evrey): Because Rust is drunk. 12 + + for<'a> Deserialize<'a>; // Note(Evrey): Because Rust is drunk. 13 +} + +pub trait Bar: self::sealed::Sealed + // comment1 + Sized + // comment2 + Eq + // comment3 + Hash + // comment4 + Debug + // comment5 + Clone + // comment6 + Default + // comment7 + Serialize + // comment8 + for<'a> Deserialize<'a> // comment9 +{ + type DoubleState: Copy + // Note(Evrey): Because Rust is drunk. 1 + ShrAssign + // Note(Evrey): Because Rust is drunk. 2 + Shl + // Note(Evrey): Because Rust is drunk. 3 +BitAnd + // Note(Evrey): Because Rust is drunk. 4 +BitOrAssign + // Note(Evrey): Because Rust is drunk. 5 + Sub + // Note(Evrey): Because Rust is drunk. 6 + Into + // Note(Evrey): Because Rust is drunk. 7 + Debug + // Note(Evrey): Because Rust is drunk. 8 + Eq + // Note(Evrey): Because Rust is drunk. 9 + Hash + // Note(Evrey): Because Rust is drunk. 10 + Default + // Note(Evrey): Because Rust is drunk. 11 + Serialize + // Note(Evrey): Because Rust is drunk. 12 + for<'a> Deserialize<'a>; // Note(Evrey): Because Rust is drunk. 13 +} + +pub trait Foo: self::sealed::Sealed + + Sized + + Eq + + Hash + + Debug + + Clone + + Default + + Serialize + + for<'a> Deserialize<'a> +{ + type DoubleState: Copy + + ShrAssign + + Shl ++ BitAnd ++ BitOrAssign + + Sub + + Into + + Debug + + Eq + + Hash + + Default + + Serialize + + for<'a> Deserialize<'a>; +} diff --git a/tests/source/issue-3669/version-two.rs b/tests/source/issue-3669/version-two.rs new file mode 100644 index 00000000000..c8e873df7de --- /dev/null +++ b/tests/source/issue-3669/version-two.rs @@ -0,0 +1,84 @@ +// rustfmt-version: Two + +pub trait PCG: self::sealed::Sealed // comment1 + + Sized // comment2 + + Eq // comment3 + + Hash // comment4 + + Debug // comment5 + + Clone // comment6 + + Default // comment7 + + Serialize // comment8 + + for<'a> Deserialize<'a> // comment9 +{ + type DoubleState: Copy // Note(Evrey): Because Rust is drunk. 1 + + ShrAssign // Note(Evrey): Because Rust is drunk. 2 + + Shl // Note(Evrey): Because Rust is drunk. 3 ++ BitAnd // Note(Evrey): Because Rust is drunk. 4 ++ BitOrAssign // Note(Evrey): Because Rust is drunk. 5 + + Sub // Note(Evrey): Because Rust is drunk. 6 + + Into // Note(Evrey): Because Rust is drunk. 7 + + Debug // Note(Evrey): Because Rust is drunk. 8 + + Eq // Note(Evrey): Because Rust is drunk. 9 + + Hash // Note(Evrey): Because Rust is drunk. 10 + + Default // Note(Evrey): Because Rust is drunk. 11 + + Serialize // Note(Evrey): Because Rust is drunk. 12 + + for<'a> Deserialize<'a>; // Note(Evrey): Because Rust is drunk. 13 +} + +pub trait Bar: self::sealed::Sealed + // comment1 + Sized + // comment2 + Eq + // comment3 + Hash + // comment4 + Debug + // comment5 + Clone + // comment6 + Default + // comment7 + Serialize + // comment8 + for<'a> Deserialize<'a> // comment9 +{ + type DoubleState: Copy + // Note(Evrey): Because Rust is drunk. 1 + ShrAssign + // Note(Evrey): Because Rust is drunk. 2 + Shl + // Note(Evrey): Because Rust is drunk. 3 +BitAnd + // Note(Evrey): Because Rust is drunk. 4 +BitOrAssign + // Note(Evrey): Because Rust is drunk. 5 + Sub + // Note(Evrey): Because Rust is drunk. 6 + Into + // Note(Evrey): Because Rust is drunk. 7 + Debug + // Note(Evrey): Because Rust is drunk. 8 + Eq + // Note(Evrey): Because Rust is drunk. 9 + Hash + // Note(Evrey): Because Rust is drunk. 10 + Default + // Note(Evrey): Because Rust is drunk. 11 + Serialize + // Note(Evrey): Because Rust is drunk. 12 + for<'a> Deserialize<'a>; // Note(Evrey): Because Rust is drunk. 13 +} + +pub trait Foo: self::sealed::Sealed + + Sized + + Eq + + Hash + + Debug + + Clone + + Default + + Serialize + + for<'a> Deserialize<'a> +{ + type DoubleState: Copy + + ShrAssign + + Shl ++ BitAnd ++ BitOrAssign + + Sub + + Into + + Debug + + Eq + + Hash + + Default + + Serialize + + for<'a> Deserialize<'a>; +} + +// #2055 +pub trait Foo: +// A and C +A + C +// and B + + B +{} diff --git a/tests/target/issue-3669/type_compressed-one.rs b/tests/target/issue-3669/type_compressed-one.rs new file mode 100644 index 00000000000..46e4a2e5fe9 --- /dev/null +++ b/tests/target/issue-3669/type_compressed-one.rs @@ -0,0 +1,45 @@ +// rustfmt-version: One +// rustfmt-type_punctuation_density: Compressed + +pub fn do_something<'a, T: Trait1+Trait2+'a>( + &fooo: u32, +) -> impl Foo + + Foo + + Foo + + Foo + + Foo + + Foo + + Foo + + Foo + + Foo + + Foo + + Foo + + Foo + + Foo + + Foo + + Foo + + Foo + + Foo + + Foo + + Foo + + Foo + + Foo + + Foo + + Foo + + Foo + + Foo + + Foo + + Foo + + Foo + + Foo + + Foo + + Foo + + Foo + + Foo + + Foo + + Foo + + Foo + + Foo + + Foo + + 'a+'a+'a+'a+'a { +} diff --git a/tests/target/issue-3669/type_compressed-two.rs b/tests/target/issue-3669/type_compressed-two.rs new file mode 100644 index 00000000000..224293f81cb --- /dev/null +++ b/tests/target/issue-3669/type_compressed-two.rs @@ -0,0 +1,45 @@ +// rustfmt-version: Two +// rustfmt-type_punctuation_density: Compressed + +pub fn do_something<'a, T: Trait1+Trait2+'a>( + &fooo: u32, +) -> impl Foo ++Foo ++Foo ++Foo ++Foo ++Foo ++Foo ++Foo ++Foo ++Foo ++Foo ++Foo ++Foo ++Foo ++Foo ++Foo ++Foo ++Foo ++Foo ++Foo ++Foo ++Foo ++Foo ++Foo ++Foo ++Foo ++Foo ++Foo ++Foo ++Foo ++Foo ++Foo ++Foo ++Foo ++Foo ++Foo ++Foo ++Foo ++'a+'a+'a+'a+'a { +} diff --git a/tests/target/issue-3669/version-one.rs b/tests/target/issue-3669/version-one.rs new file mode 100644 index 00000000000..173ec092b1a --- /dev/null +++ b/tests/target/issue-3669/version-one.rs @@ -0,0 +1,77 @@ +// rustfmt-version: One + +pub trait PCG: self::sealed::Sealed // comment1 + + Sized // comment2 + + Eq // comment3 + + Hash // comment4 + + Debug // comment5 + + Clone // comment6 + + Default // comment7 + + Serialize // comment8 + + for<'a> Deserialize<'a> // comment9 +{ + type DoubleState: Copy // Note(Evrey): Because Rust is drunk. 1 + + ShrAssign // Note(Evrey): Because Rust is drunk. 2 + + Shl // Note(Evrey): Because Rust is drunk. 3 ++ BitAnd // Note(Evrey): Because Rust is drunk. 4 ++ BitOrAssign // Note(Evrey): Because Rust is drunk. 5 + + Sub // Note(Evrey): Because Rust is drunk. 6 + + Into // Note(Evrey): Because Rust is drunk. 7 + + Debug // Note(Evrey): Because Rust is drunk. 8 + + Eq // Note(Evrey): Because Rust is drunk. 9 + + Hash // Note(Evrey): Because Rust is drunk. 10 + + Default // Note(Evrey): Because Rust is drunk. 11 + + Serialize // Note(Evrey): Because Rust is drunk. 12 + + for<'a> Deserialize<'a>; // Note(Evrey): Because Rust is drunk. 13 +} + +pub trait Bar: self::sealed::Sealed + // comment1 + Sized + // comment2 + Eq + // comment3 + Hash + // comment4 + Debug + // comment5 + Clone + // comment6 + Default + // comment7 + Serialize + // comment8 + for<'a> Deserialize<'a> // comment9 +{ + type DoubleState: Copy + // Note(Evrey): Because Rust is drunk. 1 + ShrAssign + // Note(Evrey): Because Rust is drunk. 2 + Shl + // Note(Evrey): Because Rust is drunk. 3 +BitAnd + // Note(Evrey): Because Rust is drunk. 4 +BitOrAssign + // Note(Evrey): Because Rust is drunk. 5 + Sub + // Note(Evrey): Because Rust is drunk. 6 + Into + // Note(Evrey): Because Rust is drunk. 7 + Debug + // Note(Evrey): Because Rust is drunk. 8 + Eq + // Note(Evrey): Because Rust is drunk. 9 + Hash + // Note(Evrey): Because Rust is drunk. 10 + Default + // Note(Evrey): Because Rust is drunk. 11 + Serialize + // Note(Evrey): Because Rust is drunk. 12 + for<'a> Deserialize<'a>; // Note(Evrey): Because Rust is drunk. 13 +} + +pub trait Foo: + self::sealed::Sealed + + Sized + + Eq + + Hash + + Debug + + Clone + + Default + + Serialize + + for<'a> Deserialize<'a> +{ + type DoubleState: Copy + + ShrAssign + + Shl + + BitAnd + + BitOrAssign + + Sub + + Into + + Debug + + Eq + + Hash + + Default + + Serialize + + for<'a> Deserialize<'a>; +} diff --git a/tests/target/issue-3669/version-two.rs b/tests/target/issue-3669/version-two.rs new file mode 100644 index 00000000000..082fa8164e3 --- /dev/null +++ b/tests/target/issue-3669/version-two.rs @@ -0,0 +1,88 @@ +// rustfmt-version: Two + +pub trait PCG: + self::sealed::Sealed // comment1 + + Sized // comment2 + + Eq // comment3 + + Hash // comment4 + + Debug // comment5 + + Clone // comment6 + + Default // comment7 + + Serialize // comment8 + + for<'a> Deserialize<'a> // comment9 +{ + type DoubleState: Copy // Note(Evrey): Because Rust is drunk. 1 + + ShrAssign // Note(Evrey): Because Rust is drunk. 2 + + Shl // Note(Evrey): Because Rust is drunk. 3 + + BitAnd // Note(Evrey): Because Rust is drunk. 4 + + BitOrAssign // Note(Evrey): Because Rust is drunk. 5 + + Sub // Note(Evrey): Because Rust is drunk. 6 + + Into // Note(Evrey): Because Rust is drunk. 7 + + Debug // Note(Evrey): Because Rust is drunk. 8 + + Eq // Note(Evrey): Because Rust is drunk. 9 + + Hash // Note(Evrey): Because Rust is drunk. 10 + + Default // Note(Evrey): Because Rust is drunk. 11 + + Serialize // Note(Evrey): Because Rust is drunk. 12 + + for<'a> Deserialize<'a>; // Note(Evrey): Because Rust is drunk. 13 +} + +pub trait Bar: + self::sealed::Sealed // comment1 + + Sized // comment2 + + Eq // comment3 + + Hash // comment4 + + Debug // comment5 + + Clone // comment6 + + Default // comment7 + + Serialize // comment8 + + for<'a> Deserialize<'a> // comment9 +{ + type DoubleState: Copy // Note(Evrey): Because Rust is drunk. 1 + + ShrAssign // Note(Evrey): Because Rust is drunk. 2 + + Shl // Note(Evrey): Because Rust is drunk. 3 + + BitAnd // Note(Evrey): Because Rust is drunk. 4 + + BitOrAssign // Note(Evrey): Because Rust is drunk. 5 + + Sub // Note(Evrey): Because Rust is drunk. 6 + + Into // Note(Evrey): Because Rust is drunk. 7 + + Debug // Note(Evrey): Because Rust is drunk. 8 + + Eq // Note(Evrey): Because Rust is drunk. 9 + + Hash // Note(Evrey): Because Rust is drunk. 10 + + Default // Note(Evrey): Because Rust is drunk. 11 + + Serialize // Note(Evrey): Because Rust is drunk. 12 + + for<'a> Deserialize<'a>; // Note(Evrey): Because Rust is drunk. 13 +} + +pub trait Foo: + self::sealed::Sealed + + Sized + + Eq + + Hash + + Debug + + Clone + + Default + + Serialize + + for<'a> Deserialize<'a> +{ + type DoubleState: Copy + + ShrAssign + + Shl + + BitAnd + + BitOrAssign + + Sub + + Into + + Debug + + Eq + + Hash + + Default + + Serialize + + for<'a> Deserialize<'a>; +} + +// #2055 +pub trait Foo: +// A and C + A + + C // and B + + B +{ +}