hir_ty/specialization.rs
1//! Impl specialization related things
2
3use hir_def::{HasModule, ImplId, signatures::ImplSignature, unstable_features::UnstableFeatures};
4use tracing::debug;
5
6use crate::{
7 Span,
8 db::HirDatabase,
9 lower::GenericPredicates,
10 next_solver::{
11 DbInterner, TypingMode, Unnormalized,
12 infer::{DbInternerInferExt, traits::ObligationCause},
13 obligation_ctxt::ObligationCtxt,
14 util::clauses_as_obligations,
15 },
16};
17
18// rustc does not have a cycle handling for the `specializes` query, meaning a cycle is a bug,
19// and indeed I was unable to cause cycles even with erroneous code. However, in r-a we can
20// create a cycle if there is an error in the impl's where clauses. I believe well formed code
21// cannot create a cycle, but a cycle handler is required nevertheless.
22fn specializes_query_cycle(
23 _db: &dyn HirDatabase,
24 _: salsa::Id,
25 _specializing_impl_def_id: ImplId,
26 _parent_impl_def_id: ImplId,
27) -> bool {
28 false
29}
30
31/// Is `specializing_impl_def_id` a specialization of `parent_impl_def_id`?
32///
33/// For every type that could apply to `specializing_impl_def_id`, we prove that
34/// the `parent_impl_def_id` also applies (i.e. it has a valid impl header and
35/// its where-clauses hold).
36///
37/// For the purposes of const traits, we also check that the specializing
38/// impl is not more restrictive than the parent impl. That is, if the
39/// `parent_impl_def_id` is a const impl (conditionally based off of some `[const]`
40/// bounds), then `specializing_impl_def_id` must also be const for the same
41/// set of types.
42#[salsa::tracked(cycle_result = specializes_query_cycle)]
43fn specializes_query(
44 db: &dyn HirDatabase,
45 specializing_impl_def_id: ImplId,
46 parent_impl_def_id: ImplId,
47) -> bool {
48 let trait_env = db.trait_environment(specializing_impl_def_id.into());
49 let interner = DbInterner::new_with(db, specializing_impl_def_id.krate(db));
50
51 let specializing_impl_signature = ImplSignature::of(db, specializing_impl_def_id);
52 let parent_impl_signature = ImplSignature::of(db, parent_impl_def_id);
53
54 // We determine whether there's a subset relationship by:
55 //
56 // - replacing bound vars with placeholders in impl1,
57 // - assuming the where clauses for impl1,
58 // - instantiating impl2 with fresh inference variables,
59 // - unifying,
60 // - attempting to prove the where clauses for impl2
61 //
62 // The last three steps are encapsulated in `fulfill_implication`.
63 //
64 // See RFC 1210 for more details and justification.
65
66 // Currently we do not allow e.g., a negative impl to specialize a positive one
67 if specializing_impl_signature.is_negative() != parent_impl_signature.is_negative() {
68 return false;
69 }
70
71 // create a parameter environment corresponding to an identity instantiation of the specializing impl,
72 // i.e. the most generic instantiation of the specializing impl.
73 let param_env = trait_env;
74
75 // Create an infcx, taking the predicates of the specializing impl as assumptions:
76 let infcx = interner.infer_ctxt().build(TypingMode::non_body_analysis());
77
78 let specializing_impl_trait_ref =
79 db.impl_trait(specializing_impl_def_id).unwrap().instantiate_identity().skip_norm_wip();
80 let cause = &ObligationCause::dummy();
81 debug!(
82 "fulfill_implication({:?}, trait_ref={:?} |- {:?} applies)",
83 param_env, specializing_impl_trait_ref, parent_impl_def_id
84 );
85
86 // Attempt to prove that the parent impl applies, given all of the above.
87
88 let mut ocx = ObligationCtxt::new(&infcx);
89
90 let parent_args = infcx.fresh_args_for_item(Span::Dummy, parent_impl_def_id.into());
91 let parent_impl_trait_ref = db
92 .impl_trait(parent_impl_def_id)
93 .expect("expected source impl to be a trait impl")
94 .instantiate(interner, parent_args)
95 .skip_norm_wip();
96
97 // do the impls unify? If not, no specialization.
98 let Ok(()) = ocx.eq(cause, param_env, specializing_impl_trait_ref, parent_impl_trait_ref)
99 else {
100 return false;
101 };
102
103 // Now check that the source trait ref satisfies all the where clauses of the target impl.
104 // This is not just for correctness; we also need this to constrain any params that may
105 // only be referenced via projection predicates.
106 ocx.register_obligations(clauses_as_obligations(
107 GenericPredicates::query_all(db, parent_impl_def_id.into())
108 .iter_instantiated(interner, parent_args.as_slice())
109 .map(Unnormalized::skip_norm_wip),
110 *cause,
111 param_env,
112 ));
113
114 let errors = ocx.evaluate_obligations_error_on_ambiguity();
115 if !errors.is_empty() {
116 // no dice!
117 debug!(
118 "fulfill_implication: for impls on {:?} and {:?}, \
119 could not fulfill: {:?} given {:?}",
120 specializing_impl_trait_ref, parent_impl_trait_ref, errors, param_env
121 );
122 return false;
123 }
124
125 // FIXME: Check impl constness (when we implement const impls).
126
127 debug!(
128 "fulfill_implication: an impl for {:?} specializes {:?}",
129 specializing_impl_trait_ref, parent_impl_trait_ref
130 );
131
132 true
133}
134
135// This function is used to avoid creating the query for crates that does not define `#![feature(specialization)]`,
136// as the solver is calling this a lot, and creating the query consumes a lot of memory.
137pub(crate) fn specializes(
138 db: &dyn HirDatabase,
139 specializing_impl_def_id: ImplId,
140 parent_impl_def_id: ImplId,
141) -> bool {
142 let module = specializing_impl_def_id.loc(db).container;
143
144 // We check that the specializing impl comes from a crate that has specialization enabled.
145 //
146 // We don't really care if the specialized impl (the parent) is in a crate that has
147 // specialization enabled, since it's not being specialized.
148 //
149 // rustc also checks whether the specializing impls comes from a macro marked
150 // `#[allow_internal_unstable(specialization)]`, but `#[allow_internal_unstable]`
151 // is an internal feature, std is not using it for specialization nor is likely to
152 // ever use it, and we don't have the span information necessary to replicate that.
153 let features = UnstableFeatures::query(db, module.krate(db));
154 if !features.specialization && !features.min_specialization {
155 return false;
156 }
157
158 specializes_query(db, specializing_impl_def_id, parent_impl_def_id)
159}