Skip to content

Commit 6cd9ab6

Browse files
Don't worry about uncaptured contravariant lifetimes if they outlive a captured lifetime
1 parent 91376f4 commit 6cd9ab6

File tree

2 files changed

+242
-21
lines changed

2 files changed

+242
-21
lines changed

compiler/rustc_lint/src/impl_trait_overcaptures.rs

Lines changed: 226 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,26 @@
1-
use rustc_data_structures::fx::FxIndexSet;
1+
use std::cell::LazyCell;
2+
3+
use rustc_data_structures::fx::{FxHashMap, FxIndexMap, FxIndexSet};
24
use rustc_data_structures::unord::UnordSet;
35
use rustc_errors::{Applicability, LintDiagnostic};
46
use rustc_hir as hir;
57
use rustc_hir::def::DefKind;
68
use rustc_hir::def_id::{DefId, LocalDefId};
9+
use rustc_infer::infer::outlives::env::OutlivesEnvironment;
10+
use rustc_infer::infer::TyCtxtInferExt;
711
use rustc_macros::LintDiagnostic;
8-
use rustc_middle::bug;
912
use rustc_middle::middle::resolve_bound_vars::ResolvedArg;
13+
use rustc_middle::ty::relate::{
14+
structurally_relate_consts, structurally_relate_tys, Relate, RelateResult, TypeRelation,
15+
};
1016
use rustc_middle::ty::{
1117
self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor,
1218
};
19+
use rustc_middle::{bug, span_bug};
1320
use rustc_session::{declare_lint, declare_lint_pass};
14-
use rustc_span::Span;
21+
use rustc_span::{Span, Symbol};
22+
use rustc_trait_selection::traits::outlives_bounds::InferCtxtExt;
23+
use rustc_trait_selection::traits::ObligationCtxt;
1524

1625
use crate::{fluent_generated as fluent, LateContext, LateLintPass};
1726

@@ -122,38 +131,86 @@ impl<'tcx> LateLintPass<'tcx> for ImplTraitOvercaptures {
122131
}
123132
}
124133

134+
#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
135+
enum ParamKind {
136+
// Early-bound var.
137+
Early(Symbol, u32),
138+
// Late-bound var on function, not within a binder. We can capture these.
139+
Free(DefId, Symbol),
140+
// Late-bound var in a binder. We can't capture these yet.
141+
Late,
142+
}
143+
125144
fn check_fn(tcx: TyCtxt<'_>, parent_def_id: LocalDefId) {
126145
let sig = tcx.fn_sig(parent_def_id).instantiate_identity();
127146

128-
let mut in_scope_parameters = FxIndexSet::default();
147+
let mut in_scope_parameters = FxIndexMap::default();
129148
// Populate the in_scope_parameters list first with all of the generics in scope
130149
let mut current_def_id = Some(parent_def_id.to_def_id());
131150
while let Some(def_id) = current_def_id {
132151
let generics = tcx.generics_of(def_id);
133152
for param in &generics.own_params {
134-
in_scope_parameters.insert(param.def_id);
153+
in_scope_parameters.insert(param.def_id, ParamKind::Early(param.name, param.index));
135154
}
136155
current_def_id = generics.parent;
137156
}
138157

158+
for bound_var in sig.bound_vars() {
159+
let ty::BoundVariableKind::Region(ty::BoundRegionKind::BrNamed(def_id, name)) = bound_var
160+
else {
161+
span_bug!(tcx.def_span(parent_def_id), "unexpected non-lifetime binder on fn sig");
162+
};
163+
164+
in_scope_parameters.insert(def_id, ParamKind::Free(def_id, name));
165+
}
166+
167+
let sig = tcx.liberate_late_bound_regions(parent_def_id.to_def_id(), sig);
168+
139169
// Then visit the signature to walk through all the binders (incl. the late-bound
140170
// vars on the function itself, which we need to count too).
141171
sig.visit_with(&mut VisitOpaqueTypes {
142172
tcx,
143173
parent_def_id,
144174
in_scope_parameters,
145175
seen: Default::default(),
176+
// Lazily compute these two, since they're likely a bit expensive.
177+
variances: LazyCell::new(|| {
178+
let mut functional_variances = FunctionalVariances {
179+
tcx: tcx,
180+
variances: FxHashMap::default(),
181+
ambient_variance: ty::Covariant,
182+
generics: tcx.generics_of(parent_def_id),
183+
};
184+
let _ = functional_variances.relate(sig, sig);
185+
functional_variances.variances
186+
}),
187+
outlives_env: LazyCell::new(|| {
188+
let param_env = tcx.param_env(parent_def_id);
189+
let infcx = tcx.infer_ctxt().build();
190+
let ocx = ObligationCtxt::new(&infcx);
191+
let assumed_wf_tys = ocx.assumed_wf_types(param_env, parent_def_id).unwrap_or_default();
192+
let implied_bounds =
193+
infcx.implied_bounds_tys_compat(param_env, parent_def_id, &assumed_wf_tys, false);
194+
OutlivesEnvironment::with_bounds(param_env, implied_bounds)
195+
}),
146196
});
147197
}
148198

