Skip to main content

hir_ty/
builtin_derive.rs

1//! Implementation of builtin derive impls.
2
3use std::ops::ControlFlow;
4
5use hir_def::{
6    AdtId, BuiltinDeriveImplId, BuiltinDeriveImplLoc, HasModule, LocalFieldId, TraitId,
7    TypeOrConstParamId, TypeParamId,
8    attrs::AttrFlags,
9    builtin_derive::BuiltinDeriveImplTrait,
10    hir::generics::{GenericParams, TypeOrConstParamData},
11};
12use itertools::Itertools;
13use la_arena::ArenaMap;
14use rustc_type_ir::{
15    AliasTyKind, TypeFoldable, TypeSuperVisitable, TypeVisitable, TypeVisitor, Upcast,
16    inherent::{GenericArgs as _, IntoKind},
17};
18
19use crate::{
20    FieldType, GenericPredicates,
21    db::HirDatabase,
22    next_solver::{
23        AliasTy, Clause, Clauses, DbInterner, EarlyBinder, GenericArgs, ParamEnv,
24        StoredEarlyBinder, TraitRef, Ty, TyKind, Unnormalized, fold::fold_tys, generics::Generics,
25    },
26};
27
28fn coerce_pointee_new_type_param(trait_id: TraitId) -> TypeParamId {
29    // HACK: Fake the param.
30    // We cannot use a dummy param here, because it can leak into the IDE layer and that'll cause panics
31    // when e.g. trying to display it. So we use an existing param.
32    TypeParamId::from_unchecked(TypeOrConstParamId {
33        parent: trait_id.into(),
34        local_id: la_arena::Idx::from_raw(la_arena::RawIdx::from_u32(1)),
35    })
36}
37
38fn trait_args(trait_: BuiltinDeriveImplTrait, self_ty: Ty<'_>) -> GenericArgs<'_> {
39    match trait_ {
40        BuiltinDeriveImplTrait::Copy
41        | BuiltinDeriveImplTrait::Reborrow
42        | BuiltinDeriveImplTrait::Clone
43        | BuiltinDeriveImplTrait::Default
44        | BuiltinDeriveImplTrait::Debug
45        | BuiltinDeriveImplTrait::Hash
46        | BuiltinDeriveImplTrait::Eq
47        | BuiltinDeriveImplTrait::Ord => GenericArgs::new_from_slice(&[self_ty.into()]),
48        BuiltinDeriveImplTrait::PartialOrd | BuiltinDeriveImplTrait::PartialEq => {
49            GenericArgs::new_from_slice(&[self_ty.into(), self_ty.into()])
50        }
51        BuiltinDeriveImplTrait::CoerceUnsized | BuiltinDeriveImplTrait::DispatchFromDyn => {
52            panic!("`CoerceUnsized` and `DispatchFromDyn` have special generics")
53        }
54    }
55}
56
57pub(crate) fn generics_of<'db>(
58    interner: DbInterner<'db>,
59    id: BuiltinDeriveImplId,
60) -> Generics<'db> {
61    let db = interner.db;
62    let loc = id.loc(db);
63    match loc.trait_ {
64        BuiltinDeriveImplTrait::Copy
65        | BuiltinDeriveImplTrait::Reborrow
66        | BuiltinDeriveImplTrait::Clone
67        | BuiltinDeriveImplTrait::Default
68        | BuiltinDeriveImplTrait::Debug
69        | BuiltinDeriveImplTrait::Hash
70        | BuiltinDeriveImplTrait::Ord
71        | BuiltinDeriveImplTrait::PartialOrd
72        | BuiltinDeriveImplTrait::Eq
73        | BuiltinDeriveImplTrait::PartialEq => {
74            Generics::from_generic_def(db, loc.adt.into(), false)
75        }
76        BuiltinDeriveImplTrait::CoerceUnsized | BuiltinDeriveImplTrait::DispatchFromDyn => {
77            let trait_id = loc
78                .trait_
79                .get_id(interner.lang_items())
80                .expect("we don't pass the impl to the solver if we can't resolve the trait");
81            let additional_param = coerce_pointee_new_type_param(trait_id);
82            Generics::from_generic_def_plus_one(db, loc.adt.into(), additional_param, false)
83        }
84    }
85}
86
87pub fn generic_params_count(db: &dyn HirDatabase, id: BuiltinDeriveImplId) -> usize {
88    let loc = id.loc(db);
89    let adt_params = GenericParams::of(db, loc.adt.into());
90    let extra_params_count = match loc.trait_ {
91        BuiltinDeriveImplTrait::Copy
92        | BuiltinDeriveImplTrait::Reborrow
93        | BuiltinDeriveImplTrait::Clone
94        | BuiltinDeriveImplTrait::Default
95        | BuiltinDeriveImplTrait::Debug
96        | BuiltinDeriveImplTrait::Hash
97        | BuiltinDeriveImplTrait::Ord
98        | BuiltinDeriveImplTrait::PartialOrd
99        | BuiltinDeriveImplTrait::Eq
100        | BuiltinDeriveImplTrait::PartialEq => 0,
101        BuiltinDeriveImplTrait::CoerceUnsized | BuiltinDeriveImplTrait::DispatchFromDyn => 1,
102    };
103    adt_params.len() + extra_params_count
104}
105
106pub fn impl_trait<'db>(
107    interner: DbInterner<'db>,
108    id: BuiltinDeriveImplId,
109) -> EarlyBinder<'db, TraitRef<'db>> {
110    let db = interner.db;
111    let loc = id.loc(db);
112    let trait_id = loc
113        .trait_
114        .get_id(interner.lang_items())
115        .expect("we don't pass the impl to the solver if we can't resolve the trait");
116    match loc.trait_ {
117        BuiltinDeriveImplTrait::Copy
118        | BuiltinDeriveImplTrait::Reborrow
119        | BuiltinDeriveImplTrait::Clone
120        | BuiltinDeriveImplTrait::Default
121        | BuiltinDeriveImplTrait::Debug
122        | BuiltinDeriveImplTrait::Hash
123        | BuiltinDeriveImplTrait::Ord
124        | BuiltinDeriveImplTrait::Eq
125        | BuiltinDeriveImplTrait::PartialOrd
126        | BuiltinDeriveImplTrait::PartialEq => {
127            let self_ty = Ty::new_adt(
128                interner,
129                loc.adt,
130                GenericArgs::identity_for_item(interner, loc.adt.into()),
131            );
132            EarlyBinder::bind(TraitRef::new_from_args(
133                interner,
134                trait_id.into(),
135                trait_args(loc.trait_, self_ty),
136            ))
137        }
138        BuiltinDeriveImplTrait::CoerceUnsized | BuiltinDeriveImplTrait::DispatchFromDyn => {
139            let generic_params = GenericParams::of(db, loc.adt.into());
140            let interner = DbInterner::new_no_crate(db);
141            let args = GenericArgs::identity_for_item(interner, loc.adt.into());
142            let self_ty = Ty::new_adt(interner, loc.adt, args);
143            let Some((pointee_param_idx, _, new_param_ty)) =
144                coerce_pointee_params(interner, loc, generic_params, trait_id)
145            else {
146                // Malformed derive.
147                return EarlyBinder::bind(TraitRef::new(
148                    interner,
149                    trait_id.into(),
150                    [self_ty, self_ty],
151                ));
152            };
153            let changed_args = replace_pointee(interner, pointee_param_idx, new_param_ty, args);
154            let changed_self_ty = Ty::new_adt(interner, loc.adt, changed_args);
155            EarlyBinder::bind(TraitRef::new(interner, trait_id.into(), [self_ty, changed_self_ty]))
156        }
157    }
158}
159
160#[inline]
161pub fn impl_super_outlives<'db>(
162    _interner: DbInterner<'db>,
163    _id: BuiltinDeriveImplId,
164) -> EarlyBinder<'db, &'db [Clause<'db>]> {
165    // Currently no builtin-derived traits have super outlive bounds.
166    EarlyBinder::bind(&[])
167}
168
169#[salsa::tracked(returns(ref))]
170pub fn predicates(db: &dyn HirDatabase, impl_: BuiltinDeriveImplId) -> GenericPredicates {
171    let loc = impl_.loc(db);
172    let generic_params = GenericParams::of(db, loc.adt.into());
173    let interner = DbInterner::new_with(db, loc.module(db).krate(db));
174    let adt_predicates = GenericPredicates::query(db, loc.adt.into());
175    let trait_id = loc
176        .trait_
177        .get_id(interner.lang_items())
178        .expect("we don't pass the impl to the solver if we can't resolve the trait");
179    match loc.trait_ {
180        BuiltinDeriveImplTrait::Copy
181        | BuiltinDeriveImplTrait::Clone
182        | BuiltinDeriveImplTrait::Debug
183        | BuiltinDeriveImplTrait::Hash
184        | BuiltinDeriveImplTrait::Ord
185        | BuiltinDeriveImplTrait::PartialOrd
186        | BuiltinDeriveImplTrait::Eq
187        | BuiltinDeriveImplTrait::PartialEq => {
188            simple_trait_predicates(interner, loc, generic_params, adt_predicates, trait_id)
189        }
190        BuiltinDeriveImplTrait::Reborrow => {
191            explicit_own_predicates(adt_predicates.own_explicit_predicates())
192        }
193        BuiltinDeriveImplTrait::Default => {
194            if matches!(loc.adt, AdtId::EnumId(_)) {
195                // Enums don't have extra bounds.
196                explicit_own_predicates(adt_predicates.own_explicit_predicates())
197            } else {
198                simple_trait_predicates(interner, loc, generic_params, adt_predicates, trait_id)
199            }
200        }
201        BuiltinDeriveImplTrait::CoerceUnsized | BuiltinDeriveImplTrait::DispatchFromDyn => {
202            let Some((pointee_param_idx, pointee_param_id, new_param_ty)) =
203                coerce_pointee_params(interner, loc, generic_params, trait_id)
204            else {
205                // Malformed derive.
206                return GenericPredicates::from_explicit_own_predicates(StoredEarlyBinder::bind(
207                    Clauses::empty().store(),
208                ));
209            };
210            let duplicated_bounds =
211                adt_predicates.explicit_predicates().iter_identity().filter_map(|pred| {
212                    let pred = pred.skip_norm_wip();
213                    let mentions_pointee =
214                        pred.visit_with(&mut MentionsPointee { pointee_param_idx }).is_break();
215                    if !mentions_pointee {
216                        return None;
217                    }
218                    let transformed =
219                        replace_pointee(interner, pointee_param_idx, new_param_ty, pred);
220                    Some(transformed)
221                });
222            let unsize_trait = interner.lang_items().Unsize;
223            let unsize_bound = unsize_trait.map(|unsize_trait| {
224                let pointee_param_ty = Ty::new_param(interner, pointee_param_id, pointee_param_idx);
225                TraitRef::new(interner, unsize_trait.into(), [pointee_param_ty, new_param_ty])
226                    .upcast(interner)
227            });
228            GenericPredicates::from_explicit_own_predicates(StoredEarlyBinder::bind(
229                Clauses::new_from_iter(
230                    adt_predicates
231                        .explicit_predicates()
232                        .iter_identity()
233                        .map(Unnormalized::skip_norm_wip)
234                        .chain(duplicated_bounds)
235                        .chain(unsize_bound),
236                )
237                .store(),
238            ))
239        }
240    }
241}
242
243fn explicit_own_predicates<'db>(
244    predicates: EarlyBinder<'db, impl Iterator<Item = Clause<'db>>>,
245) -> GenericPredicates {
246    GenericPredicates::from_explicit_own_predicates(StoredEarlyBinder::bind(
247        Clauses::new_from_iter(predicates.skip_binder()).store(),
248    ))
249}
250
251/// Not cached in a query, currently used in `hir` only. If you need this in `hir-ty` consider introducing a query.
252pub fn param_env<'db>(interner: DbInterner<'db>, id: BuiltinDeriveImplId) -> ParamEnv<'db> {
253    let predicates = predicates(interner.db, id);
254    crate::lower::param_env_from_predicates(interner, predicates)
255}
256
257struct MentionsPointee {
258    pointee_param_idx: u32,
259}
260
261impl<'db> TypeVisitor<DbInterner<'db>> for MentionsPointee {
262    type Result = ControlFlow<()>;
263
264    fn visit_ty(&mut self, t: Ty<'db>) -> Self::Result {
265        if let TyKind::Param(param) = t.kind()
266            && param.index == self.pointee_param_idx
267        {
268            ControlFlow::Break(())
269        } else {
270            t.super_visit_with(self)
271        }
272    }
273}
274
275fn replace_pointee<'db, T: TypeFoldable<DbInterner<'db>>>(
276    interner: DbInterner<'db>,
277    pointee_param_idx: u32,
278    new_param_ty: Ty<'db>,
279    t: T,
280) -> T {
281    fold_tys(interner, t, |ty| match ty.kind() {
282        TyKind::Param(param) if param.index == pointee_param_idx => new_param_ty,
283        _ => ty,
284    })
285}
286
287fn simple_trait_predicates<'db>(
288    interner: DbInterner<'db>,
289    loc: &BuiltinDeriveImplLoc,
290    generic_params: &GenericParams,
291    adt_predicates: &GenericPredicates,
292    trait_id: TraitId,
293) -> GenericPredicates {
294    let extra_predicates = generic_params
295        .iter_type_or_consts()
296        .filter(|(_, data)| matches!(data, TypeOrConstParamData::TypeParamData(_)))
297        .map(|(param_idx, _)| {
298            let param_id = TypeParamId::from_unchecked(TypeOrConstParamId {
299                parent: loc.adt.into(),
300                local_id: param_idx,
301            });
302            let param_idx = param_idx.into_raw().into_u32()
303                + (generic_params.len_early_bound_lifetimes() as u32);
304            let param_ty = Ty::new_param(interner, param_id, param_idx);
305            let trait_args = trait_args(loc.trait_, param_ty);
306            let trait_ref = TraitRef::new_from_args(interner, trait_id.into(), trait_args);
307            trait_ref.upcast(interner)
308        });
309    let mut assoc_type_bounds = Vec::new();
310    match loc.adt {
311        AdtId::StructId(id) => extend_assoc_type_bounds(
312            interner,
313            &mut assoc_type_bounds,
314            interner.db.field_types(id.into()),
315            trait_id,
316            loc.trait_,
317        ),
318        AdtId::UnionId(id) => extend_assoc_type_bounds(
319            interner,
320            &mut assoc_type_bounds,
321            interner.db.field_types(id.into()),
322            trait_id,
323            loc.trait_,
324        ),
325        AdtId::EnumId(id) => {
326            for &(variant_id, _) in id.enum_variants(interner.db).variants.values() {
327                extend_assoc_type_bounds(
328                    interner,
329                    &mut assoc_type_bounds,
330                    interner.db.field_types(variant_id.into()),
331                    trait_id,
332                    loc.trait_,
333                )
334            }
335        }
336    }
337    GenericPredicates::from_explicit_own_predicates(StoredEarlyBinder::bind(
338        Clauses::new_from_iter(
339            adt_predicates
340                .explicit_predicates()
341                .iter_identity()
342                .map(Unnormalized::skip_norm_wip)
343                .chain(extra_predicates)
344                .chain(assoc_type_bounds),
345        )
346        .store(),
347    ))
348}
349
350fn extend_assoc_type_bounds<'db>(
351    interner: DbInterner<'db>,
352    assoc_type_bounds: &mut Vec<Clause<'db>>,
353    fields: &ArenaMap<LocalFieldId, FieldType>,
354    trait_id: TraitId,
355    trait_: BuiltinDeriveImplTrait,
356) {
357    struct ProjectionFinder<'a, 'db> {
358        interner: DbInterner<'db>,
359        assoc_type_bounds: &'a mut Vec<Clause<'db>>,
360        trait_id: TraitId,
361        trait_: BuiltinDeriveImplTrait,
362    }
363
364    impl<'db> TypeVisitor<DbInterner<'db>> for ProjectionFinder<'_, 'db> {
365        type Result = ();
366
367        fn visit_ty(&mut self, t: Ty<'db>) -> Self::Result {
368            if let TyKind::Alias(AliasTy { kind: AliasTyKind::Projection { .. }, .. }) = t.kind() {
369                self.assoc_type_bounds.push(
370                    TraitRef::new_from_args(
371                        self.interner,
372                        self.trait_id.into(),
373                        trait_args(self.trait_, t),
374                    )
375                    .upcast(self.interner),
376                );
377            }
378
379            t.super_visit_with(self)
380        }
381    }
382
383    let mut visitor = ProjectionFinder { interner, assoc_type_bounds, trait_id, trait_ };
384    for (_, field) in fields.iter() {
385        field.ty().instantiate_identity().skip_norm_wip().visit_with(&mut visitor);
386    }
387}
388
389fn coerce_pointee_params<'db>(
390    interner: DbInterner<'db>,
391    loc: &BuiltinDeriveImplLoc,
392    generic_params: &GenericParams,
393    trait_id: TraitId,
394) -> Option<(u32, TypeParamId, Ty<'db>)> {
395    let pointee_param = {
396        if let Ok((pointee_param, _)) = generic_params
397            .iter_type_or_consts()
398            .filter(|param| matches!(param.1, TypeOrConstParamData::TypeParamData(_)))
399            .exactly_one()
400        {
401            pointee_param
402        } else {
403            let (_, generic_param_attrs) =
404                AttrFlags::query_generic_params(interner.db, loc.adt.into());
405            generic_param_attrs
406                .iter()
407                .find(|param| param.1.contains(AttrFlags::IS_POINTEE))
408                .map(|(param, _)| param)
409                .or_else(|| {
410                    generic_params
411                        .iter_type_or_consts()
412                        .find(|param| matches!(param.1, TypeOrConstParamData::TypeParamData(_)))
413                        .map(|(idx, _)| idx)
414                })?
415        }
416    };
417    let pointee_param_id = TypeParamId::from_unchecked(TypeOrConstParamId {
418        parent: loc.adt.into(),
419        local_id: pointee_param,
420    });
421    let pointee_param_idx =
422        pointee_param.into_raw().into_u32() + (generic_params.len_early_bound_lifetimes() as u32);
423    let new_param_idx = generic_params.len() as u32;
424    let new_param_id = coerce_pointee_new_type_param(trait_id);
425    let new_param_ty = Ty::new_param(interner, new_param_id, new_param_idx);
426    Some((pointee_param_idx, pointee_param_id, new_param_ty))
427}