blob: c98a96903d5018ff2e4d94ae00c4ed6cbba5005c [file] [log] [blame]
David Tolnayb79ee962016-09-04 09:39:20 -07001use super::*;
2
David Tolnay4a51dc72016-10-01 00:40:31 -07003use std::iter;
4
David Tolnayaed77b02016-09-23 20:50:31 -07005/// Doc-comments are promoted to attributes that have `is_sugared_doc` = true
David Tolnay9bf4af82017-01-07 11:17:46 -08006#[derive(Debug, Clone, Eq, PartialEq, Hash)]
David Tolnayb79ee962016-09-04 09:39:20 -07007pub struct Attribute {
David Tolnay4a51dc72016-10-01 00:40:31 -07008 pub style: AttrStyle,
Arnavion44d2bf32017-04-19 02:47:55 -07009
10 /// The path of the attribute.
11 ///
12 /// E.g. `derive` in `#[derive(Copy)]`
13 /// E.g. `crate::precondition` in `#[crate::precondition x < 5]`
Arnavione43b1b02017-04-19 02:47:45 -070014 pub path: Path,
Arnavion44d2bf32017-04-19 02:47:55 -070015
16 /// Any tokens after the path.
17 ///
18 /// E.g. `( Copy )` in `#[derive(Copy)]`
19 /// E.g. `x < 5` in `#[crate::precondition x < 5]`
Arnavionbf395bf2017-04-15 15:35:22 -070020 pub tts: Vec<TokenTree>,
Arnavion44d2bf32017-04-19 02:47:55 -070021
David Tolnayb79ee962016-09-04 09:39:20 -070022 pub is_sugared_doc: bool,
23}
24
David Tolnay02d77cc2016-10-02 09:52:08 -070025impl Attribute {
Arnavion44d2bf32017-04-19 02:47:55 -070026 /// Parses the tokens after the path as a [`MetaItem`](enum.MetaItem.html) if possible.
Arnavionbf395bf2017-04-15 15:35:22 -070027 pub fn meta_item(&self) -> Option<MetaItem> {
28 if self.tts.is_empty() {
29 return Some(MetaItem::Word);
30 }
31
32 if self.tts.len() == 1 {
33 if let TokenTree::Delimited(Delimited { delim: DelimToken::Paren, ref tts }) = self.tts[0] {
34 fn nested_meta_item_from_tokens(tts: &[TokenTree]) -> Option<(NestedMetaItem, &[TokenTree])> {
35 assert!(!tts.is_empty());
36
37 match tts[0] {
38 TokenTree::Token(Token::Literal(ref lit)) => {
39 Some((NestedMetaItem::Literal(lit.clone()), &tts[1..]))
40 }
41
42 TokenTree::Token(Token::Ident(ref ident)) => {
43 if tts.len() >= 3 {
44 match (&tts[1], &tts[2]) {
45 (&TokenTree::Token(Token::Eq), &TokenTree::Token(Token::Literal(ref lit))) => {
46 return Some((NestedMetaItem::MetaItem(ident.clone(), MetaItem::NameValue(lit.clone())), &tts[3..]));
47 }
48
49 _ => {}
50 }
51 }
52
53 if tts.len() >= 2 {
54 if let TokenTree::Delimited(Delimited { delim: DelimToken::Paren, tts: ref inner_tts }) = tts[1] {
55 return match list_of_nested_meta_items_from_tokens(vec![], inner_tts) {
56 Some(nested_meta_items) => {
57 Some((NestedMetaItem::MetaItem(ident.clone(), MetaItem::List(nested_meta_items)), &tts[2..]))
58 }
59
60 None => None
61 };
62 }
63 }
64
65 Some((NestedMetaItem::MetaItem(ident.clone(), MetaItem::Word), &tts[1..]))
66 }
67
68 _ => None
69 }
70 }
71
72 fn list_of_nested_meta_items_from_tokens(mut result: Vec<NestedMetaItem>, tts: &[TokenTree]) -> Option<Vec<NestedMetaItem>> {
73 if tts.is_empty() {
74 return Some(result);
75 }
76
77 match nested_meta_item_from_tokens(tts) {
78 Some((nested_meta_item, rest)) => {
79 result.push(nested_meta_item);
80 if rest.is_empty() {
81 list_of_nested_meta_items_from_tokens(result, rest)
82 }
Arnavionaebc06e2017-04-15 16:17:13 -070083 else if let TokenTree::Token(Token::Comma) = rest[0] {
84 list_of_nested_meta_items_from_tokens(result, &rest[1..])
85 }
Arnavionbf395bf2017-04-15 15:35:22 -070086 else {
Arnavionaebc06e2017-04-15 16:17:13 -070087 None
Arnavionbf395bf2017-04-15 15:35:22 -070088 }
89 }
90
91 None => None
92 }
93 }
94
95 if let Some(nested_meta_items) = list_of_nested_meta_items_from_tokens(vec![], tts) {
96 return Some(MetaItem::List(nested_meta_items));
97 }
98 }
99 }
100
101 if self.tts.len() == 2 {
102 match (&self.tts[0], &self.tts[1]) {
103 (&TokenTree::Token(Token::Eq), &TokenTree::Token(Token::Literal(ref lit))) => {
104 return Some(MetaItem::NameValue(lit.clone()));
105 }
106
107 _ => {}
108 }
109 }
110
111 None
David Tolnay02d77cc2016-10-02 09:52:08 -0700112 }
113}
114
David Tolnay4a51dc72016-10-01 00:40:31 -0700115/// Distinguishes between Attributes that decorate items and Attributes that
116/// are contained as statements within items. These two cases need to be
117/// distinguished for pretty-printing.
David Tolnay9bf4af82017-01-07 11:17:46 -0800118#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
David Tolnay4a51dc72016-10-01 00:40:31 -0700119pub enum AttrStyle {
Clar Charrd22b5702017-03-10 15:24:56 -0500120 /// Attribute of the form `#![...]`.
David Tolnay4a51dc72016-10-01 00:40:31 -0700121 Outer,
Clar Charrd22b5702017-03-10 15:24:56 -0500122
123 /// Attribute of the form `#[...]`.
David Tolnay4a51dc72016-10-01 00:40:31 -0700124 Inner,
125}
126
David Tolnayb79ee962016-09-04 09:39:20 -0700127/// A compile-time attribute item.
128///
129/// E.g. `#[test]`, `#[derive(..)]` or `#[feature = "foo"]`
David Tolnay9bf4af82017-01-07 11:17:46 -0800130#[derive(Debug, Clone, Eq, PartialEq, Hash)]
David Tolnayb79ee962016-09-04 09:39:20 -0700131pub enum MetaItem {
132 /// Word meta item.
133 ///
134 /// E.g. `test` as in `#[test]`
Arnavionbf395bf2017-04-15 15:35:22 -0700135 Word,
Clar Charrd22b5702017-03-10 15:24:56 -0500136
David Tolnayb79ee962016-09-04 09:39:20 -0700137 /// List meta item.
138 ///
139 /// E.g. `derive(..)` as in `#[derive(..)]`
Arnavionbf395bf2017-04-15 15:35:22 -0700140 List(Vec<NestedMetaItem>),
Clar Charrd22b5702017-03-10 15:24:56 -0500141
142 /// Name-value meta item.
David Tolnayb79ee962016-09-04 09:39:20 -0700143 ///
144 /// E.g. `feature = "foo"` as in `#[feature = "foo"]`
Arnavionbf395bf2017-04-15 15:35:22 -0700145 NameValue(Lit),
David Tolnay8e661e22016-09-27 00:00:04 -0700146}
147
David Tolnayb7fa2b62016-10-30 10:50:47 -0700148/// Possible values inside of compile-time attribute lists.
149///
150/// E.g. the '..' in `#[name(..)]`.
David Tolnay9bf4af82017-01-07 11:17:46 -0800151#[derive(Debug, Clone, Eq, PartialEq, Hash)]
David Tolnayb7fa2b62016-10-30 10:50:47 -0700152pub enum NestedMetaItem {
Clar Charrd22b5702017-03-10 15:24:56 -0500153 /// A full `MetaItem`.
David Tolnayb7fa2b62016-10-30 10:50:47 -0700154 ///
Arnavionbf395bf2017-04-15 15:35:22 -0700155 /// E.g. `Copy` in `#[derive(Copy)]` would be a `(Ident::from("Copy"), MetaItem::Word)`.
156 MetaItem(Ident, MetaItem),
Clar Charrd22b5702017-03-10 15:24:56 -0500157
158 /// A Rust literal.
159 ///
160 /// E.g. `"name"` in `#[rename("name")]`.
David Tolnayb7fa2b62016-10-30 10:50:47 -0700161 Literal(Lit),
162}
163
David Tolnay4a51dc72016-10-01 00:40:31 -0700164pub trait FilterAttrs<'a> {
165 type Ret: Iterator<Item = &'a Attribute>;
166
167 fn outer(self) -> Self::Ret;
168 fn inner(self) -> Self::Ret;
169}
170
David Tolnaydaaf7742016-10-03 11:11:43 -0700171impl<'a, T> FilterAttrs<'a> for T
172 where T: IntoIterator<Item = &'a Attribute>
173{
David Tolnay4a51dc72016-10-01 00:40:31 -0700174 type Ret = iter::Filter<T::IntoIter, fn(&&Attribute) -> bool>;
175
176 fn outer(self) -> Self::Ret {
177 fn is_outer(attr: &&Attribute) -> bool {
178 attr.style == AttrStyle::Outer
179 }
180 self.into_iter().filter(is_outer)
181 }
182
183 fn inner(self) -> Self::Ret {
184 fn is_inner(attr: &&Attribute) -> bool {
185 attr.style == AttrStyle::Inner
186 }
187 self.into_iter().filter(is_inner)
188 }
189}
190
David Tolnay86eca752016-09-04 11:26:41 -0700191#[cfg(feature = "parsing")]
David Tolnay9d8f1972016-09-04 11:58:48 -0700192pub mod parsing {
193 use super::*;
Arnavionbb252732017-04-15 16:15:15 -0700194 use lit::{Lit, StrStyle};
195 use mac::{Token, TokenTree};
Arnavioneaa2cff2017-04-15 03:32:13 -0700196 use mac::parsing::token_trees;
David Tolnay5fe14fc2017-01-27 16:22:08 -0800197 use synom::space::{block_comment, whitespace};
Arnavione43b1b02017-04-19 02:47:45 -0700198 use ty::parsing::path;
David Tolnayb79ee962016-09-04 09:39:20 -0700199
David Tolnay2a2e67c2016-10-01 14:02:01 -0700200 #[cfg(feature = "full")]
David Tolnay14cbdeb2016-10-01 12:13:59 -0700201 named!(pub inner_attr -> Attribute, alt!(
202 do_parse!(
203 punct!("#") >>
204 punct!("!") >>
Arnavione43b1b02017-04-19 02:47:45 -0700205 path_and_tts: delimited!(
Arnavionbf395bf2017-04-15 15:35:22 -0700206 punct!("["),
Arnavione43b1b02017-04-19 02:47:45 -0700207 tuple!(path, token_trees),
Arnavionbf395bf2017-04-15 15:35:22 -0700208 punct!("]")
209 ) >>
210 ({
Arnavione43b1b02017-04-19 02:47:45 -0700211 let (path, tts) = path_and_tts;
Arnavionbf395bf2017-04-15 15:35:22 -0700212
213 Attribute {
214 style: AttrStyle::Inner,
Arnavione43b1b02017-04-19 02:47:45 -0700215 path: path,
Arnavionbf395bf2017-04-15 15:35:22 -0700216 tts: tts,
217 is_sugared_doc: false,
218 }
David Tolnay14cbdeb2016-10-01 12:13:59 -0700219 })
220 )
221 |
222 do_parse!(
223 punct!("//!") >>
224 content: take_until!("\n") >>
225 (Attribute {
226 style: AttrStyle::Inner,
Arnavione43b1b02017-04-19 02:47:45 -0700227 path: "doc".into(),
Arnavionbf395bf2017-04-15 15:35:22 -0700228 tts: vec![
229 TokenTree::Token(Token::Eq),
230 TokenTree::Token(Token::Literal(Lit::Str(format!("//!{}", content).into(), StrStyle::Cooked))),
231 ],
David Tolnay14cbdeb2016-10-01 12:13:59 -0700232 is_sugared_doc: true,
233 })
234 )
235 |
236 do_parse!(
237 option!(whitespace) >>
238 peek!(tag!("/*!")) >>
239 com: block_comment >>
240 (Attribute {
241 style: AttrStyle::Inner,
Arnavione43b1b02017-04-19 02:47:45 -0700242 path: "doc".into(),
Arnavionbf395bf2017-04-15 15:35:22 -0700243 tts: vec![
244 TokenTree::Token(Token::Eq),
245 TokenTree::Token(Token::Literal(Lit::Str(com.into(), StrStyle::Cooked))),
246 ],
David Tolnay14cbdeb2016-10-01 12:13:59 -0700247 is_sugared_doc: true,
248 })
249 )
David Tolnay4a51dc72016-10-01 00:40:31 -0700250 ));
251
252 named!(pub outer_attr -> Attribute, alt!(
David Tolnay9d8f1972016-09-04 11:58:48 -0700253 do_parse!(
254 punct!("#") >>
Arnavione43b1b02017-04-19 02:47:45 -0700255 path_and_tts: delimited!(
Arnavionbf395bf2017-04-15 15:35:22 -0700256 punct!("["),
Arnavione43b1b02017-04-19 02:47:45 -0700257 tuple!(path, token_trees),
Arnavionbf395bf2017-04-15 15:35:22 -0700258 punct!("]")
Arnavioneaa2cff2017-04-15 03:32:13 -0700259 ) >>
Arnavionbf395bf2017-04-15 15:35:22 -0700260 ({
Arnavione43b1b02017-04-19 02:47:45 -0700261 let (path, tts) = path_and_tts;
Arnavionbf395bf2017-04-15 15:35:22 -0700262
263 Attribute {
264 style: AttrStyle::Outer,
Arnavione43b1b02017-04-19 02:47:45 -0700265 path: path,
Arnavionbf395bf2017-04-15 15:35:22 -0700266 tts: tts,
267 is_sugared_doc: false,
268 }
Arnavioneaa2cff2017-04-15 03:32:13 -0700269 })
270 )
271 |
272 do_parse!(
David Tolnay9d8f1972016-09-04 11:58:48 -0700273 punct!("///") >>
David Tolnay1f16b602017-02-07 20:06:55 -0500274 not!(tag!("/")) >>
David Tolnayb5a7b142016-09-13 22:46:39 -0700275 content: take_until!("\n") >>
David Tolnay9d8f1972016-09-04 11:58:48 -0700276 (Attribute {
David Tolnay4a51dc72016-10-01 00:40:31 -0700277 style: AttrStyle::Outer,
Arnavione43b1b02017-04-19 02:47:45 -0700278 path: "doc".into(),
Arnavionbf395bf2017-04-15 15:35:22 -0700279 tts: vec![
280 TokenTree::Token(Token::Eq),
281 TokenTree::Token(Token::Literal(Lit::Str(format!("///{}", content).into(), StrStyle::Cooked))),
282 ],
David Tolnay9d8f1972016-09-04 11:58:48 -0700283 is_sugared_doc: true,
284 })
285 )
David Tolnay14cbdeb2016-10-01 12:13:59 -0700286 |
287 do_parse!(
288 option!(whitespace) >>
David Tolnay84aa0752016-10-02 23:01:13 -0700289 peek!(tuple!(tag!("/**"), not!(tag!("*")))) >>
David Tolnay14cbdeb2016-10-01 12:13:59 -0700290 com: block_comment >>
291 (Attribute {
292 style: AttrStyle::Outer,
Arnavione43b1b02017-04-19 02:47:45 -0700293 path: "doc".into(),
Arnavionbf395bf2017-04-15 15:35:22 -0700294 tts: vec![
295 TokenTree::Token(Token::Eq),
296 TokenTree::Token(Token::Literal(Lit::Str(com.into(), StrStyle::Cooked))),
297 ],
David Tolnay14cbdeb2016-10-01 12:13:59 -0700298 is_sugared_doc: true,
299 })
300 )
David Tolnay9d8f1972016-09-04 11:58:48 -0700301 ));
David Tolnay9d8f1972016-09-04 11:58:48 -0700302}
David Tolnay87d0b442016-09-04 11:52:12 -0700303
304#[cfg(feature = "printing")]
305mod printing {
306 use super::*;
David Tolnayc91dd672016-10-01 01:03:56 -0700307 use lit::{Lit, StrStyle};
Arnavionbb252732017-04-15 16:15:15 -0700308 use mac::{Token, TokenTree};
David Tolnay87d0b442016-09-04 11:52:12 -0700309 use quote::{Tokens, ToTokens};
310
311 impl ToTokens for Attribute {
312 fn to_tokens(&self, tokens: &mut Tokens) {
Arnavion44d2bf32017-04-19 02:47:55 -0700313 // If this was a sugared doc, emit it in its original form instead of `#[doc = "..."]`
Arnavione43b1b02017-04-19 02:47:45 -0700314 match *self {
315 Attribute {
316 style,
317 path: Path { global: false, ref segments },
318 ref tts,
319 is_sugared_doc: true,
320 } if segments.len() == 1 &&
321 segments[0].ident == "doc" &&
322 segments[0].parameters.is_empty() &&
323 tts.len() == 2 =>
324 {
325 match (&self.tts[0], &self.tts[1]) {
Arnavionbf395bf2017-04-15 15:35:22 -0700326 (&TokenTree::Token(Token::Eq),
Arnavione43b1b02017-04-19 02:47:45 -0700327 &TokenTree::Token(Token::Literal(Lit::Str(ref value, StrStyle::Cooked)))) =>
328 {
Arnavionbf395bf2017-04-15 15:35:22 -0700329 match style {
330 AttrStyle::Inner if value.starts_with("//!") => {
331 tokens.append(&format!("{}\n", value));
332 return;
333 }
334 AttrStyle::Inner if value.starts_with("/*!") => {
335 tokens.append(value);
336 return;
337 }
338 AttrStyle::Outer if value.starts_with("///") => {
339 tokens.append(&format!("{}\n", value));
340 return;
341 }
342 AttrStyle::Outer if value.starts_with("/**") => {
343 tokens.append(value);
344 return;
345 }
346 _ => {}
347 }
David Tolnay14cbdeb2016-10-01 12:13:59 -0700348 }
Arnavionbf395bf2017-04-15 15:35:22 -0700349
David Tolnay14cbdeb2016-10-01 12:13:59 -0700350 _ => {}
David Tolnay4a51dc72016-10-01 00:40:31 -0700351 }
David Tolnayc91dd672016-10-01 01:03:56 -0700352 }
Arnavione43b1b02017-04-19 02:47:45 -0700353
354 _ => {}
David Tolnayc91dd672016-10-01 01:03:56 -0700355 }
David Tolnay14cbdeb2016-10-01 12:13:59 -0700356
357 tokens.append("#");
358 if let AttrStyle::Inner = self.style {
359 tokens.append("!");
360 }
361 tokens.append("[");
Arnavione43b1b02017-04-19 02:47:45 -0700362 self.path.to_tokens(tokens);
Arnavionbf395bf2017-04-15 15:35:22 -0700363 tokens.append_all(&self.tts);
David Tolnay14cbdeb2016-10-01 12:13:59 -0700364 tokens.append("]");
David Tolnay87d0b442016-09-04 11:52:12 -0700365 }
366 }
367
368 impl ToTokens for MetaItem {
369 fn to_tokens(&self, tokens: &mut Tokens) {
370 match *self {
Arnavionbf395bf2017-04-15 15:35:22 -0700371 MetaItem::Word => {}
372 MetaItem::List(ref inner) => {
David Tolnay87d0b442016-09-04 11:52:12 -0700373 tokens.append("(");
David Tolnay94ebdf92016-09-04 13:33:16 -0700374 tokens.append_separated(inner, ",");
David Tolnay87d0b442016-09-04 11:52:12 -0700375 tokens.append(")");
376 }
Arnavionbf395bf2017-04-15 15:35:22 -0700377 MetaItem::NameValue(ref value) => {
David Tolnay87d0b442016-09-04 11:52:12 -0700378 tokens.append("=");
379 value.to_tokens(tokens);
380 }
381 }
382 }
383 }
David Tolnayb7fa2b62016-10-30 10:50:47 -0700384
385 impl ToTokens for NestedMetaItem {
386 fn to_tokens(&self, tokens: &mut Tokens) {
387 match *self {
Arnavionbf395bf2017-04-15 15:35:22 -0700388 NestedMetaItem::MetaItem(ref ident, ref nested) => {
389 ident.to_tokens(tokens);
David Tolnayb7fa2b62016-10-30 10:50:47 -0700390 nested.to_tokens(tokens);
391 }
392 NestedMetaItem::Literal(ref lit) => {
393 lit.to_tokens(tokens);
394 }
395 }
396 }
397 }
David Tolnay87d0b442016-09-04 11:52:12 -0700398}