about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMichael Goulet <michael@errs.io>2022-10-02 04:45:54 +0000
committerMichael Goulet <michael@errs.io>2022-10-02 19:50:19 +0000
commitd0d6af91467ef60f12396f5d40a09eb4de8cb8b7 (patch)
tree870ce950f505c30d12b84104264b12c280be5363
parent39323a5877ee6b85e75c652c2518a97a1433a2dc (diff)
downloadrust-d0d6af91467ef60f12396f5d40a09eb4de8cb8b7.tar.gz
rust-d0d6af91467ef60f12396f5d40a09eb4de8cb8b7.zip
Lint for unsatisfied nested opaques
-rw-r--r--compiler/rustc_error_messages/locales/en-US/lint.ftl4
-rw-r--r--compiler/rustc_lint/src/lib.rs3
-rw-r--r--compiler/rustc_lint/src/rpit_hidden_inferred_bound.rs143
-rw-r--r--compiler/rustc_middle/src/lint.rs4
4 files changed, 153 insertions, 1 deletions
diff --git a/compiler/rustc_error_messages/locales/en-US/lint.ftl b/compiler/rustc_error_messages/locales/en-US/lint.ftl
index 80b0b1a8904..31c9a845e7e 100644
--- a/compiler/rustc_error_messages/locales/en-US/lint.ftl
+++ b/compiler/rustc_error_messages/locales/en-US/lint.ftl
@@ -433,3 +433,7 @@ lint_check_name_unknown_tool = unknown lint tool: `{$tool_name}`
 lint_check_name_warning = {$msg}
 
 lint_check_name_deprecated = lint name `{$lint_name}` is deprecated and does not have an effect anymore. Use: {$new_name}
+
+lint_rpit_hidden_inferred_bound = return-position `{$ty}` does not satisfy its associated type bounds
+    .specifically = this associated type bound is unsatisfied for `{$proj_ty}`
+    .suggestion = add this bound
diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs
index 4408f68dd63..fd7ea5a4bb5 100644
--- a/compiler/rustc_lint/src/lib.rs
+++ b/compiler/rustc_lint/src/lib.rs
@@ -65,6 +65,7 @@ mod noop_method_call;
 mod pass_by_value;
 mod passes;
 mod redundant_semicolon;
+mod rpit_hidden_inferred_bound;
 mod traits;
 mod types;
 mod unused;
@@ -95,6 +96,7 @@ use nonstandard_style::*;
 use noop_method_call::*;
 use pass_by_value::*;
 use redundant_semicolon::*;
+use rpit_hidden_inferred_bound::*;
 use traits::*;
 use types::*;
 use unused::*;
@@ -223,6 +225,7 @@ macro_rules! late_lint_mod_passes {
                 EnumIntrinsicsNonEnums: EnumIntrinsicsNonEnums,
                 InvalidAtomicOrdering: InvalidAtomicOrdering,
                 NamedAsmLabels: NamedAsmLabels,
+                RpitHiddenInferredBound: RpitHiddenInferredBound,
             ]
         );
     };
