Don't use kw::Empty
in hir::Lifetime::ident
.
`hir::Lifetime::ident` currently sometimes uses `kw::Empty` for elided lifetimes and sometimes uses `kw::UnderscoreLifetime`, and the distinction is used when creating some error suggestions, e.g. in `Lifetime::suggestion` and `ImplicitLifetimeFinder::visit_ty`. I found this *really* confusing, and it took me a while to understand what was going on. This commit replaces all uses of `kw::Empty` in `hir::Lifetime::ident` with `kw::UnderscoreLifetime`. It adds a new field `hir::Lifetime::is_path_anon` that mostly replaces the old empty/underscore distinction and makes things much clearer. Some other notable changes: - Adds a big comment to `Lifetime` talking about permissable field values. - Adds some assertions in `new_named_lifetime` about what ident values are permissible for the different `LifetimeRes` values. - Adds a `Lifetime::new` constructor that does some checking to make sure the `is_elided` and `is_anonymous` states are valid. - `add_static_impl_trait_suggestion` now looks at `Lifetime::res` instead of the ident when creating the suggestion. This is the one case where `is_path_anon` doesn't replace the old empty/underscore distinction. - A couple of minor pretty-printing improvements.
This commit is contained in:
parent
cfd00f9c16
commit
8d2c63f514
9 changed files with 136 additions and 51 deletions
|
@ -35,20 +35,60 @@ use crate::def_id::{DefId, LocalDefIdMap};
|
|||
pub(crate) use crate::hir_id::{HirId, ItemLocalId, ItemLocalMap, OwnerId};
|
||||
use crate::intravisit::{FnKind, VisitorExt};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, HashStable_Generic)]
|
||||
pub enum IsAnonInPath {
|
||||
No,
|
||||
Yes,
|
||||
}
|
||||
|
||||
/// A lifetime. The valid field combinations are non-obvious. The following
|
||||
/// example shows some of them. See also the comments on `LifetimeName`.
|
||||
/// ```
|
||||
/// #[repr(C)]
|
||||
/// struct S<'a>(&'a u32); // res=Param, name='a, IsAnonInPath::No
|
||||
/// unsafe extern "C" {
|
||||
/// fn f1(s: S); // res=Param, name='_, IsAnonInPath::Yes
|
||||
/// fn f2(s: S<'_>); // res=Param, name='_, IsAnonInPath::No
|
||||
/// fn f3<'a>(s: S<'a>); // res=Param, name='a, IsAnonInPath::No
|
||||
/// }
|
||||
///
|
||||
/// struct St<'a> { x: &'a u32 } // res=Param, name='a, IsAnonInPath::No
|
||||
/// fn f() {
|
||||
/// _ = St { x: &0 }; // res=Infer, name='_, IsAnonInPath::Yes
|
||||
/// _ = St::<'_> { x: &0 }; // res=Infer, name='_, IsAnonInPath::No
|
||||
/// }
|
||||
///
|
||||
/// struct Name<'a>(&'a str); // res=Param, name='a, IsAnonInPath::No
|
||||
/// const A: Name = Name("a"); // res=Static, name='_, IsAnonInPath::Yes
|
||||
/// const B: &str = ""; // res=Static, name='_, IsAnonInPath::No
|
||||
/// static C: &'_ str = ""; // res=Static, name='_, IsAnonInPath::No
|
||||
/// static D: &'static str = ""; // res=Static, name='static, IsAnonInPath::No
|
||||
///
|
||||
/// trait Tr {}
|
||||
/// fn tr(_: Box<dyn Tr>) {} // res=ImplicitObjectLifetimeDefault, name='_, IsAnonInPath::No
|
||||
///
|
||||
/// // (commented out because these cases trigger errors)
|
||||
/// // struct S1<'a>(&'a str); // res=Param, name='a, IsAnonInPath::No
|
||||
/// // struct S2(S1); // res=Error, name='_, IsAnonInPath::Yes
|
||||
/// // struct S3(S1<'_>); // res=Error, name='_, IsAnonInPath::No
|
||||
/// // struct S4(S1<'a>); // res=Error, name='a, IsAnonInPath::No
|
||||
/// ```
|
||||
#[derive(Debug, Copy, Clone, HashStable_Generic)]
|
||||
pub struct Lifetime {
|
||||
#[stable_hasher(ignore)]
|
||||
pub hir_id: HirId,
|
||||
|
||||
/// Either "`'a`", referring to a named lifetime definition,
|
||||
/// `'_` referring to an anonymous lifetime (either explicitly `'_` or `&type`),
|
||||
/// or "``" (i.e., `kw::Empty`) when appearing in path.
|
||||
///
|
||||
/// See `Lifetime::suggestion_position` for practical use.
|
||||
/// Either a named lifetime definition (e.g. `'a`, `'static`) or an
|
||||
/// anonymous lifetime (`'_`, either explicitly written, or inserted for
|
||||
/// things like `&type`).
|
||||
pub ident: Ident,
|
||||
|
||||
/// Semantics of this lifetime.
|
||||
pub res: LifetimeName,
|
||||
|
||||
/// Is the lifetime anonymous and in a path? Used only for error
|
||||
/// suggestions. See `Lifetime::suggestion` for example use.
|
||||
pub is_anon_in_path: IsAnonInPath,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, HashStable_Generic)]
|
||||
|
@ -111,11 +151,12 @@ pub enum LifetimeName {
|
|||
/// that was already reported.
|
||||
Error,
|
||||
|
||||
/// User wrote an anonymous lifetime, either `'_` or nothing.
|
||||
/// The semantics of this lifetime should be inferred by typechecking code.
|
||||
/// User wrote an anonymous lifetime, either `'_` or nothing (which gets
|
||||
/// converted to `'_`). The semantics of this lifetime should be inferred
|
||||
/// by typechecking code.
|
||||
Infer,
|
||||
|
||||
/// User wrote `'static`.
|
||||
/// User wrote `'static` or nothing (which gets converted to `'_`).
|
||||
Static,
|
||||
}
|
||||
|
||||
|
@ -135,34 +176,56 @@ impl LifetimeName {
|
|||
|
||||
impl fmt::Display for Lifetime {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if self.ident.name != kw::Empty { self.ident.name.fmt(f) } else { "'_".fmt(f) }
|
||||
self.ident.name.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Lifetime {
|
||||
pub fn new(
|
||||
hir_id: HirId,
|
||||
ident: Ident,
|
||||
res: LifetimeName,
|
||||
is_anon_in_path: IsAnonInPath,
|
||||
) -> Lifetime {
|
||||
let lifetime = Lifetime { hir_id, ident, res, is_anon_in_path };
|
||||
|
||||
// Sanity check: elided lifetimes form a strict subset of anonymous lifetimes.
|
||||
#[cfg(debug_assertions)]
|
||||
match (lifetime.is_elided(), lifetime.is_anonymous()) {
|
||||
(false, false) => {} // e.g. `'a`
|
||||
(false, true) => {} // e.g. explicit `'_`
|
||||
(true, true) => {} // e.g. `&x`
|
||||
(true, false) => panic!("bad Lifetime"),
|
||||
}
|
||||
|
||||
lifetime
|
||||
}
|
||||
|
||||
pub fn is_elided(&self) -> bool {
|
||||
self.res.is_elided()
|
||||
}
|
||||
|
||||
pub fn is_anonymous(&self) -> bool {
|
||||
self.ident.name == kw::Empty || self.ident.name == kw::UnderscoreLifetime
|
||||
self.ident.name == kw::UnderscoreLifetime
|
||||
}
|
||||
|
||||
pub fn suggestion(&self, new_lifetime: &str) -> (Span, String) {
|
||||
debug_assert!(new_lifetime.starts_with('\''));
|
||||
|
||||
match (self.ident.name.is_empty(), self.ident.span.is_empty()) {
|
||||
match (self.is_anon_in_path, self.ident.span.is_empty()) {
|
||||
// The user wrote `Path<T>`, and omitted the `'_,`.
|
||||
(true, true) => (self.ident.span, format!("{new_lifetime}, ")),
|
||||
(IsAnonInPath::Yes, true) => (self.ident.span, format!("{new_lifetime}, ")),
|
||||
|
||||
// The user wrote `Path` and omitted the `<'_>`.
|
||||
(true, false) => (self.ident.span.shrink_to_hi(), format!("<{new_lifetime}>")),
|
||||
(IsAnonInPath::Yes, false) => {
|
||||
(self.ident.span.shrink_to_hi(), format!("<{new_lifetime}>"))
|
||||
}
|
||||
|
||||
// The user wrote `&type` or `&mut type`.
|
||||
(false, true) => (self.ident.span, format!("{new_lifetime} ")),
|
||||
(IsAnonInPath::No, true) => (self.ident.span, format!("{new_lifetime} ")),
|
||||
|
||||
// The user wrote `'a` or `'_`.
|
||||
(false, false) => (self.ident.span, format!("{new_lifetime}")),
|
||||
(IsAnonInPath::No, false) => (self.ident.span, format!("{new_lifetime}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,6 +58,7 @@ fn trait_object_roundtrips_impl(syntax: TraitObjectSyntax) {
|
|||
hir_id: HirId::INVALID,
|
||||
ident: Ident::new(sym::name, DUMMY_SP),
|
||||
res: LifetimeName::Static,
|
||||
is_anon_in_path: IsAnonInPath::No,
|
||||
}
|
||||
},
|
||||
syntax,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue