Skip to content

Commit b17736a

Browse files
committed
Use if <cond> {} else { <then> } instead of if !<cond> { <then> }
This approach works on *every* edition and emits the desired output consistently: ``` error[E0308]: mismatched types --> $DIR/issue-28308.rs:6:13 | LL | assert!("foo"); | ^^^^^ expected `bool`, found `&str` ```
1 parent e0b793d commit b17736a

File tree

12 files changed

+60
-126
lines changed

12 files changed

+60
-126
lines changed
Lines changed: 18 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
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::{self as 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};
1010
use rustc_parse::exp;
1111
use rustc_parse::parser::Parser;
12-
use rustc_span::edition::Edition;
1312
use rustc_span::{DUMMY_SP, Ident, Span, Symbol, sym};
1413
use thin_vec::thin_vec;
1514

@@ -21,7 +20,7 @@ pub(crate) fn expand_assert<'cx>(
2120
span: Span,
2221
tts: TokenStream,
2322
) -> MacroExpanderResult<'cx> {
24-
let Assert { cond_expr, inner_cond_expr, custom_message } = match parse_assert(cx, span, tts) {
23+
let Assert { cond_expr, custom_message } = match parse_assert(cx, span, tts) {
2524
Ok(assert) => assert,
2625
Err(err) => {
2726
let guar = err.emit();
@@ -31,7 +30,7 @@ pub(crate) fn expand_assert<'cx>(
3130

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

3635
let panic_path = || {
3736
if use_panic_2021(span) {
@@ -65,15 +64,15 @@ pub(crate) fn expand_assert<'cx>(
6564
}),
6665
})),
6766
);
68-
expr_if_not(cx, call_site_span, cond_expr, then, None)
67+
expr_if_not(cx, call_site_span, cond_expr, then)
6968
}
7069
// If `generic_assert` is enabled, generates rich captured outputs
7170
//
7271
// FIXME(c410-f3r) See https://github.com/rust-lang/rust/issues/96949
7372
else if cx.ecfg.features.generic_assert() {
7473
// FIXME(estebank): we use the condition the user passed without coercing to `bool` when
7574
// `generic_assert` is enabled, but we could use `cond_expr` instead.
76-
context::Context::new(cx, call_site_span).build(inner_cond_expr, panic_path())
75+
context::Context::new(cx, call_site_span).build(cond_expr, panic_path())
7776
}
7877
// If `generic_assert` is not enabled, only outputs a literal "assertion failed: ..."
7978
// string
@@ -88,34 +87,29 @@ pub(crate) fn expand_assert<'cx>(
8887
DUMMY_SP,
8988
Symbol::intern(&format!(
9089
"assertion failed: {}",
91-
pprust::expr_to_string(&inner_cond_expr)
90+
pprust::expr_to_string(&cond_expr)
9291
)),
9392
)],
9493
);
95-
expr_if_not(cx, call_site_span, cond_expr, then, None)
94+
expr_if_not(cx, call_site_span, cond_expr, then)
9695
};
9796

9897
ExpandResult::Ready(MacEager::expr(expr))
9998
}
10099

101-
// `assert!($cond_expr, $custom_message)`
100+
/// `assert!($cond_expr, $custom_message)`
102101
struct Assert {
103-
// `{ let assert_macro: bool = $cond_expr; assert_macro }`
104102
cond_expr: P<Expr>,
105-
// We keep the condition without the `bool` coercion for the panic message.
106-
inner_cond_expr: P<Expr>,
107103
custom_message: Option<TokenStream>,
108104
}
109105

110-
// if !{ ... } { ... } else { ... }
111-
fn expr_if_not(
112-
cx: &ExtCtxt<'_>,
113-
span: Span,
114-
cond: P<Expr>,
115-
then: P<Expr>,
116-
els: Option<P<Expr>>,
117-
) -> P<Expr> {
118-
cx.expr_if(span, cx.expr(span, ExprKind::Unary(UnOp::Not, cond)), then, els)
106+
/// `if !{ ... } { ... }`
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 `if <cond> {} else { <then> }`.
109+
// This allows us to always complain about mismatched types instead of "cannot apply unary
110+
// operator `!` to type `X`" when passing an invalid `<cond>`.
111+
let els = cx.expr_block(cx.block(span, thin_vec![]));
112+
cx.expr_if(span, cond, els, Some(then))
119113
}
120114

121115
fn parse_assert<'a>(cx: &ExtCtxt<'a>, sp: Span, stream: TokenStream) -> PResult<'a, Assert> {
@@ -125,7 +119,7 @@ fn parse_assert<'a>(cx: &ExtCtxt<'a>, sp: Span, stream: TokenStream) -> PResult<
125119
return Err(cx.dcx().create_err(errors::AssertRequiresBoolean { span: sp }));
126120
}
127121

128-
let inner_cond_expr = parser.parse_expr()?;
122+
let cond_expr = parser.parse_expr()?;
129123

130124
// Some crates use the `assert!` macro in the following form (note extra semicolon):
131125
//
@@ -161,66 +155,10 @@ fn parse_assert<'a>(cx: &ExtCtxt<'a>, sp: Span, stream: TokenStream) -> PResult<
161155
parser.unexpected()?;
162156
}
163157

164-
let cond_expr = expand_cond(cx, parser, inner_cond_expr.clone());
165-
Ok(Assert { cond_expr, inner_cond_expr, custom_message })
166-
}
167-
168-
fn expand_cond(cx: &ExtCtxt<'_>, parser: Parser<'_>, cond_expr: P<Expr>) -> P<Expr> {
169-
let span = cx.with_call_site_ctxt(cond_expr.span);
170-
// Coerce the expression to `bool` for more accurate errors. If `assert!` is passed an
171-
// expression that isn't `bool`, the type error will point at only the expression and not the
172-
// entire macro call. If a non-`bool` is passed that doesn't implement `trait Not`, we won't
173-
// talk about traits, we'll just state the appropriate type error.
174-
// `let assert_macro: bool = $expr;`
175-
let ident = Ident::new(sym::assert_macro, DUMMY_SP);
176-
177-
let expr = if use_assert_2024(span) {
178-
// `{ let assert_macro: bool = $expr; assert_macro }`
179-
cond_expr
180-
} else {
181-
// In <=2021, we allow anything that can be negated to `bool`, not just `bool`s. We use the
182-
// "double not" trick to coerce the expression to `bool`. We still assign it to a new `bool`
183-
// binding so that in the case of a type that implements `Not` but doesn't return `bool`,
184-
// like `i32`, we still point at the condition and not at the whole macro.
185-
// `{ let assert_macro: bool = !!$expr; assert_macro }`
186-
let not = |expr| parser.mk_expr(span, ast::ExprKind::Unary(ast::UnOp::Not, expr));
187-
not(not(cond_expr))
188-
};
189-
let block = thin_vec![
190-
cx.stmt_let_ty(
191-
DUMMY_SP,
192-
false,
193-
ident,
194-
Some(cx.ty_ident(span, Ident::new(sym::bool, DUMMY_SP))),
195-
expr,
196-
),
197-
parser.mk_stmt(
198-
span,
199-
ast::StmtKind::Expr(
200-
parser.mk_expr(span, ast::ExprKind::Path(None, ast::Path::from_ident(ident)))
201-
),
202-
),
203-
];
204-
parser.mk_expr(
205-
span,
206-
ast::ExprKind::Block(parser.mk_block(block, ast::BlockCheckMode::Default, span), None),
207-
)
158+
Ok(Assert { cond_expr, custom_message })
208159
}
209160

