Skip to content

feat: Calculate drop glue and show it on hover #18987

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 24, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 33 additions & 2 deletions crates/hir-ty/src/consteval/tests/intrinsics.rs
Original file line number Diff line number Diff line change
@@ -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,
);
7 changes: 6 additions & 1 deletion crates/hir-ty/src/db.rs
Original file line number Diff line number Diff line change
@@ -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},
@@ -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> {
@@ -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]
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(_) => 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
@@ -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;
@@ -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},
11 changes: 10 additions & 1 deletion crates/hir-ty/src/mir/eval/shim.rs
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@ use hir_def::{
};
use hir_expand::name::Name;
use intern::{sym, Symbol};
use stdx::never;

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

mod simd;
@@ -853,7 +855,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" => {
Loading