Skip to content

Commit 3d6f276

Browse files
authored
Rollup merge of #93175 - spastorino:negative-traits-coherence-new, r=nikomatsakis
Implement stable overlap check considering negative traits This PR implement the new disjointness rules for overlap check described in https://rust-lang.github.io/negative-impls-initiative/explainer/coherence-check.html#new-disjointness-rules r? ``@nikomatsakis``
2 parents 677126c + 8189bac commit 3d6f276

File tree

5 files changed

+229
-61
lines changed

5 files changed

+229
-61
lines changed

compiler/rustc_feature/src/builtin_attrs.rs

+1
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,7 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
697697
rustc_attr!(TEST, rustc_capture_analysis, Normal, template!(Word), WarnFollowing),
698698
rustc_attr!(TEST, rustc_insignificant_dtor, Normal, template!(Word), WarnFollowing),
699699
rustc_attr!(TEST, rustc_strict_coherence, Normal, template!(Word), WarnFollowing),
700+
rustc_attr!(TEST, rustc_with_negative_coherence, Normal, template!(Word), WarnFollowing),
700701
rustc_attr!(TEST, rustc_variance, Normal, template!(Word), WarnFollowing),
701702
rustc_attr!(TEST, rustc_layout, Normal, template!(List: "field1, field2, ..."), WarnFollowing),
702703
rustc_attr!(TEST, rustc_regions, Normal, template!(Word), WarnFollowing),

compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1204,6 +1204,7 @@ symbols! {
12041204
rustc_trivial_field_reads,
12051205
rustc_unsafe_specialization_marker,
12061206
rustc_variance,
1207+
rustc_with_negative_coherence,
12071208
rustdoc,
12081209
rustdoc_internals,
12091210
rustfmt,

compiler/rustc_trait_selection/src/traits/coherence.rs

+201-61
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
use crate::infer::{CombinedSnapshot, InferOk, TyCtxtInferExt};
88
use crate::traits::query::evaluate_obligation::InferCtxtExt;
99
use crate::traits::select::IntercrateAmbiguityCause;
10+
use crate::traits::util::impl_trait_ref_and_oblig;
1011
use crate::traits::SkipLeakCheck;
1112
use crate::traits::{
12-
self, Normalized, Obligation, ObligationCause, PredicateObligation, SelectionContext,
13+
self, FulfillmentContext, Normalized, Obligation, ObligationCause, PredicateObligation,
14+
PredicateObligations, SelectionContext,
1315
};
1416
use rustc_hir::def_id::{DefId, LOCAL_CRATE};
1517
use rustc_middle::ty::fast_reject::{self, SimplifyParams, StripReferences};
@@ -135,45 +137,83 @@ fn with_fresh_ty_vars<'cx, 'tcx>(
135137
header
136138
}
137139

140+
/// What kind of overlap check are we doing -- this exists just for testing and feature-gating
141+
/// purposes.
142+
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
143+
enum OverlapMode {
144+
/// The 1.0 rules (either types fail to unify, or where clauses are not implemented for crate-local types)
145+
Stable,
146+
/// Feature-gated test: Stable, *or* there is an explicit negative impl that rules out one of the where-clauses.
147+
WithNegative,
148+
/// Just check for negative impls, not for "where clause not implemented": used for testing.
149+
Strict,
150+
}
151+
152+
impl OverlapMode {
153+
fn use_negative_impl(&self) -> bool {
154+
*self == OverlapMode::Strict || *self == OverlapMode::WithNegative
155+
}
156+
157+
fn use_implicit_negative(&self) -> bool {
158+
*self == OverlapMode::Stable || *self == OverlapMode::WithNegative
159+
}
160+
}
161+
162+
fn overlap_mode<'tcx>(tcx: TyCtxt<'tcx>, impl1_def_id: DefId, impl2_def_id: DefId) -> OverlapMode {
163+
if tcx.has_attr(impl1_def_id, sym::rustc_strict_coherence)
164+
!= tcx.has_attr(impl2_def_id, sym::rustc_strict_coherence)
165+
{
166+
bug!("Use strict coherence on both impls",);
167+
}
168+
169+
if tcx.has_attr(impl1_def_id, sym::rustc_with_negative_coherence)
170+
!= tcx.has_attr(impl2_def_id, sym::rustc_with_negative_coherence)
171+
{
172+
bug!("Use with negative coherence on both impls",);
173+
}
174+
175+
if tcx.has_attr(impl1_def_id, sym::rustc_strict_coherence) {
176+
OverlapMode::Strict
177+
} else if tcx.has_attr(impl1_def_id, sym::rustc_with_negative_coherence) {
178+
OverlapMode::WithNegative
179+
} else {
180+
OverlapMode::Stable
181+
}
182+
}
183+
138184
/// Can both impl `a` and impl `b` be satisfied by a common type (including
139185
/// where-clauses)? If so, returns an `ImplHeader` that unifies the two impls.
140186
fn overlap<'cx, 'tcx>(
141187
selcx: &mut SelectionContext<'cx, 'tcx>,
142188
skip_leak_check: SkipLeakCheck,
143-
a_def_id: DefId,
144-
b_def_id: DefId,
189+
impl1_def_id: DefId,
190+
impl2_def_id: DefId,
145191
) -> Option<OverlapResult<'tcx>> {
146-
debug!("overlap(a_def_id={:?}, b_def_id={:?})", a_def_id, b_def_id);
192+
debug!("overlap(impl1_def_id={:?}, impl2_def_id={:?})", impl1_def_id, impl2_def_id);
147193

