Skip to content

Commit

Permalink
Calculate drop glue and show it on hover
Browse files Browse the repository at this point in the history
Also fix the `needs_drop()` intrinsic.

Unions also need this information (to err if they have a drop-needing field), but this will come in a follow-up PR.
  • Loading branch information
ChayimFriedman2 committed Jan 20, 2025
1 parent 248bd51 commit dbdd5e1
Show file tree
Hide file tree
Showing 13 changed files with 1,000 additions and 6 deletions.
35 changes: 33 additions & 2 deletions crates/hir-ty/src/consteval/tests/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -354,12 +354,43 @@ fn overflowing_add() {
fn needs_drop() {
check_number(
r#"
//- minicore: copy, sized
//- minicore: drop, manually_drop, copy, sized
use core::mem::ManuallyDrop;
extern "rust-intrinsic" {
pub fn needs_drop<T: ?Sized>() -> bool;
}
struct X;
const GOAL: bool = !needs_drop::<i32>() && needs_drop::<X>();
struct NeedsDrop;
impl Drop for NeedsDrop {
fn drop(&mut self) {}
}
enum Enum<T> {
A(T),
B(X),
}
const fn val_needs_drop<T>(_v: T) -> bool { needs_drop::<T>() }
const fn closure_needs_drop() -> bool {
let a = NeedsDrop;
let b = X;
!val_needs_drop(|| &a) && val_needs_drop(move || &a) && !val_needs_drop(move || &b)
}
const fn opaque() -> impl Sized {
|| {}
}
const fn opaque_copy() -> impl Sized + Copy {
|| {}
}
trait Everything {}
impl<T> Everything for T {}
const GOAL: bool = !needs_drop::<i32>() && !needs_drop::<X>()
&& needs_drop::<NeedsDrop>() && !needs_drop::<ManuallyDrop<NeedsDrop>>()
&& needs_drop::<[NeedsDrop; 1]>() && !needs_drop::<[NeedsDrop; 0]>()
&& needs_drop::<(X, NeedsDrop)>()
&& needs_drop::<Enum<NeedsDrop>>() && !needs_drop::<Enum<X>>()
&& closure_needs_drop()
&& !val_needs_drop(opaque()) && !val_needs_drop(opaque_copy())
&& needs_drop::<[NeedsDrop]>() && needs_drop::<dyn Everything>()
&& !needs_drop::<&dyn Everything>() && !needs_drop::<str>();
"#,
1,
);
Expand Down
7 changes: 6 additions & 1 deletion crates/hir-ty/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ use hir_def::{
ConstParamId, DefWithBodyId, EnumVariantId, FunctionId, GeneralConstId, GenericDefId, ImplId,
LifetimeParamId, LocalFieldId, StaticId, TraitId, TypeAliasId, TypeOrConstParamId, VariantId,
};
use hir_expand::name::Name;
use la_arena::ArenaMap;
use smallvec::SmallVec;
use triomphe::Arc;

use crate::{
chalk_db,
consteval::ConstEvalError,
drop::DropGlue,
dyn_compatibility::DynCompatibilityViolation,
layout::{Layout, LayoutError},
lower::{Diagnostics, GenericDefaults, GenericPredicates},
Expand All @@ -28,7 +30,6 @@ use crate::{
Binders, ClosureId, Const, FnDefId, ImplTraitId, ImplTraits, InferenceResult, Interner,
PolyFnSig, Substitution, TraitEnvironment, TraitRef, Ty, TyDefId, ValueTyDefId,
};
use hir_expand::name::Name;

#[ra_salsa::query_group(HirDatabaseStorage)]
pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
Expand Down Expand Up @@ -305,6 +306,10 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
block: Option<BlockId>,
env: chalk_ir::Environment<Interner>,
) -> chalk_ir::ProgramClauses<Interner>;

#[ra_salsa::invoke(crate::drop::has_drop_glue)]
#[ra_salsa::cycle(crate::drop::has_drop_glue_recover)]
fn has_drop_glue(&self, ty: Ty, env: Arc<TraitEnvironment>) -> DropGlue {}
}

#[test]
Expand Down
209 changes: 209 additions & 0 deletions crates/hir-ty/src/drop.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
//! Utilities for computing drop info about types.
use base_db::ra_salsa;
use chalk_ir::cast::Cast;
use hir_def::data::adt::StructFlags;
use hir_def::lang_item::LangItem;
use hir_def::AdtId;
use stdx::never;
use triomphe::Arc;

use crate::{
db::HirDatabase, method_resolution::TyFingerprint, AliasTy, Canonical, CanonicalVarKinds,
InEnvironment, Interner, ProjectionTy, TraitEnvironment, Ty, TyBuilder, TyKind,
};
use crate::{ConcreteConst, ConstScalar, ConstValue};

fn has_destructor(db: &dyn HirDatabase, adt: AdtId) -> bool {
let module = match adt {
AdtId::EnumId(id) => db.lookup_intern_enum(id).container,
AdtId::StructId(id) => db.lookup_intern_struct(id).container,
AdtId::UnionId(id) => db.lookup_intern_union(id).container,
};
let Some(drop_trait) =
db.lang_item(module.krate(), LangItem::Drop).and_then(|it| it.as_trait())
else {
return false;
};
let impls = match module.containing_block() {
Some(block) => match db.trait_impls_in_block(block) {
Some(it) => it,
None => return false,
},
None => db.trait_impls_in_crate(module.krate()),
};
let result = impls.for_trait_and_self_ty(drop_trait, TyFingerprint::Adt(adt)).next().is_some();
result
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum DropGlue {
// Order of variants is important.
None,
/// May have a drop glue if some type parameter has it.
///
/// For the compiler this is considered as a positive result, IDE distinguishes this from "yes".
DependOnParams,
HasDropGlue,
}

pub(crate) fn has_drop_glue(db: &dyn HirDatabase, ty: Ty, env: Arc<TraitEnvironment>) -> DropGlue {
match ty.kind(Interner) {
TyKind::Adt(adt, subst) => {
if has_destructor(db, adt.0) {
return DropGlue::HasDropGlue;
}
match adt.0 {
AdtId::StructId(id) => {
if db.struct_data(id).flags.contains(StructFlags::IS_MANUALLY_DROP) {
return DropGlue::None;
}
db.field_types(id.into())
.iter()
.map(|(_, field_ty)| {
db.has_drop_glue(
field_ty.clone().substitute(Interner, subst),
env.clone(),
)
})
.max()
.unwrap_or(DropGlue::None)
}
// Unions cannot have fields with destructors.
AdtId::UnionId(_) => return DropGlue::None,
AdtId::EnumId(id) => db
.enum_data(id)
.variants
.iter()
.map(|&(variant, _)| {
db.field_types(variant.into())
.iter()
.map(|(_, field_ty)| {
db.has_drop_glue(
field_ty.clone().substitute(Interner, subst),
env.clone(),
)
})
.max()
.unwrap_or(DropGlue::None)
})
.max()
.unwrap_or(DropGlue::None),
}
}
TyKind::Tuple(_, subst) => subst
.iter(Interner)
.map(|ty| ty.assert_ty_ref(Interner))
.map(|ty| db.has_drop_glue(ty.clone(), env.clone()))
.max()
.unwrap_or(DropGlue::None),
TyKind::Array(ty, len) => {
if let ConstValue::Concrete(ConcreteConst { interned: ConstScalar::Bytes(len, _) }) =
&len.data(Interner).value
{
match (&**len).try_into() {
Ok(len) => {
let len = usize::from_le_bytes(len);
if len == 0 {
// Arrays of size 0 don't have drop glue.
return DropGlue::None;
}
}
Err(_) => {
never!("const array size with non-usize len");
}
}
}
db.has_drop_glue(ty.clone(), env)
}
TyKind::Slice(ty) => db.has_drop_glue(ty.clone(), env),
TyKind::Closure(closure_id, subst) => {
let owner = db.lookup_intern_closure((*closure_id).into()).0;
let infer = db.infer(owner);
let (captures, _) = infer.closure_info(closure_id);
let env = db.trait_environment_for_body(owner);
captures
.iter()
.map(|capture| db.has_drop_glue(capture.ty(subst), env.clone()))
.max()
.unwrap_or(DropGlue::None)
}
// FIXME: Handle coroutines.
TyKind::Coroutine(..) | TyKind::CoroutineWitness(..) => DropGlue::None,
TyKind::Ref(..)
| TyKind::Raw(..)
| TyKind::FnDef(..)
| TyKind::Str
| TyKind::Never
| TyKind::Scalar(_)
| TyKind::Function(_)
| TyKind::Foreign(_)
| TyKind::Error => DropGlue::None,
TyKind::Dyn(_) => DropGlue::HasDropGlue,
TyKind::AssociatedType(assoc_type_id, subst) => projection_has_drop_glue(
db,
env,
ProjectionTy { associated_ty_id: *assoc_type_id, substitution: subst.clone() },
ty,
),
TyKind::Alias(AliasTy::Projection(projection)) => {
projection_has_drop_glue(db, env, projection.clone(), ty)
}
TyKind::OpaqueType(..) | TyKind::Alias(AliasTy::Opaque(_)) => {
if is_copy(db, ty, env) {
DropGlue::None
} else {
DropGlue::HasDropGlue
}
}
TyKind::Placeholder(_) | TyKind::BoundVar(_) => {
if is_copy(db, ty, env) {
DropGlue::None
} else {
DropGlue::DependOnParams
}
}
TyKind::InferenceVar(..) => unreachable!("inference vars shouldn't exist out of inference"),
}
}

fn projection_has_drop_glue(
db: &dyn HirDatabase,
env: Arc<TraitEnvironment>,
projection: ProjectionTy,
ty: Ty,
) -> DropGlue {
let normalized = db.normalize_projection(projection, env.clone());
match normalized.kind(Interner) {
TyKind::Alias(AliasTy::Projection(_)) | TyKind::AssociatedType(..) => {
if is_copy(db, ty, env) {
DropGlue::None
} else {
DropGlue::DependOnParams
}
}
_ => db.has_drop_glue(normalized, env),
}
}

fn is_copy(db: &dyn HirDatabase, ty: Ty, env: Arc<TraitEnvironment>) -> bool {
let Some(copy_trait) = db.lang_item(env.krate, LangItem::Copy).and_then(|it| it.as_trait())
else {
return false;
};
let trait_ref = TyBuilder::trait_ref(db, copy_trait).push(ty).build();
let goal = Canonical {
value: InEnvironment::new(&env.env, trait_ref.cast(Interner)),
binders: CanonicalVarKinds::empty(Interner),
};
db.trait_solve(env.krate, env.block, goal).is_some()
}

pub(crate) fn has_drop_glue_recover(
_db: &dyn HirDatabase,
_cycle: &ra_salsa::Cycle,
_ty: &Ty,
_env: &Arc<TraitEnvironment>,
) -> DropGlue {
DropGlue::None
}
2 changes: 2 additions & 0 deletions crates/hir-ty/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ extern crate ra_ap_rustc_pattern_analysis as rustc_pattern_analysis;
mod builder;
mod chalk_db;
mod chalk_ext;
mod drop;
mod infer;
mod inhabitedness;
mod interner;
Expand Down Expand Up @@ -81,6 +82,7 @@ use crate::{
pub use autoderef::autoderef;
pub use builder::{ParamKind, TyBuilder};
pub use chalk_ext::*;
pub use drop::DropGlue;
pub use infer::{
cast::CastError,
closure::{CaptureKind, CapturedItem},
Expand Down
11 changes: 10 additions & 1 deletion crates/hir-ty/src/mir/eval/shim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use hir_def::{
};
use hir_expand::name::Name;
use intern::{sym, Symbol};
use stdx::never;

use crate::{
error_lifetime,
Expand All @@ -19,6 +20,7 @@ use crate::{
LangItem, Layout, Locals, Lookup, MirEvalError, MirSpan, Mutability, Result, Substitution,
Ty, TyBuilder, TyExt,
},
DropGlue,
};

mod simd;
Expand Down Expand Up @@ -852,7 +854,14 @@ impl Evaluator<'_> {
"size_of generic arg is not provided".into(),
));
};
let result = !ty.clone().is_copy(self.db, locals.body.owner);
let result = match self.db.has_drop_glue(ty.clone(), self.trait_env.clone()) {
DropGlue::HasDropGlue => true,
DropGlue::None => false,
DropGlue::DependOnParams => {
never!("should be fully monomorphized now");
true
}
};
destination.write_from_bytes(self, &[u8::from(result)])
}
"ptr_guaranteed_cmp" => {
Expand Down
Loading

0 comments on commit dbdd5e1

Please sign in to comment.