diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 97e070958751a..af984dd9a7011 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. @@ -1618,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. @@ -1739,6 +1755,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..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); + } + } _ => (), } @@ -369,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") } @@ -522,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( @@ -564,7 +648,7 @@ impl<'hir> LoweringContext<'_, 'hir> { body: &Block, opt_label: Option