Skip to content

Commit b12c794

Browse files
authored
Merge pull request rust-lang#20893 from ChayimFriedman2/specialization-ns
fix: Implement `Interner::impl_specializes()`
2 parents 2b5b7cc + c1ecea6 commit b12c794

File tree

6 files changed

+163
-9
lines changed

6 files changed

+163
-9
lines changed

src/tools/rust-analyzer/crates/hir-ty/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ mod infer;
2727
mod inhabitedness;
2828
mod lower;
2929
pub mod next_solver;
30+
mod specialization;
3031
mod target_feature;
3132
mod utils;
3233

src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/tests.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -636,16 +636,13 @@ fn main() {
636636
);
637637
}
638638

639-
#[ignore = "
640-
FIXME(next-solver):
641-
This does not work currently because I replaced homemade selection with selection by the trait solver;
642-
This will work once we implement `Interner::impl_specializes()` properly.
643-
"]
644639
#[test]
645640
fn specialization_array_clone() {
646641
check_pass(
647642
r#"
648643
//- minicore: copy, derive, slice, index, coerce_unsized, panic
644+
#![feature(min_specialization)]
645+
649646
impl<T: Clone, const N: usize> Clone for [T; N] {
650647
#[inline]
651648
fn clone(&self) -> Self {

src/tools/rust-analyzer/crates/hir-ty/src/next_solver/def_id.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ macro_rules! declare_id_wrapper {
211211

212212
impl std::fmt::Debug for $name {
213213
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
214-
std::fmt::Debug::fmt(&self.0, f)
214+
std::fmt::Debug::fmt(&SolverDefId::from(self.0), f)
215215
}
216216
}
217217

src/tools/rust-analyzer/crates/hir-ty/src/next_solver/interner.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1922,10 +1922,14 @@ impl<'db> Interner for DbInterner<'db> {
19221922

19231923
fn impl_specializes(
19241924
self,
1925-
_specializing_impl_def_id: Self::ImplId,
1926-
_parent_impl_def_id: Self::ImplId,
1925+
specializing_impl_def_id: Self::ImplId,
1926+
parent_impl_def_id: Self::ImplId,
19271927
) -> bool {
1928-
false
1928+
crate::specialization::specializes(
1929+
self.db,
1930+
specializing_impl_def_id.0,
1931+
parent_impl_def_id.0,
1932+
)
19291933
}
19301934

19311935
fn next_trait_solver_globally(self) -> bool {
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
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+
}

src/tools/rust-analyzer/crates/intern/src/symbol/symbols.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,4 +517,6 @@ define_symbols! {
517517
precision,
518518
width,
519519
never_type_fallback,
520+
specialization,
521+
min_specialization,
520522
}

0 commit comments

Comments
 (0)