Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/hir-def/src/body/lower.rs')
-rw-r--r--crates/hir-def/src/body/lower.rs275
1 files changed, 199 insertions, 76 deletions
diff --git a/crates/hir-def/src/body/lower.rs b/crates/hir-def/src/body/lower.rs
index 5362737583..91eeff5b0e 100644
--- a/crates/hir-def/src/body/lower.rs
+++ b/crates/hir-def/src/body/lower.rs
@@ -102,9 +102,10 @@ pub(super) fn lower(
_c: Count::new(),
},
expander,
- current_try_block: None,
+ current_try_block_label: None,
is_lowering_assignee_expr: false,
is_lowering_generator: false,
+ label_ribs: Vec::new(),
}
.collect(params, body, is_async_fn)
}
@@ -116,9 +117,43 @@ struct ExprCollector<'a> {
body: Body,
krate: CrateId,
source_map: BodySourceMap,
- current_try_block: Option<LabelId>,
+ current_try_block_label: Option<LabelId>,
is_lowering_assignee_expr: bool,
is_lowering_generator: bool,
+ label_ribs: Vec<LabelRib>,
+}
+
+#[derive(Clone, Debug)]
+struct LabelRib {
+ kind: RibKind,
+ // Once we handle macro hygiene this will need to be a map
+ label: Option<(Name, LabelId)>,
+}
+
+impl LabelRib {
+ fn new(kind: RibKind) -> Self {
+ LabelRib { kind, label: None }
+ }
+ fn new_normal(label: (Name, LabelId)) -> Self {
+ LabelRib { kind: RibKind::Normal, label: Some(label) }
+ }
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+enum RibKind {
+ Normal,
+ Closure,
+ Constant,
+}
+
+impl RibKind {
+ /// This rib forbids referring to labels defined in upwards ribs.
+ fn is_label_barrier(self) -> bool {
+ match self {
+ RibKind::Normal => false,
+ RibKind::Closure | RibKind::Constant => true,
+ }
+ }
}
#[derive(Debug, Default)]
@@ -171,26 +206,24 @@ impl ExprCollector<'_> {
self.body.params.push(param_pat);
}
};
+ self.body.body_expr = self.with_label_rib(RibKind::Closure, |this| {
+ if is_async_fn {
+ match body {
+ Some(e) => {
+ let expr = this.collect_expr(e);
+ this.alloc_expr_desugared(Expr::Async {
+ id: None,
+ statements: Box::new([]),
+ tail: Some(expr),
+ })
+ }
+ None => this.missing_expr(),
+ }
+ } else {
+ this.collect_expr_opt(body)
+ }
+ });
- self.body.body_expr = if is_async_fn {
- self.current_try_block =
- Some(self.alloc_label_desugared(Label { name: Name::generate_new_name() }));
- let expr = self.collect_expr_opt(body);
- let expr = self.alloc_expr_desugared(Expr::Block {
- id: None,
- statements: Box::new([]),
- tail: Some(expr),
- label: self.current_try_block,
- });
- let expr = self.alloc_expr_desugared(Expr::Async {
- id: None,
- statements: Box::new([]),
- tail: Some(expr),
- });
- expr
- } else {
- self.collect_expr_opt(body)
- };
(self.body, self.source_map)
}
@@ -264,6 +297,7 @@ impl ExprCollector<'_> {
let syntax_ptr = AstPtr::new(&expr);
self.check_cfg(&expr)?;
+ // FIXME: Move some of these arms out into separate methods for clarity
Some(match expr {
ast::Expr::IfExpr(e) => {
let then_branch = self.collect_block_opt(e.then_branch());
@@ -286,7 +320,7 @@ impl ExprCollector<'_> {
self.alloc_expr(Expr::Let { pat, expr }, syntax_ptr)
}
ast::Expr::BlockExpr(e) => match e.modifier() {
- Some(ast::BlockModifier::Try(_)) => self.collect_try_block(e),
+ Some(ast::BlockModifier::Try(_)) => self.desugar_try_block(e),
Some(ast::BlockModifier::Unsafe(_)) => {
self.collect_block_(e, |id, statements, tail| Expr::Unsafe {
id,
@@ -296,28 +330,43 @@ impl ExprCollector<'_> {
}
Some(ast::BlockModifier::Label(label)) => {
let label = self.collect_label(label);
- self.collect_block_(e, |id, statements, tail| Expr::Block {
- id,
- statements,
- tail,
- label: Some(label),
+ self.with_labeled_rib(label, |this| {
+ this.collect_block_(e, |id, statements, tail| Expr::Block {
+ id,
+ statements,
+ tail,
+ label: Some(label),
+ })
+ })
+ }
+ Some(ast::BlockModifier::Async(_)) => {
+ self.with_label_rib(RibKind::Closure, |this| {
+ this.collect_block_(e, |id, statements, tail| Expr::Async {
+ id,
+ statements,
+ tail,
+ })
+ })
+ }
+ Some(ast::BlockModifier::Const(_)) => {
+ self.with_label_rib(RibKind::Constant, |this| {
+ this.collect_block_(e, |id, statements, tail| Expr::Const {
+ id,
+ statements,
+ tail,
+ })
})
}
- Some(ast::BlockModifier::Async(_)) => self
- .collect_block_(e, |id, statements, tail| Expr::Async { id, statements, tail }),
- Some(ast::BlockModifier::Const(_)) => self
- .collect_block_(e, |id, statements, tail| Expr::Const { id, statements, tail }),
None => self.collect_block(e),
},
ast::Expr::LoopExpr(e) => {
let label = e.label().map(|label| self.collect_label(label));
- let body = self.collect_block_opt(e.loop_body());
+ let body = self.collect_labelled_block_opt(label, e.loop_body());
self.alloc_expr(Expr::Loop { body, label }, syntax_ptr)
}
ast::Expr::WhileExpr(e) => {
let label = e.label().map(|label| self.collect_label(label));
- let body = self.collect_block_opt(e.loop_body());
-
+ let body = self.collect_labelled_block_opt(label, e.loop_body());
let condition = self.collect_expr_opt(e.condition());
self.alloc_expr(Expr::While { condition, body, label }, syntax_ptr)
@@ -326,7 +375,7 @@ impl ExprCollector<'_> {
let label = e.label().map(|label| self.collect_label(label));
let iterable = self.collect_expr_opt(e.iterable());
let pat = self.collect_pat_opt(e.pat());
- let body = self.collect_block_opt(e.loop_body());
+ let body = self.collect_labelled_block_opt(label, e.loop_body());
self.alloc_expr(Expr::For { iterable, pat, body, label }, syntax_ptr)
}
ast::Expr::CallExpr(e) => {
@@ -386,16 +435,20 @@ impl ExprCollector<'_> {
.unwrap_or(Expr::Missing);
self.alloc_expr(path, syntax_ptr)
}
- ast::Expr::ContinueExpr(e) => self.alloc_expr(
- Expr::Continue { label: e.lifetime().map(|l| Name::new_lifetime(&l)) },
- syntax_ptr,
- ),
+ ast::Expr::ContinueExpr(e) => {
+ let label = self.resolve_label(e.lifetime()).unwrap_or_else(|e| {
+ self.source_map.diagnostics.push(e);
+ None
+ });
+ self.alloc_expr(Expr::Continue { label }, syntax_ptr)
+ }
ast::Expr::BreakExpr(e) => {
+ let label = self.resolve_label(e.lifetime()).unwrap_or_else(|e| {
+ self.source_map.diagnostics.push(e);
+ None
+ });
let expr = e.expr().map(|e| self.collect_expr(e));
- self.alloc_expr(
- Expr::Break { expr, label: e.lifetime().map(|l| Name::new_lifetime(&l)) },
- syntax_ptr,
- )
+ self.alloc_expr(Expr::Break { expr, label }, syntax_ptr)
}
ast::Expr::ParenExpr(e) => {
let inner = self.collect_expr_opt(e.expr());
@@ -496,14 +549,14 @@ impl ExprCollector<'_> {
None => self.alloc_expr(Expr::Missing, syntax_ptr),
}
}
- ast::Expr::ClosureExpr(e) => {
+ ast::Expr::ClosureExpr(e) => self.with_label_rib(RibKind::Closure, |this| {
let mut args = Vec::new();
let mut arg_types = Vec::new();
if let Some(pl) = e.param_list() {
for param in pl.params() {
- let pat = self.collect_pat_opt(param.pat());
+ let pat = this.collect_pat_opt(param.pat());
let type_ref =
- param.ty().map(|it| Interned::new(TypeRef::from_ast(&self.ctx(), it)));
+ param.ty().map(|it| Interned::new(TypeRef::from_ast(&this.ctx(), it)));
args.push(pat);
arg_types.push(type_ref);
}
@@ -511,14 +564,14 @@ impl ExprCollector<'_> {
let ret_type = e
.ret_type()
.and_then(|r| r.ty())
- .map(|it| Interned::new(TypeRef::from_ast(&self.ctx(), it)));
+ .map(|it| Interned::new(TypeRef::from_ast(&this.ctx(), it)));
- let prev_is_lowering_generator = self.is_lowering_generator;
- self.is_lowering_generator = false;
+ let prev_is_lowering_generator = this.is_lowering_generator;
+ this.is_lowering_generator = false;
- let body = self.collect_expr_opt(e.body());
+ let body = this.collect_expr_opt(e.body());
- let closure_kind = if self.is_lowering_generator {
+ let closure_kind = if this.is_lowering_generator {
let movability = if e.static_token().is_some() {
Movability::Static
} else {
@@ -530,9 +583,9 @@ impl ExprCollector<'_> {
} else {
ClosureKind::Closure
};
- self.is_lowering_generator = prev_is_lowering_generator;
+ this.is_lowering_generator = prev_is_lowering_generator;
- self.alloc_expr(
+ this.alloc_expr(
Expr::Closure {
args: args.into(),
arg_types: arg_types.into(),
@@ -542,7 +595,7 @@ impl ExprCollector<'_> {
},
syntax_ptr,
)
- }
+ }),
ast::Expr::BinExpr(e) => {
let op = e.op_kind();
if let Some(ast::BinaryOp::Assignment { op: None }) = op {
@@ -581,7 +634,9 @@ impl ExprCollector<'_> {
}
ArrayExprKind::Repeat { initializer, repeat } => {
let initializer = self.collect_expr_opt(initializer);
- let repeat = self.collect_expr_opt(repeat);
+ let repeat = self.with_label_rib(RibKind::Constant, |this| {
+ this.collect_expr_opt(repeat)
+ });
self.alloc_expr(
Expr::Array(Array::Repeat { initializer, repeat }),
syntax_ptr,
@@ -630,20 +685,24 @@ impl ExprCollector<'_> {
/// Desugar `try { <stmts>; <expr> }` into `'<new_label>: { <stmts>; ::std::ops::Try::from_output(<expr>) }`,
/// `try { <stmts>; }` into `'<new_label>: { <stmts>; ::std::ops::Try::from_output(()) }`
/// and save the `<new_label>` to use it as a break target for desugaring of the `?` operator.
- fn collect_try_block(&mut self, e: BlockExpr) -> ExprId {
+ fn desugar_try_block(&mut self, e: BlockExpr) -> ExprId {
let Some(try_from_output) = LangItem::TryTraitFromOutput.path(self.db, self.krate) else {
- return self.alloc_expr_desugared(Expr::Missing);
+ return self.collect_block(e);
};
- let prev_try_block = self.current_try_block.take();
- self.current_try_block =
- Some(self.alloc_label_desugared(Label { name: Name::generate_new_name() }));
- let expr_id = self.collect_block(e);
+ let label = self.alloc_label_desugared(Label { name: Name::generate_new_name() });
+ let old_label = self.current_try_block_label.replace(label);
+
+ let (btail, expr_id) = self.with_labeled_rib(label, |this| {
+ let mut btail = None;
+ let block = this.collect_block_(e, |id, statements, tail| {
+ btail = tail;
+ Expr::Block { id, statements, tail, label: Some(label) }
+ });
+ (btail, block)
+ });
+
let callee = self.alloc_expr_desugared(Expr::Path(try_from_output));
- let Expr::Block { label, tail, .. } = &mut self.body.exprs[expr_id] else {
- unreachable!("It is the output of collect block");
- };
- *label = self.current_try_block;
- let next_tail = match *tail {
+ let next_tail = match btail {
Some(tail) => self.alloc_expr_desugared(Expr::Call {
callee,
args: Box::new([tail]),
@@ -662,10 +721,10 @@ impl ExprCollector<'_> {
}
};
let Expr::Block { tail, .. } = &mut self.body.exprs[expr_id] else {
- unreachable!("It is the output of collect block");
+ unreachable!("block was lowered to non-block");
};
*tail = Some(next_tail);
- self.current_try_block = prev_try_block;
+ self.current_try_block_label = old_label;
expr_id
}
@@ -735,12 +794,13 @@ impl ExprCollector<'_> {
Expr::Call { callee, args: Box::new([x]), is_assignee_expr: false },
syntax_ptr.clone(),
);
- if let Some(label) = self.current_try_block {
- let label = Some(self.body.labels[label].name.clone());
- self.alloc_expr(Expr::Break { expr: Some(result), label }, syntax_ptr.clone())
- } else {
- self.alloc_expr(Expr::Return { expr: Some(result) }, syntax_ptr.clone())
- }
+ self.alloc_expr(
+ match self.current_try_block_label {
+ Some(label) => Expr::Break { expr: Some(result), label: Some(label) },
+ None => Expr::Return { expr: Some(result) },
+ },
+ syntax_ptr.clone(),
+ )
},
};
let arms = Box::new([continue_arm, break_arm]);
@@ -966,6 +1026,17 @@ impl ExprCollector<'_> {
}
}
+ fn collect_labelled_block_opt(
+ &mut self,
+ label: Option<LabelId>,
+ expr: Option<ast::BlockExpr>,
+ ) -> ExprId {
+ match label {
+ Some(label) => self.with_labeled_rib(label, |this| this.collect_block_opt(expr)),
+ None => self.collect_block_opt(expr),
+ }
+ }
+
fn collect_label(&mut self, ast_label: ast::Label) -> LabelId {
let label = Label {
name: ast_label.lifetime().as_ref().map_or_else(Name::missing, Name::new_lifetime),
@@ -1135,8 +1206,9 @@ impl ExprCollector<'_> {
Pat::Box { inner }
}
ast::Pat::ConstBlockPat(const_block_pat) => {
- if let Some(expr) = const_block_pat.block_expr() {
- let expr_id = self.collect_block(expr);
+ if let Some(block) = const_block_pat.block_expr() {
+ let expr_id =
+ self.with_label_rib(RibKind::Constant, |this| this.collect_block(block));
Pat::ConstBlock(expr_id)
} else {
Pat::Missing
@@ -1213,6 +1285,57 @@ impl ExprCollector<'_> {
fn add_definition_to_binding(&mut self, binding_id: BindingId, pat_id: PatId) {
self.body.bindings[binding_id].definitions.push(pat_id);
}
+
+ fn resolve_label(
+ &self,
+ lifetime: Option<ast::Lifetime>,
+ ) -> Result<Option<LabelId>, BodyDiagnostic> {
+ let Some(lifetime) = lifetime else {
+ return Ok(None)
+ };
+ let name = Name::new_lifetime(&lifetime);
+
+ for (rib_idx, rib) in self.label_ribs.iter().enumerate().rev() {
+ if let Some((label_name, id)) = &rib.label {
+ if *label_name == name {
+ return if self.is_label_valid_from_rib(rib_idx) {
+ Ok(Some(*id))
+ } else {
+ Err(BodyDiagnostic::UnreachableLabel {
+ name,
+ node: InFile::new(
+ self.expander.current_file_id,
+ AstPtr::new(&lifetime),
+ ),
+ })
+ };
+ }
+ }
+ }
+
+ Err(BodyDiagnostic::UndeclaredLabel {
+ name,
+ node: InFile::new(self.expander.current_file_id, AstPtr::new(&lifetime)),
+ })
+ }
+
+ fn is_label_valid_from_rib(&self, rib_index: usize) -> bool {
+ !self.label_ribs[rib_index + 1..].iter().any(|rib| rib.kind.is_label_barrier())
+ }
+
+ fn with_label_rib<T>(&mut self, kind: RibKind, f: impl FnOnce(&mut Self) -> T) -> T {
+ self.label_ribs.push(LabelRib::new(kind));
+ let res = f(self);
+ self.label_ribs.pop();
+ res
+ }
+
+ fn with_labeled_rib<T>(&mut self, label: LabelId, f: impl FnOnce(&mut Self) -> T) -> T {
+ self.label_ribs.push(LabelRib::new_normal((self.body[label].name.clone(), label)));
+ let res = f(self);
+ self.label_ribs.pop();
+ res
+ }
}
impl From<ast::LiteralKind> for Literal {