149-
struct VisitOpaqueTypes<'tcx> {
199+
struct VisitOpaqueTypes<'tcx, VarFn, OutlivesFn> {
150200
tcx: TyCtxt<'tcx>,
151201
parent_def_id: LocalDefId,
152-
in_scope_parameters: FxIndexSet<DefId>,
202+
in_scope_parameters: FxIndexMap<DefId, ParamKind>,
203+
variances: LazyCell<FxHashMap<DefId, ty::Variance>, VarFn>,
204+
outlives_env: LazyCell<OutlivesEnvironment<'tcx>, OutlivesFn>,
153205
seen: FxIndexSet<LocalDefId>,
154206
}
155207

156-
impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for VisitOpaqueTypes<'tcx> {
208+
impl<'tcx, VarFn, OutlivesFn> TypeVisitor<TyCtxt<'tcx>>
209+
for VisitOpaqueTypes<'tcx, VarFn, OutlivesFn>
210+
where
211+
VarFn: FnOnce() -> FxHashMap<DefId, ty::Variance>,
212+
OutlivesFn: FnOnce() -> OutlivesEnvironment<'tcx>,
213+
{
157214
fn visit_binder<T: TypeVisitable<TyCtxt<'tcx>>>(
158215
&mut self,
159216
t: &ty::Binder<'tcx, T>,
@@ -166,8 +223,8 @@ impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for VisitOpaqueTypes<'tcx> {
166223
ty::BoundVariableKind::Region(ty::BoundRegionKind::BrNamed(def_id, ..))
167224
| ty::BoundVariableKind::Ty(ty::BoundTyKind::Param(def_id, _)) => {
168225
added.push(def_id);
169-
let unique = self.in_scope_parameters.insert(def_id);
170-
assert!(unique);
226+
let unique = self.in_scope_parameters.insert(def_id, ParamKind::Late);
227+
assert_eq!(unique, None);
171228
}
172229
_ => {
173230
self.tcx.dcx().span_delayed_bug(
@@ -212,6 +269,7 @@ impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for VisitOpaqueTypes<'tcx> {
212269
{
213270
// Compute the set of args that are captured by the opaque...
214271
let mut captured = FxIndexSet::default();
272+
let mut captured_regions = FxIndexSet::default();
215273
let variances = self.tcx.variances_of(opaque_def_id);
216274
let mut current_def_id = Some(opaque_def_id.to_def_id());
217275
while let Some(def_id) = current_def_id {
@@ -221,25 +279,60 @@ impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for VisitOpaqueTypes<'tcx> {
221279
if variances[param.index as usize] != ty::Invariant {
222280
continue;
223281
}
282+
283+
let arg = opaque_ty.args[param.index as usize];
224284
// We need to turn all `ty::Param`/`ConstKind::Param` and
225285
// `ReEarlyParam`/`ReBound` into def ids.
226-
captured.insert(extract_def_id_from_arg(
227-
self.tcx,
228-
generics,
229-
opaque_ty.args[param.index as usize],
230-
));
286+
captured.insert(extract_def_id_from_arg(self.tcx, generics, arg));
287+
288+
captured_regions.extend(arg.as_region());
231289
}
232290
current_def_id = generics.parent;
233291
}
234292

235293
// Compute the set of in scope params that are not captured. Get their spans,
236294
// since that's all we really care about them for emitting the diagnostic.
237-
let uncaptured_spans: Vec<_> = self
295+
let mut uncaptured_args: FxIndexSet<_> = self
238296
.in_scope_parameters
239297
.iter()
240-
.filter(|def_id| !captured.contains(*def_id))
241-
.map(|def_id| self.tcx.def_span(def_id))
298+
.filter(|&(def_id, _)| !captured.contains(def_id))
299+
.collect();
300+
301+
// These are args that we know are likely fine to "overcapture", since they can be
302+
// contravariantly shortened to one of the already-captured lifetimes that they
303+
// outlive.
304+
let covariant_long_args: FxIndexSet<_> = uncaptured_args
305+
.iter()
306+
.copied()
307+
.filter(|&(def_id, kind)| {
308+
let Some(ty::Bivariant | ty::Contravariant) = self.variances.get(def_id) else {
309+
return false;
310+
};
311+
let DefKind::LifetimeParam = self.tcx.def_kind(def_id) else {
312+
return false;
313+
};
314+
let uncaptured = match *kind {
315+
ParamKind::Early(name, index) => ty::Region::new_early_param(
316+
self.tcx,
317+
ty::EarlyParamRegion { name, index },
318+
),
319+
ParamKind::Free(def_id, name) => ty::Region::new_late_param(
320+
self.tcx,
321+
self.parent_def_id.to_def_id(),
322+
ty::BoundRegionKind::BrNamed(def_id, name),
323+
),
324+
ParamKind::Late => return false,
325+
};
326+
// Does this region outlive any captured region?
327+
captured_regions.iter().any(|r| {
328+
self.outlives_env
329+
.free_region_map()
330+
.sub_free_regions(self.tcx, *r, uncaptured)
331+
})
332+
})
242333
.collect();
334+
// We don't care to warn on these args.
335+
uncaptured_args.retain(|arg| !covariant_long_args.contains(arg));
243336

244337
let opaque_span = self.tcx.def_span(opaque_def_id);
245338
let new_capture_rules =
@@ -249,7 +342,7 @@ impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for VisitOpaqueTypes<'tcx> {
249342
// `use<>` syntax on it, and we're < edition 2024, then warn the user.
250343
if !new_capture_rules
251344
&& !opaque.bounds.iter().any(|bound| matches!(bound, hir::GenericBound::Use(..)))
252-
&& !uncaptured_spans.is_empty()
345+
&& !uncaptured_args.is_empty()
253346
{
254347
let suggestion = if let Ok(snippet) =
255348
self.tcx.sess.source_map().span_to_snippet(opaque_span)
@@ -277,6 +370,11 @@ impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for VisitOpaqueTypes<'tcx> {
277370
None
278371
};
279372

373+
let uncaptured_spans: Vec<_> = uncaptured_args
374+
.into_iter()
375+
.map(|(def_id, _)| self.tcx.def_span(def_id))
376+
.collect();
377+
280378
self.tcx.emit_node_span_lint(
281379
IMPL_TRAIT_OVERCAPTURES,
282380
self.tcx.local_def_id_to_hir_id(opaque_def_id),
@@ -329,7 +427,7 @@ impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for VisitOpaqueTypes<'tcx> {
329427
if self
330428
.in_scope_parameters
331429
.iter()
332-
.all(|def_id| explicitly_captured.contains(def_id))
430+
.all(|(def_id, _)| explicitly_captured.contains(def_id))
333431
{
334432
self.tcx.emit_node_span_lint(
335433
IMPL_TRAIT_REDUNDANT_CAPTURES,
@@ -398,7 +496,11 @@ fn extract_def_id_from_arg<'tcx>(
398496
ty::ReBound(
399497
_,
400498
ty::BoundRegion { kind: ty::BoundRegionKind::BrNamed(def_id, ..), .. },
401-
) => def_id,
499+
)
500+
| ty::ReLateParam(ty::LateParamRegion {
501+
scope: _,
502+
bound_region: ty::BoundRegionKind::BrNamed(def_id, ..),
503+
}) => def_id,
402504
_ => unreachable!(),
403505
},
404506
ty::GenericArgKind::Type(ty) => {
@@ -415,3 +517,106 @@ fn extract_def_id_from_arg<'tcx>(
415517
}
416518
}
417519
}
520+
521+
/// Computes the variances of regions that appear in the type, but considering
522+
/// late-bound regions too, which don't have their variance computed usually.
523+
///
524+
/// Like generalization, this is a unary operation implemented on top of the binary
525+
/// relation infrastructure, mostly because it's much easier to have the relation
526+
/// track the variance for you, rather than having to do it yourself.
527+
struct FunctionalVariances<'tcx> {
528+
tcx: TyCtxt<'tcx>,
529+
variances: FxHashMap<DefId, ty::Variance>,
530+
ambient_variance: ty::Variance,
531+
generics: &'tcx ty::Generics,
532+
}
533+
534+
impl<'tcx> TypeRelation<TyCtxt<'tcx>> for FunctionalVariances<'tcx> {
535+
fn cx(&self) -> TyCtxt<'tcx> {
536+
self.tcx
537+
}
538+
539+
fn relate_with_variance<T: ty::relate::Relate<TyCtxt<'tcx>>>(
540+
&mut self,
541+
variance: rustc_type_ir::Variance,
542+
_: ty::VarianceDiagInfo<TyCtxt<'tcx>>,
543+
a: T,
544+
b: T,
545+
) -> RelateResult<'tcx, T> {
546+
let old_variance = self.ambient_variance;
547+
self.ambient_variance = self.ambient_variance.xform(variance);
548+
self.relate(a, b)?;
549+
self.ambient_variance = old_variance;
550+
Ok(a)
551+
}
552+
553+
fn tys(&mut self, a: Ty<'tcx>, b: Ty<'tcx>) -> RelateResult<'tcx, Ty<'tcx>> {
554+
structurally_relate_tys(self, a, b)?;
555+
Ok(a)
556+
}
557+
558+
fn regions(
559+
&mut self,
560+
a: ty::Region<'tcx>,
561+
_: ty::Region<'tcx>,
562+
) -> RelateResult<'tcx, ty::Region<'tcx>> {
563+
let def_id = match *a {
564+
ty::ReEarlyParam(ebr) => self.generics.region_param(ebr, self.tcx).def_id,
565+
ty::ReBound(
566+
_,
567+
ty::BoundRegion { kind: ty::BoundRegionKind::BrNamed(def_id, ..), .. },
568+
)
569+
| ty::ReLateParam(ty::LateParamRegion {
570+
scope: _,
571+
bound_region: ty::BoundRegionKind::BrNamed(def_id, ..),
572+
}) => def_id,
573+
_ => {
574+
return Ok(a);
575+
}
576+
};
577+
578+
if let Some(variance) = self.variances.get_mut(&def_id) {
579+
*variance = unify(*variance, self.ambient_variance);
580+
} else {
581+
self.variances.insert(def_id, self.ambient_variance);
582+
}
583+
584+
Ok(a)
585+
}
586+
587+
fn consts(
588+
&mut self,
589+
a: ty::Const<'tcx>,
590+
b: ty::Const<'tcx>,
591+
) -> RelateResult<'tcx, ty::Const<'tcx>> {
592+
structurally_relate_consts(self, a, b)?;
593+
Ok(a)
594+
}
595+
596+
fn binders<T>(
597+
&mut self,
598+
a: ty::Binder<'tcx, T>,
599+
b: ty::Binder<'tcx, T>,
600+
) -> RelateResult<'tcx, ty::Binder<'tcx, T>>
601+
where
602+
T: Relate<TyCtxt<'tcx>>,
603+
{
604+
self.relate(a.skip_binder(), b.skip_binder())?;
605+
Ok(a)
606+
}
607+
}
608+
609+
/// What is the variance that satisfies the two variances?
610+
fn unify(a: ty::Variance, b: ty::Variance) -> ty::Variance {
611+
match (a, b) {
612+
// Bivariance is lattice bottom.
613+
(ty::Bivariant, other) | (other, ty::Bivariant) => other,
614+
// Invariant is lattice top.
615+
(ty::Invariant, _) | (_, ty::Invariant) => ty::Invariant,
616+
// If type is required to be covariant and contravariant, then it's invariant.
617+
(ty::Contravariant, ty::Covariant) | (ty::Covariant, ty::Contravariant) => ty::Invariant,
618+
// Otherwise, co + co = co, contra + contra = contra.
619+
(ty::Contravariant, ty::Contravariant) => ty::Contravariant,
620+
(ty::Covariant, ty::Covariant) => ty::Covariant,
621+
}
622+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//@ check-pass
2+
3+
#![feature(precise_capturing)]
4+
#![deny(impl_trait_overcaptures)]
5+
6+
struct Ctxt<'tcx>(&'tcx ());
7+
8+
// In `compute`, we don't care that we're "overcapturing" `'tcx`
9+
// in edition 2024, because it can be shortened at the call site
10+
// and we know it outlives `'_`.
11+
12+
impl<'tcx> Ctxt<'tcx> {
13+
fn compute(&self) -> impl Sized + '_ {}
14+
}
15+
16+
fn main() {}

0 commit comments

Comments
 (0)