Skip to content

Commit 51f8144

Browse files
committed
Change the desugaring of assert! for better error output
In the desugaring of `assert!`, we now expand to a `match` expression instead of `if !cond {..}`. The span of incorrect conditions will point only at the expression, and not the whole `assert!` invocation. ``` error[E0308]: mismatched types --> $DIR/issue-14091.rs:2:13 | LL | assert!(1,1); | ^ expected `bool`, found integer ``` We no longer mention the expression needing to implement the `Not` trait. ``` error[E0308]: mismatched types --> $DIR/issue-14091-2.rs:15:13 | LL | assert!(x, x); | ^ expected `bool`, found `BytePos` ``` `assert!(val)` now desugars to: ```rust match val { true => {}, _ => $crate::panic::panic_2021!(), } ``` Fix #122159. We make some minor changes to some diagnostics to avoid span overlap on type mismatch or inverted "expected"/"found" on type errors. We remove some unnecessary parens from core, alloc and miri.
1 parent 6cab15c commit 51f8144

File tree

27 files changed

+163
-101
lines changed

27 files changed

+163
-101
lines changed

compiler/rustc_builtin_macros/src/assert.rs

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
mod context;
22

33
use rustc_ast::ptr::P;
4-
use rustc_ast::token::Delimiter;
4+
use rustc_ast::token::{self, Delimiter};
55
use rustc_ast::tokenstream::{DelimSpan, TokenStream};
6-
use rustc_ast::{DelimArgs, Expr, ExprKind, MacCall, Path, PathSegment, UnOp, token};
6+
use rustc_ast::{DelimArgs, Expr, ExprKind, MacCall, Path, PathSegment};
77
use rustc_ast_pretty::pprust;
88
use rustc_errors::PResult;
99
use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacEager, MacroExpanderResult};
@@ -30,7 +30,7 @@ pub(crate) fn expand_assert<'cx>(
3030

3131
// `core::panic` and `std::panic` are different macros, so we use call-site
3232
// context to pick up whichever is currently in scope.
33-
let call_site_span = cx.with_call_site_ctxt(span);
33+
let call_site_span = cx.with_call_site_ctxt(cond_expr.span);
3434

3535
let panic_path = || {
3636
if use_panic_2021(span) {
@@ -64,7 +64,7 @@ pub(crate) fn expand_assert<'cx>(
6464
}),
6565
})),
6666
);
67-
expr_if_not(cx, call_site_span, cond_expr, then, None)
67+
assert_cond_check(cx, call_site_span, cond_expr, then)
6868
}
6969
// If `generic_assert` is enabled, generates rich captured outputs
7070
//
@@ -89,26 +89,30 @@ pub(crate) fn expand_assert<'cx>(
8989
)),
9090
)],
9191
);
92-
expr_if_not(cx, call_site_span, cond_expr, then, None)
92+
assert_cond_check(cx, call_site_span, cond_expr, then)
9393
};
9494

9595
ExpandResult::Ready(MacEager::expr(expr))
9696
}
9797

98+
/// `assert!($cond_expr, $custom_message)`
9899
struct Assert {
99100
cond_expr: P<Expr>,
100101
custom_message: Option<TokenStream>,
101102
}
102103

103-
// if !{ ... } { ... } else { ... }
104-
fn expr_if_not(
105-
cx: &ExtCtxt<'_>,
106-
span: Span,
107-
cond: P<Expr>,
108-
then: P<Expr>,
109-
els: Option<P<Expr>>,
110-
) -> P<Expr> {
111-
cx.expr_if(span, cx.expr(span, ExprKind::Unary(UnOp::Not, cond)), then, els)
104+
/// `match <cond> { true => {} _ => <then> }`
105+
fn assert_cond_check(cx: &ExtCtxt<'_>, span: Span, cond: P<Expr>, then: P<Expr>) -> P<Expr> {
106+
// Instead of expanding to `if !<cond> { <then> }`, we expand to
107+
// `match <cond> { true => {} _ => <then> }`.
108+
// This allows us to always complain about mismatched types instead of "cannot apply unary
109+
// operator `!` to type `X`" when passing an invalid `<cond>`, while also allowing `<cond>` to
110+
// be `&true`.
111+
let els = cx.expr_block(cx.block(span, thin_vec![]));
112+
let mut arms = thin_vec![];
113+
arms.push(cx.arm(span, cx.pat_lit(span, cx.expr_bool(span, true)), els));
114+
arms.push(cx.arm(span, cx.pat_wild(span), then));
115+
cx.expr_match(span, cond, arms)
112116
}
113117

114118
fn parse_assert<'a>(cx: &ExtCtxt<'a>, sp: Span, stream: TokenStream) -> PResult<'a, Assert> {