148194
selcx.infcx().probe_maybe_skip_leak_check(skip_leak_check.is_yes(), |snapshot| {
149-
overlap_within_probe(selcx, skip_leak_check, a_def_id, b_def_id, snapshot)
195+
overlap_within_probe(selcx, skip_leak_check, impl1_def_id, impl2_def_id, snapshot)
150196
})
151197
}
152198

153199
fn overlap_within_probe<'cx, 'tcx>(
154200
selcx: &mut SelectionContext<'cx, 'tcx>,
155201
skip_leak_check: SkipLeakCheck,
156-
a_def_id: DefId,
157-
b_def_id: DefId,
202+
impl1_def_id: DefId,
203+
impl2_def_id: DefId,
158204
snapshot: &CombinedSnapshot<'_, 'tcx>,
159205
) -> Option<OverlapResult<'tcx>> {
160-
fn loose_check<'cx, 'tcx>(
161-
selcx: &mut SelectionContext<'cx, 'tcx>,
162-
o: &PredicateObligation<'tcx>,
163-
) -> bool {
164-
!selcx.predicate_may_hold_fatal(o)
165-
}
206+
let infcx = selcx.infcx();
207+
let tcx = infcx.tcx;
166208

167-
fn strict_check<'cx, 'tcx>(
168-
selcx: &SelectionContext<'cx, 'tcx>,
169-
o: &PredicateObligation<'tcx>,
170-
) -> bool {
171-
let infcx = selcx.infcx();
172-
let tcx = infcx.tcx;
173-
o.flip_polarity(tcx)
174-
.as_ref()
175-
.map(|o| selcx.infcx().predicate_must_hold_modulo_regions(o))
176-
.unwrap_or(false)
209+
let overlap_mode = overlap_mode(tcx, impl1_def_id, impl2_def_id);
210+
211+
if overlap_mode.use_negative_impl() {
212+
if negative_impl(selcx, impl1_def_id, impl2_def_id)
213+
|| negative_impl(selcx, impl2_def_id, impl1_def_id)
214+
{
215+
return None;
216+
}
177217
}
178218

179219
// For the purposes of this check, we don't bring any placeholder
@@ -182,26 +222,61 @@ fn overlap_within_probe<'cx, 'tcx>(
182222
// empty environment.
183223
let param_env = ty::ParamEnv::empty();
184224

185-
let a_impl_header = with_fresh_ty_vars(selcx, param_env, a_def_id);
186-
let b_impl_header = with_fresh_ty_vars(selcx, param_env, b_def_id);
225+
let impl1_header = with_fresh_ty_vars(selcx, param_env, impl1_def_id);
226+
let impl2_header = with_fresh_ty_vars(selcx, param_env, impl2_def_id);
187227

188-
debug!("overlap: a_impl_header={:?}", a_impl_header);
189-
debug!("overlap: b_impl_header={:?}", b_impl_header);
228+
debug!("overlap: impl1_header={:?}", impl1_header);
229+
debug!("overlap: impl2_header={:?}", impl2_header);
190230

191-
// Do `a` and `b` unify? If not, no overlap.
192-
let obligations = match selcx
193-
.infcx()
194-
.at(&ObligationCause::dummy(), param_env)
195-
.eq_impl_headers(&a_impl_header, &b_impl_header)
196-
{
197-
Ok(InferOk { obligations, value: () }) => obligations,
198-
Err(_) => {
231+
let obligations = equate_impl_headers(selcx, &impl1_header, &impl2_header)?;
232+
debug!("overlap: unification check succeeded");
233+
234+
if overlap_mode.use_implicit_negative() {
235+
if implicit_negative(selcx, param_env, &impl1_header, impl2_header, obligations) {
199236
return None;
200237
}
201-
};
238+
}
202239

203-
debug!("overlap: unification check succeeded");
240+
if !skip_leak_check.is_yes() {
241+
if infcx.leak_check(true, snapshot).is_err() {
242+
debug!("overlap: leak check failed");
243+
return None;
244+
}
245+
}
246+
247+
let intercrate_ambiguity_causes = selcx.take_intercrate_ambiguity_causes();
248+
debug!("overlap: intercrate_ambiguity_causes={:#?}", intercrate_ambiguity_causes);
249+
250+
let involves_placeholder =
251+
matches!(selcx.infcx().region_constraints_added_in_snapshot(snapshot), Some(true));
252+
253+
let impl_header = selcx.infcx().resolve_vars_if_possible(impl1_header);
254+
Some(OverlapResult { impl_header, intercrate_ambiguity_causes, involves_placeholder })
255+
}
256+
257+
fn equate_impl_headers<'cx, 'tcx>(
258+
selcx: &mut SelectionContext<'cx, 'tcx>,
259+
impl1_header: &ty::ImplHeader<'tcx>,
260+
impl2_header: &ty::ImplHeader<'tcx>,
261+
) -> Option<PredicateObligations<'tcx>> {
262+
// Do `a` and `b` unify? If not, no overlap.
263+
selcx
264+
.infcx()
265+
.at(&ObligationCause::dummy(), ty::ParamEnv::empty())
266+
.eq_impl_headers(impl1_header, impl2_header)
267+
.map(|infer_ok| infer_ok.obligations)
268+
.ok()
269+
}
204270

271+
/// Given impl1 and impl2 check if both impls can be satisfied by a common type (including
272+
/// where-clauses) If so, return false, otherwise return true, they are disjoint.
273+
fn implicit_negative<'cx, 'tcx>(
274+
selcx: &mut SelectionContext<'cx, 'tcx>,
275+
param_env: ty::ParamEnv<'tcx>,
276+
impl1_header: &ty::ImplHeader<'tcx>,
277+
impl2_header: ty::ImplHeader<'tcx>,
278+
obligations: PredicateObligations<'tcx>,
279+
) -> bool {
205280
// There's no overlap if obligations are unsatisfiable or if the obligation negated is
206281
// satisfied.
207282
//
@@ -225,11 +300,11 @@ fn overlap_within_probe<'cx, 'tcx>(
225300
// at some point an impl for `&'?a str: Error` could be added.
226301
let infcx = selcx.infcx();
227302
let tcx = infcx.tcx;
228-
let opt_failing_obligation = a_impl_header
303+
let opt_failing_obligation = impl1_header
229304
.predicates
230305
.iter()
231306
.copied()
232-
.chain(b_impl_header.predicates)
307+
.chain(impl2_header.predicates)
233308
.map(|p| infcx.resolve_vars_if_possible(p))
234309
.map(|p| Obligation {
235310
cause: ObligationCause::dummy(),
@@ -239,40 +314,105 @@ fn overlap_within_probe<'cx, 'tcx>(
239314
})
240315
.chain(obligations)
241316
.find(|o| {
242-
// if both impl headers are set to strict coherence it means that this will be accepted
243-
// only if it's stated that T: !Trait. So only prove that the negated obligation holds.
244-
if tcx.has_attr(a_def_id, sym::rustc_strict_coherence)
245-
&& tcx.has_attr(b_def_id, sym::rustc_strict_coherence)
246-
{
247-
strict_check(selcx, o)
248-
} else {
249-
loose_check(selcx, o) || tcx.features().negative_impls && strict_check(selcx, o)
250-
}
317+
loose_check(selcx, o) || tcx.features().negative_impls && negative_impl_exists(selcx, o)
251318
});
252319
// FIXME: the call to `selcx.predicate_may_hold_fatal` above should be ported
253320
// to the canonical trait query form, `infcx.predicate_may_hold`, once
254321
// the new system supports intercrate mode (which coherence needs).
255322

256323
if let Some(failing_obligation) = opt_failing_obligation {
257324
debug!("overlap: obligation unsatisfiable {:?}", failing_obligation);
258-
return None;
325+
true
326+
} else {
327+
false
259328
}
329+
}
260330

261-
if !skip_leak_check.is_yes() {
262-
if infcx.leak_check(true, snapshot).is_err() {
263-
debug!("overlap: leak check failed");
264-
return None;
265-
}
266-
}
331+
/// Given impl1 and impl2 check if both impls are never satisfied by a common type (including
332+
/// where-clauses) If so, return true, they are disjoint and false otherwise.
333+
fn negative_impl<'cx, 'tcx>(
334+
selcx: &mut SelectionContext<'cx, 'tcx>,
335+
impl1_def_id: DefId,
336+
impl2_def_id: DefId,
337+
) -> bool {
338+
let tcx = selcx.infcx().tcx;
267339

268-
let impl_header = selcx.infcx().resolve_vars_if_possible(a_impl_header);
269-
let intercrate_ambiguity_causes = selcx.take_intercrate_ambiguity_causes();
270-
debug!("overlap: intercrate_ambiguity_causes={:#?}", intercrate_ambiguity_causes);
340+
// create a parameter environment corresponding to a (placeholder) instantiation of impl1
341+
let impl1_env = tcx.param_env(impl1_def_id);
342+
let impl1_trait_ref = tcx.impl_trait_ref(impl1_def_id).unwrap();
271343

272-
let involves_placeholder =
273-
matches!(selcx.infcx().region_constraints_added_in_snapshot(snapshot), Some(true));
344+
// Create an infcx, taking the predicates of impl1 as assumptions:
345+
tcx.infer_ctxt().enter(|infcx| {
346+
// Normalize the trait reference. The WF rules ought to ensure
347+
// that this always succeeds.
348+
let impl1_trait_ref = match traits::fully_normalize(
349+
&infcx,
350+
FulfillmentContext::new(),
351+
ObligationCause::dummy(),
352+
impl1_env,
353+
impl1_trait_ref,
354+
) {
355+
Ok(impl1_trait_ref) => impl1_trait_ref,
356+
Err(err) => {
357+
bug!("failed to fully normalize {:?}: {:?}", impl1_trait_ref, err);
358+
}
359+
};
360+
361+
// Attempt to prove that impl2 applies, given all of the above.
362+
let selcx = &mut SelectionContext::new(&infcx);
363+
let impl2_substs = infcx.fresh_substs_for_item(DUMMY_SP, impl2_def_id);
364+
let (impl2_trait_ref, obligations) =
365+
impl_trait_ref_and_oblig(selcx, impl1_env, impl2_def_id, impl2_substs);
366+
367+
// do the impls unify? If not, not disjoint.
368+
let more_obligations = match infcx
369+
.at(&ObligationCause::dummy(), impl1_env)
370+
.eq(impl1_trait_ref, impl2_trait_ref)
371+
{
372+
Ok(InferOk { obligations, .. }) => obligations,
373+
Err(_) => {
374+
debug!(
375+
"explicit_disjoint: {:?} does not unify with {:?}",
376+
impl1_trait_ref, impl2_trait_ref
377+
);
378+
return false;
379+
}
380+
};
274381

275-
Some(OverlapResult { impl_header, intercrate_ambiguity_causes, involves_placeholder })
382+
let opt_failing_obligation = obligations
383+
.into_iter()
384+
.chain(more_obligations)
385+
.find(|o| negative_impl_exists(selcx, o));
386+
387+
if let Some(failing_obligation) = opt_failing_obligation {
388+
debug!("overlap: obligation unsatisfiable {:?}", failing_obligation);
389+
true
390+
} else {
391+
false
392+
}
393+
})
394+
}
395+
396+
fn loose_check<'cx, 'tcx>(
397+
selcx: &mut SelectionContext<'cx, 'tcx>,
398+
o: &PredicateObligation<'tcx>,
399+
) -> bool {
400+
!selcx.predicate_may_hold_fatal(o)
401+
}
402+
403+
fn negative_impl_exists<'cx, 'tcx>(
404+
selcx: &SelectionContext<'cx, 'tcx>,
405+
o: &PredicateObligation<'tcx>,
406+
) -> bool {
407+
let infcx = selcx.infcx();
408+
let tcx = infcx.tcx;
409+
o.flip_polarity(tcx)
410+
.as_ref()
411+
.map(|o| {
412+
// FIXME This isn't quite correct, regions should be included
413+
selcx.infcx().predicate_must_hold_modulo_regions(o)
414+
})
415+
.unwrap_or(false)
276416
}
277417

278418
pub fn trait_ref_is_knowable<'tcx>(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#![crate_type = "lib"]
2+
#![feature(negative_impls)]
3+
#![feature(rustc_attrs)]
4+
5+
pub trait Future {}
6+
7+
#[rustc_with_negative_coherence]
8+
impl<E> !Future for Option<E> where E: Sized {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// check-pass
2+
// aux-build:option_future.rs
3+
//
4+
// Check that if we promise to not impl what would overlap it doesn't actually overlap
5+
6+
#![feature(rustc_attrs)]
7+
8+
extern crate option_future as lib;
9+
use lib::Future;
10+
11+
trait Termination {}
12+
13+
#[rustc_with_negative_coherence]
14+
impl<E> Termination for Option<E> where E: Sized {}
15+
#[rustc_with_negative_coherence]
16+
impl<F> Termination for F where F: Future + Sized {}
17+
18+
fn main() {}

0 commit comments

Comments
 (0)