Auto merge of #99867 - spastorino:refactor-remap-lifetimes, r=nikomatsakis

Split create_def and lowering of lifetimes for opaque types and bare async fns

r? `@cjgillot`

This work is kind of half-way, but I think it could be merged anyway.
I think we should be able to remove all the vacant arms in `new_named_lifetime_with_res`, if I'm not wrong that requires visiting more nodes. We can do that as a follow up.
In follow-up PRs, besides the thing mentioned previously, I'll be trying to remove `LifetimeCaptureContext`, `captured_lifetimes` as a global data structure, global `binders_to_ignore` and all their friends :).

Also try to remap in a more general way based on def-ids.
This commit is contained in:
bors 2022-08-05 06:35:12 +00:00
commit cdfd675a63
8 changed files with 579 additions and 287 deletions

View file

@ -64,7 +64,7 @@ impl fmt::Debug for Label {
/// A "Lifetime" is an annotation of the scope in which variable
/// can be used, e.g. `'a` in `&'a i32`.
#[derive(Clone, Encodable, Decodable, Copy)]
#[derive(Clone, Encodable, Decodable, Copy, PartialEq, Eq)]
pub struct Lifetime {
pub id: NodeId,
pub ident: Ident,

View file

@ -864,22 +864,21 @@ impl<'hir> LoweringContext<'_, 'hir> {
(body_id, generator_option)
});
self.with_lifetime_binder(closure_id, generic_params, |this, bound_generic_params| {
// Lower outside new scope to preserve `is_in_loop_condition`.
let fn_decl = this.lower_fn_decl(decl, None, FnDeclKind::Closure, None);
let bound_generic_params = self.lower_lifetime_binder(closure_id, generic_params);
// Lower outside new scope to preserve `is_in_loop_condition`.
let fn_decl = self.lower_fn_decl(decl, None, FnDeclKind::Closure, None);
let c = self.arena.alloc(hir::Closure {
binder: binder_clause,
capture_clause,
bound_generic_params,
fn_decl,
body: body_id,
fn_decl_span: this.lower_span(fn_decl_span),
movability: generator_option,
});
let c = self.arena.alloc(hir::Closure {
binder: binder_clause,
capture_clause,
bound_generic_params,
fn_decl,
body: body_id,
fn_decl_span: self.lower_span(fn_decl_span),
movability: generator_option,
});
hir::ExprKind::Closure(c)
})
hir::ExprKind::Closure(c)
}
fn generator_movability_for_fn(
@ -991,23 +990,23 @@ impl<'hir> LoweringContext<'_, 'hir> {
body_id
});
self.with_lifetime_binder(closure_id, generic_params, |this, bound_generic_params| {
// We need to lower the declaration outside the new scope, because we
// have to conserve the state of being inside a loop condition for the
// closure argument types.
let fn_decl = this.lower_fn_decl(&outer_decl, None, FnDeclKind::Closure, None);
let bound_generic_params = self.lower_lifetime_binder(closure_id, generic_params);
let c = self.arena.alloc(hir::Closure {
binder: binder_clause,
capture_clause,
bound_generic_params,
fn_decl,
body,
fn_decl_span: this.lower_span(fn_decl_span),
movability: None,
});
hir::ExprKind::Closure(c)
})
// We need to lower the declaration outside the new scope, because we
// have to conserve the state of being inside a loop condition for the
// closure argument types.
let fn_decl = self.lower_fn_decl(&outer_decl, None, FnDeclKind::Closure, None);
let c = self.arena.alloc(hir::Closure {
binder: binder_clause,
capture_clause,
bound_generic_params,
fn_decl,
body,
fn_decl_span: self.lower_span(fn_decl_span),
movability: None,
});
hir::ExprKind::Closure(c)
}
/// Destructure the LHS of complex assignments.

View file

