Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent trailing whitespace in where clause bound predicate #5019

Merged
merged 1 commit into from
Oct 31, 2021

Conversation

ytmimi
Copy link
Contributor

@ytmimi ytmimi commented Oct 9, 2021

Resolves #5012
Resolves #4850

This behavior was noticed when using the trailing_comma = "Never"
configuration option (5012).

This behavior was also noticed when using default configurations (4850).

rustfmt would add a trailing space to where clause bounds that had an
empty right hand side.

Now no trailing space is added to the end of these where clause bounds.

Comment on lines +1968 to +1967
Some(ref new_str) if new_str.is_empty() => {
return Some(String::new());
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is on one of the hottest paths in the codebase, and while there wouldn't be any significant performance concerns with the proposed diff in this location, we should strive to avoid having to make fixes at this level due to the higher potential for unintended knock on effects.

Ideally, we'd be able to correct the issue upstream instead of having to try to retcon it here. Do you think that will be feasible in this case?

Copy link
Contributor Author

@ytmimi ytmimi Oct 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, we'd be able to correct the issue upstream instead of having to try to retcon it here. Do you think that will be feasible in this case?

I think we could address it a little further upstream. Before I pitch some ideas I want to lay out my understanding of the code flow.

Code Flow

In this case when we call rewrite on ast::WherePredicate, we hit the first match arm of ast::WherePredicate::BoundPredicate, which ultimately calls expr::rewrite_assign_rhs

rustfmt/src/types.rs

Lines 412 to 433 in 599b2fd

impl Rewrite for ast::WherePredicate {
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
// FIXME: dead spans?
let result = match *self {
ast::WherePredicate::BoundPredicate(ast::WhereBoundPredicate {
ref bound_generic_params,
ref bounded_ty,
ref bounds,
..
}) => {
let type_str = bounded_ty.rewrite(context, shape)?;
let colon = type_bound_colon(context).trim_end();
let lhs = if let Some(lifetime_str) =
rewrite_lifetime_param(context, shape, bound_generic_params)
{
format!("for<{}> {}{}", lifetime_str, type_str, colon)
} else {
format!("{}{}", type_str, colon)
};
rewrite_assign_rhs(context, lhs, bounds, shape)?
}

As it's currently implemented, expr::rewrite_assign_rhs just calls expr::rewrite_assign_rhs_with, which is where the lhs and rhs get concatenated (linked below for reference).

rustfmt/src/expr.rs

Lines 1918 to 1928 in 599b2fd

pub(crate) fn rewrite_assign_rhs_with<S: Into<String>, R: Rewrite>(
context: &RewriteContext<'_>,
lhs: S,
ex: &R,
shape: Shape,
rhs_tactics: RhsTactics,
) -> Option<String> {
let lhs = lhs.into();
let rhs = rewrite_assign_rhs_expr(context, &lhs, ex, shape, rhs_tactics)?;
Some(lhs + &rhs)
}

Ideas

  1. We could add a conditional check in expr::rewrite_assign_rhs_with to only concatenate the lhs and rhs if the rhs isn't just whitespace.
  • This change is still on the hot path, and really only patches the underlying issue introduced by expr::choose_rhs returning " " when there is no rhs, but it does tackle the problem slightly further upstream.
  1. Create a new function expr::rewrite_assign_potentially_empty_rhs, (open to name suggestion) that will be called instead of expr::rewrite_assign_rhs when rewriting ast::WherePredicate::BoundPredicate.
  • We'll still leverage code that's on the hot path, but we'll only have to perform the relevant conditional checks in cases where we can't be sure that there will always be a rhs. (I think I'm leaning towards this option)
  1. Go with the current solution that makes changes directly to expr::choose_rhs

Definitely open to your thoughts and if you have other suggestions for where to make this fix!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the excellent, detailed write up. After reading and reflecting I think you were on the right path to begin with. I was originally thinking we'd check if the bounds where empty at the original callsite and skip trying to join a lhs with a non-existent rhs to begin with. However, doing that would likely start to bleed through the encapsulation of the Rewrite impl for GenericBounds and/or require an upfront rewrite call of the bounds node to check for the empty string.

As such I think I'm leaning pretty heavily towards num 3/your original proposal. Realize this goes slightly past the literal issue reports but could you also add some cases with the snippet from 5012 that are configured to do comma inserts too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

option 3 it is! I'll add those additional test cases and request a follow up review when they've been added!

resolves 5012
resolves 4850

This behavior was noticed when using the ``trailing_comma = "Never"``
configuration option (5012).

This behavior was also noticed when using default configurations (4850).

rustfmt would add a trailing space to where clause bounds that had an
empty right hand side.

Now no trailing space is added to the end of these where clause bounds.
@calebcartwright
Copy link
Member

Thanks!

@calebcartwright calebcartwright merged commit 5ce82e1 into rust-lang:master Oct 31, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants