Skip to content

Commit 503bc7c

Browse files
committed
Report change in RPITIT lifetime capture clauses.
1 parent 344889e commit 503bc7c

File tree

5 files changed

+280
-86
lines changed

5 files changed

+280
-86
lines changed

compiler/rustc_hir_analysis/src/check/compare_method.rs

+157-73
Original file line numberDiff line numberDiff line change
@@ -367,26 +367,29 @@ pub fn collect_trait_impl_trait_tys<'tcx>(
367367
let impl_sig = ocx.normalize(
368368
&norm_cause,
369369
param_env,
370-
infcx.replace_bound_vars_with_fresh_vars(
371-
return_span,
372-
infer::HigherRankedType,
373-
tcx.fn_sig(impl_m.def_id),
374-
),
370+
tcx.liberate_late_bound_regions(impl_m.def_id, tcx.fn_sig(impl_m.def_id)),
375371
);
372+
debug!(?impl_sig);
376373
let impl_return_ty = impl_sig.output();
377374

378375
// Normalize the trait signature with liberated bound vars, passing it through
379376
// the ImplTraitInTraitCollector, which gathers all of the RPITITs and replaces
380377
// them with inference variables.
381378
// We will use these inference variables to collect the hidden types of RPITITs.
382379
let mut collector = ImplTraitInTraitCollector::new(&ocx, return_span, param_env, impl_m_hir_id);
383-
let unnormalized_trait_sig = tcx
384-
.liberate_late_bound_regions(
385-
impl_m.def_id,
380+
let unnormalized_trait_sig = infcx
381+
.replace_bound_vars_with_fresh_vars(
382+
return_span,
383+
infer::HigherRankedType,
386384
tcx.bound_fn_sig(trait_m.def_id).subst(tcx, trait_to_placeholder_substs),
387385
)
388386
.fold_with(&mut collector);
387+
if collector.types.is_empty() {
388+
return Ok(&*tcx.arena.alloc(FxHashMap::default()));
389+
}
390+
389391
let trait_sig = ocx.normalize(&norm_cause, param_env, unnormalized_trait_sig);
392+
debug!(?trait_sig);
390393
let trait_return_ty = trait_sig.output();
391394

392395
let wf_tys = FxIndexSet::from_iter(
@@ -422,7 +425,7 @@ pub fn collect_trait_impl_trait_tys<'tcx>(
422425
}
423426
}
424427

425-
debug!(?trait_sig, ?impl_sig, "equating function signatures");
428+
debug!("equating function signatures");
426429

427430
let trait_fty = tcx.mk_fn_ptr(ty::Binder::dummy(trait_sig));
428431
let impl_fty = tcx.mk_fn_ptr(ty::Binder::dummy(impl_sig));
@@ -461,86 +464,167 @@ pub fn collect_trait_impl_trait_tys<'tcx>(
461464

462465
// Finally, resolve all regions. This catches wily misuses of
463466
// lifetime parameters.
467+
debug!("check_region_obligations");
464468
let outlives_environment = OutlivesEnvironment::with_bounds(
465469
param_env,
466470
Some(infcx),
467471
infcx.implied_bounds_tys(param_env, impl_m_hir_id, wf_tys),
468472
);
473+
debug!(region_bound_pairs=?outlives_environment.region_bound_pairs());
469474
infcx.check_region_obligations_and_report_errors(
470475
impl_m.def_id.expect_local(),
471476
&outlives_environment,
472477
);
473478

479+
debug!("resolve collected");
474480
let mut collected_tys = FxHashMap::default();
475-
for (def_id, (ty, substs)) in collector.types {
476-
match infcx.fully_resolve(ty) {
477-
Ok(ty) => {
478-
// `ty` contains free regions that we created earlier while liberating the
479-
// trait fn signature. However, projection normalization expects `ty` to
480-
// contains `def_id`'s early-bound regions.
481-
let id_substs = InternalSubsts::identity_for_item(tcx, def_id);
482-
debug!(?id_substs, ?substs);
483-
let map: FxHashMap<ty::GenericArg<'tcx>, ty::GenericArg<'tcx>> =
484-
std::iter::zip(substs, id_substs).collect();
485-
debug!(?map);
486-
487-
// NOTE(compiler-errors): RPITITs, like all other RPITs, have early-bound
488-
// region substs that are synthesized during AST lowering. These are substs
489-
// that are appended to the parent substs (trait and trait method). However,
490-
// we're trying to infer the unsubstituted type value of the RPITIT inside
491-
// the *impl*, so we can later use the impl's method substs to normalize
492-
// an RPITIT to a concrete type (`confirm_impl_trait_in_trait_candidate`).
493-
//
494-
// Due to the design of RPITITs, during AST lowering, we have no idea that
495-
// an impl method corresponds to a trait method with RPITITs in it. Therefore,
496-
// we don't have a list of early-bound region substs for the RPITIT in the impl.
497-
// Since early region parameters are index-based, we can't just rebase these
498-
// (trait method) early-bound region substs onto the impl, and there's no
499-
// guarantee that the indices from the trait substs and impl substs line up.
500-
// So to fix this, we subtract the number of trait substs and add the number of
501-
// impl substs to *renumber* these early-bound regions to their corresponding
502-
// indices in the impl's substitutions list.
503-
//
504-
// Also, we only need to account for a difference in trait and impl substs,
505-
// since we previously enforce that the trait method and impl method have the
506-
// same generics.
507-
let num_trait_substs = trait_to_impl_substs.len();
508-
let num_impl_substs = tcx.generics_of(impl_m.container_id(tcx)).params.len();
509-
let ty = tcx.fold_regions(ty, |region, _| {
510-
match region.kind() {
511-
// Remap all free regions, which correspond to late-bound regions in the function.
512-
ty::ReFree(_) => {}
513-
// Remap early-bound regions as long as they don't come from the `impl` itself.
514-
ty::ReEarlyBound(ebr) if tcx.parent(ebr.def_id) != impl_m.container_id(tcx) => {}
515-
_ => return region,
516-
}
517-
let Some(ty::ReEarlyBound(e)) = map.get(&region.into()).map(|r| r.expect_region().kind())
518-
else {
519-
tcx
520-
.sess
521-
.delay_span_bug(
522-
return_span,
523-
"expected ReFree to map to ReEarlyBound"
524-
);
525-
return tcx.lifetimes.re_static;
526-
};
527-
tcx.mk_region(ty::ReEarlyBound(ty::EarlyBoundRegion {
528-
def_id: e.def_id,
529-
name: e.name,
530-
index: (e.index as usize - num_trait_substs + num_impl_substs) as u32,
531-
}))
532-
});
533-
debug!(%ty);
534-
collected_tys.insert(def_id, ty);
481+
for (def_id, (raw_ty, substs)) in collector.types {
482+
debug!(?def_id);
483+
484+
let substs = match infcx.fully_resolve(substs) {
485+
Ok(substs) => substs,
486+
Err(err) => {
487+
collected_tys.insert(
488+
def_id,
489+
tcx.ty_error_with_message(
490+
return_span,
491+
&format!("could not fully resolve: {substs:?} => {err:?}"),
492+
),
493+
);
494+
continue;
535495
}
496+
};
497+
debug!(?substs);
498+
499+
let raw_ty = match infcx.fully_resolve(raw_ty) {
500+
Ok(raw_ty) => raw_ty,
536501
Err(err) => {
537-
let reported = tcx.sess.delay_span_bug(
538-
return_span,
539-
format!("could not fully resolve: {ty} => {err:?}"),
502+
collected_tys.insert(
503+
def_id,
504+
tcx.ty_error_with_message(
505+
return_span,
506+
&format!("could not fully resolve: {raw_ty} => {err:?}"),
507+
),
540508
);
541-
collected_tys.insert(def_id, tcx.ty_error_with_guaranteed(reported));
509+
continue;
542510
}
511+
};
512+
debug!(?raw_ty);
513+
514+
// `raw_ty` contains free regions that we created earlier while liberating the
515+
// trait fn signature. However, projection normalization expects `raw_ty` to
516+
// contains `def_id`'s early-bound regions.
517+
let id_substs = InternalSubsts::identity_for_item(tcx, def_id);
518+
debug!(?id_substs, ?substs);
519+
520+
let variances = tcx.variances_of(def_id);
521+
debug!(?variances);
522+
523+
// Opaque types may only use regions that are bound. So for
524+
// ```rust
525+
// type Foo<'a, 'b, 'c> = impl Trait<'a> + 'b;
526+
// ```
527+
// we may not use `'c` in the hidden type.
528+
let map: FxHashMap<ty::GenericArg<'tcx>, ty::GenericArg<'tcx>> =
529+
std::iter::zip(substs, id_substs)
530+
.filter(|(_, v)| {
531+
let ty::GenericArgKind::Lifetime(lt) = v.unpack() else { return true };
532+
let ty::ReEarlyBound(ebr) = lt.kind() else { bug!() };
533+
variances[ebr.index as usize] == ty::Variance::Invariant
534+
})
535+
.collect();
536+
debug!(?map);
537+
538+
// NOTE(compiler-errors): RPITITs, like all other RPITs, have early-bound
539+
// region substs that are synthesized during AST lowering. These are substs
540+
// that are appended to the parent substs (trait and trait method). However,
541+
// we're trying to infer the unsubstituted type value of the RPITIT inside
542+
// the *impl*, so we can later use the impl's method substs to normalize
543+
// an RPITIT to a concrete type (`confirm_impl_trait_in_trait_candidate`).
544+
//
545+
// Due to the design of RPITITs, during AST lowering, we have no idea that
546+
// an impl method corresponds to a trait method with RPITITs in it. Therefore,
547+
// we don't have a list of early-bound region substs for the RPITIT in the impl.
548+
// Since early region parameters are index-based, we can't just rebase these
549+
// (trait method) early-bound region substs onto the impl, and there's no
550+
// guarantee that the indices from the trait substs and impl substs line up.
551+
// So to fix this, we subtract the number of trait substs and add the number of
552+
// impl substs to *renumber* these early-bound regions to their corresponding
553+
// indices in the impl's substitutions list.
554+
//
555+
// Also, we only need to account for a difference in trait and impl substs,
556+
// since we previously enforce that the trait method and impl method have the
557+
// same generics.
558+
let num_trait_substs = trait_to_impl_substs.len();
559+
let num_impl_substs = tcx.generics_of(impl_m.def_id).parent_count;
560+
debug!(?num_trait_substs, ?num_impl_substs);
561+
562+
let mut bad_regions = vec![];
563+
564+
let ty = tcx.fold_regions(raw_ty, |region, _| {
565+
debug!(?region);
566+
match region.kind() {
567+
// Remap all free regions, which correspond to late-bound regions in the function.
568+
ty::ReFree(_) => {}
569+
// Remap early-bound regions as long as they don't come from the `impl` itself.
570+
ty::ReEarlyBound(ebr) if (ebr.index as usize) >= num_impl_substs => {}
571+
_ => return region,
572+
}
573+
debug!(mapped = ?map.get(&region.into()));
574+
let Some(ty::ReEarlyBound(e)) = map.get(&region.into()).map(|r| r.expect_region().kind())
575+
else {
576+
bad_regions.push(region);
577+
return tcx.lifetimes.re_static;
578+
};
579+
tcx.mk_region(ty::ReEarlyBound(ty::EarlyBoundRegion {
580+
def_id: e.def_id,
581+
name: e.name,
582+
index: (e.index as usize - num_trait_substs + num_impl_substs) as u32,
583+
}))
584+
});
585+
586+
if !bad_regions.is_empty() {
587+
let mut err = tcx.sess.struct_span_err(
588+
return_span,
589+
"`impl` item return type captures lifetime that doesn't appear in `trait` item bounds",
590+
);
591+
for r in bad_regions {
592+
let span = match r.kind() {
593+
ty::ReEarlyBound(ebr) => tcx.def_span(ebr.def_id),
594+
ty::ReFree(fr) => fr.bound_region.opt_span(tcx).unwrap(),
595+
_ => bug!(),
596+
};
597+
let name = if r.has_name() {
598+
format!("lifetime `{r}`")
599+
} else {
600+
"the anonymous lifetime".to_string()
601+
};
602+
err.span_label(span, format!("type `{raw_ty}` captures {name} defined here"));
603+
}
604+
605+
let generics = tcx.generics_of(def_id);
606+
match &generics.params[..] {
607+
[] => {
608+
err.span_label(tcx.def_span(def_id), "type declared not to capture lifetimes");
609+
}
610+
[param] => {
611+
err.span_label(
612+
tcx.def_span(param.def_id),
613+
"type can only capture this lifetime",
614+
);
615+
}
616+
params => {
617+
err.span_labels(
618+
params.iter().map(|param| tcx.def_span(param.def_id)),
619+
"type can only capture lifetimes enumerated here",
620+
);
621+
}
622+
};
623+
err.emit();
543624
}
625+
626+
debug!(?ty);
627+
collected_tys.insert(def_id, ty);
544628
}
545629

546630
Ok(&*tcx.arena.alloc(collected_tys))

compiler/rustc_middle/src/ty/sty.rs

+8
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,14 @@ impl BoundRegionKind {
100100

101101
None
102102
}
103+
104+
pub fn opt_span(&self, tcx: TyCtxt<'_>) -> Option<Span> {
105+
match *self {
106+
ty::BrAnon(_, opt_span) => opt_span,
107+
ty::BrNamed(def_id, _) => Some(tcx.def_span(def_id)),
108+
ty::BrEnv => None,
109+
}
110+
}
103111
}
104112

105113
pub trait Article {

src/test/ui/impl-trait/in-trait/method-signature-matches.stderr

+3-3
Original file line numberDiff line numberDiff line change
@@ -67,16 +67,16 @@ LL | fn early<'late, T>(_: &'late ()) {}
6767
| - ^^^^^^^^^
6868
| | |
6969
| | expected type parameter `T`, found `()`
70-
| | help: change the parameter type to match the trait: `&'early T`
70+
| | help: change the parameter type to match the trait: `&T`
7171
| this type parameter
7272
|
7373
note: type in trait
7474
--> $DIR/method-signature-matches.rs:43:28
7575
|
7676
LL | fn early<'early, T>(x: &'early T) -> impl Sized;
7777
| ^^^^^^^^^
78-
= note: expected fn pointer `fn(&'early T)`
79-
found fn pointer `fn(&())`
78+
= note: expected fn pointer `fn(&T)`
79+
found fn pointer `fn(&'late ())`
8080

8181
error: aborting due to 5 previous errors
8282

src/test/ui/impl-trait/in-trait/signature-mismatch.rs

+52-1
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,68 @@
55

66
use std::future::Future;
77

8+
trait Captures<'a> {}
9+
impl<T> Captures<'_> for T {}
10+
811
pub trait AsyncTrait {
912
fn async_fn(&self, buff: &[u8]) -> impl Future<Output = Vec<u8>>;
13+
fn async_fn_early<'a: 'a>(&self, buff: &'a [u8]) -> impl Future<Output = Vec<u8>>;
14+
fn async_fn_multiple<'a>(&'a self, buff: &[u8])
15+
-> impl Future<Output = Vec<u8>> + Captures<'a>;
16+
fn async_fn_reduce_outlive<'a, T>(
17+
&'a self,
18+
buff: &[u8],
19+
t: T,
20+
) -> impl Future<Output = Vec<u8>> + 'a;
21+
fn async_fn_reduce<'a, T>(
22+
&'a self,
23+
buff: &[u8],
24+
t: T,
25+
) -> impl Future<Output = Vec<u8>> + Captures<'a>;
1026
}
1127

