Skip to content

Commit 1b0bcfe

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 rust-lang#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 d4bdd1e commit 1b0bcfe

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+324
-254
lines changed

compiler/rustc_builtin_macros/src/assert.rs

+20-14
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,12 +64,14 @@ pub(crate) fn expand_assert<'cx>(
6464
}),
6565
})),
6666
);
67-
expr_if_not(cx, call_site_span, cond_expr, then, None)
67+
expr_if_not(cx, call_site_span, cond_expr, then)
6868
}
6969
// If `generic_assert` is enabled, generates rich captured outputs
7070
//
7171
// FIXME(c410-f3r) See https://github.com/rust-lang/rust/issues/96949
7272
else if cx.ecfg.features.generic_assert() {
73+
// FIXME(estebank): we use the condition the user passed without coercing to `bool` when
74+
// `generic_assert` is enabled, but we could use `cond_expr` instead.
7375
context::Context::new(cx, call_site_span).build(cond_expr, panic_path())
7476
}
7577
// If `generic_assert` is not enabled, only outputs a literal "assertion failed: ..."
@@ -89,26 +91,30 @@ pub(crate) fn expand_assert<'cx>(
8991
)),
9092
)],
9193
);
92-
expr_if_not(cx, call_site_span, cond_expr, then, None)
94+
expr_if_not(cx, call_site_span, cond_expr, then)
9395
};
9496

9597
ExpandResult::Ready(MacEager::expr(expr))
9698
}
9799

100+
/// `assert!($cond_expr, $custom_message)`
98101
struct Assert {
99102
cond_expr: P<Expr>,
100103
custom_message: Option<TokenStream>,
101104
}
102105

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)
106+
/// `match <cond> { true => {} _ => <then> }`
107+
fn expr_if_not(cx: &ExtCtxt<'_>, span: Span, cond: P<Expr>, then: P<Expr>) -> P<Expr> {
108+
// Instead of expanding to `if !<cond> { <then> }`, we expand to
109+
// `match <cond> { true => {} _ <then> }`.
110+
// This allows us to always complain about mismatched types instead of "cannot apply unary
111+
// operator `!` to type `X`" when passing an invalid `<cond>`, while also allowing `<cond>` to
112+
// be `&true`.
113+
let els = cx.expr_block(cx.block(span, thin_vec![]));
114+
let mut arms = thin_vec![];
115+
arms.push(cx.arm(span, cx.pat_lit(span, cx.expr_bool(span, true)), els));
116+
arms.push(cx.arm(span, cx.pat_wild(span), then));
117+
cx.expr_match(span, cond, arms)
112118
}
113119

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

compiler/rustc_hir_typeck/src/demand.rs

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

compiler/rustc_parse/src/parser/expr.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -3859,7 +3859,7 @@ impl<'a> Parser<'a> {
38593859
P(Expr { kind, span, attrs, id: DUMMY_NODE_ID, tokens: None })
38603860
}
38613861

3862-
pub(crate) fn mk_expr(&self, span: Span, kind: ExprKind) -> P<Expr> {
3862+
pub fn mk_expr(&self, span: Span, kind: ExprKind) -> P<Expr> {
38633863
self.mk_expr_with_attrs(span, kind, AttrVec::new())
38643864
}
38653865

compiler/rustc_parse/src/parser/pat.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1732,6 +1732,7 @@ impl<'a> Parser<'a> {
17321732
})
17331733
}
17341734

1735+
// `pub` because we use it from `rustc_builtin_macros`.
17351736
pub(super) fn mk_pat_ident(&self, span: Span, ann: BindingMode, ident: Ident) -> P<Pat> {
17361737
self.mk_pat(span, PatKind::Ident(ann, ident, None))
17371738
}

compiler/rustc_parse/src/parser/stmt.rs

+2-7
Original file line numberDiff line numberDiff line change
@@ -988,12 +988,7 @@ impl<'a> Parser<'a> {
988988
Ok(Some(stmt))
989989
}
990990

991-
pub(super) fn mk_block(
992-
&self,
993-
stmts: ThinVec<Stmt>,
994-
rules: BlockCheckMode,
995-
span: Span,
996-
) -> P<Block> {
991+
pub fn mk_block(&self, stmts: ThinVec<Stmt>, rules: BlockCheckMode, span: Span) -> P<Block> {
997992
P(Block {
998993
stmts,
999994
id: DUMMY_NODE_ID,
@@ -1004,7 +999,7 @@ impl<'a> Parser<'a> {
1004999
})
10051000
}
10061001

1007-
pub(super) fn mk_stmt(&self, span: Span, kind: StmtKind) -> Stmt {
1002+
pub fn mk_stmt(&self, span: Span, kind: StmtKind) -> Stmt {
10081003
Stmt { id: DUMMY_NODE_ID, kind, span }
10091004
}
10101005

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

+25-5
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
456456
span,
457457
format!("this is an iterator with items of type `{}`", args.type_at(0)),
458458
);
459-
} else {
459+
} else if !span.overlaps(cause.span) {
460460
let expected_ty = self.tcx.short_string(expected_ty, err.long_ty_path());
461461
err.span_label(span, format!("this expression has type `{expected_ty}`"));
462462
}
@@ -1584,8 +1584,16 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
15841584
{
15851585
let e = self.tcx.erase_regions(e);
15861586
let f = self.tcx.erase_regions(f);
1587-
let expected = with_forced_trimmed_paths!(e.sort_string(self.tcx));
1588-
let found = with_forced_trimmed_paths!(f.sort_string(self.tcx));
1587+
let mut expected = with_forced_trimmed_paths!(e.sort_string(self.tcx));
1588+
let mut found = with_forced_trimmed_paths!(f.sort_string(self.tcx));
1589+
if let Some(def_id) = cause.span.ctxt().outer_expn_data().macro_def_id
1590+
&& self.tcx.is_diagnostic_item(sym::assert_macro, def_id)
1591+
{
1592+
// When the type error comes from `assert!()`, the cause and effect are reversed
1593+
// because that macro expands to `match val { false => {panic!()}, _ => {} }`, which
1594+
// would say something like "expected `Type`, found `bool`", confusing the user.
1595+
(found, expected) = (expected, found);
1596+
}
15891597
if expected == found {
15901598
label_or_note(span, terr.to_string(self.tcx));
15911599
} else {
@@ -2108,7 +2116,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
21082116
) -> Option<(DiagStyledString, DiagStyledString)> {
21092117
match values {
21102118
ValuePairs::Regions(exp_found) => self.expected_found_str(exp_found),
2111-
ValuePairs::Terms(exp_found) => self.expected_found_str_term(exp_found, file),
2119+
ValuePairs::Terms(exp_found) => self.expected_found_str_term(cause, exp_found, file),
21122120
ValuePairs::Aliases(exp_found) => self.expected_found_str(exp_found),
21132121
ValuePairs::ExistentialTraitRef(exp_found) => self.expected_found_str(exp_found),
21142122
ValuePairs::ExistentialProjection(exp_found) => self.expected_found_str(exp_found),
@@ -2147,15 +2155,27 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
21472155

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

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

library/alloc/tests/str.rs

+11-11
Original file line numberDiff line numberDiff line change
@@ -2297,21 +2297,21 @@ fn utf8_chars() {
22972297
assert_eq!(schs.len(), 4);
22982298
assert_eq!(schs.iter().cloned().collect::<String>(), s);
22992299

2300-
assert!((from_utf8(s.as_bytes()).is_ok()));
2300+
assert!(from_utf8(s.as_bytes()).is_ok());
23012301
// invalid prefix
2302-
assert!((!from_utf8(&[0x80]).is_ok()));
2302+
assert!(!from_utf8(&[0x80]).is_ok());
23032303
// invalid 2 byte prefix
2304-
assert!((!from_utf8(&[0xc0]).is_ok()));
2305-
assert!((!from_utf8(&[0xc0, 0x10]).is_ok()));
2304+
assert!(!from_utf8(&[0xc0]).is_ok());
2305+
assert!(!from_utf8(&[0xc0, 0x10]).is_ok());
23062306
// invalid 3 byte prefix
2307-
assert!((!from_utf8(&[0xe0]).is_ok()));
2308-
assert!((!from_utf8(&[0xe0, 0x10]).is_ok()));
2309-
assert!((!from_utf8(&[0xe0, 0xff, 0x10]).is_ok()));
2307+
assert!(!from_utf8(&[0xe0]).is_ok());
2308+
assert!(!from_utf8(&[0xe0, 0x10]).is_ok());
2309+
assert!(!from_utf8(&[0xe0, 0xff, 0x10]).is_ok());
23102310
// invalid 4 byte prefix
2311-
assert!((!from_utf8(&[0xf0]).is_ok()));
2312-
assert!((!from_utf8(&[0xf0, 0x10]).is_ok()));
2313-
assert!((!from_utf8(&[0xf0, 0xff, 0x10]).is_ok()));
2314-
assert!((!from_utf8(&[0xf0, 0xff, 0xff, 0x10]).is_ok()));
2311+
assert!(!from_utf8(&[0xf0]).is_ok());
2312+
assert!(!from_utf8(&[0xf0, 0x10]).is_ok());
2313+
assert!(!from_utf8(&[0xf0, 0xff, 0x10]).is_ok());
2314+
assert!(!from_utf8(&[0xf0, 0xff, 0xff, 0x10]).is_ok());
23152315
}
23162316

23172317
#[test]

library/coretests/tests/bool.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,14 @@ fn test_bool() {
7171
#[test]
7272
pub fn test_bool_not() {
7373
if !false {
74-
assert!((true));
74+
assert!(true);
7575
} else {
76-
assert!((false));
76+
assert!(false);
7777
}
7878
if !true {
79-
assert!((false));
79+
assert!(false);
8080
} else {
81-
assert!((true));
81+
assert!(true);
8282
}
8383
}
8484

library/coretests/tests/ptr.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,11 @@ fn test() {
4242
let mut v1 = vec![0u16, 0u16, 0u16];
4343

4444
copy(v0.as_ptr().offset(1), v1.as_mut_ptr().offset(1), 1);
45-
assert!((v1[0] == 0u16 && v1[1] == 32001u16 && v1[2] == 0u16));
45+
assert!(v1[0] == 0u16 && v1[1] == 32001u16 && v1[2] == 0u16);
4646
copy(v0.as_ptr().offset(2), v1.as_mut_ptr(), 1);
47-
assert!((v1[0] == 32002u16 && v1[1] == 32001u16 && v1[2] == 0u16));
47+
assert!(v1[0] == 32002u16 && v1[1] == 32001u16 && v1[2] == 0u16);
4848
copy(v0.as_ptr(), v1.as_mut_ptr().offset(2), 1);
49-
assert!((v1[0] == 32002u16 && v1[1] == 32001u16 && v1[2] == 32000u16));
49+
assert!(v1[0] == 32002u16 && v1[1] == 32001u16 && v1[2] == 32000u16);
5050
}
5151
}
5252

library/coretests/tests/tuple.rs

+1-1
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/istr.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ fn test_stack_assign() {
55
let t: String = "a".to_string();
66
assert_eq!(s, t);
77
let u: String = "b".to_string();
8-
assert!((s != u));
8+
assert!(s != u);
99
}
1010

1111
#[test]
@@ -19,7 +19,7 @@ fn test_heap_assign() {
1919
let t: String = "a big ol' string".to_string();
2020
assert_eq!(s, t);
2121
let u: String = "a bad ol' string".to_string();
22-
assert!((s != u));
22+
assert!(s != u);
2323
}
2424

2525
#[test]

library/std/tests/seq-compare.rs

+11-11
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
#[test]
22
fn seq_compare() {
3-
assert!(("hello".to_string() < "hellr".to_string()));
4-
assert!(("hello ".to_string() > "hello".to_string()));
5-
assert!(("hello".to_string() != "there".to_string()));
6-
assert!((vec![1, 2, 3, 4] > vec![1, 2, 3]));
7-
assert!((vec![1, 2, 3] < vec![1, 2, 3, 4]));
8-
assert!((vec![1, 2, 4, 4] > vec![1, 2, 3, 4]));
9-
assert!((vec![1, 2, 3, 4] < vec![1, 2, 4, 4]));
10-
assert!((vec![1, 2, 3] <= vec![1, 2, 3]));
11-
assert!((vec![1, 2, 3] <= vec![1, 2, 3, 3]));
12-
assert!((vec![1, 2, 3, 4] > vec![1, 2, 3]));
3+
assert!("hello".to_string() < "hellr".to_string());
4+
assert!("hello ".to_string() > "hello".to_string());
5+
assert!("hello".to_string() != "there".to_string());
6+
assert!(vec![1, 2, 3, 4] > vec![1, 2, 3]);
7+
assert!(vec![1, 2, 3] < vec![1, 2, 3, 4]);
8+
assert!(vec![1, 2, 4, 4] > vec![1, 2, 3, 4]);
9+
assert!(vec![1, 2, 3, 4] < vec![1, 2, 4, 4]);
10+
assert!(vec![1, 2, 3] <= vec![1, 2, 3]);
11+
assert!(vec![1, 2, 3] <= vec![1, 2, 3, 3]);
12+
assert!(vec![1, 2, 3, 4] > vec![1, 2, 3]);
1313
assert_eq!(vec![1, 2, 3], vec![1, 2, 3]);
14-
assert!((vec![1, 2, 3] != vec![1, 1, 3]));
14+
assert!(vec![1, 2, 3] != vec![1, 1, 3]);
1515
}

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

+19-5
Original file line numberDiff line numberDiff line change
@@ -129,13 +129,27 @@ impl<'tcx> LateLintPass<'tcx> for BoolAssertComparison {
129129

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

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

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

0 commit comments

Comments
 (0)