Auto merge of #14040 - HKalbasi:mir, r=HKalbasi

Beginning of MIR

This pull request introduces the initial implementation of MIR lowering and interpreting in Rust Analyzer.

The implementation of MIR has potential to bring several benefits:
- Executing a unit test without compiling it: This is my main goal. It can be useful for quickly testing code changes and print-debugging unit tests without the need for a full compilation (ideally in almost zero time, similar to languages like python and js). There is a probability that it goes nowhere, it might become slower than rustc, or it might need some unreasonable amount of memory, or we may fail to support a common pattern/function that make it unusable for most of the codes.
- Constant evaluation: MIR allows for easier and more correct constant evaluation, on par with rustc. If r-a wants to fully support the type system, it needs full const eval, which means arbitrary code execution, which needs MIR or something similar.
- Supporting more diagnostics: MIR can be used to detect errors, most famously borrow checker and lifetime errors,  but also mutability errors and uninitialized variables, which can be difficult/impossible to detect in HIR.
- Lowering closures: With MIR we can find out closure capture modes, which is useful in detecting if a closure implements the `FnMut` or `Fn` traits, and calculating its size and data layout.

But the current PR implements no diagnostics and doesn't support closures. About const eval, I removed the old const eval code and it now uses the mir interpreter. Everything that is supported in stable rustc is either implemented or is super easy to implement. About interpreting unit tests, I added an experimental config, disabled by default, that shows a `pass` or `fail` on hover of unit tests (ideally it should be a button similar to `Run test` button, but I didn't figured out how to add them). Currently, no real world test works, due to missing features including closures, heap allocation, `dyn Trait` and ... so at this point it is only useful for me selecting what to implement next.

The implementation of MIR is based on the design of rustc, the data structures are almost copy paste (so it should be easy to migrate it to a possible future stable-mir), but the lowering and interpreting code is from me.
This commit is contained in:
bors 2023-02-28 09:12:19 +00:00
commit a0be16b0b2
41 changed files with 4452 additions and 702 deletions

View file

