Skip to content

Commit

Permalink
Auto merge of #94773 - cjgillot:lifetime-fresh-did, r=oli-obk
Browse files Browse the repository at this point in the history
Identify anonymous lifetimes by their DefId in HIR.

`ParamName::Fresh` currently identifies anonymous lifetimes by an `usize` index computed from the number of lifetimes in scope. This makes the behaviour of lowering dependent on the contents of the surrounding item in unpredictable ways.

This PR replaces this index by the `LocalDefId` of the synthetized generic lifetime parameter. This makes obvious which parameter the lifetime corresponds to.
  • Loading branch information
bors committed Mar 14, 2022
2 parents 737ef08 + c8c691f commit 3f58828
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 78 deletions.
24 changes: 16 additions & 8 deletions compiler/rustc_ast_lowering/src/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ impl<'a> Visitor<'a> for ItemLowerer<'a, '_, '_> {
}

fn visit_assoc_item(&mut self, item: &'a AssocItem, ctxt: AssocCtxt) {
debug!(in_scope_lifetimes = ?self.lctx.in_scope_lifetimes);
self.lctx.with_hir_id_owner(item.id, |lctx| match ctxt {
AssocCtxt::Trait => hir::OwnerNode::TraitItem(lctx.lower_trait_item(item)),
AssocCtxt::Impl => hir::OwnerNode::ImplItem(lctx.lower_impl_item(item)),
Expand All @@ -118,35 +119,42 @@ impl<'hir> LoweringContext<'_, 'hir> {
// This should only be used with generics that have already had their
// in-band lifetimes added. In practice, this means that this function is
// only used when lowering a child item of a trait or impl.
#[tracing::instrument(level = "debug", skip(self, f))]
fn with_parent_item_lifetime_defs<T>(
&mut self,
parent_hir_id: LocalDefId,
f: impl FnOnce(&mut Self) -> T,
) -> T {
let old_len = self.in_scope_lifetimes.len();

let parent_generics = match self.owners[parent_hir_id].unwrap().node().expect_item().kind {
hir::ItemKind::Impl(hir::Impl { ref generics, .. })
| hir::ItemKind::Trait(_, _, ref generics, ..) => generics.params,
_ => &[],
};
let lt_def_names = parent_generics.iter().filter_map(|param| match param.kind {
hir::GenericParamKind::Lifetime { .. } => Some(param.name.normalize_to_macros_2_0()),
_ => None,
});
self.in_scope_lifetimes.extend(lt_def_names);
let lt_def_names = parent_generics
.iter()
.filter_map(|param| match param.kind {
hir::GenericParamKind::Lifetime { .. } => {
Some(param.name.normalize_to_macros_2_0())
}
_ => None,
})
.collect();
let old_in_scope_lifetimes = mem::replace(&mut self.in_scope_lifetimes, lt_def_names);
debug!(in_scope_lifetimes = ?self.in_scope_lifetimes);

let res = f(self);

self.in_scope_lifetimes.truncate(old_len);
self.in_scope_lifetimes = old_in_scope_lifetimes;
res
}

// Clears (and restores) the `in_scope_lifetimes` field. Used when
// visiting nested items, which never inherit in-scope lifetimes
// from their surrounding environment.
#[tracing::instrument(level = "debug", skip(self, f))]
fn without_in_scope_lifetime_defs<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
let old_in_scope_lifetimes = mem::replace(&mut self.in_scope_lifetimes, vec![]);
debug!(?old_in_scope_lifetimes);

// this vector is only used when walking over impl headers,
// input types, and the like, and should not be non-empty in
Expand Down
159 changes: 91 additions & 68 deletions compiler/rustc_ast_lowering/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,20 +129,16 @@ struct LoweringContext<'a, 'hir: 'a> {
/// written at all (e.g., `&T` or `std::cell::Ref<T>`).
anonymous_lifetime_mode: AnonymousLifetimeMode,

/// Used to create lifetime definitions from in-band lifetime usages.
/// e.g., `fn foo(x: &'x u8) -> &'x u8` to `fn foo<'x>(x: &'x u8) -> &'x u8`
/// When a named lifetime is encountered in a function or impl header and
/// has not been defined
/// (i.e., it doesn't appear in the in_scope_lifetimes list), it is added
/// Used to create lifetime definitions for anonymous lifetimes.
/// When an anonymous lifetime is encountered in a function or impl header and
/// requires to create a fresh lifetime parameter, it is added
/// to this list. The results of this list are then added to the list of
/// lifetime definitions in the corresponding impl or function generics.
lifetimes_to_define: Vec<(Span, ParamName)>,
lifetimes_to_define: Vec<(Span, NodeId)>,

/// `true` if in-band lifetimes are being collected. This is used to
/// indicate whether or not we're in a place where new lifetimes will result
/// in in-band lifetime definitions, such a function or an impl header,
/// including implicit lifetimes from `impl_header_lifetime_elision`.
is_collecting_anonymous_lifetimes: bool,
/// If anonymous lifetimes are being collected, this field holds the parent
/// `LocalDefId` to create the fresh lifetime parameters' `LocalDefId`.
is_collecting_anonymous_lifetimes: Option<LocalDefId>,

/// Currently in-scope lifetimes defined in impl headers, fn headers, or HRTB.
/// We always store a `normalize_to_macros_2_0()` version of the param-name in this
Expand Down Expand Up @@ -375,7 +371,7 @@ pub fn lower_crate<'a, 'hir>(
task_context: None,
current_item: None,
lifetimes_to_define: Vec::new(),
is_collecting_anonymous_lifetimes: false,
is_collecting_anonymous_lifetimes: None,
in_scope_lifetimes: Vec::new(),
allow_try_trait: Some([sym::try_trait_v2][..].into()),
allow_gen_future: Some([sym::gen_future][..].into()),
Expand Down Expand Up @@ -720,9 +716,11 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
/// parameter while `f` is running (and restored afterwards).
fn collect_in_band_defs<T>(
&mut self,
parent_def_id: LocalDefId,
f: impl FnOnce(&mut Self) -> T,
) -> (Vec<(Span, ParamName)>, T) {
let was_collecting = std::mem::replace(&mut self.is_collecting_anonymous_lifetimes, true);
) -> (Vec<(Span, NodeId)>, T) {
let was_collecting =
std::mem::replace(&mut self.is_collecting_anonymous_lifetimes, Some(parent_def_id));
let len = self.lifetimes_to_define.len();

let res = f(self);
Expand All @@ -733,49 +731,41 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
}

/// Converts a lifetime into a new generic parameter.
fn lifetime_to_generic_param(
fn fresh_lifetime_to_generic_param(
&mut self,
span: Span,
hir_name: ParamName,
parent_def_id: LocalDefId,
node_id: NodeId,
) -> hir::GenericParam<'hir> {
let node_id = self.resolver.next_node_id();

// Get the name we'll use to make the def-path. Note
// that collisions are ok here and this shouldn't
// really show up for end-user.
let (str_name, kind) = match hir_name {
ParamName::Plain(ident) => (ident.name, hir::LifetimeParamKind::Explicit),
ParamName::Fresh(_) => (kw::UnderscoreLifetime, hir::LifetimeParamKind::Elided),
ParamName::Error => (kw::UnderscoreLifetime, hir::LifetimeParamKind::Error),
};

// Add a definition for the in-band lifetime def.
self.resolver.create_def(
parent_def_id,
node_id,
DefPathData::LifetimeNs(str_name),
ExpnId::root(),
span.with_parent(None),
);

let hir_id = self.lower_node_id(node_id);
let def_id = self.resolver.local_def_id(node_id);
hir::GenericParam {
hir_id: self.lower_node_id(node_id),
name: hir_name,
hir_id,
name: hir::ParamName::Fresh(def_id),
bounds: &[],
span: self.lower_span(span),
pure_wrt_drop: false,
kind: hir::GenericParamKind::Lifetime { kind },
kind: hir::GenericParamKind::Lifetime { kind: hir::LifetimeParamKind::Elided },
}
}

/// When we have either an elided or `'_` lifetime in an impl
/// header, we convert it to an in-band lifetime.
fn collect_fresh_anonymous_lifetime(&mut self, span: Span) -> ParamName {
assert!(self.is_collecting_anonymous_lifetimes);
let index = self.lifetimes_to_define.len() + self.in_scope_lifetimes.len();
let hir_name = ParamName::Fresh(index);
self.lifetimes_to_define.push((span, hir_name));
let Some(parent_def_id) = self.is_collecting_anonymous_lifetimes else { panic!() };

let node_id = self.resolver.next_node_id();

// Add a definition for the in-band lifetime def.
let param_def_id = self.resolver.create_def(
parent_def_id,
node_id,
DefPathData::LifetimeNs(kw::UnderscoreLifetime),
ExpnId::root(),
span.with_parent(None),
);

let hir_name = ParamName::Fresh(param_def_id);
self.lifetimes_to_define.push((span, node_id));
hir_name
}

Expand Down Expand Up @@ -817,7 +807,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
f: impl FnOnce(&mut Self, &mut Vec<hir::GenericParam<'hir>>) -> T,
) -> (hir::Generics<'hir>, T) {
let (lifetimes_to_define, (mut lowered_generics, impl_trait_defs, res)) = self
.collect_in_band_defs(|this| {
.collect_in_band_defs(parent_def_id, |this| {
this.with_anonymous_lifetime_mode(anonymous_lifetime_mode, |this| {
this.with_in_scope_lifetime_defs(&generics.params, |this| {
let mut impl_trait_defs = Vec::new();
Expand All @@ -844,9 +834,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
lowered_generics.params.extend(
lifetimes_to_define
.into_iter()
.map(|(span, hir_name)| {
self.lifetime_to_generic_param(span, hir_name, parent_def_id)
})
.map(|(span, node_id)| self.fresh_lifetime_to_generic_param(span, node_id))
.chain(impl_trait_defs),
);

Expand Down Expand Up @@ -1763,15 +1751,53 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
.in_scope_lifetimes
.iter()
.cloned()
.map(|name| (name.ident().span, name, hir::LifetimeName::Param(name)))
.chain(
self.lifetimes_to_define
.iter()
.map(|&(span, name)| (span, name, hir::LifetimeName::Param(name))),
)
.map(|name| (name.ident().span, hir::LifetimeName::Param(name)))
.chain(self.lifetimes_to_define.iter().map(|&(span, node_id)| {
let def_id = self.resolver.local_def_id(node_id);
let name = hir::ParamName::Fresh(def_id);
(span, hir::LifetimeName::Param(name))
}))
.collect();

self.with_hir_id_owner(opaque_ty_node_id, |this| {
let mut generic_params: Vec<_> = lifetime_params
.iter()
.map(|&(span, name)| {
// We can only get lifetime names from the outside.
let hir::LifetimeName::Param(hir_name) = name else { panic!() };

let node_id = this.resolver.next_node_id();

// Add a definition for the in-band lifetime def.
let def_id = this.resolver.create_def(
opaque_ty_def_id,
node_id,
DefPathData::LifetimeNs(hir_name.ident().name),
ExpnId::root(),
span.with_parent(None),
);

let (kind, name) = match hir_name {
ParamName::Plain(ident) => {
(hir::LifetimeParamKind::Explicit, hir::ParamName::Plain(ident))
}
ParamName::Fresh(_) => {
(hir::LifetimeParamKind::Elided, hir::ParamName::Fresh(def_id))
}
ParamName::Error => (hir::LifetimeParamKind::Error, hir::ParamName::Error),
};

hir::GenericParam {
hir_id: this.lower_node_id(node_id),
name,
bounds: &[],
span: this.lower_span(span),
pure_wrt_drop: false,
kind: hir::GenericParamKind::Lifetime { kind },
}
})
.collect();

// 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
Expand All @@ -1782,25 +1808,22 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
// hence the elision takes place at the fn site.
let (lifetimes_to_define, future_bound) =
this.with_anonymous_lifetime_mode(AnonymousLifetimeMode::CreateParameter, |this| {
this.collect_in_band_defs(|this| {
this.collect_in_band_defs(opaque_ty_def_id, |this| {
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: lifetimes_to_define={:#?}", lifetimes_to_define);

lifetime_params.extend(
// Output lifetime like `'_`:
lifetimes_to_define
.into_iter()
.map(|(span, name)| (span, name, hir::LifetimeName::Implicit(false))),
);
// Output lifetime like `'_`:
for (span, node_id) in lifetimes_to_define {
let param = this.fresh_lifetime_to_generic_param(span, node_id);
lifetime_params.push((span, hir::LifetimeName::Implicit(false)));
generic_params.push(param);
}
let generic_params = this.arena.alloc_from_iter(generic_params);
debug!("lower_async_fn_ret_ty: lifetime_params={:#?}", lifetime_params);

let generic_params =
this.arena.alloc_from_iter(lifetime_params.iter().map(|&(span, hir_name, _)| {
this.lifetime_to_generic_param(span, hir_name, opaque_ty_def_id)
}));
debug!("lower_async_fn_ret_ty: generic_params={:#?}", generic_params);

let opaque_ty_item = hir::OpaqueTy {
generics: hir::Generics {
Expand Down Expand Up @@ -1833,7 +1856,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
// For the "output" lifetime parameters, we just want to
// generate `'_`.
let generic_args =
self.arena.alloc_from_iter(lifetime_params.into_iter().map(|(span, _, name)| {
self.arena.alloc_from_iter(lifetime_params.into_iter().map(|(span, name)| {
GenericArg::Lifetime(hir::Lifetime {
hir_id: self.next_id(),
span: self.lower_span(span),
Expand Down Expand Up @@ -1969,7 +1992,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
let (name, kind) = match param.kind {
GenericParamKind::Lifetime => {
let was_collecting_in_band = self.is_collecting_anonymous_lifetimes;
self.is_collecting_anonymous_lifetimes = false;
self.is_collecting_anonymous_lifetimes = None;

let lt = self
.with_anonymous_lifetime_mode(AnonymousLifetimeMode::ReportError, |this| {
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_hir/src/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ pub enum ParamName {
///
/// where `'f` is something like `Fresh(0)`. The indices are
/// unique per impl, but not necessarily continuous.
Fresh(usize),
Fresh(LocalDefId),

/// Indicates an illegal name was given and an error has been
/// reported (so we should squelch other derived errors). Occurs
Expand Down Expand Up @@ -3303,7 +3303,7 @@ mod size_asserts {
rustc_data_structures::static_assert_size!(super::Expr<'static>, 56);
rustc_data_structures::static_assert_size!(super::Pat<'static>, 88);
rustc_data_structures::static_assert_size!(super::QPath<'static>, 24);
rustc_data_structures::static_assert_size!(super::Ty<'static>, 80);
rustc_data_structures::static_assert_size!(super::Ty<'static>, 72);

rustc_data_structures::static_assert_size!(super::Item<'static>, 184);
rustc_data_structures::static_assert_size!(super::TraitItem<'static>, 128);
Expand Down

0 comments on commit 3f58828

Please sign in to comment.