From 91ac9cf5952ef00218c215c564ec7f56c479019d Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Thu, 24 Mar 2022 21:34:57 -0700 Subject: [PATCH] suggest wrapping single-expr blocks in square brackets --- compiler/rustc_parse/src/parser/expr.rs | 18 +-- compiler/rustc_typeck/src/check/demand.rs | 1 + .../rustc_typeck/src/check/fn_ctxt/checks.rs | 143 ++++++++++-------- .../src/check/fn_ctxt/suggestions.rs | 71 +++++++++ .../brackets-to-braces-single-element.rs | 10 ++ .../brackets-to-braces-single-element.stderr | 38 +++++ .../issue-87830-try-brackets-for-arrays.rs | 7 +- ...issue-87830-try-brackets-for-arrays.stderr | 23 ++- 8 files changed, 221 insertions(+), 90 deletions(-) create mode 100644 src/test/ui/did_you_mean/brackets-to-braces-single-element.rs create mode 100644 src/test/ui/did_you_mean/brackets-to-braces-single-element.stderr diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 148e0a24ec3..beffbe410ba 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -1919,17 +1919,13 @@ fn maybe_suggest_brackets_instead_of_braces( match snapshot.parse_array_or_repeat_expr(attrs, token::Brace) { Ok(arr) => { let hi = snapshot.prev_token.span; - self.struct_span_err( - arr.span, - "this code is interpreted as a block expression, not an array", - ) - .multipart_suggestion( - "try using [] instead of {}", - vec![(lo, "[".to_owned()), (hi, "]".to_owned())], - Applicability::MaybeIncorrect, - ) - .note("to define an array, one would use square brackets instead of curly braces") - .emit(); + self.struct_span_err(arr.span, "this is a block expression, not an array") + .multipart_suggestion( + "to make an array, use square brackets instead of curly braces", + vec![(lo, "[".to_owned()), (hi, "]".to_owned())], + Applicability::MaybeIncorrect, + ) + .emit(); self.restore_snapshot(snapshot); Some(self.mk_expr_err(arr.span)) diff --git a/compiler/rustc_typeck/src/check/demand.rs b/compiler/rustc_typeck/src/check/demand.rs index 58e5c9315c3..b9d7c1ea1c6 100644 --- a/compiler/rustc_typeck/src/check/demand.rs +++ b/compiler/rustc_typeck/src/check/demand.rs @@ -39,6 +39,7 @@ pub fn emit_coerce_suggestions( self.suggest_no_capture_closure(err, expected, expr_ty); self.suggest_boxing_when_appropriate(err, expr, expected, expr_ty); self.suggest_missing_parentheses(err, expr); + self.suggest_block_to_brackets_peeling_refs(err, expr, expr_ty, expected); self.note_need_for_fn_pointer(err, expected, expr_ty); self.note_internal_mutation_in_method(err, expr, expected, expr_ty); self.report_closure_inferred_return_type(err, expected); diff --git a/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs b/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs index b7b45929e14..29cefe63c0b 100644 --- a/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs +++ b/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs @@ -774,57 +774,68 @@ pub(in super::super) fn check_block_with_expected( let prev_diverges = self.diverges.get(); let ctxt = BreakableCtxt { coerce: Some(coerce), may_break: false }; - let (ctxt, ()) = - self.with_breakable_ctxt(blk.hir_id, ctxt, || { - for (pos, s) in blk.stmts.iter().enumerate() { - self.check_stmt(s, blk.stmts.len() - 1 == pos); - } + let (ctxt, ()) = self.with_breakable_ctxt(blk.hir_id, ctxt, || { + for (pos, s) in blk.stmts.iter().enumerate() { + self.check_stmt(s, blk.stmts.len() - 1 == pos); + } - // check the tail expression **without** holding the - // `enclosing_breakables` lock below. - let tail_expr_ty = tail_expr.map(|t| self.check_expr_with_expectation(t, expected)); + // check the tail expression **without** holding the + // `enclosing_breakables` lock below. + let tail_expr_ty = tail_expr.map(|t| self.check_expr_with_expectation(t, expected)); - let mut enclosing_breakables = self.enclosing_breakables.borrow_mut(); - let ctxt = enclosing_breakables.find_breakable(blk.hir_id); - let coerce = ctxt.coerce.as_mut().unwrap(); - if let Some(tail_expr_ty) = tail_expr_ty { - let tail_expr = tail_expr.unwrap(); - let span = self.get_expr_coercion_span(tail_expr); - let cause = - self.cause(span, ObligationCauseCode::BlockTailExpression(blk.hir_id)); - coerce.coerce(self, &cause, tail_expr, tail_expr_ty); - } else { - // Subtle: if there is no explicit tail expression, - // that is typically equivalent to a tail expression - // of `()` -- except if the block diverges. In that - // case, there is no value supplied from the tail - // expression (assuming there are no other breaks, - // this implies that the type of the block will be - // `!`). - // - // #41425 -- label the implicit `()` as being the - // "found type" here, rather than the "expected type". - if !self.diverges.get().is_always() { - // #50009 -- Do not point at the entire fn block span, point at the return type - // span, as it is the cause of the requirement, and - // `consider_hint_about_removing_semicolon` will point at the last expression - // if it were a relevant part of the error. This improves usability in editors - // that highlight errors inline. - let mut sp = blk.span; - let mut fn_span = None; - if let Some((decl, ident)) = self.get_parent_fn_decl(blk.hir_id) { - let ret_sp = decl.output.span(); - if let Some(block_sp) = self.parent_item_span(blk.hir_id) { - // HACK: on some cases (`ui/liveness/liveness-issue-2163.rs`) the - // output would otherwise be incorrect and even misleading. Make sure - // the span we're aiming at correspond to a `fn` body. - if block_sp == blk.span { - sp = ret_sp; - fn_span = Some(ident.span); - } + let mut enclosing_breakables = self.enclosing_breakables.borrow_mut(); + let ctxt = enclosing_breakables.find_breakable(blk.hir_id); + let coerce = ctxt.coerce.as_mut().unwrap(); + if let Some(tail_expr_ty) = tail_expr_ty { + let tail_expr = tail_expr.unwrap(); + let span = self.get_expr_coercion_span(tail_expr); + let cause = self.cause(span, ObligationCauseCode::BlockTailExpression(blk.hir_id)); + let ty_for_diagnostic = coerce.merged_ty(); + // We use coerce_inner here because we want to augment the error + // suggesting to wrap the block in square brackets if it might've + // been mistaken array syntax + coerce.coerce_inner( + self, + &cause, + Some(tail_expr), + tail_expr_ty, + Some(&mut |diag: &mut Diagnostic| { + self.suggest_block_to_brackets(diag, blk, tail_expr_ty, ty_for_diagnostic); + }), + false, + ); + } else { + // Subtle: if there is no explicit tail expression, + // that is typically equivalent to a tail expression + // of `()` -- except if the block diverges. In that + // case, there is no value supplied from the tail + // expression (assuming there are no other breaks, + // this implies that the type of the block will be + // `!`). + // + // #41425 -- label the implicit `()` as being the + // "found type" here, rather than the "expected type". + if !self.diverges.get().is_always() { + // #50009 -- Do not point at the entire fn block span, point at the return type + // span, as it is the cause of the requirement, and + // `consider_hint_about_removing_semicolon` will point at the last expression + // if it were a relevant part of the error. This improves usability in editors + // that highlight errors inline. + let mut sp = blk.span; + let mut fn_span = None; + if let Some((decl, ident)) = self.get_parent_fn_decl(blk.hir_id) { + let ret_sp = decl.output.span(); + if let Some(block_sp) = self.parent_item_span(blk.hir_id) { + // HACK: on some cases (`ui/liveness/liveness-issue-2163.rs`) the + // output would otherwise be incorrect and even misleading. Make sure + // the span we're aiming at correspond to a `fn` body. + if block_sp == blk.span { + sp = ret_sp; + fn_span = Some(ident.span); } } - coerce.coerce_forced_unit( + } + coerce.coerce_forced_unit( self, &self.misc(sp), &mut |err| { @@ -837,21 +848,25 @@ pub(in super::super) fn check_block_with_expected( // Our block must be a `assign desugar local; assignment` if let Some(hir::Node::Block(hir::Block { stmts: - [hir::Stmt { - kind: - hir::StmtKind::Local(hir::Local { - source: hir::LocalSource::AssignDesugar(_), - .. - }), - .. - }, hir::Stmt { - kind: - hir::StmtKind::Expr(hir::Expr { - kind: hir::ExprKind::Assign(..), - .. - }), - .. - }], + [ + hir::Stmt { + kind: + hir::StmtKind::Local(hir::Local { + source: + hir::LocalSource::AssignDesugar(_), + .. + }), + .. + }, + hir::Stmt { + kind: + hir::StmtKind::Expr(hir::Expr { + kind: hir::ExprKind::Assign(..), + .. + }), + .. + }, + ], .. })) = self.tcx.hir().find(blk.hir_id) { @@ -871,9 +886,9 @@ pub(in super::super) fn check_block_with_expected( }, false, ); - } } - }); + } + }); if ctxt.may_break { // If we can break from the block, then the block's exit is always reachable diff --git a/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs b/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs index 67d61668b6d..1ff4008ccdf 100644 --- a/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs +++ b/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs @@ -757,6 +757,77 @@ pub(in super::super) fn suggest_missing_parentheses( } } + /// Given an expression type mismatch, peel any `&` expressions until we get to + /// a block expression, and then suggest replacing the braces with square braces + /// if it was possibly mistaken array syntax. + pub(crate) fn suggest_block_to_brackets_peeling_refs( + &self, + diag: &mut Diagnostic, + mut expr: &hir::Expr<'_>, + mut expr_ty: Ty<'tcx>, + mut expected_ty: Ty<'tcx>, + ) { + loop { + match (&expr.kind, expr_ty.kind(), expected_ty.kind()) { + ( + hir::ExprKind::AddrOf(_, _, inner_expr), + ty::Ref(_, inner_expr_ty, _), + ty::Ref(_, inner_expected_ty, _), + ) => { + expr = *inner_expr; + expr_ty = *inner_expr_ty; + expected_ty = *inner_expected_ty; + } + (hir::ExprKind::Block(blk, _), _, _) => { + self.suggest_block_to_brackets(diag, *blk, expr_ty, expected_ty); + break; + } + _ => break, + } + } + } + + /// Suggest wrapping the block in square brackets instead of curly braces + /// in case the block was mistaken array syntax, e.g. `{ 1 }` -> `[ 1 ]`. + pub(crate) fn suggest_block_to_brackets( + &self, + diag: &mut Diagnostic, + blk: &hir::Block<'_>, + blk_ty: Ty<'tcx>, + expected_ty: Ty<'tcx>, + ) { + if let ty::Slice(elem_ty) | ty::Array(elem_ty, _) = expected_ty.kind() { + if self.can_coerce(blk_ty, *elem_ty) + && blk.stmts.is_empty() + && blk.rules == hir::BlockCheckMode::DefaultBlock + { + let source_map = self.tcx.sess.source_map(); + if let Ok(snippet) = source_map.span_to_snippet(blk.span) { + if snippet.starts_with('{') && snippet.ends_with('}') { + diag.multipart_suggestion_verbose( + "to create an array, use square brackets instead of curly braces", + vec![ + ( + blk.span + .shrink_to_lo() + .with_hi(rustc_span::BytePos(blk.span.lo().0 + 1)), + "[".to_string(), + ), + ( + blk.span + .shrink_to_hi() + .with_lo(rustc_span::BytePos(blk.span.hi().0 - 1)), + "]".to_string(), + ), + ], + Applicability::MachineApplicable, + ); + } + } + } + } + } + fn is_loop(&self, id: hir::HirId) -> bool { let node = self.tcx.hir().get(id); matches!(node, Node::Expr(Expr { kind: ExprKind::Loop(..), .. })) diff --git a/src/test/ui/did_you_mean/brackets-to-braces-single-element.rs b/src/test/ui/did_you_mean/brackets-to-braces-single-element.rs new file mode 100644 index 00000000000..4d0109767fc --- /dev/null +++ b/src/test/ui/did_you_mean/brackets-to-braces-single-element.rs @@ -0,0 +1,10 @@ +const A: [&str; 1] = { "hello" }; +//~^ ERROR mismatched types + +const B: &[u32] = &{ 1 }; +//~^ ERROR mismatched types + +const C: &&[u32; 1] = &&{ 1 }; +//~^ ERROR mismatched types + +fn main() {} diff --git a/src/test/ui/did_you_mean/brackets-to-braces-single-element.stderr b/src/test/ui/did_you_mean/brackets-to-braces-single-element.stderr new file mode 100644 index 00000000000..6ded03e45b5 --- /dev/null +++ b/src/test/ui/did_you_mean/brackets-to-braces-single-element.stderr @@ -0,0 +1,38 @@ +error[E0308]: mismatched types + --> $DIR/brackets-to-braces-single-element.rs:1:24 + | +LL | const A: [&str; 1] = { "hello" }; + | ^^^^^^^ expected array `[&'static str; 1]`, found `&str` + | +help: to create an array, use square brackets instead of curly braces + | +LL | const A: [&str; 1] = [ "hello" ]; + | ~ ~ + +error[E0308]: mismatched types + --> $DIR/brackets-to-braces-single-element.rs:4:19 + | +LL | const B: &[u32] = &{ 1 }; + | ^^^^^^ expected slice `[u32]`, found integer + | + = note: expected reference `&'static [u32]` + found reference `&{integer}` +help: to create an array, use square brackets instead of curly braces + | +LL | const B: &[u32] = &[ 1 ]; + | ~ ~ + +error[E0308]: mismatched types + --> $DIR/brackets-to-braces-single-element.rs:7:27 + | +LL | const C: &&[u32; 1] = &&{ 1 }; + | ^ expected array `[u32; 1]`, found integer + | +help: to create an array, use square brackets instead of curly braces + | +LL | const C: &&[u32; 1] = &&[ 1 ]; + | ~ ~ + +error: aborting due to 3 previous errors + +For more information about this error, try `rustc --explain E0308`. diff --git a/src/test/ui/did_you_mean/issue-87830-try-brackets-for-arrays.rs b/src/test/ui/did_you_mean/issue-87830-try-brackets-for-arrays.rs index 53dad85900a..070ffaa1eff 100644 --- a/src/test/ui/did_you_mean/issue-87830-try-brackets-for-arrays.rs +++ b/src/test/ui/did_you_mean/issue-87830-try-brackets-for-arrays.rs @@ -1,15 +1,16 @@ fn main() {} -const FOO: [u8; 3] = { //~ ERROR this code is interpreted as a block expression +const FOO: [u8; 3] = { + //~^ ERROR this is a block expression, not an array 1, 2, 3 }; const BAR: [&str; 3] = {"one", "two", "three"}; -//~^ ERROR this code is interpreted as a block expression +//~^ ERROR this is a block expression, not an array fn foo() { {1, 2, 3}; - //~^ ERROR this code is interpreted as a block expression + //~^ ERROR this is a block expression, not an array } fn bar() { diff --git a/src/test/ui/did_you_mean/issue-87830-try-brackets-for-arrays.stderr b/src/test/ui/did_you_mean/issue-87830-try-brackets-for-arrays.stderr index 9ab491f5c23..d5ad1a72b82 100644 --- a/src/test/ui/did_you_mean/issue-87830-try-brackets-for-arrays.stderr +++ b/src/test/ui/did_you_mean/issue-87830-try-brackets-for-arrays.stderr @@ -1,46 +1,45 @@ -error: this code is interpreted as a block expression, not an array +error: this is a block expression, not an array --> $DIR/issue-87830-try-brackets-for-arrays.rs:3:22 | LL | const FOO: [u8; 3] = { | ______________________^ +LL | | LL | | 1, 2, 3 LL | | }; | |_^ | - = note: to define an array, one would use square brackets instead of curly braces -help: try using [] instead of {} +help: to make an array, use square brackets instead of curly braces | LL ~ const FOO: [u8; 3] = [ +LL | LL | 1, 2, 3 LL ~ ]; | -error: this code is interpreted as a block expression, not an array - --> $DIR/issue-87830-try-brackets-for-arrays.rs:7:24 +error: this is a block expression, not an array + --> $DIR/issue-87830-try-brackets-for-arrays.rs:8:24 | LL | const BAR: [&str; 3] = {"one", "two", "three"}; | ^^^^^^^^^^^^^^^^^^^^^^^ | - = note: to define an array, one would use square brackets instead of curly braces -help: try using [] instead of {} +help: to make an array, use square brackets instead of curly braces | LL | const BAR: [&str; 3] = ["one", "two", "three"]; | ~ ~ -error: this code is interpreted as a block expression, not an array - --> $DIR/issue-87830-try-brackets-for-arrays.rs:11:5 +error: this is a block expression, not an array + --> $DIR/issue-87830-try-brackets-for-arrays.rs:12:5 | LL | {1, 2, 3}; | ^^^^^^^^^ | - = note: to define an array, one would use square brackets instead of curly braces -help: try using [] instead of {} +help: to make an array, use square brackets instead of curly braces | LL | [1, 2, 3]; | ~ ~ error: expected one of `.`, `;`, `?`, `}`, or an operator, found `,` - --> $DIR/issue-87830-try-brackets-for-arrays.rs:16:6 + --> $DIR/issue-87830-try-brackets-for-arrays.rs:17:6 | LL | 1, 2, 3 | ^ expected one of `.`, `;`, `?`, `}`, or an operator