From 046d89bee34a197ae5d4e4560bca7f2436a6f552 Mon Sep 17 00:00:00 2001 From: dianne Date: Tue, 24 Jun 2025 21:47:28 -0700 Subject: [PATCH 1/3] add `ast::ExprKind::Is` and placeholder builtin syntax Until it can be parsed properly, `expr is pat` is written `builtin # is(expr is pat)`. To be more concise, the included tests use a macro wrapper around it. --- compiler/rustc_ast/src/ast.rs | 8 +++ compiler/rustc_ast/src/util/classify.rs | 4 ++ compiler/rustc_ast/src/visit.rs | 2 + compiler/rustc_ast_lowering/src/expr.rs | 10 ++++ .../rustc_ast_pretty/src/pprust/state/expr.rs | 7 +++ .../src/assert/context.rs | 3 ++ compiler/rustc_parse/src/parser/expr.rs | 26 ++++++++++ compiler/rustc_passes/src/input_stats.rs | 2 +- compiler/rustc_resolve/src/late.rs | 9 ++++ src/tools/clippy/clippy_utils/src/sugg.rs | 2 + src/tools/rustfmt/src/expr.rs | 4 ++ src/tools/rustfmt/src/utils.rs | 1 + .../ui/rfcs/rfc-3573-is/auxiliary/is-macro.rs | 7 +++ tests/ui/rfcs/rfc-3573-is/run-pass-simple.rs | 51 +++++++++++++++++++ 14 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 tests/ui/rfcs/rfc-3573-is/auxiliary/is-macro.rs create mode 100644 tests/ui/rfcs/rfc-3573-is/run-pass-simple.rs diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 97e070958751a..0d194c77e033e 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -1562,6 +1562,9 @@ impl Expr { // need parens sometimes. E.g. we can print `(let _ = a) && b` as `let _ = a && b` // but we need to print `(let _ = a) < b` as-is with parens. | ExprKind::Let(..) + // FIXME(is): `expr is pat` should be binop-like, but until it can be parsed as such, + // it's using prefix placeholder syntax. + | ExprKind::Is(..) | ExprKind::Unary(..) => ExprPrecedence::Prefix, // Need parens if and only if there are prefix attributes. @@ -1739,6 +1742,11 @@ pub enum ExprKind { /// /// `Span` represents the whole `let pat = expr` statement. Let(P, P, Span, Recovered), + /// An `expr is pat` expression. Currently, there's no `is` keyword, so as a placeholder it's + /// parsed as `builtin # is(expr is pat)`. + /// + /// This is desugared to `if` and `let` expressions. + Is(P, P), /// An `if` block, with an optional `else` block. /// /// `if expr { block } else { expr }` diff --git a/compiler/rustc_ast/src/util/classify.rs b/compiler/rustc_ast/src/util/classify.rs index f7daec4b0648c..552e80609d207 100644 --- a/compiler/rustc_ast/src/util/classify.rs +++ b/compiler/rustc_ast/src/util/classify.rs @@ -99,6 +99,7 @@ pub fn expr_requires_semi_to_be_stmt(e: &ast::Expr) -> bool { /// } /// ``` pub fn leading_labeled_expr(mut expr: &ast::Expr) -> bool { + // FIXME(is): This will need tests for `is` once it can be parsed. loop { match &expr.kind { Block(_, label) | ForLoop { label, .. } | Loop(_, label, _) | While(_, _, label) => { @@ -110,6 +111,7 @@ pub fn leading_labeled_expr(mut expr: &ast::Expr) -> bool { | Await(e, _) | Use(e, _) | Binary(_, e, _) + | Is(e, _) | Call(e, _) | Cast(e, _) | Field(e, _) @@ -171,6 +173,7 @@ pub enum TrailingBrace<'a> { /// If an expression ends with `}`, returns the innermost expression ending in the `}` pub fn expr_trailing_brace(mut expr: &ast::Expr) -> Option> { + // FIXME(is): We should have a test for `let b = expr is Variant {} else { return }`. loop { match &expr.kind { AddrOf(_, _, e) @@ -237,6 +240,7 @@ pub fn expr_trailing_brace(mut expr: &ast::Expr) -> Option> { | Paren(_) | Try(_) | Yeet(None) + | Is(..) | UnsafeBinderCast(..) | Err(_) | Dummy => { diff --git a/compiler/rustc_ast/src/visit.rs b/compiler/rustc_ast/src/visit.rs index ab15cb28fa12d..d6c776ad04222 100644 --- a/compiler/rustc_ast/src/visit.rs +++ b/compiler/rustc_ast/src/visit.rs @@ -972,6 +972,8 @@ macro_rules! common_visitor_and_walkers { visit_visitable!($($mut)? vis, subexpression, typ), ExprKind::Let(pat, expr, span, _recovered) => visit_visitable!($($mut)? vis, pat, expr, span), + ExprKind::Is(expr, pat) => + visit_visitable!($($mut)? vis, expr, pat), ExprKind::If(head_expression, if_block, optional_else) => visit_visitable!($($mut)? vis, head_expression, if_block, optional_else), ExprKind::While(subexpression, block, opt_label) => diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index 15e736261d583..a0a5c4070e17a 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -176,6 +176,16 @@ impl<'hir> LoweringContext<'_, 'hir> { recovered: *recovered, })) } + ExprKind::Is(scrutinee, pat) => { + // TODO: properly desugar `is` outside of `if`/`while` + hir::ExprKind::Let(self.arena.alloc(hir::LetExpr { + span: self.lower_span(e.span), + pat: self.lower_pat(pat), + ty: None, + init: self.lower_expr(scrutinee), + recovered: Recovered::No, + })) + } ExprKind::If(cond, then, else_opt) => { self.lower_expr_if(cond, then, else_opt.as_deref()) } diff --git a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs index 8a2cb64b2a084..d85da3fcf2912 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs @@ -503,6 +503,13 @@ impl<'a> State<'a> { ast::ExprKind::Let(pat, scrutinee, _, _) => { self.print_let(pat, scrutinee, fixup); } + ast::ExprKind::Is(scrutinee, pat) => { + // FIXME(is): This is a placeholder. Pretty-print properly once `is` can be parsed. + self.print_expr(scrutinee, fixup); + self.space(); + self.word_space("is"); + self.print_pat(pat); + } ast::ExprKind::If(test, blk, elseopt) => self.print_if(test, blk, elseopt.as_deref()), ast::ExprKind::While(test, blk, opt_label) => { if let Some(label) = opt_label { diff --git a/compiler/rustc_builtin_macros/src/assert/context.rs b/compiler/rustc_builtin_macros/src/assert/context.rs index ea7248ca5393a..9b6cc2ada5aa5 100644 --- a/compiler/rustc_builtin_macros/src/assert/context.rs +++ b/compiler/rustc_builtin_macros/src/assert/context.rs @@ -243,6 +243,9 @@ impl<'cx, 'a> Context<'cx, 'a> { ExprKind::Let(_, local_expr, _, _) => { self.manage_cond_expr(local_expr); } + ExprKind::Is(local_expr, _) => { + self.manage_cond_expr(local_expr); + } ExprKind::Match(local_expr, ..) => { self.manage_cond_expr(local_expr); } diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 3d89530f91435..8abeb2d3e0a31 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -1962,6 +1962,7 @@ impl<'a> Parser<'a> { sym::unwrap_binder => { Some(this.parse_expr_unsafe_binder_cast(lo, UnsafeBinderCastKind::Unwrap)?) } + sym::is => Some(this.parse_expr_is(lo)?), _ => None, }) }) @@ -2046,6 +2047,17 @@ impl<'a> Parser<'a> { Ok(self.mk_expr(span, ExprKind::UnsafeBinderCast(kind, expr, ty))) } + /// Parse placeholder built-in syntax for `is` (rfcs#3573) + fn parse_expr_is(&mut self, lo: Span) -> PResult<'a, P> { + let scrutinee = self.parse_expr()?; + self.expect_keyword(exp!(Is))?; + // Likely this will need to be `parse_pat_no_top_alt` for the final syntax. + // FIXME(is): If so, we'll want a `PatternLocation` variant for `is` for diagnostics. + let pat = self.parse_pat_no_top_alt(None, None)?; + let span = lo.to(self.token.span); + Ok(self.mk_expr(span, ExprKind::Is(scrutinee, pat))) + } + /// Returns a string literal if the next token is a string literal. /// In case of error returns `Some(lit)` if the next token is a literal with a wrong kind, /// and returns `None` if the next token is not literal at all. @@ -3436,6 +3448,10 @@ impl<'a> Parser<'a> { fn parse_match_arm_guard(&mut self) -> PResult<'a, Option>> { // Used to check the `if_let_guard` feature mostly by scanning // `&&` tokens. + // FIXME(is): This can't catch `is` expressions. In order to implement placeholder + // macro-based syntax for `is`, `is` within a macro expansion is treated as part of whatever + // condition it expands into. As such, gating `is` in match guards would need to be part of + // the `feature_gate` AST pass. fn has_let_expr(expr: &Expr) -> bool { match &expr.kind { ExprKind::Binary(BinOp { node: BinOpKind::And, .. }, lhs, rhs) => { @@ -4123,6 +4139,16 @@ impl MutVisitor for CondChecker<'_> { } } } + ExprKind::Is(_, _) => { + // FIXME(is): Handle edition-dependent rules for `is` in `&&`-chains. Currently, + // `is` desugars to `let` where `let`-chains are permitted, and otherwise desugars + // to `if let`. In the latter case, we could change the desugaring to rescope its + // temporaries to work on all Editions. For `is` inside an existing `if` + // expression's condition, however, temporaries in the condition live too long to + // permit all `&&` chains in Editions ≤ 2021. Since `is` will be a new keyword, it + // may only be possible for this to arise through the use of mixed-edition macro + // expansion or raw keywords (rfcs#3098). + } ExprKind::Binary(Spanned { node: BinOpKind::And, .. }, _, _) => { mut_visit::walk_expr(self, e); } diff --git a/compiler/rustc_passes/src/input_stats.rs b/compiler/rustc_passes/src/input_stats.rs index 6ee325dce0326..def22c1105088 100644 --- a/compiler/rustc_passes/src/input_stats.rs +++ b/compiler/rustc_passes/src/input_stats.rs @@ -655,7 +655,7 @@ impl<'v> ast_visit::Visitor<'v> for StatCollector<'v> { record_variants!( (self, e, e.kind, None, ast, Expr, ExprKind), [ - Array, ConstBlock, Call, MethodCall, Tup, Binary, Unary, Lit, Cast, Type, Let, + Array, ConstBlock, Call, MethodCall, Tup, Binary, Unary, Lit, Cast, Type, Let, Is, If, While, ForLoop, Loop, Match, Closure, Block, Await, Use, TryBlock, Assign, AssignOp, Field, Index, Range, Underscore, Path, AddrOf, Break, Continue, Ret, InlineAsm, FormatArgs, OffsetOf, MacCall, Struct, Repeat, Paren, Try, Yield, Yeet, diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index a3a770502ded1..383e4c544ddf0 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -61,6 +61,7 @@ struct BindingInfo { pub(crate) enum PatternSource { Match, Let, + Is, For, FnParam, } @@ -88,6 +89,7 @@ impl PatternSource { match self { PatternSource::Match => "match binding", PatternSource::Let => "let binding", + PatternSource::Is => "is binding", PatternSource::For => "for binding", PatternSource::FnParam => "function parameter", } @@ -4844,6 +4846,13 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { self.apply_pattern_bindings(bindings); } + ExprKind::Is(ref scrutinee, ref pat) => { + // TODO: handle `is` outside of `if`/`while`: we'll need to introduce a new rib for + // the `is`'s `&&`-chain. + self.visit_expr(scrutinee); + self.resolve_pattern_top(pat, PatternSource::Is); + } + ExprKind::If(ref cond, ref then, ref opt_else) => { self.with_rib(ValueNS, RibKind::Normal, |this| { let old = this.diag_metadata.in_if_condition.replace(cond); diff --git a/src/tools/clippy/clippy_utils/src/sugg.rs b/src/tools/clippy/clippy_utils/src/sugg.rs index 7a24d07fa1dfb..67edea620baf1 100644 --- a/src/tools/clippy/clippy_utils/src/sugg.rs +++ b/src/tools/clippy/clippy_utils/src/sugg.rs @@ -184,6 +184,8 @@ impl<'a> Sugg<'a> { | ast::ExprKind::Closure { .. } | ast::ExprKind::If(..) | ast::ExprKind::Let(..) + // FIXME(is): Once `is` can be parsed, it should provide `BinOp` suggestions. + | ast::ExprKind::Is(..) | ast::ExprKind::Unary(..) | ast::ExprKind::Match(..) => match snippet_with_context(cx, expr.span, ctxt, default, app) { (snip, false) => Sugg::MaybeParen(snip), diff --git a/src/tools/rustfmt/src/expr.rs b/src/tools/rustfmt/src/expr.rs index 08aedff2b20da..0d833061b97ee 100644 --- a/src/tools/rustfmt/src/expr.rs +++ b/src/tools/rustfmt/src/expr.rs @@ -426,6 +426,10 @@ pub(crate) fn format_expr( // Also, rustfmt might get passed the output from `-Zunpretty=expanded`. Err(RewriteError::Unknown) } + ast::ExprKind::Is(..) => { + // FIXME(is): add formatting for `expr is pat` expressions. + Err(RewriteError::Unknown) + } ast::ExprKind::Err(_) | ast::ExprKind::Dummy => Err(RewriteError::Unknown), }; diff --git a/src/tools/rustfmt/src/utils.rs b/src/tools/rustfmt/src/utils.rs index fcd475b1784f5..a9f349b5007fa 100644 --- a/src/tools/rustfmt/src/utils.rs +++ b/src/tools/rustfmt/src/utils.rs @@ -505,6 +505,7 @@ pub(crate) fn is_block_expr(context: &RewriteContext<'_>, expr: &ast::Expr, repr | ast::ExprKind::Field(..) | ast::ExprKind::IncludedBytes(..) | ast::ExprKind::InlineAsm(..) + | ast::ExprKind::Is(..) | ast::ExprKind::OffsetOf(..) | ast::ExprKind::UnsafeBinderCast(..) | ast::ExprKind::Let(..) diff --git a/tests/ui/rfcs/rfc-3573-is/auxiliary/is-macro.rs b/tests/ui/rfcs/rfc-3573-is/auxiliary/is-macro.rs new file mode 100644 index 0000000000000..201001bf7932f --- /dev/null +++ b/tests/ui/rfcs/rfc-3573-is/auxiliary/is-macro.rs @@ -0,0 +1,7 @@ +// FIXME(is): This syntax is a placeholder. +#[macro_export] +macro_rules! is { + ($($args:tt)*) => { + builtin # is($($args)*) + } +} diff --git a/tests/ui/rfcs/rfc-3573-is/run-pass-simple.rs b/tests/ui/rfcs/rfc-3573-is/run-pass-simple.rs new file mode 100644 index 0000000000000..2885293df2d02 --- /dev/null +++ b/tests/ui/rfcs/rfc-3573-is/run-pass-simple.rs @@ -0,0 +1,51 @@ +//! Simple tests for matching and binding with `is` expressions. +//@ run-pass +//@ edition: 2024 +//@ aux-crate: is_macro=is-macro.rs +#![feature(builtin_syntax)] +use is_macro::is; + +// Test `is` used as `let` in `if` conditions. +fn test_if() { + for test_in in [None, Some(3), Some(4)] { + let test_if_expr = if is!(test_in is Some(x)) && x > 3 { + // `x` is bound in the success block + x + } else { + 0 + }; + let test_guard = match test_in { + x if is!(x is Some(y)) && y > 3 => { + // `y` is bound in the arm body + y + } + _ => 0, + }; + let test_expected = if let Some(x) = test_in && x > 3 { + x + } else { + 0 + }; + assert_eq!(test_if_expr, test_expected); + assert_eq!(test_guard, test_expected); + } +} + +// Test `is` used as `let` in `while` conditions. +fn test_while() { + let mut count = 0; + let mut opt = Some(3u8); + while is!(opt is Some(x)) { + // `x` is bound in the loop body + count += 1; + opt = x.checked_sub(1); + } + assert_eq!(count, 4); +} + +// TODO: test `is` not directly in conditions + +fn main() { + test_if(); + test_while(); +} From d9cfd3e9d71901a42c6c3ed51f60ac5bf867420c Mon Sep 17 00:00:00 2001 From: dianne Date: Fri, 18 Jul 2025 03:40:24 -0700 Subject: [PATCH 2/3] desugar `is` outside of `if`/`while` conditions Outside of the top level of an `if`/`while` condition, `... && expr is pat && ...` becomes `if ... && let pat = expr && ... { true } else { false }`. --- compiler/rustc_ast/src/ast.rs | 13 +++ compiler/rustc_ast_lowering/src/expr.rs | 102 +++++++++++++++--- compiler/rustc_ast_lowering/src/pat.rs | 1 + compiler/rustc_resolve/src/late.rs | 88 +++++++++++---- compiler/rustc_span/src/hygiene.rs | 4 + tests/ui/rfcs/rfc-3573-is/desugaring.rs | 21 ++++ tests/ui/rfcs/rfc-3573-is/desugaring.stdout | 27 +++++ tests/ui/rfcs/rfc-3573-is/drop-scopes.rs | 76 +++++++++++++ .../rfc-3573-is/name-resolution-errors.rs | 27 +++++ .../rfc-3573-is/name-resolution-errors.stderr | 45 ++++++++ tests/ui/rfcs/rfc-3573-is/run-pass-simple.rs | 11 +- 11 files changed, 379 insertions(+), 36 deletions(-) create mode 100644 tests/ui/rfcs/rfc-3573-is/desugaring.rs create mode 100644 tests/ui/rfcs/rfc-3573-is/desugaring.stdout create mode 100644 tests/ui/rfcs/rfc-3573-is/drop-scopes.rs create mode 100644 tests/ui/rfcs/rfc-3573-is/name-resolution-errors.rs create mode 100644 tests/ui/rfcs/rfc-3573-is/name-resolution-errors.stderr diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 0d194c77e033e..af984dd9a7011 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -1621,6 +1621,19 @@ impl Expr { ) } + /// Is this an `is` expression or a chain of `&&` operators containing an `is` expression? + /// This determines the scope of the `is` expression's bindings. E.g., since + /// `(expr is x) && f()` is broken up by parentheses, `x` is dropped before evaluating `f()`. + pub fn has_expr_is(&self) -> bool { + match &self.kind { + ExprKind::Is(..) => true, + ExprKind::Binary(BinOp { node: BinOpKind::And, .. }, lhs, rhs) => { + lhs.has_expr_is() || rhs.has_expr_is() + } + _ => false, + } + } + /// Creates a dummy `Expr`. /// /// Should only be used when it will be replaced afterwards or as a return value when an error was encountered. diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index a0a5c4070e17a..d002112d241f7 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -94,6 +94,17 @@ impl<'hir> LoweringContext<'_, 'hir> { ExprKind::ForLoop { pat, iter, body, label, kind } => { return self.lower_expr_for(e, pat, iter, body, *label, *kind); } + // Desugar `expr is pat` expressions. This likewise needs special handling as `is` + // expressions outside of `if`/`while` conditions are wrapped in an `if` expression. + ExprKind::Is(..) => return self.lower_wrapped_cond_for_expr_is(e), + ExprKind::Binary(BinOp { node: BinOpKind::And, .. }, ..) => { + if e.has_expr_is() { + return self.lower_wrapped_cond_for_expr_is(e); + } else { + // `has_expr_is` will be false for this `&&`'s operands; don't check again. + return self.lower_cond_mut(e); + } + } _ => (), } @@ -176,16 +187,6 @@ impl<'hir> LoweringContext<'_, 'hir> { recovered: *recovered, })) } - ExprKind::Is(scrutinee, pat) => { - // TODO: properly desugar `is` outside of `if`/`while` - hir::ExprKind::Let(self.arena.alloc(hir::LetExpr { - span: self.lower_span(e.span), - pat: self.lower_pat(pat), - ty: None, - init: self.lower_expr(scrutinee), - recovered: Recovered::No, - })) - } ExprKind::If(cond, then, else_opt) => { self.lower_expr_if(cond, then, else_opt.as_deref()) } @@ -379,7 +380,7 @@ impl<'hir> LoweringContext<'_, 'hir> { ExprKind::Try(sub_expr) => self.lower_expr_try(e.span, sub_expr), - ExprKind::Paren(_) | ExprKind::ForLoop { .. } => { + ExprKind::Paren(_) | ExprKind::ForLoop { .. } | ExprKind::Is(..) => { unreachable!("already handled") } @@ -532,13 +533,86 @@ impl<'hir> LoweringContext<'_, 'hir> { hir::ExprKind::Call(f, self.lower_exprs(&real_args)) } + /// Lower an expression in a context where `let` expressions are permitted, such as the + /// condition of an `if` or `while` expression. This allows `is` expressions to be lowered + /// directly to `let` expressions without needing to be wrapped in an `if`'s condition. + fn lower_cond(&mut self, e: &Expr) -> &'hir hir::Expr<'hir> { + self.arena.alloc(self.lower_cond_mut(e)) + } + + fn lower_cond_mut(&mut self, e: &Expr) -> hir::Expr<'hir> { + self.lower_cond_inner(e, None) + } + + fn lower_cond_inner(&mut self, e: &Expr, wrapper: Option<(HirId, Span)>) -> hir::Expr<'hir> { + let lower_id_and_attrs = |this: &mut LoweringContext<'_, 'hir>| { + let expr_hir_id = this.lower_node_id(e.id); + // If the condition is wrapped in an `if` expression for `is` desugaring, attach + // attributes to the `if` expression instead of `e`. + let (attr_hir_id, attr_span) = wrapper.unwrap_or((expr_hir_id, e.span)); + this.lower_attrs(attr_hir_id, &e.attrs, attr_span); + expr_hir_id + }; + match &e.kind { + ExprKind::Is(scrutinee, pat) => { + // At the top level of a condition, `is` expressions lower directly to `let`. + let hir_id = lower_id_and_attrs(self); + let span = self.mark_span_with_reason( + DesugaringKind::IsExpr, + self.lower_span(e.span), + None, + ); + let kind = hir::ExprKind::Let(self.arena.alloc(hir::LetExpr { + span, + pat: self.lower_pat(pat), + ty: None, + init: self.lower_expr(scrutinee), + recovered: Recovered::No, + })); + hir::Expr { hir_id, kind, span } + } + ExprKind::Binary(binop @ BinOp { node: BinOpKind::And, .. }, lhs, rhs) => { + // `is` expressions in chains of `&&` operators can lower to `let`, so we lower the + // operands using `self.lower_cond` rather than `self.lower_expr`. + ensure_sufficient_stack(|| { + let hir_id = lower_id_and_attrs(self); + let binop = self.lower_binop(*binop); + let lhs = self.lower_cond(lhs); + let rhs = self.lower_cond(rhs); + let kind = hir::ExprKind::Binary(binop, lhs, rhs); + hir::Expr { hir_id, kind, span: self.lower_span(e.span) } + }) + } + _ => return self.lower_expr_mut(e), + } + } + + /// Lower `cond`, wrapped in an `if` expression's condition: `if cond { true } else { false }`. + /// This allows `is` expressions in `cond` to lower to `let` expressions. + fn lower_wrapped_cond_for_expr_is(&mut self, cond: &Expr) -> hir::Expr<'hir> { + let span = + self.mark_span_with_reason(DesugaringKind::IsExpr, self.lower_span(cond.span), None); + let hir_id = self.next_id(); + let lowered_cond = self.arena.alloc(self.lower_cond_inner(cond, Some((hir_id, span)))); + let mut bool_block = |b| { + let lit = hir::Lit { span, node: ast::LitKind::Bool(b) }; + let expr_lit = self.arena.alloc(self.expr(span, hir::ExprKind::Lit(lit))); + let block = self.block_expr(expr_lit); + self.arena.alloc(self.expr_block(block)) + }; + let expr_true = bool_block(true); + let expr_false = bool_block(false); + let kind = hir::ExprKind::If(lowered_cond, expr_true, Some(expr_false)); + hir::Expr { span, kind, hir_id } + } + fn lower_expr_if( &mut self, cond: &Expr, then: &Block, else_opt: Option<&Expr>, ) -> hir::ExprKind<'hir> { - let lowered_cond = self.lower_expr(cond); + let lowered_cond = self.lower_cond(cond); let then_expr = self.lower_block_expr(then); if let Some(rslt) = else_opt { hir::ExprKind::If( @@ -574,7 +648,7 @@ impl<'hir> LoweringContext<'_, 'hir> { body: &Block, opt_label: Option