mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-01 06:11:35 +00:00
⬆️ rust-analyzer
This commit is contained in:
parent
15b867b5db
commit
b2f6fd4f96
217 changed files with 12639 additions and 3059 deletions
|
@ -152,6 +152,15 @@ impl TyBuilder<()> {
|
|||
TyKind::Tuple(0, Substitution::empty(Interner)).intern(Interner)
|
||||
}
|
||||
|
||||
// FIXME: rustc's ty is dependent on the adt type, maybe we need to do that as well
|
||||
pub fn discr_ty() -> Ty {
|
||||
TyKind::Scalar(chalk_ir::Scalar::Int(chalk_ir::IntTy::I128)).intern(Interner)
|
||||
}
|
||||
|
||||
pub fn bool() -> Ty {
|
||||
TyKind::Scalar(chalk_ir::Scalar::Bool).intern(Interner)
|
||||
}
|
||||
|
||||
pub fn usize() -> Ty {
|
||||
TyKind::Scalar(chalk_ir::Scalar::Uint(chalk_ir::UintTy::Usize)).intern(Interner)
|
||||
}
|
||||
|
|
|
@ -540,8 +540,7 @@ pub(crate) fn trait_datum_query(
|
|||
let where_clauses = convert_where_clauses(db, trait_.into(), &bound_vars);
|
||||
let associated_ty_ids = trait_data.associated_types().map(to_assoc_type_id).collect();
|
||||
let trait_datum_bound = rust_ir::TraitDatumBound { where_clauses };
|
||||
let well_known = lang_attr(db.upcast(), trait_)
|
||||
.and_then(|name| well_known_trait_from_lang_item(LangItem::from_str(&name)?));
|
||||
let well_known = lang_attr(db.upcast(), trait_).and_then(well_known_trait_from_lang_item);
|
||||
let trait_datum = TraitDatum {
|
||||
id: trait_id,
|
||||
binders: make_binders(db, &generic_params, trait_datum_bound),
|
||||
|
|
|
@ -13,7 +13,7 @@ use crate::{
|
|||
db::HirDatabase, from_assoc_type_id, from_chalk_trait_id, from_foreign_def_id,
|
||||
from_placeholder_idx, to_chalk_trait_id, utils::generics, AdtId, AliasEq, AliasTy, Binders,
|
||||
CallableDefId, CallableSig, FnPointer, ImplTraitId, Interner, Lifetime, ProjectionTy,
|
||||
QuantifiedWhereClause, Substitution, TraitRef, Ty, TyBuilder, TyKind, WhereClause,
|
||||
QuantifiedWhereClause, Substitution, TraitRef, Ty, TyBuilder, TyKind, TypeFlags, WhereClause,
|
||||
};
|
||||
|
||||
pub trait TyExt {
|
||||
|
@ -22,6 +22,7 @@ pub trait TyExt {
|
|||
fn is_floating_point(&self) -> bool;
|
||||
fn is_never(&self) -> bool;
|
||||
fn is_unknown(&self) -> bool;
|
||||
fn contains_unknown(&self) -> bool;
|
||||
fn is_ty_var(&self) -> bool;
|
||||
|
||||
fn as_adt(&self) -> Option<(hir_def::AdtId, &Substitution)>;
|
||||
|
@ -76,6 +77,10 @@ impl TyExt for Ty {
|
|||
matches!(self.kind(Interner), TyKind::Error)
|
||||
}
|
||||
|
||||
fn contains_unknown(&self) -> bool {
|
||||
self.data(Interner).flags.contains(TypeFlags::HAS_ERROR)
|
||||
}
|
||||
|
||||
fn is_ty_var(&self) -> bool {
|
||||
matches!(self.kind(Interner), TyKind::InferenceVar(_, _))
|
||||
}
|
||||
|
|
|
@ -1,30 +1,25 @@
|
|||
//! Constant evaluation details
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt::{Display, Write},
|
||||
};
|
||||
|
||||
use chalk_ir::{BoundVar, DebruijnIndex, GenericArgData, IntTy, Scalar};
|
||||
use base_db::CrateId;
|
||||
use chalk_ir::{BoundVar, DebruijnIndex, GenericArgData};
|
||||
use hir_def::{
|
||||
builtin_type::BuiltinInt,
|
||||
expr::{ArithOp, BinaryOp, Expr, ExprId, Literal, Pat, PatId},
|
||||
expr::Expr,
|
||||
path::ModPath,
|
||||
resolver::{resolver_for_expr, ResolveValueResult, Resolver, ValueNs},
|
||||
src::HasChildSource,
|
||||
type_ref::ConstScalar,
|
||||
ConstId, DefWithBodyId, EnumVariantId, Lookup,
|
||||
resolver::{Resolver, ValueNs},
|
||||
type_ref::ConstRef,
|
||||
ConstId, EnumVariantId,
|
||||
};
|
||||
use la_arena::{Arena, Idx, RawIdx};
|
||||
use la_arena::{Idx, RawIdx};
|
||||
use stdx::never;
|
||||
use syntax::ast::HasName;
|
||||
|
||||
use crate::{
|
||||
db::HirDatabase, infer::InferenceContext, lower::ParamLoweringMode, to_placeholder_idx,
|
||||
utils::Generics, Const, ConstData, ConstValue, GenericArg, InferenceResult, Interner, Ty,
|
||||
TyBuilder, TyKind,
|
||||
db::HirDatabase, infer::InferenceContext, layout::layout_of_ty, lower::ParamLoweringMode,
|
||||
to_placeholder_idx, utils::Generics, Const, ConstData, ConstScalar, ConstValue, GenericArg,
|
||||
Interner, MemoryMap, Ty, TyBuilder,
|
||||
};
|
||||
|
||||
use super::mir::{interpret_mir, lower_to_mir, pad16, MirEvalError, MirLowerError};
|
||||
|
||||
/// Extension trait for [`Const`]
|
||||
pub trait ConstExt {
|
||||
/// Is a [`Const`] unknown?
|
||||
|
@ -53,346 +48,24 @@ impl ConstExt for Const {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct ConstEvalCtx<'a> {
|
||||
pub db: &'a dyn HirDatabase,
|
||||
pub owner: DefWithBodyId,
|
||||
pub exprs: &'a Arena<Expr>,
|
||||
pub pats: &'a Arena<Pat>,
|
||||
pub local_data: HashMap<PatId, ComputedExpr>,
|
||||
infer: &'a InferenceResult,
|
||||
}
|
||||
|
||||
impl ConstEvalCtx<'_> {
|
||||
fn expr_ty(&mut self, expr: ExprId) -> Ty {
|
||||
self.infer[expr].clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ConstEvalError {
|
||||
NotSupported(&'static str),
|
||||
SemanticError(&'static str),
|
||||
Loop,
|
||||
IncompleteExpr,
|
||||
Panic(String),
|
||||
MirLowerError(MirLowerError),
|
||||
MirEvalError(MirEvalError),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ComputedExpr {
|
||||
Literal(Literal),
|
||||
Enum(String, EnumVariantId, Literal),
|
||||
Tuple(Box<[ComputedExpr]>),
|
||||
}
|
||||
|
||||
impl Display for ComputedExpr {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ComputedExpr::Literal(l) => match l {
|
||||
Literal::Int(x, _) => {
|
||||
if *x >= 10 {
|
||||
write!(f, "{x} ({x:#X})")
|
||||
} else {
|
||||
x.fmt(f)
|
||||
}
|
||||
}
|
||||
Literal::Uint(x, _) => {
|
||||
if *x >= 10 {
|
||||
write!(f, "{x} ({x:#X})")
|
||||
} else {
|
||||
x.fmt(f)
|
||||
}
|
||||
}
|
||||
Literal::Float(x, _) => x.fmt(f),
|
||||
Literal::Bool(x) => x.fmt(f),
|
||||
Literal::Char(x) => std::fmt::Debug::fmt(x, f),
|
||||
Literal::String(x) => std::fmt::Debug::fmt(x, f),
|
||||
Literal::ByteString(x) => std::fmt::Debug::fmt(x, f),
|
||||
},
|
||||
ComputedExpr::Enum(name, _, _) => name.fmt(f),
|
||||
ComputedExpr::Tuple(t) => {
|
||||
f.write_char('(')?;
|
||||
for x in &**t {
|
||||
x.fmt(f)?;
|
||||
f.write_str(", ")?;
|
||||
}
|
||||
f.write_char(')')
|
||||
}
|
||||
impl From<MirLowerError> for ConstEvalError {
|
||||
fn from(value: MirLowerError) -> Self {
|
||||
match value {
|
||||
MirLowerError::ConstEvalError(e) => *e,
|
||||
_ => ConstEvalError::MirLowerError(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn scalar_max(scalar: &Scalar) -> i128 {
|
||||
match scalar {
|
||||
Scalar::Bool => 1,
|
||||
Scalar::Char => u32::MAX as i128,
|
||||
Scalar::Int(x) => match x {
|
||||
IntTy::Isize => isize::MAX as i128,
|
||||
IntTy::I8 => i8::MAX as i128,
|
||||
IntTy::I16 => i16::MAX as i128,
|
||||
IntTy::I32 => i32::MAX as i128,
|
||||
IntTy::I64 => i64::MAX as i128,
|
||||
IntTy::I128 => i128::MAX,
|
||||
},
|
||||
Scalar::Uint(x) => match x {
|
||||
chalk_ir::UintTy::Usize => usize::MAX as i128,
|
||||
chalk_ir::UintTy::U8 => u8::MAX as i128,
|
||||
chalk_ir::UintTy::U16 => u16::MAX as i128,
|
||||
chalk_ir::UintTy::U32 => u32::MAX as i128,
|
||||
chalk_ir::UintTy::U64 => u64::MAX as i128,
|
||||
chalk_ir::UintTy::U128 => i128::MAX, // ignore too big u128 for now
|
||||
},
|
||||
Scalar::Float(_) => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_valid(scalar: &Scalar, value: i128) -> bool {
|
||||
if value < 0 {
|
||||
!matches!(scalar, Scalar::Uint(_)) && -scalar_max(scalar) - 1 <= value
|
||||
} else {
|
||||
value <= scalar_max(scalar)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_name(ctx: &mut ConstEvalCtx<'_>, variant: EnumVariantId) -> String {
|
||||
let loc = variant.parent.lookup(ctx.db.upcast());
|
||||
let children = variant.parent.child_source(ctx.db.upcast());
|
||||
let item_tree = loc.id.item_tree(ctx.db.upcast());
|
||||
|
||||
let variant_name = children.value[variant.local_id].name();
|
||||
let enum_name = item_tree[loc.id.value].name.to_string();
|
||||
enum_name + "::" + &variant_name.unwrap().to_string()
|
||||
}
|
||||
|
||||
pub fn eval_const(
|
||||
expr_id: ExprId,
|
||||
ctx: &mut ConstEvalCtx<'_>,
|
||||
) -> Result<ComputedExpr, ConstEvalError> {
|
||||
let u128_to_i128 = |it: u128| -> Result<i128, ConstEvalError> {
|
||||
it.try_into().map_err(|_| ConstEvalError::NotSupported("u128 is too big"))
|
||||
};
|
||||
|
||||
let expr = &ctx.exprs[expr_id];
|
||||
match expr {
|
||||
Expr::Missing => match ctx.owner {
|
||||
// evaluate the implicit variant index of an enum variant without expression
|
||||
// FIXME: This should return the type of the enum representation
|
||||
DefWithBodyId::VariantId(variant) => {
|
||||
let prev_idx: u32 = variant.local_id.into_raw().into();
|
||||
let prev_idx = prev_idx.checked_sub(1).map(RawIdx::from).map(Idx::from_raw);
|
||||
let value = match prev_idx {
|
||||
Some(local_id) => {
|
||||
let prev_variant = EnumVariantId { local_id, parent: variant.parent };
|
||||
1 + match ctx.db.const_eval_variant(prev_variant)? {
|
||||
ComputedExpr::Literal(Literal::Int(v, _)) => v,
|
||||
ComputedExpr::Literal(Literal::Uint(v, _)) => u128_to_i128(v)?,
|
||||
_ => {
|
||||
return Err(ConstEvalError::NotSupported(
|
||||
"Enum can't contain this kind of value",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => 0,
|
||||
};
|
||||
Ok(ComputedExpr::Literal(Literal::Int(value, Some(BuiltinInt::I128))))
|
||||
}
|
||||
_ => Err(ConstEvalError::IncompleteExpr),
|
||||
},
|
||||
Expr::Literal(l) => Ok(ComputedExpr::Literal(l.clone())),
|
||||
&Expr::UnaryOp { expr, op } => {
|
||||
let ty = &ctx.expr_ty(expr);
|
||||
let ev = eval_const(expr, ctx)?;
|
||||
match op {
|
||||
hir_def::expr::UnaryOp::Deref => Err(ConstEvalError::NotSupported("deref")),
|
||||
hir_def::expr::UnaryOp::Not => {
|
||||
let v = match ev {
|
||||
ComputedExpr::Literal(Literal::Bool(b)) => {
|
||||
return Ok(ComputedExpr::Literal(Literal::Bool(!b)))
|
||||
}
|
||||
ComputedExpr::Literal(Literal::Int(v, _)) => v,
|
||||
ComputedExpr::Literal(Literal::Uint(v, _)) => u128_to_i128(v)?,
|
||||
_ => return Err(ConstEvalError::NotSupported("this kind of operator")),
|
||||
};
|
||||
let r = match ty.kind(Interner) {
|
||||
TyKind::Scalar(Scalar::Uint(x)) => match x {
|
||||
chalk_ir::UintTy::U8 => !(v as u8) as i128,
|
||||
chalk_ir::UintTy::U16 => !(v as u16) as i128,
|
||||
chalk_ir::UintTy::U32 => !(v as u32) as i128,
|
||||
chalk_ir::UintTy::U64 => !(v as u64) as i128,
|
||||
chalk_ir::UintTy::U128 => {
|
||||
return Err(ConstEvalError::NotSupported("negation of u128"))
|
||||
}
|
||||
chalk_ir::UintTy::Usize => !(v as usize) as i128,
|
||||
},
|
||||
TyKind::Scalar(Scalar::Int(x)) => match x {
|
||||
chalk_ir::IntTy::I8 => !(v as i8) as i128,
|
||||
chalk_ir::IntTy::I16 => !(v as i16) as i128,
|
||||
chalk_ir::IntTy::I32 => !(v as i32) as i128,
|
||||
chalk_ir::IntTy::I64 => !(v as i64) as i128,
|
||||
chalk_ir::IntTy::I128 => !v,
|
||||
chalk_ir::IntTy::Isize => !(v as isize) as i128,
|
||||
},
|
||||
_ => return Err(ConstEvalError::NotSupported("unreachable?")),
|
||||
};
|
||||
Ok(ComputedExpr::Literal(Literal::Int(r, None)))
|
||||
}
|
||||
hir_def::expr::UnaryOp::Neg => {
|
||||
let v = match ev {
|
||||
ComputedExpr::Literal(Literal::Int(v, _)) => v,
|
||||
ComputedExpr::Literal(Literal::Uint(v, _)) => u128_to_i128(v)?,
|
||||
_ => return Err(ConstEvalError::NotSupported("this kind of operator")),
|
||||
};
|
||||
Ok(ComputedExpr::Literal(Literal::Int(
|
||||
v.checked_neg().ok_or_else(|| {
|
||||
ConstEvalError::Panic("overflow in negation".to_string())
|
||||
})?,
|
||||
None,
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
&Expr::BinaryOp { lhs, rhs, op } => {
|
||||
let ty = &ctx.expr_ty(lhs);
|
||||
let lhs = eval_const(lhs, ctx)?;
|
||||
let rhs = eval_const(rhs, ctx)?;
|
||||
let op = op.ok_or(ConstEvalError::IncompleteExpr)?;
|
||||
let v1 = match lhs {
|
||||
ComputedExpr::Literal(Literal::Int(v, _)) => v,
|
||||
ComputedExpr::Literal(Literal::Uint(v, _)) => u128_to_i128(v)?,
|
||||
_ => return Err(ConstEvalError::NotSupported("this kind of operator")),
|
||||
};
|
||||
let v2 = match rhs {
|
||||
ComputedExpr::Literal(Literal::Int(v, _)) => v,
|
||||
ComputedExpr::Literal(Literal::Uint(v, _)) => u128_to_i128(v)?,
|
||||
_ => return Err(ConstEvalError::NotSupported("this kind of operator")),
|
||||
};
|
||||
match op {
|
||||
BinaryOp::ArithOp(b) => {
|
||||
let panic_arith = ConstEvalError::Panic(
|
||||
"attempt to run invalid arithmetic operation".to_string(),
|
||||
);
|
||||
let r = match b {
|
||||
ArithOp::Add => v1.checked_add(v2).ok_or_else(|| panic_arith.clone())?,
|
||||
ArithOp::Mul => v1.checked_mul(v2).ok_or_else(|| panic_arith.clone())?,
|
||||
ArithOp::Sub => v1.checked_sub(v2).ok_or_else(|| panic_arith.clone())?,
|
||||
ArithOp::Div => v1.checked_div(v2).ok_or_else(|| panic_arith.clone())?,
|
||||
ArithOp::Rem => v1.checked_rem(v2).ok_or_else(|| panic_arith.clone())?,
|
||||
ArithOp::Shl => v1
|
||||
.checked_shl(v2.try_into().map_err(|_| panic_arith.clone())?)
|
||||
.ok_or_else(|| panic_arith.clone())?,
|
||||
ArithOp::Shr => v1
|
||||
.checked_shr(v2.try_into().map_err(|_| panic_arith.clone())?)
|
||||
.ok_or_else(|| panic_arith.clone())?,
|
||||
ArithOp::BitXor => v1 ^ v2,
|
||||
ArithOp::BitOr => v1 | v2,
|
||||
ArithOp::BitAnd => v1 & v2,
|
||||
};
|
||||
if let TyKind::Scalar(s) = ty.kind(Interner) {
|
||||
if !is_valid(s, r) {
|
||||
return Err(panic_arith);
|
||||
}
|
||||
}
|
||||
Ok(ComputedExpr::Literal(Literal::Int(r, None)))
|
||||
}
|
||||
BinaryOp::LogicOp(_) => Err(ConstEvalError::SemanticError("logic op on numbers")),
|
||||
_ => Err(ConstEvalError::NotSupported("bin op on this operators")),
|
||||
}
|
||||
}
|
||||
Expr::Block { statements, tail, .. } => {
|
||||
let mut prev_values = HashMap::<PatId, Option<ComputedExpr>>::default();
|
||||
for statement in &**statements {
|
||||
match *statement {
|
||||
hir_def::expr::Statement::Let { pat: pat_id, initializer, .. } => {
|
||||
let pat = &ctx.pats[pat_id];
|
||||
match pat {
|
||||
Pat::Bind { subpat, .. } if subpat.is_none() => (),
|
||||
_ => {
|
||||
return Err(ConstEvalError::NotSupported("complex patterns in let"))
|
||||
}
|
||||
};
|
||||
let value = match initializer {
|
||||
Some(x) => eval_const(x, ctx)?,
|
||||
None => continue,
|
||||
};
|
||||
if !prev_values.contains_key(&pat_id) {
|
||||
let prev = ctx.local_data.insert(pat_id, value);
|
||||
prev_values.insert(pat_id, prev);
|
||||
} else {
|
||||
ctx.local_data.insert(pat_id, value);
|
||||
}
|
||||
}
|
||||
hir_def::expr::Statement::Expr { .. } => {
|
||||
return Err(ConstEvalError::NotSupported("this kind of statement"))
|
||||
}
|
||||
}
|
||||
}
|
||||
let r = match tail {
|
||||
&Some(x) => eval_const(x, ctx),
|
||||
None => Ok(ComputedExpr::Tuple(Box::new([]))),
|
||||
};
|
||||
// clean up local data, so caller will receive the exact map that passed to us
|
||||
for (name, val) in prev_values {
|
||||
match val {
|
||||
Some(x) => ctx.local_data.insert(name, x),
|
||||
None => ctx.local_data.remove(&name),
|
||||
};
|
||||
}
|
||||
r
|
||||
}
|
||||
Expr::Path(p) => {
|
||||
let resolver = resolver_for_expr(ctx.db.upcast(), ctx.owner, expr_id);
|
||||
let pr = resolver
|
||||
.resolve_path_in_value_ns(ctx.db.upcast(), p.mod_path())
|
||||
.ok_or(ConstEvalError::SemanticError("unresolved path"))?;
|
||||
let pr = match pr {
|
||||
ResolveValueResult::ValueNs(v) => v,
|
||||
ResolveValueResult::Partial(..) => {
|
||||
return match ctx
|
||||
.infer
|
||||
.assoc_resolutions_for_expr(expr_id)
|
||||
.ok_or(ConstEvalError::SemanticError("unresolved assoc item"))?
|
||||
.0
|
||||
{
|
||||
hir_def::AssocItemId::FunctionId(_) => {
|
||||
Err(ConstEvalError::NotSupported("assoc function"))
|
||||
}
|
||||
// FIXME use actual impl for trait assoc const
|
||||
hir_def::AssocItemId::ConstId(c) => ctx.db.const_eval(c),
|
||||
hir_def::AssocItemId::TypeAliasId(_) => {
|
||||
Err(ConstEvalError::NotSupported("assoc type alias"))
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
match pr {
|
||||
ValueNs::LocalBinding(pat_id) => {
|
||||
let r = ctx
|
||||
.local_data
|
||||
.get(&pat_id)
|
||||
.ok_or(ConstEvalError::NotSupported("Unexpected missing local"))?;
|
||||
Ok(r.clone())
|
||||
}
|
||||
ValueNs::ConstId(id) => ctx.db.const_eval(id),
|
||||
ValueNs::GenericParam(_) => {
|
||||
Err(ConstEvalError::NotSupported("const generic without substitution"))
|
||||
}
|
||||
ValueNs::EnumVariantId(id) => match ctx.db.const_eval_variant(id)? {
|
||||
ComputedExpr::Literal(lit) => {
|
||||
Ok(ComputedExpr::Enum(get_name(ctx, id), id, lit))
|
||||
}
|
||||
_ => Err(ConstEvalError::NotSupported(
|
||||
"Enums can't evalute to anything but numbers",
|
||||
)),
|
||||
},
|
||||
_ => Err(ConstEvalError::NotSupported("path that are not const or local")),
|
||||
}
|
||||
}
|
||||
// FIXME: Handle the cast target
|
||||
&Expr::Cast { expr, .. } => match eval_const(expr, ctx)? {
|
||||
ComputedExpr::Enum(_, _, lit) => Ok(ComputedExpr::Literal(lit)),
|
||||
_ => Err(ConstEvalError::NotSupported("Can't cast these types")),
|
||||
},
|
||||
_ => Err(ConstEvalError::NotSupported("This kind of expression")),
|
||||
impl From<MirEvalError> for ConstEvalError {
|
||||
fn from(value: MirEvalError) -> Self {
|
||||
ConstEvalError::MirEvalError(value)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -449,68 +122,102 @@ pub fn intern_const_scalar(value: ConstScalar, ty: Ty) -> Const {
|
|||
.intern(Interner)
|
||||
}
|
||||
|
||||
/// Interns a constant scalar with the given type
|
||||
pub fn intern_const_ref(db: &dyn HirDatabase, value: &ConstRef, ty: Ty, krate: CrateId) -> Const {
|
||||
let bytes = match value {
|
||||
ConstRef::Int(i) => {
|
||||
// FIXME: We should handle failure of layout better.
|
||||
let size = layout_of_ty(db, &ty, krate).map(|x| x.size.bytes_usize()).unwrap_or(16);
|
||||
ConstScalar::Bytes(i.to_le_bytes()[0..size].to_vec(), MemoryMap::default())
|
||||
}
|
||||
ConstRef::UInt(i) => {
|
||||
let size = layout_of_ty(db, &ty, krate).map(|x| x.size.bytes_usize()).unwrap_or(16);
|
||||
ConstScalar::Bytes(i.to_le_bytes()[0..size].to_vec(), MemoryMap::default())
|
||||
}
|
||||
ConstRef::Bool(b) => ConstScalar::Bytes(vec![*b as u8], MemoryMap::default()),
|
||||
ConstRef::Char(c) => {
|
||||
ConstScalar::Bytes((*c as u32).to_le_bytes().to_vec(), MemoryMap::default())
|
||||
}
|
||||
ConstRef::Unknown => ConstScalar::Unknown,
|
||||
};
|
||||
intern_const_scalar(bytes, ty)
|
||||
}
|
||||
|
||||
/// Interns a possibly-unknown target usize
|
||||
pub fn usize_const(value: Option<u128>) -> Const {
|
||||
intern_const_scalar(value.map_or(ConstScalar::Unknown, ConstScalar::UInt), TyBuilder::usize())
|
||||
pub fn usize_const(db: &dyn HirDatabase, value: Option<u128>, krate: CrateId) -> Const {
|
||||
intern_const_ref(
|
||||
db,
|
||||
&value.map_or(ConstRef::Unknown, ConstRef::UInt),
|
||||
TyBuilder::usize(),
|
||||
krate,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn try_const_usize(c: &Const) -> Option<u128> {
|
||||
match &c.data(Interner).value {
|
||||
chalk_ir::ConstValue::BoundVar(_) => None,
|
||||
chalk_ir::ConstValue::InferenceVar(_) => None,
|
||||
chalk_ir::ConstValue::Placeholder(_) => None,
|
||||
chalk_ir::ConstValue::Concrete(c) => match &c.interned {
|
||||
ConstScalar::Bytes(x, _) => Some(u128::from_le_bytes(pad16(&x, false))),
|
||||
_ => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn const_eval_recover(
|
||||
_: &dyn HirDatabase,
|
||||
_: &[String],
|
||||
_: &ConstId,
|
||||
) -> Result<ComputedExpr, ConstEvalError> {
|
||||
Err(ConstEvalError::Loop)
|
||||
) -> Result<Const, ConstEvalError> {
|
||||
Err(ConstEvalError::MirLowerError(MirLowerError::Loop))
|
||||
}
|
||||
|
||||
pub(crate) fn const_eval_variant_recover(
|
||||
pub(crate) fn const_eval_discriminant_recover(
|
||||
_: &dyn HirDatabase,
|
||||
_: &[String],
|
||||
_: &EnumVariantId,
|
||||
) -> Result<ComputedExpr, ConstEvalError> {
|
||||
Err(ConstEvalError::Loop)
|
||||
) -> Result<i128, ConstEvalError> {
|
||||
Err(ConstEvalError::MirLowerError(MirLowerError::Loop))
|
||||
}
|
||||
|
||||
pub(crate) fn const_eval_variant_query(
|
||||
pub(crate) fn const_eval_query(
|
||||
db: &dyn HirDatabase,
|
||||
const_id: ConstId,
|
||||
) -> Result<ComputedExpr, ConstEvalError> {
|
||||
) -> Result<Const, ConstEvalError> {
|
||||
let def = const_id.into();
|
||||
let body = db.body(def);
|
||||
let infer = &db.infer(def);
|
||||
let result = eval_const(
|
||||
body.body_expr,
|
||||
&mut ConstEvalCtx {
|
||||
db,
|
||||
owner: const_id.into(),
|
||||
exprs: &body.exprs,
|
||||
pats: &body.pats,
|
||||
local_data: HashMap::default(),
|
||||
infer,
|
||||
},
|
||||
);
|
||||
result
|
||||
let body = db.mir_body(def)?;
|
||||
let c = interpret_mir(db, &body, false)?;
|
||||
Ok(c)
|
||||
}
|
||||
|
||||
pub(crate) fn const_eval_query_variant(
|
||||
pub(crate) fn const_eval_discriminant_variant(
|
||||
db: &dyn HirDatabase,
|
||||
variant_id: EnumVariantId,
|
||||
) -> Result<ComputedExpr, ConstEvalError> {
|
||||
) -> Result<i128, ConstEvalError> {
|
||||
let def = variant_id.into();
|
||||
let body = db.body(def);
|
||||
let infer = &db.infer(def);
|
||||
eval_const(
|
||||
body.body_expr,
|
||||
&mut ConstEvalCtx {
|
||||
db,
|
||||
owner: def,
|
||||
exprs: &body.exprs,
|
||||
pats: &body.pats,
|
||||
local_data: HashMap::default(),
|
||||
infer,
|
||||
},
|
||||
)
|
||||
if body.exprs[body.body_expr] == Expr::Missing {
|
||||
let prev_idx: u32 = variant_id.local_id.into_raw().into();
|
||||
let prev_idx = prev_idx.checked_sub(1).map(RawIdx::from).map(Idx::from_raw);
|
||||
let value = match prev_idx {
|
||||
Some(local_id) => {
|
||||
let prev_variant = EnumVariantId { local_id, parent: variant_id.parent };
|
||||
1 + db.const_eval_discriminant(prev_variant)?
|
||||
}
|
||||
_ => 0,
|
||||
};
|
||||
return Ok(value);
|
||||
}
|
||||
let mir_body = db.mir_body(def)?;
|
||||
let c = interpret_mir(db, &mir_body, false)?;
|
||||
let c = try_const_usize(&c).unwrap() as i128;
|
||||
Ok(c)
|
||||
}
|
||||
|
||||
// FIXME: Ideally constants in const eval should have separate body (issue #7434), and this function should
|
||||
// get an `InferenceResult` instead of an `InferenceContext`. And we should remove `ctx.clone().resolve_all()` here
|
||||
// and make this function private. See the fixme comment on `InferenceContext::resolve_all`.
|
||||
pub(crate) fn eval_to_const(
|
||||
expr: Idx<Expr>,
|
||||
mode: ParamLoweringMode,
|
||||
|
@ -518,28 +225,20 @@ pub(crate) fn eval_to_const(
|
|||
args: impl FnOnce() -> Generics,
|
||||
debruijn: DebruijnIndex,
|
||||
) -> Const {
|
||||
let db = ctx.db;
|
||||
if let Expr::Path(p) = &ctx.body.exprs[expr] {
|
||||
let db = ctx.db;
|
||||
let resolver = &ctx.resolver;
|
||||
if let Some(c) = path_to_const(db, resolver, p.mod_path(), mode, args, debruijn) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
let body = ctx.body.clone();
|
||||
let mut ctx = ConstEvalCtx {
|
||||
db: ctx.db,
|
||||
owner: ctx.owner,
|
||||
exprs: &body.exprs,
|
||||
pats: &body.pats,
|
||||
local_data: HashMap::default(),
|
||||
infer: &ctx.result,
|
||||
};
|
||||
let computed_expr = eval_const(expr, &mut ctx);
|
||||
let const_scalar = match computed_expr {
|
||||
Ok(ComputedExpr::Literal(literal)) => literal.into(),
|
||||
_ => ConstScalar::Unknown,
|
||||
};
|
||||
intern_const_scalar(const_scalar, TyBuilder::usize())
|
||||
let infer = ctx.clone().resolve_all();
|
||||
if let Ok(mir_body) = lower_to_mir(ctx.db, ctx.owner, &ctx.body, &infer, expr) {
|
||||
if let Ok(result) = interpret_mir(db, &mir_body, true) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
unknown_const(infer[expr].clone())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -16,10 +16,12 @@ use smallvec::SmallVec;
|
|||
|
||||
use crate::{
|
||||
chalk_db,
|
||||
consteval::{ComputedExpr, ConstEvalError},
|
||||
consteval::ConstEvalError,
|
||||
method_resolution::{InherentImpls, TraitImpls, TyFingerprint},
|
||||
Binders, CallableDefId, FnDefId, GenericArg, ImplTraitId, InferenceResult, Interner, PolyFnSig,
|
||||
QuantifiedWhereClause, ReturnTypeImplTraits, Substitution, TraitRef, Ty, TyDefId, ValueTyDefId,
|
||||
mir::{BorrowckResult, MirBody, MirLowerError},
|
||||
Binders, CallableDefId, Const, FnDefId, GenericArg, ImplTraitId, InferenceResult, Interner,
|
||||
PolyFnSig, QuantifiedWhereClause, ReturnTypeImplTraits, Substitution, TraitRef, Ty, TyDefId,
|
||||
ValueTyDefId,
|
||||
};
|
||||
use hir_expand::name::Name;
|
||||
|
||||
|
@ -32,6 +34,13 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
|
|||
#[salsa::invoke(crate::infer::infer_query)]
|
||||
fn infer_query(&self, def: DefWithBodyId) -> Arc<InferenceResult>;
|
||||
|
||||
#[salsa::invoke(crate::mir::mir_body_query)]
|
||||
#[salsa::cycle(crate::mir::mir_body_recover)]
|
||||
fn mir_body(&self, def: DefWithBodyId) -> Result<Arc<MirBody>, MirLowerError>;
|
||||
|
||||
#[salsa::invoke(crate::mir::borrowck_query)]
|
||||
fn borrowck(&self, def: DefWithBodyId) -> Result<Arc<BorrowckResult>, MirLowerError>;
|
||||
|
||||
#[salsa::invoke(crate::lower::ty_query)]
|
||||
#[salsa::cycle(crate::lower::ty_recover)]
|
||||
fn ty(&self, def: TyDefId) -> Binders<Ty>;
|
||||
|
@ -46,13 +55,13 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
|
|||
#[salsa::invoke(crate::lower::const_param_ty_query)]
|
||||
fn const_param_ty(&self, def: ConstParamId) -> Ty;
|
||||
|
||||
#[salsa::invoke(crate::consteval::const_eval_variant_query)]
|
||||
#[salsa::invoke(crate::consteval::const_eval_query)]
|
||||
#[salsa::cycle(crate::consteval::const_eval_recover)]
|
||||
fn const_eval(&self, def: ConstId) -> Result<ComputedExpr, ConstEvalError>;
|
||||
fn const_eval(&self, def: ConstId) -> Result<Const, ConstEvalError>;
|
||||
|
||||
#[salsa::invoke(crate::consteval::const_eval_query_variant)]
|
||||
#[salsa::cycle(crate::consteval::const_eval_variant_recover)]
|
||||
fn const_eval_variant(&self, def: EnumVariantId) -> Result<ComputedExpr, ConstEvalError>;
|
||||
#[salsa::invoke(crate::consteval::const_eval_discriminant_variant)]
|
||||
#[salsa::cycle(crate::consteval::const_eval_discriminant_recover)]
|
||||
fn const_eval_discriminant(&self, def: EnumVariantId) -> Result<i128, ConstEvalError>;
|
||||
|
||||
#[salsa::invoke(crate::lower::impl_trait_query)]
|
||||
fn impl_trait(&self, def: ImplId) -> Option<Binders<TraitRef>>;
|
||||
|
|
|
@ -178,6 +178,7 @@ impl<'a> DeclValidator<'a> {
|
|||
AttrDefId::StaticId(sid) => Some(sid.lookup(self.db.upcast()).container.into()),
|
||||
AttrDefId::ConstId(cid) => Some(cid.lookup(self.db.upcast()).container.into()),
|
||||
AttrDefId::TraitId(tid) => Some(tid.lookup(self.db.upcast()).container.into()),
|
||||
AttrDefId::TraitAliasId(taid) => Some(taid.lookup(self.db.upcast()).container.into()),
|
||||
AttrDefId::ImplId(iid) => Some(iid.lookup(self.db.upcast()).container.into()),
|
||||
AttrDefId::ExternBlockId(id) => Some(id.lookup(self.db.upcast()).container.into()),
|
||||
// These warnings should not explore macro definitions at all
|
||||
|
@ -234,8 +235,8 @@ impl<'a> DeclValidator<'a> {
|
|||
let pats_replacements = body
|
||||
.pats
|
||||
.iter()
|
||||
.filter_map(|(id, pat)| match pat {
|
||||
Pat::Bind { name, .. } => Some((id, name)),
|
||||
.filter_map(|(pat_id, pat)| match pat {
|
||||
Pat::Bind { id, .. } => Some((pat_id, &body.bindings[*id].name)),
|
||||
_ => None,
|
||||
})
|
||||
.filter_map(|(id, bind_name)| {
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
|
||||
use either::Either;
|
||||
use hir_def::lang_item::LangItem;
|
||||
use hir_def::{resolver::HasResolver, AdtId, AssocItemId, DefWithBodyId, HasModule};
|
||||
use hir_def::{ItemContainerId, Lookup};
|
||||
use hir_expand::name;
|
||||
use itertools::Either;
|
||||
use itertools::Itertools;
|
||||
use rustc_hash::FxHashSet;
|
||||
use typed_arena::Arena;
|
||||
|
@ -84,7 +84,7 @@ impl ExprValidator {
|
|||
|
||||
match expr {
|
||||
Expr::Match { expr, arms } => {
|
||||
self.validate_match(id, *expr, arms, db, self.infer.clone());
|
||||
self.validate_match(id, *expr, arms, db);
|
||||
}
|
||||
Expr::Call { .. } | Expr::MethodCall { .. } => {
|
||||
self.validate_call(db, id, expr, &mut filter_map_next_checker);
|
||||
|
@ -147,16 +147,15 @@ impl ExprValidator {
|
|||
|
||||
fn validate_match(
|
||||
&mut self,
|
||||
id: ExprId,
|
||||
match_expr: ExprId,
|
||||
scrutinee_expr: ExprId,
|
||||
arms: &[MatchArm],
|
||||
db: &dyn HirDatabase,
|
||||
infer: Arc<InferenceResult>,
|
||||
) {
|
||||
let body = db.body(self.owner);
|
||||
|
||||
let match_expr_ty = &infer[match_expr];
|
||||
if match_expr_ty.is_unknown() {
|
||||
let scrut_ty = &self.infer[scrutinee_expr];
|
||||
if scrut_ty.is_unknown() {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -166,23 +165,23 @@ impl ExprValidator {
|
|||
let mut m_arms = Vec::with_capacity(arms.len());
|
||||
let mut has_lowering_errors = false;
|
||||
for arm in arms {
|
||||
if let Some(pat_ty) = infer.type_of_pat.get(arm.pat) {
|
||||
if let Some(pat_ty) = self.infer.type_of_pat.get(arm.pat) {
|
||||
// We only include patterns whose type matches the type
|
||||
// of the match expression. If we had an InvalidMatchArmPattern
|
||||
// of the scrutinee expression. If we had an InvalidMatchArmPattern
|
||||
// diagnostic or similar we could raise that in an else
|
||||
// block here.
|
||||
//
|
||||
// When comparing the types, we also have to consider that rustc
|
||||
// will automatically de-reference the match expression type if
|
||||
// will automatically de-reference the scrutinee expression type if
|
||||
// necessary.
|
||||
//
|
||||
// FIXME we should use the type checker for this.
|
||||
if (pat_ty == match_expr_ty
|
||||
|| match_expr_ty
|
||||
if (pat_ty == scrut_ty
|
||||
|| scrut_ty
|
||||
.as_reference()
|
||||
.map(|(match_expr_ty, ..)| match_expr_ty == pat_ty)
|
||||
.unwrap_or(false))
|
||||
&& types_of_subpatterns_do_match(arm.pat, &body, &infer)
|
||||
&& types_of_subpatterns_do_match(arm.pat, &body, &self.infer)
|
||||
{
|
||||
// If we had a NotUsefulMatchArm diagnostic, we could
|
||||
// check the usefulness of each pattern as we added it
|
||||
|
@ -206,7 +205,7 @@ impl ExprValidator {
|
|||
return;
|
||||
}
|
||||
|
||||
let report = compute_match_usefulness(&cx, &m_arms, match_expr_ty);
|
||||
let report = compute_match_usefulness(&cx, &m_arms, scrut_ty);
|
||||
|
||||
// FIXME Report unreacheble arms
|
||||
// https://github.com/rust-lang/rust/blob/f31622a50/compiler/rustc_mir_build/src/thir/pattern/check_match.rs#L200
|
||||
|
@ -214,8 +213,8 @@ impl ExprValidator {
|
|||
let witnesses = report.non_exhaustiveness_witnesses;
|
||||
if !witnesses.is_empty() {
|
||||
self.diagnostics.push(BodyValidationDiagnostic::MissingMatchArms {
|
||||
match_expr: id,
|
||||
uncovered_patterns: missing_match_arms(&cx, match_expr_ty, witnesses, arms),
|
||||
match_expr,
|
||||
uncovered_patterns: missing_match_arms(&cx, scrut_ty, witnesses, arms),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -379,7 +378,7 @@ fn missing_match_arms<'p>(
|
|||
arms: &[MatchArm],
|
||||
) -> String {
|
||||
struct DisplayWitness<'a, 'p>(&'a DeconstructedPat<'p>, &'a MatchCheckCtx<'a, 'p>);
|
||||
impl<'a, 'p> fmt::Display for DisplayWitness<'a, 'p> {
|
||||
impl fmt::Display for DisplayWitness<'_, '_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let DisplayWitness(witness, cx) = *self;
|
||||
let pat = witness.to_pat(cx);
|
||||
|
|
|
@ -146,8 +146,9 @@ impl<'a> PatCtxt<'a> {
|
|||
PatKind::Leaf { subpatterns }
|
||||
}
|
||||
|
||||
hir_def::expr::Pat::Bind { ref name, subpat, .. } => {
|
||||
hir_def::expr::Pat::Bind { id, subpat, .. } => {
|
||||
let bm = self.infer.pat_binding_modes[&pat];
|
||||
let name = &self.body.bindings[id].name;
|
||||
match (bm, ty.kind(Interner)) {
|
||||
(BindingMode::Ref(_), TyKind::Ref(.., rty)) => ty = rty,
|
||||
(BindingMode::Ref(_), _) => {
|
||||
|
|
|
@ -94,8 +94,10 @@ fn walk_unsafe(
|
|||
unsafe_expr_cb(UnsafeExpr { expr: current, inside_unsafe_block });
|
||||
}
|
||||
}
|
||||
Expr::Unsafe { body: child } => {
|
||||
return walk_unsafe(db, infer, def, body, *child, true, unsafe_expr_cb);
|
||||
Expr::Unsafe { .. } => {
|
||||
return expr.walk_child_exprs(|child| {
|
||||
walk_unsafe(db, infer, def, body, child, true, unsafe_expr_cb);
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,9 @@
|
|||
use std::fmt::{self, Debug};
|
||||
|
||||
use base_db::CrateId;
|
||||
use chalk_ir::BoundVar;
|
||||
use chalk_ir::{BoundVar, TyKind};
|
||||
use hir_def::{
|
||||
adt::VariantData,
|
||||
body,
|
||||
db::DefDatabase,
|
||||
find_path,
|
||||
|
@ -14,9 +15,9 @@ use hir_def::{
|
|||
item_scope::ItemInNs,
|
||||
lang_item::{LangItem, LangItemTarget},
|
||||
path::{Path, PathKind},
|
||||
type_ref::{ConstScalar, TraitBoundModifier, TypeBound, TypeRef},
|
||||
type_ref::{TraitBoundModifier, TypeBound, TypeRef},
|
||||
visibility::Visibility,
|
||||
HasModule, ItemContainerId, Lookup, ModuleDefId, ModuleId, TraitId,
|
||||
HasModule, ItemContainerId, LocalFieldId, Lookup, ModuleDefId, ModuleId, TraitId,
|
||||
};
|
||||
use hir_expand::{hygiene::Hygiene, name::Name};
|
||||
use intern::{Internable, Interned};
|
||||
|
@ -25,14 +26,17 @@ use smallvec::SmallVec;
|
|||
|
||||
use crate::{
|
||||
db::HirDatabase,
|
||||
from_assoc_type_id, from_foreign_def_id, from_placeholder_idx, lt_from_placeholder_idx,
|
||||
from_assoc_type_id, from_foreign_def_id, from_placeholder_idx,
|
||||
layout::layout_of_ty,
|
||||
lt_from_placeholder_idx,
|
||||
mapping::from_chalk,
|
||||
mir::pad16,
|
||||
primitive, to_assoc_type_id,
|
||||
utils::{self, generics},
|
||||
AdtId, AliasEq, AliasTy, Binders, CallableDefId, CallableSig, Const, ConstValue, DomainGoal,
|
||||
GenericArg, ImplTraitId, Interner, Lifetime, LifetimeData, LifetimeOutlives, Mutability,
|
||||
OpaqueTy, ProjectionTy, ProjectionTyExt, QuantifiedWhereClause, Scalar, Substitution, TraitRef,
|
||||
TraitRefExt, Ty, TyExt, TyKind, WhereClause,
|
||||
AdtId, AliasEq, AliasTy, Binders, CallableDefId, CallableSig, Const, ConstScalar, ConstValue,
|
||||
DomainGoal, GenericArg, ImplTraitId, Interner, Lifetime, LifetimeData, LifetimeOutlives,
|
||||
MemoryMap, Mutability, OpaqueTy, ProjectionTy, ProjectionTyExt, QuantifiedWhereClause, Scalar,
|
||||
Substitution, TraitRef, TraitRefExt, Ty, TyExt, WhereClause,
|
||||
};
|
||||
|
||||
pub trait HirWrite: fmt::Write {
|
||||
|
@ -362,20 +366,176 @@ impl HirDisplay for GenericArg {
|
|||
impl HirDisplay for Const {
|
||||
fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
|
||||
let data = self.interned();
|
||||
match data.value {
|
||||
match &data.value {
|
||||
ConstValue::BoundVar(idx) => idx.hir_fmt(f),
|
||||
ConstValue::InferenceVar(..) => write!(f, "#c#"),
|
||||
ConstValue::Placeholder(idx) => {
|
||||
let id = from_placeholder_idx(f.db, idx);
|
||||
let id = from_placeholder_idx(f.db, *idx);
|
||||
let generics = generics(f.db.upcast(), id.parent);
|
||||
let param_data = &generics.params.type_or_consts[id.local_id];
|
||||
write!(f, "{}", param_data.name().unwrap())
|
||||
}
|
||||
ConstValue::Concrete(c) => write!(f, "{}", c.interned),
|
||||
ConstValue::Concrete(c) => match &c.interned {
|
||||
ConstScalar::Bytes(b, m) => render_const_scalar(f, &b, m, &data.ty),
|
||||
ConstScalar::Unknown => f.write_char('_'),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HexifiedConst(pub Const);
|
||||
|
||||
impl HirDisplay for HexifiedConst {
|
||||
fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
|
||||
let data = &self.0.data(Interner);
|
||||
if let TyKind::Scalar(s) = data.ty.kind(Interner) {
|
||||
if matches!(s, Scalar::Int(_) | Scalar::Uint(_)) {
|
||||
if let ConstValue::Concrete(c) = &data.value {
|
||||
if let ConstScalar::Bytes(b, m) = &c.interned {
|
||||
let value = u128::from_le_bytes(pad16(b, false));
|
||||
if value >= 10 {
|
||||
render_const_scalar(f, &b, m, &data.ty)?;
|
||||
return write!(f, " ({:#X})", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.0.hir_fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
fn render_const_scalar(
|
||||
f: &mut HirFormatter<'_>,
|
||||
b: &[u8],
|
||||
memory_map: &MemoryMap,
|
||||
ty: &Ty,
|
||||
) -> Result<(), HirDisplayError> {
|
||||
match ty.kind(Interner) {
|
||||
chalk_ir::TyKind::Scalar(s) => match s {
|
||||
Scalar::Bool => write!(f, "{}", if b[0] == 0 { false } else { true }),
|
||||
Scalar::Char => {
|
||||
let x = u128::from_le_bytes(pad16(b, false)) as u32;
|
||||
let Ok(c) = char::try_from(x) else {
|
||||
return f.write_str("<unicode-error>");
|
||||
};
|
||||
write!(f, "{c:?}")
|
||||
}
|
||||
Scalar::Int(_) => {
|
||||
let x = i128::from_le_bytes(pad16(b, true));
|
||||
write!(f, "{x}")
|
||||
}
|
||||
Scalar::Uint(_) => {
|
||||
let x = u128::from_le_bytes(pad16(b, false));
|
||||
write!(f, "{x}")
|
||||
}
|
||||
Scalar::Float(fl) => match fl {
|
||||
chalk_ir::FloatTy::F32 => {
|
||||
let x = f32::from_le_bytes(b.try_into().unwrap());
|
||||
write!(f, "{x:?}")
|
||||
}
|
||||
chalk_ir::FloatTy::F64 => {
|
||||
let x = f64::from_le_bytes(b.try_into().unwrap());
|
||||
write!(f, "{x:?}")
|
||||
}
|
||||
},
|
||||
},
|
||||
chalk_ir::TyKind::Ref(_, _, t) => match t.kind(Interner) {
|
||||
chalk_ir::TyKind::Str => {
|
||||
let addr = usize::from_le_bytes(b[0..b.len() / 2].try_into().unwrap());
|
||||
let bytes = memory_map.0.get(&addr).map(|x| &**x).unwrap_or(&[]);
|
||||
let s = std::str::from_utf8(bytes).unwrap_or("<utf8-error>");
|
||||
write!(f, "{s:?}")
|
||||
}
|
||||
_ => f.write_str("<ref-not-supported>"),
|
||||
},
|
||||
chalk_ir::TyKind::Tuple(_, subst) => {
|
||||
// FIXME: Remove this line. If the target data layout is independent
|
||||
// of the krate, the `db.target_data_layout` and its callers like `layout_of_ty` don't need
|
||||
// to get krate. Otherwise, we need to get krate from the final callers of the hir display
|
||||
// infrastructure and have it here as a field on `f`.
|
||||
let krate = *f.db.crate_graph().crates_in_topological_order().last().unwrap();
|
||||
let Ok(layout) = layout_of_ty(f.db, ty, krate) else {
|
||||
return f.write_str("<layout-error>");
|
||||
};
|
||||
f.write_str("(")?;
|
||||
let mut first = true;
|
||||
for (id, ty) in subst.iter(Interner).enumerate() {
|
||||
if first {
|
||||
first = false;
|
||||
} else {
|
||||
f.write_str(", ")?;
|
||||
}
|
||||
let ty = ty.assert_ty_ref(Interner); // Tuple only has type argument
|
||||
let offset = layout.fields.offset(id).bytes_usize();
|
||||
let Ok(layout) = layout_of_ty(f.db, &ty, krate) else {
|
||||
f.write_str("<layout-error>")?;
|
||||
continue;
|
||||
};
|
||||
let size = layout.size.bytes_usize();
|
||||
render_const_scalar(f, &b[offset..offset + size], memory_map, &ty)?;
|
||||
}
|
||||
f.write_str(")")
|
||||
}
|
||||
chalk_ir::TyKind::Adt(adt, subst) => match adt.0 {
|
||||
hir_def::AdtId::StructId(s) => {
|
||||
let data = f.db.struct_data(s);
|
||||
let Ok(layout) = f.db.layout_of_adt(adt.0, subst.clone()) else {
|
||||
return f.write_str("<layout-error>");
|
||||
};
|
||||
match data.variant_data.as_ref() {
|
||||
VariantData::Record(fields) | VariantData::Tuple(fields) => {
|
||||
let field_types = f.db.field_types(s.into());
|
||||
let krate = adt.0.module(f.db.upcast()).krate();
|
||||
let render_field = |f: &mut HirFormatter<'_>, id: LocalFieldId| {
|
||||
let offset = layout
|
||||
.fields
|
||||
.offset(u32::from(id.into_raw()) as usize)
|
||||
.bytes_usize();
|
||||
let ty = field_types[id].clone().substitute(Interner, subst);
|
||||
let Ok(layout) = layout_of_ty(f.db, &ty, krate) else {
|
||||
return f.write_str("<layout-error>");
|
||||
};
|
||||
let size = layout.size.bytes_usize();
|
||||
render_const_scalar(f, &b[offset..offset + size], memory_map, &ty)
|
||||
};
|
||||
let mut it = fields.iter();
|
||||
if matches!(data.variant_data.as_ref(), VariantData::Record(_)) {
|
||||
write!(f, "{} {{", data.name)?;
|
||||
if let Some((id, data)) = it.next() {
|
||||
write!(f, " {}: ", data.name)?;
|
||||
render_field(f, id)?;
|
||||
}
|
||||
for (id, data) in it {
|
||||
write!(f, ", {}: ", data.name)?;
|
||||
render_field(f, id)?;
|
||||
}
|
||||
write!(f, " }}")?;
|
||||
} else {
|
||||
let mut it = it.map(|x| x.0);
|
||||
write!(f, "{}(", data.name)?;
|
||||
if let Some(id) = it.next() {
|
||||
render_field(f, id)?;
|
||||
}
|
||||
for id in it {
|
||||
write!(f, ", ")?;
|
||||
render_field(f, id)?;
|
||||
}
|
||||
write!(f, ")")?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
VariantData::Unit => write!(f, "{}", data.name),
|
||||
}
|
||||
}
|
||||
hir_def::AdtId::UnionId(u) => write!(f, "{}", f.db.union_data(u).name),
|
||||
hir_def::AdtId::EnumId(_) => f.write_str("<enum-not-supported>"),
|
||||
},
|
||||
chalk_ir::TyKind::FnDef(..) => ty.hir_fmt(f),
|
||||
_ => f.write_str("<not-supported>"),
|
||||
}
|
||||
}
|
||||
|
||||
impl HirDisplay for BoundVar {
|
||||
fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
|
||||
write!(f, "?{}.{}", self.debruijn.depth(), self.index)
|
||||
|
@ -614,8 +774,9 @@ impl HirDisplay for Ty {
|
|||
{
|
||||
return true;
|
||||
}
|
||||
if let Some(ConstValue::Concrete(c)) =
|
||||
parameter.constant(Interner).map(|x| x.data(Interner).value)
|
||||
if let Some(ConstValue::Concrete(c)) = parameter
|
||||
.constant(Interner)
|
||||
.map(|x| &x.data(Interner).value)
|
||||
{
|
||||
if c.interned == ConstScalar::Unknown {
|
||||
return true;
|
||||
|
|
|
@ -17,11 +17,12 @@ use std::ops::Index;
|
|||
use std::sync::Arc;
|
||||
|
||||
use chalk_ir::{cast::Cast, ConstValue, DebruijnIndex, Mutability, Safety, Scalar, TypeFlags};
|
||||
use either::Either;
|
||||
use hir_def::{
|
||||
body::Body,
|
||||
builtin_type::{BuiltinInt, BuiltinType, BuiltinUint},
|
||||
data::{ConstData, StaticData},
|
||||
expr::{BindingAnnotation, ExprId, ExprOrPatId, PatId},
|
||||
expr::{BindingAnnotation, BindingId, ExprId, ExprOrPatId, PatId},
|
||||
lang_item::{LangItem, LangItemTarget},
|
||||
layout::Integer,
|
||||
path::Path,
|
||||
|
@ -30,10 +31,9 @@ use hir_def::{
|
|||
AdtId, AssocItemId, DefWithBodyId, EnumVariantId, FieldId, FunctionId, HasModule,
|
||||
ItemContainerId, Lookup, TraitId, TypeAliasId, VariantId,
|
||||
};
|
||||
use hir_expand::name::name;
|
||||
use itertools::Either;
|
||||
use hir_expand::name::{name, Name};
|
||||
use la_arena::ArenaMap;
|
||||
use rustc_hash::FxHashMap;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use stdx::always;
|
||||
|
||||
use crate::{
|
||||
|
@ -66,8 +66,10 @@ pub(crate) fn infer_query(db: &dyn HirDatabase, def: DefWithBodyId) -> Arc<Infer
|
|||
let mut ctx = InferenceContext::new(db, def, &body, resolver);
|
||||
|
||||
match def {
|
||||
DefWithBodyId::FunctionId(f) => {
|
||||
ctx.collect_fn(f);
|
||||
}
|
||||
DefWithBodyId::ConstId(c) => ctx.collect_const(&db.const_data(c)),
|
||||
DefWithBodyId::FunctionId(f) => ctx.collect_fn(f),
|
||||
DefWithBodyId::StaticId(s) => ctx.collect_static(&db.static_data(s)),
|
||||
DefWithBodyId::VariantId(v) => {
|
||||
ctx.return_ty = TyBuilder::builtin(match db.enum_data(v.parent).variant_body_type() {
|
||||
|
@ -144,44 +146,6 @@ impl Default for BindingMode {
|
|||
}
|
||||
}
|
||||
|
||||
/// Used to generalize patterns and assignee expressions.
|
||||
trait PatLike: Into<ExprOrPatId> + Copy {
|
||||
type BindingMode: Copy;
|
||||
|
||||
fn infer(
|
||||
this: &mut InferenceContext<'_>,
|
||||
id: Self,
|
||||
expected_ty: &Ty,
|
||||
default_bm: Self::BindingMode,
|
||||
) -> Ty;
|
||||
}
|
||||
|
||||
impl PatLike for ExprId {
|
||||
type BindingMode = ();
|
||||
|
||||
fn infer(
|
||||
this: &mut InferenceContext<'_>,
|
||||
id: Self,
|
||||
expected_ty: &Ty,
|
||||
_: Self::BindingMode,
|
||||
) -> Ty {
|
||||
this.infer_assignee_expr(id, expected_ty)
|
||||
}
|
||||
}
|
||||
|
||||
impl PatLike for PatId {
|
||||
type BindingMode = BindingMode;
|
||||
|
||||
fn infer(
|
||||
this: &mut InferenceContext<'_>,
|
||||
id: Self,
|
||||
expected_ty: &Ty,
|
||||
default_bm: Self::BindingMode,
|
||||
) -> Ty {
|
||||
this.infer_pat(id, expected_ty, default_bm)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct InferOk<T> {
|
||||
value: T,
|
||||
|
@ -200,11 +164,45 @@ pub(crate) type InferResult<T> = Result<InferOk<T>, TypeError>;
|
|||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum InferenceDiagnostic {
|
||||
NoSuchField { expr: ExprId },
|
||||
PrivateField { expr: ExprId, field: FieldId },
|
||||
PrivateAssocItem { id: ExprOrPatId, item: AssocItemId },
|
||||
BreakOutsideOfLoop { expr: ExprId, is_break: bool },
|
||||
MismatchedArgCount { call_expr: ExprId, expected: usize, found: usize },
|
||||
NoSuchField {
|
||||
expr: ExprId,
|
||||
},
|
||||
PrivateField {
|
||||
expr: ExprId,
|
||||
field: FieldId,
|
||||
},
|
||||
PrivateAssocItem {
|
||||
id: ExprOrPatId,
|
||||
item: AssocItemId,
|
||||
},
|
||||
UnresolvedField {
|
||||
expr: ExprId,
|
||||
receiver: Ty,
|
||||
name: Name,
|
||||
method_with_same_name_exists: bool,
|
||||
},
|
||||
UnresolvedMethodCall {
|
||||
expr: ExprId,
|
||||
receiver: Ty,
|
||||
name: Name,
|
||||
/// Contains the type the field resolves to
|
||||
field_with_same_name: Option<Ty>,
|
||||
},
|
||||
// FIXME: Make this proper
|
||||
BreakOutsideOfLoop {
|
||||
expr: ExprId,
|
||||
is_break: bool,
|
||||
bad_value_break: bool,
|
||||
},
|
||||
MismatchedArgCount {
|
||||
call_expr: ExprId,
|
||||
expected: usize,
|
||||
found: usize,
|
||||
},
|
||||
ExpectedFunction {
|
||||
call_expr: ExprId,
|
||||
found: Ty,
|
||||
},
|
||||
}
|
||||
|
||||
/// A mismatch between an expected and an inferred type.
|
||||
|
@ -293,8 +291,10 @@ pub enum Adjust {
|
|||
/// call, with the signature `&'a T -> &'a U` or `&'a mut T -> &'a mut U`.
|
||||
/// The target type is `U` in both cases, with the region and mutability
|
||||
/// being those shared by both the receiver and the returned reference.
|
||||
///
|
||||
/// Mutability is `None` when we are not sure.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct OverloadedDeref(pub Mutability);
|
||||
pub struct OverloadedDeref(pub Option<Mutability>);
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum AutoBorrow {
|
||||
|
@ -354,7 +354,10 @@ pub struct InferenceResult {
|
|||
/// **Note**: When a pattern type is resolved it may still contain
|
||||
/// unresolved or missing subpatterns or subpatterns of mismatched types.
|
||||
pub type_of_pat: ArenaMap<PatId, Ty>,
|
||||
pub type_of_binding: ArenaMap<BindingId, Ty>,
|
||||
pub type_of_rpit: ArenaMap<RpitId, Ty>,
|
||||
/// Type of the result of `.into_iter()` on the for. `ExprId` is the one of the whole for loop.
|
||||
pub type_of_for_iterator: FxHashMap<ExprId, Ty>,
|
||||
type_mismatches: FxHashMap<ExprOrPatId, TypeMismatch>,
|
||||
/// Interned common types to return references to.
|
||||
standard_types: InternedStandardTypes,
|
||||
|
@ -389,18 +392,15 @@ impl InferenceResult {
|
|||
pub fn type_mismatch_for_pat(&self, pat: PatId) -> Option<&TypeMismatch> {
|
||||
self.type_mismatches.get(&pat.into())
|
||||
}
|
||||
pub fn type_mismatches(&self) -> impl Iterator<Item = (ExprOrPatId, &TypeMismatch)> {
|
||||
self.type_mismatches.iter().map(|(expr_or_pat, mismatch)| (*expr_or_pat, mismatch))
|
||||
}
|
||||
pub fn expr_type_mismatches(&self) -> impl Iterator<Item = (ExprId, &TypeMismatch)> {
|
||||
self.type_mismatches.iter().filter_map(|(expr_or_pat, mismatch)| match *expr_or_pat {
|
||||
ExprOrPatId::ExprId(expr) => Some((expr, mismatch)),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
pub fn pat_type_mismatches(&self) -> impl Iterator<Item = (PatId, &TypeMismatch)> {
|
||||
self.type_mismatches.iter().filter_map(|(expr_or_pat, mismatch)| match *expr_or_pat {
|
||||
ExprOrPatId::PatId(pat) => Some((pat, mismatch)),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<ExprId> for InferenceResult {
|
||||
|
@ -419,6 +419,14 @@ impl Index<PatId> for InferenceResult {
|
|||
}
|
||||
}
|
||||
|
||||
impl Index<BindingId> for InferenceResult {
|
||||
type Output = Ty;
|
||||
|
||||
fn index(&self, b: BindingId) -> &Ty {
|
||||
self.type_of_binding.get(b).unwrap_or(&self.standard_types.unknown)
|
||||
}
|
||||
}
|
||||
|
||||
/// The inference context contains all information needed during type inference.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct InferenceContext<'a> {
|
||||
|
@ -428,14 +436,19 @@ pub(crate) struct InferenceContext<'a> {
|
|||
pub(crate) resolver: Resolver,
|
||||
table: unify::InferenceTable<'a>,
|
||||
trait_env: Arc<TraitEnvironment>,
|
||||
/// The traits in scope, disregarding block modules. This is used for caching purposes.
|
||||
traits_in_scope: FxHashSet<TraitId>,
|
||||
pub(crate) result: InferenceResult,
|
||||
/// The return type of the function being inferred, the closure or async block if we're
|
||||
/// currently within one.
|
||||
///
|
||||
/// We might consider using a nested inference context for checking
|
||||
/// closures, but currently this is the only field that will change there,
|
||||
/// so it doesn't make sense.
|
||||
/// closures so we can swap all shared things out at once.
|
||||
return_ty: Ty,
|
||||
/// If `Some`, this stores coercion information for returned
|
||||
/// expressions. If `None`, this is in a context where return is
|
||||
/// inappropriate, such as a const expression.
|
||||
return_coercion: Option<CoerceMany>,
|
||||
/// The resume type and the yield type, respectively, of the generator being inferred.
|
||||
resume_yield_tys: Option<(Ty, Ty)>,
|
||||
diverges: Diverges,
|
||||
|
@ -447,7 +460,7 @@ struct BreakableContext {
|
|||
/// Whether this context contains at least one break expression.
|
||||
may_break: bool,
|
||||
/// The coercion target of the context.
|
||||
coerce: CoerceMany,
|
||||
coerce: Option<CoerceMany>,
|
||||
/// The optional label of the context.
|
||||
label: Option<name::Name>,
|
||||
kind: BreakableKind,
|
||||
|
@ -503,16 +516,22 @@ impl<'a> InferenceContext<'a> {
|
|||
trait_env,
|
||||
return_ty: TyKind::Error.intern(Interner), // set in collect_* calls
|
||||
resume_yield_tys: None,
|
||||
return_coercion: None,
|
||||
db,
|
||||
owner,
|
||||
body,
|
||||
traits_in_scope: resolver.traits_in_scope(db.upcast()),
|
||||
resolver,
|
||||
diverges: Diverges::Maybe,
|
||||
breakables: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_all(self) -> InferenceResult {
|
||||
// FIXME: This function should be private in module. It is currently only used in the consteval, since we need
|
||||
// `InferenceResult` in the middle of inference. See the fixme comment in `consteval::eval_to_const`. If you
|
||||
// used this function for another workaround, mention it here. If you really need this function and believe that
|
||||
// there is no problem in it being `pub(crate)`, remove this comment.
|
||||
pub(crate) fn resolve_all(self) -> InferenceResult {
|
||||
let InferenceContext { mut table, mut result, .. } = self;
|
||||
|
||||
table.fallback_if_possible();
|
||||
|
@ -528,13 +547,46 @@ impl<'a> InferenceContext<'a> {
|
|||
for ty in result.type_of_pat.values_mut() {
|
||||
*ty = table.resolve_completely(ty.clone());
|
||||
}
|
||||
for ty in result.type_of_rpit.iter_mut().map(|x| x.1) {
|
||||
for ty in result.type_of_binding.values_mut() {
|
||||
*ty = table.resolve_completely(ty.clone());
|
||||
}
|
||||
for ty in result.type_of_rpit.values_mut() {
|
||||
*ty = table.resolve_completely(ty.clone());
|
||||
}
|
||||
for ty in result.type_of_for_iterator.values_mut() {
|
||||
*ty = table.resolve_completely(ty.clone());
|
||||
}
|
||||
for mismatch in result.type_mismatches.values_mut() {
|
||||
mismatch.expected = table.resolve_completely(mismatch.expected.clone());
|
||||
mismatch.actual = table.resolve_completely(mismatch.actual.clone());
|
||||
}
|
||||
result.diagnostics.retain_mut(|diagnostic| {
|
||||
if let InferenceDiagnostic::ExpectedFunction { found: ty, .. }
|
||||
| InferenceDiagnostic::UnresolvedField { receiver: ty, .. }
|
||||
| InferenceDiagnostic::UnresolvedMethodCall { receiver: ty, .. } = diagnostic
|
||||
{
|
||||
*ty = table.resolve_completely(ty.clone());
|
||||
// FIXME: Remove this when we are on par with rustc in terms of inference
|
||||
if ty.contains_unknown() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let InferenceDiagnostic::UnresolvedMethodCall { field_with_same_name, .. } =
|
||||
diagnostic
|
||||
{
|
||||
let clear = if let Some(ty) = field_with_same_name {
|
||||
*ty = table.resolve_completely(ty.clone());
|
||||
ty.contains_unknown()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if clear {
|
||||
*field_with_same_name = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
});
|
||||
for (_, subst) in result.method_resolutions.values_mut() {
|
||||
*subst = table.resolve_completely(subst.clone());
|
||||
}
|
||||
|
@ -580,7 +632,7 @@ impl<'a> InferenceContext<'a> {
|
|||
let ty = self.insert_type_vars(ty);
|
||||
let ty = self.normalize_associated_types_in(ty);
|
||||
|
||||
self.infer_pat(*pat, &ty, BindingMode::default());
|
||||
self.infer_top_pat(*pat, &ty);
|
||||
}
|
||||
let error_ty = &TypeRef::Error;
|
||||
let return_ty = if data.has_async_kw() {
|
||||
|
@ -632,10 +684,19 @@ impl<'a> InferenceContext<'a> {
|
|||
};
|
||||
|
||||
self.return_ty = self.normalize_associated_types_in(return_ty);
|
||||
self.return_coercion = Some(CoerceMany::new(self.return_ty.clone()));
|
||||
}
|
||||
|
||||
fn infer_body(&mut self) {
|
||||
self.infer_expr_coerce(self.body.body_expr, &Expectation::has_type(self.return_ty.clone()));
|
||||
match self.return_coercion {
|
||||
Some(_) => self.infer_return(self.body.body_expr),
|
||||
None => {
|
||||
_ = self.infer_expr_coerce(
|
||||
self.body.body_expr,
|
||||
&Expectation::has_type(self.return_ty.clone()),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_expr_ty(&mut self, expr: ExprId, ty: Ty) {
|
||||
|
@ -662,12 +723,15 @@ impl<'a> InferenceContext<'a> {
|
|||
self.result.type_of_pat.insert(pat, ty);
|
||||
}
|
||||
|
||||
fn write_binding_ty(&mut self, id: BindingId, ty: Ty) {
|
||||
self.result.type_of_binding.insert(id, ty);
|
||||
}
|
||||
|
||||
fn push_diagnostic(&mut self, diagnostic: InferenceDiagnostic) {
|
||||
self.result.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
fn make_ty(&mut self, type_ref: &TypeRef) -> Ty {
|
||||
// FIXME use right resolver for block
|
||||
let ctx = crate::lower::TyLoweringContext::new(self.db, &self.resolver);
|
||||
let ty = ctx.lower_ty(type_ref);
|
||||
let ty = self.insert_type_vars(ty);
|
||||
|
@ -681,11 +745,9 @@ impl<'a> InferenceContext<'a> {
|
|||
/// Replaces ConstScalar::Unknown by a new type var, so we can maybe still infer it.
|
||||
fn insert_const_vars_shallow(&mut self, c: Const) -> Const {
|
||||
let data = c.data(Interner);
|
||||
match data.value {
|
||||
match &data.value {
|
||||
ConstValue::Concrete(cc) => match cc.interned {
|
||||
hir_def::type_ref::ConstScalar::Unknown => {
|
||||
self.table.new_const_var(data.ty.clone())
|
||||
}
|
||||
crate::ConstScalar::Unknown => self.table.new_const_var(data.ty.clone()),
|
||||
_ => c,
|
||||
},
|
||||
_ => c,
|
||||
|
@ -785,12 +847,11 @@ impl<'a> InferenceContext<'a> {
|
|||
Some(path) => path,
|
||||
None => return (self.err_ty(), None),
|
||||
};
|
||||
let resolver = &self.resolver;
|
||||
let ctx = crate::lower::TyLoweringContext::new(self.db, &self.resolver);
|
||||
// FIXME: this should resolve assoc items as well, see this example:
|
||||
// https://play.rust-lang.org/?gist=087992e9e22495446c01c0d4e2d69521
|
||||
let (resolution, unresolved) = if value_ns {
|
||||
match resolver.resolve_path_in_value_ns(self.db.upcast(), path.mod_path()) {
|
||||
match self.resolver.resolve_path_in_value_ns(self.db.upcast(), path.mod_path()) {
|
||||
Some(ResolveValueResult::ValueNs(value)) => match value {
|
||||
ValueNs::EnumVariantId(var) => {
|
||||
let substs = ctx.substs_from_path(path, var.into(), true);
|
||||
|
@ -811,7 +872,7 @@ impl<'a> InferenceContext<'a> {
|
|||
None => return (self.err_ty(), None),
|
||||
}
|
||||
} else {
|
||||
match resolver.resolve_path_in_type_ns(self.db.upcast(), path.mod_path()) {
|
||||
match self.resolver.resolve_path_in_type_ns(self.db.upcast(), path.mod_path()) {
|
||||
Some(it) => it,
|
||||
None => return (self.err_ty(), None),
|
||||
}
|
||||
|
@ -866,7 +927,10 @@ impl<'a> InferenceContext<'a> {
|
|||
// FIXME potentially resolve assoc type
|
||||
(self.err_ty(), None)
|
||||
}
|
||||
TypeNs::AdtId(AdtId::EnumId(_)) | TypeNs::BuiltinType(_) | TypeNs::TraitId(_) => {
|
||||
TypeNs::AdtId(AdtId::EnumId(_))
|
||||
| TypeNs::BuiltinType(_)
|
||||
| TypeNs::TraitId(_)
|
||||
| TypeNs::TraitAliasId(_) => {
|
||||
// FIXME diagnostic
|
||||
(self.err_ty(), None)
|
||||
}
|
||||
|
@ -1018,6 +1082,15 @@ impl<'a> InferenceContext<'a> {
|
|||
let struct_ = self.resolve_lang_item(LangItem::VaList)?.as_struct()?;
|
||||
Some(struct_.into())
|
||||
}
|
||||
|
||||
fn get_traits_in_scope(&self) -> Either<FxHashSet<TraitId>, &FxHashSet<TraitId>> {
|
||||
let mut b_traits = self.resolver.traits_in_scope_from_block_scopes().peekable();
|
||||
if b_traits.peek().is_some() {
|
||||
Either::Left(self.traits_in_scope.iter().copied().chain(b_traits).collect())
|
||||
} else {
|
||||
Either::Right(&self.traits_in_scope)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// When inferring an expression, we propagate downward whatever type hint we
|
||||
|
|
|
@ -50,11 +50,44 @@ fn success(
|
|||
#[derive(Clone, Debug)]
|
||||
pub(super) struct CoerceMany {
|
||||
expected_ty: Ty,
|
||||
final_ty: Option<Ty>,
|
||||
}
|
||||
|
||||
impl CoerceMany {
|
||||
pub(super) fn new(expected: Ty) -> Self {
|
||||
CoerceMany { expected_ty: expected }
|
||||
CoerceMany { expected_ty: expected, final_ty: None }
|
||||
}
|
||||
|
||||
/// Returns the "expected type" with which this coercion was
|
||||
/// constructed. This represents the "downward propagated" type
|
||||
/// that was given to us at the start of typing whatever construct
|
||||
/// we are typing (e.g., the match expression).
|
||||
///
|
||||
/// Typically, this is used as the expected type when
|
||||
/// type-checking each of the alternative expressions whose types
|
||||
/// we are trying to merge.
|
||||
pub(super) fn expected_ty(&self) -> Ty {
|
||||
self.expected_ty.clone()
|
||||
}
|
||||
|
||||
/// Returns the current "merged type", representing our best-guess
|
||||
/// at the LUB of the expressions we've seen so far (if any). This
|
||||
/// isn't *final* until you call `self.complete()`, which will return
|
||||
/// the merged type.
|
||||
pub(super) fn merged_ty(&self) -> Ty {
|
||||
self.final_ty.clone().unwrap_or_else(|| self.expected_ty.clone())
|
||||
}
|
||||
|
||||
pub(super) fn complete(self, ctx: &mut InferenceContext<'_>) -> Ty {
|
||||
if let Some(final_ty) = self.final_ty {
|
||||
final_ty
|
||||
} else {
|
||||
ctx.result.standard_types.never.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn coerce_forced_unit(&mut self, ctx: &mut InferenceContext<'_>) {
|
||||
self.coerce(ctx, None, &ctx.result.standard_types.unit.clone())
|
||||
}
|
||||
|
||||
/// Merge two types from different branches, with possible coercion.
|
||||
|
@ -76,25 +109,25 @@ impl CoerceMany {
|
|||
// Special case: two function types. Try to coerce both to
|
||||
// pointers to have a chance at getting a match. See
|
||||
// https://github.com/rust-lang/rust/blob/7b805396bf46dce972692a6846ce2ad8481c5f85/src/librustc_typeck/check/coercion.rs#L877-L916
|
||||
let sig = match (self.expected_ty.kind(Interner), expr_ty.kind(Interner)) {
|
||||
let sig = match (self.merged_ty().kind(Interner), expr_ty.kind(Interner)) {
|
||||
(TyKind::FnDef(..) | TyKind::Closure(..), TyKind::FnDef(..) | TyKind::Closure(..)) => {
|
||||
// FIXME: we're ignoring safety here. To be more correct, if we have one FnDef and one Closure,
|
||||
// we should be coercing the closure to a fn pointer of the safety of the FnDef
|
||||
cov_mark::hit!(coerce_fn_reification);
|
||||
let sig =
|
||||
self.expected_ty.callable_sig(ctx.db).expect("FnDef without callable sig");
|
||||
self.merged_ty().callable_sig(ctx.db).expect("FnDef without callable sig");
|
||||
Some(sig)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
if let Some(sig) = sig {
|
||||
let target_ty = TyKind::Function(sig.to_fn_ptr()).intern(Interner);
|
||||
let result1 = ctx.table.coerce_inner(self.expected_ty.clone(), &target_ty);
|
||||
let result1 = ctx.table.coerce_inner(self.merged_ty(), &target_ty);
|
||||
let result2 = ctx.table.coerce_inner(expr_ty.clone(), &target_ty);
|
||||
if let (Ok(result1), Ok(result2)) = (result1, result2) {
|
||||
ctx.table.register_infer_ok(result1);
|
||||
ctx.table.register_infer_ok(result2);
|
||||
return self.expected_ty = target_ty;
|
||||
return self.final_ty = Some(target_ty);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,25 +135,20 @@ impl CoerceMany {
|
|||
// type is a type variable and the new one is `!`, trying it the other
|
||||
// way around first would mean we make the type variable `!`, instead of
|
||||
// just marking it as possibly diverging.
|
||||
if ctx.coerce(expr, &expr_ty, &self.expected_ty).is_ok() {
|
||||
/* self.expected_ty is already correct */
|
||||
} else if ctx.coerce(expr, &self.expected_ty, &expr_ty).is_ok() {
|
||||
self.expected_ty = expr_ty;
|
||||
if let Ok(res) = ctx.coerce(expr, &expr_ty, &self.merged_ty()) {
|
||||
self.final_ty = Some(res);
|
||||
} else if let Ok(res) = ctx.coerce(expr, &self.merged_ty(), &expr_ty) {
|
||||
self.final_ty = Some(res);
|
||||
} else {
|
||||
if let Some(id) = expr {
|
||||
ctx.result.type_mismatches.insert(
|
||||
id.into(),
|
||||
TypeMismatch { expected: self.expected_ty.clone(), actual: expr_ty },
|
||||
TypeMismatch { expected: self.merged_ty().clone(), actual: expr_ty.clone() },
|
||||
);
|
||||
}
|
||||
cov_mark::hit!(coerce_merge_fail_fallback);
|
||||
/* self.expected_ty is already correct */
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn complete(self) -> Ty {
|
||||
self.expected_ty
|
||||
}
|
||||
}
|
||||
|
||||
pub fn could_coerce(
|
||||
|
@ -665,7 +693,7 @@ pub(super) fn auto_deref_adjust_steps(autoderef: &Autoderef<'_, '_>) -> Vec<Adju
|
|||
.iter()
|
||||
.map(|(kind, _source)| match kind {
|
||||
// We do not know what kind of deref we require at this point yet
|
||||
AutoderefKind::Overloaded => Some(OverloadedDeref(Mutability::Not)),
|
||||
AutoderefKind::Overloaded => Some(OverloadedDeref(None)),
|
||||
AutoderefKind::Builtin => None,
|
||||
})
|
||||
.zip(targets)
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -4,22 +4,60 @@ use std::iter::repeat_with;
|
|||
|
||||
use chalk_ir::Mutability;
|
||||
use hir_def::{
|
||||
expr::{BindingAnnotation, Expr, Literal, Pat, PatId},
|
||||
body::Body,
|
||||
expr::{
|
||||
Binding, BindingAnnotation, BindingId, Expr, ExprId, ExprOrPatId, Literal, Pat, PatId,
|
||||
RecordFieldPat,
|
||||
},
|
||||
path::Path,
|
||||
type_ref::ConstScalar,
|
||||
};
|
||||
use hir_expand::name::Name;
|
||||
|
||||
use crate::{
|
||||
consteval::intern_const_scalar,
|
||||
consteval::{try_const_usize, usize_const},
|
||||
infer::{BindingMode, Expectation, InferenceContext, TypeMismatch},
|
||||
lower::lower_to_chalk_mutability,
|
||||
primitive::UintTy,
|
||||
static_lifetime, ConcreteConst, ConstValue, Interner, Scalar, Substitution, Ty, TyBuilder,
|
||||
TyExt, TyKind,
|
||||
static_lifetime, Interner, Scalar, Substitution, Ty, TyBuilder, TyExt, TyKind,
|
||||
};
|
||||
|
||||
use super::PatLike;
|
||||
/// Used to generalize patterns and assignee expressions.
|
||||
pub(super) trait PatLike: Into<ExprOrPatId> + Copy {
|
||||
type BindingMode: Copy;
|
||||
|
||||
fn infer(
|
||||
this: &mut InferenceContext<'_>,
|
||||
id: Self,
|
||||
expected_ty: &Ty,
|
||||
default_bm: Self::BindingMode,
|
||||
) -> Ty;
|
||||
}
|
||||
|
||||
impl PatLike for ExprId {
|
||||
type BindingMode = ();
|
||||
|
||||
fn infer(
|
||||
this: &mut InferenceContext<'_>,
|
||||
id: Self,
|
||||
expected_ty: &Ty,
|
||||
(): Self::BindingMode,
|
||||
) -> Ty {
|
||||
this.infer_assignee_expr(id, expected_ty)
|
||||
}
|
||||
}
|
||||
|
||||
impl PatLike for PatId {
|
||||
type BindingMode = BindingMode;
|
||||
|
||||
fn infer(
|
||||
this: &mut InferenceContext<'_>,
|
||||
id: Self,
|
||||
expected_ty: &Ty,
|
||||
default_bm: Self::BindingMode,
|
||||
) -> Ty {
|
||||
this.infer_pat(id, expected_ty, default_bm)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> InferenceContext<'a> {
|
||||
/// Infers type for tuple struct pattern or its corresponding assignee expression.
|
||||
|
@ -112,6 +150,7 @@ impl<'a> InferenceContext<'a> {
|
|||
ellipsis: Option<usize>,
|
||||
subs: &[T],
|
||||
) -> Ty {
|
||||
let expected = self.resolve_ty_shallow(expected);
|
||||
let expectations = match expected.as_tuple() {
|
||||
Some(parameters) => &*parameters.as_slice(Interner),
|
||||
_ => &[],
|
||||
|
@ -145,12 +184,11 @@ impl<'a> InferenceContext<'a> {
|
|||
.intern(Interner)
|
||||
}
|
||||
|
||||
pub(super) fn infer_pat(
|
||||
&mut self,
|
||||
pat: PatId,
|
||||
expected: &Ty,
|
||||
mut default_bm: BindingMode,
|
||||
) -> Ty {
|
||||
pub(super) fn infer_top_pat(&mut self, pat: PatId, expected: &Ty) {
|
||||
self.infer_pat(pat, expected, BindingMode::default());
|
||||
}
|
||||
|
||||
fn infer_pat(&mut self, pat: PatId, expected: &Ty, mut default_bm: BindingMode) -> Ty {
|
||||
let mut expected = self.resolve_ty_shallow(expected);
|
||||
|
||||
if is_non_ref_pat(self.body, pat) {
|
||||
|
@ -185,30 +223,17 @@ impl<'a> InferenceContext<'a> {
|
|||
self.infer_tuple_pat_like(&expected, default_bm, *ellipsis, args)
|
||||
}
|
||||
Pat::Or(pats) => {
|
||||
if let Some((first_pat, rest)) = pats.split_first() {
|
||||
let ty = self.infer_pat(*first_pat, &expected, default_bm);
|
||||
for pat in rest {
|
||||
self.infer_pat(*pat, &expected, default_bm);
|
||||
}
|
||||
ty
|
||||
} else {
|
||||
self.err_ty()
|
||||
for pat in pats.iter() {
|
||||
self.infer_pat(*pat, &expected, default_bm);
|
||||
}
|
||||
expected.clone()
|
||||
}
|
||||
Pat::Ref { pat, mutability } => {
|
||||
let mutability = lower_to_chalk_mutability(*mutability);
|
||||
let expectation = match expected.as_reference() {
|
||||
Some((inner_ty, _lifetime, exp_mut)) => {
|
||||
if mutability != exp_mut {
|
||||
// FIXME: emit type error?
|
||||
}
|
||||
inner_ty.clone()
|
||||
}
|
||||
_ => self.result.standard_types.unknown.clone(),
|
||||
};
|
||||
let subty = self.infer_pat(*pat, &expectation, default_bm);
|
||||
TyKind::Ref(mutability, static_lifetime(), subty).intern(Interner)
|
||||
}
|
||||
&Pat::Ref { pat, mutability } => self.infer_ref_pat(
|
||||
pat,
|
||||
lower_to_chalk_mutability(mutability),
|
||||
&expected,
|
||||
default_bm,
|
||||
),
|
||||
Pat::TupleStruct { path: p, args: subpats, ellipsis } => self
|
||||
.infer_tuple_struct_pat_like(
|
||||
p.as_deref(),
|
||||
|
@ -223,72 +248,14 @@ impl<'a> InferenceContext<'a> {
|
|||
self.infer_record_pat_like(p.as_deref(), &expected, default_bm, pat, subs)
|
||||
}
|
||||
Pat::Path(path) => {
|
||||
// FIXME use correct resolver for the surrounding expression
|
||||
let resolver = self.resolver.clone();
|
||||
self.infer_path(&resolver, path, pat.into()).unwrap_or_else(|| self.err_ty())
|
||||
// FIXME update resolver for the surrounding expression
|
||||
self.infer_path(path, pat.into()).unwrap_or_else(|| self.err_ty())
|
||||
}
|
||||
Pat::Bind { mode, name: _, subpat } => {
|
||||
let mode = if mode == &BindingAnnotation::Unannotated {
|
||||
default_bm
|
||||
} else {
|
||||
BindingMode::convert(*mode)
|
||||
};
|
||||
self.result.pat_binding_modes.insert(pat, mode);
|
||||
|
||||
let inner_ty = match subpat {
|
||||
Some(subpat) => self.infer_pat(*subpat, &expected, default_bm),
|
||||
None => expected,
|
||||
};
|
||||
let inner_ty = self.insert_type_vars_shallow(inner_ty);
|
||||
|
||||
let bound_ty = match mode {
|
||||
BindingMode::Ref(mutability) => {
|
||||
TyKind::Ref(mutability, static_lifetime(), inner_ty.clone())
|
||||
.intern(Interner)
|
||||
}
|
||||
BindingMode::Move => inner_ty.clone(),
|
||||
};
|
||||
self.write_pat_ty(pat, bound_ty);
|
||||
return inner_ty;
|
||||
Pat::Bind { id, subpat } => {
|
||||
return self.infer_bind_pat(pat, *id, default_bm, *subpat, &expected);
|
||||
}
|
||||
Pat::Slice { prefix, slice, suffix } => {
|
||||
let elem_ty = match expected.kind(Interner) {
|
||||
TyKind::Array(st, _) | TyKind::Slice(st) => st.clone(),
|
||||
_ => self.err_ty(),
|
||||
};
|
||||
|
||||
for &pat_id in prefix.iter().chain(suffix.iter()) {
|
||||
self.infer_pat(pat_id, &elem_ty, default_bm);
|
||||
}
|
||||
|
||||
if let &Some(slice_pat_id) = slice {
|
||||
let rest_pat_ty = match expected.kind(Interner) {
|
||||
TyKind::Array(_, length) => {
|
||||
let len = match length.data(Interner).value {
|
||||
ConstValue::Concrete(ConcreteConst {
|
||||
interned: ConstScalar::UInt(len),
|
||||
}) => len.checked_sub((prefix.len() + suffix.len()) as u128),
|
||||
_ => None,
|
||||
};
|
||||
TyKind::Array(
|
||||
elem_ty.clone(),
|
||||
intern_const_scalar(
|
||||
len.map_or(ConstScalar::Unknown, |len| ConstScalar::UInt(len)),
|
||||
TyBuilder::usize(),
|
||||
),
|
||||
)
|
||||
}
|
||||
_ => TyKind::Slice(elem_ty.clone()),
|
||||
}
|
||||
.intern(Interner);
|
||||
self.infer_pat(slice_pat_id, &rest_pat_ty, default_bm);
|
||||
}
|
||||
|
||||
match expected.kind(Interner) {
|
||||
TyKind::Array(_, const_) => TyKind::Array(elem_ty, const_.clone()),
|
||||
_ => TyKind::Slice(elem_ty),
|
||||
}
|
||||
.intern(Interner)
|
||||
self.infer_slice_pat(&expected, prefix, slice, suffix, default_bm)
|
||||
}
|
||||
Pat::Wild => expected.clone(),
|
||||
Pat::Range { start, end } => {
|
||||
|
@ -296,27 +263,10 @@ impl<'a> InferenceContext<'a> {
|
|||
self.infer_expr(*end, &Expectation::has_type(start_ty))
|
||||
}
|
||||
&Pat::Lit(expr) => {
|
||||
// FIXME: using `Option` here is a workaround until we can use if-let chains in stable.
|
||||
let mut pat_ty = None;
|
||||
|
||||
// Like slice patterns, byte string patterns can denote both `&[u8; N]` and `&[u8]`.
|
||||
if let Expr::Literal(Literal::ByteString(_)) = self.body[expr] {
|
||||
if let Some((inner, ..)) = expected.as_reference() {
|
||||
let inner = self.resolve_ty_shallow(inner);
|
||||
if matches!(inner.kind(Interner), TyKind::Slice(_)) {
|
||||
let elem_ty = TyKind::Scalar(Scalar::Uint(UintTy::U8)).intern(Interner);
|
||||
let slice_ty = TyKind::Slice(elem_ty).intern(Interner);
|
||||
let ty = TyKind::Ref(Mutability::Not, static_lifetime(), slice_ty)
|
||||
.intern(Interner);
|
||||
self.write_expr_ty(expr, ty.clone());
|
||||
pat_ty = Some(ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pat_ty.unwrap_or_else(|| {
|
||||
self.infer_expr(expr, &Expectation::has_type(expected.clone()))
|
||||
})
|
||||
// Don't emit type mismatches again, the expression lowering already did that.
|
||||
let ty = self.infer_lit_pat(expr, &expected);
|
||||
self.write_pat_ty(pat, ty.clone());
|
||||
return ty;
|
||||
}
|
||||
Pat::Box { inner } => match self.resolve_boxed_box() {
|
||||
Some(box_adt) => {
|
||||
|
@ -345,7 +295,8 @@ impl<'a> InferenceContext<'a> {
|
|||
};
|
||||
// use a new type variable if we got error type here
|
||||
let ty = self.insert_type_vars_shallow(ty);
|
||||
if !self.unify(&ty, &expected) {
|
||||
// FIXME: This never check is odd, but required with out we do inference right now
|
||||
if !expected.is_never() && !self.unify(&ty, &expected) {
|
||||
self.result
|
||||
.type_mismatches
|
||||
.insert(pat.into(), TypeMismatch { expected, actual: ty.clone() });
|
||||
|
@ -353,6 +304,111 @@ impl<'a> InferenceContext<'a> {
|
|||
self.write_pat_ty(pat, ty.clone());
|
||||
ty
|
||||
}
|
||||
|
||||
fn infer_ref_pat(
|
||||
&mut self,
|
||||
pat: PatId,
|
||||
mutability: Mutability,
|
||||
expected: &Ty,
|
||||
default_bm: BindingMode,
|
||||
) -> Ty {
|
||||
let expectation = match expected.as_reference() {
|
||||
Some((inner_ty, _lifetime, _exp_mut)) => inner_ty.clone(),
|
||||
_ => self.result.standard_types.unknown.clone(),
|
||||
};
|
||||
let subty = self.infer_pat(pat, &expectation, default_bm);
|
||||
TyKind::Ref(mutability, static_lifetime(), subty).intern(Interner)
|
||||
}
|
||||
|
||||
fn infer_bind_pat(
|
||||
&mut self,
|
||||
pat: PatId,
|
||||
binding: BindingId,
|
||||
default_bm: BindingMode,
|
||||
subpat: Option<PatId>,
|
||||
expected: &Ty,
|
||||
) -> Ty {
|
||||
let Binding { mode, .. } = self.body.bindings[binding];
|
||||
let mode = if mode == BindingAnnotation::Unannotated {
|
||||
default_bm
|
||||
} else {
|
||||
BindingMode::convert(mode)
|
||||
};
|
||||
self.result.pat_binding_modes.insert(pat, mode);
|
||||
|
||||
let inner_ty = match subpat {
|
||||
Some(subpat) => self.infer_pat(subpat, &expected, default_bm),
|
||||
None => expected.clone(),
|
||||
};
|
||||
let inner_ty = self.insert_type_vars_shallow(inner_ty);
|
||||
|
||||
let bound_ty = match mode {
|
||||
BindingMode::Ref(mutability) => {
|
||||
TyKind::Ref(mutability, static_lifetime(), inner_ty.clone()).intern(Interner)
|
||||
}
|
||||
BindingMode::Move => inner_ty.clone(),
|
||||
};
|
||||
self.write_pat_ty(pat, bound_ty.clone());
|
||||
self.write_binding_ty(binding, bound_ty);
|
||||
return inner_ty;
|
||||
}
|
||||
|
||||
fn infer_slice_pat(
|
||||
&mut self,
|
||||
expected: &Ty,
|
||||
prefix: &[PatId],
|
||||
slice: &Option<PatId>,
|
||||
suffix: &[PatId],
|
||||
default_bm: BindingMode,
|
||||
) -> Ty {
|
||||
let elem_ty = match expected.kind(Interner) {
|
||||
TyKind::Array(st, _) | TyKind::Slice(st) => st.clone(),
|
||||
_ => self.err_ty(),
|
||||
};
|
||||
|
||||
for &pat_id in prefix.iter().chain(suffix.iter()) {
|
||||
self.infer_pat(pat_id, &elem_ty, default_bm);
|
||||
}
|
||||
|
||||
if let &Some(slice_pat_id) = slice {
|
||||
let rest_pat_ty = match expected.kind(Interner) {
|
||||
TyKind::Array(_, length) => {
|
||||
let len = try_const_usize(length);
|
||||
let len =
|
||||
len.and_then(|len| len.checked_sub((prefix.len() + suffix.len()) as u128));
|
||||
TyKind::Array(elem_ty.clone(), usize_const(self.db, len, self.resolver.krate()))
|
||||
}
|
||||
_ => TyKind::Slice(elem_ty.clone()),
|
||||
}
|
||||
.intern(Interner);
|
||||
self.infer_pat(slice_pat_id, &rest_pat_ty, default_bm);
|
||||
}
|
||||
|
||||
match expected.kind(Interner) {
|
||||
TyKind::Array(_, const_) => TyKind::Array(elem_ty, const_.clone()),
|
||||
_ => TyKind::Slice(elem_ty),
|
||||
}
|
||||
.intern(Interner)
|
||||
}
|
||||
|
||||
fn infer_lit_pat(&mut self, expr: ExprId, expected: &Ty) -> Ty {
|
||||
// Like slice patterns, byte string patterns can denote both `&[u8; N]` and `&[u8]`.
|
||||
if let Expr::Literal(Literal::ByteString(_)) = self.body[expr] {
|
||||
if let Some((inner, ..)) = expected.as_reference() {
|
||||
let inner = self.resolve_ty_shallow(inner);
|
||||
if matches!(inner.kind(Interner), TyKind::Slice(_)) {
|
||||
let elem_ty = TyKind::Scalar(Scalar::Uint(UintTy::U8)).intern(Interner);
|
||||
let slice_ty = TyKind::Slice(elem_ty).intern(Interner);
|
||||
let ty =
|
||||
TyKind::Ref(Mutability::Not, static_lifetime(), slice_ty).intern(Interner);
|
||||
self.write_expr_ty(expr, ty.clone());
|
||||
return ty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.infer_expr(expr, &Expectation::has_type(expected.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
fn is_non_ref_pat(body: &hir_def::body::Body, pat: PatId) -> bool {
|
||||
|
@ -369,11 +425,52 @@ fn is_non_ref_pat(body: &hir_def::body::Body, pat: PatId) -> bool {
|
|||
Pat::Lit(expr) => {
|
||||
!matches!(body[*expr], Expr::Literal(Literal::String(..) | Literal::ByteString(..)))
|
||||
}
|
||||
Pat::Bind {
|
||||
mode: BindingAnnotation::Mutable | BindingAnnotation::Unannotated,
|
||||
subpat: Some(subpat),
|
||||
..
|
||||
} => is_non_ref_pat(body, *subpat),
|
||||
Pat::Bind { id, subpat: Some(subpat), .. }
|
||||
if matches!(
|
||||
body.bindings[*id].mode,
|
||||
BindingAnnotation::Mutable | BindingAnnotation::Unannotated
|
||||
) =>
|
||||
{
|
||||
is_non_ref_pat(body, *subpat)
|
||||
}
|
||||
Pat::Wild | Pat::Bind { .. } | Pat::Ref { .. } | Pat::Box { .. } | Pat::Missing => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn contains_explicit_ref_binding(body: &Body, pat_id: PatId) -> bool {
|
||||
let mut res = false;
|
||||
walk_pats(body, pat_id, &mut |pat| {
|
||||
res |= matches!(pat, Pat::Bind { id, .. } if body.bindings[*id].mode == BindingAnnotation::Ref);
|
||||
});
|
||||
res
|
||||
}
|
||||
|
||||
fn walk_pats(body: &Body, pat_id: PatId, f: &mut impl FnMut(&Pat)) {
|
||||
let pat = &body[pat_id];
|
||||
f(pat);
|
||||
match pat {
|
||||
Pat::Range { .. }
|
||||
| Pat::Lit(..)
|
||||
| Pat::Path(..)
|
||||
| Pat::ConstBlock(..)
|
||||
| Pat::Wild
|
||||
| Pat::Missing => {}
|
||||
&Pat::Bind { subpat, .. } => {
|
||||
if let Some(subpat) = subpat {
|
||||
walk_pats(body, subpat, f);
|
||||
}
|
||||
}
|
||||
Pat::Or(args) | Pat::Tuple { args, .. } | Pat::TupleStruct { args, .. } => {
|
||||
args.iter().copied().for_each(|p| walk_pats(body, p, f));
|
||||
}
|
||||
Pat::Ref { pat, .. } => walk_pats(body, *pat, f),
|
||||
Pat::Slice { prefix, slice, suffix } => {
|
||||
let total_iter = prefix.iter().chain(slice.iter()).chain(suffix.iter());
|
||||
total_iter.copied().for_each(|p| walk_pats(body, p, f));
|
||||
}
|
||||
Pat::Record { args, .. } => {
|
||||
args.iter().for_each(|RecordFieldPat { pat, .. }| walk_pats(body, *pat, f));
|
||||
}
|
||||
Pat::Box { inner } => walk_pats(body, *inner, f),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
use chalk_ir::cast::Cast;
|
||||
use hir_def::{
|
||||
path::{Path, PathSegment},
|
||||
resolver::{ResolveValueResult, Resolver, TypeNs, ValueNs},
|
||||
resolver::{ResolveValueResult, TypeNs, ValueNs},
|
||||
AdtId, AssocItemId, EnumVariantId, ItemContainerId, Lookup,
|
||||
};
|
||||
use hir_expand::name::Name;
|
||||
|
@ -21,55 +21,42 @@ use crate::{
|
|||
use super::{ExprOrPatId, InferenceContext, TraitRef};
|
||||
|
||||
impl<'a> InferenceContext<'a> {
|
||||
pub(super) fn infer_path(
|
||||
&mut self,
|
||||
resolver: &Resolver,
|
||||
path: &Path,
|
||||
id: ExprOrPatId,
|
||||
) -> Option<Ty> {
|
||||
let ty = self.resolve_value_path(resolver, path, id)?;
|
||||
pub(super) fn infer_path(&mut self, path: &Path, id: ExprOrPatId) -> Option<Ty> {
|
||||
let ty = self.resolve_value_path(path, id)?;
|
||||
let ty = self.insert_type_vars(ty);
|
||||
let ty = self.normalize_associated_types_in(ty);
|
||||
Some(ty)
|
||||
}
|
||||
|
||||
fn resolve_value_path(
|
||||
&mut self,
|
||||
resolver: &Resolver,
|
||||
path: &Path,
|
||||
id: ExprOrPatId,
|
||||
) -> Option<Ty> {
|
||||
fn resolve_value_path(&mut self, path: &Path, id: ExprOrPatId) -> Option<Ty> {
|
||||
let (value, self_subst) = if let Some(type_ref) = path.type_anchor() {
|
||||
if path.segments().is_empty() {
|
||||
// This can't actually happen syntax-wise
|
||||
return None;
|
||||
}
|
||||
let Some(last) = path.segments().last() else { return None };
|
||||
let ty = self.make_ty(type_ref);
|
||||
let remaining_segments_for_ty = path.segments().take(path.segments().len() - 1);
|
||||
let ctx = crate::lower::TyLoweringContext::new(self.db, resolver);
|
||||
let ctx = crate::lower::TyLoweringContext::new(self.db, &self.resolver);
|
||||
let (ty, _) = ctx.lower_ty_relative_path(ty, None, remaining_segments_for_ty);
|
||||
self.resolve_ty_assoc_item(
|
||||
ty,
|
||||
path.segments().last().expect("path had at least one segment").name,
|
||||
id,
|
||||
)?
|
||||
self.resolve_ty_assoc_item(ty, last.name, id).map(|(it, substs)| (it, Some(substs)))?
|
||||
} else {
|
||||
// FIXME: report error, unresolved first path segment
|
||||
let value_or_partial =
|
||||
resolver.resolve_path_in_value_ns(self.db.upcast(), path.mod_path())?;
|
||||
self.resolver.resolve_path_in_value_ns(self.db.upcast(), path.mod_path())?;
|
||||
|
||||
match value_or_partial {
|
||||
ResolveValueResult::ValueNs(it) => (it, None),
|
||||
ResolveValueResult::Partial(def, remaining_index) => {
|
||||
self.resolve_assoc_item(def, path, remaining_index, id)?
|
||||
}
|
||||
ResolveValueResult::Partial(def, remaining_index) => self
|
||||
.resolve_assoc_item(def, path, remaining_index, id)
|
||||
.map(|(it, substs)| (it, Some(substs)))?,
|
||||
}
|
||||
};
|
||||
|
||||
let typable: ValueTyDefId = match value {
|
||||
ValueNs::LocalBinding(pat) => {
|
||||
let ty = self.result.type_of_pat.get(pat)?.clone();
|
||||
return Some(ty);
|
||||
}
|
||||
ValueNs::LocalBinding(pat) => match self.result.type_of_binding.get(pat) {
|
||||
Some(ty) => return Some(ty.clone()),
|
||||
None => {
|
||||
never!("uninferred pattern?");
|
||||
return None;
|
||||
}
|
||||
},
|
||||
ValueNs::FunctionId(it) => it.into(),
|
||||
ValueNs::ConstId(it) => it.into(),
|
||||
ValueNs::StaticId(it) => it.into(),
|
||||
|
@ -91,7 +78,7 @@ impl<'a> InferenceContext<'a> {
|
|||
let ty = self.db.value_ty(struct_id.into()).substitute(Interner, &substs);
|
||||
return Some(ty);
|
||||
} else {
|
||||
// FIXME: diagnostic, invalid Self reference
|
||||
// FIXME: report error, invalid Self reference
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
@ -126,7 +113,7 @@ impl<'a> InferenceContext<'a> {
|
|||
path: &Path,
|
||||
remaining_index: usize,
|
||||
id: ExprOrPatId,
|
||||
) -> Option<(ValueNs, Option<Substitution>)> {
|
||||
) -> Option<(ValueNs, Substitution)> {
|
||||
assert!(remaining_index < path.segments().len());
|
||||
// there may be more intermediate segments between the resolved one and
|
||||
// the end. Only the last segment needs to be resolved to a value; from
|
||||
|
@ -179,7 +166,7 @@ impl<'a> InferenceContext<'a> {
|
|||
trait_ref: TraitRef,
|
||||
segment: PathSegment<'_>,
|
||||
id: ExprOrPatId,
|
||||
) -> Option<(ValueNs, Option<Substitution>)> {
|
||||
) -> Option<(ValueNs, Substitution)> {
|
||||
let trait_ = trait_ref.hir_trait_id();
|
||||
let item =
|
||||
self.db.trait_data(trait_).items.iter().map(|(_name, id)| (*id)).find_map(|item| {
|
||||
|
@ -215,7 +202,7 @@ impl<'a> InferenceContext<'a> {
|
|||
};
|
||||
|
||||
self.write_assoc_resolution(id, item, trait_ref.substitution.clone());
|
||||
Some((def, Some(trait_ref.substitution)))
|
||||
Some((def, trait_ref.substitution))
|
||||
}
|
||||
|
||||
fn resolve_ty_assoc_item(
|
||||
|
@ -223,7 +210,7 @@ impl<'a> InferenceContext<'a> {
|
|||
ty: Ty,
|
||||
name: &Name,
|
||||
id: ExprOrPatId,
|
||||
) -> Option<(ValueNs, Option<Substitution>)> {
|
||||
) -> Option<(ValueNs, Substitution)> {
|
||||
if let TyKind::Error = ty.kind(Interner) {
|
||||
return None;
|
||||
}
|
||||
|
@ -233,70 +220,66 @@ impl<'a> InferenceContext<'a> {
|
|||
}
|
||||
|
||||
let canonical_ty = self.canonicalize(ty.clone());
|
||||
let traits_in_scope = self.resolver.traits_in_scope(self.db.upcast());
|
||||
|
||||
let mut not_visible = None;
|
||||
let res = method_resolution::iterate_method_candidates(
|
||||
&canonical_ty.value,
|
||||
self.db,
|
||||
self.table.trait_env.clone(),
|
||||
&traits_in_scope,
|
||||
self.get_traits_in_scope().as_ref().left_or_else(|&it| it),
|
||||
VisibleFromModule::Filter(self.resolver.module()),
|
||||
Some(name),
|
||||
method_resolution::LookupMode::Path,
|
||||
|_ty, item, visible| {
|
||||
let (def, container) = match item {
|
||||
AssocItemId::FunctionId(f) => {
|
||||
(ValueNs::FunctionId(f), f.lookup(self.db.upcast()).container)
|
||||
}
|
||||
AssocItemId::ConstId(c) => {
|
||||
(ValueNs::ConstId(c), c.lookup(self.db.upcast()).container)
|
||||
}
|
||||
AssocItemId::TypeAliasId(_) => unreachable!(),
|
||||
};
|
||||
let substs = match container {
|
||||
ItemContainerId::ImplId(impl_id) => {
|
||||
let impl_substs = TyBuilder::subst_for_def(self.db, impl_id, None)
|
||||
.fill_with_inference_vars(&mut self.table)
|
||||
.build();
|
||||
let impl_self_ty =
|
||||
self.db.impl_self_ty(impl_id).substitute(Interner, &impl_substs);
|
||||
self.unify(&impl_self_ty, &ty);
|
||||
impl_substs
|
||||
}
|
||||
ItemContainerId::TraitId(trait_) => {
|
||||
// we're picking this method
|
||||
let trait_ref = TyBuilder::trait_ref(self.db, trait_)
|
||||
.push(ty.clone())
|
||||
.fill_with_inference_vars(&mut self.table)
|
||||
.build();
|
||||
self.push_obligation(trait_ref.clone().cast(Interner));
|
||||
trait_ref.substitution
|
||||
}
|
||||
ItemContainerId::ModuleId(_) | ItemContainerId::ExternBlockId(_) => {
|
||||
never!("assoc item contained in module/extern block");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
if visible {
|
||||
Some((def, item, Some(substs), true))
|
||||
Some((item, true))
|
||||
} else {
|
||||
if not_visible.is_none() {
|
||||
not_visible = Some((def, item, Some(substs), false));
|
||||
not_visible = Some((item, false));
|
||||
}
|
||||
None
|
||||
}
|
||||
},
|
||||
);
|
||||
let res = res.or(not_visible);
|
||||
if let Some((_, item, Some(ref substs), visible)) = res {
|
||||
self.write_assoc_resolution(id, item, substs.clone());
|
||||
if !visible {
|
||||
self.push_diagnostic(InferenceDiagnostic::PrivateAssocItem { id, item })
|
||||
let (item, visible) = res?;
|
||||
|
||||
let (def, container) = match item {
|
||||
AssocItemId::FunctionId(f) => {
|
||||
(ValueNs::FunctionId(f), f.lookup(self.db.upcast()).container)
|
||||
}
|
||||
AssocItemId::ConstId(c) => (ValueNs::ConstId(c), c.lookup(self.db.upcast()).container),
|
||||
AssocItemId::TypeAliasId(_) => unreachable!(),
|
||||
};
|
||||
let substs = match container {
|
||||
ItemContainerId::ImplId(impl_id) => {
|
||||
let impl_substs = TyBuilder::subst_for_def(self.db, impl_id, None)
|
||||
.fill_with_inference_vars(&mut self.table)
|
||||
.build();
|
||||
let impl_self_ty = self.db.impl_self_ty(impl_id).substitute(Interner, &impl_substs);
|
||||
self.unify(&impl_self_ty, &ty);
|
||||
impl_substs
|
||||
}
|
||||
ItemContainerId::TraitId(trait_) => {
|
||||
// we're picking this method
|
||||
let trait_ref = TyBuilder::trait_ref(self.db, trait_)
|
||||
.push(ty.clone())
|
||||
.fill_with_inference_vars(&mut self.table)
|
||||
.build();
|
||||
self.push_obligation(trait_ref.clone().cast(Interner));
|
||||
trait_ref.substitution
|
||||
}
|
||||
ItemContainerId::ModuleId(_) | ItemContainerId::ExternBlockId(_) => {
|
||||
never!("assoc item contained in module/extern block");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
self.write_assoc_resolution(id, item, substs.clone());
|
||||
if !visible {
|
||||
self.push_diagnostic(InferenceDiagnostic::PrivateAssocItem { id, item });
|
||||
}
|
||||
res.map(|(def, _, substs, _)| (def, substs))
|
||||
Some((def, substs))
|
||||
}
|
||||
|
||||
fn resolve_enum_variant_on_ty(
|
||||
|
@ -304,7 +287,7 @@ impl<'a> InferenceContext<'a> {
|
|||
ty: &Ty,
|
||||
name: &Name,
|
||||
id: ExprOrPatId,
|
||||
) -> Option<(ValueNs, Option<Substitution>)> {
|
||||
) -> Option<(ValueNs, Substitution)> {
|
||||
let ty = self.resolve_ty_shallow(ty);
|
||||
let (enum_id, subst) = match ty.as_adt() {
|
||||
Some((AdtId::EnumId(e), subst)) => (e, subst),
|
||||
|
@ -314,6 +297,6 @@ impl<'a> InferenceContext<'a> {
|
|||
let local_id = enum_data.variant(name)?;
|
||||
let variant = EnumVariantId { parent: enum_id, local_id };
|
||||
self.write_variant_resolution(id, variant.into());
|
||||
Some((ValueNs::EnumVariantId(variant), Some(subst.clone())))
|
||||
Some((ValueNs::EnumVariantId(variant), subst.clone()))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -704,14 +704,13 @@ impl<'a> fmt::Debug for InferenceTable<'a> {
|
|||
mod resolve {
|
||||
use super::InferenceTable;
|
||||
use crate::{
|
||||
ConcreteConst, Const, ConstData, ConstValue, DebruijnIndex, GenericArg, InferenceVar,
|
||||
Interner, Lifetime, Ty, TyVariableKind, VariableKind,
|
||||
ConcreteConst, Const, ConstData, ConstScalar, ConstValue, DebruijnIndex, GenericArg,
|
||||
InferenceVar, Interner, Lifetime, Ty, TyVariableKind, VariableKind,
|
||||
};
|
||||
use chalk_ir::{
|
||||
cast::Cast,
|
||||
fold::{TypeFoldable, TypeFolder},
|
||||
};
|
||||
use hir_def::type_ref::ConstScalar;
|
||||
|
||||
#[derive(chalk_derive::FallibleTypeFolder)]
|
||||
#[has_interner(Interner)]
|
||||
|
|
|
@ -6,12 +6,12 @@ use chalk_ir::{
|
|||
DebruijnIndex,
|
||||
};
|
||||
use hir_def::{
|
||||
adt::VariantData, attr::Attrs, type_ref::ConstScalar, visibility::Visibility, AdtId,
|
||||
EnumVariantId, HasModule, Lookup, ModuleId, VariantId,
|
||||
adt::VariantData, attr::Attrs, visibility::Visibility, AdtId, EnumVariantId, HasModule, Lookup,
|
||||
ModuleId, VariantId,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
db::HirDatabase, Binders, ConcreteConst, Const, ConstValue, Interner, Substitution, Ty, TyKind,
|
||||
consteval::try_const_usize, db::HirDatabase, Binders, Interner, Substitution, Ty, TyKind,
|
||||
};
|
||||
|
||||
/// Checks whether a type is visibly uninhabited from a particular module.
|
||||
|
@ -69,7 +69,7 @@ impl TypeVisitor<Interner> for UninhabitedFrom<'_> {
|
|||
TyKind::Adt(adt, subst) => self.visit_adt(adt.0, subst),
|
||||
TyKind::Never => BREAK_VISIBLY_UNINHABITED,
|
||||
TyKind::Tuple(..) => ty.super_visit_with(self, outer_binder),
|
||||
TyKind::Array(item_ty, len) => match try_usize_const(len) {
|
||||
TyKind::Array(item_ty, len) => match try_const_usize(len) {
|
||||
Some(0) | None => CONTINUE_OPAQUELY_INHABITED,
|
||||
Some(1..) => item_ty.super_visit_with(self, outer_binder),
|
||||
},
|
||||
|
@ -160,14 +160,3 @@ impl UninhabitedFrom<'_> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn try_usize_const(c: &Const) -> Option<u128> {
|
||||
let data = &c.data(Interner);
|
||||
if data.ty.kind(Interner) != &TyKind::Scalar(chalk_ir::Scalar::Uint(chalk_ir::UintTy::Usize)) {
|
||||
return None;
|
||||
}
|
||||
match data.value {
|
||||
ConstValue::Concrete(ConcreteConst { interned: ConstScalar::UInt(value) }) => Some(value),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
//! Implementation of the Chalk `Interner` trait, which allows customizing the
|
||||
//! representation of the various objects Chalk deals with (types, goals etc.).
|
||||
|
||||
use crate::{chalk_db, tls, GenericArg};
|
||||
use crate::{chalk_db, tls, ConstScalar, GenericArg};
|
||||
use base_db::salsa::InternId;
|
||||
use chalk_ir::{Goal, GoalData};
|
||||
use hir_def::{type_ref::ConstScalar, TypeAliasId};
|
||||
use hir_def::TypeAliasId;
|
||||
use intern::{impl_internable, Interned};
|
||||
use smallvec::SmallVec;
|
||||
use std::{fmt, sync::Arc};
|
||||
|
|
|
@ -11,7 +11,7 @@ use hir_def::{
|
|||
};
|
||||
use stdx::never;
|
||||
|
||||
use crate::{db::HirDatabase, Interner, Substitution, Ty};
|
||||
use crate::{consteval::try_const_usize, db::HirDatabase, Interner, Substitution, Ty};
|
||||
|
||||
use self::adt::struct_variant_idx;
|
||||
pub use self::{
|
||||
|
@ -122,17 +122,9 @@ pub fn layout_of_ty(db: &dyn HirDatabase, ty: &Ty, krate: CrateId) -> Result<Lay
|
|||
cx.univariant(dl, &fields, &ReprOptions::default(), kind).ok_or(LayoutError::Unknown)?
|
||||
}
|
||||
TyKind::Array(element, count) => {
|
||||
let count = match count.data(Interner).value {
|
||||
chalk_ir::ConstValue::Concrete(c) => match c.interned {
|
||||
hir_def::type_ref::ConstScalar::Int(x) => x as u64,
|
||||
hir_def::type_ref::ConstScalar::UInt(x) => x as u64,
|
||||
hir_def::type_ref::ConstScalar::Unknown => {
|
||||
user_error!("unknown const generic parameter")
|
||||
}
|
||||
_ => user_error!("mismatched type of const generic parameter"),
|
||||
},
|
||||
_ => return Err(LayoutError::HasPlaceholder),
|
||||
};
|
||||
let count = try_const_usize(&count).ok_or(LayoutError::UserError(
|
||||
"mismatched type of const generic parameter".to_string(),
|
||||
))? as u64;
|
||||
let element = layout_of_ty(db, element, krate)?;
|
||||
let size = element.size.checked_mul(count, dl).ok_or(LayoutError::SizeOverflow)?;
|
||||
|
||||
|
|
|
@ -76,17 +76,8 @@ pub fn layout_of_adt_query(
|
|||
|min, max| Integer::repr_discr(&dl, &repr, min, max).unwrap_or((Integer::I8, false)),
|
||||
variants.iter_enumerated().filter_map(|(id, _)| {
|
||||
let AdtId::EnumId(e) = def else { return None };
|
||||
let d = match db
|
||||
.const_eval_variant(EnumVariantId { parent: e, local_id: id.0 })
|
||||
.ok()?
|
||||
{
|
||||
crate::consteval::ComputedExpr::Literal(l) => match l {
|
||||
hir_def::expr::Literal::Int(i, _) => i,
|
||||
hir_def::expr::Literal::Uint(i, _) => i as i128,
|
||||
_ => return None,
|
||||
},
|
||||
_ => return None,
|
||||
};
|
||||
let d =
|
||||
db.const_eval_discriminant(EnumVariantId { parent: e, local_id: id.0 }).ok()?;
|
||||
Some((id, d))
|
||||
}),
|
||||
// FIXME: The current code for niche-filling relies on variant indices
|
||||
|
|
|
@ -65,25 +65,17 @@ fn eval_expr(ra_fixture: &str, minicore: &str) -> Result<Layout, LayoutError> {
|
|||
})
|
||||
.unwrap();
|
||||
let hir_body = db.body(adt_id.into());
|
||||
let pat = hir_body
|
||||
.pats
|
||||
.iter()
|
||||
.find(|x| match x.1 {
|
||||
hir_def::expr::Pat::Bind { name, .. } => name.to_smol_str() == "goal",
|
||||
_ => false,
|
||||
})
|
||||
.unwrap()
|
||||
.0;
|
||||
let b = hir_body.bindings.iter().find(|x| x.1.name.to_smol_str() == "goal").unwrap().0;
|
||||
let infer = db.infer(adt_id.into());
|
||||
let goal_ty = infer.type_of_pat[pat].clone();
|
||||
let goal_ty = infer.type_of_binding[b].clone();
|
||||
layout_of_ty(&db, &goal_ty, module_id.krate())
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn check_size_and_align(ra_fixture: &str, minicore: &str, size: u64, align: u64) {
|
||||
let l = eval_goal(ra_fixture, minicore).unwrap();
|
||||
assert_eq!(l.size.bytes(), size);
|
||||
assert_eq!(l.align.abi.bytes(), align);
|
||||
assert_eq!(l.size.bytes(), size, "size mismatch");
|
||||
assert_eq!(l.align.abi.bytes(), align, "align mismatch");
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
|
@ -300,4 +292,9 @@ fn enums_with_discriminants() {
|
|||
C, // implicitly becomes 256, so we need two bytes
|
||||
}
|
||||
}
|
||||
size_and_align! {
|
||||
enum Goal {
|
||||
A = 1, // This one is (perhaps surprisingly) zero sized.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ mod builder;
|
|||
mod chalk_db;
|
||||
mod chalk_ext;
|
||||
pub mod consteval;
|
||||
pub mod mir;
|
||||
mod infer;
|
||||
mod inhabitedness;
|
||||
mod interner;
|
||||
|
@ -34,7 +35,7 @@ mod tests;
|
|||
#[cfg(test)]
|
||||
mod test_db;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::{collections::HashMap, hash::Hash, sync::Arc};
|
||||
|
||||
use chalk_ir::{
|
||||
fold::{Shift, TypeFoldable},
|
||||
|
@ -42,10 +43,11 @@ use chalk_ir::{
|
|||
visit::{TypeSuperVisitable, TypeVisitable, TypeVisitor},
|
||||
NoSolution, TyData,
|
||||
};
|
||||
use either::Either;
|
||||
use hir_def::{expr::ExprId, type_ref::Rawness, TypeOrConstParamId};
|
||||
use hir_expand::name;
|
||||
use itertools::Either;
|
||||
use la_arena::{Arena, Idx};
|
||||
use mir::MirEvalError;
|
||||
use rustc_hash::FxHashSet;
|
||||
use traits::FnTrait;
|
||||
use utils::Generics;
|
||||
|
@ -145,6 +147,49 @@ pub type ConstrainedSubst = chalk_ir::ConstrainedSubst<Interner>;
|
|||
pub type Guidance = chalk_solve::Guidance<Interner>;
|
||||
pub type WhereClause = chalk_ir::WhereClause<Interner>;
|
||||
|
||||
/// A constant can have reference to other things. Memory map job is holding
|
||||
/// the neccessary bits of memory of the const eval session to keep the constant
|
||||
/// meaningful.
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
pub struct MemoryMap(pub HashMap<usize, Vec<u8>>);
|
||||
|
||||
impl MemoryMap {
|
||||
fn insert(&mut self, addr: usize, x: Vec<u8>) {
|
||||
self.0.insert(addr, x);
|
||||
}
|
||||
|
||||
/// This functions convert each address by a function `f` which gets the byte intervals and assign an address
|
||||
/// to them. It is useful when you want to load a constant with a memory map in a new memory. You can pass an
|
||||
/// allocator function as `f` and it will return a mapping of old addresses to new addresses.
|
||||
fn transform_addresses(
|
||||
&self,
|
||||
mut f: impl FnMut(&[u8]) -> Result<usize, MirEvalError>,
|
||||
) -> Result<HashMap<usize, usize>, MirEvalError> {
|
||||
self.0.iter().map(|x| Ok((*x.0, f(x.1)?))).collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// A concrete constant value
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ConstScalar {
|
||||
Bytes(Vec<u8>, MemoryMap),
|
||||
/// Case of an unknown value that rustc might know but we don't
|
||||
// FIXME: this is a hack to get around chalk not being able to represent unevaluatable
|
||||
// constants
|
||||
// https://github.com/rust-lang/rust-analyzer/pull/8813#issuecomment-840679177
|
||||
// https://rust-lang.zulipchat.com/#narrow/stream/144729-wg-traits/topic/Handling.20non.20evaluatable.20constants'.20equality/near/238386348
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl Hash for ConstScalar {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
core::mem::discriminant(self).hash(state);
|
||||
if let ConstScalar::Bytes(b, _) = self {
|
||||
b.hash(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return an index of a parameter in the generic type parameter list by it's id.
|
||||
pub fn param_idx(db: &dyn HirDatabase, id: TypeOrConstParamId) -> Option<usize> {
|
||||
generics(db.upcast(), id.parent).param_idx(id)
|
||||
|
|
|
@ -16,6 +16,7 @@ use chalk_ir::{
|
|||
cast::Cast, fold::Shift, fold::TypeFoldable, interner::HasInterner, Mutability, Safety,
|
||||
};
|
||||
|
||||
use either::Either;
|
||||
use hir_def::{
|
||||
adt::StructKind,
|
||||
body::{Expander, LowerCtx},
|
||||
|
@ -26,16 +27,13 @@ use hir_def::{
|
|||
lang_item::{lang_attr, LangItem},
|
||||
path::{GenericArg, ModPath, Path, PathKind, PathSegment, PathSegments},
|
||||
resolver::{HasResolver, Resolver, TypeNs},
|
||||
type_ref::{
|
||||
ConstScalarOrPath, TraitBoundModifier, TraitRef as HirTraitRef, TypeBound, TypeRef,
|
||||
},
|
||||
type_ref::{ConstRefOrPath, TraitBoundModifier, TraitRef as HirTraitRef, TypeBound, TypeRef},
|
||||
AdtId, AssocItemId, ConstId, ConstParamId, EnumId, EnumVariantId, FunctionId, GenericDefId,
|
||||
HasModule, ImplId, ItemContainerId, LocalFieldId, Lookup, ModuleDefId, StaticId, StructId,
|
||||
TraitId, TypeAliasId, TypeOrConstParamId, TypeParamId, UnionId, VariantId,
|
||||
};
|
||||
use hir_expand::{name::Name, ExpandResult};
|
||||
use intern::Interned;
|
||||
use itertools::Either;
|
||||
use la_arena::{Arena, ArenaMap};
|
||||
use rustc_hash::FxHashSet;
|
||||
use smallvec::SmallVec;
|
||||
|
@ -44,7 +42,7 @@ use syntax::ast;
|
|||
|
||||
use crate::{
|
||||
all_super_traits,
|
||||
consteval::{intern_const_scalar, path_to_const, unknown_const, unknown_const_as_generic},
|
||||
consteval::{intern_const_ref, path_to_const, unknown_const, unknown_const_as_generic},
|
||||
db::HirDatabase,
|
||||
make_binders,
|
||||
mapping::{from_chalk_trait_id, ToChalk},
|
||||
|
@ -524,6 +522,10 @@ impl<'a> TyLoweringContext<'a> {
|
|||
};
|
||||
return (ty, None);
|
||||
}
|
||||
TypeNs::TraitAliasId(_) => {
|
||||
// FIXME(trait_alias): Implement trait alias.
|
||||
return (TyKind::Error.intern(Interner), None);
|
||||
}
|
||||
TypeNs::GenericParam(param_id) => {
|
||||
let generics = generics(
|
||||
self.db.upcast(),
|
||||
|
@ -879,6 +881,7 @@ impl<'a> TyLoweringContext<'a> {
|
|||
) -> Option<TraitRef> {
|
||||
let resolved =
|
||||
match self.resolver.resolve_path_in_type_ns_fully(self.db.upcast(), path.mod_path())? {
|
||||
// FIXME(trait_alias): We need to handle trait alias here.
|
||||
TypeNs::TraitId(tr) => tr,
|
||||
_ => return None,
|
||||
};
|
||||
|
@ -968,7 +971,7 @@ impl<'a> TyLoweringContext<'a> {
|
|||
// - `Destruct` impls are built-in in 1.62 (current nightlies as of 08-04-2022), so until
|
||||
// the builtin impls are supported by Chalk, we ignore them here.
|
||||
if let Some(lang) = lang_attr(self.db.upcast(), tr.hir_trait_id()) {
|
||||
if lang == "drop" || lang == "destruct" {
|
||||
if matches!(lang, LangItem::Drop | LangItem::Destruct) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -1444,6 +1447,7 @@ pub(crate) fn trait_environment_query(
|
|||
GenericDefId::FunctionId(f) => Some(f.lookup(db.upcast()).container),
|
||||
GenericDefId::AdtId(_) => None,
|
||||
GenericDefId::TraitId(_) => None,
|
||||
GenericDefId::TraitAliasId(_) => None,
|
||||
GenericDefId::TypeAliasId(t) => Some(t.lookup(db.upcast()).container),
|
||||
GenericDefId::ImplId(_) => None,
|
||||
GenericDefId::EnumVariantId(_) => None,
|
||||
|
@ -1583,10 +1587,10 @@ pub(crate) fn generic_defaults_recover(
|
|||
.iter_id()
|
||||
.map(|id| {
|
||||
let val = match id {
|
||||
itertools::Either::Left(_) => {
|
||||
Either::Left(_) => {
|
||||
GenericArgData::Ty(TyKind::Error.intern(Interner)).intern(Interner)
|
||||
}
|
||||
itertools::Either::Right(id) => unknown_const_as_generic(db.const_param_ty(id)),
|
||||
Either::Right(id) => unknown_const_as_generic(db.const_param_ty(id)),
|
||||
};
|
||||
crate::make_binders(db, &generic_params, val)
|
||||
})
|
||||
|
@ -1919,7 +1923,7 @@ pub(crate) fn generic_arg_to_chalk<'a, T>(
|
|||
arg: &'a GenericArg,
|
||||
this: &mut T,
|
||||
for_type: impl FnOnce(&mut T, &TypeRef) -> Ty + 'a,
|
||||
for_const: impl FnOnce(&mut T, &ConstScalarOrPath, Ty) -> Const + 'a,
|
||||
for_const: impl FnOnce(&mut T, &ConstRefOrPath, Ty) -> Const + 'a,
|
||||
) -> Option<crate::GenericArg> {
|
||||
let kind = match kind_id {
|
||||
Either::Left(_) => ParamKind::Type,
|
||||
|
@ -1947,7 +1951,7 @@ pub(crate) fn generic_arg_to_chalk<'a, T>(
|
|||
let p = p.mod_path();
|
||||
if p.kind == PathKind::Plain {
|
||||
if let [n] = p.segments() {
|
||||
let c = ConstScalarOrPath::Path(n.clone());
|
||||
let c = ConstRefOrPath::Path(n.clone());
|
||||
return Some(
|
||||
GenericArgData::Const(for_const(this, &c, c_ty)).intern(Interner),
|
||||
);
|
||||
|
@ -1964,14 +1968,14 @@ pub(crate) fn const_or_path_to_chalk(
|
|||
db: &dyn HirDatabase,
|
||||
resolver: &Resolver,
|
||||
expected_ty: Ty,
|
||||
value: &ConstScalarOrPath,
|
||||
value: &ConstRefOrPath,
|
||||
mode: ParamLoweringMode,
|
||||
args: impl FnOnce() -> Generics,
|
||||
debruijn: DebruijnIndex,
|
||||
) -> Const {
|
||||
match value {
|
||||
ConstScalarOrPath::Scalar(s) => intern_const_scalar(*s, expected_ty),
|
||||
ConstScalarOrPath::Path(n) => {
|
||||
ConstRefOrPath::Scalar(s) => intern_const_ref(db, s, expected_ty, resolver.krate()),
|
||||
ConstRefOrPath::Path(n) => {
|
||||
let path = ModPath::from_segments(PathKind::Plain, Some(n.clone()));
|
||||
path_to_const(db, resolver, &path, mode, args, debruijn)
|
||||
.unwrap_or_else(|| unknown_const(expected_ty))
|
||||
|
|
|
@ -579,8 +579,8 @@ impl ReceiverAdjustments {
|
|||
ty = new_ty.clone();
|
||||
adjust.push(Adjustment {
|
||||
kind: Adjust::Deref(match kind {
|
||||
// FIXME should we know the mutability here?
|
||||
AutoderefKind::Overloaded => Some(OverloadedDeref(Mutability::Not)),
|
||||
// FIXME should we know the mutability here, when autoref is `None`?
|
||||
AutoderefKind::Overloaded => Some(OverloadedDeref(self.autoref)),
|
||||
AutoderefKind::Builtin => None,
|
||||
}),
|
||||
target: new_ty,
|
||||
|
@ -660,10 +660,10 @@ pub fn lookup_impl_const(
|
|||
env: Arc<TraitEnvironment>,
|
||||
const_id: ConstId,
|
||||
subs: Substitution,
|
||||
) -> ConstId {
|
||||
) -> (ConstId, Substitution) {
|
||||
let trait_id = match const_id.lookup(db.upcast()).container {
|
||||
ItemContainerId::TraitId(id) => id,
|
||||
_ => return const_id,
|
||||
_ => return (const_id, subs),
|
||||
};
|
||||
let substitution = Substitution::from_iter(Interner, subs.iter(Interner));
|
||||
let trait_ref = TraitRef { trait_id: to_chalk_trait_id(trait_id), substitution };
|
||||
|
@ -671,12 +671,14 @@ pub fn lookup_impl_const(
|
|||
let const_data = db.const_data(const_id);
|
||||
let name = match const_data.name.as_ref() {
|
||||
Some(name) => name,
|
||||
None => return const_id,
|
||||
None => return (const_id, subs),
|
||||
};
|
||||
|
||||
lookup_impl_assoc_item_for_trait_ref(trait_ref, db, env, name)
|
||||
.and_then(|assoc| if let AssocItemId::ConstId(id) = assoc { Some(id) } else { None })
|
||||
.unwrap_or(const_id)
|
||||
.and_then(
|
||||
|assoc| if let (AssocItemId::ConstId(id), s) = assoc { Some((id, s)) } else { None },
|
||||
)
|
||||
.unwrap_or((const_id, subs))
|
||||
}
|
||||
|
||||
/// Looks up the impl method that actually runs for the trait method `func`.
|
||||
|
@ -687,10 +689,10 @@ pub fn lookup_impl_method(
|
|||
env: Arc<TraitEnvironment>,
|
||||
func: FunctionId,
|
||||
fn_subst: Substitution,
|
||||
) -> FunctionId {
|
||||
) -> (FunctionId, Substitution) {
|
||||
let trait_id = match func.lookup(db.upcast()).container {
|
||||
ItemContainerId::TraitId(id) => id,
|
||||
_ => return func,
|
||||
_ => return (func, fn_subst),
|
||||
};
|
||||
let trait_params = db.generic_params(trait_id.into()).type_or_consts.len();
|
||||
let fn_params = fn_subst.len(Interner) - trait_params;
|
||||
|
@ -701,8 +703,14 @@ pub fn lookup_impl_method(
|
|||
|
||||
let name = &db.function_data(func).name;
|
||||
lookup_impl_assoc_item_for_trait_ref(trait_ref, db, env, name)
|
||||
.and_then(|assoc| if let AssocItemId::FunctionId(id) = assoc { Some(id) } else { None })
|
||||
.unwrap_or(func)
|
||||
.and_then(|assoc| {
|
||||
if let (AssocItemId::FunctionId(id), subst) = assoc {
|
||||
Some((id, subst))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or((func, fn_subst))
|
||||
}
|
||||
|
||||
fn lookup_impl_assoc_item_for_trait_ref(
|
||||
|
@ -710,7 +718,7 @@ fn lookup_impl_assoc_item_for_trait_ref(
|
|||
db: &dyn HirDatabase,
|
||||
env: Arc<TraitEnvironment>,
|
||||
name: &Name,
|
||||
) -> Option<AssocItemId> {
|
||||
) -> Option<(AssocItemId, Substitution)> {
|
||||
let self_ty = trait_ref.self_type_parameter(Interner);
|
||||
let self_ty_fp = TyFingerprint::for_trait_impl(&self_ty)?;
|
||||
let impls = db.trait_impls_in_deps(env.krate);
|
||||
|
@ -718,8 +726,8 @@ fn lookup_impl_assoc_item_for_trait_ref(
|
|||
|
||||
let table = InferenceTable::new(db, env);
|
||||
|
||||
let impl_data = find_matching_impl(impls, table, trait_ref)?;
|
||||
impl_data.items.iter().find_map(|&it| match it {
|
||||
let (impl_data, impl_subst) = find_matching_impl(impls, table, trait_ref)?;
|
||||
let item = impl_data.items.iter().find_map(|&it| match it {
|
||||
AssocItemId::FunctionId(f) => {
|
||||
(db.function_data(f).name == *name).then_some(AssocItemId::FunctionId(f))
|
||||
}
|
||||
|
@ -730,14 +738,15 @@ fn lookup_impl_assoc_item_for_trait_ref(
|
|||
.map(|n| n == name)
|
||||
.and_then(|result| if result { Some(AssocItemId::ConstId(c)) } else { None }),
|
||||
AssocItemId::TypeAliasId(_) => None,
|
||||
})
|
||||
})?;
|
||||
Some((item, impl_subst))
|
||||
}
|
||||
|
||||
fn find_matching_impl(
|
||||
mut impls: impl Iterator<Item = ImplId>,
|
||||
mut table: InferenceTable<'_>,
|
||||
actual_trait_ref: TraitRef,
|
||||
) -> Option<Arc<ImplData>> {
|
||||
) -> Option<(Arc<ImplData>, Substitution)> {
|
||||
let db = table.db;
|
||||
loop {
|
||||
let impl_ = impls.next()?;
|
||||
|
@ -758,7 +767,7 @@ fn find_matching_impl(
|
|||
.into_iter()
|
||||
.map(|b| b.cast(Interner));
|
||||
let goal = crate::Goal::all(Interner, wcs);
|
||||
table.try_obligation(goal).map(|_| impl_data)
|
||||
table.try_obligation(goal).map(|_| (impl_data, table.resolve_completely(impl_substs)))
|
||||
});
|
||||
if r.is_some() {
|
||||
break r;
|
||||
|
@ -821,9 +830,9 @@ pub fn iterate_method_candidates_dyn(
|
|||
|
||||
let mut table = InferenceTable::new(db, env.clone());
|
||||
let ty = table.instantiate_canonical(ty.clone());
|
||||
let (deref_chain, adj) = autoderef_method_receiver(&mut table, ty);
|
||||
let deref_chain = autoderef_method_receiver(&mut table, ty);
|
||||
|
||||
let result = deref_chain.into_iter().zip(adj).try_for_each(|(receiver_ty, adj)| {
|
||||
let result = deref_chain.into_iter().try_for_each(|(receiver_ty, adj)| {
|
||||
iterate_method_candidates_with_autoref(
|
||||
&receiver_ty,
|
||||
adj,
|
||||
|
@ -867,16 +876,20 @@ fn iterate_method_candidates_with_autoref(
|
|||
return ControlFlow::Continue(());
|
||||
}
|
||||
|
||||
iterate_method_candidates_by_receiver(
|
||||
receiver_ty,
|
||||
first_adjustment.clone(),
|
||||
db,
|
||||
env.clone(),
|
||||
traits_in_scope,
|
||||
visible_from_module,
|
||||
name,
|
||||
&mut callback,
|
||||
)?;
|
||||
let mut iterate_method_candidates_by_receiver = move |receiver_ty, first_adjustment| {
|
||||
iterate_method_candidates_by_receiver(
|
||||
receiver_ty,
|
||||
first_adjustment,
|
||||
db,
|
||||
env.clone(),
|
||||
traits_in_scope,
|
||||
visible_from_module,
|
||||
name,
|
||||
&mut callback,
|
||||
)
|
||||
};
|
||||
|
||||
iterate_method_candidates_by_receiver(receiver_ty, first_adjustment.clone())?;
|
||||
|
||||
let refed = Canonical {
|
||||
value: TyKind::Ref(Mutability::Not, static_lifetime(), receiver_ty.value.clone())
|
||||
|
@ -884,16 +897,7 @@ fn iterate_method_candidates_with_autoref(
|
|||
binders: receiver_ty.binders.clone(),
|
||||
};
|
||||
|
||||
iterate_method_candidates_by_receiver(
|
||||
&refed,
|
||||
first_adjustment.with_autoref(Mutability::Not),
|
||||
db,
|
||||
env.clone(),
|
||||
traits_in_scope,
|
||||
visible_from_module,
|
||||
name,
|
||||
&mut callback,
|
||||
)?;
|
||||
iterate_method_candidates_by_receiver(&refed, first_adjustment.with_autoref(Mutability::Not))?;
|
||||
|
||||
let ref_muted = Canonical {
|
||||
value: TyKind::Ref(Mutability::Mut, static_lifetime(), receiver_ty.value.clone())
|
||||
|
@ -904,12 +908,6 @@ fn iterate_method_candidates_with_autoref(
|
|||
iterate_method_candidates_by_receiver(
|
||||
&ref_muted,
|
||||
first_adjustment.with_autoref(Mutability::Mut),
|
||||
db,
|
||||
env,
|
||||
traits_in_scope,
|
||||
visible_from_module,
|
||||
name,
|
||||
&mut callback,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1210,8 +1208,8 @@ pub fn resolve_indexing_op(
|
|||
) -> Option<ReceiverAdjustments> {
|
||||
let mut table = InferenceTable::new(db, env.clone());
|
||||
let ty = table.instantiate_canonical(ty);
|
||||
let (deref_chain, adj) = autoderef_method_receiver(&mut table, ty);
|
||||
for (ty, adj) in deref_chain.into_iter().zip(adj) {
|
||||
let deref_chain = autoderef_method_receiver(&mut table, ty);
|
||||
for (ty, adj) in deref_chain {
|
||||
let goal = generic_implements_goal(db, env.clone(), index_trait, &ty);
|
||||
if db.trait_solve(env.krate, goal.cast(Interner)).is_some() {
|
||||
return Some(adj);
|
||||
|
@ -1421,25 +1419,24 @@ fn generic_implements_goal(
|
|||
fn autoderef_method_receiver(
|
||||
table: &mut InferenceTable<'_>,
|
||||
ty: Ty,
|
||||
) -> (Vec<Canonical<Ty>>, Vec<ReceiverAdjustments>) {
|
||||
let (mut deref_chain, mut adjustments): (Vec<_>, Vec<_>) = (Vec::new(), Vec::new());
|
||||
) -> Vec<(Canonical<Ty>, ReceiverAdjustments)> {
|
||||
let mut deref_chain: Vec<_> = Vec::new();
|
||||
let mut autoderef = autoderef::Autoderef::new(table, ty);
|
||||
while let Some((ty, derefs)) = autoderef.next() {
|
||||
deref_chain.push(autoderef.table.canonicalize(ty).value);
|
||||
adjustments.push(ReceiverAdjustments {
|
||||
autoref: None,
|
||||
autoderefs: derefs,
|
||||
unsize_array: false,
|
||||
});
|
||||
deref_chain.push((
|
||||
autoderef.table.canonicalize(ty).value,
|
||||
ReceiverAdjustments { autoref: None, autoderefs: derefs, unsize_array: false },
|
||||
));
|
||||
}
|
||||
// As a last step, we can do array unsizing (that's the only unsizing that rustc does for method receivers!)
|
||||
if let (Some((TyKind::Array(parameters, _), binders)), Some(adj)) = (
|
||||
deref_chain.last().map(|ty| (ty.value.kind(Interner), ty.binders.clone())),
|
||||
adjustments.last().cloned(),
|
||||
) {
|
||||
if let Some((TyKind::Array(parameters, _), binders, adj)) =
|
||||
deref_chain.last().map(|(ty, adj)| (ty.value.kind(Interner), ty.binders.clone(), adj))
|
||||
{
|
||||
let unsized_ty = TyKind::Slice(parameters.clone()).intern(Interner);
|
||||
deref_chain.push(Canonical { value: unsized_ty, binders });
|
||||
adjustments.push(ReceiverAdjustments { unsize_array: true, ..adj });
|
||||
deref_chain.push((
|
||||
Canonical { value: unsized_ty, binders },
|
||||
ReceiverAdjustments { unsize_array: true, ..adj.clone() },
|
||||
));
|
||||
}
|
||||
(deref_chain, adjustments)
|
||||
deref_chain
|
||||
}
|
||||
|
|
863
crates/hir-ty/src/mir.rs
Normal file
863
crates/hir-ty/src/mir.rs
Normal file
|
@ -0,0 +1,863 @@
|
|||
//! MIR definitions and implementation
|
||||
|
||||
use std::{fmt::Display, iter};
|
||||
|
||||
use crate::{
|
||||
infer::PointerCast, Const, ConstScalar, InferenceResult, Interner, MemoryMap, Substitution, Ty,
|
||||
};
|
||||
use chalk_ir::Mutability;
|
||||
use hir_def::{
|
||||
expr::{BindingId, Expr, ExprId, Ordering, PatId},
|
||||
DefWithBodyId, FieldId, UnionId, VariantId,
|
||||
};
|
||||
use la_arena::{Arena, ArenaMap, Idx, RawIdx};
|
||||
|
||||
mod eval;
|
||||
mod lower;
|
||||
mod borrowck;
|
||||
mod pretty;
|
||||
|
||||
pub use borrowck::{borrowck_query, BorrowckResult, MutabilityReason};
|
||||
pub use eval::{interpret_mir, pad16, Evaluator, MirEvalError};
|
||||
pub use lower::{lower_to_mir, mir_body_query, mir_body_recover, MirLowerError};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use stdx::impl_from;
|
||||
|
||||
use super::consteval::{intern_const_scalar, try_const_usize};
|
||||
|
||||
pub type BasicBlockId = Idx<BasicBlock>;
|
||||
pub type LocalId = Idx<Local>;
|
||||
|
||||
fn return_slot() -> LocalId {
|
||||
LocalId::from_raw(RawIdx::from(0))
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Local {
|
||||
pub ty: Ty,
|
||||
}
|
||||
|
||||
/// An operand in MIR represents a "value" in Rust, the definition of which is undecided and part of
|
||||
/// the memory model. One proposal for a definition of values can be found [on UCG][value-def].
|
||||
///
|
||||
/// [value-def]: https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/value-domain.md
|
||||
///
|
||||
/// The most common way to create values is via loading a place. Loading a place is an operation
|
||||
/// which reads the memory of the place and converts it to a value. This is a fundamentally *typed*
|
||||
/// operation. The nature of the value produced depends on the type of the conversion. Furthermore,
|
||||
/// there may be other effects: if the type has a validity constraint loading the place might be UB
|
||||
/// if the validity constraint is not met.
|
||||
///
|
||||
/// **Needs clarification:** Ralf proposes that loading a place not have side-effects.
|
||||
/// This is what is implemented in miri today. Are these the semantics we want for MIR? Is this
|
||||
/// something we can even decide without knowing more about Rust's memory model?
|
||||
///
|
||||
/// **Needs clarifiation:** Is loading a place that has its variant index set well-formed? Miri
|
||||
/// currently implements it, but it seems like this may be something to check against in the
|
||||
/// validator.
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum Operand {
|
||||
/// Creates a value by loading the given place.
|
||||
///
|
||||
/// Before drop elaboration, the type of the place must be `Copy`. After drop elaboration there
|
||||
/// is no such requirement.
|
||||
Copy(Place),
|
||||
|
||||
/// Creates a value by performing loading the place, just like the `Copy` operand.
|
||||
///
|
||||
/// This *may* additionally overwrite the place with `uninit` bytes, depending on how we decide
|
||||
/// in [UCG#188]. You should not emit MIR that may attempt a subsequent second load of this
|
||||
/// place without first re-initializing it.
|
||||
///
|
||||
/// [UCG#188]: https://github.com/rust-lang/unsafe-code-guidelines/issues/188
|
||||
Move(Place),
|
||||
/// Constants are already semantically values, and remain unchanged.
|
||||
Constant(Const),
|
||||
}
|
||||
|
||||
impl Operand {
|
||||
fn from_concrete_const(data: Vec<u8>, memory_map: MemoryMap, ty: Ty) -> Self {
|
||||
Operand::Constant(intern_const_scalar(ConstScalar::Bytes(data, memory_map), ty))
|
||||
}
|
||||
|
||||
fn from_bytes(data: Vec<u8>, ty: Ty) -> Self {
|
||||
Operand::from_concrete_const(data, MemoryMap::default(), ty)
|
||||
}
|
||||
|
||||
fn const_zst(ty: Ty) -> Operand {
|
||||
Self::from_bytes(vec![], ty)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum ProjectionElem<V, T> {
|
||||
Deref,
|
||||
Field(FieldId),
|
||||
TupleField(usize),
|
||||
Index(V),
|
||||
ConstantIndex { offset: u64, min_length: u64, from_end: bool },
|
||||
Subslice { from: u64, to: u64, from_end: bool },
|
||||
//Downcast(Option<Symbol>, VariantIdx),
|
||||
OpaqueCast(T),
|
||||
}
|
||||
|
||||
type PlaceElem = ProjectionElem<LocalId, Ty>;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Place {
|
||||
pub local: LocalId,
|
||||
pub projection: Vec<PlaceElem>,
|
||||
}
|
||||
|
||||
impl From<LocalId> for Place {
|
||||
fn from(local: LocalId) -> Self {
|
||||
Self { local, projection: vec![] }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum AggregateKind {
|
||||
/// The type is of the element
|
||||
Array(Ty),
|
||||
/// The type is of the tuple
|
||||
Tuple(Ty),
|
||||
Adt(VariantId, Substitution),
|
||||
Union(UnionId, FieldId),
|
||||
//Closure(LocalDefId, SubstsRef),
|
||||
//Generator(LocalDefId, SubstsRef, Movability),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
pub struct SwitchTargets {
|
||||
/// Possible values. The locations to branch to in each case
|
||||
/// are found in the corresponding indices from the `targets` vector.
|
||||
values: SmallVec<[u128; 1]>,
|
||||
|
||||
/// Possible branch sites. The last element of this vector is used
|
||||
/// for the otherwise branch, so targets.len() == values.len() + 1
|
||||
/// should hold.
|
||||
//
|
||||
// This invariant is quite non-obvious and also could be improved.
|
||||
// One way to make this invariant is to have something like this instead:
|
||||
//
|
||||
// branches: Vec<(ConstInt, BasicBlock)>,
|
||||
// otherwise: Option<BasicBlock> // exhaustive if None
|
||||
//
|
||||
// However we’ve decided to keep this as-is until we figure a case
|
||||
// where some other approach seems to be strictly better than other.
|
||||
targets: SmallVec<[BasicBlockId; 2]>,
|
||||
}
|
||||
|
||||
impl SwitchTargets {
|
||||
/// Creates switch targets from an iterator of values and target blocks.
|
||||
///
|
||||
/// The iterator may be empty, in which case the `SwitchInt` instruction is equivalent to
|
||||
/// `goto otherwise;`.
|
||||
pub fn new(
|
||||
targets: impl Iterator<Item = (u128, BasicBlockId)>,
|
||||
otherwise: BasicBlockId,
|
||||
) -> Self {
|
||||
let (values, mut targets): (SmallVec<_>, SmallVec<_>) = targets.unzip();
|
||||
targets.push(otherwise);
|
||||
Self { values, targets }
|
||||
}
|
||||
|
||||
/// Builds a switch targets definition that jumps to `then` if the tested value equals `value`,
|
||||
/// and to `else_` if not.
|
||||
pub fn static_if(value: u128, then: BasicBlockId, else_: BasicBlockId) -> Self {
|
||||
Self { values: smallvec![value], targets: smallvec![then, else_] }
|
||||
}
|
||||
|
||||
/// Returns the fallback target that is jumped to when none of the values match the operand.
|
||||
pub fn otherwise(&self) -> BasicBlockId {
|
||||
*self.targets.last().unwrap()
|
||||
}
|
||||
|
||||
/// Returns an iterator over the switch targets.
|
||||
///
|
||||
/// The iterator will yield tuples containing the value and corresponding target to jump to, not
|
||||
/// including the `otherwise` fallback target.
|
||||
///
|
||||
/// Note that this may yield 0 elements. Only the `otherwise` branch is mandatory.
|
||||
pub fn iter(&self) -> impl Iterator<Item = (u128, BasicBlockId)> + '_ {
|
||||
iter::zip(&self.values, &self.targets).map(|(x, y)| (*x, *y))
|
||||
}
|
||||
|
||||
/// Returns a slice with all possible jump targets (including the fallback target).
|
||||
pub fn all_targets(&self) -> &[BasicBlockId] {
|
||||
&self.targets
|
||||
}
|
||||
|
||||
/// Finds the `BasicBlock` to which this `SwitchInt` will branch given the
|
||||
/// specific value. This cannot fail, as it'll return the `otherwise`
|
||||
/// branch if there's not a specific match for the value.
|
||||
pub fn target_for_value(&self, value: u128) -> BasicBlockId {
|
||||
self.iter().find_map(|(v, t)| (v == value).then_some(t)).unwrap_or_else(|| self.otherwise())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum Terminator {
|
||||
/// Block has one successor; we continue execution there.
|
||||
Goto { target: BasicBlockId },
|
||||
|
||||
/// Switches based on the computed value.
|
||||
///
|
||||
/// First, evaluates the `discr` operand. The type of the operand must be a signed or unsigned
|
||||
/// integer, char, or bool, and must match the given type. Then, if the list of switch targets
|
||||
/// contains the computed value, continues execution at the associated basic block. Otherwise,
|
||||
/// continues execution at the "otherwise" basic block.
|
||||
///
|
||||
/// Target values may not appear more than once.
|
||||
SwitchInt {
|
||||
/// The discriminant value being tested.
|
||||
discr: Operand,
|
||||
|
||||
targets: SwitchTargets,
|
||||
},
|
||||
|
||||
/// Indicates that the landing pad is finished and that the process should continue unwinding.
|
||||
///
|
||||
/// Like a return, this marks the end of this invocation of the function.
|
||||
///
|
||||
/// Only permitted in cleanup blocks. `Resume` is not permitted with `-C unwind=abort` after
|
||||
/// deaggregation runs.
|
||||
Resume,
|
||||
|
||||
/// Indicates that the landing pad is finished and that the process should abort.
|
||||
///
|
||||
/// Used to prevent unwinding for foreign items or with `-C unwind=abort`. Only permitted in
|
||||
/// cleanup blocks.
|
||||
Abort,
|
||||
|
||||
/// Returns from the function.
|
||||
///
|
||||
/// Like function calls, the exact semantics of returns in Rust are unclear. Returning very
|
||||
/// likely at least assigns the value currently in the return place (`_0`) to the place
|
||||
/// specified in the associated `Call` terminator in the calling function, as if assigned via
|
||||
/// `dest = move _0`. It might additionally do other things, like have side-effects in the
|
||||
/// aliasing model.
|
||||
///
|
||||
/// If the body is a generator body, this has slightly different semantics; it instead causes a
|
||||
/// `GeneratorState::Returned(_0)` to be created (as if by an `Aggregate` rvalue) and assigned
|
||||
/// to the return place.
|
||||
Return,
|
||||
|
||||
/// Indicates a terminator that can never be reached.
|
||||
///
|
||||
/// Executing this terminator is UB.
|
||||
Unreachable,
|
||||
|
||||
/// The behavior of this statement differs significantly before and after drop elaboration.
|
||||
/// After drop elaboration, `Drop` executes the drop glue for the specified place, after which
|
||||
/// it continues execution/unwinds at the given basic blocks. It is possible that executing drop
|
||||
/// glue is special - this would be part of Rust's memory model. (**FIXME**: due we have an
|
||||
/// issue tracking if drop glue has any interesting semantics in addition to those of a function
|
||||
/// call?)
|
||||
///
|
||||
/// `Drop` before drop elaboration is a *conditional* execution of the drop glue. Specifically, the
|
||||
/// `Drop` will be executed if...
|
||||
///
|
||||
/// **Needs clarification**: End of that sentence. This in effect should document the exact
|
||||
/// behavior of drop elaboration. The following sounds vaguely right, but I'm not quite sure:
|
||||
///
|
||||
/// > The drop glue is executed if, among all statements executed within this `Body`, an assignment to
|
||||
/// > the place or one of its "parents" occurred more recently than a move out of it. This does not
|
||||
/// > consider indirect assignments.
|
||||
Drop { place: Place, target: BasicBlockId, unwind: Option<BasicBlockId> },
|
||||
|
||||
/// Drops the place and assigns a new value to it.
|
||||
///
|
||||
/// This first performs the exact same operation as the pre drop-elaboration `Drop` terminator;
|
||||
/// it then additionally assigns the `value` to the `place` as if by an assignment statement.
|
||||
/// This assignment occurs both in the unwind and the regular code paths. The semantics are best
|
||||
/// explained by the elaboration:
|
||||
///
|
||||
/// ```ignore (MIR)
|
||||
/// BB0 {
|
||||
/// DropAndReplace(P <- V, goto BB1, unwind BB2)
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// becomes
|
||||
///
|
||||
/// ```ignore (MIR)
|
||||
/// BB0 {
|
||||
/// Drop(P, goto BB1, unwind BB2)
|
||||
/// }
|
||||
/// BB1 {
|
||||
/// // P is now uninitialized
|
||||
/// P <- V
|
||||
/// }
|
||||
/// BB2 {
|
||||
/// // P is now uninitialized -- its dtor panicked
|
||||
/// P <- V
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Disallowed after drop elaboration.
|
||||
DropAndReplace {
|
||||
place: Place,
|
||||
value: Operand,
|
||||
target: BasicBlockId,
|
||||
unwind: Option<BasicBlockId>,
|
||||
},
|
||||
|
||||
/// Roughly speaking, evaluates the `func` operand and the arguments, and starts execution of
|
||||
/// the referred to function. The operand types must match the argument types of the function.
|
||||
/// The return place type must match the return type. The type of the `func` operand must be
|
||||
/// callable, meaning either a function pointer, a function type, or a closure type.
|
||||
///
|
||||
/// **Needs clarification**: The exact semantics of this. Current backends rely on `move`
|
||||
/// operands not aliasing the return place. It is unclear how this is justified in MIR, see
|
||||
/// [#71117].
|
||||
///
|
||||
/// [#71117]: https://github.com/rust-lang/rust/issues/71117
|
||||
Call {
|
||||
/// The function that’s being called.
|
||||
func: Operand,
|
||||
/// Arguments the function is called with.
|
||||
/// These are owned by the callee, which is free to modify them.
|
||||
/// This allows the memory occupied by "by-value" arguments to be
|
||||
/// reused across function calls without duplicating the contents.
|
||||
args: Vec<Operand>,
|
||||
/// Where the returned value will be written
|
||||
destination: Place,
|
||||
/// Where to go after this call returns. If none, the call necessarily diverges.
|
||||
target: Option<BasicBlockId>,
|
||||
/// Cleanups to be done if the call unwinds.
|
||||
cleanup: Option<BasicBlockId>,
|
||||
/// `true` if this is from a call in HIR rather than from an overloaded
|
||||
/// operator. True for overloaded function call.
|
||||
from_hir_call: bool,
|
||||
// This `Span` is the span of the function, without the dot and receiver
|
||||
// (e.g. `foo(a, b)` in `x.foo(a, b)`
|
||||
//fn_span: Span,
|
||||
},
|
||||
|
||||
/// Evaluates the operand, which must have type `bool`. If it is not equal to `expected`,
|
||||
/// initiates a panic. Initiating a panic corresponds to a `Call` terminator with some
|
||||
/// unspecified constant as the function to call, all the operands stored in the `AssertMessage`
|
||||
/// as parameters, and `None` for the destination. Keep in mind that the `cleanup` path is not
|
||||
/// necessarily executed even in the case of a panic, for example in `-C panic=abort`. If the
|
||||
/// assertion does not fail, execution continues at the specified basic block.
|
||||
Assert {
|
||||
cond: Operand,
|
||||
expected: bool,
|
||||
//msg: AssertMessage,
|
||||
target: BasicBlockId,
|
||||
cleanup: Option<BasicBlockId>,
|
||||
},
|
||||
|
||||
/// Marks a suspend point.
|
||||
///
|
||||
/// Like `Return` terminators in generator bodies, this computes `value` and then a
|
||||
/// `GeneratorState::Yielded(value)` as if by `Aggregate` rvalue. That value is then assigned to
|
||||
/// the return place of the function calling this one, and execution continues in the calling
|
||||
/// function. When next invoked with the same first argument, execution of this function
|
||||
/// continues at the `resume` basic block, with the second argument written to the `resume_arg`
|
||||
/// place. If the generator is dropped before then, the `drop` basic block is invoked.
|
||||
///
|
||||
/// Not permitted in bodies that are not generator bodies, or after generator lowering.
|
||||
///
|
||||
/// **Needs clarification**: What about the evaluation order of the `resume_arg` and `value`?
|
||||
Yield {
|
||||
/// The value to return.
|
||||
value: Operand,
|
||||
/// Where to resume to.
|
||||
resume: BasicBlockId,
|
||||
/// The place to store the resume argument in.
|
||||
resume_arg: Place,
|
||||
/// Cleanup to be done if the generator is dropped at this suspend point.
|
||||
drop: Option<BasicBlockId>,
|
||||
},
|
||||
|
||||
/// Indicates the end of dropping a generator.
|
||||
///
|
||||
/// Semantically just a `return` (from the generators drop glue). Only permitted in the same situations
|
||||
/// as `yield`.
|
||||
///
|
||||
/// **Needs clarification**: Is that even correct? The generator drop code is always confusing
|
||||
/// to me, because it's not even really in the current body.
|
||||
///
|
||||
/// **Needs clarification**: Are there type system constraints on these terminators? Should
|
||||
/// there be a "block type" like `cleanup` blocks for them?
|
||||
GeneratorDrop,
|
||||
|
||||
/// A block where control flow only ever takes one real path, but borrowck needs to be more
|
||||
/// conservative.
|
||||
///
|
||||
/// At runtime this is semantically just a goto.
|
||||
///
|
||||
/// Disallowed after drop elaboration.
|
||||
FalseEdge {
|
||||
/// The target normal control flow will take.
|
||||
real_target: BasicBlockId,
|
||||
/// A block control flow could conceptually jump to, but won't in
|
||||
/// practice.
|
||||
imaginary_target: BasicBlockId,
|
||||
},
|
||||
|
||||
/// A terminator for blocks that only take one path in reality, but where we reserve the right
|
||||
/// to unwind in borrowck, even if it won't happen in practice. This can arise in infinite loops
|
||||
/// with no function calls for example.
|
||||
///
|
||||
/// At runtime this is semantically just a goto.
|
||||
///
|
||||
/// Disallowed after drop elaboration.
|
||||
FalseUnwind {
|
||||
/// The target normal control flow will take.
|
||||
real_target: BasicBlockId,
|
||||
/// The imaginary cleanup block link. This particular path will never be taken
|
||||
/// in practice, but in order to avoid fragility we want to always
|
||||
/// consider it in borrowck. We don't want to accept programs which
|
||||
/// pass borrowck only when `panic=abort` or some assertions are disabled
|
||||
/// due to release vs. debug mode builds. This needs to be an `Option` because
|
||||
/// of the `remove_noop_landing_pads` and `abort_unwinding_calls` passes.
|
||||
unwind: Option<BasicBlockId>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum BorrowKind {
|
||||
/// Data must be immutable and is aliasable.
|
||||
Shared,
|
||||
|
||||
/// The immediately borrowed place must be immutable, but projections from
|
||||
/// it don't need to be. For example, a shallow borrow of `a.b` doesn't
|
||||
/// conflict with a mutable borrow of `a.b.c`.
|
||||
///
|
||||
/// This is used when lowering matches: when matching on a place we want to
|
||||
/// ensure that place have the same value from the start of the match until
|
||||
/// an arm is selected. This prevents this code from compiling:
|
||||
/// ```compile_fail,E0510
|
||||
/// let mut x = &Some(0);
|
||||
/// match *x {
|
||||
/// None => (),
|
||||
/// Some(_) if { x = &None; false } => (),
|
||||
/// Some(_) => (),
|
||||
/// }
|
||||
/// ```
|
||||
/// This can't be a shared borrow because mutably borrowing (*x as Some).0
|
||||
/// should not prevent `if let None = x { ... }`, for example, because the
|
||||
/// mutating `(*x as Some).0` can't affect the discriminant of `x`.
|
||||
/// We can also report errors with this kind of borrow differently.
|
||||
Shallow,
|
||||
|
||||
/// Data must be immutable but not aliasable. This kind of borrow
|
||||
/// cannot currently be expressed by the user and is used only in
|
||||
/// implicit closure bindings. It is needed when the closure is
|
||||
/// borrowing or mutating a mutable referent, e.g.:
|
||||
/// ```
|
||||
/// let mut z = 3;
|
||||
/// let x: &mut isize = &mut z;
|
||||
/// let y = || *x += 5;
|
||||
/// ```
|
||||
/// If we were to try to translate this closure into a more explicit
|
||||
/// form, we'd encounter an error with the code as written:
|
||||
/// ```compile_fail,E0594
|
||||
/// struct Env<'a> { x: &'a &'a mut isize }
|
||||
/// let mut z = 3;
|
||||
/// let x: &mut isize = &mut z;
|
||||
/// let y = (&mut Env { x: &x }, fn_ptr); // Closure is pair of env and fn
|
||||
/// fn fn_ptr(env: &mut Env) { **env.x += 5; }
|
||||
/// ```
|
||||
/// This is then illegal because you cannot mutate an `&mut` found
|
||||
/// in an aliasable location. To solve, you'd have to translate with
|
||||
/// an `&mut` borrow:
|
||||
/// ```compile_fail,E0596
|
||||
/// struct Env<'a> { x: &'a mut &'a mut isize }
|
||||
/// let mut z = 3;
|
||||
/// let x: &mut isize = &mut z;
|
||||
/// let y = (&mut Env { x: &mut x }, fn_ptr); // changed from &x to &mut x
|
||||
/// fn fn_ptr(env: &mut Env) { **env.x += 5; }
|
||||
/// ```
|
||||
/// Now the assignment to `**env.x` is legal, but creating a
|
||||
/// mutable pointer to `x` is not because `x` is not mutable. We
|
||||
/// could fix this by declaring `x` as `let mut x`. This is ok in
|
||||
/// user code, if awkward, but extra weird for closures, since the
|
||||
/// borrow is hidden.
|
||||
///
|
||||
/// So we introduce a "unique imm" borrow -- the referent is
|
||||
/// immutable, but not aliasable. This solves the problem. For
|
||||
/// simplicity, we don't give users the way to express this
|
||||
/// borrow, it's just used when translating closures.
|
||||
Unique,
|
||||
|
||||
/// Data is mutable and not aliasable.
|
||||
Mut {
|
||||
/// `true` if this borrow arose from method-call auto-ref
|
||||
/// (i.e., `adjustment::Adjust::Borrow`).
|
||||
allow_two_phase_borrow: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl BorrowKind {
|
||||
fn from_hir(m: hir_def::type_ref::Mutability) -> Self {
|
||||
match m {
|
||||
hir_def::type_ref::Mutability::Shared => BorrowKind::Shared,
|
||||
hir_def::type_ref::Mutability::Mut => BorrowKind::Mut { allow_two_phase_borrow: false },
|
||||
}
|
||||
}
|
||||
|
||||
fn from_chalk(m: Mutability) -> Self {
|
||||
match m {
|
||||
Mutability::Not => BorrowKind::Shared,
|
||||
Mutability::Mut => BorrowKind::Mut { allow_two_phase_borrow: false },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum UnOp {
|
||||
/// The `!` operator for logical inversion
|
||||
Not,
|
||||
/// The `-` operator for negation
|
||||
Neg,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum BinOp {
|
||||
/// The `+` operator (addition)
|
||||
Add,
|
||||
/// The `-` operator (subtraction)
|
||||
Sub,
|
||||
/// The `*` operator (multiplication)
|
||||
Mul,
|
||||
/// The `/` operator (division)
|
||||
///
|
||||
/// Division by zero is UB, because the compiler should have inserted checks
|
||||
/// prior to this.
|
||||
Div,
|
||||
/// The `%` operator (modulus)
|
||||
///
|
||||
/// Using zero as the modulus (second operand) is UB, because the compiler
|
||||
/// should have inserted checks prior to this.
|
||||
Rem,
|
||||
/// The `^` operator (bitwise xor)
|
||||
BitXor,
|
||||
/// The `&` operator (bitwise and)
|
||||
BitAnd,
|
||||
/// The `|` operator (bitwise or)
|
||||
BitOr,
|
||||
/// The `<<` operator (shift left)
|
||||
///
|
||||
/// The offset is truncated to the size of the first operand before shifting.
|
||||
Shl,
|
||||
/// The `>>` operator (shift right)
|
||||
///
|
||||
/// The offset is truncated to the size of the first operand before shifting.
|
||||
Shr,
|
||||
/// The `==` operator (equality)
|
||||
Eq,
|
||||
/// The `<` operator (less than)
|
||||
Lt,
|
||||
/// The `<=` operator (less than or equal to)
|
||||
Le,
|
||||
/// The `!=` operator (not equal to)
|
||||
Ne,
|
||||
/// The `>=` operator (greater than or equal to)
|
||||
Ge,
|
||||
/// The `>` operator (greater than)
|
||||
Gt,
|
||||
/// The `ptr.offset` operator
|
||||
Offset,
|
||||
}
|
||||
|
||||
impl Display for BinOp {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
BinOp::Add => "+",
|
||||
BinOp::Sub => "-",
|
||||
BinOp::Mul => "*",
|
||||
BinOp::Div => "/",
|
||||
BinOp::Rem => "%",
|
||||
BinOp::BitXor => "^",
|
||||
BinOp::BitAnd => "&",
|
||||
BinOp::BitOr => "|",
|
||||
BinOp::Shl => "<<",
|
||||
BinOp::Shr => ">>",
|
||||
BinOp::Eq => "==",
|
||||
BinOp::Lt => "<",
|
||||
BinOp::Le => "<=",
|
||||
BinOp::Ne => "!=",
|
||||
BinOp::Ge => ">=",
|
||||
BinOp::Gt => ">",
|
||||
BinOp::Offset => "`offset`",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<hir_def::expr::ArithOp> for BinOp {
|
||||
fn from(value: hir_def::expr::ArithOp) -> Self {
|
||||
match value {
|
||||
hir_def::expr::ArithOp::Add => BinOp::Add,
|
||||
hir_def::expr::ArithOp::Mul => BinOp::Mul,
|
||||
hir_def::expr::ArithOp::Sub => BinOp::Sub,
|
||||
hir_def::expr::ArithOp::Div => BinOp::Div,
|
||||
hir_def::expr::ArithOp::Rem => BinOp::Rem,
|
||||
hir_def::expr::ArithOp::Shl => BinOp::Shl,
|
||||
hir_def::expr::ArithOp::Shr => BinOp::Shr,
|
||||
hir_def::expr::ArithOp::BitXor => BinOp::BitXor,
|
||||
hir_def::expr::ArithOp::BitOr => BinOp::BitOr,
|
||||
hir_def::expr::ArithOp::BitAnd => BinOp::BitAnd,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<hir_def::expr::CmpOp> for BinOp {
|
||||
fn from(value: hir_def::expr::CmpOp) -> Self {
|
||||
match value {
|
||||
hir_def::expr::CmpOp::Eq { negated: false } => BinOp::Eq,
|
||||
hir_def::expr::CmpOp::Eq { negated: true } => BinOp::Ne,
|
||||
hir_def::expr::CmpOp::Ord { ordering: Ordering::Greater, strict: false } => BinOp::Ge,
|
||||
hir_def::expr::CmpOp::Ord { ordering: Ordering::Greater, strict: true } => BinOp::Gt,
|
||||
hir_def::expr::CmpOp::Ord { ordering: Ordering::Less, strict: false } => BinOp::Le,
|
||||
hir_def::expr::CmpOp::Ord { ordering: Ordering::Less, strict: true } => BinOp::Lt,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Operand> for Rvalue {
|
||||
fn from(x: Operand) -> Self {
|
||||
Self::Use(x)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum CastKind {
|
||||
/// An exposing pointer to address cast. A cast between a pointer and an integer type, or
|
||||
/// between a function pointer and an integer type.
|
||||
/// See the docs on `expose_addr` for more details.
|
||||
PointerExposeAddress,
|
||||
/// An address-to-pointer cast that picks up an exposed provenance.
|
||||
/// See the docs on `from_exposed_addr` for more details.
|
||||
PointerFromExposedAddress,
|
||||
/// All sorts of pointer-to-pointer casts. Note that reference-to-raw-ptr casts are
|
||||
/// translated into `&raw mut/const *r`, i.e., they are not actually casts.
|
||||
Pointer(PointerCast),
|
||||
/// Cast into a dyn* object.
|
||||
DynStar,
|
||||
IntToInt,
|
||||
FloatToInt,
|
||||
FloatToFloat,
|
||||
IntToFloat,
|
||||
PtrToPtr,
|
||||
FnPtrToPtr,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum Rvalue {
|
||||
/// Yields the operand unchanged
|
||||
Use(Operand),
|
||||
|
||||
/// Creates an array where each element is the value of the operand.
|
||||
///
|
||||
/// This is the cause of a bug in the case where the repetition count is zero because the value
|
||||
/// is not dropped, see [#74836].
|
||||
///
|
||||
/// Corresponds to source code like `[x; 32]`.
|
||||
///
|
||||
/// [#74836]: https://github.com/rust-lang/rust/issues/74836
|
||||
//Repeat(Operand, ty::Const),
|
||||
|
||||
/// Creates a reference of the indicated kind to the place.
|
||||
///
|
||||
/// There is not much to document here, because besides the obvious parts the semantics of this
|
||||
/// are essentially entirely a part of the aliasing model. There are many UCG issues discussing
|
||||
/// exactly what the behavior of this operation should be.
|
||||
///
|
||||
/// `Shallow` borrows are disallowed after drop lowering.
|
||||
Ref(BorrowKind, Place),
|
||||
|
||||
/// Creates a pointer/reference to the given thread local.
|
||||
///
|
||||
/// The yielded type is a `*mut T` if the static is mutable, otherwise if the static is extern a
|
||||
/// `*const T`, and if neither of those apply a `&T`.
|
||||
///
|
||||
/// **Note:** This is a runtime operation that actually executes code and is in this sense more
|
||||
/// like a function call. Also, eliminating dead stores of this rvalue causes `fn main() {}` to
|
||||
/// SIGILL for some reason that I (JakobDegen) never got a chance to look into.
|
||||
///
|
||||
/// **Needs clarification**: Are there weird additional semantics here related to the runtime
|
||||
/// nature of this operation?
|
||||
//ThreadLocalRef(DefId),
|
||||
|
||||
/// Creates a pointer with the indicated mutability to the place.
|
||||
///
|
||||
/// This is generated by pointer casts like `&v as *const _` or raw address of expressions like
|
||||
/// `&raw v` or `addr_of!(v)`.
|
||||
///
|
||||
/// Like with references, the semantics of this operation are heavily dependent on the aliasing
|
||||
/// model.
|
||||
//AddressOf(Mutability, Place),
|
||||
|
||||
/// Yields the length of the place, as a `usize`.
|
||||
///
|
||||
/// If the type of the place is an array, this is the array length. For slices (`[T]`, not
|
||||
/// `&[T]`) this accesses the place's metadata to determine the length. This rvalue is
|
||||
/// ill-formed for places of other types.
|
||||
Len(Place),
|
||||
|
||||
/// Performs essentially all of the casts that can be performed via `as`.
|
||||
///
|
||||
/// This allows for casts from/to a variety of types.
|
||||
///
|
||||
/// **FIXME**: Document exactly which `CastKind`s allow which types of casts. Figure out why
|
||||
/// `ArrayToPointer` and `MutToConstPointer` are special.
|
||||
Cast(CastKind, Operand, Ty),
|
||||
|
||||
// FIXME link to `pointer::offset` when it hits stable.
|
||||
/// * `Offset` has the same semantics as `pointer::offset`, except that the second
|
||||
/// parameter may be a `usize` as well.
|
||||
/// * The comparison operations accept `bool`s, `char`s, signed or unsigned integers, floats,
|
||||
/// raw pointers, or function pointers and return a `bool`. The types of the operands must be
|
||||
/// matching, up to the usual caveat of the lifetimes in function pointers.
|
||||
/// * Left and right shift operations accept signed or unsigned integers not necessarily of the
|
||||
/// same type and return a value of the same type as their LHS. Like in Rust, the RHS is
|
||||
/// truncated as needed.
|
||||
/// * The `Bit*` operations accept signed integers, unsigned integers, or bools with matching
|
||||
/// types and return a value of that type.
|
||||
/// * The remaining operations accept signed integers, unsigned integers, or floats with
|
||||
/// matching types and return a value of that type.
|
||||
//BinaryOp(BinOp, Box<(Operand, Operand)>),
|
||||
|
||||
/// Same as `BinaryOp`, but yields `(T, bool)` with a `bool` indicating an error condition.
|
||||
///
|
||||
/// When overflow checking is disabled and we are generating run-time code, the error condition
|
||||
/// is false. Otherwise, and always during CTFE, the error condition is determined as described
|
||||
/// below.
|
||||
///
|
||||
/// For addition, subtraction, and multiplication on integers the error condition is set when
|
||||
/// the infinite precision result would be unequal to the actual result.
|
||||
///
|
||||
/// For shift operations on integers the error condition is set when the value of right-hand
|
||||
/// side is greater than or equal to the number of bits in the type of the left-hand side, or
|
||||
/// when the value of right-hand side is negative.
|
||||
///
|
||||
/// Other combinations of types and operators are unsupported.
|
||||
CheckedBinaryOp(BinOp, Operand, Operand),
|
||||
|
||||
/// Computes a value as described by the operation.
|
||||
//NullaryOp(NullOp, Ty),
|
||||
|
||||
/// Exactly like `BinaryOp`, but less operands.
|
||||
///
|
||||
/// Also does two's-complement arithmetic. Negation requires a signed integer or a float;
|
||||
/// bitwise not requires a signed integer, unsigned integer, or bool. Both operation kinds
|
||||
/// return a value with the same type as their operand.
|
||||
UnaryOp(UnOp, Operand),
|
||||
|
||||
/// Computes the discriminant of the place, returning it as an integer of type
|
||||
/// [`discriminant_ty`]. Returns zero for types without discriminant.
|
||||
///
|
||||
/// The validity requirements for the underlying value are undecided for this rvalue, see
|
||||
/// [#91095]. Note too that the value of the discriminant is not the same thing as the
|
||||
/// variant index; use [`discriminant_for_variant`] to convert.
|
||||
///
|
||||
/// [`discriminant_ty`]: crate::ty::Ty::discriminant_ty
|
||||
/// [#91095]: https://github.com/rust-lang/rust/issues/91095
|
||||
/// [`discriminant_for_variant`]: crate::ty::Ty::discriminant_for_variant
|
||||
Discriminant(Place),
|
||||
|
||||
/// Creates an aggregate value, like a tuple or struct.
|
||||
///
|
||||
/// This is needed because dataflow analysis needs to distinguish
|
||||
/// `dest = Foo { x: ..., y: ... }` from `dest.x = ...; dest.y = ...;` in the case that `Foo`
|
||||
/// has a destructor.
|
||||
///
|
||||
/// Disallowed after deaggregation for all aggregate kinds except `Array` and `Generator`. After
|
||||
/// generator lowering, `Generator` aggregate kinds are disallowed too.
|
||||
Aggregate(AggregateKind, Vec<Operand>),
|
||||
|
||||
/// Transmutes a `*mut u8` into shallow-initialized `Box<T>`.
|
||||
///
|
||||
/// This is different from a normal transmute because dataflow analysis will treat the box as
|
||||
/// initialized but its content as uninitialized. Like other pointer casts, this in general
|
||||
/// affects alias analysis.
|
||||
ShallowInitBox(Operand, Ty),
|
||||
|
||||
/// A CopyForDeref is equivalent to a read from a place at the
|
||||
/// codegen level, but is treated specially by drop elaboration. When such a read happens, it
|
||||
/// is guaranteed (via nature of the mir_opt `Derefer` in rustc_mir_transform/src/deref_separator)
|
||||
/// that the only use of the returned value is a deref operation, immediately
|
||||
/// followed by one or more projections. Drop elaboration treats this rvalue as if the
|
||||
/// read never happened and just projects further. This allows simplifying various MIR
|
||||
/// optimizations and codegen backends that previously had to handle deref operations anywhere
|
||||
/// in a place.
|
||||
CopyForDeref(Place),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum StatementKind {
|
||||
Assign(Place, Rvalue),
|
||||
//FakeRead(Box<(FakeReadCause, Place)>),
|
||||
//SetDiscriminant {
|
||||
// place: Box<Place>,
|
||||
// variant_index: VariantIdx,
|
||||
//},
|
||||
Deinit(Place),
|
||||
StorageLive(LocalId),
|
||||
StorageDead(LocalId),
|
||||
//Retag(RetagKind, Box<Place>),
|
||||
//AscribeUserType(Place, UserTypeProjection, Variance),
|
||||
//Intrinsic(Box<NonDivergingIntrinsic>),
|
||||
Nop,
|
||||
}
|
||||
impl StatementKind {
|
||||
fn with_span(self, span: MirSpan) -> Statement {
|
||||
Statement { kind: self, span }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct Statement {
|
||||
pub kind: StatementKind,
|
||||
pub span: MirSpan,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
pub struct BasicBlock {
|
||||
/// List of statements in this block.
|
||||
pub statements: Vec<Statement>,
|
||||
|
||||
/// Terminator for this block.
|
||||
///
|
||||
/// N.B., this should generally ONLY be `None` during construction.
|
||||
/// Therefore, you should generally access it via the
|
||||
/// `terminator()` or `terminator_mut()` methods. The only
|
||||
/// exception is that certain passes, such as `simplify_cfg`, swap
|
||||
/// out the terminator temporarily with `None` while they continue
|
||||
/// to recurse over the set of basic blocks.
|
||||
pub terminator: Option<Terminator>,
|
||||
|
||||
/// If true, this block lies on an unwind path. This is used
|
||||
/// during codegen where distinct kinds of basic blocks may be
|
||||
/// generated (particularly for MSVC cleanup). Unwind blocks must
|
||||
/// only branch to other unwind blocks.
|
||||
pub is_cleanup: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct MirBody {
|
||||
pub basic_blocks: Arena<BasicBlock>,
|
||||
pub locals: Arena<Local>,
|
||||
pub start_block: BasicBlockId,
|
||||
pub owner: DefWithBodyId,
|
||||
pub arg_count: usize,
|
||||
pub binding_locals: ArenaMap<BindingId, LocalId>,
|
||||
pub param_locals: Vec<LocalId>,
|
||||
}
|
||||
|
||||
fn const_as_usize(c: &Const) -> usize {
|
||||
try_const_usize(c).unwrap() as usize
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum MirSpan {
|
||||
ExprId(ExprId),
|
||||
PatId(PatId),
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl_from!(ExprId, PatId for MirSpan);
|
223
crates/hir-ty/src/mir/borrowck.rs
Normal file
223
crates/hir-ty/src/mir/borrowck.rs
Normal file
|
@ -0,0 +1,223 @@
|
|||
//! MIR borrow checker, which is used in diagnostics like `unused_mut`
|
||||
|
||||
// Currently it is an ad-hoc implementation, only useful for mutability analysis. Feel free to remove all of these
|
||||
// if needed for implementing a proper borrow checker.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use hir_def::DefWithBodyId;
|
||||
use la_arena::ArenaMap;
|
||||
use stdx::never;
|
||||
|
||||
use crate::db::HirDatabase;
|
||||
|
||||
use super::{
|
||||
BasicBlockId, BorrowKind, LocalId, MirBody, MirLowerError, MirSpan, Place, ProjectionElem,
|
||||
Rvalue, StatementKind, Terminator,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
/// Stores spans which implies that the local should be mutable.
|
||||
pub enum MutabilityReason {
|
||||
Mut { spans: Vec<MirSpan> },
|
||||
Not,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct BorrowckResult {
|
||||
pub mir_body: Arc<MirBody>,
|
||||
pub mutability_of_locals: ArenaMap<LocalId, MutabilityReason>,
|
||||
}
|
||||
|
||||
pub fn borrowck_query(
|
||||
db: &dyn HirDatabase,
|
||||
def: DefWithBodyId,
|
||||
) -> Result<Arc<BorrowckResult>, MirLowerError> {
|
||||
let _p = profile::span("borrowck_query");
|
||||
let body = db.mir_body(def)?;
|
||||
let r = BorrowckResult { mutability_of_locals: mutability_of_locals(&body), mir_body: body };
|
||||
Ok(Arc::new(r))
|
||||
}
|
||||
|
||||
fn is_place_direct(lvalue: &Place) -> bool {
|
||||
!lvalue.projection.iter().any(|x| *x == ProjectionElem::Deref)
|
||||
}
|
||||
|
||||
enum ProjectionCase {
|
||||
/// Projection is a local
|
||||
Direct,
|
||||
/// Projection is some field or slice of a local
|
||||
DirectPart,
|
||||
/// Projection is deref of something
|
||||
Indirect,
|
||||
}
|
||||
|
||||
fn place_case(lvalue: &Place) -> ProjectionCase {
|
||||
let mut is_part_of = false;
|
||||
for proj in lvalue.projection.iter().rev() {
|
||||
match proj {
|
||||
ProjectionElem::Deref => return ProjectionCase::Indirect, // It's indirect
|
||||
ProjectionElem::ConstantIndex { .. }
|
||||
| ProjectionElem::Subslice { .. }
|
||||
| ProjectionElem::Field(_)
|
||||
| ProjectionElem::TupleField(_)
|
||||
| ProjectionElem::Index(_) => {
|
||||
is_part_of = true;
|
||||
}
|
||||
ProjectionElem::OpaqueCast(_) => (),
|
||||
}
|
||||
}
|
||||
if is_part_of {
|
||||
ProjectionCase::DirectPart
|
||||
} else {
|
||||
ProjectionCase::Direct
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a map from basic blocks to the set of locals that might be ever initialized before
|
||||
/// the start of the block. Only `StorageDead` can remove something from this map, and we ignore
|
||||
/// `Uninit` and `drop` and similars after initialization.
|
||||
fn ever_initialized_map(body: &MirBody) -> ArenaMap<BasicBlockId, ArenaMap<LocalId, bool>> {
|
||||
let mut result: ArenaMap<BasicBlockId, ArenaMap<LocalId, bool>> =
|
||||
body.basic_blocks.iter().map(|x| (x.0, ArenaMap::default())).collect();
|
||||
fn dfs(
|
||||
body: &MirBody,
|
||||
b: BasicBlockId,
|
||||
l: LocalId,
|
||||
result: &mut ArenaMap<BasicBlockId, ArenaMap<LocalId, bool>>,
|
||||
) {
|
||||
let mut is_ever_initialized = result[b][l]; // It must be filled, as we use it as mark for dfs
|
||||
let block = &body.basic_blocks[b];
|
||||
for statement in &block.statements {
|
||||
match &statement.kind {
|
||||
StatementKind::Assign(p, _) => {
|
||||
if p.projection.len() == 0 && p.local == l {
|
||||
is_ever_initialized = true;
|
||||
}
|
||||
}
|
||||
StatementKind::StorageDead(p) => {
|
||||
if *p == l {
|
||||
is_ever_initialized = false;
|
||||
}
|
||||
}
|
||||
StatementKind::Deinit(_) | StatementKind::Nop | StatementKind::StorageLive(_) => (),
|
||||
}
|
||||
}
|
||||
let Some(terminator) = &block.terminator else {
|
||||
never!("Terminator should be none only in construction");
|
||||
return;
|
||||
};
|
||||
let targets = match terminator {
|
||||
Terminator::Goto { target } => vec![*target],
|
||||
Terminator::SwitchInt { targets, .. } => targets.all_targets().to_vec(),
|
||||
Terminator::Resume
|
||||
| Terminator::Abort
|
||||
| Terminator::Return
|
||||
| Terminator::Unreachable => vec![],
|
||||
Terminator::Call { target, cleanup, destination, .. } => {
|
||||
if destination.projection.len() == 0 && destination.local == l {
|
||||
is_ever_initialized = true;
|
||||
}
|
||||
target.into_iter().chain(cleanup.into_iter()).copied().collect()
|
||||
}
|
||||
Terminator::Drop { .. }
|
||||
| Terminator::DropAndReplace { .. }
|
||||
| Terminator::Assert { .. }
|
||||
| Terminator::Yield { .. }
|
||||
| Terminator::GeneratorDrop
|
||||
| Terminator::FalseEdge { .. }
|
||||
| Terminator::FalseUnwind { .. } => {
|
||||
never!("We don't emit these MIR terminators yet");
|
||||
vec![]
|
||||
}
|
||||
};
|
||||
for target in targets {
|
||||
if !result[target].contains_idx(l) || !result[target][l] && is_ever_initialized {
|
||||
result[target].insert(l, is_ever_initialized);
|
||||
dfs(body, target, l, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
for &l in &body.param_locals {
|
||||
result[body.start_block].insert(l, true);
|
||||
dfs(body, body.start_block, l, &mut result);
|
||||
}
|
||||
for l in body.locals.iter().map(|x| x.0) {
|
||||
if !result[body.start_block].contains_idx(l) {
|
||||
result[body.start_block].insert(l, false);
|
||||
dfs(body, body.start_block, l, &mut result);
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn mutability_of_locals(body: &MirBody) -> ArenaMap<LocalId, MutabilityReason> {
|
||||
let mut result: ArenaMap<LocalId, MutabilityReason> =
|
||||
body.locals.iter().map(|x| (x.0, MutabilityReason::Not)).collect();
|
||||
let mut push_mut_span = |local, span| match &mut result[local] {
|
||||
MutabilityReason::Mut { spans } => spans.push(span),
|
||||
x @ MutabilityReason::Not => *x = MutabilityReason::Mut { spans: vec![span] },
|
||||
};
|
||||
let ever_init_maps = ever_initialized_map(body);
|
||||
for (block_id, mut ever_init_map) in ever_init_maps.into_iter() {
|
||||
let block = &body.basic_blocks[block_id];
|
||||
for statement in &block.statements {
|
||||
match &statement.kind {
|
||||
StatementKind::Assign(place, value) => {
|
||||
match place_case(place) {
|
||||
ProjectionCase::Direct => {
|
||||
if ever_init_map.get(place.local).copied().unwrap_or_default() {
|
||||
push_mut_span(place.local, statement.span);
|
||||
} else {
|
||||
ever_init_map.insert(place.local, true);
|
||||
}
|
||||
}
|
||||
ProjectionCase::DirectPart => {
|
||||
// Partial initialization is not supported, so it is definitely `mut`
|
||||
push_mut_span(place.local, statement.span);
|
||||
}
|
||||
ProjectionCase::Indirect => (),
|
||||
}
|
||||
if let Rvalue::Ref(BorrowKind::Mut { .. }, p) = value {
|
||||
if is_place_direct(p) {
|
||||
push_mut_span(p.local, statement.span);
|
||||
}
|
||||
}
|
||||
}
|
||||
StatementKind::StorageDead(p) => {
|
||||
ever_init_map.insert(*p, false);
|
||||
}
|
||||
StatementKind::Deinit(_) | StatementKind::StorageLive(_) | StatementKind::Nop => (),
|
||||
}
|
||||
}
|
||||
let Some(terminator) = &block.terminator else {
|
||||
never!("Terminator should be none only in construction");
|
||||
continue;
|
||||
};
|
||||
match terminator {
|
||||
Terminator::Goto { .. }
|
||||
| Terminator::Resume
|
||||
| Terminator::Abort
|
||||
| Terminator::Return
|
||||
| Terminator::Unreachable
|
||||
| Terminator::FalseEdge { .. }
|
||||
| Terminator::FalseUnwind { .. }
|
||||
| Terminator::GeneratorDrop
|
||||
| Terminator::SwitchInt { .. }
|
||||
| Terminator::Drop { .. }
|
||||
| Terminator::DropAndReplace { .. }
|
||||
| Terminator::Assert { .. }
|
||||
| Terminator::Yield { .. } => (),
|
||||
Terminator::Call { destination, .. } => {
|
||||
if destination.projection.len() == 0 {
|
||||
if ever_init_map.get(destination.local).copied().unwrap_or_default() {
|
||||
push_mut_span(destination.local, MirSpan::Unknown);
|
||||
} else {
|
||||
ever_init_map.insert(destination.local, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
1253
crates/hir-ty/src/mir/eval.rs
Normal file
1253
crates/hir-ty/src/mir/eval.rs
Normal file
File diff suppressed because it is too large
Load diff
1577
crates/hir-ty/src/mir/lower.rs
Normal file
1577
crates/hir-ty/src/mir/lower.rs
Normal file
File diff suppressed because it is too large
Load diff
237
crates/hir-ty/src/mir/lower/as_place.rs
Normal file
237
crates/hir-ty/src/mir/lower/as_place.rs
Normal file
|
@ -0,0 +1,237 @@
|
|||
//! MIR lowering for places
|
||||
|
||||
use super::*;
|
||||
use hir_expand::name;
|
||||
|
||||
macro_rules! not_supported {
|
||||
($x: expr) => {
|
||||
return Err(MirLowerError::NotSupported(format!($x)))
|
||||
};
|
||||
}
|
||||
|
||||
impl MirLowerCtx<'_> {
|
||||
fn lower_expr_to_some_place_without_adjust(
|
||||
&mut self,
|
||||
expr_id: ExprId,
|
||||
prev_block: BasicBlockId,
|
||||
) -> Result<Option<(Place, BasicBlockId)>> {
|
||||
let ty = self.expr_ty(expr_id);
|
||||
let place = self.temp(ty)?;
|
||||
let Some(current) = self.lower_expr_to_place_without_adjust(expr_id, place.into(), prev_block)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
Ok(Some((place.into(), current)))
|
||||
}
|
||||
|
||||
fn lower_expr_to_some_place_with_adjust(
|
||||
&mut self,
|
||||
expr_id: ExprId,
|
||||
prev_block: BasicBlockId,
|
||||
adjustments: &[Adjustment],
|
||||
) -> Result<Option<(Place, BasicBlockId)>> {
|
||||
let ty =
|
||||
adjustments.last().map(|x| x.target.clone()).unwrap_or_else(|| self.expr_ty(expr_id));
|
||||
let place = self.temp(ty)?;
|
||||
let Some(current) = self.lower_expr_to_place_with_adjust(expr_id, place.into(), prev_block, adjustments)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
Ok(Some((place.into(), current)))
|
||||
}
|
||||
|
||||
pub(super) fn lower_expr_as_place_with_adjust(
|
||||
&mut self,
|
||||
current: BasicBlockId,
|
||||
expr_id: ExprId,
|
||||
upgrade_rvalue: bool,
|
||||
adjustments: &[Adjustment],
|
||||
) -> Result<Option<(Place, BasicBlockId)>> {
|
||||
let try_rvalue = |this: &mut MirLowerCtx<'_>| {
|
||||
if !upgrade_rvalue {
|
||||
return Err(MirLowerError::MutatingRvalue);
|
||||
}
|
||||
this.lower_expr_to_some_place_with_adjust(expr_id, current, adjustments)
|
||||
};
|
||||
if let Some((last, rest)) = adjustments.split_last() {
|
||||
match last.kind {
|
||||
Adjust::Deref(None) => {
|
||||
let Some(mut x) = self.lower_expr_as_place_with_adjust(
|
||||
current,
|
||||
expr_id,
|
||||
upgrade_rvalue,
|
||||
rest,
|
||||
)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
x.0.projection.push(ProjectionElem::Deref);
|
||||
Ok(Some(x))
|
||||
}
|
||||
Adjust::Deref(Some(od)) => {
|
||||
let Some((r, current)) = self.lower_expr_as_place_with_adjust(
|
||||
current,
|
||||
expr_id,
|
||||
upgrade_rvalue,
|
||||
rest,
|
||||
)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
self.lower_overloaded_deref(
|
||||
current,
|
||||
r,
|
||||
rest.last()
|
||||
.map(|x| x.target.clone())
|
||||
.unwrap_or_else(|| self.expr_ty(expr_id)),
|
||||
last.target.clone(),
|
||||
expr_id.into(),
|
||||
match od.0 {
|
||||
Some(Mutability::Mut) => true,
|
||||
Some(Mutability::Not) => false,
|
||||
None => {
|
||||
not_supported!("implicit overloaded deref with unknown mutability")
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
Adjust::NeverToAny | Adjust::Borrow(_) | Adjust::Pointer(_) => try_rvalue(self),
|
||||
}
|
||||
} else {
|
||||
self.lower_expr_as_place_without_adjust(current, expr_id, upgrade_rvalue)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn lower_expr_as_place(
|
||||
&mut self,
|
||||
current: BasicBlockId,
|
||||
expr_id: ExprId,
|
||||
upgrade_rvalue: bool,
|
||||
) -> Result<Option<(Place, BasicBlockId)>> {
|
||||
match self.infer.expr_adjustments.get(&expr_id) {
|
||||
Some(a) => self.lower_expr_as_place_with_adjust(current, expr_id, upgrade_rvalue, a),
|
||||
None => self.lower_expr_as_place_without_adjust(current, expr_id, upgrade_rvalue),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn lower_expr_as_place_without_adjust(
|
||||
&mut self,
|
||||
current: BasicBlockId,
|
||||
expr_id: ExprId,
|
||||
upgrade_rvalue: bool,
|
||||
) -> Result<Option<(Place, BasicBlockId)>> {
|
||||
let try_rvalue = |this: &mut MirLowerCtx<'_>| {
|
||||
if !upgrade_rvalue {
|
||||
return Err(MirLowerError::MutatingRvalue);
|
||||
}
|
||||
this.lower_expr_to_some_place_without_adjust(expr_id, current)
|
||||
};
|
||||
match &self.body.exprs[expr_id] {
|
||||
Expr::Path(p) => {
|
||||
let resolver = resolver_for_expr(self.db.upcast(), self.owner, expr_id);
|
||||
let Some(pr) = resolver.resolve_path_in_value_ns(self.db.upcast(), p.mod_path()) else {
|
||||
return Err(MirLowerError::unresolved_path(self.db, p));
|
||||
};
|
||||
let pr = match pr {
|
||||
ResolveValueResult::ValueNs(v) => v,
|
||||
ResolveValueResult::Partial(..) => return try_rvalue(self),
|
||||
};
|
||||
match pr {
|
||||
ValueNs::LocalBinding(pat_id) => {
|
||||
Ok(Some((self.result.binding_locals[pat_id].into(), current)))
|
||||
}
|
||||
_ => try_rvalue(self),
|
||||
}
|
||||
}
|
||||
Expr::UnaryOp { expr, op } => match op {
|
||||
hir_def::expr::UnaryOp::Deref => {
|
||||
if !matches!(
|
||||
self.expr_ty(*expr).kind(Interner),
|
||||
TyKind::Ref(..) | TyKind::Raw(..)
|
||||
) {
|
||||
let Some(_) = self.lower_expr_as_place(current, *expr, true)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
not_supported!("explicit overloaded deref");
|
||||
}
|
||||
let Some((mut r, current)) = self.lower_expr_as_place(current, *expr, true)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
r.projection.push(ProjectionElem::Deref);
|
||||
Ok(Some((r, current)))
|
||||
}
|
||||
_ => try_rvalue(self),
|
||||
},
|
||||
Expr::Field { expr, .. } => {
|
||||
let Some((mut r, current)) = self.lower_expr_as_place(current, *expr, true)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
self.push_field_projection(&mut r, expr_id)?;
|
||||
Ok(Some((r, current)))
|
||||
}
|
||||
Expr::Index { base, index } => {
|
||||
let base_ty = self.expr_ty_after_adjustments(*base);
|
||||
let index_ty = self.expr_ty_after_adjustments(*index);
|
||||
if index_ty != TyBuilder::usize()
|
||||
|| !matches!(base_ty.kind(Interner), TyKind::Array(..) | TyKind::Slice(..))
|
||||
{
|
||||
not_supported!("overloaded index");
|
||||
}
|
||||
let Some((mut p_base, current)) =
|
||||
self.lower_expr_as_place(current, *base, true)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
let l_index = self.temp(self.expr_ty_after_adjustments(*index))?;
|
||||
let Some(current) = self.lower_expr_to_place(*index, l_index.into(), current)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
p_base.projection.push(ProjectionElem::Index(l_index));
|
||||
Ok(Some((p_base, current)))
|
||||
}
|
||||
_ => try_rvalue(self),
|
||||
}
|
||||
}
|
||||
|
||||
fn lower_overloaded_deref(
|
||||
&mut self,
|
||||
current: BasicBlockId,
|
||||
place: Place,
|
||||
source_ty: Ty,
|
||||
target_ty: Ty,
|
||||
span: MirSpan,
|
||||
mutability: bool,
|
||||
) -> Result<Option<(Place, BasicBlockId)>> {
|
||||
let (chalk_mut, trait_lang_item, trait_method_name, borrow_kind) = if !mutability {
|
||||
(Mutability::Not, LangItem::Deref, name![deref], BorrowKind::Shared)
|
||||
} else {
|
||||
(
|
||||
Mutability::Mut,
|
||||
LangItem::DerefMut,
|
||||
name![deref_mut],
|
||||
BorrowKind::Mut { allow_two_phase_borrow: false },
|
||||
)
|
||||
};
|
||||
let ty_ref = TyKind::Ref(chalk_mut, static_lifetime(), source_ty.clone()).intern(Interner);
|
||||
let target_ty_ref = TyKind::Ref(chalk_mut, static_lifetime(), target_ty).intern(Interner);
|
||||
let ref_place: Place = self.temp(ty_ref)?.into();
|
||||
self.push_assignment(current, ref_place.clone(), Rvalue::Ref(borrow_kind, place), span);
|
||||
let deref_trait = self
|
||||
.resolve_lang_item(trait_lang_item)?
|
||||
.as_trait()
|
||||
.ok_or(MirLowerError::LangItemNotFound(trait_lang_item))?;
|
||||
let deref_fn = self
|
||||
.db
|
||||
.trait_data(deref_trait)
|
||||
.method_by_name(&trait_method_name)
|
||||
.ok_or(MirLowerError::LangItemNotFound(trait_lang_item))?;
|
||||
let deref_fn_op = Operand::const_zst(
|
||||
TyKind::FnDef(
|
||||
self.db.intern_callable_def(CallableDefId::FunctionId(deref_fn)).into(),
|
||||
Substitution::from1(Interner, source_ty),
|
||||
)
|
||||
.intern(Interner),
|
||||
);
|
||||
let mut result: Place = self.temp(target_ty_ref)?.into();
|
||||
let Some(current) = self.lower_call(deref_fn_op, vec![Operand::Copy(ref_place)], result.clone(), current, false)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
result.projection.push(ProjectionElem::Deref);
|
||||
Ok(Some((result, current)))
|
||||
}
|
||||
}
|
348
crates/hir-ty/src/mir/pretty.rs
Normal file
348
crates/hir-ty/src/mir/pretty.rs
Normal file
|
@ -0,0 +1,348 @@
|
|||
//! A pretty-printer for MIR.
|
||||
|
||||
use std::fmt::{Display, Write};
|
||||
|
||||
use hir_def::{body::Body, expr::BindingId};
|
||||
use hir_expand::name::Name;
|
||||
use la_arena::ArenaMap;
|
||||
|
||||
use crate::{
|
||||
db::HirDatabase,
|
||||
display::HirDisplay,
|
||||
mir::{PlaceElem, ProjectionElem, StatementKind, Terminator},
|
||||
};
|
||||
|
||||
use super::{
|
||||
AggregateKind, BasicBlockId, BorrowKind, LocalId, MirBody, Operand, Place, Rvalue, UnOp,
|
||||
};
|
||||
|
||||
impl MirBody {
|
||||
pub fn pretty_print(&self, db: &dyn HirDatabase) -> String {
|
||||
let hir_body = db.body(self.owner);
|
||||
let mut ctx = MirPrettyCtx::new(self, &hir_body, db);
|
||||
ctx.for_body();
|
||||
ctx.result
|
||||
}
|
||||
}
|
||||
|
||||
struct MirPrettyCtx<'a> {
|
||||
body: &'a MirBody,
|
||||
hir_body: &'a Body,
|
||||
db: &'a dyn HirDatabase,
|
||||
result: String,
|
||||
ident: String,
|
||||
local_to_binding: ArenaMap<LocalId, BindingId>,
|
||||
}
|
||||
|
||||
macro_rules! w {
|
||||
($dst:expr, $($arg:tt)*) => {
|
||||
{ let _ = write!($dst, $($arg)*); }
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! wln {
|
||||
($dst:expr) => {
|
||||
{ let _ = writeln!($dst); }
|
||||
};
|
||||
($dst:expr, $($arg:tt)*) => {
|
||||
{ let _ = writeln!($dst, $($arg)*); }
|
||||
};
|
||||
}
|
||||
|
||||
impl Write for MirPrettyCtx<'_> {
|
||||
fn write_str(&mut self, s: &str) -> std::fmt::Result {
|
||||
let mut it = s.split('\n'); // note: `.lines()` is wrong here
|
||||
self.write(it.next().unwrap_or_default());
|
||||
for line in it {
|
||||
self.write_line();
|
||||
self.write(line);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
enum LocalName {
|
||||
Unknown(LocalId),
|
||||
Binding(Name, LocalId),
|
||||
}
|
||||
|
||||
impl Display for LocalName {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
LocalName::Unknown(l) => write!(f, "_{}", u32::from(l.into_raw())),
|
||||
LocalName::Binding(n, l) => write!(f, "{n}_{}", u32::from(l.into_raw())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MirPrettyCtx<'a> {
|
||||
fn for_body(&mut self) {
|
||||
self.with_block(|this| {
|
||||
this.locals();
|
||||
wln!(this);
|
||||
this.blocks();
|
||||
});
|
||||
}
|
||||
|
||||
fn with_block(&mut self, f: impl FnOnce(&mut MirPrettyCtx<'_>)) {
|
||||
self.ident += " ";
|
||||
wln!(self, "{{");
|
||||
f(self);
|
||||
for _ in 0..4 {
|
||||
self.result.pop();
|
||||
self.ident.pop();
|
||||
}
|
||||
wln!(self, "}}");
|
||||
}
|
||||
|
||||
fn new(body: &'a MirBody, hir_body: &'a Body, db: &'a dyn HirDatabase) -> Self {
|
||||
let local_to_binding = body.binding_locals.iter().map(|(x, y)| (*y, x)).collect();
|
||||
MirPrettyCtx {
|
||||
body,
|
||||
db,
|
||||
result: String::new(),
|
||||
ident: String::new(),
|
||||
local_to_binding,
|
||||
hir_body,
|
||||
}
|
||||
}
|
||||
|
||||
fn write_line(&mut self) {
|
||||
self.result.push('\n');
|
||||
self.result += &self.ident;
|
||||
}
|
||||
|
||||
fn write(&mut self, line: &str) {
|
||||
self.result += line;
|
||||
}
|
||||
|
||||
fn locals(&mut self) {
|
||||
for (id, local) in self.body.locals.iter() {
|
||||
wln!(self, "let {}: {};", self.local_name(id), local.ty.display(self.db));
|
||||
}
|
||||
}
|
||||
|
||||
fn local_name(&self, local: LocalId) -> LocalName {
|
||||
match self.local_to_binding.get(local) {
|
||||
Some(b) => LocalName::Binding(self.hir_body.bindings[*b].name.clone(), local),
|
||||
None => LocalName::Unknown(local),
|
||||
}
|
||||
}
|
||||
|
||||
fn basic_block_id(&self, basic_block_id: BasicBlockId) -> String {
|
||||
format!("'bb{}", u32::from(basic_block_id.into_raw()))
|
||||
}
|
||||
|
||||
fn blocks(&mut self) {
|
||||
for (id, block) in self.body.basic_blocks.iter() {
|
||||
wln!(self);
|
||||
w!(self, "{}: ", self.basic_block_id(id));
|
||||
self.with_block(|this| {
|
||||
for statement in &block.statements {
|
||||
match &statement.kind {
|
||||
StatementKind::Assign(l, r) => {
|
||||
this.place(l);
|
||||
w!(this, " = ");
|
||||
this.rvalue(r);
|
||||
wln!(this, ";");
|
||||
}
|
||||
StatementKind::StorageDead(p) => {
|
||||
wln!(this, "StorageDead({})", this.local_name(*p));
|
||||
}
|
||||
StatementKind::StorageLive(p) => {
|
||||
wln!(this, "StorageLive({})", this.local_name(*p));
|
||||
}
|
||||
StatementKind::Deinit(p) => {
|
||||
w!(this, "Deinit(");
|
||||
this.place(p);
|
||||
wln!(this, ");");
|
||||
}
|
||||
StatementKind::Nop => wln!(this, "Nop;"),
|
||||
}
|
||||
}
|
||||
match &block.terminator {
|
||||
Some(terminator) => match terminator {
|
||||
Terminator::Goto { target } => {
|
||||
wln!(this, "goto 'bb{};", u32::from(target.into_raw()))
|
||||
}
|
||||
Terminator::SwitchInt { discr, targets } => {
|
||||
w!(this, "switch ");
|
||||
this.operand(discr);
|
||||
w!(this, " ");
|
||||
this.with_block(|this| {
|
||||
for (c, b) in targets.iter() {
|
||||
wln!(this, "{c} => {},", this.basic_block_id(b));
|
||||
}
|
||||
wln!(this, "_ => {},", this.basic_block_id(targets.otherwise()));
|
||||
});
|
||||
}
|
||||
Terminator::Call { func, args, destination, target, .. } => {
|
||||
w!(this, "Call ");
|
||||
this.with_block(|this| {
|
||||
w!(this, "func: ");
|
||||
this.operand(func);
|
||||
wln!(this, ",");
|
||||
w!(this, "args: [");
|
||||
this.operand_list(args);
|
||||
wln!(this, "],");
|
||||
w!(this, "destination: ");
|
||||
this.place(destination);
|
||||
wln!(this, ",");
|
||||
w!(this, "target: ");
|
||||
match target {
|
||||
Some(t) => w!(this, "{}", this.basic_block_id(*t)),
|
||||
None => w!(this, "<unreachable>"),
|
||||
}
|
||||
wln!(this, ",");
|
||||
});
|
||||
}
|
||||
_ => wln!(this, "{:?};", terminator),
|
||||
},
|
||||
None => wln!(this, "<no-terminator>;"),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn place(&mut self, p: &Place) {
|
||||
fn f(this: &mut MirPrettyCtx<'_>, local: LocalId, projections: &[PlaceElem]) {
|
||||
let Some((last, head)) = projections.split_last() else {
|
||||
// no projection
|
||||
w!(this, "{}", this.local_name(local));
|
||||
return;
|
||||
};
|
||||
match last {
|
||||
ProjectionElem::Deref => {
|
||||
w!(this, "(*");
|
||||
f(this, local, head);
|
||||
w!(this, ")");
|
||||
}
|
||||
ProjectionElem::Field(field) => {
|
||||
let variant_data = field.parent.variant_data(this.db.upcast());
|
||||
let name = &variant_data.fields()[field.local_id].name;
|
||||
match field.parent {
|
||||
hir_def::VariantId::EnumVariantId(e) => {
|
||||
w!(this, "(");
|
||||
f(this, local, head);
|
||||
let variant_name =
|
||||
&this.db.enum_data(e.parent).variants[e.local_id].name;
|
||||
w!(this, " as {}).{}", variant_name, name);
|
||||
}
|
||||
hir_def::VariantId::StructId(_) | hir_def::VariantId::UnionId(_) => {
|
||||
f(this, local, head);
|
||||
w!(this, ".{name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
ProjectionElem::TupleField(x) => {
|
||||
f(this, local, head);
|
||||
w!(this, ".{}", x);
|
||||
}
|
||||
ProjectionElem::Index(l) => {
|
||||
f(this, local, head);
|
||||
w!(this, "[{}]", this.local_name(*l));
|
||||
}
|
||||
x => {
|
||||
f(this, local, head);
|
||||
w!(this, ".{:?}", x);
|
||||
}
|
||||
}
|
||||
}
|
||||
f(self, p.local, &p.projection);
|
||||
}
|
||||
|
||||
fn operand(&mut self, r: &Operand) {
|
||||
match r {
|
||||
Operand::Copy(p) | Operand::Move(p) => {
|
||||
// MIR at the time of writing doesn't have difference between move and copy, so we show them
|
||||
// equally. Feel free to change it.
|
||||
self.place(p);
|
||||
}
|
||||
Operand::Constant(c) => w!(self, "Const({})", c.display(self.db)),
|
||||
}
|
||||
}
|
||||
|
||||
fn rvalue(&mut self, r: &Rvalue) {
|
||||
match r {
|
||||
Rvalue::Use(op) => self.operand(op),
|
||||
Rvalue::Ref(r, p) => {
|
||||
match r {
|
||||
BorrowKind::Shared => w!(self, "&"),
|
||||
BorrowKind::Shallow => w!(self, "&shallow "),
|
||||
BorrowKind::Unique => w!(self, "&uniq "),
|
||||
BorrowKind::Mut { .. } => w!(self, "&mut "),
|
||||
}
|
||||
self.place(p);
|
||||
}
|
||||
Rvalue::Aggregate(AggregateKind::Tuple(_), x) => {
|
||||
w!(self, "(");
|
||||
self.operand_list(x);
|
||||
w!(self, ")");
|
||||
}
|
||||
Rvalue::Aggregate(AggregateKind::Array(_), x) => {
|
||||
w!(self, "[");
|
||||
self.operand_list(x);
|
||||
w!(self, "]");
|
||||
}
|
||||
Rvalue::Aggregate(AggregateKind::Adt(_, _), x) => {
|
||||
w!(self, "Adt(");
|
||||
self.operand_list(x);
|
||||
w!(self, ")");
|
||||
}
|
||||
Rvalue::Aggregate(AggregateKind::Union(_, _), x) => {
|
||||
w!(self, "Union(");
|
||||
self.operand_list(x);
|
||||
w!(self, ")");
|
||||
}
|
||||
Rvalue::Len(p) => {
|
||||
w!(self, "Len(");
|
||||
self.place(p);
|
||||
w!(self, ")");
|
||||
}
|
||||
Rvalue::Cast(ck, op, ty) => {
|
||||
w!(self, "Discriminant({ck:?}");
|
||||
self.operand(op);
|
||||
w!(self, "{})", ty.display(self.db));
|
||||
}
|
||||
Rvalue::CheckedBinaryOp(b, o1, o2) => {
|
||||
self.operand(o1);
|
||||
w!(self, " {b} ");
|
||||
self.operand(o2);
|
||||
}
|
||||
Rvalue::UnaryOp(u, o) => {
|
||||
let u = match u {
|
||||
UnOp::Not => "!",
|
||||
UnOp::Neg => "-",
|
||||
};
|
||||
w!(self, "{u} ");
|
||||
self.operand(o);
|
||||
}
|
||||
Rvalue::Discriminant(p) => {
|
||||
w!(self, "Discriminant(");
|
||||
self.place(p);
|
||||
w!(self, ")");
|
||||
}
|
||||
Rvalue::ShallowInitBox(op, _) => {
|
||||
w!(self, "ShallowInitBox(");
|
||||
self.operand(op);
|
||||
w!(self, ")");
|
||||
}
|
||||
Rvalue::CopyForDeref(p) => {
|
||||
w!(self, "CopyForDeref(");
|
||||
self.place(p);
|
||||
w!(self, ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn operand_list(&mut self, x: &[Operand]) {
|
||||
let mut it = x.iter();
|
||||
if let Some(first) = it.next() {
|
||||
self.operand(first);
|
||||
for op in it {
|
||||
w!(self, ", ");
|
||||
self.operand(op);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -61,22 +61,27 @@ fn setup_tracing() -> Option<tracing::subscriber::DefaultGuard> {
|
|||
Some(tracing::subscriber::set_default(subscriber))
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn check_types(ra_fixture: &str) {
|
||||
check_impl(ra_fixture, false, true, false)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn check_types_source_code(ra_fixture: &str) {
|
||||
check_impl(ra_fixture, false, true, true)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn check_no_mismatches(ra_fixture: &str) {
|
||||
check_impl(ra_fixture, true, false, false)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn check(ra_fixture: &str) {
|
||||
check_impl(ra_fixture, false, false, false)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_source: bool) {
|
||||
let _tracing = setup_tracing();
|
||||
let (db, files) = TestDB::with_many_files(ra_fixture);
|
||||
|
@ -158,7 +163,7 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour
|
|||
} else {
|
||||
ty.display_test(&db).to_string()
|
||||
};
|
||||
assert_eq!(actual, expected);
|
||||
assert_eq!(actual, expected, "type annotation differs at {:#?}", range.range);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -174,7 +179,7 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour
|
|||
} else {
|
||||
ty.display_test(&db).to_string()
|
||||
};
|
||||
assert_eq!(actual, expected);
|
||||
assert_eq!(actual, expected, "type annotation differs at {:#?}", range.range);
|
||||
}
|
||||
if let Some(expected) = adjustments.remove(&range) {
|
||||
let adjustments = inference_result
|
||||
|
@ -191,30 +196,11 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour
|
|||
}
|
||||
}
|
||||
|
||||
for (pat, mismatch) in inference_result.pat_type_mismatches() {
|
||||
let node = match pat_node(&body_source_map, pat, &db) {
|
||||
Some(value) => value,
|
||||
None => continue,
|
||||
};
|
||||
let range = node.as_ref().original_file_range(&db);
|
||||
let actual = format!(
|
||||
"expected {}, got {}",
|
||||
mismatch.expected.display_test(&db),
|
||||
mismatch.actual.display_test(&db)
|
||||
);
|
||||
match mismatches.remove(&range) {
|
||||
Some(annotation) => assert_eq!(actual, annotation),
|
||||
None => format_to!(unexpected_type_mismatches, "{:?}: {}\n", range.range, actual),
|
||||
}
|
||||
}
|
||||
for (expr, mismatch) in inference_result.expr_type_mismatches() {
|
||||
let node = match body_source_map.expr_syntax(expr) {
|
||||
Ok(sp) => {
|
||||
let root = db.parse_or_expand(sp.file_id).unwrap();
|
||||
sp.map(|ptr| ptr.to_node(&root).syntax().clone())
|
||||
}
|
||||
Err(SyntheticSyntax) => continue,
|
||||
};
|
||||
for (expr_or_pat, mismatch) in inference_result.type_mismatches() {
|
||||
let Some(node) = (match expr_or_pat {
|
||||
hir_def::expr::ExprOrPatId::ExprId(expr) => expr_node(&body_source_map, expr, &db),
|
||||
hir_def::expr::ExprOrPatId::PatId(pat) => pat_node(&body_source_map, pat, &db),
|
||||
}) else { continue; };
|
||||
let range = node.as_ref().original_file_range(&db);
|
||||
let actual = format!(
|
||||
"expected {}, got {}",
|
||||
|
|
|
@ -258,6 +258,7 @@ fn test() {
|
|||
|
||||
#[test]
|
||||
fn coerce_autoderef_block() {
|
||||
// FIXME: We should know mutability in overloaded deref
|
||||
check_no_mismatches(
|
||||
r#"
|
||||
//- minicore: deref
|
||||
|
@ -267,7 +268,7 @@ fn takes_ref_str(x: &str) {}
|
|||
fn returns_string() -> String { loop {} }
|
||||
fn test() {
|
||||
takes_ref_str(&{ returns_string() });
|
||||
// ^^^^^^^^^^^^^^^^^^^^^ adjustments: Deref(None), Deref(Some(OverloadedDeref(Not))), Borrow(Ref(Not))
|
||||
// ^^^^^^^^^^^^^^^^^^^^^ adjustments: Deref(None), Deref(Some(OverloadedDeref(None))), Borrow(Ref(Not))
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
|
|
@ -73,3 +73,24 @@ fn test(x: bool) -> &'static str {
|
|||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_unit_block_expr_stmt_no_semi() {
|
||||
check(
|
||||
r#"
|
||||
fn test(x: bool) {
|
||||
if x {
|
||||
"notok"
|
||||
//^^^^^^^ expected (), got &str
|
||||
} else {
|
||||
"ok"
|
||||
//^^^^ expected (), got &str
|
||||
}
|
||||
match x { true => true, false => 0 }
|
||||
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected (), got bool
|
||||
//^ expected bool, got i32
|
||||
()
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1167,7 +1167,6 @@ fn test() {
|
|||
123..167 '{ ...o(); }': ()
|
||||
133..134 's': &S
|
||||
137..151 'unsafe { f() }': &S
|
||||
137..151 'unsafe { f() }': &S
|
||||
146..147 'f': fn f() -> &S
|
||||
146..149 'f()': &S
|
||||
157..158 's': &S
|
||||
|
@ -1253,6 +1252,7 @@ fn foo<T: Trait>(a: &T) {
|
|||
|
||||
#[test]
|
||||
fn autoderef_visibility_field() {
|
||||
// FIXME: We should know mutability in overloaded deref
|
||||
check(
|
||||
r#"
|
||||
//- minicore: deref
|
||||
|
@ -1274,7 +1274,7 @@ mod a {
|
|||
mod b {
|
||||
fn foo() {
|
||||
let x = super::a::Bar::new().0;
|
||||
// ^^^^^^^^^^^^^^^^^^^^ adjustments: Deref(Some(OverloadedDeref(Not)))
|
||||
// ^^^^^^^^^^^^^^^^^^^^ adjustments: Deref(Some(OverloadedDeref(None)))
|
||||
// ^^^^^^^^^^^^^^^^^^^^^^ type: char
|
||||
}
|
||||
}
|
||||
|
|
|
@ -476,7 +476,7 @@ fn infer_adt_pattern() {
|
|||
183..184 'x': usize
|
||||
190..191 'x': usize
|
||||
201..205 'E::B': E
|
||||
209..212 'foo': bool
|
||||
209..212 'foo': {unknown}
|
||||
216..217 '1': usize
|
||||
227..231 'E::B': E
|
||||
235..237 '10': usize
|
||||
|
@ -953,9 +953,9 @@ fn main() {
|
|||
42..51 'true | ()': bool
|
||||
49..51 '()': ()
|
||||
57..59 '{}': ()
|
||||
68..80 '(() | true,)': ((),)
|
||||
68..80 '(() | true,)': (bool,)
|
||||
69..71 '()': ()
|
||||
69..78 '() | true': ()
|
||||
69..78 '() | true': bool
|
||||
74..78 'true': bool
|
||||
74..78 'true': bool
|
||||
84..86 '{}': ()
|
||||
|
@ -964,19 +964,15 @@ fn main() {
|
|||
96..102 '_ | ()': bool
|
||||
100..102 '()': ()
|
||||
108..110 '{}': ()
|
||||
119..128 '(() | _,)': ((),)
|
||||
119..128 '(() | _,)': (bool,)
|
||||
120..122 '()': ()
|
||||
120..126 '() | _': ()
|
||||
120..126 '() | _': bool
|
||||
125..126 '_': bool
|
||||
132..134 '{}': ()
|
||||
49..51: expected bool, got ()
|
||||
68..80: expected (bool,), got ((),)
|
||||
69..71: expected bool, got ()
|
||||
69..78: expected bool, got ()
|
||||
100..102: expected bool, got ()
|
||||
119..128: expected (bool,), got ((),)
|
||||
120..122: expected bool, got ()
|
||||
120..126: expected bool, got ()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
@ -1092,3 +1088,19 @@ fn my_fn(foo: ...) {}
|
|||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ref_pat_mutability() {
|
||||
check(
|
||||
r#"
|
||||
fn foo() {
|
||||
let &() = &();
|
||||
let &mut () = &mut ();
|
||||
let &mut () = &();
|
||||
//^^^^^^^ expected &(), got &mut ()
|
||||
let &() = &mut ();
|
||||
//^^^ expected &mut (), got &()
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -270,7 +270,7 @@ fn infer_std_crash_5() {
|
|||
61..320 '{ ... }': ()
|
||||
75..79 'name': &{unknown}
|
||||
82..166 'if doe... }': &{unknown}
|
||||
85..98 'doesnt_matter': bool
|
||||
85..98 'doesnt_matter': {unknown}
|
||||
99..128 '{ ... }': &{unknown}
|
||||
113..118 'first': &{unknown}
|
||||
134..166 '{ ... }': &{unknown}
|
||||
|
@ -279,7 +279,7 @@ fn infer_std_crash_5() {
|
|||
181..188 'content': &{unknown}
|
||||
191..313 'if ICE... }': &{unknown}
|
||||
194..231 'ICE_RE..._VALUE': {unknown}
|
||||
194..247 'ICE_RE...&name)': bool
|
||||
194..247 'ICE_RE...&name)': {unknown}
|
||||
241..246 '&name': &&{unknown}
|
||||
242..246 'name': &{unknown}
|
||||
248..276 '{ ... }': &{unknown}
|
||||
|
@ -1015,9 +1015,9 @@ fn cfg_tail() {
|
|||
20..31 '{ "first" }': ()
|
||||
22..29 '"first"': &str
|
||||
72..190 '{ ...] 13 }': ()
|
||||
78..88 '{ "fake" }': &str
|
||||
78..88 '{ "fake" }': ()
|
||||
80..86 '"fake"': &str
|
||||
93..103 '{ "fake" }': &str
|
||||
93..103 '{ "fake" }': ()
|
||||
95..101 '"fake"': &str
|
||||
108..120 '{ "second" }': ()
|
||||
110..118 '"second"': &str
|
||||
|
@ -1744,3 +1744,15 @@ fn foo(b: Bar) {
|
|||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_14305() {
|
||||
check_no_mismatches(
|
||||
r#"
|
||||
//- minicore: add
|
||||
trait Tr {}
|
||||
impl Tr for [u8; C] {}
|
||||
const C: usize = 2 + 2;
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -352,7 +352,6 @@ unsafe fn baz(u: MyUnion) {
|
|||
71..89 'MyUnio...o: 0 }': MyUnion
|
||||
86..87 '0': u32
|
||||
95..113 'unsafe...(u); }': ()
|
||||
95..113 'unsafe...(u); }': ()
|
||||
104..107 'baz': fn baz(MyUnion)
|
||||
104..110 'baz(u)': ()
|
||||
108..109 'u': MyUnion
|
||||
|
@ -360,7 +359,6 @@ unsafe fn baz(u: MyUnion) {
|
|||
126..146 'MyUnio... 0.0 }': MyUnion
|
||||
141..144 '0.0': f32
|
||||
152..170 'unsafe...(u); }': ()
|
||||
152..170 'unsafe...(u); }': ()
|
||||
161..164 'baz': fn baz(MyUnion)
|
||||
161..167 'baz(u)': ()
|
||||
165..166 'u': MyUnion
|
||||
|
@ -2077,22 +2075,17 @@ async fn main() {
|
|||
16..193 '{ ...2 }; }': ()
|
||||
26..27 'x': i32
|
||||
30..43 'unsafe { 92 }': i32
|
||||
30..43 'unsafe { 92 }': i32
|
||||
39..41 '92': i32
|
||||
53..54 'y': impl Future<Output = ()>
|
||||
57..85 'async ...wait }': ()
|
||||
57..85 'async ...wait }': impl Future<Output = ()>
|
||||
65..77 'async { () }': ()
|
||||
65..77 'async { () }': impl Future<Output = ()>
|
||||
65..83 'async ....await': ()
|
||||
73..75 '()': ()
|
||||
95..96 'z': ControlFlow<(), ()>
|
||||
130..140 'try { () }': ()
|
||||
130..140 'try { () }': ControlFlow<(), ()>
|
||||
136..138 '()': ()
|
||||
150..151 'w': i32
|
||||
154..166 'const { 92 }': i32
|
||||
154..166 'const { 92 }': i32
|
||||
162..164 '92': i32
|
||||
176..177 't': i32
|
||||
180..190 ''a: { 92 }': i32
|
||||
|
@ -2122,7 +2115,6 @@ fn main() {
|
|||
83..84 'f': F
|
||||
89..91 '{}': ()
|
||||
103..231 '{ ... }); }': ()
|
||||
109..161 'async ... }': Result<(), ()>
|
||||
109..161 'async ... }': impl Future<Output = Result<(), ()>>
|
||||
125..139 'return Err(())': !
|
||||
132..135 'Err': Err<(), ()>(()) -> Result<(), ()>
|
||||
|
@ -2134,7 +2126,6 @@ fn main() {
|
|||
167..171 'test': fn test<(), (), || -> impl Future<Output = Result<(), ()>>, impl Future<Output = Result<(), ()>>>(|| -> impl Future<Output = Result<(), ()>>)
|
||||
167..228 'test(|... })': ()
|
||||
172..227 '|| asy... }': || -> impl Future<Output = Result<(), ()>>
|
||||
175..227 'async ... }': Result<(), ()>
|
||||
175..227 'async ... }': impl Future<Output = Result<(), ()>>
|
||||
191..205 'return Err(())': !
|
||||
198..201 'Err': Err<(), ()>(()) -> Result<(), ()>
|
||||
|
@ -3283,3 +3274,18 @@ fn func() {
|
|||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issue_14275() {
|
||||
// FIXME: evaluate const generic
|
||||
check_types(
|
||||
r#"
|
||||
struct Foo<const T: bool>;
|
||||
fn main() {
|
||||
const B: bool = false;
|
||||
let foo = Foo::<B>;
|
||||
//^^^ Foo<_>
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ use std::iter;
|
|||
|
||||
use base_db::CrateId;
|
||||
use chalk_ir::{cast::Cast, fold::Shift, BoundVar, DebruijnIndex};
|
||||
use either::Either;
|
||||
use hir_def::{
|
||||
db::DefDatabase,
|
||||
generics::{
|
||||
|
@ -19,7 +20,6 @@ use hir_def::{
|
|||
};
|
||||
use hir_expand::name::Name;
|
||||
use intern::Interned;
|
||||
use itertools::Either;
|
||||
use rustc_hash::FxHashSet;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
|
||||
|
@ -315,7 +315,10 @@ fn parent_generic_def(db: &dyn DefDatabase, def: GenericDefId) -> Option<Generic
|
|||
GenericDefId::TypeAliasId(it) => it.lookup(db).container,
|
||||
GenericDefId::ConstId(it) => it.lookup(db).container,
|
||||
GenericDefId::EnumVariantId(it) => return Some(it.parent.into()),
|
||||
GenericDefId::AdtId(_) | GenericDefId::TraitId(_) | GenericDefId::ImplId(_) => return None,
|
||||
GenericDefId::AdtId(_)
|
||||
| GenericDefId::TraitId(_)
|
||||
| GenericDefId::ImplId(_)
|
||||
| GenericDefId::TraitAliasId(_) => return None,
|
||||
};
|
||||
|
||||
match container {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue