Skip to content

Commit 257755a

Browse files
committed
Declare that any RFunctionDefinition can benefit from argument grouping
Allowing you to put a line break here `map(xs, function(x)<here>x + 1)`, which then causes autobracing to kick in, and now gives you the "middle variant" that you probably want of ``` map(xs, function(x) { x + 1 }) ```
1 parent 46de645 commit 257755a

File tree

6 files changed

+344
-165
lines changed

6 files changed

+344
-165
lines changed

crates/air_r_formatter/src/r/auxiliary/call_arguments.rs

+135-23
Original file line numberDiff line numberDiff line change
@@ -123,25 +123,60 @@ impl RCallLikeArguments {
123123
///
124124
/// Example:
125125
///
126+
/// An inline function without braces is an example where "most flat" is considered.
127+
/// We consider this as groupable because it is possible that a long function body
128+
/// or long function signature can cause a break, which may benefit from the middle
129+
/// variant before expanding all the way to the most expanded variant.
130+
///
126131
/// ```r
127-
/// # NOTE: Not currently possible, as the `{}` always force a break right now,
128-
/// # but this would be an example if `{}` didn't force a break.
129-
/// map(xs, function(x) {})
132+
/// # Most flat wins
133+
/// map(xs, function(x) x + 1)
134+
///
135+
/// # Middle variant wins
136+
/// map(xs, function(x) x + a_really_long_thing_here + a_really_long_thing_there)
137+
/// # ->
138+
/// map(xs, function(x) {
139+
/// x + a_really_long_thing_here + a_really_long_thing_there
140+
/// })
141+
///
142+
/// # Most expanded wins (middle variant wouldn't fit within line length)
143+
/// map(xs_are_extremely_long_too, function(this_argument_is_really_long, option = "so is this") this_argument_is_really_long)
144+
/// # ->
145+
/// map(
146+
/// xs_are_extremely_long_too,
147+
/// function(this_argument_is_really_long, option = "so is this") {
148+
/// this_argument_is_really_long
149+
/// }
150+
/// )
130151
/// ```
131152
///
132153
/// This variant is removed from the set if we detect that the grouped argument
133154
/// contains a forced break in the body (if a forced break is found in the
134155
/// parameters, we bail entirely and use the most expanded form, as noted
135156
/// at the beginning of this documentation page).
136157
///
137-
/// Note that because `{}` currently unconditionally force a break, and because
138-
/// we only go down this path when we have a `{}` to begin with, that means that
139-
/// currently the most flat variant is always removed. There is an
140-
/// `unreachable!()` in the code to assert this. We can't simply remove the
141-
/// `most_flat` code path though, because it is also where we detect if a
142-
/// parameter forces a break, triggering one of our early exists. Additionally,
143-
/// in the future we may allow `{}` to not force a break, meaning this variant
144-
/// may come back into play.
158+
/// ```r
159+
/// # Forced break in the body
160+
/// map(xs, function(x) {
161+
/// x
162+
/// })
163+
///
164+
/// # This is a forced break in the body too, and triggers autobracing and
165+
/// # results in the middle variant winning
166+
/// map(xs, function(x)
167+
/// x)
168+
/// ```
169+
///
170+
/// Note that because `{}` currently unconditionally force a break, that means that
171+
/// currently the most flat variant is always removed when `{}` are present. In the
172+
/// future we will make empty `{}` never break and we will declare cases of empty
173+
/// `{}` as not groupable, so it won't go through this best fitting process at all.
174+
///
175+
/// ```r
176+
/// # NOTE: Currently considered groupable, but the `{}` currently unconditionally
177+
/// # force a break, so the `most_flat` variant is always removed
178+
/// map(xs, function(x) {})
179+
/// ```
145180
///
146181
/// ```r
147182
/// # NOTE: We explicitly disallow curly-curly as a groupable argument,
@@ -297,7 +332,7 @@ impl RCallLikeArguments {
297332

298333
// Write the most flat variant with the first or last argument grouped
299334
// (but not forcibly expanded)
300-
let _most_flat = {
335+
let most_flat = {
301336
let snapshot = f.state_snapshot();
302337
let mut buffer = VecBuffer::new(f.state_mut());
303338
buffer.write_element(FormatElement::Tag(Tag::StartEntry))?;
@@ -375,14 +410,15 @@ impl RCallLikeArguments {
375410
buffer.into_vec().into_boxed_slice()
376411
};
377412

378-
// If the grouped content breaks, then we can skip the most_flat variant,
379-
// since we already know that it won't be fitting on a single line.
380413
let variants = if grouped_breaks {
414+
// If the grouped content breaks, then we can skip the most_flat variant,
415+
// since we already know that it won't be fitting on a single line. We can
416+
// also go ahead and signal that we will be expanding.
381417
write!(f, [expand_parent()])?;
382418
vec![middle_variant, most_expanded.into_boxed_slice()]
383419
} else {
384-
unreachable!("`grouped_breaks` is currently always `true`.");
385-
// vec![most_flat, middle_variant, most_expanded.into_boxed_slice()]
420+
// Otherwise we best fit between all variants
421+
vec![most_flat, middle_variant, most_expanded.into_boxed_slice()]
386422
};
387423

388424
// SAFETY: Safe because variants is guaranteed to contain >=2 entries:
@@ -1087,7 +1123,7 @@ fn argument_is_groupable(argument: &AnyRExpression) -> SyntaxResult<bool> {
10871123
// })
10881124
// ```
10891125
//
1090-
// Empty braces always expand, so they benefit from grouping
1126+
// Empty braces currently always expand, so they benefit from grouping
10911127
//
10921128
// ```r
10931129
// with(data, {
@@ -1098,7 +1134,7 @@ fn argument_is_groupable(argument: &AnyRExpression) -> SyntaxResult<bool> {
10981134
//
10991135
// ```r
11001136
// with(data, {
1101-
// // comment
1137+
// # comment
11021138
// })
11031139
// ```
11041140
//
@@ -1119,15 +1155,91 @@ fn argument_is_groupable(argument: &AnyRExpression) -> SyntaxResult<bool> {
11191155
// ```
11201156
RBracedExpressions(node) => as_curly_curly(node).is_none(),
11211157

1158+
// Currently, every kind of function definition can benefit from grouping.
1159+
//
1160+
// - If the body has `{}` and is not empty, the braces will be expanded across
1161+
// multiple lines, so it can benefit from grouping.
1162+
//
1163+
// - If the body does not have `{}`, we may keep the function definition
1164+
// completely flat on one line, or we may expand it over multiple lines if
1165+
// it exceeds the line length or a persistent line break forces a break. If
1166+
// it were to break, it would benefit from grouping.
1167+
//
1168+
// - NOTE: If the body has `{}` and is empty (including no comments), then
1169+
// currently those are always expanded anyways, so it can benefit from grouping.
1170+
// In the future we will force empty `{}` to not expand, and when that happens
1171+
// we should return `false` from here to signal that it can't benefit from
1172+
// grouping (use the same rule as `RBracedExpressions` above, but applied to
1173+
// the `node.body()`).
1174+
//
1175+
// Here are some examples:
1176+
//
1177+
// Trailing function definition with braces is the classic example of something
1178+
// that benefits from grouping
1179+
//
11221180
// ```r
1123-
// map(a, function(x) {
1181+
// map(xs, function(x) {
11241182
// x
11251183
// })
11261184
// ```
1127-
RFunctionDefinition(node) => {
1128-
let body = node.body()?;
1129-
matches!(&body, AnyRExpression::RBracedExpressions(_))
1130-
}
1185+
//
1186+
// When the braces are empty, it still benefits from grouping right now because
1187+
// we currently always expand the braces.
1188+
//
1189+
// ```r
1190+
// map(xs, function(x) {
1191+
// })
1192+
// ```
1193+
//
1194+
// With a dangling comment in empty braces, we always benefit from grouping
1195+
//
1196+
// ```r
1197+
// map(xs, function(x) {
1198+
// # comment
1199+
// })
1200+
// ```
1201+
//
1202+
// Long function definition that would be split over multiple lines, triggering
1203+
// autobracing, and would benefit from grouping
1204+
//
1205+
// ```r
1206+
// map(xs, function(x) x + a_really_really_long_name + another_really_long_name)
1207+
//
1208+
// # Becomes:
1209+
// map(xs, function(x) {
1210+
// x + a_really_really_long_name + another_really_long_name
1211+
// })
1212+
//
1213+
// # Which is preferred over fully expanding:
1214+
// map(
1215+
// xs,
1216+
// function(x) {
1217+
// x + a_really_really_long_name + another_really_long_name
1218+
// }
1219+
// )
1220+
// ```
1221+
//
1222+
// Line break in the function definition body, triggering autobracing, and would
1223+
// benefit from grouping
1224+
//
1225+
// ```r
1226+
// map(xs, function(x)
1227+
// x + 1)
1228+
//
1229+
// # Becomes:
1230+
// map(xs, function(x) {
1231+
// x + 1
1232+
// })
1233+
//
1234+
// # Which is preferred over fully expanding:
1235+
// map(
1236+
// xs,
1237+
// function(x) {
1238+
// x + 1
1239+
// }
1240+
// )
1241+
// ```
1242+
RFunctionDefinition(_) => true,
11311243

11321244
// Nothing else benefits from grouping
11331245
_ => false,

crates/air_r_formatter/tests/specs/r/call.R

+63-8
Original file line numberDiff line numberDiff line change
@@ -340,23 +340,78 @@ map(xs, function(x) {
340340
# Braces expand over multiple lines
341341
map(xs, function(x) { })
342342

343-
# This should stay where it is
343+
# Best fitting is used to choose the most flat variant, and this stays
344+
# as is
344345
map(xs, function(x) x)
345346

346-
# This form is too wide, so it fully expands
347-
map(my_long_list_my_long_list_my_long_list_my_long_list, function(my_long_argument) {
347+
# Best fitting is used to choose the most expanded variant over
348+
# the current middle variant, because the middle variant exceeds
349+
# the line length
350+
map(my_long_list_my_long_list_my_long_list_my_long_list_my_long_list_my_long_list, function(my_long_argument) {
348351
my_long_body_my_long_body_my_long_body_my_long_body_my_long_body
349352
})
350353

351-
# Parameter names are very long, so it fully expands
352-
# (Note that this uses best-fitting. The `parameters` themselves don't force a
353-
# break, but when a best-fit choice is made between this form with no
354-
# soft-indents allowed in the `parameters` and the fully expanded form, the
355-
# fully expanded form wins)
354+
# Best fitting is used to choose the most expanded variant over
355+
# the current middle variant. Remember no soft-indents are allowed in the
356+
# `parameters` when looking at options for the middle variant, so the middle
357+
# variant can't choose to put each parameter on its own line in the current
358+
# form. Only the most expanded form can do that when fully breaking everything.
356359
map(x, function(a, a_really_really_long_parameter, and_another_one_here_too_wow_this_is_long) {
357360
1
358361
})
359362

363+
# Best fitting is used to choose most expanded here. Even with the middle
364+
# variant, the first map() argument and the function signature would exceed the
365+
# line length. Autobracing DOES NOT kick in as it expands in this case.
366+
map(my_long_list_my_long_list_my_long_list_my_long_list, function(my_long_argument) my_long_argument)
367+
368+
# Best fitting is used to choose most expanded here. Even with the middle
369+
# variant, the first map() argument and the function signature would exceed the
370+
# line length. Autobracing DOES kick in as it expands in this case as it fully
371+
# expands the function definition too to keep it within the line length.
372+
map(my_long_list_my_long_list_my_long_list_my_long_list, function(my_long_argument) my_long_list_my_long_list_my_long_list_my_long_list_my_long_list)
373+
374+
# Best fitting is used to choose the middle variant over the current most flat
375+
# form. The middle variant fits within the line length, so is preferred over
376+
# the most expanded form.
377+
map(xs, function(my_long_argument) my_long_argument + my_extra_long_extra_argument)
378+
379+
# Best fitting is used to choose the middle variant. The line break forces
380+
# autobracing, which means the function definition breaks. That rules out
381+
# the most flat variant, and then the middle variant is preferred over most
382+
# expanded because it fits in the line length.
383+
map(xs, function(x, option = "a")
384+
x
385+
)
386+
387+
# Best fitting is attempted, but we bail early because the persistent line
388+
# break causes the function definition `parameters` to expand, and if that
389+
# happens we fully expand
390+
map(xs, function(
391+
x, option = "a") {
392+
x
393+
})
394+
395+
# Best fitting is not used here. The `xs` already has a line break, so
396+
# we stay fully expanded. The function parameters are flattened though.
397+
map(
398+
xs,
399+
function(x,
400+
option = "a"
401+
) {
402+
x
403+
}
404+
)
405+
406+
# Best fitting is used to choose the middle variant. This is not a persistent
407+
# line break location, so the function `parameters` don't actually break here.
408+
# And then we choose middle over most expanded because it fits within the line
409+
# length.
410+
map(xs, function(x,
411+
option = "a") {
412+
x
413+
})
414+
360415
# The `{ 1 }` parameter would force a hard line break. We detect this and don't
361416
# use best-fitting. Instead we fall back to the most expanded form.
362417
map(x, function(a = { 1 }) {

0 commit comments

Comments
 (0)