-
Notifications
You must be signed in to change notification settings - Fork 12.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Handle cycles in overlap with negative impls #109673
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -120,6 +120,48 @@ pub fn overlapping_impls( | |
Some(overlap(selcx, skip_leak_check, impl1_def_id, impl2_def_id, overlap_mode).unwrap()) | ||
} | ||
|
||
/// Given an impl_def_id that "positively" implement a trait, check if the "negative" holds. | ||
pub fn negative_impl_holds(tcx: TyCtxt<'_>, impl_def_id: DefId, overlap_mode: OverlapMode) -> bool { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. s/negative_impl_holds/negative_impl_may_hold/ |
||
debug!("negative_impl_holds(impl1_header={:?}, overlap_mode={:?})", impl_def_id, overlap_mode); | ||
// `for<T> (Vec<u32>, T): Trait` | ||
let header = tcx.impl_trait_ref(impl_def_id).unwrap(); | ||
|
||
let infcx = tcx | ||
.infer_ctxt() | ||
.with_opaque_type_inference(DefiningAnchor::Bubble) | ||
.intercrate(true) | ||
.build(); | ||
|
||
// `[?t]` | ||
let infer_substs = infcx.fresh_substs_for_item(DUMMY_SP, impl_def_id); | ||
|
||
// `(Vec<u32>, ?t): Trait` | ||
let trait_ref = header.subst(tcx, infer_substs); | ||
|
||
// `(Vec<u32>, ?t): !Trait` | ||
let trait_pred = tcx.mk_predicate(ty::Binder::dummy(ty::PredicateKind::Clause( | ||
ty::Clause::Trait(ty::TraitPredicate { | ||
trait_ref, | ||
constness: ty::BoundConstness::NotConst, | ||
polarity: ty::ImplPolarity::Negative, | ||
}), | ||
))); | ||
|
||
// Ideally we would use param_env(impl_def_id) but that's unsound today. | ||
compiler-errors marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let param_env = ty::ParamEnv::empty(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To do this properly, what we ought to do is get the parameter environment and then apply the impl<A..Z> SomeTrait<T1..Tn> for T0
where WC we want to test if...
this would be an error, because that means that for at least some instance of this positive impl, there exists a negative impl. Problem is: applying It should be sound to not assume WC is true, because proving There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. inference variables in the param env generally cause issues, especially in the old solver. Don't remember exactly what breaks but iirc @BoxyUwU encountered a bunch of issues recently when she tried it. I think a bigger issue is that the way |
||
|
||
let selcx = &mut SelectionContext::new(&infcx); | ||
selcx | ||
.evaluate_root_obligation(&Obligation::new( | ||
tcx, | ||
ObligationCause::dummy(), | ||
param_env, | ||
trait_pred, | ||
)) | ||
.expect("Overflow should be caught earlier in standard query mode") | ||
.must_apply_modulo_regions() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why use this over Sorry if this question is misunderstanding what this PR is trying to do. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The idea if I got everything correct is that we want to check that there's not a negative impl that applies. This is why I wanted to check for a must. But this is one of the things that I have doubts if using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be may apply modulo regions, not must apply. |
||
} | ||
|
||
fn with_fresh_ty_vars<'cx, 'tcx>( | ||
selcx: &mut SelectionContext<'cx, 'tcx>, | ||
param_env: ty::ParamEnv<'tcx>, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,17 +20,18 @@ pub struct FutureCompatOverlapError<'tcx> { | |
pub kind: FutureCompatOverlapErrorKind, | ||
} | ||
|
||
/// The result of attempting to insert an impl into a group of children. | ||
/// The result of overlap_check, we would attempt to insert an impl into a group of children | ||
/// afterwards. | ||
#[derive(Debug)] | ||
enum Inserted<'tcx> { | ||
/// The impl was inserted as a new child in this group of children. | ||
BecameNewSibling(Option<FutureCompatOverlapError<'tcx>>), | ||
enum OverlapResult<'tcx> { | ||
/// The impl is going to be inserted as a new child in this group of children. | ||
NoOverlap(Option<FutureCompatOverlapError<'tcx>>), | ||
|
||
/// The impl should replace existing impls [X1, ..], because the impl specializes X1, X2, etc. | ||
ReplaceChildren(Vec<DefId>), | ||
SpecializeAll(Vec<DefId>), | ||
|
||
/// The impl is a specialization of an existing child. | ||
ShouldRecurseOn(DefId), | ||
SpecializeOne(DefId), | ||
} | ||
|
||
trait ChildrenExt<'tcx> { | ||
|
@@ -43,7 +44,7 @@ trait ChildrenExt<'tcx> { | |
impl_def_id: DefId, | ||
simplified_self: Option<SimplifiedType>, | ||
overlap_mode: OverlapMode, | ||
) -> Result<Inserted<'tcx>, OverlapError<'tcx>>; | ||
) -> Result<OverlapResult<'tcx>, OverlapError<'tcx>>; | ||
} | ||
|
||
impl<'tcx> ChildrenExt<'tcx> for Children { | ||
|
@@ -90,123 +91,158 @@ impl<'tcx> ChildrenExt<'tcx> for Children { | |
impl_def_id: DefId, | ||
simplified_self: Option<SimplifiedType>, | ||
overlap_mode: OverlapMode, | ||
) -> Result<Inserted<'tcx>, OverlapError<'tcx>> { | ||
let mut last_lint = None; | ||
let mut replace_children = Vec::new(); | ||
|
||
) -> Result<OverlapResult<'tcx>, OverlapError<'tcx>> { | ||
let possible_siblings = match simplified_self { | ||
Some(st) => PotentialSiblings::Filtered(filtered_children(self, st)), | ||
None => PotentialSiblings::Unfiltered(iter_children(self)), | ||
}; | ||
|
||
for possible_sibling in possible_siblings { | ||
debug!(?possible_sibling); | ||
|
||
let create_overlap_error = |overlap: traits::coherence::OverlapResult<'tcx>| { | ||
let trait_ref = overlap.impl_header.trait_ref.unwrap(); | ||
let self_ty = trait_ref.self_ty(); | ||
|
||
OverlapError { | ||
with_impl: possible_sibling, | ||
trait_ref, | ||
// Only report the `Self` type if it has at least | ||
// some outer concrete shell; otherwise, it's | ||
// not adding much information. | ||
self_ty: self_ty.has_concrete_skeleton().then_some(self_ty), | ||
intercrate_ambiguity_causes: overlap.intercrate_ambiguity_causes, | ||
involves_placeholder: overlap.involves_placeholder, | ||
} | ||
}; | ||
|
||
let report_overlap_error = |overlap: traits::coherence::OverlapResult<'tcx>, | ||
last_lint: &mut _| { | ||
// Found overlap, but no specialization; error out or report future-compat warning. | ||
|
||
// Do we *still* get overlap if we disable the future-incompatible modes? | ||
let should_err = traits::overlapping_impls( | ||
tcx, | ||
possible_sibling, | ||
impl_def_id, | ||
traits::SkipLeakCheck::default(), | ||
overlap_mode, | ||
) | ||
.is_some(); | ||
|
||
let error = create_overlap_error(overlap); | ||
|
||
if should_err { | ||
Err(error) | ||
} else { | ||
*last_lint = Some(FutureCompatOverlapError { | ||
error, | ||
kind: FutureCompatOverlapErrorKind::LeakCheck, | ||
}); | ||
|
||
Ok((false, false)) | ||
} | ||
}; | ||
let result = overlap_check_considering_specialization( | ||
tcx, | ||
impl_def_id, | ||
possible_siblings, | ||
overlap_mode, | ||
); | ||
|
||
if let Ok(OverlapResult::NoOverlap(_)) = result { | ||
// No overlap with any potential siblings, so add as a new sibling. | ||
debug!("placing as new sibling"); | ||
self.insert_blindly(tcx, impl_def_id); | ||
} | ||
|
||
result | ||
} | ||
} | ||
|
||
let last_lint_mut = &mut last_lint; | ||
let (le, ge) = traits::overlapping_impls( | ||
fn overlap_check_considering_specialization<'tcx>( | ||
tcx: TyCtxt<'tcx>, | ||
impl_def_id: DefId, | ||
possible_siblings: PotentialSiblings<impl Iterator<Item = DefId>, impl Iterator<Item = DefId>>, | ||
overlap_mode: OverlapMode, | ||
) -> Result<OverlapResult<'tcx>, OverlapError<'tcx>> { | ||
let mut last_lint = None; | ||
let mut replace_children = Vec::new(); | ||
|
||
for possible_sibling in possible_siblings { | ||
debug!(?possible_sibling); | ||
|
||
let create_overlap_error = |overlap: traits::coherence::OverlapResult<'tcx>| { | ||
let trait_ref = overlap.impl_header.trait_ref.unwrap(); | ||
let self_ty = trait_ref.self_ty(); | ||
|
||
OverlapError { | ||
with_impl: possible_sibling, | ||
trait_ref, | ||
// Only report the `Self` type if it has at least | ||
// some outer concrete shell; otherwise, it's | ||
// not adding much information. | ||
self_ty: self_ty.has_concrete_skeleton().then_some(self_ty), | ||
intercrate_ambiguity_causes: overlap.intercrate_ambiguity_causes, | ||
involves_placeholder: overlap.involves_placeholder, | ||
} | ||
}; | ||
|
||
let report_overlap_error = |overlap: traits::coherence::OverlapResult<'tcx>, | ||
last_lint: &mut _| { | ||
// Found overlap, but no specialization; error out or report future-compat warning. | ||
|
||
// Do we *still* get overlap if we disable the future-incompatible modes? | ||
let should_err = traits::overlapping_impls( | ||
tcx, | ||
possible_sibling, | ||
impl_def_id, | ||
traits::SkipLeakCheck::Yes, | ||
traits::SkipLeakCheck::default(), | ||
overlap_mode, | ||
) | ||
.map_or(Ok((false, false)), |overlap| { | ||
if let Some(overlap_kind) = | ||
tcx.impls_are_allowed_to_overlap(impl_def_id, possible_sibling) | ||
{ | ||
match overlap_kind { | ||
ty::ImplOverlapKind::Permitted { marker: _ } => {} | ||
ty::ImplOverlapKind::Issue33140 => { | ||
*last_lint_mut = Some(FutureCompatOverlapError { | ||
error: create_overlap_error(overlap), | ||
kind: FutureCompatOverlapErrorKind::Issue33140, | ||
}); | ||
} | ||
} | ||
.is_some(); | ||
|
||
let error = create_overlap_error(overlap); | ||
|
||
if should_err { | ||
Err(error) | ||
} else { | ||
*last_lint = Some(FutureCompatOverlapError { | ||
error, | ||
kind: FutureCompatOverlapErrorKind::LeakCheck, | ||
}); | ||
|
||
return Ok((false, false)); | ||
Ok((false, false)) | ||
} | ||
}; | ||
|
||
let last_lint_mut = &mut last_lint; | ||
let (le, ge) = traits::overlapping_impls( | ||
tcx, | ||
possible_sibling, | ||
impl_def_id, | ||
traits::SkipLeakCheck::Yes, | ||
overlap_mode, | ||
) | ||
.map_or(Ok((false, false)), |overlap| { | ||
if let Some(overlap_kind) = | ||
tcx.impls_are_allowed_to_overlap(impl_def_id, possible_sibling) | ||
{ | ||
match overlap_kind { | ||
ty::ImplOverlapKind::Permitted { marker: _ } => {} | ||
ty::ImplOverlapKind::Issue33140 => { | ||
*last_lint_mut = Some(FutureCompatOverlapError { | ||
error: create_overlap_error(overlap), | ||
kind: FutureCompatOverlapErrorKind::Issue33140, | ||
}); | ||
} | ||
} | ||
|
||
let le = tcx.specializes((impl_def_id, possible_sibling)); | ||
let ge = tcx.specializes((possible_sibling, impl_def_id)); | ||
return Ok((false, false)); | ||
} | ||
|
||
if le == ge { report_overlap_error(overlap, last_lint_mut) } else { Ok((le, ge)) } | ||
})?; | ||
let le = tcx.specializes((impl_def_id, possible_sibling)); | ||
let ge = tcx.specializes((possible_sibling, impl_def_id)); | ||
|
||
if le && !ge { | ||
debug!( | ||
"descending as child of TraitRef {:?}", | ||
tcx.impl_trait_ref(possible_sibling).unwrap().subst_identity() | ||
); | ||
if le == ge { report_overlap_error(overlap, last_lint_mut) } else { Ok((le, ge)) } | ||
})?; | ||
|
||
// The impl specializes `possible_sibling`. | ||
return Ok(Inserted::ShouldRecurseOn(possible_sibling)); | ||
} else if ge && !le { | ||
debug!( | ||
"placing as parent of TraitRef {:?}", | ||
tcx.impl_trait_ref(possible_sibling).unwrap().subst_identity() | ||
); | ||
if le && !ge { | ||
debug!( | ||
"descending as child of TraitRef {:?}", | ||
tcx.impl_trait_ref(possible_sibling).unwrap().subst_identity() | ||
); | ||
|
||
replace_children.push(possible_sibling); | ||
} else { | ||
// Either there's no overlap, or the overlap was already reported by | ||
// `overlap_error`. | ||
} | ||
} | ||
// The impl specializes `possible_sibling`. | ||
return Ok(OverlapResult::SpecializeOne(possible_sibling)); | ||
} else if ge && !le { | ||
debug!( | ||
"placing as parent of TraitRef {:?}", | ||
tcx.impl_trait_ref(possible_sibling).unwrap().subst_identity() | ||
); | ||
|
||
if !replace_children.is_empty() { | ||
return Ok(Inserted::ReplaceChildren(replace_children)); | ||
replace_children.push(possible_sibling); | ||
} else { | ||
// Either there's no overlap, or the overlap was already reported by | ||
// `overlap_error`. | ||
} | ||
} | ||
|
||
// No overlap with any potential siblings, so add as a new sibling. | ||
debug!("placing as new sibling"); | ||
self.insert_blindly(tcx, impl_def_id); | ||
Ok(Inserted::BecameNewSibling(last_lint)) | ||
if !replace_children.is_empty() { | ||
return Ok(OverlapResult::SpecializeAll(replace_children)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Block: early returns |
||
} | ||
|
||
if overlap_mode.use_negative_impl() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove this if, but before you do, let's try to figure out why the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. probably because we didn't remove the old coherence logic |
||
&& tcx.impl_polarity(impl_def_id) == ty::ImplPolarity::Positive | ||
&& traits::negative_impl_holds(tcx, impl_def_id, overlap_mode) | ||
{ | ||
let trait_ref = tcx.impl_trait_ref(impl_def_id).unwrap().skip_binder(); | ||
let self_ty = trait_ref.self_ty(); | ||
|
||
return Err(OverlapError { | ||
with_impl: impl_def_id, | ||
trait_ref, | ||
self_ty: self_ty.has_concrete_skeleton().then_some(self_ty), | ||
intercrate_ambiguity_causes: Default::default(), | ||
involves_placeholder: false, | ||
}); | ||
} | ||
|
||
Ok(OverlapResult::NoOverlap(last_lint)) | ||
} | ||
|
||
fn iter_children(children: &mut Children) -> impl Iterator<Item = DefId> + '_ { | ||
|
@@ -306,7 +342,7 @@ impl<'tcx> GraphExt<'tcx> for Graph { | |
|
||
// Descend the specialization tree, where `parent` is the current parent node. | ||
loop { | ||
use self::Inserted::*; | ||
use self::OverlapResult::*; | ||
|
||
let insert_result = self.children.entry(parent).or_default().insert( | ||
tcx, | ||
|
@@ -316,11 +352,11 @@ impl<'tcx> GraphExt<'tcx> for Graph { | |
)?; | ||
|
||
match insert_result { | ||
BecameNewSibling(opt_lint) => { | ||
NoOverlap(opt_lint) => { | ||
last_lint = opt_lint; | ||
break; | ||
} | ||
ReplaceChildren(grand_children_to_be) => { | ||
SpecializeAll(grand_children_to_be) => { | ||
// We currently have | ||
// | ||
// P | ||
|
@@ -359,7 +395,7 @@ impl<'tcx> GraphExt<'tcx> for Graph { | |
} | ||
break; | ||
} | ||
ShouldRecurseOn(new_parent) => { | ||
SpecializeOne(new_parent) => { | ||
parent = new_parent; | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
#![feature(trivial_bounds)] | ||
#![feature(negative_impls)] | ||
#![feature(rustc_attrs)] | ||
#![feature(with_negative_coherence)] | ||
#![allow(trivial_bounds)] | ||
|
||
#[rustc_strict_coherence] | ||
trait MyTrait {} | ||
|
||
struct Foo {} | ||
|
||
impl !MyTrait for Foo {} | ||
|
||
impl MyTrait for Foo where Foo: MyTrait {} | ||
//~^ ERROR: conflicting implementations of trait `MyTrait` | ||
|
||
fn main() {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/implement/implements/