210161
fn parse_custom_message(parser: &mut Parser<'_>) -> Option<TokenStream> {
211162
let ts = parser.parse_tokens();
212163
if !ts.is_empty() { Some(ts) } else { None }
213164
}
214-
215-
pub(crate) fn use_assert_2024(mut span: Span) -> bool {
216-
// To determine the edition, we check the first span up the expansion
217-
// stack that isn't internal.
218-
loop {
219-
let expn = span.ctxt().outer_expn_data();
220-
if let Some(_features) = expn.allow_internal_unstable {
221-
span = expn.call_site;
222-
continue;
223-
}
224-
break expn.edition >= Edition::Edition2024;
225-
}
226-
}

tests/coverage/assert_not.cov-map

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
Function name: assert_not::main
2-
Raw bytes (29): 0x[01, 01, 00, 05, 01, 06, 01, 01, 12, 05, 02, 05, 00, 14, 09, 01, 05, 00, 14, 0d, 01, 05, 00, 16, 11, 01, 01, 00, 02]
2+
Raw bytes (58): 0x[01, 01, 02, 05, 00, 0d, 00, 0a, 01, 06, 01, 00, 0b, 05, 01, 05, 00, 0c, 01, 00, 0d, 00, 11, 00, 01, 05, 00, 0c, 05, 00, 0d, 00, 13, 0d, 01, 05, 00, 0c, 02, 00, 0d, 00, 13, 00, 01, 05, 00, 0c, 0d, 00, 0d, 00, 15, 06, 01, 01, 00, 02]
33
Number of files: 1
44
- file 0 => global file 1
5-
Number of expressions: 0
6-
Number of file 0 mappings: 5
7-
- Code(Counter(0)) at (prev + 6, 1) to (start + 1, 18)
8-
- Code(Counter(1)) at (prev + 2, 5) to (start + 0, 20)
9-
- Code(Counter(2)) at (prev + 1, 5) to (start + 0, 20)
10-
- Code(Counter(3)) at (prev + 1, 5) to (start + 0, 22)
11-
- Code(Counter(4)) at (prev + 1, 1) to (start + 0, 2)
12-
Highest counter ID seen: c4
5+
Number of expressions: 2
6+
- expression 0 operands: lhs = Counter(1), rhs = Zero
7+
- expression 1 operands: lhs = Counter(3), rhs = Zero
8+
Number of file 0 mappings: 10
9+
- Code(Counter(0)) at (prev + 6, 1) to (start + 0, 11)
10+
- Code(Counter(1)) at (prev + 1, 5) to (start + 0, 12)
11+
- Code(Counter(0)) at (prev + 0, 13) to (start + 0, 17)
12+
- Code(Zero) at (prev + 1, 5) to (start + 0, 12)
13+
- Code(Counter(1)) at (prev + 0, 13) to (start + 0, 19)
14+
- Code(Counter(3)) at (prev + 1, 5) to (start + 0, 12)
15+
- Code(Expression(0, Sub)) at (prev + 0, 13) to (start + 0, 19)
16+
= (c1 - Zero)
17+
- Code(Zero) at (prev + 1, 5) to (start + 0, 12)
18+
- Code(Counter(3)) at (prev + 0, 13) to (start + 0, 21)
19+
- Code(Expression(1, Sub)) at (prev + 1, 1) to (start + 0, 2)
20+
= (c3 - Zero)
21+
Highest counter ID seen: c3
1322

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
error[E0600]: cannot apply unary operator `!` to type `&'static str`
1+
error[E0308]: mismatched types
22
--> $DIR/issue-28308.rs:6:13
33
|
44
LL | assert!("foo");
5-
| ^^^^^ cannot apply unary operator `!`
6-
|
7-
= note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info)
5+
| ^^^^^ expected `bool`, found `&str`
86

97
error: aborting due to 1 previous error
108

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

tests/ui/codemap_tests/issue-28308.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@
55
fn main() {
66
assert!("foo");
77
//[edition2024]~^ ERROR mismatched types
8-
//[edition2021]~^^ ERROR cannot apply unary operator `!` to type `&'static str`
8+
//[edition2021]~^^ ERROR mismatched types
99
}

