From c1ecea6d7ee9ea3e00f88e09b61a26da1eb7a52a Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Thu, 23 Oct 2025 20:48:51 +0300 Subject: [PATCH] Implement `Interner::impl_specializes()` Using specialization logic ported from rustc. --- .../rust-analyzer/crates/hir-ty/src/lib.rs | 1 + .../crates/hir-ty/src/mir/eval/tests.rs | 7 +- .../crates/hir-ty/src/next_solver/def_id.rs | 2 +- .../crates/hir-ty/src/next_solver/interner.rs | 10 +- .../crates/hir-ty/src/specialization.rs | 150 ++++++++++++++++++ .../crates/intern/src/symbol/symbols.rs | 2 + 6 files changed, 163 insertions(+), 9 deletions(-) create mode 100644 src/tools/rust-analyzer/crates/hir-ty/src/specialization.rs diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs index 536c81ab03b2..96dd48b53aa8 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs @@ -27,6 +27,7 @@ mod infer; mod inhabitedness; mod lower; pub mod next_solver; +mod specialization; mod target_feature; mod utils; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/tests.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/tests.rs index 4eb4aa91598e..f242115afeff 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/tests.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/tests.rs @@ -636,16 +636,13 @@ fn main() { ); } -#[ignore = " -FIXME(next-solver): -This does not work currently because I replaced homemade selection with selection by the trait solver; -This will work once we implement `Interner::impl_specializes()` properly. -"] #[test] fn specialization_array_clone() { check_pass( r#" //- minicore: copy, derive, slice, index, coerce_unsized, panic +#![feature(min_specialization)] + impl Clone for [T; N] { #[inline] fn clone(&self) -> Self { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/def_id.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/def_id.rs index 928e1321e738..0ff0b086a087 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/def_id.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/def_id.rs @@ -211,7 +211,7 @@ macro_rules! declare_id_wrapper { impl std::fmt::Debug for $name { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - std::fmt::Debug::fmt(&self.0, f) + std::fmt::Debug::fmt(&SolverDefId::from(self.0), f) } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/interner.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/interner.rs index 42f1d926d7db..43b47398f96d 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/interner.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/interner.rs @@ -1922,10 +1922,14 @@ impl<'db> rustc_type_ir::Interner for DbInterner<'db> { fn impl_specializes( self, - _specializing_impl_def_id: Self::ImplId, - _parent_impl_def_id: Self::ImplId, + specializing_impl_def_id: Self::ImplId, + parent_impl_def_id: Self::ImplId, ) -> bool { - false + crate::specialization::specializes( + self.db, + specializing_impl_def_id.0, + parent_impl_def_id.0, + ) } fn next_trait_solver_globally(self) -> bool { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/specialization.rs b/src/tools/rust-analyzer/crates/hir-ty/src/specialization.rs new file mode 100644 index 000000000000..611947b96b71 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-ty/src/specialization.rs @@ -0,0 +1,150 @@ +//! Impl specialization related things + +use hir_def::{ImplId, nameres::crate_def_map}; +use intern::sym; +use tracing::debug; + +use crate::{ + db::HirDatabase, + next_solver::{ + DbInterner, TypingMode, + infer::{ + DbInternerInferExt, + traits::{Obligation, ObligationCause}, + }, + obligation_ctxt::ObligationCtxt, + }, +}; + +// rustc does not have a cycle handling for the `specializes` query, meaning a cycle is a bug, +// and indeed I was unable to cause cycles even with erroneous code. However, in r-a we can +// create a cycle if there is an error in the impl's where clauses. I believe well formed code +// cannot create a cycle, but a cycle handler is required nevertheless. +fn specializes_cycle( + _db: &dyn HirDatabase, + _specializing_impl_def_id: ImplId, + _parent_impl_def_id: ImplId, +) -> bool { + false +} + +/// Is `specializing_impl_def_id` a specialization of `parent_impl_def_id`? +/// +/// For every type that could apply to `specializing_impl_def_id`, we prove that +/// the `parent_impl_def_id` also applies (i.e. it has a valid impl header and +/// its where-clauses hold). +/// +/// For the purposes of const traits, we also check that the specializing +/// impl is not more restrictive than the parent impl. That is, if the +/// `parent_impl_def_id` is a const impl (conditionally based off of some `[const]` +/// bounds), then `specializing_impl_def_id` must also be const for the same +/// set of types. +#[salsa::tracked(cycle_result = specializes_cycle)] +pub(crate) fn specializes( + db: &dyn HirDatabase, + specializing_impl_def_id: ImplId, + parent_impl_def_id: ImplId, +) -> bool { + let module = specializing_impl_def_id.loc(db).container; + + // We check that the specializing impl comes from a crate that has specialization enabled. + // + // We don't really care if the specialized impl (the parent) is in a crate that has + // specialization enabled, since it's not being specialized. + // + // rustc also checks whether the specializing impls comes from a macro marked + // `#[allow_internal_unstable(specialization)]`, but `#[allow_internal_unstable]` + // is an internal feature, std is not using it for specialization nor is likely to + // ever use it, and we don't have the span information necessary to replicate that. + let def_map = crate_def_map(db, module.krate()); + if !def_map.is_unstable_feature_enabled(&sym::specialization) + && !def_map.is_unstable_feature_enabled(&sym::min_specialization) + { + return false; + } + + let interner = DbInterner::new_with(db, Some(module.krate()), module.containing_block()); + + let specializing_impl_signature = db.impl_signature(specializing_impl_def_id); + let parent_impl_signature = db.impl_signature(parent_impl_def_id); + + // We determine whether there's a subset relationship by: + // + // - replacing bound vars with placeholders in impl1, + // - assuming the where clauses for impl1, + // - instantiating impl2 with fresh inference variables, + // - unifying, + // - attempting to prove the where clauses for impl2 + // + // The last three steps are encapsulated in `fulfill_implication`. + // + // See RFC 1210 for more details and justification. + + // Currently we do not allow e.g., a negative impl to specialize a positive one + if specializing_impl_signature.is_negative() != parent_impl_signature.is_negative() { + return false; + } + + // create a parameter environment corresponding to an identity instantiation of the specializing impl, + // i.e. the most generic instantiation of the specializing impl. + let param_env = db.trait_environment(specializing_impl_def_id.into()).env; + + // Create an infcx, taking the predicates of the specializing impl as assumptions: + let infcx = interner.infer_ctxt().build(TypingMode::non_body_analysis()); + + let specializing_impl_trait_ref = + db.impl_trait(specializing_impl_def_id).unwrap().instantiate_identity(); + let cause = &ObligationCause::dummy(); + debug!( + "fulfill_implication({:?}, trait_ref={:?} |- {:?} applies)", + param_env, specializing_impl_trait_ref, parent_impl_def_id + ); + + // Attempt to prove that the parent impl applies, given all of the above. + + let mut ocx = ObligationCtxt::new(&infcx); + + let parent_args = infcx.fresh_args_for_item(parent_impl_def_id.into()); + let parent_impl_trait_ref = db + .impl_trait(parent_impl_def_id) + .expect("expected source impl to be a trait impl") + .instantiate(interner, parent_args); + + // do the impls unify? If not, no specialization. + let Ok(()) = ocx.eq(cause, param_env, specializing_impl_trait_ref, parent_impl_trait_ref) + else { + return false; + }; + + // Now check that the source trait ref satisfies all the where clauses of the target impl. + // This is not just for correctness; we also need this to constrain any params that may + // only be referenced via projection predicates. + if let Some(predicates) = + db.generic_predicates(parent_impl_def_id.into()).instantiate(interner, parent_args) + { + ocx.register_obligations( + predicates + .map(|predicate| Obligation::new(interner, cause.clone(), param_env, predicate)), + ); + } + + let errors = ocx.evaluate_obligations_error_on_ambiguity(); + if !errors.is_empty() { + // no dice! + debug!( + "fulfill_implication: for impls on {:?} and {:?}, \ + could not fulfill: {:?} given {:?}", + specializing_impl_trait_ref, parent_impl_trait_ref, errors, param_env + ); + return false; + } + + // FIXME: Check impl constness (when we implement const impls). + + debug!( + "fulfill_implication: an impl for {:?} specializes {:?}", + specializing_impl_trait_ref, parent_impl_trait_ref + ); + + true +} diff --git a/src/tools/rust-analyzer/crates/intern/src/symbol/symbols.rs b/src/tools/rust-analyzer/crates/intern/src/symbol/symbols.rs index 920bdd9568fc..756377fe56f7 100644 --- a/src/tools/rust-analyzer/crates/intern/src/symbol/symbols.rs +++ b/src/tools/rust-analyzer/crates/intern/src/symbol/symbols.rs @@ -517,4 +517,6 @@ define_symbols! { precision, width, never_type_fallback, + specialization, + min_specialization, }