Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/hir-ty/src/mir.rs')
| -rw-r--r-- | crates/hir-ty/src/mir.rs | 328 |
1 files changed, 220 insertions, 108 deletions
diff --git a/crates/hir-ty/src/mir.rs b/crates/hir-ty/src/mir.rs index e82038907c..7c5c0ea564 100644 --- a/crates/hir-ty/src/mir.rs +++ b/crates/hir-ty/src/mir.rs @@ -2,14 +2,13 @@ use std::{fmt::Display, iter}; -use base_db::Crate; -use either::Either; use hir_def::{ - FieldId, StaticId, TupleFieldId, UnionId, VariantId, + FieldId, LocalFieldId, StaticId, UnionId, VariantId, hir::{BindingId, Expr, ExprId, Ordering, PatId}, }; use intern::{InternedSlice, InternedSliceRef, impl_slice_internable}; use la_arena::{Arena, ArenaMap, Idx, RawIdx}; +use macros::{TypeFoldable, TypeVisitable}; use rustc_ast_ir::Mutability; use rustc_hash::FxHashMap; use rustc_type_ir::{ @@ -17,13 +16,11 @@ use rustc_type_ir::{ inherent::{GenericArgs as _, IntoKind, Ty as _}, }; use smallvec::{SmallVec, smallvec}; -use stdx::{impl_from, never}; +use stdx::impl_from; use crate::{ CallableDefId, InferBodyId, InferenceResult, MemoryMap, - consteval::usize_const, db::{HirDatabase, InternedClosureId}, - display::{DisplayTarget, HirDisplay}, infer::PointerCast, next_solver::{ Allocation, AllocationData, DbInterner, ErrorGuaranteed, GenericArgs, ParamEnv, @@ -52,8 +49,6 @@ pub use monomorphization::{ monomorphized_mir_body_for_closure_query, monomorphized_mir_body_query, }; -use super::consteval::try_const_usize; - pub type BasicBlockId = Idx<BasicBlock>; pub type LocalId = Idx<Local>; @@ -61,7 +56,7 @@ fn return_slot() -> LocalId { LocalId::from_raw(RawIdx::from(0)) } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Local { pub ty: StoredTy, } @@ -150,116 +145,72 @@ impl<'db> Operand { } } +/// The index of a field (whether of a struct/enum variant, tuple, or closure). +/// For a struct/enum it converts from and to the LocalFieldId, for a tuple or closure it's simply the index. +#[derive(Copy, Clone, PartialEq, Eq, Hash, salsa::Update, PartialOrd, Ord, Debug)] +pub struct FieldIndex(pub u32); + +impl FieldIndex { + pub fn to_local_field_id(self) -> LocalFieldId { + LocalFieldId::from_raw(RawIdx::from_u32(self.0)) + } +} + +impl From<LocalFieldId> for FieldIndex { + fn from(value: LocalFieldId) -> Self { + FieldIndex(value.into_raw().into_u32()) + } +} + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum ProjectionElem<V: PartialEq> { Deref, - Field(Either<FieldId, TupleFieldId>), - // FIXME: get rid of this, and use FieldId for tuples and closures - ClosureField(usize), + /// A field (e.g., `f` in `_1.f`). + Field(FieldIndex), + /// Index into a slice/array. Index(V), - ConstantIndex { offset: u64, from_end: bool }, - Subslice { from: u64, to: u64 }, - //Downcast(Option<Symbol>, VariantIdx), - OpaqueCast(std::convert::Infallible), + /// These indices are generated by slice patterns. + ConstantIndex { + offset: u64, + from_end: bool, + }, + /// These indices are generated by slice patterns. + Subslice { + from: u64, + to: u64, + }, + /// "Downcast" to a variant of an enum or a coroutine. + Downcast(VariantId), } impl<V: PartialEq> ProjectionElem<V> { - pub fn projected_ty<'db>( - &self, - infcx: &InferCtxt<'db>, - env: ParamEnv<'db>, - mut base: Ty<'db>, - krate: Crate, - ) -> Ty<'db> { - let interner = infcx.interner; - let db = interner.db; - - // we only bail on mir building when there are type mismatches - // but error types may pop up resulting in us still attempting to build the mir - // so just propagate the error type - if base.is_ty_error() { - return Ty::new_error(interner, ErrorGuaranteed); - } - - if matches!(base.kind(), TyKind::Alias(..)) { - let mut ocx = ObligationCtxt::new(infcx); - match ocx.structurally_normalize_ty(&ObligationCause::dummy(), env, base) { - Ok(it) => base = it, - Err(_) => return Ty::new_error(interner, ErrorGuaranteed), + pub fn map<V2: PartialEq>(self, v: impl FnOnce(V) -> V2) -> ProjectionElem<V2> { + match self { + ProjectionElem::Deref => ProjectionElem::Deref, + ProjectionElem::Field(field_index) => ProjectionElem::Field(field_index), + ProjectionElem::Index(idx) => ProjectionElem::Index(v(idx)), + ProjectionElem::ConstantIndex { offset, from_end } => { + ProjectionElem::ConstantIndex { offset, from_end } } + ProjectionElem::Subslice { from, to } => ProjectionElem::Subslice { from, to }, + ProjectionElem::Downcast(variant_id) => ProjectionElem::Downcast(variant_id), } + } - match self { - ProjectionElem::Deref => match base.kind() { - TyKind::RawPtr(inner, _) | TyKind::Ref(_, inner, _) => inner, - TyKind::Adt(adt_def, subst) if adt_def.is_box() => subst.type_at(0), - _ => { - never!( - "Overloaded deref on type {} is not a projection", - base.display(db, DisplayTarget::from_crate(db, krate)) - ); - Ty::new_error(interner, ErrorGuaranteed) - } - }, - ProjectionElem::Field(Either::Left(f)) => match base.kind() { - TyKind::Adt(_, subst) => db.field_types(f.parent)[f.local_id] - .get() - .instantiate(interner, subst) - .skip_norm_wip(), - ty => { - never!("Only adt has field, found {:?}", ty); - Ty::new_error(interner, ErrorGuaranteed) - } - }, - ProjectionElem::Field(Either::Right(f)) => match base.kind() { - TyKind::Tuple(subst) => { - subst.as_slice().get(f.index as usize).copied().unwrap_or_else(|| { - never!("Out of bound tuple field"); - Ty::new_error(interner, ErrorGuaranteed) - }) - } - ty => { - never!("Only tuple has tuple field: {:?}", ty); - Ty::new_error(interner, ErrorGuaranteed) - } - }, - ProjectionElem::ClosureField(f) => match base.kind() { - TyKind::Closure(_, args) => args.as_closure().tupled_upvars_ty().tuple_fields()[*f], - _ => { - never!("Only closure has closure field"); - Ty::new_error(interner, ErrorGuaranteed) - } - }, - ProjectionElem::ConstantIndex { .. } | ProjectionElem::Index(_) => match base.kind() { - TyKind::Array(inner, _) | TyKind::Slice(inner) => inner, - _ => { - never!("Overloaded index is not a projection"); - Ty::new_error(interner, ErrorGuaranteed) - } - }, - &ProjectionElem::Subslice { from, to } => match base.kind() { - TyKind::Array(inner, c) => { - let next_c = usize_const( - db, - match try_const_usize(db, c) { - None => None, - Some(x) => x.checked_sub(u128::from(from + to)), - }, - krate, - ); - Ty::new_array_with_const_len(interner, inner, next_c) - } - TyKind::Slice(_) => base, - _ => { - never!("Subslice projection should only happen on slice and array"); - Ty::new_error(interner, ErrorGuaranteed) - } - }, - ProjectionElem::OpaqueCast(_) => { - never!("We don't emit these yet"); - Ty::new_error(interner, ErrorGuaranteed) + pub fn try_map<V2: PartialEq>( + self, + v: impl FnOnce(V) -> Option<V2>, + ) -> Option<ProjectionElem<V2>> { + Some(match self { + ProjectionElem::Deref => ProjectionElem::Deref, + ProjectionElem::Field(field_index) => ProjectionElem::Field(field_index), + ProjectionElem::Index(idx) => ProjectionElem::Index(v(idx)?), + ProjectionElem::ConstantIndex { offset, from_end } => { + ProjectionElem::ConstantIndex { offset, from_end } } - } + ProjectionElem::Subslice { from, to } => ProjectionElem::Subslice { from, to }, + ProjectionElem::Downcast(variant_id) => ProjectionElem::Downcast(variant_id), + }) } } @@ -367,6 +318,13 @@ impl<'db> PlaceRef<'db> { pub fn store(&self) -> Place { Place { local: self.local, projection: self.projection.store() } } + pub fn ty(&self, body: &MirBody, infcx: &InferCtxt<'db>, env: ParamEnv<'db>) -> PlaceTy<'db> { + PlaceTy::from_ty(body.locals[self.local].ty.as_ref()).multi_projection_ty( + infcx, + env, + self.projection.as_slice(), + ) + } } impl<'db> From<LocalId> for PlaceRef<'db> { @@ -1243,3 +1201,157 @@ impl From<&ExprId> for MirSpan { (*value).into() } } + +impl<'tcx> PlaceRef<'tcx> { + /// If this place represents a local variable like `_X` with no + /// projections, return `Some(_X)`. + #[inline] + pub fn as_local(&self) -> Option<LocalId> { + match *self { + PlaceRef { local, projection } if projection.as_slice().is_empty() => Some(local), + _ => None, + } + } +} + +/// To determine the type of a place, we need to keep track of the variant that has been downcast to, in order to find the correct fields. +/// This type does that. +#[derive(Copy, Clone, Debug, TypeFoldable, TypeVisitable, Hash, PartialEq, Eq)] +pub struct PlaceTy<'db> { + pub ty: Ty<'db>, + /// Downcast to a particular variant of an enum or a coroutine, if included. + #[type_foldable(identity)] + #[type_visitable(ignore)] + pub variant_id: Option<VariantId>, +} + +impl<'db> PlaceTy<'db> { + #[inline] + pub fn from_ty(ty: Ty<'db>) -> PlaceTy<'db> { + PlaceTy { ty, variant_id: None } + } + + pub fn multi_projection_ty( + self, + infcx: &InferCtxt<'db>, + env: ParamEnv<'db>, + elems: &[PlaceElem], + ) -> PlaceTy<'db> { + elems.iter().fold(self, |place_ty, elem| place_ty.projection_ty(infcx, elem, env)) + } + + fn field_ty( + infcx: &InferCtxt<'db>, + self_ty: Ty<'db>, + variant: Option<VariantId>, + f: FieldIndex, + ) -> Ty<'db> { + if let Some(variant_id) = variant { + match self_ty.kind() { + TyKind::Adt(adt_def, args) if adt_def.is_enum() => { + infcx.interner.db().field_types(variant_id)[f.to_local_field_id()] + .get() + .instantiate(infcx.interner, args) + .skip_norm_wip() + } + // FIXME TyKind::Coroutine... + _ => panic!("can't downcast non-adt non-coroutine type: {self_ty:?}"), + } + } else { + match self_ty.kind() { + TyKind::Adt(adt_def, args) if !adt_def.is_enum() => { + let variant_id = VariantId::from_non_enum(adt_def.def_id()).unwrap(); + infcx.interner.db().field_types(variant_id)[f.to_local_field_id()] + .get() + .instantiate(infcx.interner, args) + .skip_norm_wip() + } + TyKind::Closure(_, args) => { + args.as_closure().tupled_upvars_ty().tuple_fields()[f.0 as usize] + } + // FIXME TyKind::Coroutine / TyKind::CoroutineClosure... + TyKind::Tuple(tys) => tys + .get(f.0 as usize) + .cloned() + .unwrap_or_else(|| panic!("field {f:?} out of range: {self_ty:?}")), + _ => panic!("can't project out of {self_ty:?}"), + } + } + } + + /// Convenience wrapper around `projection_ty_core` for `PlaceElem`. + pub fn projection_ty<V: ::std::fmt::Debug + PartialEq>( + self, + infcx: &InferCtxt<'db>, + elem: &ProjectionElem<V>, + env: ParamEnv<'db>, + ) -> PlaceTy<'db> { + self.projection_ty_core( + infcx.interner, + elem, + |ty| { + if matches!(ty.kind(), TyKind::Alias(..)) { + let mut ocx = ObligationCtxt::new(infcx); + match ocx.structurally_normalize_ty(&ObligationCause::dummy(), env, ty) { + Ok(it) => it, + Err(_) => Ty::new_error(infcx.interner, ErrorGuaranteed), + } + } else { + ty + } + }, + |self_ty, variant, field_id| Self::field_ty(infcx, self_ty, variant, field_id), + ) + } + + /// `place_ty.projection_ty_core(tcx, elem, |...| { ... })` + /// projects `place_ty` onto `elem`, returning the appropriate + /// `Ty` or downcast variant corresponding to that projection. + /// The `handle_field` callback must map a `FieldIndex` to its `Ty` + pub fn projection_ty_core<V: PartialEq + ::std::fmt::Debug>( + self, + tcx: DbInterner<'db>, + elem: &ProjectionElem<V>, + mut structurally_normalize: impl FnMut(Ty<'db>) -> Ty<'db>, + mut handle_field: impl FnMut(Ty<'db>, Option<VariantId>, FieldIndex /*, T*/) -> Ty<'db>, + ) -> PlaceTy<'db> { + // we only bail on mir building when there are type mismatches + // but error types may pop up resulting in us still attempting to build the mir + // so just propagate the error type + if self.ty.is_ty_error() { + return PlaceTy::from_ty(Ty::new_error(tcx, ErrorGuaranteed)); + } + if self.variant_id.is_some() && !matches!(elem, ProjectionElem::Field(..)) { + panic!("cannot use non field projection on downcasted place") + } + match *elem { + ProjectionElem::Deref => { + let ty = structurally_normalize(self.ty).builtin_deref(true).unwrap_or_else(|| { + panic!("deref projection of non-dereferenceable ty {:?}", self) + }); + PlaceTy::from_ty(ty) + } + ProjectionElem::Index(_) | ProjectionElem::ConstantIndex { .. } => { + PlaceTy::from_ty(structurally_normalize(self.ty).builtin_index().unwrap()) + } + ProjectionElem::Subslice { from, to /*, from_end*/ } => { + PlaceTy::from_ty(match structurally_normalize(self.ty).kind() { + TyKind::Slice(..) => self.ty, + TyKind::Array(inner, _) /*if !from_end*/ => Ty::new_array_opt(tcx, inner, to.checked_sub(from).map(|x| x.into())), + // TyKind::Array(inner, size) if from_end => { + // let size = size + // .try_to_target_usize(tcx) + // .expect("expected subslice projection on fixed-size array"); + // let len = size - from - to; + // Ty::new_array(tcx, *inner, len) + // } + _ => panic!("cannot subslice non-array type: `{:?}`", self), + }) + } + ProjectionElem::Downcast(index) => PlaceTy { ty: self.ty, variant_id: Some(index) }, + ProjectionElem::Field(f) => { + PlaceTy::from_ty(handle_field(structurally_normalize(self.ty), self.variant_id, f)) + } + } + } +} |