tests/ui/consts/control-flow/assert.stderr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
error[E0080]: evaluation of constant value failed
2-
--> $DIR/assert.rs:5:15
2+
--> $DIR/assert.rs:5:23
33
|
44
LL | const _: () = assert!(false);
5-
| ^^^^^^^^^^^^^^ the evaluated program panicked at 'assertion failed: false', $DIR/assert.rs:5:15
5+
| ^^^^^ the evaluated program panicked at 'assertion failed: false', $DIR/assert.rs:5:15
66
|
77
= note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info)
88

tests/ui/generics/post_monomorphization_error_backtrace.stderr

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
error[E0080]: evaluation of `assert_zst::F::<u32>::V` failed
2-
--> $DIR/post_monomorphization_error_backtrace.rs:6:23
2+
--> $DIR/post_monomorphization_error_backtrace.rs:6:31
33
|
44
LL | const V: () = assert!(std::mem::size_of::<T>() == 0);
5-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'assertion failed: std::mem::size_of::<T>() == 0', $DIR/post_monomorphization_error_backtrace.rs:6:23
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'assertion failed: std::mem::size_of::<T>() == 0', $DIR/post_monomorphization_error_backtrace.rs:6:23
66
|
77
= note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info)
88

@@ -19,10 +19,10 @@ LL | assert_zst::<U>()
1919
| ^^^^^^^^^^^^^^^^^
2020

2121
error[E0080]: evaluation of `assert_zst::F::<i32>::V` failed
22-
--> $DIR/post_monomorphization_error_backtrace.rs:6:23
22+
--> $DIR/post_monomorphization_error_backtrace.rs:6:31
2323
|
2424
LL | const V: () = assert!(std::mem::size_of::<T>() == 0);
25-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'assertion failed: std::mem::size_of::<T>() == 0', $DIR/post_monomorphization_error_backtrace.rs:6:23
25+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'assertion failed: std::mem::size_of::<T>() == 0', $DIR/post_monomorphization_error_backtrace.rs:6:23
2626
|
2727
= note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info)
2828

tests/ui/inline-const/const-expr-generic-err.stderr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
error[E0080]: evaluation of `foo::<i32>::{constant#0}` failed
2-
--> $DIR/const-expr-generic-err.rs:4:13
2+
--> $DIR/const-expr-generic-err.rs:4:21
33
|
44
LL | const { assert!(std::mem::size_of::<T>() == 0); }
5-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'assertion failed: std::mem::size_of::<T>() == 0', $DIR/const-expr-generic-err.rs:4:13
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'assertion failed: std::mem::size_of::<T>() == 0', $DIR/const-expr-generic-err.rs:4:13
66
|
77
= note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info)
88

tests/ui/issues/issue-14091-2.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@ fn main() {
1313
let x = BytePos(1);
1414

1515
assert!(x, x);
16-
//~^ ERROR cannot apply unary operator `!` to type `BytePos`
16+
//~^ ERROR mismatched types
1717
}
Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,9 @@
1-
error[E0600]: cannot apply unary operator `!` to type `BytePos`
1+
error[E0308]: mismatched types
22
--> $DIR/issue-14091-2.rs:15:13
33
|
44
LL | assert!(x, x);
5-
| ^ cannot apply unary operator `!`
6-
|
7-
note: an implementation of `Not` might be missing for `BytePos`
8-
--> $DIR/issue-14091-2.rs:6:1
9-
|
10-
LL | pub struct BytePos(pub u32);
11-
| ^^^^^^^^^^^^^^^^^^ must implement `Not`
12-
note: the trait `Not` must be implemented
13-
--> $SRC_DIR/core/src/ops/bit.rs:LL:COL
14-
= note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info)
5+
| ^ expected `bool`, found `BytePos`
156

167
error: aborting due to 1 previous error
178

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

tests/ui/issues/issue-14091.stderr

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ error[E0308]: mismatched types
33
|
44
LL | assert!(1,1);
55
| ^ expected `bool`, found integer
6-
|
7-
= note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info)
86

97
error: aborting due to 1 previous error
108

0 commit comments

Comments
 (0)