Skip to content

Commit

Permalink
Report change in RPITIT lifetime capture clauses.
Browse files Browse the repository at this point in the history
  • Loading branch information
cjgillot committed Dec 4, 2022
1 parent 344889e commit 503bc7c
Show file tree
Hide file tree
Showing 5 changed files with 280 additions and 86 deletions.
230 changes: 157 additions & 73 deletions compiler/rustc_hir_analysis/src/check/compare_method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -367,26 +367,29 @@ pub fn collect_trait_impl_trait_tys<'tcx>(
let impl_sig = ocx.normalize(
&norm_cause,
param_env,
infcx.replace_bound_vars_with_fresh_vars(
return_span,
infer::HigherRankedType,
tcx.fn_sig(impl_m.def_id),
),
tcx.liberate_late_bound_regions(impl_m.def_id, tcx.fn_sig(impl_m.def_id)),
);
debug!(?impl_sig);
let impl_return_ty = impl_sig.output();

// Normalize the trait signature with liberated bound vars, passing it through
// the ImplTraitInTraitCollector, which gathers all of the RPITITs and replaces
// them with inference variables.
// We will use these inference variables to collect the hidden types of RPITITs.
let mut collector = ImplTraitInTraitCollector::new(&ocx, return_span, param_env, impl_m_hir_id);
let unnormalized_trait_sig = tcx
.liberate_late_bound_regions(
impl_m.def_id,
let unnormalized_trait_sig = infcx
.replace_bound_vars_with_fresh_vars(
return_span,
infer::HigherRankedType,
tcx.bound_fn_sig(trait_m.def_id).subst(tcx, trait_to_placeholder_substs),
)
.fold_with(&mut collector);
if collector.types.is_empty() {
return Ok(&*tcx.arena.alloc(FxHashMap::default()));
}

let trait_sig = ocx.normalize(&norm_cause, param_env, unnormalized_trait_sig);
debug!(?trait_sig);
let trait_return_ty = trait_sig.output();

let wf_tys = FxIndexSet::from_iter(
Expand Down Expand Up @@ -422,7 +425,7 @@ pub fn collect_trait_impl_trait_tys<'tcx>(
}
}

debug!(?trait_sig, ?impl_sig, "equating function signatures");
debug!("equating function signatures");

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

// Finally, resolve all regions. This catches wily misuses of
// lifetime parameters.
debug!("check_region_obligations");
let outlives_environment = OutlivesEnvironment::with_bounds(
param_env,
Some(infcx),
infcx.implied_bounds_tys(param_env, impl_m_hir_id, wf_tys),
);
debug!(region_bound_pairs=?outlives_environment.region_bound_pairs());
infcx.check_region_obligations_and_report_errors(
impl_m.def_id.expect_local(),
&outlives_environment,
);

debug!("resolve collected");
let mut collected_tys = FxHashMap::default();
for (def_id, (ty, substs)) in collector.types {
match infcx.fully_resolve(ty) {
Ok(ty) => {
// `ty` contains free regions that we created earlier while liberating the
// trait fn signature. However, projection normalization expects `ty` to
// contains `def_id`'s early-bound regions.
let id_substs = InternalSubsts::identity_for_item(tcx, def_id);
debug!(?id_substs, ?substs);
let map: FxHashMap<ty::GenericArg<'tcx>, ty::GenericArg<'tcx>> =
std::iter::zip(substs, id_substs).collect();
debug!(?map);

// NOTE(compiler-errors): RPITITs, like all other RPITs, have early-bound
// region substs that are synthesized during AST lowering. These are substs
// that are appended to the parent substs (trait and trait method). However,
// we're trying to infer the unsubstituted type value of the RPITIT inside
// the *impl*, so we can later use the impl's method substs to normalize
// an RPITIT to a concrete type (`confirm_impl_trait_in_trait_candidate`).
//
// Due to the design of RPITITs, during AST lowering, we have no idea that
// an impl method corresponds to a trait method with RPITITs in it. Therefore,
// we don't have a list of early-bound region substs for the RPITIT in the impl.
// Since early region parameters are index-based, we can't just rebase these
// (trait method) early-bound region substs onto the impl, and there's no
// guarantee that the indices from the trait substs and impl substs line up.
// So to fix this, we subtract the number of trait substs and add the number of
// impl substs to *renumber* these early-bound regions to their corresponding
// indices in the impl's substitutions list.
//
// Also, we only need to account for a difference in trait and impl substs,
// since we previously enforce that the trait method and impl method have the
// same generics.
let num_trait_substs = trait_to_impl_substs.len();
let num_impl_substs = tcx.generics_of(impl_m.container_id(tcx)).params.len();
let ty = tcx.fold_regions(ty, |region, _| {
match region.kind() {
// Remap all free regions, which correspond to late-bound regions in the function.
ty::ReFree(_) => {}
// Remap early-bound regions as long as they don't come from the `impl` itself.
ty::ReEarlyBound(ebr) if tcx.parent(ebr.def_id) != impl_m.container_id(tcx) => {}
_ => return region,
}
let Some(ty::ReEarlyBound(e)) = map.get(&region.into()).map(|r| r.expect_region().kind())
else {
tcx
.sess
.delay_span_bug(
return_span,
"expected ReFree to map to ReEarlyBound"
);
return tcx.lifetimes.re_static;
};
tcx.mk_region(ty::ReEarlyBound(ty::EarlyBoundRegion {
def_id: e.def_id,
name: e.name,
index: (e.index as usize - num_trait_substs + num_impl_substs) as u32,
}))
});
debug!(%ty);
collected_tys.insert(def_id, ty);
for (def_id, (raw_ty, substs)) in collector.types {
debug!(?def_id);

let substs = match infcx.fully_resolve(substs) {
Ok(substs) => substs,
Err(err) => {
collected_tys.insert(
def_id,
tcx.ty_error_with_message(
return_span,
&format!("could not fully resolve: {substs:?} => {err:?}"),
),
);
continue;
}
};
debug!(?substs);

let raw_ty = match infcx.fully_resolve(raw_ty) {
Ok(raw_ty) => raw_ty,
Err(err) => {
let reported = tcx.sess.delay_span_bug(
return_span,
format!("could not fully resolve: {ty} => {err:?}"),
collected_tys.insert(
def_id,
tcx.ty_error_with_message(
return_span,
&format!("could not fully resolve: {raw_ty} => {err:?}"),
),
);
collected_tys.insert(def_id, tcx.ty_error_with_guaranteed(reported));
continue;
}
};
debug!(?raw_ty);

// `raw_ty` contains free regions that we created earlier while liberating the
// trait fn signature. However, projection normalization expects `raw_ty` to
// contains `def_id`'s early-bound regions.
let id_substs = InternalSubsts::identity_for_item(tcx, def_id);
debug!(?id_substs, ?substs);

let variances = tcx.variances_of(def_id);
debug!(?variances);

// Opaque types may only use regions that are bound. So for
// ```rust
// type Foo<'a, 'b, 'c> = impl Trait<'a> + 'b;
// ```
// we may not use `'c` in the hidden type.
let map: FxHashMap<ty::GenericArg<'tcx>, ty::GenericArg<'tcx>> =
std::iter::zip(substs, id_substs)
.filter(|(_, v)| {
let ty::GenericArgKind::Lifetime(lt) = v.unpack() else { return true };
let ty::ReEarlyBound(ebr) = lt.kind() else { bug!() };
variances[ebr.index as usize] == ty::Variance::Invariant
})
.collect();
debug!(?map);

// NOTE(compiler-errors): RPITITs, like all other RPITs, have early-bound
// region substs that are synthesized during AST lowering. These are substs
// that are appended to the parent substs (trait and trait method). However,
// we're trying to infer the unsubstituted type value of the RPITIT inside
// the *impl*, so we can later use the impl's method substs to normalize
// an RPITIT to a concrete type (`confirm_impl_trait_in_trait_candidate`).
//
// Due to the design of RPITITs, during AST lowering, we have no idea that
// an impl method corresponds to a trait method with RPITITs in it. Therefore,
// we don't have a list of early-bound region substs for the RPITIT in the impl.
// Since early region parameters are index-based, we can't just rebase these
// (trait method) early-bound region substs onto the impl, and there's no
// guarantee that the indices from the trait substs and impl substs line up.
// So to fix this, we subtract the number of trait substs and add the number of
// impl substs to *renumber* these early-bound regions to their corresponding
// indices in the impl's substitutions list.
//
// Also, we only need to account for a difference in trait and impl substs,
// since we previously enforce that the trait method and impl method have the
// same generics.
let num_trait_substs = trait_to_impl_substs.len();
let num_impl_substs = tcx.generics_of(impl_m.def_id).parent_count;
debug!(?num_trait_substs, ?num_impl_substs);

let mut bad_regions = vec![];

let ty = tcx.fold_regions(raw_ty, |region, _| {
debug!(?region);
match region.kind() {
// Remap all free regions, which correspond to late-bound regions in the function.
ty::ReFree(_) => {}
// Remap early-bound regions as long as they don't come from the `impl` itself.
ty::ReEarlyBound(ebr) if (ebr.index as usize) >= num_impl_substs => {}
_ => return region,
}
debug!(mapped = ?map.get(&region.into()));
let Some(ty::ReEarlyBound(e)) = map.get(&region.into()).map(|r| r.expect_region().kind())
else {
bad_regions.push(region);
return tcx.lifetimes.re_static;
};
tcx.mk_region(ty::ReEarlyBound(ty::EarlyBoundRegion {
def_id: e.def_id,
name: e.name,
index: (e.index as usize - num_trait_substs + num_impl_substs) as u32,
}))
});

if !bad_regions.is_empty() {
let mut err = tcx.sess.struct_span_err(
return_span,
"`impl` item return type captures lifetime that doesn't appear in `trait` item bounds",
);
for r in bad_regions {
let span = match r.kind() {
ty::ReEarlyBound(ebr) => tcx.def_span(ebr.def_id),
ty::ReFree(fr) => fr.bound_region.opt_span(tcx).unwrap(),
_ => bug!(),
};
let name = if r.has_name() {
format!("lifetime `{r}`")
} else {
"the anonymous lifetime".to_string()
};
err.span_label(span, format!("type `{raw_ty}` captures {name} defined here"));
}

let generics = tcx.generics_of(def_id);
match &generics.params[..] {
[] => {
err.span_label(tcx.def_span(def_id), "type declared not to capture lifetimes");
}
[param] => {
err.span_label(
tcx.def_span(param.def_id),
"type can only capture this lifetime",
);
}
params => {
err.span_labels(
params.iter().map(|param| tcx.def_span(param.def_id)),
"type can only capture lifetimes enumerated here",
);
}
};
err.emit();
}

debug!(?ty);
collected_tys.insert(def_id, ty);
}

Ok(&*tcx.arena.alloc(collected_tys))
Expand Down
8 changes: 8 additions & 0 deletions compiler/rustc_middle/src/ty/sty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,14 @@ impl BoundRegionKind {

None
}

pub fn opt_span(&self, tcx: TyCtxt<'_>) -> Option<Span> {
match *self {
ty::BrAnon(_, opt_span) => opt_span,
ty::BrNamed(def_id, _) => Some(tcx.def_span(def_id)),
ty::BrEnv => None,
}
}
}

pub trait Article {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,16 @@ LL | fn early<'late, T>(_: &'late ()) {}
| - ^^^^^^^^^
| | |
| | expected type parameter `T`, found `()`
| | help: change the parameter type to match the trait: `&'early T`
| | help: change the parameter type to match the trait: `&T`
| this type parameter
|
note: type in trait
--> $DIR/method-signature-matches.rs:43:28
|
LL | fn early<'early, T>(x: &'early T) -> impl Sized;
| ^^^^^^^^^
= note: expected fn pointer `fn(&'early T)`
found fn pointer `fn(&())`
= note: expected fn pointer `fn(&T)`
found fn pointer `fn(&'late ())`

error: aborting due to 5 previous errors

Expand Down
53 changes: 52 additions & 1 deletion src/test/ui/impl-trait/in-trait/signature-mismatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,68 @@

use std::future::Future;

trait Captures<'a> {}
impl<T> Captures<'_> for T {}

pub trait AsyncTrait {
fn async_fn(&self, buff: &[u8]) -> impl Future<Output = Vec<u8>>;
fn async_fn_early<'a: 'a>(&self, buff: &'a [u8]) -> impl Future<Output = Vec<u8>>;
fn async_fn_multiple<'a>(&'a self, buff: &[u8])
-> impl Future<Output = Vec<u8>> + Captures<'a>;
fn async_fn_reduce_outlive<'a, T>(
&'a self,
buff: &[u8],
t: T,
) -> impl Future<Output = Vec<u8>> + 'a;
fn async_fn_reduce<'a, T>(
&'a self,
buff: &[u8],
t: T,
) -> impl Future<Output = Vec<u8>> + Captures<'a>;
}

