Skip to content

minimally implement is (RFC 3573), sans parsing #144174

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions compiler/rustc_ast/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -1739,6 +1755,11 @@ pub enum ExprKind {
///
/// `Span` represents the whole `let pat = expr` statement.
Let(P<Pat>, P<Expr>, 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<Expr>, P<Pat>),
/// An `if` block, with an optional `else` block.
///
/// `if expr { block } else { expr }`
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_ast/src/util/classify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand All @@ -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, _)
Expand Down Expand Up @@ -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<TrailingBrace<'_>> {
// FIXME(is): We should have a test for `let b = expr is Variant {} else { return }`.
loop {
match &expr.kind {
AddrOf(_, _, e)
Expand Down Expand Up @@ -237,6 +240,7 @@ pub fn expr_trailing_brace(mut expr: &ast::Expr) -> Option<TrailingBrace<'_>> {
| Paren(_)
| Try(_)
| Yeet(None)
| Is(..)
| UnsafeBinderCast(..)
| Err(_)
| Dummy => {
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_ast/src/visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) =>
Expand Down
92 changes: 88 additions & 4 deletions compiler/rustc_ast_lowering/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
_ => (),
}

Expand Down Expand Up @@ -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")
}

Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -564,7 +648,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
body: &Block,
opt_label: Option<Label>,
) -> hir::ExprKind<'hir> {
let lowered_cond = self.with_loop_condition_scope(|t| t.lower_expr(cond));
let lowered_cond = self.with_loop_condition_scope(|t| t.lower_cond(cond));
let then = self.lower_block_expr(body);
let expr_break = self.expr_break(span);
let stmt_break = self.stmt_expr(span, expr_break);
Expand Down Expand Up @@ -633,7 +717,7 @@ impl<'hir> LoweringContext<'_, 'hir> {

fn lower_arm(&mut self, arm: &Arm) -> hir::Arm<'hir> {
let pat = self.lower_pat(&arm.pat);
let guard = arm.guard.as_ref().map(|cond| self.lower_expr(cond));
let guard = arm.guard.as_ref().map(|cond| self.lower_cond(cond));
let hir_id = self.next_id();
let span = self.lower_span(arm.span);
self.lower_attrs(hir_id, &arm.attrs, arm.span);
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_ast_lowering/src/pat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
);
}
PatKind::Guard(inner, cond) => {
// FIXME(is, guard_patterns): use `self.lower_cond` to support `is` bindings
break hir::PatKind::Guard(self.lower_pat(inner), self.lower_expr(cond));
}
PatKind::Slice(pats) => break self.lower_pat_slice(pats),
Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_ast_pretty/src/pprust/state/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_builtin_macros/src/assert/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
26 changes: 26 additions & 0 deletions compiler/rustc_parse/src/parser/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})
})
Expand Down Expand Up @@ -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<Expr>> {
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.
Expand Down Expand Up @@ -3436,6 +3448,10 @@ impl<'a> Parser<'a> {
fn parse_match_arm_guard(&mut self) -> PResult<'a, Option<P<Expr>>> {
// 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) => {
Expand Down Expand Up @@ -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);
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_passes/src/input_stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading
Loading