]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
1 | use rustc_ast::{ast, ptr}; |
2 | use rustc_span::Span; | |
3 | ||
4 | use crate::attr::get_attrs_from_stmt; | |
5 | use crate::config::lists::*; | |
6 | use crate::config::Version; | |
7 | use crate::expr::{block_contains_comment, is_simple_block, is_unsafe_block, rewrite_cond}; | |
8 | use crate::items::{span_hi_for_param, span_lo_for_param}; | |
9 | use crate::lists::{definitive_tactic, itemize_list, write_list, ListFormatting, Separator}; | |
10 | use crate::overflow::OverflowableItem; | |
11 | use crate::rewrite::{Rewrite, RewriteContext}; | |
12 | use crate::shape::Shape; | |
13 | use crate::source_map::SpanUtils; | |
064997fb | 14 | use crate::types::rewrite_lifetime_param; |
f20569fa XL |
15 | use crate::utils::{last_line_width, left_most_sub_expr, stmt_expr, NodeIdExt}; |
16 | ||
17 | // This module is pretty messy because of the rules around closures and blocks: | |
18 | // FIXME - the below is probably no longer true in full. | |
19 | // * if there is a return type, then there must be braces, | |
20 | // * given a closure with braces, whether that is parsed to give an inner block | |
21 | // or not depends on if there is a return type and if there are statements | |
22 | // in that block, | |
23 | // * if the first expression in the body ends with a block (i.e., is a | |
24 | // statement without needing a semi-colon), then adding or removing braces | |
25 | // can change whether it is treated as an expression or statement. | |
26 | ||
27 | pub(crate) fn rewrite_closure( | |
064997fb | 28 | binder: &ast::ClosureBinder, |
6522a427 | 29 | constness: ast::Const, |
f20569fa XL |
30 | capture: ast::CaptureBy, |
31 | is_async: &ast::Async, | |
32 | movability: ast::Movability, | |
33 | fn_decl: &ast::FnDecl, | |
34 | body: &ast::Expr, | |
35 | span: Span, | |
36 | context: &RewriteContext<'_>, | |
37 | shape: Shape, | |
38 | ) -> Option<String> { | |
39 | debug!("rewrite_closure {:?}", body); | |
40 | ||
41 | let (prefix, extra_offset) = rewrite_closure_fn_decl( | |
6522a427 | 42 | binder, constness, capture, is_async, movability, fn_decl, body, span, context, shape, |
f20569fa XL |
43 | )?; |
44 | // 1 = space between `|...|` and body. | |
45 | let body_shape = shape.offset_left(extra_offset)?; | |
46 | ||
47 | if let ast::ExprKind::Block(ref block, _) = body.kind { | |
48 | // The body of the closure is an empty block. | |
49 | if block.stmts.is_empty() && !block_contains_comment(context, block) { | |
50 | return body | |
51 | .rewrite(context, shape) | |
52 | .map(|s| format!("{} {}", prefix, s)); | |
53 | } | |
54 | ||
55 | let result = match fn_decl.output { | |
56 | ast::FnRetTy::Default(_) if !context.inside_macro() => { | |
57 | try_rewrite_without_block(body, &prefix, context, shape, body_shape) | |
58 | } | |
59 | _ => None, | |
60 | }; | |
61 | ||
62 | result.or_else(|| { | |
63 | // Either we require a block, or tried without and failed. | |
64 | rewrite_closure_block(block, &prefix, context, body_shape) | |
65 | }) | |
66 | } else { | |
67 | rewrite_closure_expr(body, &prefix, context, body_shape).or_else(|| { | |
68 | // The closure originally had a non-block expression, but we can't fit on | |
69 | // one line, so we'll insert a block. | |
70 | rewrite_closure_with_block(body, &prefix, context, body_shape) | |
71 | }) | |
72 | } | |
73 | } | |
74 | ||
75 | fn try_rewrite_without_block( | |
76 | expr: &ast::Expr, | |
77 | prefix: &str, | |
78 | context: &RewriteContext<'_>, | |
79 | shape: Shape, | |
80 | body_shape: Shape, | |
81 | ) -> Option<String> { | |
82 | let expr = get_inner_expr(expr, prefix, context); | |
83 | ||
84 | if is_block_closure_forced(context, expr) { | |
85 | rewrite_closure_with_block(expr, prefix, context, shape) | |
86 | } else { | |
87 | rewrite_closure_expr(expr, prefix, context, body_shape) | |
88 | } | |
89 | } | |
90 | ||
91 | fn get_inner_expr<'a>( | |
92 | expr: &'a ast::Expr, | |
93 | prefix: &str, | |
94 | context: &RewriteContext<'_>, | |
95 | ) -> &'a ast::Expr { | |
96 | if let ast::ExprKind::Block(ref block, _) = expr.kind { | |
97 | if !needs_block(block, prefix, context) { | |
98 | // block.stmts.len() == 1 except with `|| {{}}`; | |
99 | // https://github.com/rust-lang/rustfmt/issues/3844 | |
100 | if let Some(expr) = block.stmts.first().and_then(stmt_expr) { | |
101 | return get_inner_expr(expr, prefix, context); | |
102 | } | |
103 | } | |
104 | } | |
105 | ||
106 | expr | |
107 | } | |
108 | ||
109 | // Figure out if a block is necessary. | |
110 | fn needs_block(block: &ast::Block, prefix: &str, context: &RewriteContext<'_>) -> bool { | |
111 | let has_attributes = block.stmts.first().map_or(false, |first_stmt| { | |
112 | !get_attrs_from_stmt(first_stmt).is_empty() | |
113 | }); | |
114 | ||
115 | is_unsafe_block(block) | |
116 | || block.stmts.len() > 1 | |
117 | || has_attributes | |
118 | || block_contains_comment(context, block) | |
119 | || prefix.contains('\n') | |
120 | } | |
121 | ||
122 | fn veto_block(e: &ast::Expr) -> bool { | |
123 | match e.kind { | |
124 | ast::ExprKind::Call(..) | |
125 | | ast::ExprKind::Binary(..) | |
126 | | ast::ExprKind::Cast(..) | |
127 | | ast::ExprKind::Type(..) | |
128 | | ast::ExprKind::Assign(..) | |
129 | | ast::ExprKind::AssignOp(..) | |
130 | | ast::ExprKind::Field(..) | |
131 | | ast::ExprKind::Index(..) | |
132 | | ast::ExprKind::Range(..) | |
133 | | ast::ExprKind::Try(..) => true, | |
134 | _ => false, | |
135 | } | |
136 | } | |
137 | ||
138 | // Rewrite closure with a single expression wrapping its body with block. | |
139 | // || { #[attr] foo() } -> Block { #[attr] foo() } | |
140 | fn rewrite_closure_with_block( | |
141 | body: &ast::Expr, | |
142 | prefix: &str, | |
143 | context: &RewriteContext<'_>, | |
144 | shape: Shape, | |
145 | ) -> Option<String> { | |
146 | let left_most = left_most_sub_expr(body); | |
147 | let veto_block = veto_block(body) && !expr_requires_semi_to_be_stmt(left_most); | |
148 | if veto_block { | |
149 | return None; | |
150 | } | |
151 | ||
152 | let block = ast::Block { | |
153 | stmts: vec![ast::Stmt { | |
154 | id: ast::NodeId::root(), | |
155 | kind: ast::StmtKind::Expr(ptr::P(body.clone())), | |
156 | span: body.span, | |
157 | }], | |
158 | id: ast::NodeId::root(), | |
159 | rules: ast::BlockCheckMode::Default, | |
160 | tokens: None, | |
161 | span: body | |
162 | .attrs | |
163 | .first() | |
164 | .map(|attr| attr.span.to(body.span)) | |
165 | .unwrap_or(body.span), | |
94222f64 | 166 | could_be_bare_literal: false, |
f20569fa XL |
167 | }; |
168 | let block = crate::expr::rewrite_block_with_visitor( | |
169 | context, | |
170 | "", | |
171 | &block, | |
172 | Some(&body.attrs), | |
173 | None, | |
174 | shape, | |
175 | false, | |
176 | )?; | |
177 | Some(format!("{} {}", prefix, block)) | |
178 | } | |
179 | ||
180 | // Rewrite closure with a single expression without wrapping its body with block. | |
181 | fn rewrite_closure_expr( | |
182 | expr: &ast::Expr, | |
183 | prefix: &str, | |
184 | context: &RewriteContext<'_>, | |
185 | shape: Shape, | |
186 | ) -> Option<String> { | |
187 | fn allow_multi_line(expr: &ast::Expr) -> bool { | |
188 | match expr.kind { | |
189 | ast::ExprKind::Match(..) | |
190 | | ast::ExprKind::Async(..) | |
191 | | ast::ExprKind::Block(..) | |
192 | | ast::ExprKind::TryBlock(..) | |
193 | | ast::ExprKind::Loop(..) | |
194 | | ast::ExprKind::Struct(..) => true, | |
195 | ||
196 | ast::ExprKind::AddrOf(_, _, ref expr) | |
197 | | ast::ExprKind::Box(ref expr) | |
198 | | ast::ExprKind::Try(ref expr) | |
199 | | ast::ExprKind::Unary(_, ref expr) | |
200 | | ast::ExprKind::Cast(ref expr, _) => allow_multi_line(expr), | |
201 | ||
202 | _ => false, | |
203 | } | |
204 | } | |
205 | ||
206 | // When rewriting closure's body without block, we require it to fit in a single line | |
207 | // unless it is a block-like expression or we are inside macro call. | |
208 | let veto_multiline = (!allow_multi_line(expr) && !context.inside_macro()) | |
209 | || context.config.force_multiline_blocks(); | |
210 | expr.rewrite(context, shape) | |
211 | .and_then(|rw| { | |
212 | if veto_multiline && rw.contains('\n') { | |
213 | None | |
214 | } else { | |
215 | Some(rw) | |
216 | } | |
217 | }) | |
218 | .map(|rw| format!("{} {}", prefix, rw)) | |
219 | } | |
220 | ||
221 | // Rewrite closure whose body is block. | |
222 | fn rewrite_closure_block( | |
223 | block: &ast::Block, | |
224 | prefix: &str, | |
225 | context: &RewriteContext<'_>, | |
226 | shape: Shape, | |
227 | ) -> Option<String> { | |
228 | Some(format!("{} {}", prefix, block.rewrite(context, shape)?)) | |
229 | } | |
230 | ||
231 | // Return type is (prefix, extra_offset) | |
232 | fn rewrite_closure_fn_decl( | |
064997fb | 233 | binder: &ast::ClosureBinder, |
6522a427 | 234 | constness: ast::Const, |
f20569fa XL |
235 | capture: ast::CaptureBy, |
236 | asyncness: &ast::Async, | |
237 | movability: ast::Movability, | |
238 | fn_decl: &ast::FnDecl, | |
239 | body: &ast::Expr, | |
240 | span: Span, | |
241 | context: &RewriteContext<'_>, | |
242 | shape: Shape, | |
243 | ) -> Option<(String, usize)> { | |
064997fb FG |
244 | let binder = match binder { |
245 | ast::ClosureBinder::For { generic_params, .. } if generic_params.is_empty() => { | |
246 | "for<> ".to_owned() | |
247 | } | |
248 | ast::ClosureBinder::For { generic_params, .. } => { | |
249 | let lifetime_str = rewrite_lifetime_param(context, shape, generic_params)?; | |
250 | format!("for<{lifetime_str}> ") | |
251 | } | |
252 | ast::ClosureBinder::NotPresent => "".to_owned(), | |
253 | }; | |
254 | ||
6522a427 EL |
255 | let const_ = if matches!(constness, ast::Const::Yes(_)) { |
256 | "const " | |
257 | } else { | |
258 | "" | |
259 | }; | |
260 | ||
a2a8927a XL |
261 | let immovable = if movability == ast::Movability::Static { |
262 | "static " | |
f20569fa XL |
263 | } else { |
264 | "" | |
265 | }; | |
a2a8927a XL |
266 | let is_async = if asyncness.is_async() { "async " } else { "" }; |
267 | let mover = if capture == ast::CaptureBy::Value { | |
268 | "move " | |
f20569fa XL |
269 | } else { |
270 | "" | |
271 | }; | |
272 | // 4 = "|| {".len(), which is overconservative when the closure consists of | |
273 | // a single expression. | |
274 | let nested_shape = shape | |
6522a427 | 275 | .shrink_left(binder.len() + const_.len() + immovable.len() + is_async.len() + mover.len())? |
f20569fa XL |
276 | .sub_width(4)?; |
277 | ||
278 | // 1 = | | |
279 | let param_offset = nested_shape.indent + 1; | |
280 | let param_shape = nested_shape.offset_left(1)?.visual_indent(0); | |
281 | let ret_str = fn_decl.output.rewrite(context, param_shape)?; | |
282 | ||
283 | let param_items = itemize_list( | |
284 | context.snippet_provider, | |
285 | fn_decl.inputs.iter(), | |
286 | "|", | |
287 | ",", | |
288 | |param| span_lo_for_param(param), | |
289 | |param| span_hi_for_param(context, param), | |
290 | |param| param.rewrite(context, param_shape), | |
291 | context.snippet_provider.span_after(span, "|"), | |
292 | body.span.lo(), | |
293 | false, | |
294 | ); | |
295 | let item_vec = param_items.collect::<Vec<_>>(); | |
296 | // 1 = space between parameters and return type. | |
297 | let horizontal_budget = nested_shape.width.saturating_sub(ret_str.len() + 1); | |
298 | let tactic = definitive_tactic( | |
299 | &item_vec, | |
300 | ListTactic::HorizontalVertical, | |
301 | Separator::Comma, | |
302 | horizontal_budget, | |
303 | ); | |
304 | let param_shape = match tactic { | |
305 | DefinitiveListTactic::Horizontal => param_shape.sub_width(ret_str.len() + 1)?, | |
306 | _ => param_shape, | |
307 | }; | |
308 | ||
309 | let fmt = ListFormatting::new(param_shape, context.config) | |
310 | .tactic(tactic) | |
311 | .preserve_newline(true); | |
312 | let list_str = write_list(&item_vec, &fmt)?; | |
6522a427 EL |
313 | let mut prefix = format!( |
314 | "{}{}{}{}{}|{}|", | |
315 | binder, const_, immovable, is_async, mover, list_str | |
316 | ); | |
f20569fa XL |
317 | |
318 | if !ret_str.is_empty() { | |
319 | if prefix.contains('\n') { | |
320 | prefix.push('\n'); | |
321 | prefix.push_str(¶m_offset.to_string(context.config)); | |
322 | } else { | |
323 | prefix.push(' '); | |
324 | } | |
325 | prefix.push_str(&ret_str); | |
326 | } | |
327 | // 1 = space between `|...|` and body. | |
328 | let extra_offset = last_line_width(&prefix) + 1; | |
329 | ||
330 | Some((prefix, extra_offset)) | |
331 | } | |
332 | ||
333 | // Rewriting closure which is placed at the end of the function call's arg. | |
334 | // Returns `None` if the reformatted closure 'looks bad'. | |
335 | pub(crate) fn rewrite_last_closure( | |
336 | context: &RewriteContext<'_>, | |
337 | expr: &ast::Expr, | |
338 | shape: Shape, | |
339 | ) -> Option<String> { | |
6522a427 EL |
340 | if let ast::ExprKind::Closure(ref closure) = expr.kind { |
341 | let ast::Closure { | |
342 | ref binder, | |
343 | constness, | |
344 | capture_clause, | |
345 | ref asyncness, | |
346 | movability, | |
347 | ref fn_decl, | |
348 | ref body, | |
349 | fn_decl_span: _, | |
350 | fn_arg_span: _, | |
351 | } = **closure; | |
f20569fa XL |
352 | let body = match body.kind { |
353 | ast::ExprKind::Block(ref block, _) | |
354 | if !is_unsafe_block(block) | |
355 | && !context.inside_macro() | |
356 | && is_simple_block(context, block, Some(&body.attrs)) => | |
357 | { | |
358 | stmt_expr(&block.stmts[0]).unwrap_or(body) | |
359 | } | |
360 | _ => body, | |
361 | }; | |
362 | let (prefix, extra_offset) = rewrite_closure_fn_decl( | |
6522a427 EL |
363 | binder, |
364 | constness, | |
365 | capture_clause, | |
366 | asyncness, | |
367 | movability, | |
368 | fn_decl, | |
369 | body, | |
370 | expr.span, | |
371 | context, | |
372 | shape, | |
f20569fa XL |
373 | )?; |
374 | // If the closure goes multi line before its body, do not overflow the closure. | |
375 | if prefix.contains('\n') { | |
376 | return None; | |
377 | } | |
378 | ||
379 | let body_shape = shape.offset_left(extra_offset)?; | |
380 | ||
381 | // We force to use block for the body of the closure for certain kinds of expressions. | |
382 | if is_block_closure_forced(context, body) { | |
94222f64 | 383 | return rewrite_closure_with_block(body, &prefix, context, body_shape).map( |
f20569fa XL |
384 | |body_str| { |
385 | match fn_decl.output { | |
386 | ast::FnRetTy::Default(..) if body_str.lines().count() <= 7 => { | |
387 | // If the expression can fit in a single line, we need not force block | |
388 | // closure. However, if the closure has a return type, then we must | |
389 | // keep the blocks. | |
390 | match rewrite_closure_expr(body, &prefix, context, shape) { | |
94222f64 | 391 | Some(single_line_body_str) |
f20569fa XL |
392 | if !single_line_body_str.contains('\n') => |
393 | { | |
94222f64 | 394 | single_line_body_str |
f20569fa | 395 | } |
94222f64 | 396 | _ => body_str, |
f20569fa XL |
397 | } |
398 | } | |
94222f64 | 399 | _ => body_str, |
f20569fa XL |
400 | } |
401 | }, | |
402 | ); | |
403 | } | |
404 | ||
405 | // When overflowing the closure which consists of a single control flow expression, | |
406 | // force to use block if its condition uses multi line. | |
407 | let is_multi_lined_cond = rewrite_cond(context, body, body_shape).map_or(false, |cond| { | |
408 | cond.contains('\n') || cond.len() > body_shape.width | |
409 | }); | |
410 | if is_multi_lined_cond { | |
411 | return rewrite_closure_with_block(body, &prefix, context, body_shape); | |
412 | } | |
413 | ||
414 | // Seems fine, just format the closure in usual manner. | |
415 | return expr.rewrite(context, shape); | |
416 | } | |
417 | None | |
418 | } | |
419 | ||
420 | /// Returns `true` if the given vector of arguments has more than one `ast::ExprKind::Closure`. | |
421 | pub(crate) fn args_have_many_closure(args: &[OverflowableItem<'_>]) -> bool { | |
422 | args.iter() | |
423 | .filter_map(OverflowableItem::to_expr) | |
94222f64 | 424 | .filter(|expr| matches!(expr.kind, ast::ExprKind::Closure(..))) |
f20569fa XL |
425 | .count() |
426 | > 1 | |
427 | } | |
428 | ||
429 | fn is_block_closure_forced(context: &RewriteContext<'_>, expr: &ast::Expr) -> bool { | |
430 | // If we are inside macro, we do not want to add or remove block from closure body. | |
431 | if context.inside_macro() { | |
432 | false | |
433 | } else { | |
434 | is_block_closure_forced_inner(expr, context.config.version()) | |
435 | } | |
436 | } | |
437 | ||
438 | fn is_block_closure_forced_inner(expr: &ast::Expr, version: Version) -> bool { | |
439 | match expr.kind { | |
440 | ast::ExprKind::If(..) | ast::ExprKind::While(..) | ast::ExprKind::ForLoop(..) => true, | |
441 | ast::ExprKind::Loop(..) if version == Version::Two => true, | |
442 | ast::ExprKind::AddrOf(_, _, ref expr) | |
443 | | ast::ExprKind::Box(ref expr) | |
444 | | ast::ExprKind::Try(ref expr) | |
445 | | ast::ExprKind::Unary(_, ref expr) | |
446 | | ast::ExprKind::Cast(ref expr, _) => is_block_closure_forced_inner(expr, version), | |
447 | _ => false, | |
448 | } | |
449 | } | |
450 | ||
451 | /// Does this expression require a semicolon to be treated | |
452 | /// as a statement? The negation of this: 'can this expression | |
453 | /// be used as a statement without a semicolon' -- is used | |
454 | /// as an early-bail-out in the parser so that, for instance, | |
455 | /// if true {...} else {...} | |
456 | /// |x| 5 | |
457 | /// isn't parsed as (if true {...} else {...} | x) | 5 | |
458 | // From https://github.com/rust-lang/rust/blob/master/src/libsyntax/parse/classify.rs. | |
459 | fn expr_requires_semi_to_be_stmt(e: &ast::Expr) -> bool { | |
460 | match e.kind { | |
461 | ast::ExprKind::If(..) | |
462 | | ast::ExprKind::Match(..) | |
463 | | ast::ExprKind::Block(..) | |
464 | | ast::ExprKind::While(..) | |
465 | | ast::ExprKind::Loop(..) | |
466 | | ast::ExprKind::ForLoop(..) | |
467 | | ast::ExprKind::TryBlock(..) => false, | |
468 | _ => true, | |
469 | } | |
470 | } |