diff --git a/compiler/rustc_lint/src/rpit_hidden_inferred_bound.rs b/compiler/rustc_lint/src/rpit_hidden_inferred_bound.rs
new file mode 100644
index 00000000000..fd9872c80a9
--- /dev/null
+++ b/compiler/rustc_lint/src/rpit_hidden_inferred_bound.rs
@@ -0,0 +1,143 @@
+use hir::def_id::LocalDefId;
+use rustc_hir as hir;
+use rustc_infer::infer::{InferCtxt, TyCtxtInferExt};
+use rustc_macros::LintDiagnostic;
+use rustc_middle::ty::{
+    self, fold::BottomUpFolder, Ty, TypeFoldable, TypeSuperVisitable, TypeVisitable, TypeVisitor,
+};
+use rustc_span::Span;
+use rustc_trait_selection::traits;
+use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
+
+use crate::{LateContext, LateLintPass, LintContext};
+
+declare_lint! {
+    /// The `rpit_hidden_inferred_bound` lint detects cases in which nested RPITs
+    /// in associated type bounds are not written generally enough to satisfy the
+    /// bounds of the associated type. This functionality was removed in #97346,
+    /// but then rolled back in #99860 because it was made into a hard error too
+    /// quickly.
+    ///
+    /// We plan on reintroducing this as a hard error, but in the mean time, this
+    /// lint serves to warn and suggest fixes for any use-cases which rely on this
+    /// behavior.
+    pub RPIT_HIDDEN_INFERRED_BOUND,
+    Warn,
+    "detects the use of nested RPITs in associated type bounds that are not general enough"
+}
+
+declare_lint_pass!(RpitHiddenInferredBound => [RPIT_HIDDEN_INFERRED_BOUND]);
+
+impl<'tcx> LateLintPass<'tcx> for RpitHiddenInferredBound {
+    fn check_fn(
+        &mut self,
+        cx: &LateContext<'tcx>,
+        kind: hir::intravisit::FnKind<'tcx>,
+        _: &'tcx hir::FnDecl<'tcx>,
+        _: &'tcx hir::Body<'tcx>,
+        _: rustc_span::Span,
+        id: hir::HirId,
+    ) {
+        if matches!(kind, hir::intravisit::FnKind::Closure) {
+            return;
+        }
+
+        let fn_def_id = cx.tcx.hir().local_def_id(id);
+        let sig: ty::FnSig<'tcx> =
+            cx.tcx.liberate_late_bound_regions(fn_def_id.to_def_id(), cx.tcx.fn_sig(fn_def_id));
+        cx.tcx.infer_ctxt().enter(|ref infcx| {
+            sig.output().visit_with(&mut VisitOpaqueBounds { infcx, cx, fn_def_id });
+        });
+    }
+}
+
+struct VisitOpaqueBounds<'a, 'cx, 'tcx> {
+    infcx: &'a InferCtxt<'a, 'tcx>,
+    cx: &'cx LateContext<'tcx>,
+    fn_def_id: LocalDefId,
+}
+
+impl<'tcx> TypeVisitor<'tcx> for VisitOpaqueBounds<'_, '_, 'tcx> {
+    fn visit_ty(&mut self, ty: Ty<'tcx>) -> std::ops::ControlFlow<Self::BreakTy> {
+        if let ty::Opaque(def_id, substs) = *ty.kind()
+            && let Some(hir::Node::Item(item)) = self.cx.tcx.hir().get_if_local(def_id)
+            && let hir::ItemKind::OpaqueTy(opaque) = &item.kind
+            && let hir::OpaqueTyOrigin::FnReturn(origin_def_id) = opaque.origin
+            && origin_def_id == self.fn_def_id
+        {
+            for pred_and_span in self.cx.tcx.bound_explicit_item_bounds(def_id).transpose_iter() {
+                let pred_span = pred_and_span.0.1;
+                let predicate = self.cx.tcx.liberate_late_bound_regions(
+                    def_id,
+                    pred_and_span.map_bound(|(pred, _)| *pred).subst(self.cx.tcx, substs).kind(),
+                );
+                let ty::PredicateKind::Projection(proj) = predicate else {
+                    continue;
+                };
+                let Some(proj_term) = proj.term.ty() else { continue };
+
+                let proj_ty = self
+                    .cx
+                    .tcx
+                    .mk_projection(proj.projection_ty.item_def_id, proj.projection_ty.substs);
+                let proj_replacer = &mut BottomUpFolder {
+                    tcx: self.cx.tcx,
+                    ty_op: |ty| if ty == proj_ty { proj_term } else { ty },
+                    lt_op: |lt| lt,
+                    ct_op: |ct| ct,
+                };
+                for assoc_pred_and_span in self
+                    .cx
+                    .tcx
+                    .bound_explicit_item_bounds(proj.projection_ty.item_def_id)
+                    .transpose_iter()
+                {
+                    let assoc_pred_span = assoc_pred_and_span.0.1;
+                    let assoc_pred = assoc_pred_and_span
+                        .map_bound(|(pred, _)| *pred)
+                        .subst(self.cx.tcx, &proj.projection_ty.substs)
+                        .fold_with(proj_replacer);
+                    if !self.infcx.predicate_must_hold_modulo_regions(&traits::Obligation::new(
+                        traits::ObligationCause::dummy(),
+                        self.cx.param_env,
+                        assoc_pred,
+                    )) {
+                        let (suggestion, suggest_span) =
+                            match (proj_term.kind(), assoc_pred.kind().skip_binder()) {
+                                (ty::Opaque(def_id, _), ty::PredicateKind::Trait(trait_pred)) => (
+                                    format!(" + {}", trait_pred.print_modifiers_and_trait_path()),
+                                    Some(self.cx.tcx.def_span(def_id).shrink_to_hi()),
+                                ),
+                                _ => (String::new(), None),
+                            };
+                        self.cx.emit_spanned_lint(
+                            RPIT_HIDDEN_INFERRED_BOUND,
+                            pred_span,
+                            RpitHiddenInferredBoundLint {
+                                ty,
+                                proj_ty: proj_term,
+                                assoc_pred_span,
+                                suggestion,
+                                suggest_span,
+                            },
+                        );
+                    }
+                }
+            }
+        }
+
+        ty.super_visit_with(self)
+    }
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint::rpit_hidden_inferred_bound)]
+struct RpitHiddenInferredBoundLint<'tcx> {
+    ty: Ty<'tcx>,
+    proj_ty: Ty<'tcx>,
+    #[label(lint::specifically)]
+    assoc_pred_span: Span,
+    #[suggestion_verbose(applicability = "machine-applicable", code = "{suggestion}")]
+    suggest_span: Option<Span>,
+    suggestion: String,
+}
diff --git a/compiler/rustc_middle/src/lint.rs b/compiler/rustc_middle/src/lint.rs
index f3e4f1faeb0..1fb27fd87e8 100644
--- a/compiler/rustc_middle/src/lint.rs
+++ b/compiler/rustc_middle/src/lint.rs
@@ -444,7 +444,9 @@ pub fn in_external_macro(sess: &Session, span: Span) -> bool {
     match expn_data.kind {
         ExpnKind::Inlined
         | ExpnKind::Root
-        | ExpnKind::Desugaring(DesugaringKind::ForLoop | DesugaringKind::WhileLoop) => false,
+        | ExpnKind::Desugaring(
+            DesugaringKind::ForLoop | DesugaringKind::WhileLoop | DesugaringKind::OpaqueTy,
+        ) => false,
         ExpnKind::AstPass(_) | ExpnKind::Desugaring(_) => true, // well, it's "external"
         ExpnKind::Macro(MacroKind::Bang, _) => {
             // Dummy span for the `def_site` means it's an external macro.