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.rs328
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))
+ }
+ }
+ }
+}