1#[cfg(feature = "parsing")]
2use crate::lookahead;
3use proc_macro2::{Ident, Span};
4use std::cmp::Ordering;
5use std::fmt::{self, Display};
6use std::hash::{Hash, Hasher};
7
8/// A Rust lifetime: `'a`.
9///
10/// Lifetime names must conform to the following rules:
11///
12/// - Must start with an apostrophe.
13/// - Must not consist of just an apostrophe: `'`.
14/// - Character after the apostrophe must be `_` or a Unicode code point with
15/// the XID_Start property.
16/// - All following characters must be Unicode code points with the XID_Continue
17/// property.
18pub struct Lifetime {
19 pub apostrophe: Span,
20 pub ident: Ident,
21}
22
23impl Lifetime {
24 /// # Panics
25 ///
26 /// Panics if the lifetime does not conform to the bulleted rules above.
27 ///
28 /// # Invocation
29 ///
30 /// ```
31 /// # use proc_macro2::Span;
32 /// # use syn::Lifetime;
33 /// #
34 /// # fn f() -> Lifetime {
35 /// Lifetime::new("'a", Span::call_site())
36 /// # }
37 /// ```
38 pub fn new(symbol: &str, span: Span) -> Self {
39 if !symbol.starts_with('\'') {
40 panic!(
41 "lifetime name must start with apostrophe as in \"'a\", got {:?}",
42 symbol
43 );
44 }
45
46 if symbol == "'" {
47 panic!("lifetime name must not be empty");
48 }
49
50 if !crate::ident::xid_ok(&symbol[1..]) {
51 panic!("{:?} is not a valid lifetime name", symbol);
52 }
53
54 Lifetime {
55 apostrophe: span,
56 ident: Ident::new(&symbol[1..], span),
57 }
58 }
59
60 pub fn span(&self) -> Span {
61 self.apostrophe
62 .join(self.ident.span())
63 .unwrap_or(self.apostrophe)
64 }
65
66 pub fn set_span(&mut self, span: Span) {
67 self.apostrophe = span;
68 self.ident.set_span(span);
69 }
70}
71
72impl Display for Lifetime {
73 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
74 "'".fmt(formatter)?;
75 self.ident.fmt(formatter)
76 }
77}
78
79impl Clone for Lifetime {
80 fn clone(&self) -> Self {
81 Lifetime {
82 apostrophe: self.apostrophe,
83 ident: self.ident.clone(),
84 }
85 }
86}
87
88impl PartialEq for Lifetime {
89 fn eq(&self, other: &Lifetime) -> bool {
90 self.ident.eq(&other.ident)
91 }
92}
93
94impl Eq for Lifetime {}
95
96impl PartialOrd for Lifetime {
97 fn partial_cmp(&self, other: &Lifetime) -> Option<Ordering> {
98 Some(self.cmp(other))
99 }
100}
101
102impl Ord for Lifetime {
103 fn cmp(&self, other: &Lifetime) -> Ordering {
104 self.ident.cmp(&other.ident)
105 }
106}
107
108impl Hash for Lifetime {
109 fn hash<H: Hasher>(&self, h: &mut H) {
110 self.ident.hash(state:h);
111 }
112}
113
114#[cfg(feature = "parsing")]
115pub_if_not_doc! {
116 #[doc(hidden)]
117 #[allow(non_snake_case)]
118 pub fn Lifetime(marker: lookahead::TokenMarker) -> Lifetime {
119 match marker {}
120 }
121}
122
123#[cfg(feature = "parsing")]
124pub(crate) mod parsing {
125 use crate::error::Result;
126 use crate::lifetime::Lifetime;
127 use crate::parse::{Parse, ParseStream};
128
129 #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))]
130 impl Parse for Lifetime {
131 fn parse(input: ParseStream) -> Result<Self> {
132 input.step(|cursor: StepCursor<'_, '_>| {
133 cursor
134 .lifetime()
135 .ok_or_else(|| cursor.error(message:"expected lifetime"))
136 })
137 }
138 }
139}
140
141#[cfg(feature = "printing")]
142mod printing {
143 use crate::lifetime::Lifetime;
144 use proc_macro2::{Punct, Spacing, TokenStream};
145 use quote::{ToTokens, TokenStreamExt};
146
147 #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))]
148 impl ToTokens for Lifetime {
149 fn to_tokens(&self, tokens: &mut TokenStream) {
150 let mut apostrophe: Punct = Punct::new(ch:'\'', Spacing::Joint);
151 apostrophe.set_span(self.apostrophe);
152 tokens.append(token:apostrophe);
153 self.ident.to_tokens(tokens);
154 }
155 }
156}
157