pub struct Struct;

impl AsyncTrait for Struct {
fn async_fn<'a>(&self, buff: &'a [u8]) -> impl Future<Output = Vec<u8>> + 'a {
//~^ ERROR `impl` item signature doesn't match `trait` item signature
//~^ ERROR `impl` item return type captures lifetime that doesn't appear in `trait`
async move { buff.to_vec() }
}

fn async_fn_early<'a: 'a>(&self, buff: &'a [u8]) -> impl Future<Output = Vec<u8>> + 'a {
//~^ ERROR `impl` item return type captures lifetime that doesn't appear in `trait`
async move { buff.to_vec() }
}

fn async_fn_multiple<'a, 'b>(
&'a self,
buff: &'b [u8],
) -> impl Future<Output = Vec<u8>> + Captures<'a> + Captures<'b> {
//~^ ERROR `impl` item return type captures lifetime that doesn't appear in `trait`
//~| ERROR lifetime mismatch
async move { buff.to_vec() }
}

fn async_fn_reduce_outlive<'a, 'b, T>(
&'a self,
buff: &'b [u8],
t: T,
) -> impl Future<Output = Vec<u8>> {
//~^ ERROR the parameter type `T` may not live long enough
async move {
let _t = t;
vec![]
}
}

// OK: We remove the `Captures<'a>`, providing a guarantee that we don't capture `'a`,
// but we still fulfill the `Captures<'a>` trait bound.
fn async_fn_reduce<'a, 'b, T>(&'a self, buff: &'b [u8], t: T) -> impl Future<Output = Vec<u8>> {
async move {
let _t = t;
vec![]
}
}
}

fn main() {}
Loading

0 comments on commit 503bc7c

Please sign in to comment.