Posted on :: Tags:

Contributor: SeoYoung Lee(@ding-young)

Mentor: Yacin Tmimi(@ytmimi)

Project: Rewriting the Rewrite trait

Organization: The Rust Foundation

Overview

pub(crate) trait Rewrite {
    /// Rewrite self into shape.
    fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String>;
}

Rustfmt's internal formatting mechanism heavily relies on the rewrite trait. Each AST node's rewrite implementation is called to handle formatting. The current rewrite method returns an Option, with None indicating a formatting failure.

pub(crate) fn rewrite_struct_field(
    context: &RewriteContext<'_>,
    field: &ast::FieldDef,
    shape: Shape,
    lhs_max_width: usize,
) -> Option<String> {
    if contains_skip(&field.attrs) {
        return Some(context.snippet(field.span()).to_owned());
    }

    let type_annotation_spacing = type_annotation_spacing(context.config);
    let prefix = rewrite_struct_field_prefix(context, field)?;

    let attrs_str = field.attrs.rewrite(context, shape)?;
    ...
}

As shown in the rustfmt code snippet above, formatting can fail while processing an inner attribute of a struct's field or when rewriting prefixes like pub(crate) for visibility.

error[internal]: line formatted, but exceeded maximum width (maximum: 100 (see `max_width` option), found: 105)
 --> /home/ding-young/ws/project/rust/rustfmt/mytest.rs:4:4:101
  |
4 |     pub a:  foo::AAAAAAAAAAAAAAAAAAAAAAAAAA::BBBBBBBBBBBBBBBBBBBB::CCCCCCCCCCCCCCCCCCCCCCCCC::DDDDDDDDDD,
  |                                                                                                     ^^^^^
  |

While errors were reported to the user by checking whether each line exceeded the maximum line width, this approach didn’t track where exactly the formatting failure occurred. Also, it was challenging to determine the root cause of the failure during formatting since the failure was modeled as None.
To track the context of where and why we fails to format, the primary goal of this project was to enhance the rewrite trait and its implementation.

What work was done

Initial work was to add a new struct representing internal rewrite error and add a default implementation for rewrite_result method in Rewrite trait. This rewrite_result method, which returns Result<String, RewriteError> is used to gradually replace rewrite methods.

We then created a to-do list of AST nodes that require rewrite_result implementation and documented it as a tracking issue.

impl Rewrite for ast::FieldDef {
    fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
        self.rewrite_result(context, shape).ok()
    }

    fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
        // ...  
    }
}

We visited about 42 nodes, adding rewrite_result implementations one by one, based on the previous rewrite implementations.

Since rustfmt uses the itemize_list and write_list functions to format list-like expressions, we also updated the related structs and functions.

We added a new dedicated err variant for representing macro specific rewrite failures, such as when we fail to parse given macro.

We implemented rewrite_result for ast::Expr after the above PRs were merged, as it required updating functions for rewriting sub-expressions.

Current status of the project

At present, we have added rewrite_result implementation for 41 nodes, and internal formatting actually call our new rewrite_result method instead of rewrite. However, the result is currently converted to Option, so the context information contained in the rewrite errors has not yet been reflected in the user-level error messages.
The propagation of this RewriteResult will be addressed in future works.

Ongoing / Future work

We plan to refactor functions responsible for formatting high-level items and ensure that rewrite errors are properly propagated to the user. The overall implementation plan has already been discussed, and the necessary tasks have been added to the tracking issue. For more details about the plan, refer to my previous blog post.

Any challenges or important things I learned during the project.

The process took longer than expected. Initially, I thought it would be a simple conversion from None to RewriteResult, but unexpected issues emerged, requiring deeper consideration at several points.
One of the key challenges was deciding how to model failure. The existing codebase modeled failure with None, without distinguishing between the causes of the error. I encountered cases where None was returned not because of a failure, but because the target wasn't subject to formatting. The work involved reviewing functions used for formatting specific AST nodes, understanding the context (with the compiler's help), and determining whether an actual error should be returned or if it was a situation where we should attempt a second try with different formatting requirements (e.g., moving to the next line). After that, I would replace None accordingly. While this added complexity, it helped me become more familiar with Rustfmt's codebase.
Throughout this project, I’ve recognized many areas where I need improvement, especially in terms of project management and communication. For example, when I encounter an obstacle, I’ve learned that instead of spending too much time on it, it's more effective to clearly identify the issue and request help where needed. I also realized the importance of regularly sharing what I’m working on and keeping others engaged with the project.
The process of planning the project and achieving the goals throughout actual coding period during the GSoC program was invaluable. I gained experience in breaking down tasks and working step by step, especially when I had to modify many parts of the codebase. I fondly remember preparing what to share and organizing the issues to discuss with my mentor before each weekly meeting, as well as the satisfaction of checking off items in the tracking issue. I’m also excited to continue contributing even after the GSoC period.

Acknowledgement

I would like to express my sincere gratitude to my mentor, who provided invaluable guidance and support during our weekly meetings. I am also thankful to the Rust Foundation and our organization admin for their support throughout the project. Additionally, I have learned a great deal from other contributors participating in the GSoC program.