|
| 1 | +//! Impl specialization related things |
| 2 | +
|
| 3 | +use hir_def::{ImplId, nameres::crate_def_map}; |
| 4 | +use intern::sym; |
| 5 | +use tracing::debug; |
| 6 | + |
| 7 | +use crate::{ |
| 8 | + db::HirDatabase, |
| 9 | + next_solver::{ |
| 10 | + DbInterner, TypingMode, |
| 11 | + infer::{ |
| 12 | + DbInternerInferExt, |
| 13 | + traits::{Obligation, ObligationCause}, |
| 14 | + }, |
| 15 | + obligation_ctxt::ObligationCtxt, |
| 16 | + }, |
| 17 | +}; |
| 18 | + |
| 19 | +// rustc does not have a cycle handling for the `specializes` query, meaning a cycle is a bug, |
| 20 | +// and indeed I was unable to cause cycles even with erroneous code. However, in r-a we can |
| 21 | +// create a cycle if there is an error in the impl's where clauses. I believe well formed code |
| 22 | +// cannot create a cycle, but a cycle handler is required nevertheless. |
| 23 | +fn specializes_cycle( |
| 24 | + _db: &dyn HirDatabase, |
| 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_cycle)] |
| 43 | +pub(crate) fn specializes( |
| 44 | + db: &dyn HirDatabase, |
| 45 | + specializing_impl_def_id: ImplId, |
| 46 | + parent_impl_def_id: ImplId, |
| 47 | +) -> bool { |
| 48 | + let module = specializing_impl_def_id.loc(db).container; |
| 49 | + |
| 50 | + // We check that the specializing impl comes from a crate that has specialization enabled. |
| 51 | + // |
| 52 | + // We don't really care if the specialized impl (the parent) is in a crate that has |
| 53 | + // specialization enabled, since it's not being specialized. |
| 54 | + // |
| 55 | + // rustc also checks whether the specializing impls comes from a macro marked |
| 56 | + // `#[allow_internal_unstable(specialization)]`, but `#[allow_internal_unstable]` |
| 57 | + // is an internal feature, std is not using it for specialization nor is likely to |
| 58 | + // ever use it, and we don't have the span information necessary to replicate that. |
| 59 | + let def_map = crate_def_map(db, module.krate()); |
| 60 | + if !def_map.is_unstable_feature_enabled(&sym::specialization) |
| 61 | + && !def_map.is_unstable_feature_enabled(&sym::min_specialization) |
| 62 | + { |
| 63 | + return false; |
| 64 | + } |
| 65 | + |
| 66 | + let interner = DbInterner::new_with(db, Some(module.krate()), module.containing_block()); |
| 67 | + |
| 68 | + let specializing_impl_signature = db.impl_signature(specializing_impl_def_id); |
| 69 | + let parent_impl_signature = db.impl_signature(parent_impl_def_id); |
| 70 | + |
| 71 | + // We determine whether there's a subset relationship by: |
| 72 | + // |
| 73 | + // - replacing bound vars with placeholders in impl1, |
| 74 | + // - assuming the where clauses for impl1, |
| 75 | + // - instantiating impl2 with fresh inference variables, |
| 76 | + // - unifying, |
| 77 | + // - attempting to prove the where clauses for impl2 |
| 78 | + // |
| 79 | + // The last three steps are encapsulated in `fulfill_implication`. |
| 80 | + // |
| 81 | + // See RFC 1210 for more details and justification. |
| 82 | + |
| 83 | + // Currently we do not allow e.g., a negative impl to specialize a positive one |
| 84 | + if specializing_impl_signature.is_negative() != parent_impl_signature.is_negative() { |
| 85 | + return false; |
| 86 | + } |
| 87 | + |
| 88 | + // create a parameter environment corresponding to an identity instantiation of the specializing impl, |
| 89 | + // i.e. the most generic instantiation of the specializing impl. |
| 90 | + let param_env = db.trait_environment(specializing_impl_def_id.into()).env; |
| 91 | + |
| 92 | + // Create an infcx, taking the predicates of the specializing impl as assumptions: |
| 93 | + let infcx = interner.infer_ctxt().build(TypingMode::non_body_analysis()); |
| 94 | + |
| 95 | + let specializing_impl_trait_ref = |
| 96 | + db.impl_trait(specializing_impl_def_id).unwrap().instantiate_identity(); |
| 97 | + let cause = &ObligationCause::dummy(); |
| 98 | + debug!( |
| 99 | + "fulfill_implication({:?}, trait_ref={:?} |- {:?} applies)", |
| 100 | + param_env, specializing_impl_trait_ref, parent_impl_def_id |
| 101 | + ); |
| 102 | + |
| 103 | + // Attempt to prove that the parent impl applies, given all of the above. |
| 104 | + |
| 105 | + let mut ocx = ObligationCtxt::new(&infcx); |
| 106 | + |
| 107 | + let parent_args = infcx.fresh_args_for_item(parent_impl_def_id.into()); |
| 108 | + let parent_impl_trait_ref = db |
| 109 | + .impl_trait(parent_impl_def_id) |
| 110 | + .expect("expected source impl to be a trait impl") |
| 111 | + .instantiate(interner, parent_args); |
| 112 | + |
| 113 | + // do the impls unify? If not, no specialization. |
| 114 | + let Ok(()) = ocx.eq(cause, param_env, specializing_impl_trait_ref, parent_impl_trait_ref) |
| 115 | + else { |
| 116 | + return false; |
| 117 | + }; |
| 118 | + |
| 119 | + // Now check that the source trait ref satisfies all the where clauses of the target impl. |
| 120 | + // This is not just for correctness; we also need this to constrain any params that may |
| 121 | + // only be referenced via projection predicates. |
| 122 | + if let Some(predicates) = |
| 123 | + db.generic_predicates(parent_impl_def_id.into()).instantiate(interner, parent_args) |
| 124 | + { |
| 125 | + ocx.register_obligations( |
| 126 | + predicates |
| 127 | + .map(|predicate| Obligation::new(interner, cause.clone(), param_env, predicate)), |
| 128 | + ); |
| 129 | + } |
| 130 | + |
| 131 | + let errors = ocx.evaluate_obligations_error_on_ambiguity(); |
| 132 | + if !errors.is_empty() { |
| 133 | + // no dice! |
| 134 | + debug!( |
| 135 | + "fulfill_implication: for impls on {:?} and {:?}, \ |
| 136 | + could not fulfill: {:?} given {:?}", |
| 137 | + specializing_impl_trait_ref, parent_impl_trait_ref, errors, param_env |
| 138 | + ); |
| 139 | + return false; |
| 140 | + } |
| 141 | + |
| 142 | + // FIXME: Check impl constness (when we implement const impls). |
| 143 | + |
| 144 | + debug!( |
| 145 | + "fulfill_implication: an impl for {:?} specializes {:?}", |
| 146 | + specializing_impl_trait_ref, parent_impl_trait_ref |
| 147 | + ); |
| 148 | + |
| 149 | + true |
| 150 | +} |
0 commit comments