Skip to content

Commit 4b40440

Browse files
committed
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 }`.
1 parent d3bc7d2 commit 4b40440

File tree

11 files changed

+379
-36
lines changed

11 files changed

+379
-36
lines changed

compiler/rustc_ast/src/ast.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1616,6 +1616,19 @@ impl Expr {
16161616
)
16171617
}
16181618

1619+
/// Is this an `is` expression or a chain of `&&` operators containing an `is` expression?
1620+
/// This determines the scope of the `is` expression's bindings. E.g., since
1621+
/// `(expr is x) && f()` is broken up by parentheses, `x` is dropped before evaluating `f()`.
1622+
pub fn has_expr_is(&self) -> bool {
1623+
match &self.kind {
1624+
ExprKind::Is(..) => true,
1625+
ExprKind::Binary(BinOp { node: BinOpKind::And, .. }, lhs, rhs) => {
1626+
lhs.has_expr_is() || rhs.has_expr_is()
1627+
}
1628+
_ => false,
1629+
}
1630+
}
1631+
16191632
/// Creates a dummy `Expr`.
16201633
///
16211634
/// Should only be used when it will be replaced afterwards or as a return value when an error was encountered.

compiler/rustc_ast_lowering/src/expr.rs

Lines changed: 88 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,17 @@ impl<'hir> LoweringContext<'_, 'hir> {
9494
ExprKind::ForLoop { pat, iter, body, label, kind } => {
9595
return self.lower_expr_for(e, pat, iter, body, *label, *kind);
9696
}
97+
// Desugar `expr is pat` expressions. This likewise needs special handling as `is`
98+
// expressions outside of `if`/`while` conditions are wrapped in an `if` expression.
99+
ExprKind::Is(..) => return self.lower_wrapped_cond_for_expr_is(e),
100+
ExprKind::Binary(BinOp { node: BinOpKind::And, .. }, ..) => {
101+
if e.has_expr_is() {
102+
return self.lower_wrapped_cond_for_expr_is(e);
103+
} else {
104+
// `has_expr_is` will be false for this `&&`'s operands; don't check again.
105+
return self.lower_cond_mut(e);
106+
}
107+
}
97108
_ => (),
98109
}
99110

@@ -176,16 +187,6 @@ impl<'hir> LoweringContext<'_, 'hir> {
176187
recovered: *recovered,
177188
}))
178189
}
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-
}
189190
ExprKind::If(cond, then, else_opt) => {
190191
self.lower_expr_if(cond, then, else_opt.as_deref())
191192
}
@@ -379,7 +380,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
379380

380381
ExprKind::Try(sub_expr) => self.lower_expr_try(e.span, sub_expr),
381382

382-
ExprKind::Paren(_) | ExprKind::ForLoop { .. } => {
383+
ExprKind::Paren(_) | ExprKind::ForLoop { .. } | ExprKind::Is(..) => {
383384
unreachable!("already handled")
384385
}
385386

@@ -532,13 +533,86 @@ impl<'hir> LoweringContext<'_, 'hir> {
532533
hir::ExprKind::Call(f, self.lower_exprs(&real_args))
533534
}
534535

536+
/// Lower an expression in a context where `let` expressions are permitted, such as the
537+
/// condition of an `if` or `while` expression. This allows `is` expressions to be lowered
538+
/// directly to `let` expressions without needing to be wrapped in an `if`'s condition.
539+
fn lower_cond(&mut self, e: &Expr) -> &'hir hir::Expr<'hir> {
540+
self.arena.alloc(self.lower_cond_mut(e))
541+
}
542+
543+
fn lower_cond_mut(&mut self, e: &Expr) -> hir::Expr<'hir> {
544+
self.lower_cond_inner(e, None)
545+
}
546+
547+
fn lower_cond_inner(&mut self, e: &Expr, wrapper: Option<(HirId, Span)>) -> hir::Expr<'hir> {
548+
let lower_id_and_attrs = |this: &mut LoweringContext<'_, 'hir>| {
549+
let expr_hir_id = this.lower_node_id(e.id);
550+
// If the condition is wrapped in an `if` expression for `is` desugaring, attach
551+
// attributes to the `if` expression instead of `e`.
552+
let (attr_hir_id, attr_span) = wrapper.unwrap_or((expr_hir_id, e.span));
553+
this.lower_attrs(attr_hir_id, &e.attrs, attr_span);
554+
expr_hir_id
555+
};
556+
match &e.kind {
557+
ExprKind::Is(scrutinee, pat) => {
558+
// At the top level of a condition, `is` expressions lower directly to `let`.
559+
let hir_id = lower_id_and_attrs(self);
560+
let span = self.mark_span_with_reason(
561+
DesugaringKind::IsExpr,
562+
self.lower_span(e.span),
563+
None,
564+
);
565+
let kind = hir::ExprKind::Let(self.arena.alloc(hir::LetExpr {
566+
span,
567+
pat: self.lower_pat(pat),
568+
ty: None,
569+
init: self.lower_expr(scrutinee),
570+
recovered: Recovered::No,
571+
}));
572+
hir::Expr { hir_id, kind, span }
573+
}
574+
ExprKind::Binary(binop @ BinOp { node: BinOpKind::And, .. }, lhs, rhs) => {
575+
// `is` expressions in chains of `&&` operators can lower to `let`, so we lower the
576+
// operands using `self.lower_cond` rather than `self.lower_expr`.
577+
ensure_sufficient_stack(|| {
578+
let hir_id = lower_id_and_attrs(self);
579+
let binop = self.lower_binop(*binop);
580+
let lhs = self.lower_cond(lhs);
581+
let rhs = self.lower_cond(rhs);
582+
let kind = hir::ExprKind::Binary(binop, lhs, rhs);
583+
hir::Expr { hir_id, kind, span: self.lower_span(e.span) }
584+
})
585+
}
586+
_ => return self.lower_expr_mut(e),
587+
}
588+
}
589+
590+
/// Lower `cond`, wrapped in an `if` expression's condition: `if cond { true } else { false }`.
591+
/// This allows `is` expressions in `cond` to lower to `let` expressions.
592+
fn lower_wrapped_cond_for_expr_is(&mut self, cond: &Expr) -> hir::Expr<'hir> {
593+
let span =
594+
self.mark_span_with_reason(DesugaringKind::IsExpr, self.lower_span(cond.span), None);
595+
let hir_id = self.next_id();
596+
let lowered_cond = self.arena.alloc(self.lower_cond_inner(cond, Some((hir_id, span))));
597+
let mut bool_block = |b| {
598+
let lit = hir::Lit { span, node: ast::LitKind::Bool(b) };
599+
let expr_lit = self.arena.alloc(self.expr(span, hir::ExprKind::Lit(lit)));
600+
let block = self.block_expr(expr_lit);
601+
self.arena.alloc(self.expr_block(block))
602+
};
603+
let expr_true = bool_block(true);
604+
let expr_false = bool_block(false);
605+
let kind = hir::ExprKind::If(lowered_cond, expr_true, Some(expr_false));
606+
hir::Expr { span, kind, hir_id }
607+
}
608+
535609
fn lower_expr_if(
536610
&mut self,
537611
cond: &Expr,
538612
then: &Block,
539613
else_opt: Option<&Expr>,
540614
) -> hir::ExprKind<'hir> {
541-
let lowered_cond = self.lower_expr(cond);
615+
let lowered_cond = self.lower_cond(cond);
542616
let then_expr = self.lower_block_expr(then);
543617
if let Some(rslt) = else_opt {
544618
hir::ExprKind::If(
@@ -574,7 +648,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
574648
body: &Block,
575649
opt_label: Option<Label>,
576650
) -> hir::ExprKind<'hir> {
577-
let lowered_cond = self.with_loop_condition_scope(|t| t.lower_expr(cond));
651+
let lowered_cond = self.with_loop_condition_scope(|t| t.lower_cond(cond));
578652
let then = self.lower_block_expr(body);
579653
let expr_break = self.expr_break(span);
580654
let stmt_break = self.stmt_expr(span, expr_break);
@@ -643,7 +717,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
643717

644718
fn lower_arm(&mut self, arm: &Arm) -> hir::Arm<'hir> {
645719
let pat = self.lower_pat(&arm.pat);
646-
let guard = arm.guard.as_ref().map(|cond| self.lower_expr(cond));
720+
let guard = arm.guard.as_ref().map(|cond| self.lower_cond(cond));
647721
let hir_id = self.next_id();
648722
let span = self.lower_span(arm.span);
649723
self.lower_attrs(hir_id, &arm.attrs, arm.span);

compiler/rustc_ast_lowering/src/pat.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
134134
);
135135
}
136136
PatKind::Guard(inner, cond) => {
137+
// FIXME(is, guard_patterns): use `self.lower_cond` to support `is` bindings
137138
break hir::PatKind::Guard(self.lower_pat(inner), self.lower_expr(cond));
138139
}
139140
PatKind::Slice(pats) => break self.lower_pat_slice(pats),

compiler/rustc_resolve/src/late.rs

Lines changed: 67 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,18 @@ enum PatBoundCtx {
126126
/// Each identifier must map to at most one distinct [`Res`].
127127
type PatternBindings = SmallVec<[(PatBoundCtx, FxIndexMap<Ident, Res>); 1]>;
128128

129+
/// In what scope should `is` expressions introduce bindings? See RFC 3573 for more details.
130+
enum IsBindingScope {
131+
/// `is` expressions at the top-level of an `if` or `while` condition introduce their bindings
132+
/// within the condition's rib, so that they are in scope in the expression's success block.
133+
/// Likewise, within an `&&`-chain outside of an `if` or `while`, `is` expressions' bindings are
134+
/// in scope for the remainder of the `&&`-chain.
135+
InCurrentRib,
136+
/// Indicates that an `&&` or `is` expression will need to introduce a new rib for `is`
137+
/// expressions' bindings.
138+
InNewRib,
139+
}
140+
129141
/// Does this the item (from the item rib scope) allow generic parameters?
130142
#[derive(Copy, Clone, Debug)]
131143
pub(crate) enum HasGenericParams {
@@ -800,7 +812,7 @@ impl<'ast, 'ra, 'tcx> Visitor<'ast> for LateResolutionVisitor<'_, 'ast, 'ra, 'tc
800812
bug!("encountered anon const without a manual call to `resolve_anon_const`: {constant:#?}");
801813
}
802814
fn visit_expr(&mut self, expr: &'ast Expr) {
803-
self.resolve_expr(expr, None);
815+
self.resolve_expr(expr, None, IsBindingScope::InNewRib);
804816
}
805817
fn visit_pat(&mut self, p: &'ast Pat) {
806818
let prev = self.diag_metadata.current_pat;
@@ -3791,7 +3803,9 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
37913803
fn resolve_arm(&mut self, arm: &'ast Arm) {
37923804
self.with_rib(ValueNS, RibKind::Normal, |this| {
37933805
this.resolve_pattern_top(&arm.pat, PatternSource::Match);
3794-
visit_opt!(this, visit_expr, &arm.guard);
3806+
if let Some(guard) = &arm.guard {
3807+
this.resolve_expr(guard, None, IsBindingScope::InCurrentRib)
3808+
}
37953809
visit_opt!(this, visit_expr, &arm.body);
37963810
});
37973811
}
@@ -3933,7 +3947,9 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
39333947
let subpat_bindings = bindings.pop().unwrap().1;
39343948
self.with_rib(ValueNS, RibKind::Normal, |this| {
39353949
*this.innermost_rib_bindings(ValueNS) = subpat_bindings.clone();
3936-
this.resolve_expr(guard, None);
3950+
// FIXME(guard_patterns): For `is` guards, we'll want to call `resolve_expr`
3951+
// with `IsBindingScope::InCurrentRib`.
3952+
this.resolve_expr(guard, None, IsBindingScope::InNewRib);
39373953
});
39383954
// Propagate the subpattern's bindings upwards.
39393955
// FIXME(guard_patterns): For `if let` guards, we'll also need to get the
@@ -4723,7 +4739,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
47234739
.value
47244740
.is_potential_trivial_const_arg(self.r.tcx.features().min_generic_const_args());
47254741
self.resolve_anon_const_manual(is_trivial_const_arg, anon_const_kind, |this| {
4726-
this.resolve_expr(&constant.value, None)
4742+
this.visit_expr(&constant.value)
47274743
})
47284744
}
47294745

@@ -4767,12 +4783,17 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
47674783
}
47684784

47694785
fn resolve_expr_field(&mut self, f: &'ast ExprField, e: &'ast Expr) {
4770-
self.resolve_expr(&f.expr, Some(e));
4786+
self.resolve_expr(&f.expr, Some(e), IsBindingScope::InNewRib);
47714787
self.visit_ident(&f.ident);
47724788
walk_list!(self, visit_attribute, f.attrs.iter());
47734789
}
47744790

4775-
fn resolve_expr(&mut self, expr: &'ast Expr, parent: Option<&'ast Expr>) {
4791+
fn resolve_expr(
4792+
&mut self,
4793+
expr: &'ast Expr,
4794+
parent: Option<&'ast Expr>,
4795+
is_binding_scope: IsBindingScope,
4796+
) {
47764797
// First, record candidate traits for this expression if it could
47774798
// result in the invocation of a method call.
47784799

@@ -4821,7 +4842,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
48214842
ExprKind::Break(None, Some(ref e)) => {
48224843
// We use this instead of `visit::walk_expr` to keep the parent expr around for
48234844
// better diagnostics.
4824-
self.resolve_expr(e, Some(expr));
4845+
self.resolve_expr(e, Some(expr), IsBindingScope::InNewRib);
48254846
}
48264847

48274848
ExprKind::Let(ref pat, ref scrutinee, _, Recovered::No) => {
@@ -4847,16 +4868,21 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
48474868
}
48484869

48494870
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.
48524871
self.visit_expr(scrutinee);
4853-
self.resolve_pattern_top(pat, PatternSource::Is);
4872+
match is_binding_scope {
4873+
IsBindingScope::InCurrentRib => {
4874+
self.resolve_pattern_top(pat, PatternSource::Is)
4875+
}
4876+
IsBindingScope::InNewRib => self.with_rib(ValueNS, RibKind::Normal, |this| {
4877+
this.resolve_pattern_top(pat, PatternSource::Is)
4878+
}),
4879+
}
48544880
}
48554881

48564882
ExprKind::If(ref cond, ref then, ref opt_else) => {
48574883
self.with_rib(ValueNS, RibKind::Normal, |this| {
48584884
let old = this.diag_metadata.in_if_condition.replace(cond);
4859-
this.visit_expr(cond);
4885+
this.resolve_expr(cond, None, IsBindingScope::InCurrentRib);
48604886
this.diag_metadata.in_if_condition = old;
48614887
this.visit_block(then);
48624888
});
@@ -4873,7 +4899,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
48734899
self.with_resolved_label(label, expr.id, |this| {
48744900
this.with_rib(ValueNS, RibKind::Normal, |this| {
48754901
let old = this.diag_metadata.in_if_condition.replace(cond);
4876-
this.visit_expr(cond);
4902+
this.resolve_expr(cond, None, IsBindingScope::InCurrentRib);
48774903
this.diag_metadata.in_if_condition = old;
48784904
this.visit_block(block);
48794905
})
@@ -4888,22 +4914,42 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
48884914
});
48894915
}
48904916

4917+
ExprKind::Binary(BinOp { node: BinOpKind::And, .. }, ref lhs, ref rhs) => {
4918+
match is_binding_scope {
4919+
// At the top level of an `&&`-chain, we introduce a new rib to restrict `is`
4920+
// expressions' bindings' lexical scope to that `&&`-chain.
4921+
IsBindingScope::InNewRib if lhs.has_expr_is() || rhs.has_expr_is() => self
4922+
.with_rib(ValueNS, RibKind::Normal, |this| {
4923+
this.resolve_expr(lhs, None, IsBindingScope::InCurrentRib);
4924+
this.resolve_expr(rhs, None, IsBindingScope::InCurrentRib);
4925+
}),
4926+
// Inside an `&&`-chain, we use the `&&`-chain's rib.
4927+
// We also avoid introducing a new rib if the `&&`-chain has no `is`
4928+
// expressions, to reduce the number of diagnostics emitted for malformed
4929+
// `let` chains such as `(let x = true) && x`.
4930+
IsBindingScope::InCurrentRib | IsBindingScope::InNewRib => {
4931+
self.resolve_expr(lhs, None, IsBindingScope::InCurrentRib);
4932+
self.resolve_expr(rhs, None, IsBindingScope::InCurrentRib);
4933+
}
4934+
}
4935+
}
4936+
48914937
ExprKind::Block(ref block, label) => self.resolve_labeled_block(label, block.id, block),
48924938

48934939
// Equivalent to `visit::walk_expr` + passing some context to children.
48944940
ExprKind::Field(ref subexpression, _) => {
4895-
self.resolve_expr(subexpression, Some(expr));
4941+
self.resolve_expr(subexpression, Some(expr), IsBindingScope::InNewRib);
48964942
}
48974943
ExprKind::MethodCall(box MethodCall { ref seg, ref receiver, ref args, .. }) => {
4898-
self.resolve_expr(receiver, Some(expr));
4944+
self.resolve_expr(receiver, Some(expr), IsBindingScope::InNewRib);
48994945
for arg in args {
4900-
self.resolve_expr(arg, None);
4946+
self.resolve_expr(arg, None, IsBindingScope::InNewRib);
49014947
}
49024948
self.visit_path_segment(seg);
49034949
}
49044950

49054951
ExprKind::Call(ref callee, ref arguments) => {
4906-
self.resolve_expr(callee, Some(expr));
4952+
self.resolve_expr(callee, Some(expr), IsBindingScope::InNewRib);
49074953
let const_args = self.r.legacy_const_generic_args(callee).unwrap_or_default();
49084954
for (idx, argument) in arguments.iter().enumerate() {
49094955
// Constant arguments need to be treated as AnonConst since
@@ -4915,10 +4961,10 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
49154961
self.resolve_anon_const_manual(
49164962
is_trivial_const_arg,
49174963
AnonConstKind::ConstArg(IsRepeatExpr::No),
4918-
|this| this.resolve_expr(argument, None),
4964+
|this| this.visit_expr(argument),
49194965
);
49204966
} else {
4921-
self.resolve_expr(argument, None);
4967+
self.visit_expr(argument);
49224968
}
49234969
}
49244970
}
@@ -4951,7 +4997,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
49514997
self.resolve_anon_const(ct, AnonConstKind::InlineConst);
49524998
}
49534999
ExprKind::Index(ref elem, ref idx, _) => {
4954-
self.resolve_expr(elem, Some(expr));
5000+
self.resolve_expr(elem, Some(expr), IsBindingScope::InNewRib);
49555001
self.visit_expr(idx);
49565002
}
49575003
ExprKind::Assign(ref lhs, ref rhs, _) => {
@@ -4966,8 +5012,8 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
49665012
}
49675013
ExprKind::Range(Some(ref start), Some(ref end), RangeLimits::HalfOpen) => {
49685014
self.diag_metadata.in_range = Some((start, end));
4969-
self.resolve_expr(start, Some(expr));
4970-
self.resolve_expr(end, Some(expr));
5015+
self.resolve_expr(start, Some(expr), IsBindingScope::InNewRib);
5016+
self.resolve_expr(end, Some(expr), IsBindingScope::InNewRib);
49715017
self.diag_metadata.in_range = None;
49725018
}
49735019
_ => {

0 commit comments

Comments
 (0)