mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-27 05:23:24 +00:00
beginning of MIR
This commit is contained in:
parent
b881deb66a
commit
cd67589f63
41 changed files with 4452 additions and 702 deletions
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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('_'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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 {
|
||||||
if let Expr::Path(p) = &ctx.body.exprs[expr] {
|
|
||||||
let db = ctx.db;
|
let db = ctx.db;
|
||||||
|
if let Expr::Path(p) = &ctx.body.exprs[expr] {
|
||||||
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)]
|
||||||
|
|
|
@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>>;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,7 +990,10 @@ 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(
|
||||||
|
self.err_ty(),
|
||||||
|
crate::consteval::usize_const(self.db, None, self.resolver.krate()),
|
||||||
|
)
|
||||||
.intern(Interner),
|
.intern(Interner),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()),
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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};
|
||||||
|
|
|
@ -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)?;
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 hir_def::{expr::ExprId, type_ref::Rawness, TypeOrConstParamId};
|
||||||
use hir_expand::name;
|
use hir_expand::name;
|
||||||
use itertools::Either;
|
use itertools::Either;
|
||||||
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)
|
||||||
|
|
|
@ -26,9 +26,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))
|
||||||
|
|
|
@ -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
811
crates/hir-ty/src/mir.rs
Normal 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 we’ve decided to keep this as-is until we figure a case
|
||||||
|
// where some other approach seems to be strictly better than other.
|
||||||
|
targets: SmallVec<[BasicBlockId; 2]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SwitchTargets {
|
||||||
|
/// Creates switch targets from an iterator of values and target blocks.
|
||||||
|
///
|
||||||
|
/// The iterator may be empty, in which case the `SwitchInt` instruction is equivalent to
|
||||||
|
/// `goto otherwise;`.
|
||||||
|
pub fn new(
|
||||||
|
targets: impl Iterator<Item = (u128, BasicBlockId)>,
|
||||||
|
otherwise: BasicBlockId,
|
||||||
|
) -> Self {
|
||||||
|
let (values, mut targets): (SmallVec<_>, SmallVec<_>) = targets.unzip();
|
||||||
|
targets.push(otherwise);
|
||||||
|
Self { values, targets }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds a switch targets definition that jumps to `then` if the tested value equals `value`,
|
||||||
|
/// and to `else_` if not.
|
||||||
|
pub fn static_if(value: u128, then: BasicBlockId, else_: BasicBlockId) -> Self {
|
||||||
|
Self { values: smallvec![value], targets: smallvec![then, else_] }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the fallback target that is jumped to when none of the values match the operand.
|
||||||
|
pub fn otherwise(&self) -> BasicBlockId {
|
||||||
|
*self.targets.last().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator over the switch targets.
|
||||||
|
///
|
||||||
|
/// The iterator will yield tuples containing the value and corresponding target to jump to, not
|
||||||
|
/// including the `otherwise` fallback target.
|
||||||
|
///
|
||||||
|
/// Note that this may yield 0 elements. Only the `otherwise` branch is mandatory.
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = (u128, BasicBlockId)> + '_ {
|
||||||
|
iter::zip(&self.values, &self.targets).map(|(x, y)| (*x, *y))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finds the `BasicBlock` to which this `SwitchInt` will branch given the
|
||||||
|
/// specific value. This cannot fail, as it'll return the `otherwise`
|
||||||
|
/// branch if there's not a specific match for the value.
|
||||||
|
pub fn target_for_value(&self, value: u128) -> BasicBlockId {
|
||||||
|
self.iter().find_map(|(v, t)| (v == value).then_some(t)).unwrap_or_else(|| self.otherwise())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub enum Terminator {
|
||||||
|
/// Block has one successor; we continue execution there.
|
||||||
|
Goto { target: BasicBlockId },
|
||||||
|
|
||||||
|
/// Switches based on the computed value.
|
||||||
|
///
|
||||||
|
/// First, evaluates the `discr` operand. The type of the operand must be a signed or unsigned
|
||||||
|
/// integer, char, or bool, and must match the given type. Then, if the list of switch targets
|
||||||
|
/// contains the computed value, continues execution at the associated basic block. Otherwise,
|
||||||
|
/// continues execution at the "otherwise" basic block.
|
||||||
|
///
|
||||||
|
/// Target values may not appear more than once.
|
||||||
|
SwitchInt {
|
||||||
|
/// The discriminant value being tested.
|
||||||
|
discr: Operand,
|
||||||
|
|
||||||
|
targets: SwitchTargets,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Indicates that the landing pad is finished and that the process should continue unwinding.
|
||||||
|
///
|
||||||
|
/// Like a return, this marks the end of this invocation of the function.
|
||||||
|
///
|
||||||
|
/// Only permitted in cleanup blocks. `Resume` is not permitted with `-C unwind=abort` after
|
||||||
|
/// deaggregation runs.
|
||||||
|
Resume,
|
||||||
|
|
||||||
|
/// Indicates that the landing pad is finished and that the process should abort.
|
||||||
|
///
|
||||||
|
/// Used to prevent unwinding for foreign items or with `-C unwind=abort`. Only permitted in
|
||||||
|
/// cleanup blocks.
|
||||||
|
Abort,
|
||||||
|
|
||||||
|
/// Returns from the function.
|
||||||
|
///
|
||||||
|
/// Like function calls, the exact semantics of returns in Rust are unclear. Returning very
|
||||||
|
/// likely at least assigns the value currently in the return place (`_0`) to the place
|
||||||
|
/// specified in the associated `Call` terminator in the calling function, as if assigned via
|
||||||
|
/// `dest = move _0`. It might additionally do other things, like have side-effects in the
|
||||||
|
/// aliasing model.
|
||||||
|
///
|
||||||
|
/// If the body is a generator body, this has slightly different semantics; it instead causes a
|
||||||
|
/// `GeneratorState::Returned(_0)` to be created (as if by an `Aggregate` rvalue) and assigned
|
||||||
|
/// to the return place.
|
||||||
|
Return,
|
||||||
|
|
||||||
|
/// Indicates a terminator that can never be reached.
|
||||||
|
///
|
||||||
|
/// Executing this terminator is UB.
|
||||||
|
Unreachable,
|
||||||
|
|
||||||
|
/// The behavior of this statement differs significantly before and after drop elaboration.
|
||||||
|
/// After drop elaboration, `Drop` executes the drop glue for the specified place, after which
|
||||||
|
/// it continues execution/unwinds at the given basic blocks. It is possible that executing drop
|
||||||
|
/// glue is special - this would be part of Rust's memory model. (**FIXME**: due we have an
|
||||||
|
/// issue tracking if drop glue has any interesting semantics in addition to those of a function
|
||||||
|
/// call?)
|
||||||
|
///
|
||||||
|
/// `Drop` before drop elaboration is a *conditional* execution of the drop glue. Specifically, the
|
||||||
|
/// `Drop` will be executed if...
|
||||||
|
///
|
||||||
|
/// **Needs clarification**: End of that sentence. This in effect should document the exact
|
||||||
|
/// behavior of drop elaboration. The following sounds vaguely right, but I'm not quite sure:
|
||||||
|
///
|
||||||
|
/// > The drop glue is executed if, among all statements executed within this `Body`, an assignment to
|
||||||
|
/// > the place or one of its "parents" occurred more recently than a move out of it. This does not
|
||||||
|
/// > consider indirect assignments.
|
||||||
|
Drop { place: Place, target: BasicBlockId, unwind: Option<BasicBlockId> },
|
||||||
|
|
||||||
|
/// Drops the place and assigns a new value to it.
|
||||||
|
///
|
||||||
|
/// This first performs the exact same operation as the pre drop-elaboration `Drop` terminator;
|
||||||
|
/// it then additionally assigns the `value` to the `place` as if by an assignment statement.
|
||||||
|
/// This assignment occurs both in the unwind and the regular code paths. The semantics are best
|
||||||
|
/// explained by the elaboration:
|
||||||
|
///
|
||||||
|
/// ```ignore (MIR)
|
||||||
|
/// BB0 {
|
||||||
|
/// DropAndReplace(P <- V, goto BB1, unwind BB2)
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// becomes
|
||||||
|
///
|
||||||
|
/// ```ignore (MIR)
|
||||||
|
/// BB0 {
|
||||||
|
/// Drop(P, goto BB1, unwind BB2)
|
||||||
|
/// }
|
||||||
|
/// BB1 {
|
||||||
|
/// // P is now uninitialized
|
||||||
|
/// P <- V
|
||||||
|
/// }
|
||||||
|
/// BB2 {
|
||||||
|
/// // P is now uninitialized -- its dtor panicked
|
||||||
|
/// P <- V
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Disallowed after drop elaboration.
|
||||||
|
DropAndReplace {
|
||||||
|
place: Place,
|
||||||
|
value: Operand,
|
||||||
|
target: BasicBlockId,
|
||||||
|
unwind: Option<BasicBlockId>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Roughly speaking, evaluates the `func` operand and the arguments, and starts execution of
|
||||||
|
/// the referred to function. The operand types must match the argument types of the function.
|
||||||
|
/// The return place type must match the return type. The type of the `func` operand must be
|
||||||
|
/// callable, meaning either a function pointer, a function type, or a closure type.
|
||||||
|
///
|
||||||
|
/// **Needs clarification**: The exact semantics of this. Current backends rely on `move`
|
||||||
|
/// operands not aliasing the return place. It is unclear how this is justified in MIR, see
|
||||||
|
/// [#71117].
|
||||||
|
///
|
||||||
|
/// [#71117]: https://github.com/rust-lang/rust/issues/71117
|
||||||
|
Call {
|
||||||
|
/// The function that’s being called.
|
||||||
|
func: Operand,
|
||||||
|
/// Arguments the function is called with.
|
||||||
|
/// These are owned by the callee, which is free to modify them.
|
||||||
|
/// This allows the memory occupied by "by-value" arguments to be
|
||||||
|
/// reused across function calls without duplicating the contents.
|
||||||
|
args: Vec<Operand>,
|
||||||
|
/// Where the returned value will be written
|
||||||
|
destination: Place,
|
||||||
|
/// Where to go after this call returns. If none, the call necessarily diverges.
|
||||||
|
target: Option<BasicBlockId>,
|
||||||
|
/// Cleanups to be done if the call unwinds.
|
||||||
|
cleanup: Option<BasicBlockId>,
|
||||||
|
/// `true` if this is from a call in HIR rather than from an overloaded
|
||||||
|
/// operator. True for overloaded function call.
|
||||||
|
from_hir_call: bool,
|
||||||
|
// This `Span` is the span of the function, without the dot and receiver
|
||||||
|
// (e.g. `foo(a, b)` in `x.foo(a, b)`
|
||||||
|
//fn_span: Span,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Evaluates the operand, which must have type `bool`. If it is not equal to `expected`,
|
||||||
|
/// initiates a panic. Initiating a panic corresponds to a `Call` terminator with some
|
||||||
|
/// unspecified constant as the function to call, all the operands stored in the `AssertMessage`
|
||||||
|
/// as parameters, and `None` for the destination. Keep in mind that the `cleanup` path is not
|
||||||
|
/// necessarily executed even in the case of a panic, for example in `-C panic=abort`. If the
|
||||||
|
/// assertion does not fail, execution continues at the specified basic block.
|
||||||
|
Assert {
|
||||||
|
cond: Operand,
|
||||||
|
expected: bool,
|
||||||
|
//msg: AssertMessage,
|
||||||
|
target: BasicBlockId,
|
||||||
|
cleanup: Option<BasicBlockId>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Marks a suspend point.
|
||||||
|
///
|
||||||
|
/// Like `Return` terminators in generator bodies, this computes `value` and then a
|
||||||
|
/// `GeneratorState::Yielded(value)` as if by `Aggregate` rvalue. That value is then assigned to
|
||||||
|
/// the return place of the function calling this one, and execution continues in the calling
|
||||||
|
/// function. When next invoked with the same first argument, execution of this function
|
||||||
|
/// continues at the `resume` basic block, with the second argument written to the `resume_arg`
|
||||||
|
/// place. If the generator is dropped before then, the `drop` basic block is invoked.
|
||||||
|
///
|
||||||
|
/// Not permitted in bodies that are not generator bodies, or after generator lowering.
|
||||||
|
///
|
||||||
|
/// **Needs clarification**: What about the evaluation order of the `resume_arg` and `value`?
|
||||||
|
Yield {
|
||||||
|
/// The value to return.
|
||||||
|
value: Operand,
|
||||||
|
/// Where to resume to.
|
||||||
|
resume: BasicBlockId,
|
||||||
|
/// The place to store the resume argument in.
|
||||||
|
resume_arg: Place,
|
||||||
|
/// Cleanup to be done if the generator is dropped at this suspend point.
|
||||||
|
drop: Option<BasicBlockId>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Indicates the end of dropping a generator.
|
||||||
|
///
|
||||||
|
/// Semantically just a `return` (from the generators drop glue). Only permitted in the same situations
|
||||||
|
/// as `yield`.
|
||||||
|
///
|
||||||
|
/// **Needs clarification**: Is that even correct? The generator drop code is always confusing
|
||||||
|
/// to me, because it's not even really in the current body.
|
||||||
|
///
|
||||||
|
/// **Needs clarification**: Are there type system constraints on these terminators? Should
|
||||||
|
/// there be a "block type" like `cleanup` blocks for them?
|
||||||
|
GeneratorDrop,
|
||||||
|
|
||||||
|
/// A block where control flow only ever takes one real path, but borrowck needs to be more
|
||||||
|
/// conservative.
|
||||||
|
///
|
||||||
|
/// At runtime this is semantically just a goto.
|
||||||
|
///
|
||||||
|
/// Disallowed after drop elaboration.
|
||||||
|
FalseEdge {
|
||||||
|
/// The target normal control flow will take.
|
||||||
|
real_target: BasicBlockId,
|
||||||
|
/// A block control flow could conceptually jump to, but won't in
|
||||||
|
/// practice.
|
||||||
|
imaginary_target: BasicBlockId,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// A terminator for blocks that only take one path in reality, but where we reserve the right
|
||||||
|
/// to unwind in borrowck, even if it won't happen in practice. This can arise in infinite loops
|
||||||
|
/// with no function calls for example.
|
||||||
|
///
|
||||||
|
/// At runtime this is semantically just a goto.
|
||||||
|
///
|
||||||
|
/// Disallowed after drop elaboration.
|
||||||
|
FalseUnwind {
|
||||||
|
/// The target normal control flow will take.
|
||||||
|
real_target: BasicBlockId,
|
||||||
|
/// The imaginary cleanup block link. This particular path will never be taken
|
||||||
|
/// in practice, but in order to avoid fragility we want to always
|
||||||
|
/// consider it in borrowck. We don't want to accept programs which
|
||||||
|
/// pass borrowck only when `panic=abort` or some assertions are disabled
|
||||||
|
/// due to release vs. debug mode builds. This needs to be an `Option` because
|
||||||
|
/// of the `remove_noop_landing_pads` and `abort_unwinding_calls` passes.
|
||||||
|
unwind: Option<BasicBlockId>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub enum BorrowKind {
|
||||||
|
/// Data must be immutable and is aliasable.
|
||||||
|
Shared,
|
||||||
|
|
||||||
|
/// The immediately borrowed place must be immutable, but projections from
|
||||||
|
/// it don't need to be. For example, a shallow borrow of `a.b` doesn't
|
||||||
|
/// conflict with a mutable borrow of `a.b.c`.
|
||||||
|
///
|
||||||
|
/// This is used when lowering matches: when matching on a place we want to
|
||||||
|
/// ensure that place have the same value from the start of the match until
|
||||||
|
/// an arm is selected. This prevents this code from compiling:
|
||||||
|
/// ```compile_fail,E0510
|
||||||
|
/// let mut x = &Some(0);
|
||||||
|
/// match *x {
|
||||||
|
/// None => (),
|
||||||
|
/// Some(_) if { x = &None; false } => (),
|
||||||
|
/// Some(_) => (),
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
/// This can't be a shared borrow because mutably borrowing (*x as Some).0
|
||||||
|
/// should not prevent `if let None = x { ... }`, for example, because the
|
||||||
|
/// mutating `(*x as Some).0` can't affect the discriminant of `x`.
|
||||||
|
/// We can also report errors with this kind of borrow differently.
|
||||||
|
Shallow,
|
||||||
|
|
||||||
|
/// Data must be immutable but not aliasable. This kind of borrow
|
||||||
|
/// cannot currently be expressed by the user and is used only in
|
||||||
|
/// implicit closure bindings. It is needed when the closure is
|
||||||
|
/// borrowing or mutating a mutable referent, e.g.:
|
||||||
|
/// ```
|
||||||
|
/// let mut z = 3;
|
||||||
|
/// let x: &mut isize = &mut z;
|
||||||
|
/// let y = || *x += 5;
|
||||||
|
/// ```
|
||||||
|
/// If we were to try to translate this closure into a more explicit
|
||||||
|
/// form, we'd encounter an error with the code as written:
|
||||||
|
/// ```compile_fail,E0594
|
||||||
|
/// struct Env<'a> { x: &'a &'a mut isize }
|
||||||
|
/// let mut z = 3;
|
||||||
|
/// let x: &mut isize = &mut z;
|
||||||
|
/// let y = (&mut Env { x: &x }, fn_ptr); // Closure is pair of env and fn
|
||||||
|
/// fn fn_ptr(env: &mut Env) { **env.x += 5; }
|
||||||
|
/// ```
|
||||||
|
/// This is then illegal because you cannot mutate an `&mut` found
|
||||||
|
/// in an aliasable location. To solve, you'd have to translate with
|
||||||
|
/// an `&mut` borrow:
|
||||||
|
/// ```compile_fail,E0596
|
||||||
|
/// struct Env<'a> { x: &'a mut &'a mut isize }
|
||||||
|
/// let mut z = 3;
|
||||||
|
/// let x: &mut isize = &mut z;
|
||||||
|
/// let y = (&mut Env { x: &mut x }, fn_ptr); // changed from &x to &mut x
|
||||||
|
/// fn fn_ptr(env: &mut Env) { **env.x += 5; }
|
||||||
|
/// ```
|
||||||
|
/// Now the assignment to `**env.x` is legal, but creating a
|
||||||
|
/// mutable pointer to `x` is not because `x` is not mutable. We
|
||||||
|
/// could fix this by declaring `x` as `let mut x`. This is ok in
|
||||||
|
/// user code, if awkward, but extra weird for closures, since the
|
||||||
|
/// borrow is hidden.
|
||||||
|
///
|
||||||
|
/// So we introduce a "unique imm" borrow -- the referent is
|
||||||
|
/// immutable, but not aliasable. This solves the problem. For
|
||||||
|
/// simplicity, we don't give users the way to express this
|
||||||
|
/// borrow, it's just used when translating closures.
|
||||||
|
Unique,
|
||||||
|
|
||||||
|
/// Data is mutable and not aliasable.
|
||||||
|
Mut {
|
||||||
|
/// `true` if this borrow arose from method-call auto-ref
|
||||||
|
/// (i.e., `adjustment::Adjust::Borrow`).
|
||||||
|
allow_two_phase_borrow: bool,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BorrowKind {
|
||||||
|
fn from_hir(m: hir_def::type_ref::Mutability) -> Self {
|
||||||
|
match m {
|
||||||
|
hir_def::type_ref::Mutability::Shared => BorrowKind::Shared,
|
||||||
|
hir_def::type_ref::Mutability::Mut => BorrowKind::Mut { allow_two_phase_borrow: false },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_chalk(m: Mutability) -> Self {
|
||||||
|
match m {
|
||||||
|
Mutability::Not => BorrowKind::Shared,
|
||||||
|
Mutability::Mut => BorrowKind::Mut { allow_two_phase_borrow: false },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub enum UnOp {
|
||||||
|
/// The `!` operator for logical inversion
|
||||||
|
Not,
|
||||||
|
/// The `-` operator for negation
|
||||||
|
Neg,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub enum BinOp {
|
||||||
|
/// The `+` operator (addition)
|
||||||
|
Add,
|
||||||
|
/// The `-` operator (subtraction)
|
||||||
|
Sub,
|
||||||
|
/// The `*` operator (multiplication)
|
||||||
|
Mul,
|
||||||
|
/// The `/` operator (division)
|
||||||
|
///
|
||||||
|
/// Division by zero is UB, because the compiler should have inserted checks
|
||||||
|
/// prior to this.
|
||||||
|
Div,
|
||||||
|
/// The `%` operator (modulus)
|
||||||
|
///
|
||||||
|
/// Using zero as the modulus (second operand) is UB, because the compiler
|
||||||
|
/// should have inserted checks prior to this.
|
||||||
|
Rem,
|
||||||
|
/// The `^` operator (bitwise xor)
|
||||||
|
BitXor,
|
||||||
|
/// The `&` operator (bitwise and)
|
||||||
|
BitAnd,
|
||||||
|
/// The `|` operator (bitwise or)
|
||||||
|
BitOr,
|
||||||
|
/// The `<<` operator (shift left)
|
||||||
|
///
|
||||||
|
/// The offset is truncated to the size of the first operand before shifting.
|
||||||
|
Shl,
|
||||||
|
/// The `>>` operator (shift right)
|
||||||
|
///
|
||||||
|
/// The offset is truncated to the size of the first operand before shifting.
|
||||||
|
Shr,
|
||||||
|
/// The `==` operator (equality)
|
||||||
|
Eq,
|
||||||
|
/// The `<` operator (less than)
|
||||||
|
Lt,
|
||||||
|
/// The `<=` operator (less than or equal to)
|
||||||
|
Le,
|
||||||
|
/// The `!=` operator (not equal to)
|
||||||
|
Ne,
|
||||||
|
/// The `>=` operator (greater than or equal to)
|
||||||
|
Ge,
|
||||||
|
/// The `>` operator (greater than)
|
||||||
|
Gt,
|
||||||
|
/// The `ptr.offset` operator
|
||||||
|
Offset,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl 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
|
||||||
|
}
|
1245
crates/hir-ty/src/mir/eval.rs
Normal file
1245
crates/hir-ty/src/mir/eval.rs
Normal file
File diff suppressed because it is too large
Load diff
1209
crates/hir-ty/src/mir/lower.rs
Normal file
1209
crates/hir-ty/src/mir/lower.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3260,12 +3268,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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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)];
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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 }
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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!(
|
||||||
|
|
|
@ -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",
|
||||||
|
@ -1441,6 +1443,7 @@ impl Config {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
keywords: self.data.hover_documentation_keywords_enable,
|
keywords: self.data.hover_documentation_keywords_enable,
|
||||||
|
interpret_tests: self.data.interpret_tests,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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:?}"));
|
||||||
|
|
|
@ -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 {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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`)::
|
||||||
+
|
+
|
||||||
--
|
--
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue