Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 43 additions & 5 deletions src/items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1762,15 +1762,53 @@ fn rewrite_ty<R: Rewrite>(
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 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_pos = context
.snippet_provider
.span_after(mk_sp(generics.span.hi(), bounds_lo), ":");
let before_colon = mk_sp(generics.span.hi(), colon_pos - BytePos(1));
let after_colon = mk_sp(colon_pos, bounds_lo);

if contains_comment(context.snippet(before_colon)) {
result = combine_strs_with_missing_comments(
context,
&result,
":",
before_colon,
shape,
true,
)?;
} else {
result.push(':');
}

if contains_comment(context.snippet(after_colon)) {
result = combine_strs_with_missing_comments(
context,
&result,
&type_bounds,
after_colon,
shape,
true,
)?;
} else {
result.push_str(&format!(" {}", type_bounds));
}
span_end_before_where = bounds[bounds.len() - 1].span().hi();
}
}

Expand All @@ -1787,7 +1825,7 @@ fn rewrite_ty<R: Rewrite>(
false,
"=",
None,
generics.span.hi(),
span_end_before_where,
option,
)?;
result.push_str(&before_where_clause_str);
Expand Down
94 changes: 94 additions & 0 deletions tests/source/issue-6761.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#![feature(associated_type_defaults)]

// 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;
}

// Case 2: comment between the bound and `where`.
trait Bar1 {
type B: Iterator<Item = u8> // trailing
where
Self: Copy;
}

// Case 3: a `/` appears in the bounds without being a comment.
trait Foo<const N: usize> {}
trait Baz {
type C: Foo<{ 6 / 2 }>
where
Self: Copy;
}

// Case 4: bound + RHS + trailing where clause (associated type default).
trait Bound {}
impl Bound for () {}
trait Qux {
type D: Bound = () where Self: Copy;
}

// Case 5a: associated type in an impl block, no bounds (bounds have no
// effect on impl assoc types and are rejected by rustc), still exercises
// the shared rewrite_ty/where-clause path.
trait Gat {
type I<T>
where
T: Copy;
}
struct S;
impl Gat for S {
type I<T>
// comment before where
where
T: Copy,
= Vec<T>;
}

// Case 5b: free type alias, no bounds (bounds have no effect outside trait
// definitions), still exercises the shared rewrite_ty/where-clause path.
type E<T>
// a free comment
where
T: Copy,
= Vec<T>;

// Case 6: no bounds + comment before `where` (guards the preserved
// generics.span.hi() default).
trait NoBounds {
type F // just a comment
where
Self: Copy;
}

// Case 7: comments around the `:` must not be dropped. The bounds rewrite
// covers only the bounds themselves, so these are recovered separately.
trait AroundColon {
type G: /* after colon */ Bound
where
Self: Copy;

type H: /* c1 */ Bound + Bound
where
Self: Copy;

type I: // line comment after colon
Bound
where
Self: Copy;

type J /* before colon */: Bound
where
Self: Copy;
}

// Case 8: comment around the `:` on a generic associated type.
trait AroundColonGat {
type K<U>: /* generics too */ Bound
where
U: Copy;
}
99 changes: 99 additions & 0 deletions tests/target/issue-6761.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#![feature(associated_type_defaults)]

// 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;
}

// Case 2: comment between the bound and `where`.
trait Bar1 {
type B: Iterator<Item = u8>
// trailing
where
Self: Copy;
}

// Case 3: a `/` appears in the bounds without being a comment.
trait Foo<const N: usize> {}
trait Baz {
type C: Foo<{ 6 / 2 }>
where
Self: Copy;
}

// Case 4: bound + RHS + trailing where clause (associated type default).
trait Bound {}
impl Bound for () {}
trait Qux {
type D: Bound
= ()
where
Self: Copy;
}

// Case 5a: associated type in an impl block, no bounds (bounds have no
// effect on impl assoc types and are rejected by rustc), still exercises
// the shared rewrite_ty/where-clause path.
trait Gat {
type I<T>
where
T: Copy;
}
struct S;
impl Gat for S {
type I<T>
// comment before where
where
T: Copy,
= Vec<T>;
}

// Case 5b: free type alias, no bounds (bounds have no effect outside trait
// definitions), still exercises the shared rewrite_ty/where-clause path.
type E<T>
// a free comment
where
T: Copy,
= Vec<T>;

// Case 6: no bounds + comment before `where` (guards the preserved
// generics.span.hi() default).
trait NoBounds {
type F
// just a comment
where
Self: Copy;
}

// Case 7: comments around the `:` must not be dropped. The bounds rewrite
// covers only the bounds themselves, so these are recovered separately.
trait AroundColon {
type G: /* after colon */ Bound
where
Self: Copy;

type H: /* c1 */ Bound + Bound
where
Self: Copy;

type I: // line comment after colon
Bound
where
Self: Copy;

type J /* before colon */ : Bound
where
Self: Copy;
}

// Case 8: comment around the `:` on a generic associated type.
trait AroundColonGat {
type K<U>: /* generics too */ Bound
where
U: Copy;
}