diff --git a/src/items.rs b/src/items.rs index a63fd874487..ab9ace9de20 100644 --- a/src/items.rs +++ b/src/items.rs @@ -1762,15 +1762,53 @@ fn rewrite_ty( result.push_str(&generics_str); } + // Default keeps today's behavior when there are no bounds, so comments between + // the ident and `where` are still recovered. + let mut span_end_before_where = generics.span.hi(); + if let Some(bounds) = generic_bounds_opt { if !bounds.is_empty() { // 2 = `: ` - let shape = Shape::indented(indent, context.config); - let shape = shape.offset_left(result.len() + 2, span)?; - let type_bounds = bounds - .rewrite_result(context, shape) - .map(|s| format!(": {}", s))?; - result.push_str(&type_bounds); + let item_shape = Shape::indented(indent, context.config); + let shape = item_shape.offset_left(result.len() + 2, span)?; + let type_bounds = bounds.rewrite_result(context, shape)?; + + // The bounds rewrite only covers the bounds themselves, so comments + // around the `:` would otherwise be dropped. Recover them on either + // side of the colon, keeping the exact `: ` layout when there are none. + let bounds_lo = bounds[0].span().lo(); + let colon_lo = context + .snippet_provider + .span_before(mk_sp(generics.span.hi(), bounds_lo), ":"); + let before_colon = mk_sp(generics.span.hi(), colon_lo); + let after_colon = mk_sp(colon_lo + BytePos(1), bounds_lo); + + if contains_comment(context.snippet(before_colon)) { + result = combine_strs_with_missing_comments( + context, + &result, + ":", + before_colon, + item_shape, + true, + )?; + } else { + result.push(':'); + } + + if contains_comment(context.snippet(after_colon)) { + result = combine_strs_with_missing_comments( + context, + &result, + &type_bounds, + after_colon, + item_shape, + true, + )?; + } else { + result.push_str(&format!(" {}", type_bounds)); + } + span_end_before_where = bounds[bounds.len() - 1].span().hi(); } } @@ -1787,7 +1825,7 @@ fn rewrite_ty( false, "=", None, - generics.span.hi(), + span_end_before_where, option, )?; result.push_str(&before_where_clause_str); diff --git a/tests/source/associated-type-bounds-with-comments.rs b/tests/source/associated-type-bounds-with-comments.rs new file mode 100644 index 00000000000..d6310b89cdf --- /dev/null +++ b/tests/source/associated-type-bounds-with-comments.rs @@ -0,0 +1,269 @@ +#![feature(associated_type_defaults)] + +// Exhaustive coverage of comments around associated-type bounds, keyed by +// position relative to the `:` / bounds / `where`, comment kind (line, +// block, multi-line line, multi-line block), and context (trait item with +// bounds, trait item without bounds, GAT, impl/free-alias shared path, +// bounds + RHS default). See #6761. +// +// Out of scope: a comment after bounds with no `where` clause at all (#6815) +// — that region has no span computed for it, so it needs separate handling. + +// P1: between the ident/generics and the `:`. +trait P1 { + type A // line + : Bound + where + Self: Copy; + + type B /* inline */: Bound + where + Self: Copy; + + type C + // multi- + // line + : Bound + where + Self: Copy; + + type D + /* multi- + * line + */ + : Bound + where + Self: Copy; +} + +// P2: between the `:` and the first bound. +trait P2 { + type A: // line + Bound + where + Self: Copy; + + type B: /* inline */ Bound + where + Self: Copy; + + type C: + // multi- + // line + Bound + where + Self: Copy; + + type D: + /* multi- + * line + */ + Bound + where + Self: Copy; +} + +// P3: inside the bounds (generic args). Original #6761 repro. +trait P3 { + type A: Iterator< + // line + Item = Self, + > + where + Self: Copy; + + type B: Iterator< + /* inline */ + Item = Self, + > + where + Self: Copy; + + type C: Iterator< + // multi- + // line + Item = Self, + > + where + Self: Copy; + + type D: Iterator< + /* multi- + * line + */ + Item = Self, + > + where + Self: Copy; +} + +// P4: between two bounds. +trait P4 { + type A: Bound + // line + Bound + where + Self: Copy; + + type B: Bound + /* inline */ Bound + where + Self: Copy; + + type C: Bound + + // multi- + // line + Bound + where + Self: Copy; + + type D: Bound + + /* multi- + * line + */ + Bound + where + Self: Copy; +} + +// P5: after the last bound, before `where`. This is the duplication bug. +trait P5 { + type A: Bound + // line + where + Self: Copy; + + type B: Bound /* inline */ + where + Self: Copy; + + type C: Bound + // multi- + // line + where + Self: Copy; + + type D: Bound + /* multi- + * line + */ + where + Self: Copy; +} + +// P5, same-line trailing comment: the reviewer's exact input on #7014. +// rustfmt moves this onto its own line. That is `rewrite_where_keyword`'s +// shared behavior for every item kind (see tests/target/issue-3194.rs for +// struct/enum and type-alias-where-clauses-with-comments.rs for aliases), +// not something this fix introduces. Pinned here so the behavior is explicit. +trait P5SameLine { + type A: Iterator // trailing + where + Self: Copy; + + type B: Bound // trailing + where + Self: Copy; + + type C: Bound /* trailing */ + where + Self: Copy; +} + +// P5, no bounds: exercises the preserved `generics.span.hi()` default. +trait P5NoBounds { + type A + // line + where + Self: Copy; + + type B /* inline */ + where + Self: Copy; + + type C + // multi- + // line + where + Self: Copy; + + type D + /* multi- + * line + */ + where + Self: Copy; +} + +// Width: a colon comment must not wrap while the line still fits in +// max_width. `combine_strs_with_missing_comments` already measures the whole +// prefix, so it must be given the item shape, not the bounds-offset shape, +// or the prefix is charged twice and the line breaks early. +trait ColonCommentWidth { + type AssociatedTypeWithLongName: /* comment */ SomeModeratelyLongTraitName + where + Self: Copy; +} + +// P6: after the `where` keyword, before the first predicate. +trait P6 { + type A: Bound + where + // line + Self: Copy; + + type B: Bound + where + /* inline */ + Self: Copy; + + type C: Bound + where + // multi- + // line + Self: Copy; + + type D: Bound + where + /* multi- + * line + */ + Self: Copy; +} + +// GAT with bounds: non-empty `generics.span`. +trait GatP5 { + type A: Bound + // line + where + U: Copy; + + type B: Bound /* inline */ + where + U: Copy; +} + +// Impl associated type: shared rewrite_ty/where-clause path. +impl ImplTarget for S { + type A + // line, impl assoc type + where + U: Copy, + = Vec; +} + +// Free type alias: shared rewrite_ty/where-clause path. +type Free +// line, free alias +where + U: Copy, += Vec; + +// Bounds + RHS default, with comments (was Case 4, previously untested). +trait Rhs { + type A: Bound = () // line + where + Self: Copy; + + type B: Bound /* inline */ = () + where + Self: Copy; +} diff --git a/tests/source/issue-6761.rs b/tests/source/issue-6761.rs new file mode 100644 index 00000000000..f203ae340c2 --- /dev/null +++ b/tests/source/issue-6761.rs @@ -0,0 +1,9 @@ +// Case 1: exact issue repro (comment inside bounds + before-where clause). +pub trait Trait { + type I: Iterator< + // This is an item + Item = Self, + > + where + Self: Copy; +} diff --git a/tests/target/associated-type-bounds-with-comments.rs b/tests/target/associated-type-bounds-with-comments.rs new file mode 100644 index 00000000000..a8c25f03a33 --- /dev/null +++ b/tests/target/associated-type-bounds-with-comments.rs @@ -0,0 +1,277 @@ +#![feature(associated_type_defaults)] + +// Exhaustive coverage of comments around associated-type bounds, keyed by +// position relative to the `:` / bounds / `where`, comment kind (line, +// block, multi-line line, multi-line block), and context (trait item with +// bounds, trait item without bounds, GAT, impl/free-alias shared path, +// bounds + RHS default). See #6761. +// +// Out of scope: a comment after bounds with no `where` clause at all (#6815) +// — that region has no span computed for it, so it needs separate handling. + +// P1: between the ident/generics and the `:`. +trait P1 { + type A // line + : Bound + where + Self: Copy; + + type B /* inline */ : Bound + where + Self: Copy; + + type C + // multi- + // line + : Bound + where + Self: Copy; + + type D + /* multi- + * line + */ + : Bound + where + Self: Copy; +} + +// P2: between the `:` and the first bound. +trait P2 { + type A: // line + Bound + where + Self: Copy; + + type B: /* inline */ Bound + where + Self: Copy; + + type C: + // multi- + // line + Bound + where + Self: Copy; + + type D: + /* multi- + * line + */ + Bound + where + Self: Copy; +} + +// P3: inside the bounds (generic args). Original #6761 repro. +trait P3 { + type A: Iterator< + // line + Item = Self, + > + where + Self: Copy; + + type B: Iterator + where + Self: Copy; + + type C: Iterator< + // multi- + // line + Item = Self, + > + where + Self: Copy; + + type D: Iterator< + /* multi- + * line + */ + Item = Self, + > + where + Self: Copy; +} + +// P4: between two bounds. +trait P4 { + type A: Bound + + // line + Bound + where + Self: Copy; + + type B: Bound + /* inline */ Bound + where + Self: Copy; + + type C: Bound + + + // multi- + // line + Bound + where + Self: Copy; + + type D: Bound + + + /* multi- + * line + */ + Bound + where + Self: Copy; +} + +// P5: after the last bound, before `where`. This is the duplication bug. +trait P5 { + type A: Bound + // line + where + Self: Copy; + + type B: Bound + /* inline */ + where + Self: Copy; + + type C: Bound + // multi- + // line + where + Self: Copy; + + type D: Bound + /* multi- + * line + */ + where + Self: Copy; +} + +// P5, same-line trailing comment: the reviewer's exact input on #7014. +// rustfmt moves this onto its own line. That is `rewrite_where_keyword`'s +// shared behavior for every item kind (see tests/target/issue-3194.rs for +// struct/enum and type-alias-where-clauses-with-comments.rs for aliases), +// not something this fix introduces. Pinned here so the behavior is explicit. +trait P5SameLine { + type A: Iterator + // trailing + where + Self: Copy; + + type B: Bound + // trailing + where + Self: Copy; + + type C: Bound + /* trailing */ + where + Self: Copy; +} + +// P5, no bounds: exercises the preserved `generics.span.hi()` default. +trait P5NoBounds { + type A + // line + where + Self: Copy; + + type B + /* inline */ + where + Self: Copy; + + type C + // multi- + // line + where + Self: Copy; + + type D + /* multi- + * line + */ + where + Self: Copy; +} + +// Width: a colon comment must not wrap while the line still fits in +// max_width. `combine_strs_with_missing_comments` already measures the whole +// prefix, so it must be given the item shape, not the bounds-offset shape, +// or the prefix is charged twice and the line breaks early. +trait ColonCommentWidth { + type AssociatedTypeWithLongName: /* comment */ SomeModeratelyLongTraitName + where + Self: Copy; +} + +// P6: after the `where` keyword, before the first predicate. +trait P6 { + type A: Bound + where + // line + Self: Copy; + + type B: Bound + where + /* inline */ + Self: Copy; + + type C: Bound + where + // multi- + // line + Self: Copy; + + type D: Bound + where + /* multi- + * line + */ + Self: Copy; +} + +// GAT with bounds: non-empty `generics.span`. +trait GatP5 { + type A: Bound + // line + where + U: Copy; + + type B: Bound + /* inline */ + where + U: Copy; +} + +// Impl associated type: shared rewrite_ty/where-clause path. +impl ImplTarget for S { + type A + // line, impl assoc type + where + U: Copy, + = Vec; +} + +// Free type alias: shared rewrite_ty/where-clause path. +type Free +// line, free alias +where + U: Copy, += Vec; + +// Bounds + RHS default, with comments (was Case 4, previously untested). +trait Rhs { + type A: Bound + = () + // line + where + Self: Copy; + + type B: Bound /* inline */ = () + where + Self: Copy; +} diff --git a/tests/target/issue-6761.rs b/tests/target/issue-6761.rs new file mode 100644 index 00000000000..f203ae340c2 --- /dev/null +++ b/tests/target/issue-6761.rs @@ -0,0 +1,9 @@ +// Case 1: exact issue repro (comment inside bounds + before-where clause). +pub trait Trait { + type I: Iterator< + // This is an item + Item = Self, + > + where + Self: Copy; +}