From d2d25a5be0c45b8aa247a3d0b6c7fda12e9ede9a Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 21 Jan 2022 12:53:50 -0300 Subject: [PATCH] Implement stable with negative coherence mode --- compiler/rustc_feature/src/builtin_attrs.rs | 1 + compiler/rustc_span/src/symbol.rs | 1 + .../src/traits/coherence.rs | 99 ++++++++++++++++++- .../ui/coherence/auxiliary/option_future.rs | 8 ++ .../coherence-overlap-negative-trait2.rs | 18 ++++ 5 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 src/test/ui/coherence/auxiliary/option_future.rs create mode 100644 src/test/ui/coherence/coherence-overlap-negative-trait2.rs diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index 723cc06864a90..0e643ff599834 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -697,6 +697,7 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ rustc_attr!(TEST, rustc_capture_analysis, Normal, template!(Word), WarnFollowing), rustc_attr!(TEST, rustc_insignificant_dtor, Normal, template!(Word), WarnFollowing), rustc_attr!(TEST, rustc_strict_coherence, Normal, template!(Word), WarnFollowing), + rustc_attr!(TEST, rustc_with_negative_coherence, Normal, template!(Word), WarnFollowing), rustc_attr!(TEST, rustc_variance, Normal, template!(Word), WarnFollowing), rustc_attr!(TEST, rustc_layout, Normal, template!(List: "field1, field2, ..."), WarnFollowing), rustc_attr!(TEST, rustc_regions, Normal, template!(Word), WarnFollowing), diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 702e359466079..7e0ce89be67fc 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1203,6 +1203,7 @@ symbols! { rustc_trivial_field_reads, rustc_unsafe_specialization_marker, rustc_variance, + rustc_with_negative_coherence, rustdoc, rustdoc_internals, rustfmt, diff --git a/compiler/rustc_trait_selection/src/traits/coherence.rs b/compiler/rustc_trait_selection/src/traits/coherence.rs index 170740d2cbdc1..42b7139c00698 100644 --- a/compiler/rustc_trait_selection/src/traits/coherence.rs +++ b/compiler/rustc_trait_selection/src/traits/coherence.rs @@ -7,9 +7,11 @@ use crate::infer::{CombinedSnapshot, InferOk, TyCtxtInferExt}; use crate::traits::query::evaluate_obligation::InferCtxtExt; use crate::traits::select::IntercrateAmbiguityCause; +use crate::traits::util::impl_trait_ref_and_oblig; use crate::traits::SkipLeakCheck; use crate::traits::{ - self, Normalized, Obligation, ObligationCause, PredicateObligation, SelectionContext, + self, FulfillmentContext, Normalized, Obligation, ObligationCause, PredicateObligation, + SelectionContext, }; use rustc_hir::def_id::{DefId, LOCAL_CRATE}; use rustc_middle::ty::fast_reject::{self, SimplifyParams, StripReferences}; @@ -137,6 +139,7 @@ fn with_fresh_ty_vars<'cx, 'tcx>( enum OverlapMode { Stable, + WithNegative, Strict, } @@ -147,8 +150,16 @@ fn overlap_mode<'tcx>(tcx: TyCtxt<'tcx>, impl1_def_id: DefId, impl2_def_id: DefI bug!("Use strict coherence on both impls",); } + if tcx.has_attr(impl1_def_id, sym::rustc_with_negative_coherence) + != tcx.has_attr(impl2_def_id, sym::rustc_with_negative_coherence) + { + bug!("Use with negative coherence on both impls",); + } + if tcx.has_attr(impl1_def_id, sym::rustc_strict_coherence) { OverlapMode::Strict + } else if tcx.has_attr(impl1_def_id, sym::rustc_with_negative_coherence) { + OverlapMode::WithNegative } else { OverlapMode::Stable } @@ -188,9 +199,25 @@ fn overlap_within_probe<'cx, 'tcx>( let impl1_header = with_fresh_ty_vars(selcx, param_env, impl1_def_id); let impl2_header = with_fresh_ty_vars(selcx, param_env, impl2_def_id); - let overlap_mode = overlap_mode(tcx, impl1_def_id, impl2_def_id); - if stable_disjoint(selcx, param_env, &impl1_header, impl2_header, overlap_mode) { - return None; + match overlap_mode(tcx, impl1_def_id, impl2_def_id) { + OverlapMode::Stable => { + if stable_disjoint(selcx, param_env, &impl1_header, impl2_header, OverlapMode::Stable) { + return None; + } + } + OverlapMode::Strict => { + if stable_disjoint(selcx, param_env, &impl1_header, impl2_header, OverlapMode::Strict) { + return None; + } + } + OverlapMode::WithNegative => { + if stable_disjoint(selcx, param_env, &impl1_header, impl2_header, OverlapMode::Stable) + || explicit_disjoint(selcx, impl1_def_id, impl2_def_id) + || explicit_disjoint(selcx, impl2_def_id, impl1_def_id) + { + return None; + } + } } if !skip_leak_check.is_yes() { @@ -280,6 +307,7 @@ fn stable_disjoint<'cx, 'tcx>( loose_check(selcx, o) || tcx.features().negative_impls && strict_check(selcx, o) } OverlapMode::Strict => strict_check(selcx, o), + OverlapMode::WithNegative => loose_check(selcx, o), } }); // FIXME: the call to `selcx.predicate_may_hold_fatal` above should be ported @@ -294,6 +322,69 @@ fn stable_disjoint<'cx, 'tcx>( } } +/// Given impl1 and impl2 check if both impls are never satisfied by a common type (including +/// where-clauses) If so, return true, they are disjoint and false otherwise. +fn explicit_disjoint<'cx, 'tcx>( + selcx: &mut SelectionContext<'cx, 'tcx>, + impl1_def_id: DefId, + impl2_def_id: DefId, +) -> bool { + let tcx = selcx.infcx().tcx; + + // create a parameter environment corresponding to a (placeholder) instantiation of impl1 + let impl1_env = tcx.param_env(impl1_def_id); + let impl1_trait_ref = tcx.impl_trait_ref(impl1_def_id).unwrap(); + + // Create an infcx, taking the predicates of impl1 as assumptions: + tcx.infer_ctxt().enter(|infcx| { + // Normalize the trait reference. The WF rules ought to ensure + // that this always succeeds. + let impl1_trait_ref = match traits::fully_normalize( + &infcx, + FulfillmentContext::new(), + ObligationCause::dummy(), + impl1_env, + impl1_trait_ref, + ) { + Ok(impl1_trait_ref) => impl1_trait_ref, + Err(err) => { + bug!("failed to fully normalize {:?}: {:?}", impl1_trait_ref, err); + } + }; + + // Attempt to prove that impl2 applies, given all of the above. + let selcx = &mut SelectionContext::new(&infcx); + let impl2_substs = infcx.fresh_substs_for_item(DUMMY_SP, impl2_def_id); + let (impl2_trait_ref, obligations) = + impl_trait_ref_and_oblig(selcx, impl1_env, impl2_def_id, impl2_substs); + + // do the impls unify? If not, not disjoint. + let more_obligations = match infcx + .at(&ObligationCause::dummy(), impl1_env) + .eq(impl1_trait_ref, impl2_trait_ref) + { + Ok(InferOk { obligations, .. }) => obligations, + Err(_) => { + debug!( + "explicit_disjoint: {:?} does not unify with {:?}", + impl1_trait_ref, impl2_trait_ref + ); + return false; + } + }; + + let opt_failing_obligation = + obligations.into_iter().chain(more_obligations).find(|o| strict_check(selcx, o)); + + if let Some(failing_obligation) = opt_failing_obligation { + debug!("overlap: obligation unsatisfiable {:?}", failing_obligation); + true + } else { + false + } + }) +} + fn loose_check<'cx, 'tcx>( selcx: &mut SelectionContext<'cx, 'tcx>, o: &PredicateObligation<'tcx>, diff --git a/src/test/ui/coherence/auxiliary/option_future.rs b/src/test/ui/coherence/auxiliary/option_future.rs new file mode 100644 index 0000000000000..f71df1b87fcda --- /dev/null +++ b/src/test/ui/coherence/auxiliary/option_future.rs @@ -0,0 +1,8 @@ +#![crate_type = "lib"] +#![feature(negative_impls)] +#![feature(rustc_attrs)] + +pub trait Future {} + +#[rustc_with_negative_coherence] +impl !Future for Option where E: Sized {} diff --git a/src/test/ui/coherence/coherence-overlap-negative-trait2.rs b/src/test/ui/coherence/coherence-overlap-negative-trait2.rs new file mode 100644 index 0000000000000..1f47b5ba46e41 --- /dev/null +++ b/src/test/ui/coherence/coherence-overlap-negative-trait2.rs @@ -0,0 +1,18 @@ +// check-pass +// aux-build:option_future.rs +// +// Check that if we promise to not impl what would overlap it doesn't actually overlap + +#![feature(rustc_attrs)] + +extern crate option_future as lib; +use lib::Future; + +trait Termination {} + +#[rustc_with_negative_coherence] +impl Termination for Option where E: Sized {} +#[rustc_with_negative_coherence] +impl Termination for F where F: Future + Sized {} + +fn main() {}