Skip to content

Commit d57e57c

Browse files
committedMay 14, 2024
Implement initial IMPL_TRAIT_OVERCAPTURES lint
1 parent 9105c57 commit d57e57c

File tree

3 files changed

+246
-0
lines changed

3 files changed

+246
-0
lines changed
 

‎compiler/rustc_lint/messages.ftl

+7
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@ lint_array_into_iter =
99
.use_explicit_into_iter_suggestion =
1010
or use `IntoIterator::into_iter(..)` instead of `.into_iter()` to explicitly iterate by value
1111
12+
lint_impl_trait_overcaptures = `{$self_ty}` will capture more lifetimes than possibly intended in edition 2024
13+
.note = specifically, {$num_captured ->
14+
[one] this lifetime is
15+
*[other] these lifetimes are
16+
} in scope but not mentioned in the type's bounds
17+
.note2 = all lifetimes in scope will be captured by `impl Trait`s in edition 2024
18+
1219
lint_async_fn_in_trait = use of `async fn` in public traits is discouraged as auto trait bounds cannot be specified
1320
.note = you can suppress this lint if you plan to use the trait only in your own code, or do not care about auto traits like `Send` on the `Future`
1421
.suggestion = you can alternatively desugar to a normal `fn` that returns `impl Future` and add any desired bounds such as `Send`, but these cannot be relaxed without a breaking API change
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
use rustc_data_structures::{fx::FxIndexSet, unord::UnordSet};
2+
use rustc_errors::LintDiagnostic;
3+
use rustc_hir as hir;
4+
use rustc_hir::def::DefKind;
5+
use rustc_hir::def_id::{DefId, LocalDefId};
6+
use rustc_hir::intravisit;
7+
use rustc_middle::ty::{
8+
self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor,
9+
};
10+
use rustc_span::Span;
11+
12+
use crate::fluent_generated as fluent;
13+
use crate::{LateContext, LateLintPass};
14+
15+
declare_lint! {
16+
/// UwU
17+
pub IMPL_TRAIT_OVERCAPTURES,
18+
Warn,
19+
"will capture more lifetimes than possibly intended in edition 2024",
20+
@future_incompatible = FutureIncompatibleInfo {
21+
reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2024),
22+
reference: "<https://doc.rust-lang.org/nightly/edition-guide/rust-2021/IntoIterator-for-arrays.html>",
23+
};
24+
}
25+
26+
declare_lint_pass!(
27+
/// Lint for use of `async fn` in the definition of a publicly-reachable
28+
/// trait.
29+
ImplTraitOvercaptures => [IMPL_TRAIT_OVERCAPTURES]
30+
);
31+
32+
impl<'tcx> LateLintPass<'tcx> for ImplTraitOvercaptures {
33+
fn check_fn(
34+
&mut self,
35+
cx: &LateContext<'tcx>,
36+
_: intravisit::FnKind<'tcx>,
37+
_: &'tcx hir::FnDecl<'tcx>,
38+
_: &'tcx hir::Body<'tcx>,
39+
_: Span,
40+
parent_def_id: LocalDefId,
41+
) {
42+
match cx.tcx.def_kind(parent_def_id) {
43+
DefKind::AssocFn => {
44+
// RPITITs already capture all lifetimes in scope, so skip them.
45+
if matches!(
46+
cx.tcx.def_kind(cx.tcx.local_parent(parent_def_id)),
47+
DefKind::Trait | DefKind::Impl { of_trait: true }
48+
) {
49+
return;
50+
}
51+
}
52+
DefKind::Fn => {
53+
// All freee functions need to check for overcaptures.
54+
}
55+
DefKind::Closure => return,
56+
kind => {
57+
unreachable!(
58+
"expected function item, found {}",
59+
kind.descr(parent_def_id.to_def_id())
60+
)
61+
}
62+
}
63+
64+
let sig = cx.tcx.fn_sig(parent_def_id).instantiate_identity();
65+
66+
let mut in_scope_parameters = FxIndexSet::default();
67+
let mut current_def_id = Some(parent_def_id.to_def_id());
68+
while let Some(def_id) = current_def_id {
69+
let generics = cx.tcx.generics_of(def_id);
70+
for param in &generics.params {
71+
in_scope_parameters.insert(param.def_id);
72+
}
73+
current_def_id = generics.parent;
74+
}
75+
76+
sig.visit_with(&mut VisitOpaqueTypes {
77+
tcx: cx.tcx,
78+
parent_def_id,
79+
in_scope_parameters,
80+
seen: Default::default(),
81+
});
82+
}
83+
}
84+
85+
struct VisitOpaqueTypes<'tcx> {
86+
tcx: TyCtxt<'tcx>,
87+
parent_def_id: LocalDefId,
88+
in_scope_parameters: FxIndexSet<DefId>,
89+
seen: FxIndexSet<LocalDefId>,
90+
}
91+
92+
impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for VisitOpaqueTypes<'tcx> {
93+
fn visit_binder<T: TypeVisitable<TyCtxt<'tcx>>>(
94+
&mut self,
95+
t: &ty::Binder<'tcx, T>,
96+
) -> Self::Result {
97+
let mut added = vec![];
98+
for arg in t.bound_vars() {
99+
let arg: ty::BoundVariableKind = arg;
100+
match arg {
101+
ty::BoundVariableKind::Region(ty::BoundRegionKind::BrNamed(def_id, ..)) => {
102+
added.push(def_id);
103+
let unique = self.in_scope_parameters.insert(def_id);
104+
assert!(unique);
105+
}
106+
ty::BoundVariableKind::Ty(_) => {
107+
todo!("we don't support late-bound type params in `impl Trait`")
108+
}
109+
ty::BoundVariableKind::Region(..) => {
110+
unreachable!("all AST-derived bound regions should have a name")
111+
}
112+
ty::BoundVariableKind::Const => {
113+
unreachable!("non-lifetime binder consts are not allowed")
114+
}
115+
}
116+
}
117+
118+
t.super_visit_with(self);
119+
120+
for arg in added.into_iter().rev() {
121+
self.in_scope_parameters.shift_remove(&arg);
122+
}
123+
}
124+
125+
fn visit_ty(&mut self, t: Ty<'tcx>) -> Self::Result {
126+
if !t.has_opaque_types() {
127+
return;
128+
}
129+
130+
if let ty::Alias(ty::Opaque, opaque_ty) = *t.kind()
131+
&& let Some(opaque_def_id) = opaque_ty.def_id.as_local()
132+
&& self.seen.insert(opaque_def_id)
133+
&& let opaque =
134+
self.tcx.hir_node_by_def_id(opaque_def_id).expect_item().expect_opaque_ty()
135+
&& let hir::OpaqueTyOrigin::FnReturn(parent_def_id) = opaque.origin
136+
&& parent_def_id == self.parent_def_id
137+
&& opaque.precise_capturing_args.is_none()
138+
{
139+
let mut captured = UnordSet::default();
140+
let variances = self.tcx.variances_of(opaque_def_id);
141+
let mut current_def_id = Some(opaque_def_id.to_def_id());
142+
while let Some(def_id) = current_def_id {
143+
let generics = self.tcx.generics_of(def_id);
144+
for param in &generics.params {
145+
if variances[param.index as usize] != ty::Invariant {
146+
continue;
147+
}
148+
captured.insert(extract_def_id_from_arg(
149+
self.tcx,
150+
generics,
151+
opaque_ty.args[param.index as usize],
152+
));
153+
}
154+
current_def_id = generics.parent;
155+
}
156+
157+
let uncaptured_spans: Vec<_> = self
158+
.in_scope_parameters
159+
.iter()
160+
.filter(|def_id| !captured.contains(def_id))
161+
.map(|def_id| self.tcx.def_span(def_id))
162+
.collect();
163+
164+
if !uncaptured_spans.is_empty() {
165+
self.tcx.emit_node_lint(
166+
IMPL_TRAIT_OVERCAPTURES,
167+
self.tcx.local_def_id_to_hir_id(opaque_def_id),
168+
ImplTraitOvercapturesLint {
169+
opaque_span: self.tcx.def_span(opaque_def_id),
170+
self_ty: t,
171+
num_captured: uncaptured_spans.len(),
172+
uncaptured_spans,
173+
},
174+
);
175+
}
176+
177+
for clause in
178+
self.tcx.item_bounds(opaque_ty.def_id).iter_instantiated(self.tcx, opaque_ty.args)
179+
{
180+
clause.visit_with(self)
181+
}
182+
}
183+
184+
t.super_visit_with(self);
185+
}
186+
}
187+
188+
struct ImplTraitOvercapturesLint<'tcx> {
189+
opaque_span: Span,
190+
uncaptured_spans: Vec<Span>,
191+
self_ty: Ty<'tcx>,
192+
num_captured: usize,
193+
}
194+
195+
impl<'a> LintDiagnostic<'a, ()> for ImplTraitOvercapturesLint<'_> {
196+
fn decorate_lint<'b>(self, diag: &'b mut rustc_errors::Diag<'a, ()>) {
197+
diag.arg("self_ty", self.self_ty.to_string())
198+
.arg("num_captured", self.num_captured)
199+
.span(self.opaque_span)
200+
.span_note(self.uncaptured_spans, fluent::lint_note)
201+
.note(fluent::lint_note2);
202+
}
203+
204+
fn msg(&self) -> rustc_errors::DiagMessage {
205+
fluent::lint_impl_trait_overcaptures
206+
}
207+
}
208+
209+
fn extract_def_id_from_arg<'tcx>(
210+
tcx: TyCtxt<'tcx>,
211+
generics: &'tcx ty::Generics,
212+
arg: ty::GenericArg<'tcx>,
213+
) -> DefId {
214+
match arg.unpack() {
215+
ty::GenericArgKind::Lifetime(re) => match *re {
216+
ty::ReEarlyParam(ebr) => generics.region_param(ebr, tcx).def_id,
217+
ty::ReBound(
218+
_,
219+
ty::BoundRegion { kind: ty::BoundRegionKind::BrNamed(def_id, ..), .. },
220+
) => def_id,
221+
_ => unreachable!(),
222+
},
223+
ty::GenericArgKind::Type(ty) => {
224+
let ty::Param(param_ty) = *ty.kind() else {
225+
bug!();
226+
};
227+
generics.type_param(param_ty, tcx).def_id
228+
}
229+
ty::GenericArgKind::Const(ct) => {
230+
let ty::ConstKind::Param(param_ct) = ct.kind() else {
231+
bug!();
232+
};
233+
generics.const_param(param_ct, tcx).def_id
234+
}
235+
}
236+
}

‎compiler/rustc_lint/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ mod expect;
5555
mod for_loops_over_fallibles;
5656
mod foreign_modules;
5757
pub mod hidden_unicode_codepoints;
58+
mod impl_trait_overcaptures;
5859
mod internal;
5960
mod invalid_from_utf8;
6061
mod late;
@@ -94,6 +95,7 @@ use drop_forget_useless::*;
9495
use enum_intrinsics_non_enums::EnumIntrinsicsNonEnums;
9596
use for_loops_over_fallibles::*;
9697
use hidden_unicode_codepoints::*;
98+
use impl_trait_overcaptures::ImplTraitOvercaptures;
9799
use internal::*;
98100
use invalid_from_utf8::*;
99101
use let_underscore::*;
@@ -228,6 +230,7 @@ late_lint_methods!(
228230
MissingDoc: MissingDoc,
229231
AsyncFnInTrait: AsyncFnInTrait,
230232
NonLocalDefinitions: NonLocalDefinitions::default(),
233+
ImplTraitOvercaptures: ImplTraitOvercaptures,
231234
]
232235
]
233236
);

0 commit comments

Comments
 (0)
Please sign in to comment.