@ -80,7 +80,6 @@ impl<'a, 'hir> ItemLowerer<'a, 'hir> {
generator_kind: None,
task_context: None,
current_item: None,
captured_lifetimes: None,
impl_trait_defs: Vec::new(),
impl_trait_bounds: Vec::new(),
allow_try_trait: Some([sym::try_trait_v2, sym::yeet_desugar_details][..].into()),
@ -1350,12 +1349,12 @@ impl<'hir> LoweringContext<'_, 'hir> {
let mut predicates: SmallVec<[hir::WherePredicate<'hir>; 4]> = SmallVec::new();
predicates.extend(generics.params.iter().filter_map(|param| {
let bounds = self.lower_param_bounds(&param.bounds, itctx);
self.lower_generic_bound_predicate(
param.ident,
param.id,
&param.kind,
bounds,
&param.bounds,
itctx,
PredicateOrigin::GenericParam,
)
}));
@ -1403,13 +1402,17 @@ impl<'hir> LoweringContext<'_, 'hir> {
ident: Ident,
id: NodeId,
kind: &GenericParamKind,
bounds: &'hir [hir::GenericBound<'hir>],
bounds: &[GenericBound],
itctx: ImplTraitContext,
origin: PredicateOrigin,
) -> Option<hir::WherePredicate<'hir>> {
// Do not create a clause if we do not have anything inside it.
if bounds.is_empty() {
return None;
}
let bounds = self.lower_param_bounds(bounds, itctx);
let ident = self.lower_ident(ident);
let param_span = ident.span;
let span = bounds
@ -1450,11 +1453,8 @@ impl<'hir> LoweringContext<'_, 'hir> {
GenericParamKind::Lifetime => {
let ident_span = self.lower_span(ident.span);
let ident = self.lower_ident(ident);
let res = self.resolver.get_lifetime_res(id).unwrap_or_else(|| {
panic!("Missing resolution for lifetime {:?} at {:?}", id, ident.span)
});
let lt_id = self.next_node_id();
let lifetime = self.new_named_lifetime_with_res(lt_id, ident_span, ident, res);
let lifetime = self.new_named_lifetime(id, lt_id, ident_span, ident);
Some(hir::WherePredicate::RegionPredicate(hir::WhereRegionPredicate {
lifetime,
span,

View file

@ -45,7 +45,7 @@ use rustc_ast::{self as ast, *};
use rustc_ast_pretty::pprust;
use rustc_data_structures::captures::Captures;
use rustc_data_structures::fingerprint::Fingerprint;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::sorted_map::SortedMap;
use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
use rustc_data_structures::sync::Lrc;
@ -56,6 +56,7 @@ use rustc_hir::def_id::{LocalDefId, CRATE_DEF_ID};
use rustc_hir::definitions::DefPathData;
use rustc_hir::{ConstArg, GenericArg, ItemLocalId, ParamName, TraitCandidate};
use rustc_index::vec::{Idx, IndexVec};
use rustc_middle::span_bug;
use rustc_middle::ty::{ResolverAstLowering, TyCtxt};
use rustc_session::parse::feature_err;
use rustc_span::hygiene::MacroKind;
@ -77,6 +78,7 @@ mod block;
mod expr;
mod index;
mod item;
mod lifetime_collector;
mod pat;
mod path;
@ -110,9 +112,6 @@ struct LoweringContext<'a, 'hir> {
is_in_trait_impl: bool,
is_in_dyn_type: bool,
/// Used to handle lifetimes appearing in impl-traits.
captured_lifetimes: Option<LifetimeCaptureContext>,
current_hir_id_owner: LocalDefId,
item_local_id_counter: hir::ItemLocalId,
local_id_to_def_id: SortedMap<ItemLocalId, LocalDefId>,
@ -129,28 +128,6 @@ struct LoweringContext<'a, 'hir> {
allow_into_future: Option<Lrc<[Symbol]>>,
}
/// When we lower a lifetime, it is inserted in `captures`, and the resolution is modified so
/// to point to the lifetime parameter impl-trait will generate.
/// When traversing `for<...>` binders, they are inserted in `binders_to_ignore` so we know *not*
/// to rebind the introduced lifetimes.
#[derive(Debug)]
struct LifetimeCaptureContext {
/// parent def_id for new definitions
parent_def_id: LocalDefId,
/// Set of lifetimes to rebind.
captures: FxHashMap<
LocalDefId, // original parameter id
(
Span, // Span
NodeId, // synthetized parameter id
ParamName, // parameter name
LifetimeRes, // original resolution
),
>,
/// Traversed binders. The ids in this set should *not* be rebound.
binders_to_ignore: FxHashSet<NodeId>,
}
trait ResolverAstLoweringExt {
fn legacy_const_generic_args(&self, expr: &Expr) -> Option<Vec<usize>>;
fn get_partial_res(&self, id: NodeId) -> Option<PartialRes>;
@ -159,6 +136,12 @@ trait ResolverAstLoweringExt {
fn get_lifetime_res(&self, id: NodeId) -> Option<LifetimeRes>;
fn take_extra_lifetime_params(&mut self, id: NodeId) -> Vec<(Ident, NodeId, LifetimeRes)>;
fn decl_macro_kind(&self, def_id: LocalDefId) -> MacroKind;
/// Record the map from `from` local def id to `to` local def id, on `generics_def_id_map`
/// field.
fn record_def_id_remap(&mut self, from: LocalDefId, to: LocalDefId);
/// Get the previously recorded `to` local def id given the `from` local def id, obtained using
/// `generics_def_id_map` field.
fn get_remapped_def_id(&self, local_def_id: LocalDefId) -> LocalDefId;
}
impl ResolverAstLoweringExt for ResolverAstLowering {
@ -226,6 +209,28 @@ impl ResolverAstLoweringExt for ResolverAstLowering {
fn decl_macro_kind(&self, def_id: LocalDefId) -> MacroKind {
self.builtin_macro_kinds.get(&def_id).copied().unwrap_or(MacroKind::Bang)
}
/// Push a remapping into the top-most map.
/// Panics if no map has been pushed.
/// Remapping is used when creating lowering `-> impl Trait` return
/// types to create the resulting opaque type.
#[tracing::instrument(level = "debug", skip(self))]
fn record_def_id_remap(&mut self, from: LocalDefId, to: LocalDefId) {
self.generics_def_id_map.last_mut().expect("no map pushed").insert(from, to);
}
fn get_remapped_def_id(&self, mut local_def_id: LocalDefId) -> LocalDefId {
for map in &self.generics_def_id_map {
if let Some(r) = map.get(&local_def_id) {
debug!("def_id_remapper: remapping from `{local_def_id:?}` to `{r:?}`");
local_def_id = *r;
} else {
debug!("def_id_remapper: no remapping for `{local_def_id:?}` found in map");
}
}
local_def_id
}
}
/// Context of `impl Trait` in code, which determines whether it is allowed in an HIR subtree,
@ -481,8 +486,24 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
start
}
/// Given the id of some node in the AST, finds the `LocalDefId` associated with it by the name
/// resolver (if any), after applying any remapping from `get_remapped_def_id`.
///
/// For example, in a function like `fn foo<'a>(x: &'a u32)`,
/// invoking with the id from the `ast::Lifetime` node found inside
/// the `&'a u32` type would return the `LocalDefId` of the
/// `'a` parameter declared on `foo`.
///
/// This function also applies remapping from `get_remapped_def_id`.
/// These are used when synthesizing opaque types from `-> impl Trait` return types and so forth.
/// For example, in a function like `fn foo<'a>() -> impl Debug + 'a`,
/// we would create an opaque type `type FooReturn<'a1> = impl Debug + 'a1`.
/// When lowering the `Debug + 'a` bounds, we add a remapping to map `'a` to `'a1`.
fn opt_local_def_id(&self, node: NodeId) -> Option<LocalDefId> {
self.resolver.node_id_to_def_id.get(&node).copied()
self.resolver
.node_id_to_def_id
.get(&node)
.map(|local_def_id| self.resolver.get_remapped_def_id(*local_def_id))
}
fn local_def_id(&self, node: NodeId) -> LocalDefId {
@ -542,6 +563,27 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
debug_assert!(_old.is_none())
}
/// Installs the remapping `remap` in scope while `f` is being executed.
/// This causes references to the `LocalDefId` keys to be changed to
/// refer to the values instead.
///
/// The remapping is used when one piece of AST expands to multiple
/// pieces of HIR. For example, the function `fn foo<'a>(...) -> impl Debug + 'a`,
/// expands to both a function definition (`foo`) and a TAIT for the return value,
/// both of which have a lifetime parameter `'a`. The remapping allows us to
/// rewrite the `'a` in the return value to refer to the
/// `'a` declared on the TAIT, instead of the function.
fn with_remapping<R>(
&mut self,
remap: FxHashMap<LocalDefId, LocalDefId>,
f: impl FnOnce(&mut Self) -> R,
) -> R {
self.resolver.generics_def_id_map.push(remap);
let res = f(self);
self.resolver.generics_def_id_map.pop();
res
}
fn make_owner_info(&mut self, node: hir::OwnerNode<'hir>) -> &'hir hir::OwnerInfo<'hir> {
let attrs = std::mem::take(&mut self.attrs);
let mut bodies = std::mem::take(&mut self.bodies);
@ -751,40 +793,18 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
})
}
/// Setup lifetime capture for and impl-trait.
/// The captures will be added to `captures`.
fn while_capturing_lifetimes<T>(
&mut self,
parent_def_id: LocalDefId,
captures: &mut FxHashMap<LocalDefId, (Span, NodeId, ParamName, LifetimeRes)>,
f: impl FnOnce(&mut Self) -> T,
) -> T {
let lifetime_stash = std::mem::replace(
&mut self.captured_lifetimes,
Some(LifetimeCaptureContext {
parent_def_id,
captures: std::mem::take(captures),
binders_to_ignore: Default::default(),
}),
);
let ret = f(self);
let ctxt = std::mem::replace(&mut self.captured_lifetimes, lifetime_stash).unwrap();
*captures = ctxt.captures;
ret
}
/// Register a binder to be ignored for lifetime capture.
#[tracing::instrument(level = "debug", skip(self, f))]
/// Lowers a lifetime binder that defines `generic_params`, returning the corresponding HIR
/// nodes. The returned list includes any "extra" lifetime parameters that were added by the
/// name resolver owing to lifetime elision; this also populates the resolver's node-id->def-id
/// map, so that later calls to `opt_node_id_to_def_id` that refer to these extra lifetime
/// parameters will be successful.
#[tracing::instrument(level = "debug", skip(self))]
#[inline]
fn with_lifetime_binder<T>(
fn lower_lifetime_binder(
&mut self,
binder: NodeId,
generic_params: &[GenericParam],
f: impl FnOnce(&mut Self, &'hir [hir::GenericParam<'hir>]) -> T,
) -> T {
) -> &'hir [hir::GenericParam<'hir>] {
let mut generic_params: Vec<_> = self.lower_generic_params_mut(generic_params).collect();
let extra_lifetimes = self.resolver.take_extra_lifetime_params(binder);
debug!(?extra_lifetimes);
@ -794,14 +814,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
let generic_params = self.arena.alloc_from_iter(generic_params);
debug!(?generic_params);
if let Some(ctxt) = &mut self.captured_lifetimes {
ctxt.binders_to_ignore.insert(binder);
}
let ret = f(self, generic_params);
if let Some(ctxt) = &mut self.captured_lifetimes {
ctxt.binders_to_ignore.remove(&binder);
}
ret
generic_params
}
fn with_dyn_type_scope<T>(&mut self, in_scope: bool, f: impl FnOnce(&mut Self) -> T) -> T {
@ -1222,15 +1235,14 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
hir::TyKind::Rptr(lifetime, self.lower_mt(mt, itctx))
}
TyKind::BareFn(ref f) => {
self.with_lifetime_binder(t.id, &f.generic_params, |this, generic_params| {
hir::TyKind::BareFn(this.arena.alloc(hir::BareFnTy {
generic_params,
unsafety: this.lower_unsafety(f.unsafety),
abi: this.lower_extern(f.ext),
decl: this.lower_fn_decl(&f.decl, None, FnDeclKind::Pointer, None),
param_names: this.lower_fn_params_to_names(&f.decl),
}))
})
let generic_params = self.lower_lifetime_binder(t.id, &f.generic_params);
hir::TyKind::BareFn(self.arena.alloc(hir::BareFnTy {
generic_params,
unsafety: self.lower_unsafety(f.unsafety),
abi: self.lower_extern(f.ext),
decl: self.lower_fn_decl(&f.decl, None, FnDeclKind::Pointer, None),
param_names: self.lower_fn_params_to_names(&f.decl),
}))
}
TyKind::Never => hir::TyKind::Never,
TyKind::Tup(ref tys) => hir::TyKind::Tup(
@ -1293,17 +1305,17 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
TyKind::ImplTrait(def_node_id, ref bounds) => {
let span = t.span;
match itctx {
ImplTraitContext::ReturnPositionOpaqueTy { origin } => self
.lower_opaque_impl_trait(span, origin, def_node_id, |this| {
this.lower_param_bounds(bounds, itctx)
}),
ImplTraitContext::ReturnPositionOpaqueTy { origin } => {
self.lower_opaque_impl_trait(span, origin, def_node_id, bounds, itctx)
}
ImplTraitContext::TypeAliasesOpaqueTy => {
let nested_itctx = ImplTraitContext::TypeAliasesOpaqueTy;
self.lower_opaque_impl_trait(
span,
hir::OpaqueTyOrigin::TyAlias,
def_node_id,
|this| this.lower_param_bounds(bounds, nested_itctx),
bounds,
nested_itctx,
)
}
ImplTraitContext::Universal => {
@ -1343,13 +1355,43 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
hir::Ty { kind, span: self.lower_span(t.span), hir_id: self.lower_node_id(t.id) }
}
#[tracing::instrument(level = "debug", skip(self, lower_bounds))]
/// Lowers a `ReturnPositionOpaqueTy` (`-> impl Trait`) or a `TypeAliasesOpaqueTy` (`type F =
/// impl Trait`): this creates the associated Opaque Type (TAIT) definition and then returns a
/// HIR type that references the TAIT.
///
/// Given a function definition like:
///
/// ```rust
/// fn test<'a, T: Debug>(x: &'a T) -> impl Debug + 'a {
/// x
/// }
/// ```
///
/// we will create a TAIT definition in the HIR like
///
/// ```
/// type TestReturn<'a, T, 'x> = impl Debug + 'x
/// ```
///
/// and return a type like `TestReturn<'static, T, 'a>`, so that the function looks like:
///
/// ```rust
/// fn test<'a, T: Debug>(x: &'a T) -> TestReturn<'static, T, 'a>
/// ```
///
/// Note the subtlety around type parameters! The new TAIT, `TestReturn`, inherits all the
/// type parameters from the function `test` (this is implemented in the query layer, they aren't
/// added explicitly in the HIR). But this includes all the lifetimes, and we only want to
/// capture the lifetimes that are referenced in the bounds. Therefore, we add *extra* lifetime parameters
/// for the lifetimes that get captured (`'x`, in our example above) and reference those.
#[tracing::instrument(level = "debug", skip(self))]
fn lower_opaque_impl_trait(
&mut self,
span: Span,
origin: hir::OpaqueTyOrigin,
opaque_ty_node_id: NodeId,
lower_bounds: impl FnOnce(&mut Self) -> hir::GenericBounds<'hir>,
bounds: &GenericBounds,
itctx: ImplTraitContext,
) -> hir::TyKind<'hir> {
// Make sure we know that some funky desugaring has been going on here.
// This is a first: there is code in other places like for loop
@ -1359,70 +1401,108 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
let opaque_ty_span = self.mark_span_with_reason(DesugaringKind::OpaqueTy, span, None);
let opaque_ty_def_id = self.local_def_id(opaque_ty_node_id);
debug!(?opaque_ty_def_id);
// Contains the new lifetime definitions created for the TAIT (if any).
let mut collected_lifetimes = Vec::new();
// If this came from a TAIT (as opposed to a function that returns an RPIT), we only want
// to capture the lifetimes that appear in the bounds. So visit the bounds to find out
// exactly which ones those are.
let lifetimes_to_remap = if origin == hir::OpaqueTyOrigin::TyAlias {
// in a TAIT like `type Foo<'a> = impl Foo<'a>`, we don't keep all the lifetime parameters
Vec::new()
} else {
// in fn return position, like the `fn test<'a>() -> impl Debug + 'a` example,
// we only keep the lifetimes that appear in the `impl Debug` itself:
lifetime_collector::lifetimes_in_bounds(&self.resolver, bounds)
};
debug!(?lifetimes_to_remap);
let mut collected_lifetimes = FxHashMap::default();
self.with_hir_id_owner(opaque_ty_node_id, |lctx| {
let hir_bounds = if origin == hir::OpaqueTyOrigin::TyAlias {
lower_bounds(lctx)
} else {
lctx.while_capturing_lifetimes(
opaque_ty_def_id,
&mut collected_lifetimes,
lower_bounds,
)
};
let mut new_remapping = FxHashMap::default();
// If this opaque type is only capturing a subset of the lifetimes (those that appear
// in bounds), then create the new lifetime parameters required and create a mapping
// from the old `'a` (on the function) to the new `'a` (on the opaque type).
collected_lifetimes = lctx.create_lifetime_defs(
opaque_ty_def_id,
&lifetimes_to_remap,
&mut new_remapping,
);
debug!(?collected_lifetimes);
debug!(?new_remapping);
let lifetime_defs = lctx.arena.alloc_from_iter(collected_lifetimes.iter().map(
|(_, &(span, p_id, p_name, _))| {
let hir_id = lctx.lower_node_id(p_id);
debug_assert_ne!(lctx.opt_local_def_id(p_id), None);
// Install the remapping from old to new (if any):
lctx.with_remapping(new_remapping, |lctx| {
// This creates HIR lifetime definitions as `hir::GenericParam`, in the given
// example `type TestReturn<'a, T, 'x> = impl Debug + 'x`, it creates a collection
// containing `&['x]`.
let lifetime_defs = lctx.arena.alloc_from_iter(collected_lifetimes.iter().map(
|&(new_node_id, lifetime)| {
let hir_id = lctx.lower_node_id(new_node_id);
debug_assert_ne!(lctx.opt_local_def_id(new_node_id), None);
let kind = if p_name.ident().name == kw::UnderscoreLifetime {
hir::LifetimeParamKind::Elided
} else {
hir::LifetimeParamKind::Explicit
};
let (name, kind) = if lifetime.ident.name == kw::UnderscoreLifetime {
(hir::ParamName::Fresh, hir::LifetimeParamKind::Elided)
} else {
(
hir::ParamName::Plain(lifetime.ident),
hir::LifetimeParamKind::Explicit,
)
};
hir::GenericParam {
hir_id,
name: p_name,
span,
pure_wrt_drop: false,
kind: hir::GenericParamKind::Lifetime { kind },
colon_span: None,
}
},
));
hir::GenericParam {
hir_id,
name,
span: lifetime.ident.span,
pure_wrt_drop: false,
kind: hir::GenericParamKind::Lifetime { kind },
colon_span: None,
}
},
));
debug!(?lifetime_defs);
debug!("lower_opaque_impl_trait: lifetime_defs={:#?}", lifetime_defs);
// Then when we lower the param bounds, references to 'a are remapped to 'a1, so we
// get back Debug + 'a1, which is suitable for use on the TAIT.
let hir_bounds = lctx.lower_param_bounds(bounds, itctx);
debug!(?hir_bounds);
let opaque_ty_item = hir::OpaqueTy {
generics: self.arena.alloc(hir::Generics {
params: lifetime_defs,
predicates: &[],
has_where_clause_predicates: false,
where_clause_span: lctx.lower_span(span),
span: lctx.lower_span(span),
}),
bounds: hir_bounds,
origin,
};
let opaque_ty_item = hir::OpaqueTy {
generics: self.arena.alloc(hir::Generics {
params: lifetime_defs,
predicates: &[],
has_where_clause_predicates: false,
where_clause_span: lctx.lower_span(span),
span: lctx.lower_span(span),
}),
bounds: hir_bounds,
origin,
};
debug!(?opaque_ty_item);
trace!("lower_opaque_impl_trait: {:#?}", opaque_ty_def_id);
lctx.generate_opaque_type(opaque_ty_def_id, opaque_ty_item, span, opaque_ty_span)
lctx.generate_opaque_type(opaque_ty_def_id, opaque_ty_item, span, opaque_ty_span)
})
});
let lifetimes = self.arena.alloc_from_iter(collected_lifetimes.into_iter().map(
|(_, (span, _, p_name, res))| {
// This creates HIR lifetime arguments as `hir::GenericArg`, in the given example `type
// TestReturn<'a, T, 'x> = impl Debug + 'x`, it creates a collection containing `&['x]`.
let lifetimes =
self.arena.alloc_from_iter(collected_lifetimes.into_iter().map(|(_, lifetime)| {
let id = self.next_node_id();
let ident = Ident::new(p_name.ident().name, span);
let l = self.new_named_lifetime_with_res(id, span, ident, res);
hir::GenericArg::Lifetime(l)
},
));
let span = lifetime.ident.span;
debug!("lower_opaque_impl_trait: lifetimes={:#?}", lifetimes);
let ident = if lifetime.ident.name == kw::UnderscoreLifetime {
Ident::with_dummy_span(kw::UnderscoreLifetime)
} else {
lifetime.ident
};
let l = self.new_named_lifetime(lifetime.id, id, span, ident);
hir::GenericArg::Lifetime(l)
}));
debug!(?lifetimes);
// `impl Trait` now just becomes `Foo<'a, 'b, ..>`.
hir::TyKind::OpaqueDef(hir::ItemId { def_id: opaque_ty_def_id }, lifetimes)
@ -1450,6 +1530,70 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
hir::OwnerNode::Item(self.arena.alloc(opaque_ty_item))
}
/// Given a `parent_def_id`, a list of `lifetimes_in_bounds and a `remapping` hash to be
/// filled, this function creates new definitions for `Param` and `Fresh` lifetimes, inserts the
/// new definition, adds it to the remapping with the definition of the given lifetime and
/// returns a list of lifetimes to be lowered afterwards.
fn create_lifetime_defs(
&mut self,
parent_def_id: LocalDefId,
lifetimes_in_bounds: &[Lifetime],
remapping: &mut FxHashMap<LocalDefId, LocalDefId>,
) -> Vec<(NodeId, Lifetime)> {
let mut result = Vec::new();
for lifetime in lifetimes_in_bounds {
let res = self.resolver.get_lifetime_res(lifetime.id).unwrap_or(LifetimeRes::Error);
debug!(?res);
match res {
LifetimeRes::Param { param: old_def_id, binder: _ } => {
if remapping.get(&old_def_id).is_none() {
let node_id = self.next_node_id();
let new_def_id = self.create_def(
parent_def_id,
node_id,
DefPathData::LifetimeNs(lifetime.ident.name),
);
remapping.insert(old_def_id, new_def_id);
result.push((node_id, *lifetime));
}
}
LifetimeRes::Fresh { param, binder: _ } => {
debug_assert_eq!(lifetime.ident.name, kw::UnderscoreLifetime);
let old_def_id = self.local_def_id(param);
if remapping.get(&old_def_id).is_none() {
let node_id = self.next_node_id();
let new_def_id = self.create_def(
parent_def_id,
node_id,
DefPathData::LifetimeNs(kw::UnderscoreLifetime),
);
remapping.insert(old_def_id, new_def_id);
result.push((node_id, *lifetime));
}
}
LifetimeRes::Static | LifetimeRes::Error => {}
res => {
let bug_msg = format!(
"Unexpected lifetime resolution {:?} for {:?} at {:?}",
res, lifetime.ident, lifetime.ident.span
);
span_bug!(lifetime.ident.span, "{}", bug_msg);
}
}
}
result
}
fn lower_fn_params_to_names(&mut self, decl: &FnDecl) -> &'hir [Ident] {
// Skip the `...` (`CVarArgs`) trailing arguments from the AST,
// as they are not explicit in HIR/Ty function signatures.
@ -1582,11 +1726,9 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
//
// type OpaqueTy<generics_from_parent_fn> = impl Future<Output = T>;
//
// `inputs`: lowered types of parameters to the function (used to collect lifetimes)
// `output`: unlowered output type (`T` in `-> T`)
// `fn_def_id`: `DefId` of the parent function (used to create child impl trait definition)
// `opaque_ty_node_id`: `NodeId` of the opaque `impl Trait` type that should be created
// `elided_lt_replacement`: replacement for elided lifetimes in the return type
#[tracing::instrument(level = "debug", skip(self))]
fn lower_async_fn_ret_ty(
&mut self,
@ -1643,90 +1785,126 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
// by the opaque type. This should include all in-scope
// lifetime parameters, including those defined in-band.
let mut captures = FxHashMap::default();
// Contains the new lifetime definitions created for the TAIT (if any) generated for the
// return type.
let mut collected_lifetimes = Vec::new();
let mut new_remapping = FxHashMap::default();
let extra_lifetime_params = self.resolver.take_extra_lifetime_params(opaque_ty_node_id);
debug!(?extra_lifetime_params);
for (ident, outer_node_id, outer_res) in extra_lifetime_params {
let Ident { name, span } = ident;
let outer_def_id = self.local_def_id(outer_node_id);
let inner_node_id = self.next_node_id();
// Add a definition for the in scope lifetime def.
self.create_def(opaque_ty_def_id, inner_node_id, DefPathData::LifetimeNs(name));
let inner_def_id = self.create_def(
opaque_ty_def_id,
inner_node_id,
DefPathData::LifetimeNs(ident.name),
);
new_remapping.insert(outer_def_id, inner_def_id);
let (p_name, inner_res) = match outer_res {
let inner_res = match outer_res {
// Input lifetime like `'a`:
LifetimeRes::Param { param, .. } => {
(hir::ParamName::Plain(ident), LifetimeRes::Param { param, binder: fn_node_id })
LifetimeRes::Param { param, binder: fn_node_id }
}
// Input lifetime like `'1`:
LifetimeRes::Fresh { param, .. } => {
(hir::ParamName::Fresh, LifetimeRes::Fresh { param, binder: fn_node_id })
LifetimeRes::Fresh { param, binder: fn_node_id }
}
LifetimeRes::Static | LifetimeRes::Error => continue,
res => {
panic!("Unexpected lifetime resolution {:?} for {:?} at {:?}", res, ident, span)
panic!(
"Unexpected lifetime resolution {:?} for {:?} at {:?}",
res, ident, ident.span
)
}
};
captures.insert(outer_def_id, (span, inner_node_id, p_name, inner_res));
let lifetime = Lifetime { id: outer_node_id, ident };
collected_lifetimes.push((inner_node_id, lifetime, Some(inner_res)));
}
debug!(?captures);
debug!(?collected_lifetimes);
// We only want to capture the lifetimes that appear in the bounds. So visit the bounds to
// find out exactly which ones those are.
// in fn return position, like the `fn test<'a>() -> impl Debug + 'a` example,
// we only keep the lifetimes that appear in the `impl Debug` itself:
let lifetimes_to_remap = lifetime_collector::lifetimes_in_ret_ty(&self.resolver, output);
debug!(?lifetimes_to_remap);
self.with_hir_id_owner(opaque_ty_node_id, |this| {
let future_bound =
this.while_capturing_lifetimes(opaque_ty_def_id, &mut captures, |this| {
// We have to be careful to get elision right here. The
// idea is that we create a lifetime parameter for each
// lifetime in the return type. So, given a return type
// like `async fn foo(..) -> &[&u32]`, we lower to `impl
// Future<Output = &'1 [ &'2 u32 ]>`.
//
// Then, we will create `fn foo(..) -> Foo<'_, '_>`, and
// hence the elision takes place at the fn site.
this.lower_async_fn_output_type_to_future_bound(output, fn_def_id, span)
});
debug!("lower_async_fn_ret_ty: future_bound={:#?}", future_bound);
debug!("lower_async_fn_ret_ty: captures={:#?}", captures);
// If this opaque type is only capturing a subset of the lifetimes (those that appear
// in bounds), then create the new lifetime parameters required and create a mapping
// from the old `'a` (on the function) to the new `'a` (on the opaque type).
collected_lifetimes.extend(
this.create_lifetime_defs(
opaque_ty_def_id,
&lifetimes_to_remap,
&mut new_remapping,
)
.into_iter()
.map(|(new_node_id, lifetime)| (new_node_id, lifetime, None)),
);
debug!(?collected_lifetimes);
debug!(?new_remapping);
let generic_params =
this.arena.alloc_from_iter(captures.iter().map(|(_, &(span, p_id, p_name, _))| {
let hir_id = this.lower_node_id(p_id);
debug_assert_ne!(this.opt_local_def_id(p_id), None);
// Install the remapping from old to new (if any):
this.with_remapping(new_remapping, |this| {
// We have to be careful to get elision right here. The
// idea is that we create a lifetime parameter for each
// lifetime in the return type. So, given a return type
// like `async fn foo(..) -> &[&u32]`, we lower to `impl
// Future<Output = &'1 [ &'2 u32 ]>`.
//
// Then, we will create `fn foo(..) -> Foo<'_, '_>`, and
// hence the elision takes place at the fn site.
let future_bound =
this.lower_async_fn_output_type_to_future_bound(output, fn_def_id, span);
let kind = if p_name.ident().name == kw::UnderscoreLifetime {
hir::LifetimeParamKind::Elided
} else {
hir::LifetimeParamKind::Explicit
};
let generic_params = this.arena.alloc_from_iter(collected_lifetimes.iter().map(
|&(new_node_id, lifetime, _)| {
let hir_id = this.lower_node_id(new_node_id);
debug_assert_ne!(this.opt_local_def_id(new_node_id), None);
hir::GenericParam {
hir_id,
name: p_name,
span,
pure_wrt_drop: false,
kind: hir::GenericParamKind::Lifetime { kind },
colon_span: None,
}
}));
debug!("lower_async_fn_ret_ty: generic_params={:#?}", generic_params);
let (name, kind) = if lifetime.ident.name == kw::UnderscoreLifetime {
(hir::ParamName::Fresh, hir::LifetimeParamKind::Elided)
} else {
(
hir::ParamName::Plain(lifetime.ident),
hir::LifetimeParamKind::Explicit,
)
};
let opaque_ty_item = hir::OpaqueTy {
generics: this.arena.alloc(hir::Generics {
params: generic_params,
predicates: &[],
has_where_clause_predicates: false,
where_clause_span: this.lower_span(span),
span: this.lower_span(span),
}),
bounds: arena_vec![this; future_bound],
origin: hir::OpaqueTyOrigin::AsyncFn(fn_def_id),
};
hir::GenericParam {
hir_id,
name,
span: lifetime.ident.span,
pure_wrt_drop: false,
kind: hir::GenericParamKind::Lifetime { kind },
colon_span: None,
}
},
));
debug!("lower_async_fn_ret_ty: generic_params={:#?}", generic_params);
trace!("exist ty from async fn def id: {:#?}", opaque_ty_def_id);
this.generate_opaque_type(opaque_ty_def_id, opaque_ty_item, span, opaque_ty_span)
let opaque_ty_item = hir::OpaqueTy {
generics: this.arena.alloc(hir::Generics {
params: generic_params,
predicates: &[],
has_where_clause_predicates: false,
where_clause_span: this.lower_span(span),
span: this.lower_span(span),
}),
bounds: arena_vec![this; future_bound],
origin: hir::OpaqueTyOrigin::AsyncFn(fn_def_id),
};
trace!("exist ty from async fn def id: {:#?}", opaque_ty_def_id);
this.generate_opaque_type(opaque_ty_def_id, opaque_ty_item, span, opaque_ty_span)
})
});
// As documented above, we need to create the lifetime
@ -1744,13 +1922,24 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
//
// For the "output" lifetime parameters, we just want to
// generate `'_`.
let generic_args =
self.arena.alloc_from_iter(captures.into_iter().map(|(_, (span, _, p_name, res))| {
let generic_args = self.arena.alloc_from_iter(collected_lifetimes.into_iter().map(
|(_, lifetime, res)| {
let id = self.next_node_id();
let ident = Ident::new(p_name.ident().name, span);
let span = lifetime.ident.span;
let ident = if lifetime.ident.name == kw::UnderscoreLifetime {
Ident::with_dummy_span(kw::UnderscoreLifetime)
} else {
lifetime.ident
};
let res = res.unwrap_or(
self.resolver.get_lifetime_res(lifetime.id).unwrap_or(LifetimeRes::Error),
);
let l = self.new_named_lifetime_with_res(id, span, ident, res);
hir::GenericArg::Lifetime(l)
}));
},
));
// Create the `Foo<...>` reference itself. Note that the `type
// Foo = impl Trait` is, internally, created as a child of the
@ -1820,8 +2009,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
fn lower_lifetime(&mut self, l: &Lifetime) -> hir::Lifetime {
let span = self.lower_span(l.ident.span);
let ident = self.lower_ident(l.ident);
let res = self.resolver.get_lifetime_res(l.id).unwrap_or(LifetimeRes::Error);
self.new_named_lifetime_with_res(l.id, span, ident, res)
self.new_named_lifetime(l.id, l.id, span, ident)
}
#[tracing::instrument(level = "debug", skip(self))]
@ -1832,55 +2020,17 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
ident: Ident,
res: LifetimeRes,
) -> hir::Lifetime {
debug!(?self.captured_lifetimes);
let name = match res {
LifetimeRes::Param { mut param, binder } => {
LifetimeRes::Param { param, .. } => {
let p_name = ParamName::Plain(ident);
if let Some(mut captured_lifetimes) = self.captured_lifetimes.take() {
if !captured_lifetimes.binders_to_ignore.contains(&binder) {
match captured_lifetimes.captures.entry(param) {
Entry::Occupied(o) => param = self.local_def_id(o.get().1),
Entry::Vacant(v) => {
let p_id = self.next_node_id();
let p_def_id = self.create_def(
captured_lifetimes.parent_def_id,
p_id,
DefPathData::LifetimeNs(p_name.ident().name),
);
let param = self.resolver.get_remapped_def_id(param);
v.insert((span, p_id, p_name, res));
param = p_def_id;
}
}
}
self.captured_lifetimes = Some(captured_lifetimes);
}
hir::LifetimeName::Param(param, p_name)
}
LifetimeRes::Fresh { param, binder } => {
LifetimeRes::Fresh { param, .. } => {
debug_assert_eq!(ident.name, kw::UnderscoreLifetime);
let mut param = self.local_def_id(param);
if let Some(mut captured_lifetimes) = self.captured_lifetimes.take() {
if !captured_lifetimes.binders_to_ignore.contains(&binder) {
match captured_lifetimes.captures.entry(param) {
Entry::Occupied(o) => param = self.local_def_id(o.get().1),
Entry::Vacant(v) => {
let p_id = self.next_node_id();
let p_def_id = self.create_def(
captured_lifetimes.parent_def_id,
p_id,
DefPathData::LifetimeNs(kw::UnderscoreLifetime),
);
let param = self.local_def_id(param);
v.insert((span, p_id, ParamName::Fresh, res));
param = p_def_id;
}
}
}
self.captured_lifetimes = Some(captured_lifetimes);
}
hir::LifetimeName::Param(param, ParamName::Fresh)
}
LifetimeRes::Infer => hir::LifetimeName::Infer,
@ -1888,11 +2038,23 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
LifetimeRes::Error => hir::LifetimeName::Error,
res => panic!("Unexpected lifetime resolution {:?} for {:?} at {:?}", res, ident, span),
};
debug!(?self.captured_lifetimes);
debug!(?name);
hir::Lifetime { hir_id: self.lower_node_id(id), span: self.lower_span(span), name }
}
#[tracing::instrument(level = "debug", skip(self))]
fn new_named_lifetime(
&mut self,
id: NodeId,
new_id: NodeId,
span: Span,
ident: Ident,
) -> hir::Lifetime {
let res = self.resolver.get_lifetime_res(id).unwrap_or(LifetimeRes::Error);
self.new_named_lifetime_with_res(new_id, span, ident, res)
}
fn lower_generic_params_mut<'s>(
&'s mut self,
params: &'s [GenericParam],
@ -1975,14 +2137,10 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
p: &PolyTraitRef,
itctx: ImplTraitContext,
) -> hir::PolyTraitRef<'hir> {
self.with_lifetime_binder(
p.trait_ref.ref_id,
&p.bound_generic_params,
|this, bound_generic_params| {
let trait_ref = this.lower_trait_ref(&p.trait_ref, itctx);
hir::PolyTraitRef { bound_generic_params, trait_ref, span: this.lower_span(p.span) }
},
)
let bound_generic_params =
self.lower_lifetime_binder(p.trait_ref.ref_id, &p.bound_generic_params);
let trait_ref = self.lower_trait_ref(&p.trait_ref, itctx);
hir::PolyTraitRef { bound_generic_params, trait_ref, span: self.lower_span(p.span) }
}
fn lower_mt(&mut self, mt: &MutTy, itctx: ImplTraitContext) -> hir::MutTy<'hir> {
@ -2015,7 +2173,6 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
// Add a definition for the in-band `Param`.
let def_id = self.local_def_id(node_id);
let hir_bounds = self.lower_param_bounds(bounds, ImplTraitContext::Universal);
// Set the name to `impl Bound1 + Bound2`.
let param = hir::GenericParam {
hir_id: self.lower_node_id(node_id),
@ -2030,7 +2187,8 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
ident,
node_id,
&GenericParamKind::Type { default: None },
hir_bounds,
bounds,
ImplTraitContext::Universal,
hir::PredicateOrigin::ImplTrait,
);

View file

@ -0,0 +1,115 @@
use super::ResolverAstLoweringExt;
use rustc_ast::visit::{self, BoundKind, LifetimeCtxt, Visitor};
use rustc_ast::{
FnRetTy, GenericBounds, Lifetime, NodeId, PathSegment, PolyTraitRef, TraitBoundModifier, Ty,
TyKind,
};
use rustc_hir::def::LifetimeRes;
use rustc_middle::span_bug;
use rustc_middle::ty::ResolverAstLowering;
use rustc_span::symbol::{kw, Ident};
use rustc_span::Span;
struct LifetimeCollectVisitor<'ast> {
resolver: &'ast ResolverAstLowering,
current_binders: Vec<NodeId>,
collected_lifetimes: Vec<Lifetime>,
}
impl<'ast> LifetimeCollectVisitor<'ast> {
fn new(resolver: &'ast ResolverAstLowering) -> Self {
Self { resolver, current_binders: Vec::new(), collected_lifetimes: Vec::new() }
}
fn record_lifetime_use(&mut self, lifetime: Lifetime) {
match self.resolver.get_lifetime_res(lifetime.id).unwrap_or(LifetimeRes::Error) {
LifetimeRes::Param { binder, .. } | LifetimeRes::Fresh { binder, .. } => {
if !self.current_binders.contains(&binder) {
if !self.collected_lifetimes.contains(&lifetime) {
self.collected_lifetimes.push(lifetime);
}
}
}
LifetimeRes::Static | LifetimeRes::Error => {
if !self.collected_lifetimes.contains(&lifetime) {
self.collected_lifetimes.push(lifetime);
}
}
LifetimeRes::Infer => {}
res => {
let bug_msg = format!(
"Unexpected lifetime resolution {:?} for {:?} at {:?}",
res, lifetime.ident, lifetime.ident.span
);
span_bug!(lifetime.ident.span, "{}", bug_msg);
}
}
}
/// This collect lifetimes that are elided, for nodes like `Foo<T>` where there are no explicit
/// lifetime nodes. Is equivalent to having "pseudo" nodes introduced for each of the node ids
/// in the list start..end.
fn record_elided_anchor(&mut self, node_id: NodeId, span: Span) {
if let Some(LifetimeRes::ElidedAnchor { start, end }) =
self.resolver.get_lifetime_res(node_id)
{
for i in start..end {
let lifetime = Lifetime { id: i, ident: Ident::new(kw::UnderscoreLifetime, span) };
self.record_lifetime_use(lifetime);
}
}
}
}
impl<'ast> Visitor<'ast> for LifetimeCollectVisitor<'ast> {
fn visit_lifetime(&mut self, lifetime: &'ast Lifetime, _: LifetimeCtxt) {
self.record_lifetime_use(*lifetime);
}
fn visit_path_segment(&mut self, path_span: Span, path_segment: &'ast PathSegment) {
self.record_elided_anchor(path_segment.id, path_span);
visit::walk_path_segment(self, path_span, path_segment);
}
fn visit_poly_trait_ref(&mut self, t: &'ast PolyTraitRef, m: &'ast TraitBoundModifier) {
self.current_binders.push(t.trait_ref.ref_id);
visit::walk_poly_trait_ref(self, t, m);
self.current_binders.pop();
}
fn visit_ty(&mut self, t: &'ast Ty) {
match t.kind {
TyKind::BareFn(_) => {
self.current_binders.push(t.id);
visit::walk_ty(self, t);
self.current_binders.pop();
}
TyKind::Rptr(None, _) => {
self.record_elided_anchor(t.id, t.span);
visit::walk_ty(self, t);
}
_ => {
visit::walk_ty(self, t);
}
}
}
}
pub fn lifetimes_in_ret_ty(resolver: &ResolverAstLowering, ret_ty: &FnRetTy) -> Vec<Lifetime> {
let mut visitor = LifetimeCollectVisitor::new(resolver);
visitor.visit_fn_ret_ty(ret_ty);
visitor.collected_lifetimes
}
pub fn lifetimes_in_bounds(
resolver: &ResolverAstLowering,
bounds: &GenericBounds,
) -> Vec<Lifetime> {
let mut visitor = LifetimeCollectVisitor::new(resolver);
for bound in bounds {
visitor.visit_param_bound(bound, BoundKind::Bound);
}
visitor.collected_lifetimes
}

View file

@ -177,6 +177,11 @@ pub struct ResolverAstLowering {
pub label_res_map: NodeMap<ast::NodeId>,
/// Resolutions for lifetimes.
pub lifetimes_res_map: NodeMap<LifetimeRes>,
/// Mapping from generics `def_id`s to TAIT generics `def_id`s.
/// For each captured lifetime (e.g., 'a), we create a new lifetime parameter that is a generic
/// defined on the TAIT, so we have type Foo<'a1> = ... and we establish a mapping in this
/// field from the original parameter 'a to the new parameter 'a1.
pub generics_def_id_map: Vec<FxHashMap<LocalDefId, LocalDefId>>,
/// Lifetime parameters that lowering will have to introduce.
pub extra_lifetime_params_map: NodeMap<Vec<(Ident, ast::NodeId, LifetimeRes)>>,

View file

@ -913,6 +913,11 @@ pub struct Resolver<'a> {
label_res_map: NodeMap<NodeId>,
/// Resolutions for lifetimes.
lifetimes_res_map: NodeMap<LifetimeRes>,
/// Mapping from generics `def_id`s to TAIT generics `def_id`s.
/// For each captured lifetime (e.g., 'a), we create a new lifetime parameter that is a generic
/// defined on the TAIT, so we have type Foo<'a1> = ... and we establish a mapping in this
/// field from the original parameter 'a to the new parameter 'a1.
generics_def_id_map: Vec<FxHashMap<LocalDefId, LocalDefId>>,
/// Lifetime parameters that lowering will have to introduce.
extra_lifetime_params_map: NodeMap<Vec<(Ident, NodeId, LifetimeRes)>>,
@ -1277,6 +1282,7 @@ impl<'a> Resolver<'a> {
import_res_map: Default::default(),
label_res_map: Default::default(),
lifetimes_res_map: Default::default(),
generics_def_id_map: Vec::new(),
extra_lifetime_params_map: Default::default(),
extern_crate_map: Default::default(),
reexport_map: FxHashMap::default(),
@ -1444,6 +1450,7 @@ impl<'a> Resolver<'a> {
import_res_map: self.import_res_map,
label_res_map: self.label_res_map,
lifetimes_res_map: self.lifetimes_res_map,
generics_def_id_map: self.generics_def_id_map,
extra_lifetime_params_map: self.extra_lifetime_params_map,
next_node_id: self.next_node_id,
node_id_to_def_id: self.node_id_to_def_id,
@ -1488,6 +1495,7 @@ impl<'a> Resolver<'a> {
import_res_map: self.import_res_map.clone(),
label_res_map: self.label_res_map.clone(),
lifetimes_res_map: self.lifetimes_res_map.clone(),
generics_def_id_map: self.generics_def_id_map.clone(),
extra_lifetime_params_map: self.extra_lifetime_params_map.clone(),
next_node_id: self.next_node_id.clone(),
node_id_to_def_id: self.node_id_to_def_id.clone(),

View file

@ -0,0 +1,7 @@
// check-pass
pub fn iter<'a>(v: Vec<(u32, &'a u32)>) -> impl DoubleEndedIterator<Item = (u32, &u32)> {
v.into_iter()
}
fn main() {}