1228
pub struct Struct;
1329

1430
impl AsyncTrait for Struct {
1531
fn async_fn<'a>(&self, buff: &'a [u8]) -> impl Future<Output = Vec<u8>> + 'a {
16-
//~^ ERROR `impl` item signature doesn't match `trait` item signature
32+
//~^ ERROR `impl` item return type captures lifetime that doesn't appear in `trait`
33+
async move { buff.to_vec() }
34+
}
35+
36+
fn async_fn_early<'a: 'a>(&self, buff: &'a [u8]) -> impl Future<Output = Vec<u8>> + 'a {
37+
//~^ ERROR `impl` item return type captures lifetime that doesn't appear in `trait`
1738
async move { buff.to_vec() }
1839
}
40+
41+
fn async_fn_multiple<'a, 'b>(
42+
&'a self,
43+
buff: &'b [u8],
44+
) -> impl Future<Output = Vec<u8>> + Captures<'a> + Captures<'b> {
45+
//~^ ERROR `impl` item return type captures lifetime that doesn't appear in `trait`
46+
//~| ERROR lifetime mismatch
47+
async move { buff.to_vec() }
48+
}
49+
50+
fn async_fn_reduce_outlive<'a, 'b, T>(
51+
&'a self,
52+
buff: &'b [u8],
53+
t: T,
54+
) -> impl Future<Output = Vec<u8>> {
55+
//~^ ERROR the parameter type `T` may not live long enough
56+
async move {
57+
let _t = t;
58+
vec![]
59+
}
60+
}
61+
62+
// OK: We remove the `Captures<'a>`, providing a guarantee that we don't capture `'a`,
63+
// but we still fulfill the `Captures<'a>` trait bound.
64+
fn async_fn_reduce<'a, 'b, T>(&'a self, buff: &'b [u8], t: T) -> impl Future<Output = Vec<u8>> {
65+
async move {
66+
let _t = t;
67+
vec![]
68+
}
69+
}
1970
}
2071

2172
fn main() {}

0 commit comments

Comments
 (0)