Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/hir-ty/src/mir/lower.rs')
| -rw-r--r-- | crates/hir-ty/src/mir/lower.rs | 271 |
1 files changed, 183 insertions, 88 deletions
diff --git a/crates/hir-ty/src/mir/lower.rs b/crates/hir-ty/src/mir/lower.rs index f1365e4df8..0f0ed729c9 100644 --- a/crates/hir-ty/src/mir/lower.rs +++ b/crates/hir-ty/src/mir/lower.rs @@ -8,8 +8,8 @@ use hir_def::{ HasModule, ItemContainerId, LocalFieldId, Lookup, TraitId, TupleId, expr_store::{Body, ExpressionStore, HygieneId, path::Path}, hir::{ - ArithOp, Array, BinaryOp, BindingAnnotation, BindingId, ClosureKind, ExprId, LabelId, - Literal, MatchArm, Pat, PatId, RecordFieldPat, RecordLitField, RecordSpread, + ArithOp, Array, BinaryOp, BindingAnnotation, BindingId, ClosureKind, ExprId, ExprOrPatId, + LabelId, Literal, MatchArm, Pat, PatId, RecordFieldPat, RecordLitField, RecordSpread, generics::GenericParams, }, item_tree::FieldsShape, @@ -18,10 +18,11 @@ use hir_def::{ signatures::{ConstSignature, EnumSignature, FunctionSignature, StaticSignature}, }; use hir_expand::name::Name; +use itertools::{EitherOrBoth, Itertools}; use la_arena::ArenaMap; use rustc_apfloat::Float; use rustc_hash::FxHashMap; -use rustc_type_ir::inherent::{Const as _, GenericArgs as _, IntoKind, Ty as _}; +use rustc_type_ir::inherent::{AdtDef, Const as _, GenericArgs as _, IntoKind, Ty as _}; use span::{Edition, FileId}; use syntax::TextRange; use triomphe::Arc; @@ -33,8 +34,11 @@ use crate::{ display::{DisplayTarget, HirDisplay, hir_display_with_store}, generics::generics, infer::{ - CaptureKind, CapturedItem, TypeMismatch, cast::CastTy, - closure::analysis::HirPlaceProjection, + CaptureSourceStack, CapturedPlace, TypeMismatch, UpvarCapture, + cast::CastTy, + closure::analysis::expr_use_visitor::{ + Place as HirPlace, PlaceBase as HirPlaceBase, ProjectionKind as HirProjectionKind, + }, }, inhabitedness::is_ty_uninhabited_from, layout::LayoutError, @@ -52,7 +56,6 @@ use crate::{ abi::Safety, infer::{DbInternerInferExt, InferCtxt}, }, - traits::FnTrait, }; use super::OperandKind; @@ -304,6 +307,7 @@ impl<'a, 'db> MirLowerCtx<'a, 'db> { locals, start_block, binding_locals, + upvar_locals: FxHashMap::default(), param_locals: vec![], owner, closures: vec![], @@ -440,7 +444,7 @@ impl<'a, 'db> MirLowerCtx<'a, 'db> { else { return Ok(None); }; - let bk = BorrowKind::from_rustc(m); + let bk = BorrowKind::from_rustc_mutability(m); self.push_assignment(current, place, Rvalue::Ref(bk, p), expr_id.into()); Ok(Some(current)) } @@ -996,7 +1000,7 @@ impl<'a, 'db> MirLowerCtx<'a, 'db> { let Some((p, current)) = self.lower_expr_as_place(current, *expr, true)? else { return Ok(None); }; - let bk = BorrowKind::from_hir(*mutability); + let bk = BorrowKind::from_hir_mutability(*mutability); self.push_assignment(current, place, Rvalue::Ref(bk, p), expr_id.into()); Ok(Some(current)) } @@ -1251,49 +1255,58 @@ impl<'a, 'db> MirLowerCtx<'a, 'db> { not_supported!("closure with non closure type"); }; self.result.closures.push(id.0); - let (captures, _) = self.infer.closure_info(id.0); - let mut operands = vec![]; - for capture in captures.iter() { - let p = Place { - local: self.binding_local(capture.place.local)?, - projection: self.result.projection_store.intern( - capture - .place - .projections - .clone() - .into_iter() - .map(|it| match it { - HirPlaceProjection::Deref => ProjectionElem::Deref, - HirPlaceProjection::Field(field_id) => { - ProjectionElem::Field(Either::Left(field_id)) - } - HirPlaceProjection::TupleField(idx) => { - ProjectionElem::Field(Either::Right(TupleFieldId { - tuple: TupleId(!0), // Dummy as it's unused - index: idx, - })) - } - }) - .collect(), - ), + let closure_data = &self.infer.closures_data[&id.0.loc(self.db).1]; + + let span = |sources: &[CaptureSourceStack]| match sources + .first() + .map(|it| it.final_source()) + { + Some(ExprOrPatId::ExprId(it)) => it.into(), + Some(ExprOrPatId::PatId(it)) => it.into(), + None => MirSpan::Unknown, + }; + let convert_place = |this: &mut Self, place: &HirPlace| { + let (HirPlaceBase::Local(local) | HirPlaceBase::Upvar { var_id: local, .. }) = + place.base + else { + not_supported!("non-local capture"); }; - match &capture.kind { - CaptureKind::ByRef(bk) => { - let tmp_ty = capture.ty.get().instantiate_identity(); + Ok(Place { + local: this.binding_local(local)?, + projection: this + .result + .projection_store + .intern(convert_closure_capture_projections(self.db, place).collect()), + }) + }; + + for (place, _, sources) in &closure_data.fake_reads { + let p = convert_place(self, place)?; + self.push_fake_read(current, p, span(sources)); + } + + let captures = closure_data.min_captures.values().flatten(); + let mut operands = vec![]; + for capture in captures { + let p = convert_place(self, &capture.place)?; + match capture.info.capture_kind { + UpvarCapture::ByRef(bk) => { + let tmp_ty = capture.captured_ty(self.db); // FIXME: Handle more than one span. - let capture_spans = capture.spans(); - let tmp: Place = self.temp(tmp_ty, current, capture_spans[0])?.into(); + let capture_span = span(&capture.info.sources); + let tmp: Place = self.temp(tmp_ty, current, capture_span)?.into(); self.push_assignment( current, tmp, - Rvalue::Ref(*bk, p), - capture_spans[0], + Rvalue::Ref(BorrowKind::from_hir(bk), p), + capture_span, ); operands.push(Operand { kind: OperandKind::Move(tmp), span: None }); } - CaptureKind::ByValue => { + UpvarCapture::ByValue => { operands.push(Operand { kind: OperandKind::Move(p), span: None }) } + UpvarCapture::ByUse => not_supported!("capture by use"), } } self.push_assignment( @@ -2068,6 +2081,44 @@ impl<'a, 'db> MirLowerCtx<'a, 'db> { } } +fn convert_closure_capture_projections( + db: &dyn HirDatabase, + place: &HirPlace, +) -> impl Iterator<Item = PlaceElem> { + place.projections.iter().enumerate().map(|(i, proj)| match proj.kind { + HirProjectionKind::Deref => ProjectionElem::Deref, + HirProjectionKind::Field { field_idx, variant_idx } => { + let ty = place.ty_before_projection(i); + match ty.kind() { + TyKind::Tuple(_) => { + ProjectionElem::Field(Either::Right(TupleFieldId { + tuple: TupleId(!0), // Dummy as it's unused + index: field_idx, + })) + } + TyKind::Adt(adt_def, _) => { + let local_field_id = LocalFieldId::from_raw(RawIdx::from_u32(field_idx)); + let field = match adt_def.def_id().0 { + AdtId::StructId(id) => { + FieldId { parent: id.into(), local_id: local_field_id } + } + AdtId::UnionId(id) => { + FieldId { parent: id.into(), local_id: local_field_id } + } + AdtId::EnumId(id) => { + let variant = id.enum_variants(db).variants[variant_idx as usize].0; + FieldId { parent: variant.into(), local_id: local_field_id } + } + }; + ProjectionElem::Field(Either::Left(field)) + } + _ => panic!("unexpected type"), + } + } + _ => panic!("unexpected projection"), + }) +} + fn cast_kind<'db>( db: &'db dyn HirDatabase, source_ty: Ty<'db>, @@ -2105,20 +2156,22 @@ pub fn mir_body_for_closure_query<'db>( let crate::next_solver::TyKind::Closure(_, substs) = infer.expr_ty(expr).kind() else { implementation_error!("closure expression is not closure"); }; - let (captures, kind) = infer.closure_info(closure); + let kind = substs.as_closure().kind(); + let captures = infer.closures_data[&expr].min_captures.values().flatten(); let mut ctx = MirLowerCtx::new(db, body_owner, &body.store, infer); + // 0 is return local ctx.result.locals.alloc(Local { ty: infer.expr_ty(*root).store() }); let closure_local = ctx.result.locals.alloc(Local { ty: match kind { - FnTrait::FnOnce | FnTrait::AsyncFnOnce => infer.expr_ty(expr), - FnTrait::FnMut | FnTrait::AsyncFnMut => Ty::new_ref( + rustc_type_ir::ClosureKind::FnOnce => infer.expr_ty(expr), + rustc_type_ir::ClosureKind::FnMut => Ty::new_ref( ctx.interner(), Region::error(ctx.interner()), infer.expr_ty(expr), Mutability::Mut, ), - FnTrait::Fn | FnTrait::AsyncFn => Ty::new_ref( + rustc_type_ir::ClosureKind::Fn => Ty::new_ref( ctx.interner(), Region::error(ctx.interner()), infer.expr_ty(expr), @@ -2128,6 +2181,7 @@ pub fn mir_body_for_closure_query<'db>( .store(), }); ctx.result.param_locals.push(closure_local); + let sig = ctx.interner().signature_unclosure(substs.as_closure().sig(), Safety::Safe); let resolver_guard = ctx.resolver.update_to_inner_scope(db, body_owner, expr); let current = ctx.lower_params_and_bindings( @@ -2135,60 +2189,101 @@ pub fn mir_body_for_closure_query<'db>( None, |_| true, )?; + + // Push local for every upvar in the closure. rustc doesn't do that, but we have to so we have locals + // to associate with upvars for borrowck. + let is_by_ref_closure = match kind { + rustc_type_ir::ClosureKind::Fn | rustc_type_ir::ClosureKind::FnMut => true, + rustc_type_ir::ClosureKind::FnOnce => false, + }; + let mut upvar_map: FxHashMap<LocalId, Vec<(&CapturedPlace, LocalId)>> = FxHashMap::default(); + for (capture_idx, capture) in captures.enumerate() { + let capture_local = ctx.result.locals.alloc(Local { ty: capture.captured_ty(db).store() }); + ctx.push_storage_live_for_local(capture_local, current, MirSpan::Unknown)?; + let mut projections = Vec::with_capacity(usize::from(is_by_ref_closure) + 1); + if is_by_ref_closure { + projections.push(ProjectionElem::Deref); + } + projections.push(ProjectionElem::ClosureField(capture_idx)); + let capture_param_place = Place { + local: closure_local, + projection: ctx.result.projection_store.intern(projections.into_boxed_slice()), + }; + let capture_local_place = Place { + local: capture_local, + projection: ctx.result.projection_store.intern(Box::new([])), + }; + let capture_local_rvalue = + Rvalue::Use(Operand { kind: OperandKind::Move(capture_param_place), span: None }); + ctx.push_assignment(current, capture_local_place, capture_local_rvalue, MirSpan::Unknown); + + let local = capture.captured_local(); + let local = ctx.binding_local(local)?; + upvar_map.entry(local).or_default().push((capture, capture_local)); + + ctx.result + .upvar_locals + .entry(capture.captured_local()) + .or_default() + .push((capture_local, capture.place.clone())); + } + ctx.resolver.reset_to_guard(resolver_guard); if let Some(current) = ctx.lower_expr_to_place(*root, return_slot().into(), current)? { let current = ctx.pop_drop_scope_assert_finished(current, root.into())?; ctx.set_terminator(current, TerminatorKind::Return, (*root).into()); } - let mut upvar_map: FxHashMap<LocalId, Vec<(&CapturedItem, usize)>> = FxHashMap::default(); - for (i, capture) in captures.iter().enumerate() { - let local = ctx.binding_local(capture.place.local)?; - upvar_map.entry(local).or_default().push((capture, i)); - } + let mut err = None; - let closure_local = ctx.result.locals.iter().nth(1).unwrap().0; - let closure_projection = match kind { - FnTrait::FnOnce | FnTrait::AsyncFnOnce => vec![], - FnTrait::FnMut | FnTrait::Fn | FnTrait::AsyncFnMut | FnTrait::AsyncFn => { - vec![ProjectionElem::Deref] - } - }; - ctx.result.walk_places(|p, store| { - if let Some(it) = upvar_map.get(&p.local) { - let r = it.iter().find(|it| { - if p.projection.lookup(store).len() < it.0.place.projections.len() { - return false; - } - for (it, y) in p.projection.lookup(store).iter().zip(it.0.place.projections.iter()) - { - match (it, y) { - (ProjectionElem::Deref, HirPlaceProjection::Deref) => (), - (ProjectionElem::Field(Either::Left(it)), HirPlaceProjection::Field(y)) - if it == y => {} - ( - ProjectionElem::Field(Either::Right(it)), - HirPlaceProjection::TupleField(y), - ) if it.index == *y => (), - _ => return false, + ctx.result.walk_places(|mir_place, store| { + let mir_projections = mir_place.projection.lookup(store); + if let Some(hir_places) = upvar_map.get(&mir_place.local) { + let projections = hir_places.iter().find_map(|hir_place| { + let iter = mir_projections + .iter() + .cloned() + .zip_longest(convert_closure_capture_projections(db, &hir_place.0.place)) + .enumerate(); + + for (idx, item) in iter { + match item { + EitherOrBoth::Both(mir, hir) => { + if mir != hir { + // Not this place. + return None; + } + } + EitherOrBoth::Right(_) => { + // FIXME: This can happen in fake reads. I believe this is a bug. So we change the fake read's meaning. + // never!( + // "mir upvar place shorter than hir upvar place; this should not happen, \ + // capture analysis should have picked the shorter place" + // ); + // return None; + return Some((mir_projections.len(), hir_place)); + } + // This place, but truncated. + EitherOrBoth::Left(_) => return Some((idx, hir_place)), } } - true + // Exactly this place. + Some((hir_place.0.place.projections.len(), hir_place)) }); - match r { - Some(it) => { - p.local = closure_local; - let mut next_projs = closure_projection.clone(); - next_projs.push(PlaceElem::ClosureField(it.1)); - let prev_projs = p.projection; - if it.0.kind != CaptureKind::ByValue { - next_projs.push(ProjectionElem::Deref); - } - next_projs.extend( - prev_projs.lookup(store).iter().skip(it.0.place.projections.len()).cloned(), + match projections { + Some((skip_projections_up_to, (hir_place, upvar_local))) => { + mir_place.local = *upvar_local; + let mut result_projections = Vec::with_capacity( + usize::from(hir_place.is_by_ref()) + + (mir_projections.len() - skip_projections_up_to), ); - p.projection = store.intern(next_projs.into()); + if hir_place.is_by_ref() { + result_projections.push(ProjectionElem::Deref); + } + result_projections + .extend(mir_projections[skip_projections_up_to..].iter().cloned()); + mir_place.projection = store.intern(result_projections.into()); } - None => err = Some(*p), + None => err = Some(*mir_place), } } }); |