diff --git a/compiler/rustc_middle/src/middle/resolve_lifetime.rs b/compiler/rustc_middle/src/middle/resolve_lifetime.rs index c71ba7b175313..0ed2193015fac 100644 --- a/compiler/rustc_middle/src/middle/resolve_lifetime.rs +++ b/compiler/rustc_middle/src/middle/resolve_lifetime.rs @@ -36,7 +36,13 @@ impl Set1 { } } -pub type ObjectLifetimeDefault = Set1; +#[derive(Copy, Clone, Debug, HashStable, Encodable, Decodable)] +pub enum ObjectLifetimeDefault { + Empty, + Static, + Ambiguous, + Param(DefId), +} /// Maps the id of each lifetime reference to the lifetime decl /// that it corresponds to. diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index 5b48f164016f7..1643b84ea2d44 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -1591,8 +1591,16 @@ rustc_queries! { /// for each parameter if a trait object were to be passed for that parameter. /// For example, for `struct Foo<'a, T, U>`, this would be `['static, 'static]`. /// For `struct Foo<'a, T: 'a, U>`, this would instead be `['a, 'static]`. - query object_lifetime_defaults(_: LocalDefId) -> Option<&'tcx [ObjectLifetimeDefault]> { - desc { "looking up lifetime defaults for a region on an item" } + query object_lifetime_defaults(def_id: LocalDefId) -> Option<&'tcx [ObjectLifetimeDefault]> { + desc { "computing object lifetime defaults for `{:?}`'s generic parameters", def_id } + } + /// Fetch the lifetimes for all the trait objects in an item-like. This query uses + /// `object_lifetime_defaults` which returns a map `GenericParam -> ObjectLifetimeDefault`, + /// and build a map from each `dyn Trait` type to the implicit lifetime `'a`, so that + /// `dyn Trait` should be understood as `dyn Trait + 'a` + query object_lifetime_map(def_id: LocalDefId) -> FxHashMap { + storage(ArenaCacheSelector<'tcx>) + desc { "looking up lifetime for trait-object types inside `{:?}`", def_id } } query late_bound_vars_map(_: LocalDefId) -> Option<&'tcx FxHashMap>> { diff --git a/compiler/rustc_resolve/src/late/lifetimes.rs b/compiler/rustc_resolve/src/late/lifetimes.rs index 447f4174c10d5..8d2dd3d72bec4 100644 --- a/compiler/rustc_resolve/src/late/lifetimes.rs +++ b/compiler/rustc_resolve/src/late/lifetimes.rs @@ -1,4 +1,3 @@ -// ignore-tidy-filelength //! Name resolution for lifetimes. //! //! Name resolution for lifetimes follows *much* simpler rules than the @@ -12,19 +11,18 @@ use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; use rustc_errors::struct_span_err; use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::def_id::{DefIdMap, LocalDefId}; +use rustc_hir::def_id::LocalDefId; use rustc_hir::intravisit::{self, Visitor}; use rustc_hir::{GenericArg, GenericParam, LifetimeName, Node}; use rustc_hir::{GenericParamKind, HirIdMap}; use rustc_middle::hir::map::Map; use rustc_middle::hir::nested_filter; use rustc_middle::middle::resolve_lifetime::*; -use rustc_middle::ty::{self, GenericParamDefKind, TyCtxt}; +use rustc_middle::ty::{self, TyCtxt}; use rustc_middle::{bug, span_bug}; use rustc_span::def_id::DefId; use rustc_span::symbol::{kw, sym, Ident}; use rustc_span::Span; -use std::borrow::Cow; use std::cell::Cell; use std::fmt; use std::mem::take; @@ -43,10 +41,6 @@ trait RegionExt { fn shifted(self, amount: u32) -> Region; fn shifted_out_to_binder(self, binder: ty::DebruijnIndex) -> Region; - - fn subst<'a, L>(self, params: L, map: &NamedRegionMap) -> Option - where - L: Iterator; } impl RegionExt for Region { @@ -108,17 +102,6 @@ impl RegionExt for Region { _ => self, } } - - fn subst<'a, L>(self, mut params: L, map: &NamedRegionMap) -> Option - where - L: Iterator, - { - if let Region::EarlyBound(index, _) = self { - params.nth(index as usize).and_then(|lifetime| map.defs.get(&lifetime.hir_id).cloned()) - } else { - Some(self) - } - } } /// Maps the id of each lifetime reference to the lifetime decl @@ -152,9 +135,6 @@ pub(crate) struct LifetimeContext<'a, 'tcx> { /// we eventually need lifetimes resolve for trait items. trait_definition_only: bool, - /// Cache for cross-crate per-definition object lifetime defaults. - xcrate_object_lifetime_defaults: DefIdMap>, - /// When encountering an undefined named lifetime, we will suggest introducing it in these /// places. pub(crate) missing_named_lifetime_spots: Vec>, @@ -228,14 +208,6 @@ enum Scope<'a> { s: ScopeRef<'a>, }, - /// Use a specific lifetime (if `Some`) or leave it unset (to be - /// inferred in a function body or potentially error outside one), - /// for the default choice of lifetime in a trait object type. - ObjectLifetimeDefault { - lifetime: Option, - s: ScopeRef<'a>, - }, - /// When we have nested trait refs, we concatenate late bound vars for inner /// trait refs from outer ones. But we also need to include any HRTB /// lifetimes encountered when identifying the trait that an associated type @@ -300,11 +272,6 @@ impl<'a> fmt::Debug for TruncatedScopeDebug<'a> { Scope::Elision { elide, s: _ } => { f.debug_struct("Elision").field("elide", elide).field("s", &"..").finish() } - Scope::ObjectLifetimeDefault { lifetime, s: _ } => f - .debug_struct("ObjectLifetimeDefault") - .field("lifetime", lifetime) - .field("s", &"..") - .finish(), Scope::Supertrait { lifetimes, s: _ } => f .debug_struct("Supertrait") .field("lifetimes", lifetimes) @@ -353,10 +320,6 @@ pub fn provide(providers: &mut ty::query::Providers) { named_region_map: |tcx, id| resolve_lifetimes_for(tcx, id).defs.get(&id), is_late_bound_map, - object_lifetime_defaults: |tcx, id| match tcx.hir().find_by_def_id(id) { - Some(Node::Item(item)) => compute_object_lifetime_defaults(tcx, item), - _ => None, - }, late_bound_vars_map: |tcx, id| resolve_lifetimes_for(tcx, id).late_bound_vars.get(&id), ..*providers @@ -422,7 +385,6 @@ fn do_resolve( map: &mut named_region_map, scope: ROOT_SCOPE, trait_definition_only, - xcrate_object_lifetime_defaults: Default::default(), missing_named_lifetime_spots: vec![], }; visitor.visit_item(item); @@ -523,7 +485,7 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> { break (vec![], BinderScopeType::Normal); } - Scope::Elision { s, .. } | Scope::ObjectLifetimeDefault { s, .. } => { + Scope::Elision { s, .. } => { scope = s; } @@ -809,11 +771,8 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> { span_bug!(ty.span, "object-lifetime-default expected, not implicit",); } LifetimeName::ImplicitObjectLifetimeDefault => { - // If the user does not write *anything*, we - // use the object lifetime defaulting - // rules. So e.g., `Box` becomes - // `Box`. - self.resolve_object_lifetime_default(lifetime) + // ObjectLifetimeDefault will be resolved in a separate pass during + // astconv. } LifetimeName::Underscore => { // If the user writes `'_`, we use the *ordinary* elision @@ -832,11 +791,7 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> { } hir::TyKind::Rptr(ref lifetime_ref, ref mt) => { self.visit_lifetime(lifetime_ref); - let scope = Scope::ObjectLifetimeDefault { - lifetime: self.map.defs.get(&lifetime_ref.hir_id).cloned(), - s: self.scope, - }; - self.with(scope, |this| this.visit_ty(&mt.ty)); + self.visit_ty(&mt.ty); } hir::TyKind::OpaqueDef(item_id, lifetimes) => { // Resolve the lifetimes in the bounds to the lifetime defs in the generics. @@ -1370,155 +1325,18 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> { } } -fn compute_object_lifetime_defaults<'tcx>( - tcx: TyCtxt<'tcx>, - item: &hir::Item<'_>, -) -> Option<&'tcx [ObjectLifetimeDefault]> { - match item.kind { - hir::ItemKind::Struct(_, ref generics) - | hir::ItemKind::Union(_, ref generics) - | hir::ItemKind::Enum(_, ref generics) - | hir::ItemKind::OpaqueTy(hir::OpaqueTy { - ref generics, - origin: hir::OpaqueTyOrigin::TyAlias, - .. - }) - | hir::ItemKind::TyAlias(_, ref generics) - | hir::ItemKind::Trait(_, _, ref generics, ..) => { - let result = object_lifetime_defaults_for_item(tcx, generics); - - // Debugging aid. - let attrs = tcx.hir().attrs(item.hir_id()); - if tcx.sess.contains_name(attrs, sym::rustc_object_lifetime_default) { - let object_lifetime_default_reprs: String = result - .iter() - .map(|set| match *set { - Set1::Empty => "BaseDefault".into(), - Set1::One(Region::Static) => "'static".into(), - Set1::One(Region::EarlyBound(mut i, _)) => generics - .params - .iter() - .find_map(|param| match param.kind { - GenericParamKind::Lifetime { .. } => { - if i == 0 { - return Some(param.name.ident().to_string().into()); - } - i -= 1; - None - } - _ => None, - }) - .unwrap(), - Set1::One(_) => bug!(), - Set1::Many => "Ambiguous".into(), - }) - .collect::>>() - .join(","); - tcx.sess.span_err(item.span, &object_lifetime_default_reprs); - } - - Some(result) - } - _ => None, - } -} - -/// Scan the bounds and where-clauses on parameters to extract bounds -/// of the form `T:'a` so as to determine the `ObjectLifetimeDefault` -/// for each type parameter. -fn object_lifetime_defaults_for_item<'tcx>( - tcx: TyCtxt<'tcx>, - generics: &hir::Generics<'_>, -) -> &'tcx [ObjectLifetimeDefault] { - fn add_bounds(set: &mut Set1, bounds: &[hir::GenericBound<'_>]) { - for bound in bounds { - if let hir::GenericBound::Outlives(ref lifetime) = *bound { - set.insert(lifetime.name.normalize_to_macros_2_0()); - } - } - } - - let process_param = |param: &hir::GenericParam<'_>| match param.kind { - GenericParamKind::Lifetime { .. } => None, - GenericParamKind::Type { .. } => { - let mut set = Set1::Empty; - - let param_def_id = tcx.hir().local_def_id(param.hir_id); - for predicate in generics.predicates { - // Look for `type: ...` where clauses. - let hir::WherePredicate::BoundPredicate(ref data) = *predicate else { continue }; - - // Ignore `for<'a> type: ...` as they can change what - // lifetimes mean (although we could "just" handle it). - if !data.bound_generic_params.is_empty() { - continue; - } - - let res = match data.bounded_ty.kind { - hir::TyKind::Path(hir::QPath::Resolved(None, ref path)) => path.res, - _ => continue, - }; - - if res == Res::Def(DefKind::TyParam, param_def_id.to_def_id()) { - add_bounds(&mut set, &data.bounds); - } - } - - Some(match set { - Set1::Empty => Set1::Empty, - Set1::One(name) => { - if name == hir::LifetimeName::Static { - Set1::One(Region::Static) - } else { - generics - .params - .iter() - .filter_map(|param| match param.kind { - GenericParamKind::Lifetime { .. } => { - let param_def_id = tcx.hir().local_def_id(param.hir_id); - Some(( - param_def_id, - hir::LifetimeName::Param(param_def_id, param.name), - )) - } - _ => None, - }) - .enumerate() - .find(|&(_, (_, lt_name))| lt_name == name) - .map_or(Set1::Many, |(i, (def_id, _))| { - Set1::One(Region::EarlyBound(i as u32, def_id.to_def_id())) - }) - } - } - Set1::Many => Set1::Many, - }) - } - GenericParamKind::Const { .. } => { - // Generic consts don't impose any constraints. - // - // We still store a dummy value here to allow generic parameters - // in an arbitrary order. - Some(Set1::Empty) - } - }; - - tcx.arena.alloc_from_iter(generics.params.iter().filter_map(process_param)) -} - impl<'a, 'tcx> LifetimeContext<'a, 'tcx> { fn with(&mut self, wrap_scope: Scope<'_>, f: F) where F: for<'b> FnOnce(&mut LifetimeContext<'b, 'tcx>), { let LifetimeContext { tcx, map, .. } = self; - let xcrate_object_lifetime_defaults = take(&mut self.xcrate_object_lifetime_defaults); let missing_named_lifetime_spots = take(&mut self.missing_named_lifetime_spots); let mut this = LifetimeContext { tcx: *tcx, map, scope: &wrap_scope, trait_definition_only: self.trait_definition_only, - xcrate_object_lifetime_defaults, missing_named_lifetime_spots, }; let span = tracing::debug_span!("scope", scope = ?TruncatedScopeDebug(&this.scope)); @@ -1526,7 +1344,6 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> { let _enter = span.enter(); f(&mut this); } - self.xcrate_object_lifetime_defaults = this.xcrate_object_lifetime_defaults; self.missing_named_lifetime_spots = this.missing_named_lifetime_spots; } @@ -1638,7 +1455,6 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> { Scope::Binder { s, .. } | Scope::Body { s, .. } | Scope::Elision { s, .. } - | Scope::ObjectLifetimeDefault { s, .. } | Scope::Supertrait { s, .. } | Scope::TraitRefBoundary { s, .. } => scope = s, } @@ -1695,7 +1511,6 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> { } Scope::Elision { s, .. } - | Scope::ObjectLifetimeDefault { s, .. } | Scope::Supertrait { s, .. } | Scope::TraitRefBoundary { s, .. } => { scope = s; @@ -1752,7 +1567,6 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> { Scope::Binder { s, .. } | Scope::Body { s, .. } | Scope::Elision { s, .. } - | Scope::ObjectLifetimeDefault { s, .. } | Scope::Supertrait { s, .. } | Scope::TraitRefBoundary { s, .. } => { scope = s; @@ -1825,166 +1639,29 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> { }; debug!("visit_segment_args: type_def_id={:?}", type_def_id); - - // Compute a vector of defaults, one for each type parameter, - // per the rules given in RFCs 599 and 1156. Example: - // - // ```rust - // struct Foo<'a, T: 'a, U> { } - // ``` - // - // If you have `Foo<'x, dyn Bar, dyn Baz>`, we want to default - // `dyn Bar` to `dyn Bar + 'x` (because of the `T: 'a` bound) - // and `dyn Baz` to `dyn Baz + 'static` (because there is no - // such bound). - // - // Therefore, we would compute `object_lifetime_defaults` to a - // vector like `['x, 'static]`. Note that the vector only - // includes type parameters. - let object_lifetime_defaults = type_def_id.map_or_else(Vec::new, |def_id| { - let in_body = { - let mut scope = self.scope; - loop { - match *scope { - Scope::Root => break false, - - Scope::Body { .. } => break true, - - Scope::Binder { s, .. } - | Scope::Elision { s, .. } - | Scope::ObjectLifetimeDefault { s, .. } - | Scope::Supertrait { s, .. } - | Scope::TraitRefBoundary { s, .. } => { - scope = s; - } - } - } - }; - - let map = &self.map; - let set_to_region = |set: &ObjectLifetimeDefault| match *set { - Set1::Empty => { - if in_body { - None - } else { - Some(Region::Static) - } - } - Set1::One(r) => { - let lifetimes = generic_args.args.iter().filter_map(|arg| match arg { - GenericArg::Lifetime(lt) => Some(lt), - _ => None, - }); - r.subst(lifetimes, map) - } - Set1::Many => None, - }; - if let Some(def_id) = def_id.as_local() { - let id = self.tcx.hir().local_def_id_to_hir_id(def_id); - self.tcx - .object_lifetime_defaults(id.owner) - .unwrap() - .iter() - .map(set_to_region) - .collect() - } else { - let tcx = self.tcx; - self.xcrate_object_lifetime_defaults - .entry(def_id) - .or_insert_with(|| { - tcx.generics_of(def_id) - .params - .iter() - .filter_map(|param| match param.kind { - GenericParamDefKind::Type { object_lifetime_default, .. } => { - Some(object_lifetime_default) - } - GenericParamDefKind::Const { .. } => Some(Set1::Empty), - GenericParamDefKind::Lifetime => None, - }) - .collect() - }) - .iter() - .map(set_to_region) - .collect() - } - }); - - debug!("visit_segment_args: object_lifetime_defaults={:?}", object_lifetime_defaults); - - let mut i = 0; for arg in generic_args.args { match arg { GenericArg::Lifetime(_) => {} - GenericArg::Type(ty) => { - if let Some(<) = object_lifetime_defaults.get(i) { - let scope = Scope::ObjectLifetimeDefault { lifetime: lt, s: self.scope }; - self.with(scope, |this| this.visit_ty(ty)); - } else { - self.visit_ty(ty); - } - i += 1; - } - GenericArg::Const(ct) => { - self.visit_anon_const(&ct.value); - i += 1; - } - GenericArg::Infer(inf) => { - self.visit_id(inf.hir_id); - i += 1; - } + GenericArg::Type(ty) => self.visit_ty(ty), + GenericArg::Const(ct) => self.visit_anon_const(&ct.value), + GenericArg::Infer(inf) => self.visit_id(inf.hir_id), } } - // Hack: when resolving the type `XX` in binding like `dyn - // Foo<'b, Item = XX>`, the current object-lifetime default - // would be to examine the trait `Foo` to check whether it has - // a lifetime bound declared on `Item`. e.g., if `Foo` is - // declared like so, then the default object lifetime bound in - // `XX` should be `'b`: - // - // ```rust - // trait Foo<'a> { - // type Item: 'a; - // } - // ``` - // - // but if we just have `type Item;`, then it would be - // `'static`. However, we don't get all of this logic correct. - // - // Instead, we do something hacky: if there are no lifetime parameters - // to the trait, then we simply use a default object lifetime - // bound of `'static`, because there is no other possibility. On the other hand, - // if there ARE lifetime parameters, then we require the user to give an - // explicit bound for now. - // - // This is intended to leave room for us to implement the - // correct behavior in the future. - let has_lifetime_parameter = - generic_args.args.iter().any(|arg| matches!(arg, GenericArg::Lifetime(_))); - // Resolve lifetimes found in the bindings, so either in the type `XX` in `Item = XX` or // in the trait ref `YY<...>` in `Item: YY<...>`. for binding in generic_args.bindings { - let scope = Scope::ObjectLifetimeDefault { - lifetime: if has_lifetime_parameter { None } else { Some(Region::Static) }, - s: self.scope, - }; if let Some(type_def_id) = type_def_id { let lifetimes = LifetimeContext::supertrait_hrtb_lifetimes( self.tcx, type_def_id, binding.ident, ); - self.with(scope, |this| { - let scope = Scope::Supertrait { - lifetimes: lifetimes.unwrap_or_default(), - s: this.scope, - }; - this.with(scope, |this| this.visit_assoc_type_binding(binding)); - }); - } else { + let scope = + Scope::Supertrait { lifetimes: lifetimes.unwrap_or_default(), s: self.scope }; self.with(scope, |this| this.visit_assoc_type_binding(binding)); + } else { + self.visit_assoc_type_binding(binding); } } } @@ -2065,8 +1742,7 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> { Scope::Binder { hir_id, allow_late_bound: true, .. } => { break *hir_id; } - Scope::ObjectLifetimeDefault { ref s, .. } - | Scope::Elision { ref s, .. } + Scope::Elision { ref s, .. } | Scope::Supertrait { ref s, .. } | Scope::TraitRefBoundary { ref s, .. } => { scope = *s; @@ -2448,8 +2124,7 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> { in_scope_lifetimes.extend(lifetimes.keys().copied()); scope = s; } - Scope::ObjectLifetimeDefault { ref s, .. } - | Scope::Elision { ref s, .. } + Scope::Elision { ref s, .. } | Scope::TraitRefBoundary { ref s, .. } => { scope = s; } @@ -2461,9 +2136,7 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> { Scope::Elision { elide: Elide::Forbid, .. } => break None, - Scope::ObjectLifetimeDefault { s, .. } - | Scope::Supertrait { s, .. } - | Scope::TraitRefBoundary { s, .. } => { + Scope::Supertrait { s, .. } | Scope::TraitRefBoundary { s, .. } => { scope = s; } } @@ -2489,34 +2162,6 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> { err.emit(); } - fn resolve_object_lifetime_default(&mut self, lifetime_ref: &'tcx hir::Lifetime) { - debug!("resolve_object_lifetime_default(lifetime_ref={:?})", lifetime_ref); - let mut late_depth = 0; - let mut scope = self.scope; - let lifetime = loop { - match *scope { - Scope::Binder { s, scope_type, .. } => { - match scope_type { - BinderScopeType::Normal => late_depth += 1, - BinderScopeType::Concatenating => {} - } - scope = s; - } - - Scope::Root | Scope::Elision { .. } => break Region::Static, - - Scope::Body { .. } | Scope::ObjectLifetimeDefault { lifetime: None, .. } => return, - - Scope::ObjectLifetimeDefault { lifetime: Some(l), .. } => break l, - - Scope::Supertrait { s, .. } | Scope::TraitRefBoundary { s, .. } => { - scope = s; - } - } - }; - self.insert_lifetime(lifetime_ref, lifetime.shifted(late_depth)); - } - #[tracing::instrument(level = "debug", skip(self))] fn insert_lifetime(&mut self, lifetime_ref: &'tcx hir::Lifetime, def: Region) { debug!( diff --git a/compiler/rustc_typeck/src/astconv/mod.rs b/compiler/rustc_typeck/src/astconv/mod.rs index 32bbfd7e33232..859ea4476c273 100644 --- a/compiler/rustc_typeck/src/astconv/mod.rs +++ b/compiler/rustc_typeck/src/astconv/mod.rs @@ -204,13 +204,45 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { lifetime: &hir::Lifetime, def: Option<&ty::GenericParamDef>, ) -> ty::Region<'tcx> { + let tcx = self.tcx(); + if let hir::LifetimeName::ImplicitObjectLifetimeDefault = lifetime.name { + panic!( + "ImplicitObjectLifetimeDefault should be handled separately from ast_region_to_region." + ); + } + + let r = if let Some(rl) = tcx.named_region(lifetime.hir_id) { + self.ast_region_to_region_inner(rl) + } else { + self.re_infer(def, lifetime.span).unwrap_or_else(|| { + debug!(?lifetime, "unelided lifetime in signature"); + + // This indicates an illegal lifetime + // elision. `resolve_lifetime` should have + // reported an error in this case -- but if + // not, let's error out. + tcx.sess.delay_span_bug(lifetime.span, "unelided lifetime in signature"); + + // Supply some dummy value. We don't have an + // `re_error`, annoyingly, so use `'static`. + tcx.lifetimes.re_static + }) + }; + + debug!("ast_region_to_region(lifetime={:?}) yields {:?}", lifetime, r); + + r + } + + #[tracing::instrument(level = "debug", skip(self))] + fn ast_region_to_region_inner(&self, lifetime: rl::Region) -> ty::Region<'tcx> { let tcx = self.tcx(); let lifetime_name = |def_id| tcx.hir().name(tcx.hir().local_def_id_to_hir_id(def_id)); - let r = match tcx.named_region(lifetime.hir_id) { - Some(rl::Region::Static) => tcx.lifetimes.re_static, + let r = match lifetime { + rl::Region::Static => tcx.lifetimes.re_static, - Some(rl::Region::LateBound(debruijn, index, def_id)) => { + rl::Region::LateBound(debruijn, index, def_id) => { let name = lifetime_name(def_id.expect_local()); let br = ty::BoundRegion { var: ty::BoundVar::from_u32(index), @@ -219,7 +251,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { tcx.mk_region(ty::ReLateBound(debruijn, br)) } - Some(rl::Region::LateBoundAnon(debruijn, index, anon_index)) => { + rl::Region::LateBoundAnon(debruijn, index, anon_index) => { let br = ty::BoundRegion { var: ty::BoundVar::from_u32(index), kind: ty::BrAnon(anon_index), @@ -227,12 +259,12 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { tcx.mk_region(ty::ReLateBound(debruijn, br)) } - Some(rl::Region::EarlyBound(index, id)) => { + rl::Region::EarlyBound(index, id) => { let name = lifetime_name(id.expect_local()); tcx.mk_region(ty::ReEarlyBound(ty::EarlyBoundRegion { def_id: id, index, name })) } - Some(rl::Region::Free(scope, id)) => { + rl::Region::Free(scope, id) => { let name = lifetime_name(id.expect_local()); tcx.mk_region(ty::ReFree(ty::FreeRegion { scope, @@ -241,26 +273,8 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { // (*) -- not late-bound, won't change } - - None => { - self.re_infer(def, lifetime.span).unwrap_or_else(|| { - debug!(?lifetime, "unelided lifetime in signature"); - - // This indicates an illegal lifetime - // elision. `resolve_lifetime` should have - // reported an error in this case -- but if - // not, let's error out. - tcx.sess.delay_span_bug(lifetime.span, "unelided lifetime in signature"); - - // Supply some dummy value. We don't have an - // `re_error`, annoyingly, so use `'static`. - tcx.lifetimes.re_static - }) - } }; - debug!("ast_region_to_region(lifetime={:?}) yields {:?}", lifetime, r); - r } @@ -1541,8 +1555,12 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { self.ast_region_to_region(lifetime, None) } else { self.compute_object_lifetime_bound(span, existential_predicates).unwrap_or_else(|| { - if tcx.named_region(lifetime.hir_id).is_some() { - self.ast_region_to_region(lifetime, None) + if let Some(rl) = tcx.named_region(lifetime.hir_id) { + self.ast_region_to_region_inner(rl) + } else if let Some(&rl) = + tcx.object_lifetime_map(lifetime.hir_id.owner).get(&lifetime.hir_id.local_id) + { + self.ast_region_to_region_inner(rl) } else { self.re_infer(None, span).unwrap_or_else(|| { let mut err = struct_span_err!( diff --git a/compiler/rustc_typeck/src/collect.rs b/compiler/rustc_typeck/src/collect.rs index 92910a3066d12..6140837010156 100644 --- a/compiler/rustc_typeck/src/collect.rs +++ b/compiler/rustc_typeck/src/collect.rs @@ -34,6 +34,7 @@ use rustc_hir::weak_lang_items; use rustc_hir::{GenericParamKind, HirId, Node}; use rustc_middle::hir::nested_filter; use rustc_middle::middle::codegen_fn_attrs::{CodegenFnAttrFlags, CodegenFnAttrs}; +use rustc_middle::middle::resolve_lifetime::ObjectLifetimeDefault; use rustc_middle::mir::mono::Linkage; use rustc_middle::ty::query::Providers; use rustc_middle::ty::subst::InternalSubsts; @@ -50,6 +51,7 @@ use rustc_trait_selection::traits::error_reporting::suggestions::NextTypeParamNa use std::iter; mod item_bounds; +mod object_lifetime_defaults; mod type_of; #[derive(Debug)] @@ -68,6 +70,8 @@ pub fn provide(providers: &mut Providers) { type_of: type_of::type_of, item_bounds: item_bounds::item_bounds, explicit_item_bounds: item_bounds::explicit_item_bounds, + object_lifetime_defaults: object_lifetime_defaults::object_lifetime_defaults, + object_lifetime_map: object_lifetime_defaults::object_lifetime_map, generics_of, predicates_of, predicates_defined_on, @@ -1304,6 +1308,7 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: DefId) -> ty::TraitDef { ) } +#[tracing::instrument(level = "debug", skip(tcx))] fn has_late_bound_regions<'tcx>(tcx: TyCtxt<'tcx>, node: Node<'tcx>) -> Option { struct LateBoundRegionsDetector<'tcx> { tcx: TyCtxt<'tcx>, @@ -1343,8 +1348,16 @@ fn has_late_bound_regions<'tcx>(tcx: TyCtxt<'tcx>, node: Node<'tcx>) -> Option {} Some( rl::Region::LateBound(debruijn, _, _) @@ -1380,6 +1393,7 @@ fn has_late_bound_regions<'tcx>(tcx: TyCtxt<'tcx>, node: Node<'tcx>) -> Option, def_id: DefId) -> ty::Generics { pure_wrt_drop: false, kind: ty::GenericParamDefKind::Type { has_default: false, - object_lifetime_default: rl::Set1::Empty, + object_lifetime_default: ObjectLifetimeDefault::Empty, synthetic: false, }, }); @@ -1679,7 +1693,7 @@ fn generics_of(tcx: TyCtxt<'_>, def_id: DefId) -> ty::Generics { has_default: default.is_some(), object_lifetime_default: object_lifetime_defaults .as_ref() - .map_or(rl::Set1::Empty, |o| o[i]), + .map_or(ObjectLifetimeDefault::Empty, |o| o[i]), synthetic, }; @@ -1731,7 +1745,7 @@ fn generics_of(tcx: TyCtxt<'_>, def_id: DefId) -> ty::Generics { pure_wrt_drop: false, kind: ty::GenericParamDefKind::Type { has_default: false, - object_lifetime_default: rl::Set1::Empty, + object_lifetime_default: ObjectLifetimeDefault::Empty, synthetic: false, }, })); @@ -1748,7 +1762,7 @@ fn generics_of(tcx: TyCtxt<'_>, def_id: DefId) -> ty::Generics { pure_wrt_drop: false, kind: ty::GenericParamDefKind::Type { has_default: false, - object_lifetime_default: rl::Set1::Empty, + object_lifetime_default: ObjectLifetimeDefault::Empty, synthetic: false, }, }); diff --git a/compiler/rustc_typeck/src/collect/object_lifetime_defaults.rs b/compiler/rustc_typeck/src/collect/object_lifetime_defaults.rs new file mode 100644 index 0000000000000..8e33d2efabe49 --- /dev/null +++ b/compiler/rustc_typeck/src/collect/object_lifetime_defaults.rs @@ -0,0 +1,651 @@ +use rustc_data_structures::fx::FxHashMap; +use rustc_hir as hir; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::def_id::LocalDefId; +use rustc_hir::hir_id::ItemLocalId; +use rustc_hir::intravisit::{self, Visitor}; +use rustc_hir::{GenericArg, GenericParamKind, LifetimeName, Node}; +use rustc_middle::bug; +use rustc_middle::hir::nested_filter; +use rustc_middle::middle::resolve_lifetime::*; +use rustc_middle::ty::{GenericParamDefKind, TyCtxt}; +use rustc_span::def_id::DefId; +use rustc_span::symbol::sym; +use std::borrow::Cow; + +use tracing::debug; + +pub(super) fn object_lifetime_defaults( + tcx: TyCtxt<'_>, + def_id: LocalDefId, +) -> Option<&[ObjectLifetimeDefault]> { + let Node::Item(item) = tcx.hir().get_by_def_id(def_id) else { return None }; + match item.kind { + hir::ItemKind::Struct(_, ref generics) + | hir::ItemKind::Union(_, ref generics) + | hir::ItemKind::Enum(_, ref generics) + | hir::ItemKind::OpaqueTy(hir::OpaqueTy { + ref generics, + origin: hir::OpaqueTyOrigin::TyAlias, + .. + }) + | hir::ItemKind::TyAlias(_, ref generics) + | hir::ItemKind::Trait(_, _, ref generics, ..) => { + let result = object_lifetime_defaults_for_item(tcx, generics); + debug!(?result); + + // Debugging aid. + let attrs = tcx.hir().attrs(item.hir_id()); + if tcx.sess.contains_name(attrs, sym::rustc_object_lifetime_default) { + let object_lifetime_default_reprs: String = result + .iter() + .map(|set| match *set { + ObjectLifetimeDefault::Empty => "BaseDefault".into(), + ObjectLifetimeDefault::Static => "'static".into(), + ObjectLifetimeDefault::Param(def_id) => { + let def_id = def_id.expect_local(); + generics + .params + .iter() + .find(|param| tcx.hir().local_def_id(param.hir_id) == def_id) + .map(|param| param.name.ident().to_string().into()) + .unwrap() + } + ObjectLifetimeDefault::Ambiguous => "Ambiguous".into(), + }) + .collect::>>() + .join(","); + tcx.sess.span_err(item.span, &object_lifetime_default_reprs); + } + + Some(result) + } + _ => None, + } +} + +/// Scan the bounds and where-clauses on parameters to extract bounds +/// of the form `T:'a` so as to determine the `ObjectLifetimeDefault` +/// for each type parameter. +fn object_lifetime_defaults_for_item<'tcx>( + tcx: TyCtxt<'tcx>, + generics: &hir::Generics<'_>, +) -> &'tcx [ObjectLifetimeDefault] { + fn add_bounds(set: &mut Set1, bounds: &[hir::GenericBound<'_>]) { + for bound in bounds { + if let hir::GenericBound::Outlives(ref lifetime) = *bound { + set.insert(lifetime.name.normalize_to_macros_2_0()); + } + } + } + + let process_param = |param: &hir::GenericParam<'_>| match param.kind { + GenericParamKind::Lifetime { .. } => None, + GenericParamKind::Type { .. } => { + let mut set = Set1::Empty; + + let param_def_id = tcx.hir().local_def_id(param.hir_id); + for predicate in generics.predicates { + // Look for `type: ...` where clauses. + let hir::WherePredicate::BoundPredicate(ref data) = *predicate else { continue }; + + // Ignore `for<'a> type: ...` as they can change what + // lifetimes mean (although we could "just" handle it). + if !data.bound_generic_params.is_empty() { + continue; + } + + let res = match data.bounded_ty.kind { + hir::TyKind::Path(hir::QPath::Resolved(None, ref path)) => path.res, + _ => continue, + }; + + if res == Res::Def(DefKind::TyParam, param_def_id.to_def_id()) { + add_bounds(&mut set, &data.bounds); + } + } + + Some(match set { + Set1::Empty => ObjectLifetimeDefault::Empty, + Set1::One(hir::LifetimeName::Static) => ObjectLifetimeDefault::Static, + Set1::One(hir::LifetimeName::Param(def_id, _)) => { + ObjectLifetimeDefault::Param(def_id.to_def_id()) + } + Set1::One(_) | Set1::Many => ObjectLifetimeDefault::Ambiguous, + }) + } + GenericParamKind::Const { .. } => { + // Generic consts don't impose any constraints. + // + // We still store a dummy value here to allow generic parameters + // in an arbitrary order. + Some(ObjectLifetimeDefault::Empty) + } + }; + + tcx.arena.alloc_from_iter(generics.params.iter().filter_map(process_param)) +} + +pub(super) fn object_lifetime_map( + tcx: TyCtxt<'_>, + def_id: LocalDefId, +) -> FxHashMap { + let mut named_region_map = Default::default(); + let mut visitor = LifetimeContext { tcx, defs: &mut named_region_map, scope: ROOT_SCOPE }; + let node = tcx.hir().get_by_def_id(def_id); + match node { + Node::Item(item) => visitor.visit_item(item), + Node::TraitItem(item) => visitor.visit_trait_item(item), + Node::ImplItem(item) => visitor.visit_impl_item(item), + Node::ForeignItem(item) => visitor.visit_foreign_item(item), + _ => bug!(), + } + + named_region_map +} + +trait RegionExt { + fn shifted(self, amount: u32) -> Region; +} + +impl RegionExt for Region { + fn shifted(self, amount: u32) -> Region { + match self { + Region::LateBound(debruijn, idx, id) => { + Region::LateBound(debruijn.shifted_in(amount), idx, id) + } + _ => self, + } + } +} + +struct LifetimeContext<'a, 'tcx> { + tcx: TyCtxt<'tcx>, + defs: &'a mut FxHashMap, + scope: ScopeRef<'a>, +} + +#[derive(Debug)] +enum Scope<'a> { + /// Declares lifetimes, and each can be early-bound or late-bound. + /// The `DebruijnIndex` of late-bound lifetimes starts at `1` and + /// it should be shifted by the number of `Binder`s in between the + /// declaration `Binder` and the location it's referenced from. + Binder { + s: ScopeRef<'a>, + }, + + /// Lifetimes introduced by a fn are scoped to the call-site for that fn, + /// if this is a fn body, otherwise the original definitions are used. + /// Unspecified lifetimes are inferred, unless an elision scope is nested, + /// e.g., `(&T, fn(&T) -> &T);` becomes `(&'_ T, for<'a> fn(&'a T) -> &'a T)`. + Body, + + /// A scope which either determines unspecified lifetimes to static. + Static { + s: ScopeRef<'a>, + }, + + /// Use a specific lifetime (if `Some`) or leave it unset (to be + /// inferred in a function body or potentially error outside one), + /// for the default choice of lifetime in a trait object type. + ObjectLifetimeDefault { + //FIXME(cjgillot) This should use a `ty::Region`. + lifetime: Option, + s: ScopeRef<'a>, + }, + + Root, +} + +type ScopeRef<'a> = &'a Scope<'a>; + +const ROOT_SCOPE: ScopeRef<'static> = &Scope::Root; + +impl<'a, 'tcx> LifetimeContext<'a, 'tcx> { + /// Returns the binders in scope and the type of `Binder` that should be created for a poly trait ref. + fn poly_trait_ref_needs_binder(&mut self) -> bool { + let mut scope = self.scope; + loop { + match scope { + // Nested poly trait refs have the binders concatenated + Scope::Binder { .. } => break false, + Scope::Body | Scope::Root => break true, + Scope::Static { s, .. } | Scope::ObjectLifetimeDefault { s, .. } => scope = s, + } + } + } +} +impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> { + type NestedFilter = nested_filter::OnlyBodies; + + fn nested_visit_map(&mut self) -> Self::Map { + self.tcx.hir() + } + + fn visit_nested_body(&mut self, body: hir::BodyId) { + let body = self.tcx.hir().body(body); + self.with(Scope::Body, |this| { + this.visit_body(body); + }); + } + + fn visit_expr(&mut self, e: &'tcx hir::Expr<'tcx>) { + if let hir::ExprKind::Closure(..) = e.kind { + let scope = Scope::Binder { s: self.scope }; + self.with(scope, |this| { + // a closure has no bounds, so everything + // contained within is scoped within its binder. + intravisit::walk_expr(this, e) + }); + } else { + intravisit::walk_expr(self, e) + } + } + + fn visit_item(&mut self, item: &'tcx hir::Item<'tcx>) { + match item.kind { + hir::ItemKind::ExternCrate(_) + | hir::ItemKind::Use(..) + | hir::ItemKind::Macro(..) + | hir::ItemKind::Mod(..) + | hir::ItemKind::ForeignMod { .. } + | hir::ItemKind::GlobalAsm(..) => { + // These sorts of items have no lifetime parameters at all. + intravisit::walk_item(self, item); + } + hir::ItemKind::Static(..) | hir::ItemKind::Const(..) => { + // No lifetime parameters, but implied 'static. + self.with(Scope::Static { s: self.scope }, |this| { + intravisit::walk_item(this, item) + }); + } + hir::ItemKind::OpaqueTy(..) + | hir::ItemKind::TyAlias(..) + | hir::ItemKind::Enum(..) + | hir::ItemKind::Struct(..) + | hir::ItemKind::Union(..) + | hir::ItemKind::Trait(..) + | hir::ItemKind::TraitAlias(..) + | hir::ItemKind::Fn(..) + | hir::ItemKind::Impl(..) => { + // These kinds of items have only early-bound lifetime parameters. + let scope = Scope::Binder { s: ROOT_SCOPE }; + self.with(scope, |this| intravisit::walk_item(this, item)); + } + } + } + + fn visit_foreign_item(&mut self, item: &'tcx hir::ForeignItem<'tcx>) { + match item.kind { + hir::ForeignItemKind::Fn(..) => { + let scope = Scope::Binder { s: self.scope }; + self.with(scope, |this| intravisit::walk_foreign_item(this, item)) + } + hir::ForeignItemKind::Static(..) | hir::ForeignItemKind::Type => { + intravisit::walk_foreign_item(self, item) + } + } + } + + #[tracing::instrument(level = "debug", skip(self))] + fn visit_ty(&mut self, ty: &'tcx hir::Ty<'tcx>) { + match ty.kind { + hir::TyKind::BareFn(..) => { + let scope = Scope::Binder { s: self.scope }; + self.with(scope, |this| { + // a bare fn has no bounds, so everything + // contained within is scoped within its binder. + intravisit::walk_ty(this, ty); + }); + } + hir::TyKind::TraitObject(bounds, ref lifetime, _) => { + debug!(?bounds, ?lifetime, "TraitObject"); + for bound in bounds { + self.visit_poly_trait_ref(bound, hir::TraitBoundModifier::None); + } + if let LifetimeName::ImplicitObjectLifetimeDefault = lifetime.name { + // If the user does not write *anything*, we + // use the object lifetime defaulting + // rules. So e.g., `Box` becomes + // `Box`. + self.resolve_object_lifetime_default(lifetime) + } + } + hir::TyKind::Rptr(ref lifetime_ref, ref mt) => { + let lifetime = self.tcx.named_region(lifetime_ref.hir_id); + let scope = Scope::ObjectLifetimeDefault { lifetime, s: self.scope }; + self.with(scope, |this| this.visit_ty(&mt.ty)); + } + _ => intravisit::walk_ty(self, ty), + } + } + + fn visit_trait_item(&mut self, trait_item: &'tcx hir::TraitItem<'tcx>) { + use self::hir::TraitItemKind::*; + match trait_item.kind { + Fn(..) | Type(..) => { + let scope = Scope::Binder { s: self.scope }; + self.with(scope, |this| intravisit::walk_trait_item(this, trait_item)); + } + // Only methods and types support generics. + Const(_, _) => intravisit::walk_trait_item(self, trait_item), + } + } + + fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) { + use self::hir::ImplItemKind::*; + match impl_item.kind { + Fn(..) | TyAlias(..) => { + let scope = Scope::Binder { s: self.scope }; + self.with(scope, |this| intravisit::walk_impl_item(this, impl_item)); + } + // Only methods and types support generics. + Const(..) => intravisit::walk_impl_item(self, impl_item), + } + } + + fn visit_fn_decl(&mut self, fd: &'tcx hir::FnDecl<'tcx>) { + let output = match fd.output { + hir::FnRetTy::DefaultReturn(_) => None, + hir::FnRetTy::Return(ref output) => { + let parent = self.tcx.hir().get_parent_node(output.hir_id); + let static_for_output = match self.tcx.hir().get(parent) { + // `fn` definitions and methods. + Node::Item(_) | Node::TraitItem(_) | Node::ImplItem(_) => true, + + // Foreign functions, `fn(...) -> R` and `Trait(...) -> R` (both types and bounds). + Node::ForeignItem(_) | Node::Ty(_) | Node::TraitRef(_) => true, + + Node::TypeBinding(_) => matches!( + self.tcx.hir().get(self.tcx.hir().get_parent_node(parent)), + Node::TraitRef(_) + ), + + // Everything else (only closures?) doesn't + // actually enjoy elision in return types. + _ => false, + }; + Some((output, static_for_output)) + } + }; + + // Lifetime elision prescribes a `'static` default lifetime. + let scope = Scope::ObjectLifetimeDefault { lifetime: Some(Region::Static), s: self.scope }; + self.with(scope, |this| { + for ty in fd.inputs { + this.visit_ty(ty) + } + + if let Some((output, static_for_output)) = output && static_for_output { + this.visit_ty(output) + } + }); + + if let Some((output, static_for_output)) = output && !static_for_output { + self.visit_ty(output) + } + } + + fn visit_path(&mut self, path: &'tcx hir::Path<'tcx>, _: hir::HirId) { + for (i, segment) in path.segments.iter().enumerate() { + let depth = path.segments.len() - i - 1; + if let Some(ref args) = segment.args { + self.visit_segment_args(path.res, depth, args); + } + } + } + + fn visit_where_predicate(&mut self, predicate: &'tcx hir::WherePredicate<'tcx>) { + match predicate { + &hir::WherePredicate::BoundPredicate(..) => { + // Even if there are no lifetimes defined here, we still wrap it in a binder + // scope. If there happens to be a nested poly trait ref (an error), that + // will be `Concatenating` anyways, so we don't have to worry about the depth + // being wrong. + let scope = Scope::Binder { s: self.scope }; + self.with(scope, |this| intravisit::walk_where_predicate(this, predicate)) + } + &hir::WherePredicate::RegionPredicate(..) | &hir::WherePredicate::EqPredicate(..) => { + intravisit::walk_where_predicate(self, predicate) + } + } + } + + fn visit_param_bound(&mut self, bound: &'tcx hir::GenericBound<'tcx>) { + match bound { + // FIXME(jackh726): This is pretty weird. `LangItemTrait` doesn't go + // through the regular poly trait ref code, so we don't get another + // chance to introduce a binder. For now, I'm keeping the existing logic + // of "if there isn't a Binder scope above us, add one", but I + // imagine there's a better way to go about this. + hir::GenericBound::LangItemTrait(..) if self.poly_trait_ref_needs_binder() => { + let scope = Scope::Binder { s: self.scope }; + self.with(scope, |this| intravisit::walk_param_bound(this, bound)); + } + _ => intravisit::walk_param_bound(self, bound), + } + } + + fn visit_poly_trait_ref( + &mut self, + trait_ref: &'tcx hir::PolyTraitRef<'tcx>, + modifier: hir::TraitBoundModifier, + ) { + debug!("visit_poly_trait_ref(trait_ref={:?})", trait_ref); + + if self.poly_trait_ref_needs_binder() { + // Always introduce a scope here, even if this is in a where clause and + // we introduced the binders around the bounded Ty. In that case, we + // just reuse the concatenation functionality also present in nested trait + // refs. + let scope = Scope::Binder { s: self.scope }; + self.with(scope, |this| intravisit::walk_poly_trait_ref(this, trait_ref, modifier)); + } else { + intravisit::walk_poly_trait_ref(self, trait_ref, modifier); + } + } +} + +impl<'a, 'tcx> LifetimeContext<'a, 'tcx> { + fn with(&mut self, wrap_scope: Scope<'_>, f: F) + where + F: for<'b> FnOnce(&mut LifetimeContext<'b, 'tcx>), + { + let LifetimeContext { tcx, defs, .. } = self; + let mut this = LifetimeContext { tcx: *tcx, defs, scope: &wrap_scope }; + f(&mut this); + } + + #[tracing::instrument(level = "debug", skip(self))] + fn visit_segment_args( + &mut self, + res: Res, + depth: usize, + generic_args: &'tcx hir::GenericArgs<'tcx>, + ) { + if generic_args.parenthesized { + // Lifetime elision rules require us to use a `'static` default lifetime. + let scope = + Scope::ObjectLifetimeDefault { lifetime: Some(Region::Static), s: self.scope }; + self.with(scope, |this| { + for input in generic_args.inputs() { + this.visit_ty(input); + } + + let output = generic_args.bindings[0].ty(); + this.visit_ty(output); + }); + return; + } + + // Figure out if this is a type/trait segment, + // which requires object lifetime defaults. + let parent_def_id = |this: &mut Self, def_id: DefId| { + let def_key = this.tcx.def_key(def_id); + DefId { krate: def_id.krate, index: def_key.parent.expect("missing parent") } + }; + let type_def_id = match res { + Res::Def(DefKind::AssocTy, def_id) if depth == 1 => Some(parent_def_id(self, def_id)), + Res::Def(DefKind::Variant, def_id) if depth == 0 => Some(parent_def_id(self, def_id)), + Res::Def( + DefKind::Struct + | DefKind::Union + | DefKind::Enum + | DefKind::TyAlias + | DefKind::Trait, + def_id, + ) if depth == 0 => Some(def_id), + _ => None, + }; + debug!(?type_def_id); + + // Compute a vector of defaults, one for each type parameter, + // per the rules given in RFCs 599 and 1156. Example: + // + // ```rust + // struct Foo<'a, T: 'a, U> { } + // ``` + // + // If you have `Foo<'x, dyn Bar, dyn Baz>`, we want to default + // `dyn Bar` to `dyn Bar + 'x` (because of the `T: 'a` bound) + // and `dyn Baz` to `dyn Baz + 'static` (because there is no + // such bound). + // + // Therefore, we would compute `object_lifetime_defaults` to a + // vector like `['x, 'static]`. Note that the vector only + // includes type parameters. + let object_lifetime_defaults = type_def_id.map_or_else(Vec::new, |def_id| { + let in_body = { + let mut scope = self.scope; + loop { + match *scope { + Scope::Root => break false, + Scope::Body => break true, + Scope::Binder { s, .. } + | Scope::Static { s, .. } + | Scope::ObjectLifetimeDefault { s, .. } => { + scope = s; + } + } + } + }; + let generics = self.tcx.generics_of(def_id); + let set_to_region = |set: ObjectLifetimeDefault| match set { + ObjectLifetimeDefault::Empty => { + if in_body { + None + } else { + Some(Region::Static) + } + } + ObjectLifetimeDefault::Static => Some(Region::Static), + ObjectLifetimeDefault::Param(def_id) => { + let index = generics.param_def_id_to_index[&def_id]; + generic_args.args.get(index as usize).and_then(|arg| match arg { + GenericArg::Lifetime(lt) => self.tcx.named_region(lt.hir_id), + _ => None, + }) + } + ObjectLifetimeDefault::Ambiguous => None, + }; + generics + .params + .iter() + .filter_map(|param| match param.kind { + GenericParamDefKind::Type { object_lifetime_default, .. } => { + Some(object_lifetime_default) + } + GenericParamDefKind::Const { .. } => Some(ObjectLifetimeDefault::Empty), + GenericParamDefKind::Lifetime => None, + }) + .map(set_to_region) + .collect() + }); + debug!(?object_lifetime_defaults); + + let mut i = 0; + for arg in generic_args.args { + match arg { + GenericArg::Lifetime(_) => {} + GenericArg::Type(ty) => { + if let Some(<) = object_lifetime_defaults.get(i) { + let scope = Scope::ObjectLifetimeDefault { lifetime: lt, s: self.scope }; + self.with(scope, |this| this.visit_ty(ty)); + } else { + self.visit_ty(ty); + } + i += 1; + } + GenericArg::Const(ct) => { + self.visit_anon_const(&ct.value); + i += 1; + } + GenericArg::Infer(inf) => { + self.visit_id(inf.hir_id); + i += 1; + } + } + } + + // Hack: when resolving the type `XX` in binding like `dyn + // Foo<'b, Item = XX>`, the current object-lifetime default + // would be to examine the trait `Foo` to check whether it has + // a lifetime bound declared on `Item`. e.g., if `Foo` is + // declared like so, then the default object lifetime bound in + // `XX` should be `'b`: + // + // ```rust + // trait Foo<'a> { + // type Item: 'a; + // } + // ``` + // + // but if we just have `type Item;`, then it would be + // `'static`. However, we don't get all of this logic correct. + // + // Instead, we do something hacky: if there are no lifetime parameters + // to the trait, then we simply use a default object lifetime + // bound of `'static`, because there is no other possibility. On the other hand, + // if there ARE lifetime parameters, then we require the user to give an + // explicit bound for now. + // + // This is intended to leave room for us to implement the + // correct behavior in the future. + let has_lifetime_parameter = + generic_args.args.iter().any(|arg| matches!(arg, GenericArg::Lifetime(_))); + + // Resolve lifetimes found in the bindings, so either in the type `XX` in `Item = XX` or + // in the trait ref `YY<...>` in `Item: YY<...>`. + for binding in generic_args.bindings { + let scope = Scope::ObjectLifetimeDefault { + lifetime: if has_lifetime_parameter { None } else { Some(Region::Static) }, + s: self.scope, + }; + self.with(scope, |this| this.visit_assoc_type_binding(binding)); + } + } + + fn resolve_object_lifetime_default(&mut self, lifetime_ref: &'tcx hir::Lifetime) { + debug!("resolve_object_lifetime_default(lifetime_ref={:?})", lifetime_ref); + let mut late_depth = 0; + let mut scope = self.scope; + let lifetime = loop { + match *scope { + Scope::Binder { s, .. } => { + late_depth += 1; + scope = s; + } + + Scope::Root | Scope::Static { .. } => break Region::Static, + + Scope::Body | Scope::ObjectLifetimeDefault { lifetime: None, .. } => return, + + Scope::ObjectLifetimeDefault { lifetime: Some(l), .. } => break l, + } + }; + let def = lifetime.shifted(late_depth); + debug!(?def); + self.defs.insert(lifetime_ref.hir_id.local_id, def); + } +} diff --git a/src/test/ui/object-lifetime/object-lifetime-default-default-to-static.rs b/src/test/ui/object-lifetime/object-lifetime-default-default-to-static.rs index 467767ae59d76..2fc5140211834 100644 --- a/src/test/ui/object-lifetime/object-lifetime-default-default-to-static.rs +++ b/src/test/ui/object-lifetime/object-lifetime-default-default-to-static.rs @@ -31,5 +31,34 @@ fn d(t: Box, mut ss: SomeStruct) { ss.u = t; } +// Check that closures obey the same rules. +fn e() { + let _ = |t: Box, mut ss: SomeStruct| { + ss.t = t; + }; + let _ = |t: Box, mut ss: SomeStruct| { + ss.t = t; + }; + let _ = |t: Box, mut ss: SomeStruct| { + ss.u = t; + }; + let _ = |t: Box, mut ss: SomeStruct| { + ss.u = t; + }; +} + +// Check that bare fns and Fn trait obey the same rules. +fn f() { + let _: fn(Box, SomeStruct) = a; + let _: fn(Box, SomeStruct) = a; + let _: &dyn Fn(Box, SomeStruct) = &a; + let _: &dyn Fn(Box, SomeStruct) = &a; +} + +// But closure return type does not need to. +fn g<'a>(t: Box) { + let _ = || -> Box { t }; +} + fn main() { }