Skip to content

Commit d0d6af9

Browse files
Lint for unsatisfied nested opaques
1 parent 39323a5 commit d0d6af9

File tree

4 files changed

+153
-1
lines changed

4 files changed

+153
-1
lines changed

compiler/rustc_error_messages/locales/en-US/lint.ftl

+4
Original file line numberDiff line numberDiff line change
@@ -433,3 +433,7 @@ lint_check_name_unknown_tool = unknown lint tool: `{$tool_name}`
433433
lint_check_name_warning = {$msg}
434434
435435
lint_check_name_deprecated = lint name `{$lint_name}` is deprecated and does not have an effect anymore. Use: {$new_name}
436+
437+
lint_rpit_hidden_inferred_bound = return-position `{$ty}` does not satisfy its associated type bounds
438+
.specifically = this associated type bound is unsatisfied for `{$proj_ty}`
439+
.suggestion = add this bound

compiler/rustc_lint/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ mod noop_method_call;
6565
mod pass_by_value;
6666
mod passes;
6767
mod redundant_semicolon;
68+
mod rpit_hidden_inferred_bound;
6869
mod traits;
6970
mod types;
7071
mod unused;
@@ -95,6 +96,7 @@ use nonstandard_style::*;
9596
use noop_method_call::*;
9697
use pass_by_value::*;
9798
use redundant_semicolon::*;
99+
use rpit_hidden_inferred_bound::*;
98100
use traits::*;
99101
use types::*;
100102
use unused::*;
@@ -223,6 +225,7 @@ macro_rules! late_lint_mod_passes {
223225
EnumIntrinsicsNonEnums: EnumIntrinsicsNonEnums,
224226
InvalidAtomicOrdering: InvalidAtomicOrdering,
225227
NamedAsmLabels: NamedAsmLabels,
228+
RpitHiddenInferredBound: RpitHiddenInferredBound,
226229
]
227230
);
228231
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
use hir::def_id::LocalDefId;
2+
use rustc_hir as hir;
3+
use rustc_infer::infer::{InferCtxt, TyCtxtInferExt};
4+
use rustc_macros::LintDiagnostic;
5+
use rustc_middle::ty::{
6+
self, fold::BottomUpFolder, Ty, TypeFoldable, TypeSuperVisitable, TypeVisitable, TypeVisitor,
7+
};
8+
use rustc_span::Span;
9+
use rustc_trait_selection::traits;
10+
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
11+
12+
use crate::{LateContext, LateLintPass, LintContext};
13+
14+
declare_lint! {
15+
/// The `rpit_hidden_inferred_bound` lint detects cases in which nested RPITs
16+
/// in associated type bounds are not written generally enough to satisfy the
17+
/// bounds of the associated type. This functionality was removed in #97346,
18+
/// but then rolled back in #99860 because it was made into a hard error too
19+
/// quickly.
20+
///
21+
/// We plan on reintroducing this as a hard error, but in the mean time, this
22+
/// lint serves to warn and suggest fixes for any use-cases which rely on this
23+
/// behavior.
24+
pub RPIT_HIDDEN_INFERRED_BOUND,
25+
Warn,
26+
"detects the use of nested RPITs in associated type bounds that are not general enough"
27+
}
28+
29+
declare_lint_pass!(RpitHiddenInferredBound => [RPIT_HIDDEN_INFERRED_BOUND]);
30+
31+
impl<'tcx> LateLintPass<'tcx> for RpitHiddenInferredBound {
32+
fn check_fn(
33+
&mut self,
34+
cx: &LateContext<'tcx>,
35+
kind: hir::intravisit::FnKind<'tcx>,
36+
_: &'tcx hir::FnDecl<'tcx>,
37+
_: &'tcx hir::Body<'tcx>,
38+
_: rustc_span::Span,
39+
id: hir::HirId,
40+
) {
41+
if matches!(kind, hir::intravisit::FnKind::Closure) {
42+
return;
43+
}
44+
45+
let fn_def_id = cx.tcx.hir().local_def_id(id);
46+
let sig: ty::FnSig<'tcx> =
47+
cx.tcx.liberate_late_bound_regions(fn_def_id.to_def_id(), cx.tcx.fn_sig(fn_def_id));
48+
cx.tcx.infer_ctxt().enter(|ref infcx| {
49+
sig.output().visit_with(&mut VisitOpaqueBounds { infcx, cx, fn_def_id });
50+
});
51+
}
52+
}
53+
54+
struct VisitOpaqueBounds<'a, 'cx, 'tcx> {
55+
infcx: &'a InferCtxt<'a, 'tcx>,
56+
cx: &'cx LateContext<'tcx>,
57+
fn_def_id: LocalDefId,
58+
}
59+
60+
impl<'tcx> TypeVisitor<'tcx> for VisitOpaqueBounds<'_, '_, 'tcx> {
61+
fn visit_ty(&mut self, ty: Ty<'tcx>) -> std::ops::ControlFlow<Self::BreakTy> {
62+
if let ty::Opaque(def_id, substs) = *ty.kind()
63+
&& let Some(hir::Node::Item(item)) = self.cx.tcx.hir().get_if_local(def_id)
64+
&& let hir::ItemKind::OpaqueTy(opaque) = &item.kind
65+
&& let hir::OpaqueTyOrigin::FnReturn(origin_def_id) = opaque.origin
66+
&& origin_def_id == self.fn_def_id
67+
{
68+
for pred_and_span in self.cx.tcx.bound_explicit_item_bounds(def_id).transpose_iter() {
69+
let pred_span = pred_and_span.0.1;
70+
let predicate = self.cx.tcx.liberate_late_bound_regions(
71+
def_id,
72+
pred_and_span.map_bound(|(pred, _)| *pred).subst(self.cx.tcx, substs).kind(),
73+
);
74+
let ty::PredicateKind::Projection(proj) = predicate else {
75+
continue;
76+
};
77+
let Some(proj_term) = proj.term.ty() else { continue };
78+
79+
let proj_ty = self
80+
.cx
81+
.tcx
82+
.mk_projection(proj.projection_ty.item_def_id, proj.projection_ty.substs);
83+
let proj_replacer = &mut BottomUpFolder {
84+
tcx: self.cx.tcx,
85+
ty_op: |ty| if ty == proj_ty { proj_term } else { ty },
86+
lt_op: |lt| lt,
87+
ct_op: |ct| ct,
88+
};
89+
for assoc_pred_and_span in self
90+
.cx
91+
.tcx
92+
.bound_explicit_item_bounds(proj.projection_ty.item_def_id)
93+
.transpose_iter()
94+
{
95+
let assoc_pred_span = assoc_pred_and_span.0.1;
96+
let assoc_pred = assoc_pred_and_span
97+
.map_bound(|(pred, _)| *pred)
98+
.subst(self.cx.tcx, &proj.projection_ty.substs)
99+
.fold_with(proj_replacer);
100+
if !self.infcx.predicate_must_hold_modulo_regions(&traits::Obligation::new(
101+
traits::ObligationCause::dummy(),
102+
self.cx.param_env,
103+
assoc_pred,
104+
)) {
105+
let (suggestion, suggest_span) =
106+
match (proj_term.kind(), assoc_pred.kind().skip_binder()) {
107+
(ty::Opaque(def_id, _), ty::PredicateKind::Trait(trait_pred)) => (
108+
format!(" + {}", trait_pred.print_modifiers_and_trait_path()),
109+
Some(self.cx.tcx.def_span(def_id).shrink_to_hi()),
110+
),
111+
_ => (String::new(), None),
112+
};
113+
self.cx.emit_spanned_lint(
114+
RPIT_HIDDEN_INFERRED_BOUND,
115+
pred_span,
116+
RpitHiddenInferredBoundLint {
117+
ty,
118+
proj_ty: proj_term,
119+
assoc_pred_span,
120+
suggestion,
121+
suggest_span,
122+
},
123+
);
124+
}
125+
}
126+
}
127+
}
128+
129+
ty.super_visit_with(self)
130+
}
131+
}
132+
133+
#[derive(LintDiagnostic)]
134+
#[diag(lint::rpit_hidden_inferred_bound)]
135+
struct RpitHiddenInferredBoundLint<'tcx> {
136+
ty: Ty<'tcx>,
137+
proj_ty: Ty<'tcx>,
138+
#[label(lint::specifically)]
139+
assoc_pred_span: Span,
140+
#[suggestion_verbose(applicability = "machine-applicable", code = "{suggestion}")]
141+
suggest_span: Option<Span>,
142+
suggestion: String,
143+
}

compiler/rustc_middle/src/lint.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,9 @@ pub fn in_external_macro(sess: &Session, span: Span) -> bool {
444444
match expn_data.kind {
445445
ExpnKind::Inlined
446446
| ExpnKind::Root
447-
| ExpnKind::Desugaring(DesugaringKind::ForLoop | DesugaringKind::WhileLoop) => false,
447+
| ExpnKind::Desugaring(
448+
DesugaringKind::ForLoop | DesugaringKind::WhileLoop | DesugaringKind::OpaqueTy,
449+
) => false,
448450
ExpnKind::AstPass(_) | ExpnKind::Desugaring(_) => true, // well, it's "external"
449451
ExpnKind::Macro(MacroKind::Bang, _) => {
450452
// Dummy span for the `def_site` means it's an external macro.

0 commit comments

Comments
 (0)