//! Utilities for computing drop info about types. use hir_def::{AdtId, signatures::StructFlags}; use rustc_hash::FxHashSet; use rustc_type_ir::inherent::{AdtDef, IntoKind}; use stdx::never; use crate::{ InferenceResult, consteval, method_resolution::TraitImpls, next_solver::{ DbInterner, ParamEnv, SimplifiedType, Ty, TyKind, infer::{InferCtxt, traits::ObligationCause}, obligation_ctxt::ObligationCtxt, }, }; fn has_destructor(interner: DbInterner<'_>, adt: AdtId) -> bool { let db = interner.db; 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) = interner.lang_items().Drop else { return false; }; let impls = match module.block(db) { Some(block) => match TraitImpls::for_block(db, block) { Some(it) => &**it, None => return false, }, None => TraitImpls::for_crate(db, module.krate(db)), }; !impls.for_trait_and_self_ty(drop_trait, &SimplifiedType::Adt(adt.into())).0.is_empty() } #[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 fn has_drop_glue<'db>(infcx: &InferCtxt<'db>, ty: Ty<'db>, env: ParamEnv<'db>) -> DropGlue { has_drop_glue_impl(infcx, ty, env, &mut FxHashSet::default()) } fn has_drop_glue_impl<'db>( infcx: &InferCtxt<'db>, ty: Ty<'db>, env: ParamEnv<'db>, visited: &mut FxHashSet>, ) -> DropGlue { let mut ocx = ObligationCtxt::new(infcx); let ty = ocx.structurally_normalize_ty(&ObligationCause::dummy(), env, ty).unwrap_or(ty); if !visited.insert(ty) { // Recursive type. return DropGlue::None; } let db = infcx.interner.db; match ty.kind() { TyKind::Adt(adt_def, subst) => { let adt_id = adt_def.def_id().0; if has_destructor(infcx.interner, adt_id) { return DropGlue::HasDropGlue; } match adt_id { AdtId::StructId(id) => { if db .struct_signature(id) .flags .intersects(StructFlags::IS_MANUALLY_DROP | StructFlags::IS_PHANTOM_DATA) { return DropGlue::None; } db.field_types(id.into()) .iter() .map(|(_, field_ty)| { has_drop_glue_impl( infcx, field_ty.get().instantiate(infcx.interner, subst), env, visited, ) }) .max() .unwrap_or(DropGlue::None) } // Unions cannot have fields with destructors. AdtId::UnionId(_) => DropGlue::None, AdtId::EnumId(id) => id .enum_variants(db) .variants .iter() .map(|&(variant, _, _)| { db.field_types(variant.into()) .iter() .map(|(_, field_ty)| { has_drop_glue_impl( infcx, field_ty.get().instantiate(infcx.interner, subst), env, visited, ) }) .max() .unwrap_or(DropGlue::None) }) .max() .unwrap_or(DropGlue::None), } } TyKind::Tuple(tys) => tys .iter() .map(|ty| has_drop_glue_impl(infcx, ty, env, visited)) .max() .unwrap_or(DropGlue::None), TyKind::Array(ty, len) => { if consteval::try_const_usize(db, len) == Some(0) { // Arrays of size 0 don't have drop glue. return DropGlue::None; } has_drop_glue_impl(infcx, ty, env, visited) } TyKind::Slice(ty) => has_drop_glue_impl(infcx, ty, env, visited), TyKind::Closure(closure_id, subst) => { let owner = db.lookup_intern_closure(closure_id.0).0; let infer = InferenceResult::for_body(db, owner); let (captures, _) = infer.closure_info(closure_id.0); let env = db.trait_environment_for_body(owner); captures .iter() .map(|capture| has_drop_glue_impl(infcx, capture.ty(db, subst), env, visited)) .max() .unwrap_or(DropGlue::None) } // FIXME: Handle coroutines. TyKind::Coroutine(..) | TyKind::CoroutineWitness(..) | TyKind::CoroutineClosure(..) => { DropGlue::None } TyKind::Ref(..) | TyKind::RawPtr(..) | TyKind::FnDef(..) | TyKind::Str | TyKind::Never | TyKind::Bool | TyKind::Char | TyKind::Int(_) | TyKind::Uint(_) | TyKind::Float(_) | TyKind::FnPtr(..) | TyKind::Foreign(_) | TyKind::Error(_) | TyKind::Bound(..) | TyKind::Placeholder(..) => DropGlue::None, TyKind::Dynamic(..) => DropGlue::HasDropGlue, TyKind::Alias(..) => { if infcx.type_is_copy_modulo_regions(env, ty) { DropGlue::None } else { DropGlue::HasDropGlue } } TyKind::Param(_) => { if infcx.type_is_copy_modulo_regions(env, ty) { DropGlue::None } else { DropGlue::DependOnParams } } TyKind::Infer(..) => unreachable!("inference vars shouldn't exist out of inference"), TyKind::Pat(..) | TyKind::UnsafeBinder(..) => { never!("we do not handle pattern and unsafe binder types"); DropGlue::None } } }