Skip to content

Commit 9c5a407

Browse files
committed
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.
1 parent ebd8557 commit 9c5a407

File tree

12 files changed

+132
-1
lines changed

12 files changed

+132
-1
lines changed

compiler/rustc_ast/src/ast.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1557,6 +1557,9 @@ impl Expr {
15571557
// need parens sometimes. E.g. we can print `(let _ = a) && b` as `let _ = a && b`
15581558
// but we need to print `(let _ = a) < b` as-is with parens.
15591559
| ExprKind::Let(..)
1560+
// FIXME(is): `expr is pat` should be binop-like, but until it can be parsed as such,
1561+
// it's using prefix placeholder syntax.
1562+
| ExprKind::Is(..)
15601563
| ExprKind::Unary(..) => ExprPrecedence::Prefix,
15611564

15621565
// Need parens if and only if there are prefix attributes.
@@ -1734,6 +1737,11 @@ pub enum ExprKind {
17341737
///
17351738
/// `Span` represents the whole `let pat = expr` statement.
17361739
Let(P<Pat>, P<Expr>, Span, Recovered),
1740+
/// An `expr is pat` expression. Currently, there's no `is` keyword, so as a placeholder it's
1741+
/// parsed as `builtin # is(expr is pat)`.
1742+
///
1743+
/// This is desugared to `if` and `let` expressions.
1744+
Is(P<Expr>, P<Pat>),
17371745
/// An `if` block, with an optional `else` block.
17381746
///
17391747
/// `if expr { block } else { expr }`

compiler/rustc_ast/src/util/classify.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ pub fn expr_requires_semi_to_be_stmt(e: &ast::Expr) -> bool {
9999
/// }
100100
/// ```
101101
pub fn leading_labeled_expr(mut expr: &ast::Expr) -> bool {
102+
// FIXME(is): This will need tests for `is` once it can be parsed.
102103
loop {
103104
match &expr.kind {
104105
Block(_, label) | ForLoop { label, .. } | Loop(_, label, _) | While(_, _, label) => {
@@ -110,6 +111,7 @@ pub fn leading_labeled_expr(mut expr: &ast::Expr) -> bool {
110111
| Await(e, _)
111112
| Use(e, _)
112113
| Binary(_, e, _)
114+
| Is(e, _)
113115
| Call(e, _)
114116
| Cast(e, _)
115117
| Field(e, _)
@@ -171,6 +173,7 @@ pub enum TrailingBrace<'a> {
171173

172174
/// If an expression ends with `}`, returns the innermost expression ending in the `}`
173175
pub fn expr_trailing_brace(mut expr: &ast::Expr) -> Option<TrailingBrace<'_>> {
176+
// FIXME(is): We should have a test for `let b = expr is Variant {} else { return }`.
174177
loop {
175178
match &expr.kind {
176179
AddrOf(_, _, e)
@@ -237,6 +240,7 @@ pub fn expr_trailing_brace(mut expr: &ast::Expr) -> Option<TrailingBrace<'_>> {
237240
| Paren(_)
238241
| Try(_)
239242
| Yeet(None)
243+
| Is(..)
240244
| UnsafeBinderCast(..)
241245
| Err(_)
242246
| Dummy => {

compiler/rustc_ast/src/visit.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1705,6 +1705,10 @@ macro_rules! common_visitor_and_walkers {
17051705
try_visit!(vis.visit_expr(expr));
17061706
try_visit!(visit_span(vis, span))
17071707
}
1708+
ExprKind::Is(expr, pat) => {
1709+
try_visit!(vis.visit_expr(expr));
1710+
try_visit!(vis.visit_pat(pat));
1711+
}
17081712
ExprKind::If(head_expression, if_block, optional_else) => {
17091713
try_visit!(vis.visit_expr(head_expression));
17101714
try_visit!(vis.visit_block(if_block));

compiler/rustc_ast_lowering/src/expr.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,16 @@ impl<'hir> LoweringContext<'_, 'hir> {
176176
recovered: *recovered,
177177
}))
178178
}
179+
ExprKind::Is(scrutinee, pat) => {
180+
// TODO: properly desugar `is` outside of `if`/`while`
181+
hir::ExprKind::Let(self.arena.alloc(hir::LetExpr {
182+
span: self.lower_span(e.span),
183+
pat: self.lower_pat(pat),
184+
ty: None,
185+
init: self.lower_expr(scrutinee),
186+
recovered: Recovered::No,
187+
}))
188+
}
179189
ExprKind::If(cond, then, else_opt) => {
180190
self.lower_expr_if(cond, then, else_opt.as_deref())
181191
}

compiler/rustc_ast_pretty/src/pprust/state/expr.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,13 @@ impl<'a> State<'a> {
503503
ast::ExprKind::Let(pat, scrutinee, _, _) => {
504504
self.print_let(pat, scrutinee, fixup);
505505
}
506+
ast::ExprKind::Is(scrutinee, pat) => {
507+
// FIXME(is): This is a placeholder. Pretty-print properly once `is` can be parsed.
508+
self.print_expr(scrutinee, fixup);
509+
self.space();
510+
self.word_space("is");
511+
self.print_pat(pat);
512+
}
506513
ast::ExprKind::If(test, blk, elseopt) => self.print_if(test, blk, elseopt.as_deref()),
507514
ast::ExprKind::While(test, blk, opt_label) => {
508515
if let Some(label) = opt_label {

compiler/rustc_builtin_macros/src/assert/context.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,9 @@ impl<'cx, 'a> Context<'cx, 'a> {
243243
ExprKind::Let(_, local_expr, _, _) => {
244244
self.manage_cond_expr(local_expr);
245245
}
246+
ExprKind::Is(local_expr, _) => {
247+
self.manage_cond_expr(local_expr);
248+
}
246249
ExprKind::Match(local_expr, ..) => {
247250
self.manage_cond_expr(local_expr);
248251
}

compiler/rustc_parse/src/parser/expr.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1962,6 +1962,7 @@ impl<'a> Parser<'a> {
19621962
sym::unwrap_binder => {
19631963
Some(this.parse_expr_unsafe_binder_cast(lo, UnsafeBinderCastKind::Unwrap)?)
19641964
}
1965+
sym::is => Some(this.parse_expr_is(lo)?),
19651966
_ => None,
19661967
})
19671968
})
@@ -2046,6 +2047,17 @@ impl<'a> Parser<'a> {
20462047
Ok(self.mk_expr(span, ExprKind::UnsafeBinderCast(kind, expr, ty)))
20472048
}
20482049

2050+
/// Parse placeholder built-in syntax for `is` (rfcs#3573)
2051+
fn parse_expr_is(&mut self, lo: Span) -> PResult<'a, P<Expr>> {
2052+
let scrutinee = self.parse_expr()?;
2053+
self.expect_keyword(exp!(Is))?;
2054+
// Likely this will need to be `parse_pat_no_top_alt` for the final syntax.
2055+
// FIXME(is): If so, we'll want a `PatternLocation` variant for `is` for diagnostics.
2056+
let pat = self.parse_pat_no_top_alt(None, None)?;
2057+
let span = lo.to(self.token.span);
2058+
Ok(self.mk_expr(span, ExprKind::Is(scrutinee, pat)))
2059+
}
2060+
20492061
/// Returns a string literal if the next token is a string literal.
20502062
/// In case of error returns `Some(lit)` if the next token is a literal with a wrong kind,
20512063
/// and returns `None` if the next token is not literal at all.
@@ -3436,6 +3448,10 @@ impl<'a> Parser<'a> {
34363448
fn parse_match_arm_guard(&mut self) -> PResult<'a, Option<P<Expr>>> {
34373449
// Used to check the `if_let_guard` feature mostly by scanning
34383450
// `&&` tokens.
3451+
// FIXME(is): This can't catch `is` expressions. In order to implement placeholder
3452+
// macro-based syntax for `is`, `is` within a macro expansion is treated as part of whatever
3453+
// condition it expands into. As such, gating `is` in match guards would need to be part of
3454+
// the `feature_gate` AST pass.
34393455
fn has_let_expr(expr: &Expr) -> bool {
34403456
match &expr.kind {
34413457
ExprKind::Binary(BinOp { node: BinOpKind::And, .. }, lhs, rhs) => {
@@ -4123,6 +4139,16 @@ impl MutVisitor for CondChecker<'_> {
41234139
}
41244140
}
41254141
}
4142+
ExprKind::Is(_, _) => {
4143+
// FIXME(is): Handle edition-dependent rules for `is` in `&&`-chains. Currently,
4144+
// `is` desugars to `let` where `let`-chains are permitted, and otherwise desugars
4145+
// to `if let`. In the latter case, we could change the desugaring to rescope its
4146+
// temporaries to work on all Editions. For `is` inside an existing `if`
4147+
// expression's condition, however, temporaries in the condition live too long to
4148+
// permit all `&&` chains in Editions ≤ 2021. Since `is` will be a new keyword, it
4149+
// may only be possible for this to arise through the use of mixed-edition macro
4150+
// expansion or raw keywords (rfcs#3098).
4151+
}
41264152
ExprKind::Binary(Spanned { node: BinOpKind::And, .. }, _, _) => {
41274153
mut_visit::walk_expr(self, e);
41284154
}

compiler/rustc_passes/src/input_stats.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -655,7 +655,7 @@ impl<'v> ast_visit::Visitor<'v> for StatCollector<'v> {
655655
record_variants!(
656656
(self, e, e.kind, None, ast, Expr, ExprKind),
657657
[
658-
Array, ConstBlock, Call, MethodCall, Tup, Binary, Unary, Lit, Cast, Type, Let,
658+
Array, ConstBlock, Call, MethodCall, Tup, Binary, Unary, Lit, Cast, Type, Let, Is,
659659
If, While, ForLoop, Loop, Match, Closure, Block, Await, Use, TryBlock, Assign,
660660
AssignOp, Field, Index, Range, Underscore, Path, AddrOf, Break, Continue, Ret,
661661
InlineAsm, FormatArgs, OffsetOf, MacCall, Struct, Repeat, Paren, Try, Yield, Yeet,

compiler/rustc_resolve/src/late.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ struct BindingInfo {
6161
pub(crate) enum PatternSource {
6262
Match,
6363
Let,
64+
Is,
6465
For,
6566
FnParam,
6667
}
@@ -88,6 +89,7 @@ impl PatternSource {
8889
match self {
8990
PatternSource::Match => "match binding",
9091
PatternSource::Let => "let binding",
92+
PatternSource::Is => "is binding",
9193
PatternSource::For => "for binding",
9294
PatternSource::FnParam => "function parameter",
9395
}
@@ -4844,6 +4846,13 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
48444846
self.apply_pattern_bindings(bindings);
48454847
}
48464848

4849+
ExprKind::Is(ref scrutinee, ref pat) => {
4850+
// TODO: handle `is` outside of `if`/`while`: we'll need to introduce a new rib for
4851+
// the `is`'s `&&`-chain.
4852+
self.visit_expr(scrutinee);
4853+
self.resolve_pattern_top(pat, PatternSource::Is);
4854+
}
4855+
48474856
ExprKind::If(ref cond, ref then, ref opt_else) => {
48484857
self.with_rib(ValueNS, RibKind::Normal, |this| {
48494858
let old = this.diag_metadata.in_if_condition.replace(cond);

src/tools/clippy/clippy_utils/src/sugg.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,8 @@ impl<'a> Sugg<'a> {
184184
| ast::ExprKind::Closure { .. }
185185
| ast::ExprKind::If(..)
186186
| ast::ExprKind::Let(..)
187+
// FIXME(is): Once `is` can be parsed, it should provide `BinOp` suggestions.
188+
| ast::ExprKind::Is(..)
187189
| ast::ExprKind::Unary(..)
188190
| ast::ExprKind::Match(..) => match snippet_with_context(cx, expr.span, ctxt, default, app) {
189191
(snip, false) => Sugg::MaybeParen(snip),

0 commit comments

Comments
 (0)