@ -52,13 +52,21 @@ pub type LabelId = Idx<Label>;
// We convert float values into bits and that's how we don't need to deal with f32 and f64. // We convert float values into bits and that's how we don't need to deal with f32 and f64.
// For PartialEq, bits comparison should work, as ordering is not important // For PartialEq, bits comparison should work, as ordering is not important
// https://github.com/rust-lang/rust-analyzer/issues/12380#issuecomment-1137284360 // https://github.com/rust-lang/rust-analyzer/issues/12380#issuecomment-1137284360
#[derive(Default, Debug, Clone, Eq, PartialEq)] #[derive(Default, Debug, Clone, Copy, Eq, PartialEq)]
pub struct FloatTypeWrapper(u64); pub struct FloatTypeWrapper(u64);
impl FloatTypeWrapper { impl FloatTypeWrapper {
pub fn new(value: f64) -> Self { pub fn new(value: f64) -> Self {
Self(value.to_bits()) Self(value.to_bits())
} }
pub fn into_f64(self) -> f64 {
f64::from_bits(self.0)
}
pub fn into_f32(self) -> f32 {
f64::from_bits(self.0) as f32
}
} }
impl fmt::Display for FloatTypeWrapper { impl fmt::Display for FloatTypeWrapper {

View file

@ -181,15 +181,15 @@ impl LangItems {
T: Into<AttrDefId> + Copy, T: Into<AttrDefId> + Copy,
{ {
let _p = profile::span("collect_lang_item"); let _p = profile::span("collect_lang_item");
if let Some(lang_item) = lang_attr(db, item).and_then(|it| LangItem::from_str(&it)) { if let Some(lang_item) = lang_attr(db, item) {
self.items.entry(lang_item).or_insert_with(|| constructor(item)); self.items.entry(lang_item).or_insert_with(|| constructor(item));
} }
} }
} }
pub fn lang_attr(db: &dyn DefDatabase, item: impl Into<AttrDefId> + Copy) -> Option<SmolStr> { pub fn lang_attr(db: &dyn DefDatabase, item: impl Into<AttrDefId> + Copy) -> Option<LangItem> {
let attrs = db.attrs(item.into()); let attrs = db.attrs(item.into());
attrs.by_key("lang").string_value().cloned() attrs.by_key("lang").string_value().cloned().and_then(|it| LangItem::from_str(&it))
} }
pub enum GenericRequirement { pub enum GenericRequirement {

View file

@ -143,7 +143,7 @@ macro_rules! assert {
fn main() { fn main() {
{ {
if !true { if !(true ) {
$crate::panic!("{} {:?}", arg1(a, b, c), arg2); $crate::panic!("{} {:?}", arg1(a, b, c), arg2);
} }
}; };

View file

@ -8,7 +8,7 @@ use std::{
use crate::{ use crate::{
body::LowerCtx, body::LowerCtx,
type_ref::{ConstScalarOrPath, LifetimeRef}, type_ref::{ConstRefOrPath, LifetimeRef},
}; };
use hir_expand::name::Name; use hir_expand::name::Name;
use intern::Interned; use intern::Interned;
@ -85,7 +85,7 @@ pub struct AssociatedTypeBinding {
pub enum GenericArg { pub enum GenericArg {
Type(TypeRef), Type(TypeRef),
Lifetime(LifetimeRef), Lifetime(LifetimeRef),
Const(ConstScalarOrPath), Const(ConstRefOrPath),
} }
impl Path { impl Path {

View file

@ -2,7 +2,7 @@
use std::iter; use std::iter;
use crate::type_ref::ConstScalarOrPath; use crate::type_ref::ConstRefOrPath;
use either::Either; use either::Either;
use hir_expand::name::{name, AsName}; use hir_expand::name::{name, AsName};
@ -212,7 +212,7 @@ pub(super) fn lower_generic_args(
} }
} }
ast::GenericArg::ConstArg(arg) => { ast::GenericArg::ConstArg(arg) => {
let arg = ConstScalarOrPath::from_expr_opt(arg.expr()); let arg = ConstRefOrPath::from_expr_opt(arg.expr());
args.push(GenericArg::Const(arg)) args.push(GenericArg::Const(arg))
} }
} }

View file

@ -116,7 +116,7 @@ pub enum TypeRef {
Reference(Box<TypeRef>, Option<LifetimeRef>, Mutability), Reference(Box<TypeRef>, Option<LifetimeRef>, Mutability),
// FIXME: for full const generics, the latter element (length) here is going to have to be an // FIXME: for full const generics, the latter element (length) here is going to have to be an
// expression that is further lowered later in hir_ty. // expression that is further lowered later in hir_ty.
Array(Box<TypeRef>, ConstScalarOrPath), Array(Box<TypeRef>, ConstRefOrPath),
Slice(Box<TypeRef>), Slice(Box<TypeRef>),
/// A fn pointer. Last element of the vector is the return type. /// A fn pointer. Last element of the vector is the return type.
Fn(Vec<(Option<Name>, TypeRef)>, bool /*varargs*/, bool /*is_unsafe*/), Fn(Vec<(Option<Name>, TypeRef)>, bool /*varargs*/, bool /*is_unsafe*/),
@ -188,7 +188,7 @@ impl TypeRef {
// `hir_def::body::lower` to lower this into an `Expr` and then evaluate it at the // `hir_def::body::lower` to lower this into an `Expr` and then evaluate it at the
// `hir_ty` level, which would allow knowing the type of: // `hir_ty` level, which would allow knowing the type of:
// let v: [u8; 2 + 2] = [0u8; 4]; // let v: [u8; 2 + 2] = [0u8; 4];
let len = ConstScalarOrPath::from_expr_opt(inner.expr()); let len = ConstRefOrPath::from_expr_opt(inner.expr());
TypeRef::Array(Box::new(TypeRef::from_ast_opt(ctx, inner.ty())), len) TypeRef::Array(Box::new(TypeRef::from_ast_opt(ctx, inner.ty())), len)
} }
ast::Type::SliceType(inner) => { ast::Type::SliceType(inner) => {
@ -378,25 +378,25 @@ impl TypeBound {
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ConstScalarOrPath { pub enum ConstRefOrPath {
Scalar(ConstScalar), Scalar(ConstRef),
Path(Name), Path(Name),
} }
impl std::fmt::Display for ConstScalarOrPath { impl std::fmt::Display for ConstRefOrPath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
ConstScalarOrPath::Scalar(s) => s.fmt(f), ConstRefOrPath::Scalar(s) => s.fmt(f),
ConstScalarOrPath::Path(n) => n.fmt(f), ConstRefOrPath::Path(n) => n.fmt(f),
} }
} }
} }
impl ConstScalarOrPath { impl ConstRefOrPath {
pub(crate) fn from_expr_opt(expr: Option<ast::Expr>) -> Self { pub(crate) fn from_expr_opt(expr: Option<ast::Expr>) -> Self {
match expr { match expr {
Some(x) => Self::from_expr(x), Some(x) => Self::from_expr(x),
None => Self::Scalar(ConstScalar::Unknown), None => Self::Scalar(ConstRef::Unknown),
} }
} }
@ -407,7 +407,7 @@ impl ConstScalarOrPath {
ast::Expr::PathExpr(p) => { ast::Expr::PathExpr(p) => {
match p.path().and_then(|x| x.segment()).and_then(|x| x.name_ref()) { match p.path().and_then(|x| x.segment()).and_then(|x| x.name_ref()) {
Some(x) => Self::Path(x.as_name()), Some(x) => Self::Path(x.as_name()),
None => Self::Scalar(ConstScalar::Unknown), None => Self::Scalar(ConstRef::Unknown),
} }
} }
ast::Expr::PrefixExpr(prefix_expr) => match prefix_expr.op_kind() { ast::Expr::PrefixExpr(prefix_expr) => match prefix_expr.op_kind() {
@ -415,8 +415,8 @@ impl ConstScalarOrPath {
let unsigned = Self::from_expr_opt(prefix_expr.expr()); let unsigned = Self::from_expr_opt(prefix_expr.expr());
// Add sign // Add sign
match unsigned { match unsigned {
Self::Scalar(ConstScalar::UInt(num)) => { Self::Scalar(ConstRef::UInt(num)) => {
Self::Scalar(ConstScalar::Int(-(num as i128))) Self::Scalar(ConstRef::Int(-(num as i128)))
} }
other => other, other => other,
} }
@ -425,22 +425,22 @@ impl ConstScalarOrPath {
}, },
ast::Expr::Literal(literal) => Self::Scalar(match literal.kind() { ast::Expr::Literal(literal) => Self::Scalar(match literal.kind() {
ast::LiteralKind::IntNumber(num) => { ast::LiteralKind::IntNumber(num) => {
num.value().map(ConstScalar::UInt).unwrap_or(ConstScalar::Unknown) num.value().map(ConstRef::UInt).unwrap_or(ConstRef::Unknown)
} }
ast::LiteralKind::Char(c) => { ast::LiteralKind::Char(c) => {
c.value().map(ConstScalar::Char).unwrap_or(ConstScalar::Unknown) c.value().map(ConstRef::Char).unwrap_or(ConstRef::Unknown)
} }
ast::LiteralKind::Bool(f) => ConstScalar::Bool(f), ast::LiteralKind::Bool(f) => ConstRef::Bool(f),
_ => ConstScalar::Unknown, _ => ConstRef::Unknown,
}), }),
_ => Self::Scalar(ConstScalar::Unknown), _ => Self::Scalar(ConstRef::Unknown),
} }
} }
} }
/// A concrete constant value /// A concrete constant value
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ConstScalar { pub enum ConstRef {
Int(i128), Int(i128),
UInt(u128), UInt(u128),
Bool(bool), Bool(bool),
@ -454,18 +454,18 @@ pub enum ConstScalar {
Unknown, Unknown,
} }
impl ConstScalar { impl ConstRef {
pub fn builtin_type(&self) -> BuiltinType { pub fn builtin_type(&self) -> BuiltinType {
match self { match self {
ConstScalar::UInt(_) | ConstScalar::Unknown => BuiltinType::Uint(BuiltinUint::U128), ConstRef::UInt(_) | ConstRef::Unknown => BuiltinType::Uint(BuiltinUint::U128),
ConstScalar::Int(_) => BuiltinType::Int(BuiltinInt::I128), ConstRef::Int(_) => BuiltinType::Int(BuiltinInt::I128),
ConstScalar::Char(_) => BuiltinType::Char, ConstRef::Char(_) => BuiltinType::Char,
ConstScalar::Bool(_) => BuiltinType::Bool, ConstRef::Bool(_) => BuiltinType::Bool,
} }
} }
} }
impl From<Literal> for ConstScalar { impl From<Literal> for ConstRef {
fn from(literal: Literal) -> Self { fn from(literal: Literal) -> Self {
match literal { match literal {
Literal::Char(c) => Self::Char(c), Literal::Char(c) => Self::Char(c),
@ -477,14 +477,14 @@ impl From<Literal> for ConstScalar {
} }
} }
impl std::fmt::Display for ConstScalar { impl std::fmt::Display for ConstRef {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
match self { match self {
ConstScalar::Int(num) => num.fmt(f), ConstRef::Int(num) => num.fmt(f),
ConstScalar::UInt(num) => num.fmt(f), ConstRef::UInt(num) => num.fmt(f),
ConstScalar::Bool(flag) => flag.fmt(f), ConstRef::Bool(flag) => flag.fmt(f),
ConstScalar::Char(c) => write!(f, "'{c}'"), ConstRef::Char(c) => write!(f, "'{c}'"),
ConstScalar::Unknown => f.write_char('_'), ConstRef::Unknown => f.write_char('_'),
} }
} }
} }

View file

@ -206,7 +206,7 @@ fn assert_expand(
let cond = cond.clone(); let cond = cond.clone();
let panic_args = itertools::Itertools::intersperse(panic_args.iter().cloned(), comma); let panic_args = itertools::Itertools::intersperse(panic_args.iter().cloned(), comma);
quote! {{ quote! {{
if !#cond { if !(#cond) {
#DOLLAR_CRATE::panic!(##panic_args); #DOLLAR_CRATE::panic!(##panic_args);
} }
}} }}

View file

@ -152,6 +152,15 @@ impl TyBuilder<()> {
TyKind::Tuple(0, Substitution::empty(Interner)).intern(Interner) 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 { pub fn usize() -> Ty {
TyKind::Scalar(chalk_ir::Scalar::Uint(chalk_ir::UintTy::Usize)).intern(Interner) TyKind::Scalar(chalk_ir::Scalar::Uint(chalk_ir::UintTy::Usize)).intern(Interner)
} }

View file

@ -540,8 +540,7 @@ pub(crate) fn trait_datum_query(
let where_clauses = convert_where_clauses(db, trait_.into(), &bound_vars); 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 associated_ty_ids = trait_data.associated_types().map(to_assoc_type_id).collect();
let trait_datum_bound = rust_ir::TraitDatumBound { where_clauses }; let trait_datum_bound = rust_ir::TraitDatumBound { where_clauses };
let well_known = lang_attr(db.upcast(), trait_) let well_known = lang_attr(db.upcast(), trait_).and_then(well_known_trait_from_lang_item);
.and_then(|name| well_known_trait_from_lang_item(LangItem::from_str(&name)?));
let trait_datum = TraitDatum { let trait_datum = TraitDatum {
id: trait_id, id: trait_id,
binders: make_binders(db, &generic_params, trait_datum_bound), binders: make_binders(db, &generic_params, trait_datum_bound),

View file

@ -1,30 +1,25 @@
//! Constant evaluation details //! Constant evaluation details
use std::{ use base_db::CrateId;
collections::HashMap, use chalk_ir::{BoundVar, DebruijnIndex, GenericArgData};
fmt::{Display, Write},
};
use chalk_ir::{BoundVar, DebruijnIndex, GenericArgData, IntTy, Scalar};
use hir_def::{ use hir_def::{
builtin_type::BuiltinInt, expr::Expr,
expr::{ArithOp, BinaryOp, Expr, ExprId, Literal, Pat, PatId},
path::ModPath, path::ModPath,
resolver::{resolver_for_expr, ResolveValueResult, Resolver, ValueNs}, resolver::{Resolver, ValueNs},
src::HasChildSource, type_ref::ConstRef,
type_ref::ConstScalar, ConstId, EnumVariantId,
ConstId, DefWithBodyId, EnumVariantId, Lookup,
}; };
use la_arena::{Arena, Idx, RawIdx}; use la_arena::{Idx, RawIdx};
use stdx::never; use stdx::never;
use syntax::ast::HasName;
use crate::{ use crate::{
db::HirDatabase, infer::InferenceContext, lower::ParamLoweringMode, to_placeholder_idx, db::HirDatabase, infer::InferenceContext, layout::layout_of_ty, lower::ParamLoweringMode,
utils::Generics, Const, ConstData, ConstValue, GenericArg, InferenceResult, Interner, Ty, to_placeholder_idx, utils::Generics, Const, ConstData, ConstScalar, ConstValue, GenericArg,
TyBuilder, TyKind, Interner, MemoryMap, Ty, TyBuilder,
}; };
use super::mir::{interpret_mir, lower_to_mir, pad16, MirEvalError, MirLowerError};
/// Extension trait for [`Const`] /// Extension trait for [`Const`]
pub trait ConstExt { pub trait ConstExt {
/// Is a [`Const`] unknown? /// 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)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConstEvalError { pub enum ConstEvalError {
NotSupported(&'static str), MirLowerError(MirLowerError),
SemanticError(&'static str), MirEvalError(MirEvalError),
Loop,
IncompleteExpr,
Panic(String),
} }
#[derive(Debug, Clone, PartialEq, Eq)] impl From<MirLowerError> for ConstEvalError {
pub enum ComputedExpr { fn from(value: MirLowerError) -> Self {
Literal(Literal), match value {
Enum(String, EnumVariantId, Literal), MirLowerError::ConstEvalError(e) => *e,
Tuple(Box<[ComputedExpr]>), _ => ConstEvalError::MirLowerError(value),
}
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(')')
}
} }
} }
} }
fn scalar_max(scalar: &Scalar) -> i128 { impl From<MirEvalError> for ConstEvalError {
match scalar { fn from(value: MirEvalError) -> Self {
Scalar::Bool => 1, ConstEvalError::MirEvalError(value)
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")),
} }
} }
@ -449,68 +122,102 @@ pub fn intern_const_scalar(value: ConstScalar, ty: Ty) -> Const {
.intern(Interner) .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 /// Interns a possibly-unknown target usize
pub fn usize_const(value: Option<u128>) -> Const { pub fn usize_const(db: &dyn HirDatabase, value: Option<u128>, krate: CrateId) -> Const {
intern_const_scalar(value.map_or(ConstScalar::Unknown, ConstScalar::UInt), TyBuilder::usize()) 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( pub(crate) fn const_eval_recover(
_: &dyn HirDatabase, _: &dyn HirDatabase,
_: &[String], _: &[String],
_: &ConstId, _: &ConstId,
) -> Result<ComputedExpr, ConstEvalError> { ) -> Result<Const, ConstEvalError> {
Err(ConstEvalError::Loop) Err(ConstEvalError::MirLowerError(MirLowerError::Loop))
} }
pub(crate) fn const_eval_variant_recover( pub(crate) fn const_eval_discriminant_recover(
_: &dyn HirDatabase, _: &dyn HirDatabase,
_: &[String], _: &[String],
_: &EnumVariantId, _: &EnumVariantId,
) -> Result<ComputedExpr, ConstEvalError> { ) -> Result<i128, ConstEvalError> {
Err(ConstEvalError::Loop) Err(ConstEvalError::MirLowerError(MirLowerError::Loop))
} }
pub(crate) fn const_eval_variant_query( pub(crate) fn const_eval_query(
db: &dyn HirDatabase, db: &dyn HirDatabase,
const_id: ConstId, const_id: ConstId,
) -> Result<ComputedExpr, ConstEvalError> { ) -> Result<Const, ConstEvalError> {
let def = const_id.into(); let def = const_id.into();
let body = db.body(def); let body = db.mir_body(def)?;
let infer = &db.infer(def); let c = interpret_mir(db, &body, false)?;
let result = eval_const( Ok(c)
body.body_expr,
&mut ConstEvalCtx {
db,
owner: const_id.into(),
exprs: &body.exprs,
pats: &body.pats,
local_data: HashMap::default(),
infer,
},
);
result
} }
pub(crate) fn const_eval_query_variant( pub(crate) fn const_eval_discriminant_variant(
db: &dyn HirDatabase, db: &dyn HirDatabase,
variant_id: EnumVariantId, variant_id: EnumVariantId,
) -> Result<ComputedExpr, ConstEvalError> { ) -> Result<i128, ConstEvalError> {
let def = variant_id.into(); let def = variant_id.into();
let body = db.body(def); let body = db.body(def);
let infer = &db.infer(def); if body.exprs[body.body_expr] == Expr::Missing {
eval_const( let prev_idx: u32 = variant_id.local_id.into_raw().into();
body.body_expr, let prev_idx = prev_idx.checked_sub(1).map(RawIdx::from).map(Idx::from_raw);
&mut ConstEvalCtx { let value = match prev_idx {
db, Some(local_id) => {
owner: def, let prev_variant = EnumVariantId { local_id, parent: variant_id.parent };
exprs: &body.exprs, 1 + db.const_eval_discriminant(prev_variant)?
pats: &body.pats, }
local_data: HashMap::default(), _ => 0,
infer, };
}, 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( pub(crate) fn eval_to_const(
expr: Idx<Expr>, expr: Idx<Expr>,
mode: ParamLoweringMode, mode: ParamLoweringMode,
@ -518,28 +225,20 @@ pub(crate) fn eval_to_const(
args: impl FnOnce() -> Generics, args: impl FnOnce() -> Generics,
debruijn: DebruijnIndex, debruijn: DebruijnIndex,
) -> Const { ) -> Const {
let db = ctx.db;
if let Expr::Path(p) = &ctx.body.exprs[expr] { if let Expr::Path(p) = &ctx.body.exprs[expr] {
let db = ctx.db;
let resolver = &ctx.resolver; let resolver = &ctx.resolver;
if let Some(c) = path_to_const(db, resolver, p.mod_path(), mode, args, debruijn) { if let Some(c) = path_to_const(db, resolver, p.mod_path(), mode, args, debruijn) {
return c; return c;
} }
} }
let body = ctx.body.clone(); let infer = ctx.clone().resolve_all();
let mut ctx = ConstEvalCtx { if let Ok(mir_body) = lower_to_mir(ctx.db, ctx.owner, &ctx.body, &infer, expr) {
db: ctx.db, if let Ok(result) = interpret_mir(db, &mir_body, true) {
owner: ctx.owner, return result;
exprs: &body.exprs, }
pats: &body.pats, }
local_data: HashMap::default(), unknown_const(infer[expr].clone())
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())
} }
#[cfg(test)] #[cfg(test)]

View file

@ -1,24 +1,44 @@
use base_db::fixture::WithFixture; use base_db::fixture::WithFixture;
use hir_def::{db::DefDatabase, expr::Literal}; use hir_def::db::DefDatabase;
use crate::{consteval::ComputedExpr, db::HirDatabase, test_db::TestDB}; use crate::{
consteval::try_const_usize, db::HirDatabase, test_db::TestDB, Const, ConstScalar, Interner,
};
use super::ConstEvalError; use super::{
super::mir::{MirEvalError, MirLowerError},
ConstEvalError,
};
fn check_fail(ra_fixture: &str, error: ConstEvalError) { fn simplify(e: ConstEvalError) -> ConstEvalError {
assert_eq!(eval_goal(ra_fixture), Err(error)); match e {
} ConstEvalError::MirEvalError(MirEvalError::InFunction(_, e)) => {
simplify(ConstEvalError::MirEvalError(*e))
fn check_number(ra_fixture: &str, answer: i128) { }
let r = eval_goal(ra_fixture).unwrap(); _ => e,
match r {
ComputedExpr::Literal(Literal::Int(r, _)) => assert_eq!(r, answer),
ComputedExpr::Literal(Literal::Uint(r, _)) => assert_eq!(r, answer as u128),
x => panic!("Expected number but found {x:?}"),
} }
} }
fn eval_goal(ra_fixture: &str) -> Result<ComputedExpr, ConstEvalError> { #[track_caller]
fn check_fail(ra_fixture: &str, error: ConstEvalError) {
assert_eq!(eval_goal(ra_fixture).map_err(simplify), Err(error));
}
#[track_caller]
fn check_number(ra_fixture: &str, answer: i128) {
let r = eval_goal(ra_fixture).unwrap();
match &r.data(Interner).value {
chalk_ir::ConstValue::Concrete(c) => match &c.interned {
ConstScalar::Bytes(b, _) => {
assert_eq!(b, &answer.to_le_bytes()[0..b.len()]);
}
x => panic!("Expected number but found {:?}", x),
},
_ => panic!("result of const eval wasn't a concrete const"),
}
}
fn eval_goal(ra_fixture: &str) -> Result<Const, ConstEvalError> {
let (db, file_id) = TestDB::with_single_file(ra_fixture); let (db, file_id) = TestDB::with_single_file(ra_fixture);
let module_id = db.module_for_file(file_id); let module_id = db.module_for_file(file_id);
let def_map = module_id.def_map(&db); let def_map = module_id.def_map(&db);
@ -42,21 +62,18 @@ fn eval_goal(ra_fixture: &str) -> Result<ComputedExpr, ConstEvalError> {
#[test] #[test]
fn add() { fn add() {
check_number(r#"const GOAL: usize = 2 + 2;"#, 4); check_number(r#"const GOAL: usize = 2 + 2;"#, 4);
check_number(r#"const GOAL: i32 = -2 + --5;"#, 3);
check_number(r#"const GOAL: i32 = 7 - 5;"#, 2);
check_number(r#"const GOAL: i32 = 7 + (1 - 5);"#, 3);
} }
#[test] #[test]
fn bit_op() { fn bit_op() {
check_number(r#"const GOAL: u8 = !0 & !(!0 >> 1)"#, 128); check_number(r#"const GOAL: u8 = !0 & !(!0 >> 1)"#, 128);
check_number(r#"const GOAL: i8 = !0 & !(!0 >> 1)"#, 0); check_number(r#"const GOAL: i8 = !0 & !(!0 >> 1)"#, 0);
// FIXME: rustc evaluate this to -128 check_number(r#"const GOAL: i8 = 1 << 7"#, (1i8 << 7) as i128);
check_fail( // FIXME: report panic here
r#"const GOAL: i8 = 1 << 7"#, check_number(r#"const GOAL: i8 = 1 << 8"#, 0);
ConstEvalError::Panic("attempt to run invalid arithmetic operation".to_string()),
);
check_fail(
r#"const GOAL: i8 = 1 << 8"#,
ConstEvalError::Panic("attempt to run invalid arithmetic operation".to_string()),
);
} }
#[test] #[test]
@ -73,6 +90,562 @@ fn locals() {
); );
} }
#[test]
fn references() {
check_number(
r#"
const GOAL: usize = {
let x = 3;
let y = &mut x;
*y = 5;
x
};
"#,
5,
);
}
#[test]
fn reference_autoderef() {
check_number(
r#"
const GOAL: usize = {
let x = 3;
let y = &mut x;
let y: &mut usize = &mut y;
*y = 5;
x
};
"#,
5,
);
check_number(
r#"
const GOAL: usize = {
let x = 3;
let y = &&&&&&&x;
let z: &usize = &y;
*z
};
"#,
3,
);
}
#[test]
fn function_call() {
check_number(
r#"
const fn f(x: usize) -> usize {
2 * x + 5
}
const GOAL: usize = f(3);
"#,
11,
);
check_number(
r#"
const fn add(x: usize, y: usize) -> usize {
x + y
}
const GOAL: usize = add(add(1, 2), add(3, add(4, 5)));
"#,
15,
);
}
#[test]
fn intrinsics() {
check_number(
r#"
extern "rust-intrinsic" {
pub fn size_of<T>() -> usize;
}
const GOAL: usize = size_of::<i32>();
"#,
4,
);
}
#[test]
fn trait_basic() {
check_number(
r#"
trait Foo {
fn f(&self) -> u8;
}
impl Foo for u8 {
fn f(&self) -> u8 {
*self + 33
}
}
const GOAL: u8 = {
let x = 3;
Foo::f(&x)
};
"#,
36,
);
}
#[test]
fn trait_method() {
check_number(
r#"
trait Foo {
fn f(&self) -> u8;
}
impl Foo for u8 {
fn f(&self) -> u8 {
*self + 33
}
}
const GOAL: u8 = {
let x = 3;
x.f()
};
"#,
36,
);
}
#[test]
fn generic_fn() {
check_number(
r#"
trait Foo {
fn f(&self) -> u8;
}
impl Foo for () {
fn f(&self) -> u8 {
0
}
}
struct Succ<S>(S);
impl<T: Foo> Foo for Succ<T> {
fn f(&self) -> u8 {
self.0.f() + 1
}
}
const GOAL: u8 = Succ(Succ(())).f();
"#,
2,
);
check_number(
r#"
trait Foo {
fn f(&self) -> u8;
}
impl Foo for u8 {
fn f(&self) -> u8 {
*self + 33
}
}
fn foof<T: Foo>(x: T, y: T) -> u8 {
x.f() + y.f()
}
const GOAL: u8 = foof(2, 5);
"#,
73,
);
check_number(
r#"
fn bar<A, B>(a: A, b: B) -> B {
b
}
const GOAL: u8 = bar("hello", 12);
"#,
12,
);
check_number(
r#"
//- minicore: coerce_unsized, index, slice
fn bar<A, B>(a: A, b: B) -> B {
b
}
fn foo<T>(x: [T; 2]) -> T {
bar(x[0], x[1])
}
const GOAL: u8 = foo([2, 5]);
"#,
5,
);
}
#[test]
fn impl_trait() {
check_number(
r#"
trait Foo {
fn f(&self) -> u8;
}
impl Foo for u8 {
fn f(&self) -> u8 {
*self + 33
}
}
fn foof(x: impl Foo, y: impl Foo) -> impl Foo {
x.f() + y.f()
}
const GOAL: u8 = foof(2, 5).f();
"#,
106,
);
check_number(
r#"
struct Foo<T>(T, T, (T, T));
trait S {
fn sum(&self) -> i64;
}
impl S for i64 {
fn sum(&self) -> i64 {
*self
}
}
impl<T: S> S for Foo<T> {
fn sum(&self) -> i64 {
self.0.sum() + self.1.sum() + self.2 .0.sum() + self.2 .1.sum()
}
}
fn foo() -> Foo<impl S> {
Foo(
Foo(1i64, 2, (3, 4)),
Foo(5, 6, (7, 8)),
(
Foo(9, 10, (11, 12)),
Foo(13, 14, (15, 16)),
),
)
}
const GOAL: i64 = foo().sum();
"#,
136,
);
}
#[test]
fn ifs() {
check_number(
r#"
const fn f(b: bool) -> u8 {
if b { 1 } else { 10 }
}
const GOAL: u8 = f(true) + f(true) + f(false);
"#,
12,
);
check_number(
r#"
const fn max(a: i32, b: i32) -> i32 {
if a < b { b } else { a }
}
const GOAL: u8 = max(max(1, max(10, 3)), 0-122);
"#,
10,
);
check_number(
r#"
const fn max(a: &i32, b: &i32) -> &i32 {
if a < b { b } else { a }
}
const GOAL: i32 = *max(max(&1, max(&10, &3)), &5);
"#,
10,
);
}
#[test]
fn loops() {
check_number(
r#"
const GOAL: u8 = {
let mut x = 0;
loop {
x = x + 1;
while true {
break;
}
x = x + 1;
if x == 2 {
continue;
}
break;
}
x
};
"#,
4,
);
}
#[test]
fn recursion() {
check_number(
r#"
const fn fact(k: i32) -> i32 {
if k > 0 { fact(k - 1) * k } else { 1 }
}
const GOAL: i32 = fact(5);
"#,
120,
);
}
#[test]
fn structs() {
check_number(
r#"
struct Point {
x: i32,
y: i32,
}
const GOAL: i32 = {
let p = Point { x: 5, y: 2 };
let y = 1;
let x = 3;
let q = Point { y, x };
p.x + p.y + p.x + q.y + q.y + q.x
};
"#,
17,
);
}
#[test]
fn unions() {
check_number(
r#"
union U {
f1: i64,
f2: (i32, i32),
}
const GOAL: i32 = {
let p = U { f1: 0x0123ABCD0123DCBA };
let p = unsafe { p.f2 };
p.0 + p.1 + p.1
};
"#,
0x0123ABCD * 2 + 0x0123DCBA,
);
}
#[test]
fn tuples() {
check_number(
r#"
const GOAL: u8 = {
let a = (10, 20, 3, 15);
a.1
};
"#,
20,
);
check_number(
r#"
struct TupleLike(i32, u8, i64, u16);
const GOAL: u8 = {
let a = TupleLike(10, 20, 3, 15);
a.1
};
"#,
20,
);
check_number(
r#"
const GOAL: u8 = {
match (&(2 + 2), &4) {
(left_val, right_val) => {
if !(*left_val == *right_val) {
2
} else {
5
}
}
}
};
"#,
5,
);
}
#[test]
fn pattern_matching_ergonomics() {
check_number(
r#"
const fn f(x: &(u8, u8)) -> u8 {
match x {
(a, b) => *a + *b
}
}
const GOAL: u8 = f(&(2, 3));
"#,
5,
);
}
#[test]
fn let_else() {
check_number(
r#"
const fn f(x: &(u8, u8)) -> u8 {
let (a, b) = x;
*a + *b
}
const GOAL: u8 = f(&(2, 3));
"#,
5,
);
check_number(
r#"
enum SingleVariant {
Var(u8, u8),
}
const fn f(x: &&&&&SingleVariant) -> u8 {
let SingleVariant::Var(a, b) = x;
*a + *b
}
const GOAL: u8 = f(&&&&&SingleVariant::Var(2, 3));
"#,
5,
);
check_number(
r#"
//- minicore: option
const fn f(x: Option<i32>) -> i32 {
let Some(x) = x else { return 10 };
2 * x
}
const GOAL: u8 = f(Some(1000)) + f(None);
"#,
2010,
);
}
#[test]
fn options() {
check_number(
r#"
//- minicore: option
const GOAL: u8 = {
let x = Some(2);
match x {
Some(y) => 2 * y,
_ => 10,
}
};
"#,
4,
);
check_number(
r#"
//- minicore: option
fn f(x: Option<Option<i32>>) -> i32 {
if let Some(y) = x && let Some(z) = y {
z
} else if let Some(y) = x {
1
} else {
0
}
}
const GOAL: u8 = f(Some(Some(10))) + f(Some(None)) + f(None);
"#,
11,
);
check_number(
r#"
//- minicore: option
const GOAL: u8 = {
let x = None;
match x {
Some(y) => 2 * y,
_ => 10,
}
};
"#,
10,
);
check_number(
r#"
//- minicore: option
const GOAL: Option<&u8> = None;
"#,
0,
);
}
#[test]
fn array_and_index() {
check_number(
r#"
//- minicore: coerce_unsized, index, slice
const GOAL: u8 = {
let a = [10, 20, 3, 15];
let x: &[u8] = &a;
x[1]
};
"#,
20,
);
check_number(
r#"
//- minicore: coerce_unsized, index, slice
const GOAL: usize = [1, 2, 3][2];"#,
3,
);
check_number(
r#"
//- minicore: coerce_unsized, index, slice
const GOAL: usize = { let a = [1, 2, 3]; let x: &[i32] = &a; x.len() };"#,
3,
);
check_number(
r#"
//- minicore: coerce_unsized, index, slice
const GOAL: usize = [1, 2, 3, 4, 5].len();"#,
5,
);
}
#[test]
fn byte_string() {
check_number(
r#"
//- minicore: coerce_unsized, index, slice
const GOAL: u8 = {
let a = b"hello";
let x: &[u8] = a;
x[0]
};
"#,
104,
);
}
#[test] #[test]
fn consts() { fn consts() {
check_number( check_number(
@ -115,18 +688,12 @@ fn enums() {
); );
let r = eval_goal( let r = eval_goal(
r#" r#"
enum E { A = 1, } enum E { A = 1, B }
const GOAL: E = E::A; const GOAL: E = E::A;
"#, "#,
) )
.unwrap(); .unwrap();
match r { assert_eq!(try_const_usize(&r), Some(1));
ComputedExpr::Enum(name, _, Literal::Uint(val, _)) => {
assert_eq!(name, "E::A");
assert_eq!(val, 1);
}
x => panic!("Expected enum but found {x:?}"),
}
} }
#[test] #[test]
@ -138,7 +705,19 @@ fn const_loop() {
const F2: i32 = 2 * F1; const F2: i32 = 2 * F1;
const GOAL: i32 = F3; const GOAL: i32 = F3;
"#, "#,
ConstEvalError::Loop, ConstEvalError::MirLowerError(MirLowerError::Loop),
);
}
#[test]
fn const_transfer_memory() {
check_number(
r#"
const A1: &i32 = &2;
const A2: &i32 = &5;
const GOAL: i32 = *A1 + *A2;
"#,
7,
); );
} }
@ -157,7 +736,20 @@ fn const_impl_assoc() {
} }
#[test] #[test]
fn const_generic_subst() { fn const_generic_subst_fn() {
check_number(
r#"
const fn f<const A: usize>(x: usize) -> usize {
A * x + 5
}
const GOAL: usize = f::<2>(3);
"#,
11,
);
}
#[test]
fn const_generic_subst_assoc_const_impl() {
// FIXME: this should evaluate to 5 // FIXME: this should evaluate to 5
check_fail( check_fail(
r#" r#"
@ -167,7 +759,7 @@ fn const_generic_subst() {
} }
const GOAL: usize = Adder::<2, 3>::VAL; const GOAL: usize = Adder::<2, 3>::VAL;
"#, "#,
ConstEvalError::NotSupported("const generic without substitution"), ConstEvalError::MirEvalError(MirEvalError::TypeError("missing generic arg")),
); );
} }
@ -185,6 +777,44 @@ fn const_trait_assoc() {
} }
const GOAL: usize = U0::VAL; const GOAL: usize = U0::VAL;
"#, "#,
ConstEvalError::IncompleteExpr, ConstEvalError::MirLowerError(MirLowerError::IncompleteExpr),
);
}
#[test]
fn exec_limits() {
check_fail(
r#"
const GOAL: usize = loop {};
"#,
ConstEvalError::MirEvalError(MirEvalError::ExecutionLimitExceeded),
);
check_fail(
r#"
const fn f(x: i32) -> i32 {
f(x + 1)
}
const GOAL: i32 = f(0);
"#,
ConstEvalError::MirEvalError(MirEvalError::StackOverflow),
);
// Reasonable code should still work
check_number(
r#"
const fn nth_odd(n: i32) -> i32 {
2 * n - 1
}
const fn f(n: i32) -> i32 {
let sum = 0;
let i = 0;
while i < n {
i = i + 1;
sum = sum + nth_odd(i);
}
sum
}
const GOAL: usize = f(10000);
"#,
10000 * 10000,
); );
} }

View file

@ -16,10 +16,12 @@ use smallvec::SmallVec;
use crate::{ use crate::{
chalk_db, chalk_db,
consteval::{ComputedExpr, ConstEvalError}, consteval::ConstEvalError,
method_resolution::{InherentImpls, TraitImpls, TyFingerprint}, method_resolution::{InherentImpls, TraitImpls, TyFingerprint},
Binders, CallableDefId, FnDefId, GenericArg, ImplTraitId, InferenceResult, Interner, PolyFnSig, mir::{MirBody, MirLowerError},
QuantifiedWhereClause, ReturnTypeImplTraits, Substitution, TraitRef, Ty, TyDefId, ValueTyDefId, Binders, CallableDefId, Const, FnDefId, GenericArg, ImplTraitId, InferenceResult, Interner,
PolyFnSig, QuantifiedWhereClause, ReturnTypeImplTraits, Substitution, TraitRef, Ty, TyDefId,
ValueTyDefId,
}; };
use hir_expand::name::Name; use hir_expand::name::Name;
@ -32,6 +34,10 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
#[salsa::invoke(crate::infer::infer_query)] #[salsa::invoke(crate::infer::infer_query)]
fn infer_query(&self, def: DefWithBodyId) -> Arc<InferenceResult>; 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::lower::ty_query)] #[salsa::invoke(crate::lower::ty_query)]
#[salsa::cycle(crate::lower::ty_recover)] #[salsa::cycle(crate::lower::ty_recover)]
fn ty(&self, def: TyDefId) -> Binders<Ty>; fn ty(&self, def: TyDefId) -> Binders<Ty>;
@ -46,13 +52,13 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
#[salsa::invoke(crate::lower::const_param_ty_query)] #[salsa::invoke(crate::lower::const_param_ty_query)]
fn const_param_ty(&self, def: ConstParamId) -> Ty; 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)] #[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::invoke(crate::consteval::const_eval_discriminant_variant)]
#[salsa::cycle(crate::consteval::const_eval_variant_recover)] #[salsa::cycle(crate::consteval::const_eval_discriminant_recover)]
fn const_eval_variant(&self, def: EnumVariantId) -> Result<ComputedExpr, ConstEvalError>; fn const_eval_discriminant(&self, def: EnumVariantId) -> Result<i128, ConstEvalError>;
#[salsa::invoke(crate::lower::impl_trait_query)] #[salsa::invoke(crate::lower::impl_trait_query)]
fn impl_trait(&self, def: ImplId) -> Option<Binders<TraitRef>>; fn impl_trait(&self, def: ImplId) -> Option<Binders<TraitRef>>;

View file

@ -7,6 +7,7 @@ use std::fmt::{self, Debug};
use base_db::CrateId; use base_db::CrateId;
use chalk_ir::BoundVar; use chalk_ir::BoundVar;
use hir_def::{ use hir_def::{
adt::VariantData,
body, body,
db::DefDatabase, db::DefDatabase,
find_path, find_path,
@ -14,9 +15,9 @@ use hir_def::{
item_scope::ItemInNs, item_scope::ItemInNs,
lang_item::{LangItem, LangItemTarget}, lang_item::{LangItem, LangItemTarget},
path::{Path, PathKind}, path::{Path, PathKind},
type_ref::{ConstScalar, TraitBoundModifier, TypeBound, TypeRef}, type_ref::{TraitBoundModifier, TypeBound, TypeRef},
visibility::Visibility, visibility::Visibility,
HasModule, ItemContainerId, Lookup, ModuleDefId, ModuleId, TraitId, HasModule, ItemContainerId, LocalFieldId, Lookup, ModuleDefId, ModuleId, TraitId,
}; };
use hir_expand::{hygiene::Hygiene, name::Name}; use hir_expand::{hygiene::Hygiene, name::Name};
use intern::{Internable, Interned}; use intern::{Internable, Interned};
@ -25,14 +26,17 @@ use smallvec::SmallVec;
use crate::{ use crate::{
db::HirDatabase, 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, mapping::from_chalk,
mir::pad16,
primitive, to_assoc_type_id, primitive, to_assoc_type_id,
utils::{self, generics}, utils::{self, generics},
AdtId, AliasEq, AliasTy, Binders, CallableDefId, CallableSig, Const, ConstValue, DomainGoal, AdtId, AliasEq, AliasTy, Binders, CallableDefId, CallableSig, Const, ConstScalar, ConstValue,
GenericArg, ImplTraitId, Interner, Lifetime, LifetimeData, LifetimeOutlives, Mutability, DomainGoal, GenericArg, ImplTraitId, Interner, Lifetime, LifetimeData, LifetimeOutlives,
OpaqueTy, ProjectionTy, ProjectionTyExt, QuantifiedWhereClause, Scalar, Substitution, TraitRef, MemoryMap, Mutability, OpaqueTy, ProjectionTy, ProjectionTyExt, QuantifiedWhereClause, Scalar,
TraitRefExt, Ty, TyExt, TyKind, WhereClause, Substitution, TraitRef, TraitRefExt, Ty, TyExt, TyKind, WhereClause,
}; };
pub trait HirWrite: fmt::Write { pub trait HirWrite: fmt::Write {
@ -362,20 +366,125 @@ impl HirDisplay for GenericArg {
impl HirDisplay for Const { impl HirDisplay for Const {
fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
let data = self.interned(); let data = self.interned();
match data.value { match &data.value {
ConstValue::BoundVar(idx) => idx.hir_fmt(f), ConstValue::BoundVar(idx) => idx.hir_fmt(f),
ConstValue::InferenceVar(..) => write!(f, "#c#"), ConstValue::InferenceVar(..) => write!(f, "#c#"),
ConstValue::Placeholder(idx) => { 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 generics = generics(f.db.upcast(), id.parent);
let param_data = &generics.params.type_or_consts[id.local_id]; let param_data = &generics.params.type_or_consts[id.local_id];
write!(f, "{}", param_data.name().unwrap()) 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('_'),
},
} }
} }
} }
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("<error>"),
},
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>"),
},
_ => f.write_str("<error>"),
}
}
impl HirDisplay for BoundVar { impl HirDisplay for BoundVar {
fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
write!(f, "?{}.{}", self.debruijn.depth(), self.index) write!(f, "?{}.{}", self.debruijn.depth(), self.index)
@ -614,8 +723,9 @@ impl HirDisplay for Ty {
{ {
return true; return true;
} }
if let Some(ConstValue::Concrete(c)) = if let Some(ConstValue::Concrete(c)) = parameter
parameter.constant(Interner).map(|x| x.data(Interner).value) .constant(Interner)
.map(|x| &x.data(Interner).value)
{ {
if c.interned == ConstScalar::Unknown { if c.interned == ConstScalar::Unknown {
return true; return true;

View file

@ -512,7 +512,11 @@ impl<'a> InferenceContext<'a> {
} }
} }
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; let InferenceContext { mut table, mut result, .. } = self;
table.fallback_if_possible(); table.fallback_if_possible();
@ -681,11 +685,9 @@ impl<'a> InferenceContext<'a> {
/// Replaces ConstScalar::Unknown by a new type var, so we can maybe still infer it. /// 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 { fn insert_const_vars_shallow(&mut self, c: Const) -> Const {
let data = c.data(Interner); let data = c.data(Interner);
match data.value { match &data.value {
ConstValue::Concrete(cc) => match cc.interned { ConstValue::Concrete(cc) => match cc.interned {
hir_def::type_ref::ConstScalar::Unknown => { crate::ConstScalar::Unknown => self.table.new_const_var(data.ty.clone()),
self.table.new_const_var(data.ty.clone())
}
_ => c, _ => c,
}, },
_ => c, _ => c,

View file

@ -822,7 +822,11 @@ impl<'a> InferenceContext<'a> {
let cur_elem_ty = self.infer_expr_inner(expr, &expected); let cur_elem_ty = self.infer_expr_inner(expr, &expected);
coerce.coerce(self, Some(expr), &cur_elem_ty); coerce.coerce(self, Some(expr), &cur_elem_ty);
} }
consteval::usize_const(Some(elements.len() as u128)) consteval::usize_const(
self.db,
Some(elements.len() as u128),
self.resolver.krate(),
)
} }
&Array::Repeat { initializer, repeat } => { &Array::Repeat { initializer, repeat } => {
self.infer_expr_coerce(initializer, &Expectation::has_type(elem_ty)); self.infer_expr_coerce(initializer, &Expectation::has_type(elem_ty));
@ -843,7 +847,7 @@ impl<'a> InferenceContext<'a> {
DebruijnIndex::INNERMOST, DebruijnIndex::INNERMOST,
) )
} else { } else {
consteval::usize_const(None) consteval::usize_const(self.db, None, self.resolver.krate())
} }
} }
}; };
@ -859,7 +863,11 @@ impl<'a> InferenceContext<'a> {
Literal::ByteString(bs) => { Literal::ByteString(bs) => {
let byte_type = TyKind::Scalar(Scalar::Uint(UintTy::U8)).intern(Interner); let byte_type = TyKind::Scalar(Scalar::Uint(UintTy::U8)).intern(Interner);
let len = consteval::usize_const(Some(bs.len() as u128)); let len = consteval::usize_const(
self.db,
Some(bs.len() as u128),
self.resolver.krate(),
);
let array_type = TyKind::Array(byte_type, len).intern(Interner); let array_type = TyKind::Array(byte_type, len).intern(Interner);
TyKind::Ref(Mutability::Not, static_lifetime(), array_type).intern(Interner) TyKind::Ref(Mutability::Not, static_lifetime(), array_type).intern(Interner)
@ -982,8 +990,11 @@ impl<'a> InferenceContext<'a> {
// type and length). This should not be just an error type, // type and length). This should not be just an error type,
// because we are to compute the unifiability of this type and // because we are to compute the unifiability of this type and
// `rhs_ty` in the end of this function to issue type mismatches. // `rhs_ty` in the end of this function to issue type mismatches.
_ => TyKind::Array(self.err_ty(), crate::consteval::usize_const(None)) _ => TyKind::Array(
.intern(Interner), self.err_ty(),
crate::consteval::usize_const(self.db, None, self.resolver.krate()),
)
.intern(Interner),
} }
} }
Expr::RecordLit { path, fields, .. } => { Expr::RecordLit { path, fields, .. } => {

View file

@ -6,17 +6,15 @@ use chalk_ir::Mutability;
use hir_def::{ use hir_def::{
expr::{BindingAnnotation, Expr, Literal, Pat, PatId}, expr::{BindingAnnotation, Expr, Literal, Pat, PatId},
path::Path, path::Path,
type_ref::ConstScalar,
}; };
use hir_expand::name::Name; use hir_expand::name::Name;
use crate::{ use crate::{
consteval::intern_const_scalar, consteval::{try_const_usize, usize_const},
infer::{BindingMode, Expectation, InferenceContext, TypeMismatch}, infer::{BindingMode, Expectation, InferenceContext, TypeMismatch},
lower::lower_to_chalk_mutability, lower::lower_to_chalk_mutability,
primitive::UintTy, primitive::UintTy,
static_lifetime, ConcreteConst, ConstValue, Interner, Scalar, Substitution, Ty, TyBuilder, static_lifetime, Interner, Scalar, Substitution, Ty, TyBuilder, TyExt, TyKind,
TyExt, TyKind,
}; };
use super::PatLike; use super::PatLike;
@ -264,18 +262,13 @@ impl<'a> InferenceContext<'a> {
if let &Some(slice_pat_id) = slice { if let &Some(slice_pat_id) = slice {
let rest_pat_ty = match expected.kind(Interner) { let rest_pat_ty = match expected.kind(Interner) {
TyKind::Array(_, length) => { TyKind::Array(_, length) => {
let len = match length.data(Interner).value { let len = try_const_usize(length);
ConstValue::Concrete(ConcreteConst { let len = len.and_then(|len| {
interned: ConstScalar::UInt(len), len.checked_sub((prefix.len() + suffix.len()) as u128)
}) => len.checked_sub((prefix.len() + suffix.len()) as u128), });
_ => None,
};
TyKind::Array( TyKind::Array(
elem_ty.clone(), elem_ty.clone(),
intern_const_scalar( usize_const(self.db, len, self.resolver.krate()),
len.map_or(ConstScalar::Unknown, |len| ConstScalar::UInt(len)),
TyBuilder::usize(),
),
) )
} }
_ => TyKind::Slice(elem_ty.clone()), _ => TyKind::Slice(elem_ty.clone()),

View file

@ -704,14 +704,13 @@ impl<'a> fmt::Debug for InferenceTable<'a> {
mod resolve { mod resolve {
use super::InferenceTable; use super::InferenceTable;
use crate::{ use crate::{
ConcreteConst, Const, ConstData, ConstValue, DebruijnIndex, GenericArg, InferenceVar, ConcreteConst, Const, ConstData, ConstScalar, ConstValue, DebruijnIndex, GenericArg,
Interner, Lifetime, Ty, TyVariableKind, VariableKind, InferenceVar, Interner, Lifetime, Ty, TyVariableKind, VariableKind,
}; };
use chalk_ir::{ use chalk_ir::{
cast::Cast, cast::Cast,
fold::{TypeFoldable, TypeFolder}, fold::{TypeFoldable, TypeFolder},
}; };
use hir_def::type_ref::ConstScalar;
#[derive(chalk_derive::FallibleTypeFolder)] #[derive(chalk_derive::FallibleTypeFolder)]
#[has_interner(Interner)] #[has_interner(Interner)]

View file

@ -6,12 +6,12 @@ use chalk_ir::{
DebruijnIndex, DebruijnIndex,
}; };
use hir_def::{ use hir_def::{
adt::VariantData, attr::Attrs, type_ref::ConstScalar, visibility::Visibility, AdtId, adt::VariantData, attr::Attrs, visibility::Visibility, AdtId, EnumVariantId, HasModule, Lookup,
EnumVariantId, HasModule, Lookup, ModuleId, VariantId, ModuleId, VariantId,
}; };
use crate::{ 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. /// 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::Adt(adt, subst) => self.visit_adt(adt.0, subst),
TyKind::Never => BREAK_VISIBLY_UNINHABITED, TyKind::Never => BREAK_VISIBLY_UNINHABITED,
TyKind::Tuple(..) => ty.super_visit_with(self, outer_binder), 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(0) | None => CONTINUE_OPAQUELY_INHABITED,
Some(1..) => item_ty.super_visit_with(self, outer_binder), 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,
}
}

View file

@ -1,10 +1,10 @@
//! Implementation of the Chalk `Interner` trait, which allows customizing the //! Implementation of the Chalk `Interner` trait, which allows customizing the
//! representation of the various objects Chalk deals with (types, goals etc.). //! 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 base_db::salsa::InternId;
use chalk_ir::{Goal, GoalData}; use chalk_ir::{Goal, GoalData};
use hir_def::{type_ref::ConstScalar, TypeAliasId}; use hir_def::TypeAliasId;
use intern::{impl_internable, Interned}; use intern::{impl_internable, Interned};
use smallvec::SmallVec; use smallvec::SmallVec;
use std::{fmt, sync::Arc}; use std::{fmt, sync::Arc};

View file

@ -11,7 +11,7 @@ use hir_def::{
}; };
use stdx::never; 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; use self::adt::struct_variant_idx;
pub use self::{ 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)? cx.univariant(dl, &fields, &ReprOptions::default(), kind).ok_or(LayoutError::Unknown)?
} }
TyKind::Array(element, count) => { TyKind::Array(element, count) => {
let count = match count.data(Interner).value { let count = try_const_usize(&count).ok_or(LayoutError::UserError(
chalk_ir::ConstValue::Concrete(c) => match c.interned { "mismatched type of const generic parameter".to_string(),
hir_def::type_ref::ConstScalar::Int(x) => x as u64, ))? 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 element = layout_of_ty(db, element, krate)?; let element = layout_of_ty(db, element, krate)?;
let size = element.size.checked_mul(count, dl).ok_or(LayoutError::SizeOverflow)?; let size = element.size.checked_mul(count, dl).ok_or(LayoutError::SizeOverflow)?;

View file

@ -76,17 +76,8 @@ pub fn layout_of_adt_query(
|min, max| Integer::repr_discr(&dl, &repr, min, max).unwrap_or((Integer::I8, false)), |min, max| Integer::repr_discr(&dl, &repr, min, max).unwrap_or((Integer::I8, false)),
variants.iter_enumerated().filter_map(|(id, _)| { variants.iter_enumerated().filter_map(|(id, _)| {
let AdtId::EnumId(e) = def else { return None }; let AdtId::EnumId(e) = def else { return None };
let d = match db let d =
.const_eval_variant(EnumVariantId { parent: e, local_id: id.0 }) db.const_eval_discriminant(EnumVariantId { parent: e, local_id: id.0 }).ok()?;
.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,
};
Some((id, d)) Some((id, d))
}), }),
// FIXME: The current code for niche-filling relies on variant indices // FIXME: The current code for niche-filling relies on variant indices

View file

@ -82,8 +82,8 @@ fn eval_expr(ra_fixture: &str, minicore: &str) -> Result<Layout, LayoutError> {
#[track_caller] #[track_caller]
fn check_size_and_align(ra_fixture: &str, minicore: &str, size: u64, align: u64) { fn check_size_and_align(ra_fixture: &str, minicore: &str, size: u64, align: u64) {
let l = eval_goal(ra_fixture, minicore).unwrap(); let l = eval_goal(ra_fixture, minicore).unwrap();
assert_eq!(l.size.bytes(), size); assert_eq!(l.size.bytes(), size, "size mismatch");
assert_eq!(l.align.abi.bytes(), align); assert_eq!(l.align.abi.bytes(), align, "align mismatch");
} }
#[track_caller] #[track_caller]
@ -300,4 +300,9 @@ fn enums_with_discriminants() {
C, // implicitly becomes 256, so we need two bytes C, // implicitly becomes 256, so we need two bytes
} }
} }
size_and_align! {
enum Goal {
A = 1, // This one is (perhaps surprisingly) zero sized.
}
}
} }

View file

@ -13,6 +13,7 @@ mod builder;
mod chalk_db; mod chalk_db;
mod chalk_ext; mod chalk_ext;
pub mod consteval; pub mod consteval;
pub mod mir;
mod infer; mod infer;
mod inhabitedness; mod inhabitedness;
mod interner; mod interner;
@ -34,7 +35,7 @@ mod tests;
#[cfg(test)] #[cfg(test)]
mod test_db; mod test_db;
use std::sync::Arc; use std::{collections::HashMap, hash::Hash, sync::Arc};
use chalk_ir::{ use chalk_ir::{
fold::{Shift, TypeFoldable}, fold::{Shift, TypeFoldable},
@ -46,6 +47,7 @@ use either::Either;
use hir_def::{expr::ExprId, type_ref::Rawness, TypeOrConstParamId}; use hir_def::{expr::ExprId, type_ref::Rawness, TypeOrConstParamId};
use hir_expand::name; use hir_expand::name;
use la_arena::{Arena, Idx}; use la_arena::{Arena, Idx};
use mir::MirEvalError;
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use traits::FnTrait; use traits::FnTrait;
use utils::Generics; use utils::Generics;
@ -145,6 +147,49 @@ pub type ConstrainedSubst = chalk_ir::ConstrainedSubst<Interner>;
pub type Guidance = chalk_solve::Guidance<Interner>; pub type Guidance = chalk_solve::Guidance<Interner>;
pub type WhereClause = chalk_ir::WhereClause<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. /// 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> { pub fn param_idx(db: &dyn HirDatabase, id: TypeOrConstParamId) -> Option<usize> {
generics(db.upcast(), id.parent).param_idx(id) generics(db.upcast(), id.parent).param_idx(id)

View file

@ -27,9 +27,7 @@ use hir_def::{
lang_item::{lang_attr, LangItem}, lang_item::{lang_attr, LangItem},
path::{GenericArg, ModPath, Path, PathKind, PathSegment, PathSegments}, path::{GenericArg, ModPath, Path, PathKind, PathSegment, PathSegments},
resolver::{HasResolver, Resolver, TypeNs}, resolver::{HasResolver, Resolver, TypeNs},
type_ref::{ type_ref::{ConstRefOrPath, TraitBoundModifier, TraitRef as HirTraitRef, TypeBound, TypeRef},
ConstScalarOrPath, TraitBoundModifier, TraitRef as HirTraitRef, TypeBound, TypeRef,
},
AdtId, AssocItemId, ConstId, ConstParamId, EnumId, EnumVariantId, FunctionId, GenericDefId, AdtId, AssocItemId, ConstId, ConstParamId, EnumId, EnumVariantId, FunctionId, GenericDefId,
HasModule, ImplId, ItemContainerId, LocalFieldId, Lookup, ModuleDefId, StaticId, StructId, HasModule, ImplId, ItemContainerId, LocalFieldId, Lookup, ModuleDefId, StaticId, StructId,
TraitId, TypeAliasId, TypeOrConstParamId, TypeParamId, UnionId, VariantId, TraitId, TypeAliasId, TypeOrConstParamId, TypeParamId, UnionId, VariantId,
@ -44,7 +42,7 @@ use syntax::ast;
use crate::{ use crate::{
all_super_traits, 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, db::HirDatabase,
make_binders, make_binders,
mapping::{from_chalk_trait_id, ToChalk}, mapping::{from_chalk_trait_id, ToChalk},
@ -968,7 +966,7 @@ impl<'a> TyLoweringContext<'a> {
// - `Destruct` impls are built-in in 1.62 (current nightlies as of 08-04-2022), so until // - `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. // 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 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; return false;
} }
} }
@ -1919,7 +1917,7 @@ pub(crate) fn generic_arg_to_chalk<'a, T>(
arg: &'a GenericArg, arg: &'a GenericArg,
this: &mut T, this: &mut T,
for_type: impl FnOnce(&mut T, &TypeRef) -> Ty + 'a, 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> { ) -> Option<crate::GenericArg> {
let kind = match kind_id { let kind = match kind_id {
Either::Left(_) => ParamKind::Type, Either::Left(_) => ParamKind::Type,
@ -1947,7 +1945,7 @@ pub(crate) fn generic_arg_to_chalk<'a, T>(
let p = p.mod_path(); let p = p.mod_path();
if p.kind == PathKind::Plain { if p.kind == PathKind::Plain {
if let [n] = p.segments() { if let [n] = p.segments() {
let c = ConstScalarOrPath::Path(n.clone()); let c = ConstRefOrPath::Path(n.clone());
return Some( return Some(
GenericArgData::Const(for_const(this, &c, c_ty)).intern(Interner), GenericArgData::Const(for_const(this, &c, c_ty)).intern(Interner),
); );
@ -1964,14 +1962,14 @@ pub(crate) fn const_or_path_to_chalk(
db: &dyn HirDatabase, db: &dyn HirDatabase,
resolver: &Resolver, resolver: &Resolver,
expected_ty: Ty, expected_ty: Ty,
value: &ConstScalarOrPath, value: &ConstRefOrPath,
mode: ParamLoweringMode, mode: ParamLoweringMode,
args: impl FnOnce() -> Generics, args: impl FnOnce() -> Generics,
debruijn: DebruijnIndex, debruijn: DebruijnIndex,
) -> Const { ) -> Const {
match value { match value {
ConstScalarOrPath::Scalar(s) => intern_const_scalar(*s, expected_ty), ConstRefOrPath::Scalar(s) => intern_const_ref(db, s, expected_ty, resolver.krate()),
ConstScalarOrPath::Path(n) => { ConstRefOrPath::Path(n) => {
let path = ModPath::from_segments(PathKind::Plain, Some(n.clone())); let path = ModPath::from_segments(PathKind::Plain, Some(n.clone()));
path_to_const(db, resolver, &path, mode, args, debruijn) path_to_const(db, resolver, &path, mode, args, debruijn)
.unwrap_or_else(|| unknown_const(expected_ty)) .unwrap_or_else(|| unknown_const(expected_ty))

View file

@ -660,10 +660,10 @@ pub fn lookup_impl_const(
env: Arc<TraitEnvironment>, env: Arc<TraitEnvironment>,
const_id: ConstId, const_id: ConstId,
subs: Substitution, subs: Substitution,
) -> ConstId { ) -> (ConstId, Substitution) {
let trait_id = match const_id.lookup(db.upcast()).container { let trait_id = match const_id.lookup(db.upcast()).container {
ItemContainerId::TraitId(id) => id, ItemContainerId::TraitId(id) => id,
_ => return const_id, _ => return (const_id, subs),
}; };
let substitution = Substitution::from_iter(Interner, subs.iter(Interner)); let substitution = Substitution::from_iter(Interner, subs.iter(Interner));
let trait_ref = TraitRef { trait_id: to_chalk_trait_id(trait_id), substitution }; 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 const_data = db.const_data(const_id);
let name = match const_data.name.as_ref() { let name = match const_data.name.as_ref() {
Some(name) => name, Some(name) => name,
None => return const_id, None => return (const_id, subs),
}; };
lookup_impl_assoc_item_for_trait_ref(trait_ref, db, env, name) 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 }) .and_then(
.unwrap_or(const_id) |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`. /// 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>, env: Arc<TraitEnvironment>,
func: FunctionId, func: FunctionId,
fn_subst: Substitution, fn_subst: Substitution,
) -> FunctionId { ) -> (FunctionId, Substitution) {
let trait_id = match func.lookup(db.upcast()).container { let trait_id = match func.lookup(db.upcast()).container {
ItemContainerId::TraitId(id) => id, ItemContainerId::TraitId(id) => id,
_ => return func, _ => return (func, fn_subst),
}; };
let trait_params = db.generic_params(trait_id.into()).type_or_consts.len(); let trait_params = db.generic_params(trait_id.into()).type_or_consts.len();
let fn_params = fn_subst.len(Interner) - trait_params; 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; let name = &db.function_data(func).name;
lookup_impl_assoc_item_for_trait_ref(trait_ref, db, env, 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 }) .and_then(|assoc| {
.unwrap_or(func) 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( fn lookup_impl_assoc_item_for_trait_ref(
@ -710,7 +718,7 @@ fn lookup_impl_assoc_item_for_trait_ref(
db: &dyn HirDatabase, db: &dyn HirDatabase,
env: Arc<TraitEnvironment>, env: Arc<TraitEnvironment>,
name: &Name, name: &Name,
) -> Option<AssocItemId> { ) -> Option<(AssocItemId, Substitution)> {
let self_ty = trait_ref.self_type_parameter(Interner); let self_ty = trait_ref.self_type_parameter(Interner);
let self_ty_fp = TyFingerprint::for_trait_impl(&self_ty)?; let self_ty_fp = TyFingerprint::for_trait_impl(&self_ty)?;
let impls = db.trait_impls_in_deps(env.krate); 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 table = InferenceTable::new(db, env);
let impl_data = find_matching_impl(impls, table, trait_ref)?; let (impl_data, impl_subst) = find_matching_impl(impls, table, trait_ref)?;
impl_data.items.iter().find_map(|&it| match it { let item = impl_data.items.iter().find_map(|&it| match it {
AssocItemId::FunctionId(f) => { AssocItemId::FunctionId(f) => {
(db.function_data(f).name == *name).then_some(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) .map(|n| n == name)
.and_then(|result| if result { Some(AssocItemId::ConstId(c)) } else { None }), .and_then(|result| if result { Some(AssocItemId::ConstId(c)) } else { None }),
AssocItemId::TypeAliasId(_) => None, AssocItemId::TypeAliasId(_) => None,
}) })?;
Some((item, impl_subst))
} }
fn find_matching_impl( fn find_matching_impl(
mut impls: impl Iterator<Item = ImplId>, mut impls: impl Iterator<Item = ImplId>,
mut table: InferenceTable<'_>, mut table: InferenceTable<'_>,
actual_trait_ref: TraitRef, actual_trait_ref: TraitRef,
) -> Option<Arc<ImplData>> { ) -> Option<(Arc<ImplData>, Substitution)> {
let db = table.db; let db = table.db;
loop { loop {
let impl_ = impls.next()?; let impl_ = impls.next()?;
@ -758,7 +767,7 @@ fn find_matching_impl(
.into_iter() .into_iter()
.map(|b| b.cast(Interner)); .map(|b| b.cast(Interner));
let goal = crate::Goal::all(Interner, wcs); 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() { if r.is_some() {
break r; break r;

811
crates/hir-ty/src/mir.rs Normal file
View file

@ -0,0 +1,811 @@
//! MIR definitions and implementation
use std::iter;
use crate::{
infer::PointerCast, Const, ConstScalar, InferenceResult, Interner, MemoryMap, Substitution, Ty,
};
use chalk_ir::Mutability;
use hir_def::{
expr::{Expr, Ordering},
DefWithBodyId, FieldId, UnionId, VariantId,
};
use la_arena::{Arena, Idx, RawIdx};
mod eval;
mod lower;
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 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 mutability: Mutability,
//pub local_info: Option<Box<LocalInfo>>,
//pub internal: bool,
//pub is_block_tail: Option<BlockTailInfo>,
pub ty: Ty,
//pub user_ty: Option<Box<UserTypeProjections>>,
//pub source_info: SourceInfo,
}
/// 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)
}
}
#[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 weve 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))
}
/// 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 thats 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 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),
/// * `Offset` has the same semantics as [`offset`](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 Statement {
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,
}
#[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,
}
impl MirBody {}
fn const_as_usize(c: &Const) -> usize {
try_const_usize(c).unwrap() as usize
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -50,7 +50,6 @@ use hir_def::{
per_ns::PerNs, per_ns::PerNs,
resolver::{HasResolver, Resolver}, resolver::{HasResolver, Resolver},
src::HasSource as _, src::HasSource as _,
type_ref::ConstScalar,
AdtId, AssocItemId, AssocItemLoc, AttrDefId, ConstId, ConstParamId, DefWithBodyId, EnumId, AdtId, AssocItemId, AssocItemLoc, AttrDefId, ConstId, ConstParamId, DefWithBodyId, EnumId,
EnumVariantId, FunctionId, GenericDefId, HasModule, ImplId, ItemContainerId, LifetimeParamId, EnumVariantId, FunctionId, GenericDefId, HasModule, ImplId, ItemContainerId, LifetimeParamId,
LocalEnumVariantId, LocalFieldId, Lookup, MacroExpander, MacroId, ModuleId, StaticId, StructId, LocalEnumVariantId, LocalFieldId, Lookup, MacroExpander, MacroId, ModuleId, StaticId, StructId,
@ -59,16 +58,16 @@ use hir_def::{
use hir_expand::{name::name, MacroCallKind}; use hir_expand::{name::name, MacroCallKind};
use hir_ty::{ use hir_ty::{
all_super_traits, autoderef, all_super_traits, autoderef,
consteval::{unknown_const_as_generic, ComputedExpr, ConstEvalError, ConstExt}, consteval::{try_const_usize, unknown_const_as_generic, ConstEvalError, ConstExt},
diagnostics::BodyValidationDiagnostic, diagnostics::BodyValidationDiagnostic,
layout::layout_of_ty, layout::layout_of_ty,
method_resolution::{self, TyFingerprint}, method_resolution::{self, TyFingerprint},
mir::interpret_mir,
primitive::UintTy, primitive::UintTy,
traits::FnTrait, traits::FnTrait,
AliasTy, CallableDefId, CallableSig, Canonical, CanonicalVarKinds, Cast, ClosureId, AliasTy, CallableDefId, CallableSig, Canonical, CanonicalVarKinds, Cast, ClosureId,
ConcreteConst, ConstValue, GenericArgData, Interner, ParamKind, QuantifiedWhereClause, Scalar, GenericArgData, Interner, ParamKind, QuantifiedWhereClause, Scalar, Substitution,
Substitution, TraitEnvironment, TraitRefExt, Ty, TyBuilder, TyDefId, TyExt, TyKind, TraitEnvironment, TraitRefExt, Ty, TyBuilder, TyDefId, TyExt, TyKind, WhereClause,
WhereClause,
}; };
use itertools::Itertools; use itertools::Itertools;
use nameres::diagnostics::DefDiagnosticKind; use nameres::diagnostics::DefDiagnosticKind;
@ -130,6 +129,7 @@ pub use {
}, },
hir_ty::{ hir_ty::{
display::{HirDisplay, HirDisplayError, HirWrite}, display::{HirDisplay, HirDisplayError, HirWrite},
mir::MirEvalError,
PointerCast, Safety, PointerCast, Safety,
}, },
}; };
@ -1092,8 +1092,8 @@ impl Variant {
self.source(db)?.value.expr() self.source(db)?.value.expr()
} }
pub fn eval(self, db: &dyn HirDatabase) -> Result<ComputedExpr, ConstEvalError> { pub fn eval(self, db: &dyn HirDatabase) -> Result<i128, ConstEvalError> {
db.const_eval_variant(self.into()) db.const_eval_discriminant(self.into())
} }
} }
@ -1639,6 +1639,14 @@ impl Function {
let def_map = db.crate_def_map(loc.krate(db).into()); let def_map = db.crate_def_map(loc.krate(db).into());
def_map.fn_as_proc_macro(self.id).map(|id| Macro { id: id.into() }) def_map.fn_as_proc_macro(self.id).map(|id| Macro { id: id.into() })
} }
pub fn eval(self, db: &dyn HirDatabase) -> Result<(), MirEvalError> {
let body = db
.mir_body(self.id.into())
.map_err(|e| MirEvalError::MirLowerError(self.id.into(), e))?;
interpret_mir(db, &body, false)?;
Ok(())
}
} }
// Note: logically, this belongs to `hir_ty`, but we are not using it there yet. // Note: logically, this belongs to `hir_ty`, but we are not using it there yet.
@ -1781,7 +1789,7 @@ impl Const {
Type::new_with_resolver_inner(db, &resolver, ty) Type::new_with_resolver_inner(db, &resolver, ty)
} }
pub fn eval(self, db: &dyn HirDatabase) -> Result<ComputedExpr, ConstEvalError> { pub fn eval(self, db: &dyn HirDatabase) -> Result<hir_ty::Const, ConstEvalError> {
db.const_eval(self.id) db.const_eval(self.id)
} }
} }
@ -3268,12 +3276,7 @@ impl Type {
pub fn as_array(&self, _db: &dyn HirDatabase) -> Option<(Type, usize)> { pub fn as_array(&self, _db: &dyn HirDatabase) -> Option<(Type, usize)> {
if let TyKind::Array(ty, len) = &self.ty.kind(Interner) { if let TyKind::Array(ty, len) = &self.ty.kind(Interner) {
match len.data(Interner).value { try_const_usize(len).map(|x| (self.derived(ty.clone()), x as usize))
ConstValue::Concrete(ConcreteConst { interned: ConstScalar::UInt(len) }) => {
Some((self.derived(ty.clone()), len as usize))
}
_ => None,
}
} else { } else {
None None
} }

View file

@ -791,7 +791,7 @@ impl SourceAnalyzer {
|| Arc::new(hir_ty::TraitEnvironment::empty(krate)), || Arc::new(hir_ty::TraitEnvironment::empty(krate)),
|d| db.trait_environment(d), |d| db.trait_environment(d),
); );
method_resolution::lookup_impl_method(db, env, func, substs) method_resolution::lookup_impl_method(db, env, func, substs).0
} }
fn resolve_impl_const_or_trait_def( fn resolve_impl_const_or_trait_def(
@ -809,7 +809,7 @@ impl SourceAnalyzer {
|| Arc::new(hir_ty::TraitEnvironment::empty(krate)), || Arc::new(hir_ty::TraitEnvironment::empty(krate)),
|d| db.trait_environment(d), |d| db.trait_environment(d),
); );
method_resolution::lookup_impl_const(db, env, const_id, subs) method_resolution::lookup_impl_const(db, env, const_id, subs).0
} }
fn lang_trait_fn( fn lang_trait_fn(

View file

@ -211,10 +211,8 @@ fn main() {
check_assist_not_applicable( check_assist_not_applicable(
add_explicit_type, add_explicit_type,
r#" r#"
//- minicore: option
fn main() { fn main() {
let $0l = [0.0; Some(2).unwrap()]; let $0l = [0.0; unresolved_function(5)];
} }
"#, "#,
); );

View file

@ -30,6 +30,7 @@ pub struct HoverConfig {
pub documentation: bool, pub documentation: bool,
pub keywords: bool, pub keywords: bool,
pub format: HoverDocFormat, pub format: HoverDocFormat,
pub interpret_tests: bool,
} }
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]

View file

@ -3,7 +3,8 @@ use std::fmt::Display;
use either::Either; use either::Either;
use hir::{ use hir::{
Adt, AsAssocItem, AttributeTemplate, HasAttrs, HasSource, HirDisplay, Semantics, TypeInfo, db::DefDatabase, Adt, AsAssocItem, AttributeTemplate, HasAttrs, HasSource, HirDisplay,
MirEvalError, Semantics, TypeInfo,
}; };
use ide_db::{ use ide_db::{
base_db::SourceDatabase, base_db::SourceDatabase,
@ -402,7 +403,20 @@ pub(super) fn definition(
)) ))
}), }),
Definition::Module(it) => label_and_docs(db, it), Definition::Module(it) => label_and_docs(db, it),
Definition::Function(it) => label_and_docs(db, it), Definition::Function(it) => label_and_layout_info_and_docs(db, it, |_| {
if !config.interpret_tests {
return None;
}
match it.eval(db) {
Ok(()) => Some("pass".into()),
Err(MirEvalError::Panic) => Some("fail".into()),
Err(MirEvalError::MirLowerError(f, e)) => {
let name = &db.function_data(f).name;
Some(format!("error: fail to lower {name} due {e:?}"))
}
Err(e) => Some(format!("error: {e:?}")),
}
}),
Definition::Adt(it) => label_and_layout_info_and_docs(db, it, |&it| { Definition::Adt(it) => label_and_layout_info_and_docs(db, it, |&it| {
let layout = it.layout(db).ok()?; let layout = it.layout(db).ok()?;
Some(format!("size = {}, align = {}", layout.size.bytes(), layout.align.abi.bytes())) Some(format!("size = {}, align = {}", layout.size.bytes(), layout.align.abi.bytes()))
@ -410,7 +424,7 @@ pub(super) fn definition(
Definition::Variant(it) => label_value_and_docs(db, it, |&it| { Definition::Variant(it) => label_value_and_docs(db, it, |&it| {
if !it.parent_enum(db).is_data_carrying(db) { if !it.parent_enum(db).is_data_carrying(db) {
match it.eval(db) { match it.eval(db) {
Ok(x) => Some(format!("{x}")), Ok(x) => Some(if x >= 10 { format!("{x} ({x:#X})") } else { format!("{x}") }),
Err(_) => it.value(db).map(|x| format!("{x:?}")), Err(_) => it.value(db).map(|x| format!("{x:?}")),
} }
} else { } else {
@ -420,7 +434,7 @@ pub(super) fn definition(
Definition::Const(it) => label_value_and_docs(db, it, |it| { Definition::Const(it) => label_value_and_docs(db, it, |it| {
let body = it.eval(db); let body = it.eval(db);
match body { match body {
Ok(x) => Some(format!("{x}")), Ok(x) => Some(format!("{}", x.display(db))),
Err(_) => { Err(_) => {
let source = it.source(db)?; let source = it.source(db)?;
let mut body = source.value.body()?.syntax().clone(); let mut body = source.value.body()?.syntax().clone();

View file

@ -4,16 +4,19 @@ use syntax::TextRange;
use crate::{fixture, HoverConfig, HoverDocFormat}; use crate::{fixture, HoverConfig, HoverDocFormat};
const HOVER_BASE_CONFIG: HoverConfig = HoverConfig {
links_in_hover: false,
documentation: true,
format: HoverDocFormat::Markdown,
keywords: true,
interpret_tests: false,
};
fn check_hover_no_result(ra_fixture: &str) { fn check_hover_no_result(ra_fixture: &str) {
let (analysis, position) = fixture::position(ra_fixture); let (analysis, position) = fixture::position(ra_fixture);
let hover = analysis let hover = analysis
.hover( .hover(
&HoverConfig { &HoverConfig { links_in_hover: true, ..HOVER_BASE_CONFIG },
links_in_hover: true,
documentation: true,
keywords: true,
format: HoverDocFormat::Markdown,
},
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) }, FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
) )
.unwrap(); .unwrap();
@ -25,12 +28,7 @@ fn check(ra_fixture: &str, expect: Expect) {
let (analysis, position) = fixture::position(ra_fixture); let (analysis, position) = fixture::position(ra_fixture);
let hover = analysis let hover = analysis
.hover( .hover(
&HoverConfig { &HoverConfig { links_in_hover: true, ..HOVER_BASE_CONFIG },
links_in_hover: true,
documentation: true,
keywords: true,
format: HoverDocFormat::Markdown,
},
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) }, FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
) )
.unwrap() .unwrap()
@ -47,12 +45,7 @@ fn check_hover_no_links(ra_fixture: &str, expect: Expect) {
let (analysis, position) = fixture::position(ra_fixture); let (analysis, position) = fixture::position(ra_fixture);
let hover = analysis let hover = analysis
.hover( .hover(
&HoverConfig { &HOVER_BASE_CONFIG,
links_in_hover: false,
documentation: true,
keywords: true,
format: HoverDocFormat::Markdown,
},
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) }, FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
) )
.unwrap() .unwrap()
@ -71,9 +64,8 @@ fn check_hover_no_markdown(ra_fixture: &str, expect: Expect) {
.hover( .hover(
&HoverConfig { &HoverConfig {
links_in_hover: true, links_in_hover: true,
documentation: true,
keywords: true,
format: HoverDocFormat::PlainText, format: HoverDocFormat::PlainText,
..HOVER_BASE_CONFIG
}, },
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) }, FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
) )
@ -91,12 +83,7 @@ fn check_actions(ra_fixture: &str, expect: Expect) {
let (analysis, file_id, position) = fixture::range_or_position(ra_fixture); let (analysis, file_id, position) = fixture::range_or_position(ra_fixture);
let hover = analysis let hover = analysis
.hover( .hover(
&HoverConfig { &HoverConfig { links_in_hover: true, ..HOVER_BASE_CONFIG },
links_in_hover: true,
documentation: true,
keywords: true,
format: HoverDocFormat::Markdown,
},
FileRange { file_id, range: position.range_or_empty() }, FileRange { file_id, range: position.range_or_empty() },
) )
.unwrap() .unwrap()
@ -106,34 +93,13 @@ fn check_actions(ra_fixture: &str, expect: Expect) {
fn check_hover_range(ra_fixture: &str, expect: Expect) { fn check_hover_range(ra_fixture: &str, expect: Expect) {
let (analysis, range) = fixture::range(ra_fixture); let (analysis, range) = fixture::range(ra_fixture);
let hover = analysis let hover = analysis.hover(&HOVER_BASE_CONFIG, range).unwrap().unwrap();
.hover(
&HoverConfig {
links_in_hover: false,
documentation: true,
keywords: true,
format: HoverDocFormat::Markdown,
},
range,
)
.unwrap()
.unwrap();
expect.assert_eq(hover.info.markup.as_str()) expect.assert_eq(hover.info.markup.as_str())
} }
fn check_hover_range_no_results(ra_fixture: &str) { fn check_hover_range_no_results(ra_fixture: &str) {
let (analysis, range) = fixture::range(ra_fixture); let (analysis, range) = fixture::range(ra_fixture);
let hover = analysis let hover = analysis.hover(&HOVER_BASE_CONFIG, range).unwrap();
.hover(
&HoverConfig {
links_in_hover: false,
documentation: true,
keywords: true,
format: HoverDocFormat::Markdown,
},
range,
)
.unwrap();
assert!(hover.is_none()); assert!(hover.is_none());
} }
@ -490,7 +456,6 @@ fn hover_field_offset() {
// Hovering over the field when instantiating // Hovering over the field when instantiating
check( check(
r#" r#"
//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128
struct Foo { fiel$0d_a: u8, field_b: i32, field_c: i16 } struct Foo { fiel$0d_a: u8, field_b: i32, field_c: i16 }
"#, "#,
expect![[r#" expect![[r#"
@ -512,7 +477,6 @@ fn hover_shows_struct_field_info() {
// Hovering over the field when instantiating // Hovering over the field when instantiating
check( check(
r#" r#"
//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128
struct Foo { field_a: u32 } struct Foo { field_a: u32 }
fn main() { fn main() {
@ -535,7 +499,6 @@ fn main() {
// Hovering over the field in the definition // Hovering over the field in the definition
check( check(
r#" r#"
//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128
struct Foo { field_a$0: u32 } struct Foo { field_a$0: u32 }
fn main() { fn main() {
@ -568,7 +531,7 @@ fn hover_const_static() {
``` ```
```rust ```rust
const foo: u32 = 123 (0x7B) const foo: u32 = 123
``` ```
"#]], "#]],
); );
@ -1467,8 +1430,6 @@ fn my() {}
fn test_hover_struct_doc_comment() { fn test_hover_struct_doc_comment() {
check( check(
r#" r#"
//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128
/// This is an example /// This is an example
/// multiline doc /// multiline doc
/// ///
@ -1527,7 +1488,7 @@ fn foo() { let bar = Ba$0r; }
``` ```
```rust ```rust
struct Bar struct Bar // size = 0, align = 1
``` ```
--- ---
@ -1556,7 +1517,7 @@ fn foo() { let bar = Ba$0r; }
``` ```
```rust ```rust
struct Bar struct Bar // size = 0, align = 1
``` ```
--- ---
@ -1584,7 +1545,7 @@ pub struct B$0ar
``` ```
```rust ```rust
pub struct Bar pub struct Bar // size = 0, align = 1
``` ```
--- ---
@ -1611,7 +1572,7 @@ pub struct B$0ar
``` ```
```rust ```rust
pub struct Bar pub struct Bar // size = 0, align = 1
``` ```
--- ---
@ -2913,8 +2874,6 @@ fn main() { let foo_test = name_with_dashes::wrapper::Thing::new$0(); }
fn hover_field_pat_shorthand_ref_match_ergonomics() { fn hover_field_pat_shorthand_ref_match_ergonomics() {
check( check(
r#" r#"
//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128
struct S { struct S {
f: i32, f: i32,
} }
@ -3506,8 +3465,8 @@ impl<const LEN: usize> Foo<LEN$0> {}
} }
#[test] #[test]
fn hover_const_eval_variant() { fn hover_const_eval_discriminant() {
// show hex for <10 // Don't show hex for <10
check( check(
r#" r#"
#[repr(u8)] #[repr(u8)]
@ -3532,7 +3491,7 @@ enum E {
This is a doc This is a doc
"#]], "#]],
); );
// show hex for >10 // Show hex for >10
check( check(
r#" r#"
#[repr(u8)] #[repr(u8)]
@ -3656,7 +3615,7 @@ trait T {
} }
impl T for i32 { impl T for i32 {
const AA: A = A { const AA: A = A {
i: 2 i: 2 + 3
} }
} }
fn main() { fn main() {
@ -3671,9 +3630,7 @@ fn main() {
``` ```
```rust ```rust
const AA: A = A { const AA: A = A { i: 5 }
i: 2
}
``` ```
"#]], "#]],
); );
@ -3792,7 +3749,7 @@ const FOO$0: usize = 1 << 3;
This is a doc This is a doc
"#]], "#]],
); );
// show hex for >10 // FIXME: show hex for >10
check( check(
r#" r#"
/// This is a doc /// This is a doc
@ -3806,7 +3763,7 @@ const FOO$0: usize = (1 << 3) + (1 << 2);
``` ```
```rust ```rust
const FOO: usize = 12 (0xC) const FOO: usize = 12
``` ```
--- ---
@ -3937,7 +3894,7 @@ const FOO$0: u8 = b'a';
``` ```
```rust ```rust
const FOO: u8 = 97 (0x61) const FOO: u8 = 97
``` ```
--- ---
@ -3959,7 +3916,7 @@ const FOO$0: u8 = b'\x61';
``` ```
```rust ```rust
const FOO: u8 = 97 (0x61) const FOO: u8 = 97
``` ```
--- ---
@ -4354,8 +4311,6 @@ fn main() {
fn hover_intra_doc_links() { fn hover_intra_doc_links() {
check( check(
r#" r#"
//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128
pub mod theitem { pub mod theitem {
/// This is the item. Cool! /// This is the item. Cool!
pub struct TheItem; pub struct TheItem;
@ -4496,7 +4451,7 @@ trait A where
fn string_shadowed_with_inner_items() { fn string_shadowed_with_inner_items() {
check( check(
r#" r#"
//- /main.rs crate:main deps:alloc target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128 //- /main.rs crate:main deps:alloc
/// Custom `String` type. /// Custom `String` type.
struct String; struct String;
@ -5191,7 +5146,7 @@ foo_macro!(
``` ```
```rust ```rust
pub struct Foo pub struct Foo // size = 0, align = 1
``` ```
--- ---
@ -5205,8 +5160,6 @@ foo_macro!(
fn hover_intra_in_attr() { fn hover_intra_in_attr() {
check( check(
r#" r#"
//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128
#[doc = "Doc comment for [`Foo$0`]"] #[doc = "Doc comment for [`Foo$0`]"]
pub struct Foo(i32); pub struct Foo(i32);
"#, "#,
@ -5295,7 +5248,7 @@ pub struct Type;
``` ```
```rust ```rust
const KONST: dep::Type = $crate::Type const KONST: dep::Type = Type
``` ```
"#]], "#]],
); );
@ -5327,8 +5280,6 @@ enum Enum {
fn hover_record_variant_field() { fn hover_record_variant_field() {
check( check(
r#" r#"
//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128
enum Enum { enum Enum {
RecordV { field$0: u32 } RecordV { field$0: u32 }
} }

View file

@ -59,8 +59,14 @@ fn variant_hints(
}, },
kind: InlayKind::Discriminant, kind: InlayKind::Discriminant,
label: InlayHintLabel::simple( label: InlayHintLabel::simple(
match &d { match d {
Ok(v) => format!("{}", v), Ok(x) => {
if x >= 10 {
format!("{x} ({x:#X})")
} else {
format!("{x}")
}
}
Err(_) => "?".into(), Err(_) => "?".into(),
}, },
Some(InlayTooltip::String(match &d { Some(InlayTooltip::String(match &d {

View file

@ -139,6 +139,7 @@ impl StaticIndex<'_> {
documentation: true, documentation: true,
keywords: true, keywords: true,
format: crate::HoverDocFormat::Markdown, format: crate::HoverDocFormat::Markdown,
interpret_tests: false,
}; };
let tokens = tokens.filter(|token| { let tokens = tokens.filter(|token| {
matches!( matches!(

View file

@ -366,6 +366,8 @@ config_data! {
inlayHints_typeHints_hideClosureInitialization: bool = "false", inlayHints_typeHints_hideClosureInitialization: bool = "false",
/// Whether to hide inlay type hints for constructors. /// Whether to hide inlay type hints for constructors.
inlayHints_typeHints_hideNamedConstructor: bool = "false", inlayHints_typeHints_hideNamedConstructor: bool = "false",
/// Enables the experimental support for interpreting tests.
interpret_tests: bool = "false",
/// Join lines merges consecutive declaration and initialization of an assignment. /// Join lines merges consecutive declaration and initialization of an assignment.
joinLines_joinAssignments: bool = "true", joinLines_joinAssignments: bool = "true",
@ -1444,6 +1446,7 @@ impl Config {
} }
}, },
keywords: self.data.hover_documentation_keywords_enable, keywords: self.data.hover_documentation_keywords_enable,
interpret_tests: self.data.interpret_tests,
} }
} }

View file

@ -180,7 +180,9 @@ impl Fixture {
let mut cfg_key_values = Vec::new(); let mut cfg_key_values = Vec::new();
let mut env = FxHashMap::default(); let mut env = FxHashMap::default();
let mut introduce_new_source_root = None; let mut introduce_new_source_root = None;
let mut target_data_layout = None; let mut target_data_layout = Some(
"e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128".to_string(),
);
for component in components[1..].iter() { for component in components[1..].iter() {
let (key, value) = let (key, value) =
component.split_once(':').unwrap_or_else(|| panic!("invalid meta line: {meta:?}")); component.split_once(':').unwrap_or_else(|| panic!("invalid meta line: {meta:?}"));

View file

@ -510,6 +510,7 @@ pub mod fmt {
pub mod slice { pub mod slice {
#[lang = "slice"] #[lang = "slice"]
impl<T> [T] { impl<T> [T] {
#[lang = "slice_len_fn"]
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
loop {} loop {}
} }

View file

@ -537,6 +537,11 @@ Only applies to closures with blocks, same as `#rust-analyzer.inlayHints.closure
-- --
Whether to hide inlay type hints for constructors. Whether to hide inlay type hints for constructors.
-- --
[[rust-analyzer.interpret.tests]]rust-analyzer.interpret.tests (default: `false`)::
+
--
Enables the experimental support for interpreting tests.
--
[[rust-analyzer.joinLines.joinAssignments]]rust-analyzer.joinLines.joinAssignments (default: `true`):: [[rust-analyzer.joinLines.joinAssignments]]rust-analyzer.joinLines.joinAssignments (default: `true`)::
+ +
-- --

View file

@ -1110,6 +1110,11 @@
"default": false, "default": false,
"type": "boolean" "type": "boolean"
}, },
"rust-analyzer.interpret.tests": {
"markdownDescription": "Enables the experimental support for interpreting tests.",
"default": false,
"type": "boolean"
},
"rust-analyzer.joinLines.joinAssignments": { "rust-analyzer.joinLines.joinAssignments": {
"markdownDescription": "Join lines merges consecutive declaration and initialization of an assignment.", "markdownDescription": "Join lines merges consecutive declaration and initialization of an assignment.",
"default": true, "default": true,