about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMichael Goulet <michael@errs.io>2022-12-11 21:16:43 +0000
committerMichael Goulet <michael@errs.io>2022-12-19 18:16:22 +0000
commit96154d7fa7ced3184b69645ed8b6e7c210ca15b4 (patch)
treed6492644481911c90557daa7bed8fc1152259258
parent4653c93e4442d88bf3278067183c8fdc0be74a1f (diff)
downloadrust-96154d7fa7ced3184b69645ed8b6e7c210ca15b4.tar.gz
rust-96154d7fa7ced3184b69645ed8b6e7c210ca15b4.zip
Add IMPLIED_BOUNDS_ENTAILMENT lint
-rw-r--r--compiler/rustc_hir_analysis/src/check/compare_method.rs75
-rw-r--r--compiler/rustc_infer/src/infer/mod.rs6
-rw-r--r--compiler/rustc_infer/src/infer/outlives/obligations.rs3
-rw-r--r--compiler/rustc_lint_defs/src/builtin.rs41
-rw-r--r--src/test/ui/implied-bounds/impl-implied-bounds-compatibility-unnormalized.rs20
-rw-r--r--src/test/ui/implied-bounds/impl-implied-bounds-compatibility-unnormalized.stderr23
-rw-r--r--src/test/ui/implied-bounds/impl-implied-bounds-compatibility.rs19
-rw-r--r--src/test/ui/implied-bounds/impl-implied-bounds-compatibility.stderr23
8 files changed, 203 insertions, 7 deletions
diff --git a/compiler/rustc_hir_analysis/src/check/compare_method.rs b/compiler/rustc_hir_analysis/src/check/compare_method.rs
index b69728c24aa..23d3e6041ef 100644
--- a/compiler/rustc_hir_analysis/src/check/compare_method.rs
+++ b/compiler/rustc_hir_analysis/src/check/compare_method.rs
@@ -255,15 +255,15 @@ fn compare_predicate_entailment<'tcx>(
 
     let mut wf_tys = FxIndexSet::default();
 
-    let impl_sig = infcx.replace_bound_vars_with_fresh_vars(
+    let unnormalized_impl_sig = infcx.replace_bound_vars_with_fresh_vars(
         impl_m_span,
         infer::HigherRankedType,
         tcx.fn_sig(impl_m.def_id),
     );
+    let unnormalized_impl_fty = tcx.mk_fn_ptr(ty::Binder::dummy(unnormalized_impl_sig));
 
     let norm_cause = ObligationCause::misc(impl_m_span, impl_m_hir_id);
-    let impl_sig = ocx.normalize(&norm_cause, param_env, impl_sig);
-    let impl_fty = tcx.mk_fn_ptr(ty::Binder::dummy(impl_sig));
+    let impl_fty = ocx.normalize(&norm_cause, param_env, unnormalized_impl_fty);
     debug!("compare_impl_method: impl_fty={:?}", impl_fty);
 
     let trait_sig = tcx.bound_fn_sig(trait_m.def_id).subst(tcx, trait_to_placeholder_substs);
@@ -312,21 +312,86 @@ fn compare_predicate_entailment<'tcx>(
         return Err(reported);
     }
 
+    // FIXME(compiler-errors): This can be removed when IMPLIED_BOUNDS_ENTAILMENT
+    // becomes a hard error.
+    let lint_infcx = infcx.fork();
+
     // Finally, resolve all regions. This catches wily misuses of
     // lifetime parameters.
     let outlives_environment = OutlivesEnvironment::with_bounds(
         param_env,
         Some(infcx),
-        infcx.implied_bounds_tys(param_env, impl_m_hir_id, wf_tys),
+        infcx.implied_bounds_tys(param_env, impl_m_hir_id, wf_tys.clone()),
     );
-    infcx.check_region_obligations_and_report_errors(
+    if let Some(guar) = infcx.check_region_obligations_and_report_errors(
         impl_m.def_id.expect_local(),
         &outlives_environment,
+    ) {
+        return Err(guar);
+    }
+
+    // FIXME(compiler-errors): This can be simplified when IMPLIED_BOUNDS_ENTAILMENT
+    // becomes a hard error (i.e. ideally we'd just register a WF obligation above...)
+    lint_implied_wf_entailment(
+        impl_m.def_id.expect_local(),
+        lint_infcx,
+        param_env,
+        unnormalized_impl_fty,
+        wf_tys,
     );
 
     Ok(())
 }
 