compiler/rustc_hir_typeck/src/demand.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -694,7 +694,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
694694
) {
695695
match (self.tcx.parent_hir_node(expr.hir_id), error) {
696696
(hir::Node::LetStmt(hir::LetStmt { ty: Some(ty), init: Some(init), .. }), _)
697-
if init.hir_id == expr.hir_id =>
697+
if init.hir_id == expr.hir_id && !ty.span.source_equal(init.span) =>
698698
{
699699
// Point at `let` assignment type.
700700
err.span_label(ty.span, "expected due to this");

compiler/rustc_parse/src/parser/expr.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3965,7 +3965,7 @@ impl<'a> Parser<'a> {
39653965
P(Expr { kind, span, attrs, id: DUMMY_NODE_ID, tokens: None })
39663966
}
39673967

3968-
pub(crate) fn mk_expr(&self, span: Span, kind: ExprKind) -> P<Expr> {
3968+
pub fn mk_expr(&self, span: Span, kind: ExprKind) -> P<Expr> {
39693969
self.mk_expr_with_attrs(span, kind, AttrVec::new())
39703970
}
39713971

compiler/rustc_parse/src/parser/stmt.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,16 +1046,11 @@ impl<'a> Parser<'a> {
10461046
Ok(Some(stmt))
10471047
}
10481048

1049-
pub(super) fn mk_block(
1050-
&self,
1051-
stmts: ThinVec<Stmt>,
1052-
rules: BlockCheckMode,
1053-
span: Span,
1054-
) -> P<Block> {
1049+
pub fn mk_block(&self, stmts: ThinVec<Stmt>, rules: BlockCheckMode, span: Span) -> P<Block> {
10551050
P(Block { stmts, id: DUMMY_NODE_ID, rules, span, tokens: None })
10561051
}
10571052

1058-
pub(super) fn mk_stmt(&self, span: Span, kind: StmtKind) -> Stmt {
1053+
pub fn mk_stmt(&self, span: Span, kind: StmtKind) -> Stmt {
10591054
Stmt { id: DUMMY_NODE_ID, kind, span }
10601055
}
10611056

compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
458458
span,
459459
format!("this is an iterator with items of type `{}`", args.type_at(0)),
460460
);
461-
} else {
461+
} else if !span.overlaps(cause.span) {
462462
let expected_ty = self.tcx.short_string(expected_ty, err.long_ty_path());
463463
err.span_label(span, format!("this expression has type `{expected_ty}`"));
464464
}
@@ -1586,8 +1586,16 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
15861586
{
15871587
let e = self.tcx.erase_regions(e);
15881588
let f = self.tcx.erase_regions(f);
1589-
let expected = with_forced_trimmed_paths!(e.sort_string(self.tcx));
1590-
let found = with_forced_trimmed_paths!(f.sort_string(self.tcx));
1589+
let mut expected = with_forced_trimmed_paths!(e.sort_string(self.tcx));
1590+
let mut found = with_forced_trimmed_paths!(f.sort_string(self.tcx));
1591+
if let Some(def_id) = cause.span.ctxt().outer_expn_data().macro_def_id
1592+
&& self.tcx.is_diagnostic_item(sym::assert_macro, def_id)
1593+
{
1594+
// When the type error comes from `assert!()`, the cause and effect are reversed
1595+
// because that macro expands to `match val { false => {panic!()}, _ => {} }`, which
1596+
// would say something like "expected `Type`, found `bool`", confusing the user.
1597+
(found, expected) = (expected, found);
1598+
}
15911599
if expected == found {
15921600
label_or_note(span, terr.to_string(self.tcx));
15931601
} else {
@@ -2109,7 +2117,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
21092117
) -> Option<(DiagStyledString, DiagStyledString)> {
21102118
match values {
21112119
ValuePairs::Regions(exp_found) => self.expected_found_str(exp_found),
2112-
ValuePairs::Terms(exp_found) => self.expected_found_str_term(exp_found, file),
2120+
ValuePairs::Terms(exp_found) => self.expected_found_str_term(cause, exp_found, file),
21132121
ValuePairs::Aliases(exp_found) => self.expected_found_str(exp_found),
21142122
ValuePairs::ExistentialTraitRef(exp_found) => self.expected_found_str(exp_found),
21152123
ValuePairs::ExistentialProjection(exp_found) => self.expected_found_str(exp_found),
@@ -2148,15 +2156,27 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
21482156

21492157
fn expected_found_str_term(
21502158
&self,
2159+
cause: &ObligationCause<'tcx>,
21512160
exp_found: ty::error::ExpectedFound<ty::Term<'tcx>>,
21522161
path: &mut Option<PathBuf>,
21532162
) -> Option<(DiagStyledString, DiagStyledString)> {
21542163
let exp_found = self.resolve_vars_if_possible(exp_found);
21552164
if exp_found.references_error() {
21562165
return None;
21572166
}
2167+
let (mut expected, mut found) = (exp_found.expected, exp_found.found);
2168+
if let Some(def_id) = cause.span.ctxt().outer_expn_data().macro_def_id
2169+
&& self.tcx.is_diagnostic_item(sym::assert_macro, def_id)
2170+
{
2171+
// When the type error comes from `assert!()`, the cause and effect are reversed
2172+
// because that macro expands to `match val { false => {panic!()}, _ => {} }`, which
2173+
// would say something like
2174+
// = note: expected `Type`
2175+
// found `bool`"
2176+
(expected, found) = (found, expected);
2177+
}
21582178

2159-
Some(match (exp_found.expected.unpack(), exp_found.found.unpack()) {
2179+
Some(match (expected.unpack(), found.unpack()) {
21602180
(ty::TermKind::Ty(expected), ty::TermKind::Ty(found)) => {
21612181
let (mut exp, mut fnd) = self.cmp(expected, found);
21622182
// Use the terminal width as the basis to determine when to compress the printed

library/coretests/tests/tuple.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ fn test_partial_ord() {
3737
assert!(!((1.0f64, 2.0f64) <= (f64::NAN, 3.0)));
3838
assert!(!((1.0f64, 2.0f64) > (f64::NAN, 3.0)));
3939
assert!(!((1.0f64, 2.0f64) >= (f64::NAN, 3.0)));
40-
assert!(((1.0f64, 2.0f64) < (2.0, f64::NAN)));
40+
assert!((1.0f64, 2.0f64) < (2.0, f64::NAN));
4141
assert!(!((2.0f64, 2.0f64) < (2.0, f64::NAN)));
4242
}
4343

library/std/tests/env.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ fn test_self_exe_path() {
1616

1717
#[test]
1818
fn test() {
19-
assert!((!Path::new("test-path").is_absolute()));
19+
assert!(!Path::new("test-path").is_absolute());
2020

2121
#[cfg(not(target_env = "sgx"))]
2222
current_dir().unwrap();

src/tools/clippy/clippy_lints/src/bool_assert_comparison.rs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -130,13 +130,27 @@ impl<'tcx> LateLintPass<'tcx> for BoolAssertComparison {
130130

131131
let mut suggestions = vec![(name_span, non_eq_mac.to_string()), (lit_span, String::new())];
132132

133-
if bool_value ^ eq_macro {
134-
let Some(sugg) = Sugg::hir_opt(cx, non_lit_expr) else {
135-
return;
136-
};
137-
suggestions.push((non_lit_expr.span, (!sugg).to_string()));
133+
if let ty::Bool = non_lit_ty.kind() {
134+
if bool_value ^ eq_macro {
135+
let Some(sugg) = Sugg::hir_opt(cx, non_lit_expr) else {
136+
return;
137+
};
138+
suggestions.push((non_lit_expr.span, (!sugg).to_string()));
139+
}
140+
} else {
141+
// If we have a `value` that is *not* `bool` but that `!value` *is*, we suggest
142+
// `!!value`.
143+
suggestions.push((
144+
non_lit_expr.span.shrink_to_lo(),
145+
if bool_value ^ eq_macro {
146+
"!".to_string()
147+
} else {
148+
"!!".to_string()
149+
},
150+
));
138151
}
139152

153+
140154
diag.multipart_suggestion(
141155
format!("replace it with `{non_eq_mac}!(..)`"),
142156
suggestions,

src/tools/clippy/clippy_lints/src/missing_asserts_for_indexing.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use rustc_ast::{BinOpKind, LitKind, RangeLimits};
1111
use rustc_data_structures::packed::Pu128;
1212
use rustc_data_structures::unhash::UnindexMap;
1313
use rustc_errors::{Applicability, Diag};
14-
use rustc_hir::{Block, Body, Expr, ExprKind, UnOp};
14+
use rustc_hir::{Body, Expr, ExprKind};
1515
use rustc_lint::{LateContext, LateLintPass};
1616
use rustc_session::declare_lint_pass;
1717
use rustc_span::source_map::Spanned;
@@ -135,12 +135,12 @@ fn assert_len_expr<'hir>(
135135
cx: &LateContext<'_>,
136136
expr: &'hir Expr<'hir>,
137137
) -> Option<(LengthComparison, usize, &'hir Expr<'hir>)> {
138-
let (cmp, asserted_len, slice_len) = if let Some(higher::If { cond, then, .. }) = higher::If::hir(expr)
139-
&& let ExprKind::Unary(UnOp::Not, condition) = &cond.kind
140-
&& let ExprKind::Binary(bin_op, left, right) = &condition.kind
138+
let (cmp, asserted_len, slice_len) = if let Some(
139+
higher::IfLetOrMatch::Match(cond, [_, then], _)
140+
) = higher::IfLetOrMatch::parse(cx, expr)
141+
&& let ExprKind::Binary(bin_op, left, right) = &cond.kind
141142
// check if `then` block has a never type expression
142-
&& let ExprKind::Block(Block { expr: Some(then_expr), .. }, _) = then.kind
143-
&& cx.typeck_results().expr_ty(then_expr).is_never()
143+
&& cx.typeck_results().expr_ty(then.body).is_never()
144144
{
145145
len_comparison(bin_op.node, left, right)?
146146
} else if let Some((macro_call, bin_op)) = first_node_macro_backtrace(cx, expr).find_map(|macro_call| {

src/tools/clippy/tests/ui/bool_assert_comparison.fixed

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ fn main() {
9494
assert_eq!(a!(), "".is_empty());
9595
assert_eq!("".is_empty(), b!());
9696
assert_eq!(a, true);
97-
assert!(b);
97+
assert!(!!b);
9898
//~^ bool_assert_comparison
9999

100100
assert_ne!("a".len(), 1);
@@ -122,7 +122,7 @@ fn main() {
122122
debug_assert_eq!(a!(), "".is_empty());
123123
debug_assert_eq!("".is_empty(), b!());
124124
debug_assert_eq!(a, true);
125-
debug_assert!(b);
125+
debug_assert!(!!b);
126126
//~^ bool_assert_comparison
127127

128128
debug_assert_ne!("a".len(), 1);
@@ -167,7 +167,7 @@ fn main() {
167167

168168
use debug_assert_eq as renamed;
169169
renamed!(a, true);
170-
debug_assert!(b);
170+
debug_assert!(!!b);
171171
//~^ bool_assert_comparison
172172

173173
let non_copy = NonCopy;

src/tools/clippy/tests/ui/bool_assert_comparison.stderr

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ LL | assert_eq!(b, true);
4545
help: replace it with `assert!(..)`
4646
|
4747
LL - assert_eq!(b, true);
48-
LL + assert!(b);
48+
LL + assert!(!!b);
4949
|
5050

5151
error: used `assert_ne!` with a literal bool
@@ -141,7 +141,7 @@ LL | debug_assert_eq!(b, true);
141141
help: replace it with `debug_assert!(..)`
142142
|
143143
LL - debug_assert_eq!(b, true);
144-
LL + debug_assert!(b);
144+
LL + debug_assert!(!!b);
145145
|
146146

147147
error: used `debug_assert_ne!` with a literal bool
@@ -297,7 +297,7 @@ LL | renamed!(b, true);
297297
help: replace it with `debug_assert!(..)`
298298
|
299299
LL - renamed!(b, true);
300-
LL + debug_assert!(b);
300+
LL + debug_assert!(!!b);
301301
|
302302

303303
error: used `assert_eq!` with a literal bool

src/tools/clippy/tests/ui/const_is_empty.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ fn issue_13106() {
196196

197197
const {
198198
assert!(EMPTY_STR.is_empty());
199+
//~^ const_is_empty
199200
}
200201

201202
const {

src/tools/clippy/tests/ui/const_is_empty.stderr

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,10 +158,16 @@ LL | let _ = val.is_empty();
158158
| ^^^^^^^^^^^^^^
159159

160160
error: this expression always evaluates to true
161-
--> tests/ui/const_is_empty.rs:202:9
161+
--> tests/ui/const_is_empty.rs:198:17
162+
|
163+
LL | assert!(EMPTY_STR.is_empty());
164+
| ^^^^^^^^^^^^^^^^^^^^
165+
166+
error: this expression always evaluates to true
167+
--> tests/ui/const_is_empty.rs:203:9
162168
|
163169
LL | EMPTY_STR.is_empty();
164170
| ^^^^^^^^^^^^^^^^^^^^
165171

166-
error: aborting due to 27 previous errors
172+
error: aborting due to 28 previous errors
167173

src/tools/clippy/tests/ui/incompatible_msrv.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#![warn(clippy::incompatible_msrv)]
2-
#![feature(custom_inner_attributes)]
3-
#![feature(panic_internals)]
2+
#![allow(clippy::diverging_sub_expression)]
3+
#![feature(custom_inner_attributes, panic_internals)]
44
#![clippy::msrv = "1.3.0"]
55

66
use std::collections::HashMap;

tests/ui/codemap_tests/issue-28308.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,16 @@
11
fn main() {
2-
assert!("foo");
3-
//~^ ERROR cannot apply unary operator `!`
2+
assert!("foo"); //~ ERROR mismatched types
3+
//~^ NOTE expected `bool`, found `str`
4+
//~| NOTE in this expansion of assert!
5+
let x = Some(&1);
6+
assert!(x); //~ ERROR mismatched types
7+
//~^ NOTE expected `bool`, found `Option<&{integer}>`
8+
//~| NOTE expected enum `bool`
9+
//~| NOTE in this expansion of assert!
10+
//~| NOTE in this expansion of assert!
11+
assert!(x, ""); //~ ERROR mismatched types
12+
//~^ NOTE expected `bool`, found `Option<&{integer}>`
13+
//~| NOTE expected enum `bool`
14+
//~| NOTE in this expansion of assert!
15+
//~| NOTE in this expansion of assert!
416
}
Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,27 @@
1-
error[E0600]: cannot apply unary operator `!` to type `&'static str`
2-
--> $DIR/issue-28308.rs:2:5
1+
error[E0308]: mismatched types
2+
--> $DIR/issue-28308.rs:2:13
33
|
44
LL | assert!("foo");
5-
| ^^^^^^^^^^^^^^ cannot apply unary operator `!`
5+
| ^^^^^ expected `bool`, found `str`
66

7-
error: aborting due to 1 previous error
7+
error[E0308]: mismatched types
8+
--> $DIR/issue-28308.rs:6:13
9+
|
10+
LL | assert!(x);
11+
| ^ expected `bool`, found `Option<&{integer}>`
12+
|
13+
= note: expected enum `bool`
14+
found type `Option<&{integer}>`
15+
16+
error[E0308]: mismatched types
17+
--> $DIR/issue-28308.rs:11:13
18+
|
19+
LL | assert!(x, "");
20+
| ^ expected `bool`, found `Option<&{integer}>`
21+
|
22+
= note: expected enum `bool`
23+
found type `Option<&{integer}>`
24+
25+
error: aborting due to 3 previous errors
826

9-
For more information about this error, try `rustc --explain E0600`.
27+
For more information about this error, try `rustc --explain E0308`.

0 commit comments

Comments
 (0)