Skip to content

Conversation

shulaoda
Copy link
Contributor

@shulaoda shulaoda commented Oct 10, 2025

Fixes #6161

Summary

The issue was in the CharClasses iterator's raw string parsing logic (src/comment.rs:1358-1372). When encountering the closing " of a zero-hash raw string (r"..."), the code incorrectly set char_kind to Normal before transitioning the state machine to CharClassesStatus::Normal.

This caused the closing quote to be classified as FullCodeCharKind::Normal instead of FullCodeCharKind::InString. The LineClasses iterator downstream depends on accurate character classification to determine string boundaries. Misclassifying the closing quote led to incorrect string boundary detection, which cascaded into wrong indentation calculations during macro formatting.

More Details: Why the Bug Only Affects Specific Cases

Example 1: Closing quote " at line start (❌ Bug triggers)

fn f() {
    my_macro! {
        m =>
        "a": r"bb
                    ccc
",  // ← Line starts with " (closing quote)
    };
}

What happens in LineClasses for the line ",:

// Step 1: peek() sees " as first character
// Bug: CharClasses marked it as Normal instead of InString
start_kind = FullCodeCharKind::Normal  // ❌ Wrong!

// Step 2: When reaching \n
match (start_kind, kind) {
    (FullCodeCharKind::InString, FullCodeCharKind::Normal) => {
        FullCodeCharKind::EndString
    }
    _ => kind,  // ❌ Matches here! start_kind is Normal
}

// Result: Line is marked as Normal ❌
// Impact: In trim_left_preserve_layout, this line is treated as regular code,
//         included in minimum indent calculation.
//
//         Since " is at line start, prefix_space_width = 0
//         → min_prefix_space_width = 0 (always!)
//         → new_indent_width = indent.width() + original_indent_width - 0
//         → Each formatting adds original_indent_width, causing infinite growth!

Example 2: Content character c at line start (✓ Works despite bug)

fn f() {
    my_macro! {
        m =>
        "a": r"bb
                    ccc
c",  // ← Line starts with c (string content)
    };
}

What happens in LineClasses for the line c",:

// Step 1: peek() sees c as first character
// Correct: CharClasses marked it as InString
start_kind = FullCodeCharKind::InString  // ✓ Correct!

// Step 2: When reaching \n (kind = Normal after reading ", which has bug)
match (start_kind, kind) {
    (FullCodeCharKind::InString, FullCodeCharKind::Normal) => {
        FullCodeCharKind::EndString  // ✓ Matches here!
    }
    _ => kind,
}

// Result: Line is marked as EndString ✓
// Impact: In trim_left_preserve_layout, this line is correctly excluded
//         from indent calculation → formatting is idempotent!

Reference

rustfmt/src/comment.rs

Lines 1531 to 1558 in 50a49e7

let start_kind = match self.base.peek() {
Some((kind, _)) => *kind,
None => unreachable!(),
};
for (kind, c) in self.base.by_ref() {
// needed to set the kind of the ending character on the last line
self.kind = kind;
if c == '\n' {
self.kind = match (start_kind, kind) {
(FullCodeCharKind::Normal, FullCodeCharKind::InString) => {
FullCodeCharKind::StartString
}
(FullCodeCharKind::InString, FullCodeCharKind::Normal) => {
FullCodeCharKind::EndString
}
(FullCodeCharKind::InComment, FullCodeCharKind::InStringCommented) => {
FullCodeCharKind::StartStringCommented
}
(FullCodeCharKind::InStringCommented, FullCodeCharKind::InComment) => {
FullCodeCharKind::EndStringCommented
}
_ => kind,
};
break;
}
line.push(c);
}

rustfmt/src/utils.rs

Lines 615 to 625 in 50a49e7

// Because there is a veto against trimming and indenting lines within a string,
// such lines should not be taken into account when computing the minimum.
match kind {
FullCodeCharKind::InStringCommented | FullCodeCharKind::EndStringCommented
if config.style_edition() >= StyleEdition::Edition2024 =>
{
None
}
FullCodeCharKind::InString | FullCodeCharKind::EndString => None,
_ => prefix_space_width,
}

@shulaoda
Copy link
Contributor Author

I’m not very familiar with the codebase, so there might be a better way to handle this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

perpetually increasing indentation

2 participants