+fn lint_implied_wf_entailment<'tcx>(
+    impl_m_def_id: LocalDefId,
+    infcx: InferCtxt<'tcx>,
+    param_env: ty::ParamEnv<'tcx>,
+    unnormalized_impl_fty: Ty<'tcx>,
+    wf_tys: FxIndexSet<Ty<'tcx>>,
+) {
+    let ocx = ObligationCtxt::new(&infcx);
+
+    // We need to check that the impl's args are well-formed given
+    // the hybrid param-env (impl + trait method where-clauses).
+    ocx.register_obligation(traits::Obligation::new(
+        infcx.tcx,
+        ObligationCause::dummy(),
+        param_env,
+        ty::Binder::dummy(ty::PredicateKind::WellFormed(unnormalized_impl_fty.into())),
+    ));
+
+    let hir_id = infcx.tcx.hir().local_def_id_to_hir_id(impl_m_def_id);
+    let lint = || {
+        infcx.tcx.struct_span_lint_hir(
+            rustc_session::lint::builtin::IMPLIED_BOUNDS_ENTAILMENT,
+            hir_id,
+            infcx.tcx.def_span(impl_m_def_id),
+            "impl method assumes more implied bounds than the corresponding trait method",
+            |lint| lint,
+        );
+    };
+
+    let errors = ocx.select_all_or_error();
+    if !errors.is_empty() {
+        lint();
+    }
+
+    let outlives_environment = OutlivesEnvironment::with_bounds(
+        param_env,
+        Some(&infcx),
+        infcx.implied_bounds_tys(param_env, hir_id, wf_tys.clone()),
+    );
+    infcx.process_registered_region_obligations(
+        outlives_environment.region_bound_pairs(),
+        param_env,
+    );
+
+    if !infcx.resolve_regions(&outlives_environment).is_empty() {
+        lint();
+    }
+}
+
 fn compare_asyncness<'tcx>(
     tcx: TyCtxt<'tcx>,
     impl_m: &ty::AssocItem,
diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs
index 268b3bf1dcd..a9ef91db059 100644
--- a/compiler/rustc_infer/src/infer/mod.rs
+++ b/compiler/rustc_infer/src/infer/mod.rs
@@ -1693,7 +1693,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
         &self,
         generic_param_scope: LocalDefId,
         outlives_env: &OutlivesEnvironment<'tcx>,
-    ) {
+    ) -> Option<ErrorGuaranteed> {
         let errors = self.resolve_regions(outlives_env);
 
         if let None = self.tainted_by_errors() {
@@ -1704,6 +1704,10 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
             // errors from silly ones.
             self.report_region_errors(generic_param_scope, &errors);
         }
+
+        (!errors.is_empty()).then(|| {
+            self.tcx.sess.delay_span_bug(rustc_span::DUMMY_SP, "error should have been emitted")
+        })
     }
 
     // [Note-Type-error-reporting]
diff --git a/compiler/rustc_infer/src/infer/outlives/obligations.rs b/compiler/rustc_infer/src/infer/outlives/obligations.rs
index ccae7165d80..47bd1564f08 100644
--- a/compiler/rustc_infer/src/infer/outlives/obligations.rs
+++ b/compiler/rustc_infer/src/infer/outlives/obligations.rs
@@ -68,6 +68,7 @@ use crate::infer::{
 };
 use crate::traits::{ObligationCause, ObligationCauseCode};
 use rustc_data_structures::undo_log::UndoLogs;
+use rustc_errors::ErrorGuaranteed;
 use rustc_hir::def_id::DefId;
 use rustc_hir::def_id::LocalDefId;
 use rustc_middle::mir::ConstraintCategory;
@@ -177,7 +178,7 @@ impl<'tcx> InferCtxt<'tcx> {
         &self,
         generic_param_scope: LocalDefId,
         outlives_env: &OutlivesEnvironment<'tcx>,
-    ) {
+    ) -> Option<ErrorGuaranteed> {
         self.process_registered_region_obligations(
             outlives_env.region_bound_pairs(),
             outlives_env.param_env,
diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs
index 33cb35e60eb..c88158da763 100644
--- a/compiler/rustc_lint_defs/src/builtin.rs
+++ b/compiler/rustc_lint_defs/src/builtin.rs
@@ -3998,3 +3998,44 @@ declare_lint! {
     Warn,
     "named arguments in format used positionally"
 }
+
+declare_lint! {
+    /// The `implied_bounds_entailment` lint detects cases where the arguments of an impl method
+    /// have stronger implied bounds than those from the trait method it's implementing.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,compile_fail
+    /// #![deny(implied_bounds_entailment)]
+    ///
+    /// trait Trait {
+    ///     fn get<'s>(s: &'s str, _: &'static &'static ()) -> &'static str;
+    /// }
+    ///
+    /// impl Trait for () {
+    ///     fn get<'s>(s: &'s str, _: &'static &'s ()) -> &'static str {
+    ///         s
+    ///     }
+    /// }
+    ///
+    /// let val = <() as Trait>::get(&String::from("blah blah blah"), &&());
+    /// println!("{}", val);
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Neither the trait method, which provides no implied bounds about `'s`, nor the impl,
+    /// which can't name `'s`, requires the main function to prove that 's: 'static, but the
+    /// impl method is able to assume that 's: 'static within its own body.
+    ///
+    /// This can be used to implement an unsound API if used incorrectly.
+    pub IMPLIED_BOUNDS_ENTAILMENT,
+    Deny,
+    "impl method assumes more implied bounds than its corresponding trait method",
+    @future_incompatible = FutureIncompatibleInfo {
+        reference: "issue #105572 <https://github.com/rust-lang/rust/issues/105572>",
+        reason: FutureIncompatibilityReason::FutureReleaseErrorReportNow,
+    };
+}
diff --git a/src/test/ui/implied-bounds/impl-implied-bounds-compatibility-unnormalized.rs b/src/test/ui/implied-bounds/impl-implied-bounds-compatibility-unnormalized.rs
new file mode 100644
index 00000000000..cb5d83abfb6
--- /dev/null
+++ b/src/test/ui/implied-bounds/impl-implied-bounds-compatibility-unnormalized.rs
@@ -0,0 +1,20 @@
+trait Project {
+    type Ty;
+}
+impl Project for &'_ &'_ () {
+    type Ty = ();
+}
+trait Trait {
+    fn get<'s>(s: &'s str, _: ()) -> &'static str;
+}
+impl Trait for () {
+    fn get<'s>(s: &'s str, _: <&'static &'s () as Project>::Ty) -> &'static str {
+        //~^ ERROR impl method assumes more implied bounds than the corresponding trait method
+        //~| WARN this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+        s
+    }
+}
+fn main() {
+    let val = <() as Trait>::get(&String::from("blah blah blah"), ());
+    println!("{}", val);
+}
diff --git a/src/test/ui/implied-bounds/impl-implied-bounds-compatibility-unnormalized.stderr b/src/test/ui/implied-bounds/impl-implied-bounds-compatibility-unnormalized.stderr
new file mode 100644
index 00000000000..5a757901777
--- /dev/null
+++ b/src/test/ui/implied-bounds/impl-implied-bounds-compatibility-unnormalized.stderr
@@ -0,0 +1,23 @@
+error: impl method assumes more implied bounds than the corresponding trait method
+  --> $DIR/impl-implied-bounds-compatibility-unnormalized.rs:11:5
+   |
+LL |     fn get<'s>(s: &'s str, _: <&'static &'s () as Project>::Ty) -> &'static str {
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+   = note: for more information, see issue #105572 <https://github.com/rust-lang/rust/issues/105572>
+   = note: `#[deny(implied_bounds_entailment)]` on by default
+
+error: aborting due to previous error
+
+Future incompatibility report: Future breakage diagnostic:
+error: impl method assumes more implied bounds than the corresponding trait method
+  --> $DIR/impl-implied-bounds-compatibility-unnormalized.rs:11:5
+   |
+LL |     fn get<'s>(s: &'s str, _: <&'static &'s () as Project>::Ty) -> &'static str {
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+   = note: for more information, see issue #105572 <https://github.com/rust-lang/rust/issues/105572>
+   = note: `#[deny(implied_bounds_entailment)]` on by default
+
diff --git a/src/test/ui/implied-bounds/impl-implied-bounds-compatibility.rs b/src/test/ui/implied-bounds/impl-implied-bounds-compatibility.rs
new file mode 100644
index 00000000000..2d7cc38d263
--- /dev/null
+++ b/src/test/ui/implied-bounds/impl-implied-bounds-compatibility.rs
@@ -0,0 +1,19 @@
+use std::cell::RefCell;
+
+pub struct MessageListeners<'a> {
+    listeners: RefCell<Vec<Box<dyn FnMut(()) + 'a>>>,
+}
+
+pub trait MessageListenersInterface {
+    fn listeners<'c>(&'c self) -> &'c MessageListeners<'c>;
+}
+
+impl<'a> MessageListenersInterface for MessageListeners<'a> {
+    fn listeners<'b>(&'b self) -> &'a MessageListeners<'b> {
+        //~^ ERROR impl method assumes more implied bounds than the corresponding trait method
+        //~| WARN this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+        self
+    }
+}
+
+fn main() {}
diff --git a/src/test/ui/implied-bounds/impl-implied-bounds-compatibility.stderr b/src/test/ui/implied-bounds/impl-implied-bounds-compatibility.stderr
new file mode 100644
index 00000000000..b7dbfc8ab8c
--- /dev/null
+++ b/src/test/ui/implied-bounds/impl-implied-bounds-compatibility.stderr
@@ -0,0 +1,23 @@
+error: impl method assumes more implied bounds than the corresponding trait method
+  --> $DIR/impl-implied-bounds-compatibility.rs:12:5
+   |
+LL |     fn listeners<'b>(&'b self) -> &'a MessageListeners<'b> {
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+   = note: for more information, see issue #105572 <https://github.com/rust-lang/rust/issues/105572>
+   = note: `#[deny(implied_bounds_entailment)]` on by default
+
+error: aborting due to previous error
+
+Future incompatibility report: Future breakage diagnostic:
+error: impl method assumes more implied bounds than the corresponding trait method
+  --> $DIR/impl-implied-bounds-compatibility.rs:12:5
+   |
+LL |     fn listeners<'b>(&'b self) -> &'a MessageListeners<'b> {
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+   = note: for more information, see issue #105572 <https://github.com/rust-lang/rust/issues/105572>
+   = note: `#[deny(implied_bounds_entailment)]` on by default
+