Skip to content

Commit e91fc1b

Browse files
Reimplement specialization for const traits
1 parent d49be02 commit e91fc1b

File tree

4 files changed

+142
-25
lines changed

4 files changed

+142
-25
lines changed

compiler/rustc_trait_selection/src/traits/specialize/mod.rs

+112-18
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use rustc_data_structures::fx::FxIndexSet;
1515
use rustc_errors::codes::*;
1616
use rustc_errors::{Diag, EmissionGuarantee};
1717
use rustc_hir::def_id::{DefId, LocalDefId};
18+
use rustc_infer::traits::Obligation;
1819
use rustc_middle::bug;
1920
use rustc_middle::query::LocalCrate;
2021
use rustc_middle::ty::print::PrintTraitRefExt as _;
@@ -230,23 +231,26 @@ pub(super) fn specialization_enabled_in(tcx: TyCtxt<'_>, _: LocalCrate) -> bool
230231
/// `impl1` specializes `impl2` if it applies to a subset of the types `impl2` applies
231232
/// to.
232233
#[instrument(skip(tcx), level = "debug")]
233-
pub(super) fn specializes(tcx: TyCtxt<'_>, (impl1_def_id, impl2_def_id): (DefId, DefId)) -> bool {
234+
pub(super) fn specializes(
235+
tcx: TyCtxt<'_>,
236+
(specializing_impl_def_id, parent_impl_def_id): (DefId, DefId),
237+
) -> bool {
234238
// We check that the specializing impl comes from a crate that has specialization enabled,
235239
// or if the specializing impl is marked with `allow_internal_unstable`.
236240
//
237241
// We don't really care if the specialized impl (the parent) is in a crate that has
238242
// specialization enabled, since it's not being specialized, and it's already been checked
239243
// for coherence.
240-
if !tcx.specialization_enabled_in(impl1_def_id.krate) {
241-
let span = tcx.def_span(impl1_def_id);
244+
if !tcx.specialization_enabled_in(specializing_impl_def_id.krate) {
245+
let span = tcx.def_span(specializing_impl_def_id);
242246
if !span.allows_unstable(sym::specialization)
243247
&& !span.allows_unstable(sym::min_specialization)
244248
{
245249
return false;
246250
}
247251
}
248252

249-
let impl1_trait_header = tcx.impl_trait_header(impl1_def_id).unwrap();
253+
let specializing_impl_trait_header = tcx.impl_trait_header(specializing_impl_def_id).unwrap();
250254

251255
// We determine whether there's a subset relationship by:
252256
//
@@ -261,27 +265,117 @@ pub(super) fn specializes(tcx: TyCtxt<'_>, (impl1_def_id, impl2_def_id): (DefId,
261265
// See RFC 1210 for more details and justification.
262266

263267
// Currently we do not allow e.g., a negative impl to specialize a positive one
264-
if impl1_trait_header.polarity != tcx.impl_polarity(impl2_def_id) {
268+
if specializing_impl_trait_header.polarity != tcx.impl_polarity(parent_impl_def_id) {
265269
return false;
266270
}
267271

268-
// create a parameter environment corresponding to an identity instantiation of impl1,
269-
// i.e. the most generic instantiation of impl1.
270-
let param_env = tcx.param_env(impl1_def_id);
272+
// create a parameter environment corresponding to an identity instantiation of the specializing impl,
273+
// i.e. the most generic instantiation of the specializing impl.
274+
let param_env = tcx.param_env(specializing_impl_def_id);
271275

272-
// Create an infcx, taking the predicates of impl1 as assumptions:
276+
// Create an infcx, taking the predicates of the specializing impl as assumptions:
273277
let infcx = tcx.infer_ctxt().build(TypingMode::non_body_analysis());
274278

275-
// Attempt to prove that impl2 applies, given all of the above.
276-
fulfill_implication(
277-
&infcx,
279+
let specializing_impl_trait_ref =
280+
specializing_impl_trait_header.trait_ref.instantiate_identity();
281+
let cause = &ObligationCause::dummy();
282+
debug!(
283+
"fulfill_implication({:?}, trait_ref={:?} |- {:?} applies)",
284+
param_env, specializing_impl_trait_ref, parent_impl_def_id
285+
);
286+
287+
// Attempt to prove that the parent impl applies, given all of the above.
288+
289+
let ocx = ObligationCtxt::new(&infcx);
290+
let specializing_impl_trait_ref = ocx.normalize(cause, param_env, specializing_impl_trait_ref);
291+
292+
if !ocx.select_all_or_error().is_empty() {
293+
infcx.dcx().span_delayed_bug(
294+
infcx.tcx.def_span(specializing_impl_def_id),
295+
format!("failed to fully normalize {specializing_impl_trait_ref}"),
296+
);
297+
return false;
298+
}
299+
300+
let parent_args = infcx.fresh_args_for_item(DUMMY_SP, parent_impl_def_id);
301+
let parent_impl_trait_ref = ocx.normalize(
302+
cause,
278303
param_env,
279-
impl1_trait_header.trait_ref.instantiate_identity(),
280-
impl1_def_id,
281-
impl2_def_id,
282-
&ObligationCause::dummy(),
283-
)
284-
.is_ok()
304+
infcx
305+
.tcx
306+
.impl_trait_ref(parent_impl_def_id)
307+
.expect("expected source impl to be a trait impl")
308+
.instantiate(infcx.tcx, parent_args),
309+
);
310+
311+
// do the impls unify? If not, no specialization.
312+
let Ok(()) = ocx.eq(cause, param_env, specializing_impl_trait_ref, parent_impl_trait_ref)
313+
else {
314+
return false;
315+
};
316+
317+
// Now check that the source trait ref satisfies all the where clauses of the target impl.
318+
// This is not just for correctness; we also need this to constrain any params that may
319+
// only be referenced via projection predicates.
320+
let predicates = ocx.normalize(
321+
cause,
322+
param_env,
323+
infcx.tcx.predicates_of(parent_impl_def_id).instantiate(infcx.tcx, parent_args),
324+
);
325+
let obligations = predicates_for_generics(|_, _| cause.clone(), param_env, predicates);
326+
ocx.register_obligations(obligations);
327+
328+
let errors = ocx.select_all_or_error();
329+
if !errors.is_empty() {
330+
// no dice!
331+
debug!(
332+
"fulfill_implication: for impls on {:?} and {:?}, \
333+
could not fulfill: {:?} given {:?}",
334+
specializing_impl_trait_ref,
335+
parent_impl_trait_ref,
336+
errors,
337+
param_env.caller_bounds()
338+
);
339+
return false;
340+
}
341+
342+
// If the parent impl is const, then the specializing impl must be const.
343+
if tcx.is_conditionally_const(parent_impl_def_id) {
344+
let const_conditions = ocx.normalize(
345+
cause,
346+
param_env,
347+
infcx.tcx.const_conditions(parent_impl_def_id).instantiate(infcx.tcx, parent_args),
348+
);
349+
ocx.register_obligations(const_conditions.into_iter().map(|(trait_ref, _)| {
350+
Obligation::new(
351+
infcx.tcx,
352+
cause.clone(),
353+
param_env,
354+
trait_ref.to_host_effect_clause(infcx.tcx, ty::BoundConstness::Maybe),
355+
)
356+
}));
357+
358+
let errors = ocx.select_all_or_error();
359+
if !errors.is_empty() {
360+
// no dice!
361+
debug!(
362+
"fulfill_implication: for impls on {:?} and {:?}, \
363+
could not fulfill: {:?} given {:?}",
364+
specializing_impl_trait_ref,
365+
parent_impl_trait_ref,
366+
errors,
367+
param_env.caller_bounds()
368+
);
369+
return false;
370+
}
371+
}
372+
373+
debug!(
374+
"fulfill_implication: an impl for {:?} specializes {:?}",
375+
specializing_impl_trait_ref, parent_impl_trait_ref
376+
);
377+
378+
true
285379
}
286380

287381
/// Query provider for `specialization_graph_of`.
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,31 @@
1-
error: cannot specialize on const impl with non-const impl
1+
error[E0119]: conflicting implementations of trait `Bar`
22
--> $DIR/const-default-bound-non-const-specialized-bound.rs:28:1
33
|
4+
LL | / impl<T> const Bar for T
5+
LL | | where
6+
LL | | T: ~const Foo,
7+
| |__________________- first implementation here
8+
...
49
LL | / impl<T> Bar for T
510
LL | | where
611
LL | | T: Foo, //FIXME ~ ERROR missing `~const` qualifier
712
LL | | T: Specialize,
8-
| |__________________^
13+
| |__________________^ conflicting implementation
14+
15+
error[E0119]: conflicting implementations of trait `Baz`
16+
--> $DIR/const-default-bound-non-const-specialized-bound.rs:48:1
17+
|
18+
LL | / impl<T> const Baz for T
19+
LL | | where
20+
LL | | T: ~const Foo,
21+
| |__________________- first implementation here
22+
...
23+
LL | / impl<T> const Baz for T //FIXME ~ ERROR conflicting implementations of trait `Baz`
24+
LL | | where
25+
LL | | T: Foo,
26+
LL | | T: Specialize,
27+
| |__________________^ conflicting implementation
928

10-
error: aborting due to 1 previous error
29+
error: aborting due to 2 previous errors
1130

31+
For more information about this error, try `rustc --explain E0119`.

tests/ui/traits/const-traits/specializing-constness.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@ impl<T: ~const Spec> const A for T {
2121
}
2222

2323
impl<T: Spec + Sup> A for T {
24-
//~^ ERROR: cannot specialize
25-
//FIXME(const_trait_impl) ~| ERROR: missing `~const` qualifier
24+
//~^ ERROR conflicting implementations of trait `A`
2625
fn a() -> u32 {
2726
3
2827
}
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
error: cannot specialize on const impl with non-const impl
1+
error[E0119]: conflicting implementations of trait `A`
22
--> $DIR/specializing-constness.rs:23:1
33
|
4+
LL | impl<T: ~const Spec> const A for T {
5+
| ---------------------------------- first implementation here
6+
...
47
LL | impl<T: Spec + Sup> A for T {
5-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
8+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation
69

710
error: aborting due to 1 previous error
811

12+
For more information about this error, try `rustc --explain E0119`.

0 commit comments

Comments
 (0)