blob: 82bc02a74743e9b1dbfef2848152163bd003fde9 [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> {
Arnavion95f8a7a2017-04-19 03:29:56 -070028 let name = if self.path.segments.len() == 1 {
29 &self.path.segments[0].ident
30 } else {
31 return None;
32 };
33
Arnavionbf395bf2017-04-15 15:35:22 -070034 if self.tts.is_empty() {
Arnavion95f8a7a2017-04-19 03:29:56 -070035 return Some(MetaItem::Word(name.clone()));
Arnavionbf395bf2017-04-15 15:35:22 -070036 }
37
38 if self.tts.len() == 1 {
39 if let TokenTree::Delimited(Delimited { delim: DelimToken::Paren, ref tts }) = self.tts[0] {
40 fn nested_meta_item_from_tokens(tts: &[TokenTree]) -> Option<(NestedMetaItem, &[TokenTree])> {
41 assert!(!tts.is_empty());
42
43 match tts[0] {
44 TokenTree::Token(Token::Literal(ref lit)) => {
45 Some((NestedMetaItem::Literal(lit.clone()), &tts[1..]))
46 }
47
48 TokenTree::Token(Token::Ident(ref ident)) => {
49 if tts.len() >= 3 {
50 match (&tts[1], &tts[2]) {
51 (&TokenTree::Token(Token::Eq), &TokenTree::Token(Token::Literal(ref lit))) => {
Arnavion95f8a7a2017-04-19 03:29:56 -070052 return Some((NestedMetaItem::MetaItem(MetaItem::NameValue(ident.clone(), lit.clone())), &tts[3..]));
Arnavionbf395bf2017-04-15 15:35:22 -070053 }
54
55 _ => {}
56 }
57 }
58
59 if tts.len() >= 2 {
60 if let TokenTree::Delimited(Delimited { delim: DelimToken::Paren, tts: ref inner_tts }) = tts[1] {
61 return match list_of_nested_meta_items_from_tokens(vec![], inner_tts) {
62 Some(nested_meta_items) => {
Arnavion95f8a7a2017-04-19 03:29:56 -070063 Some((NestedMetaItem::MetaItem(MetaItem::List(ident.clone(), nested_meta_items)), &tts[2..]))
Arnavionbf395bf2017-04-15 15:35:22 -070064 }
65
66 None => None
67 };
68 }
69 }
70
Arnavion95f8a7a2017-04-19 03:29:56 -070071 Some((NestedMetaItem::MetaItem(MetaItem::Word(ident.clone())), &tts[1..]))
Arnavionbf395bf2017-04-15 15:35:22 -070072 }
73
74 _ => None
75 }
76 }
77
78 fn list_of_nested_meta_items_from_tokens(mut result: Vec<NestedMetaItem>, tts: &[TokenTree]) -> Option<Vec<NestedMetaItem>> {
79 if tts.is_empty() {
80 return Some(result);
81 }
82
83 match nested_meta_item_from_tokens(tts) {
84 Some((nested_meta_item, rest)) => {
85 result.push(nested_meta_item);
86 if rest.is_empty() {
87 list_of_nested_meta_items_from_tokens(result, rest)
88 }
Arnavionaebc06e2017-04-15 16:17:13 -070089 else if let TokenTree::Token(Token::Comma) = rest[0] {
90 list_of_nested_meta_items_from_tokens(result, &rest[1..])
91 }
Arnavionbf395bf2017-04-15 15:35:22 -070092 else {
Arnavionaebc06e2017-04-15 16:17:13 -070093 None
Arnavionbf395bf2017-04-15 15:35:22 -070094 }
95 }
96
97 None => None
98 }
99 }
100
101 if let Some(nested_meta_items) = list_of_nested_meta_items_from_tokens(vec![], tts) {
Arnavion95f8a7a2017-04-19 03:29:56 -0700102 return Some(MetaItem::List(name.clone(), nested_meta_items));
Arnavionbf395bf2017-04-15 15:35:22 -0700103 }
104 }
105 }
106
107 if self.tts.len() == 2 {
108 match (&self.tts[0], &self.tts[1]) {
109 (&TokenTree::Token(Token::Eq), &TokenTree::Token(Token::Literal(ref lit))) => {
Arnavion95f8a7a2017-04-19 03:29:56 -0700110 return Some(MetaItem::NameValue(name.clone(), lit.clone()));
Arnavionbf395bf2017-04-15 15:35:22 -0700111 }
112
113 _ => {}
114 }
115 }
116
117 None
David Tolnay02d77cc2016-10-02 09:52:08 -0700118 }
119}
120
David Tolnay4a51dc72016-10-01 00:40:31 -0700121/// Distinguishes between Attributes that decorate items and Attributes that
122/// are contained as statements within items. These two cases need to be
123/// distinguished for pretty-printing.
David Tolnay9bf4af82017-01-07 11:17:46 -0800124#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
David Tolnay4a51dc72016-10-01 00:40:31 -0700125pub enum AttrStyle {
Clar Charrd22b5702017-03-10 15:24:56 -0500126 /// Attribute of the form `#![...]`.
David Tolnay4a51dc72016-10-01 00:40:31 -0700127 Outer,
Clar Charrd22b5702017-03-10 15:24:56 -0500128
129 /// Attribute of the form `#[...]`.
David Tolnay4a51dc72016-10-01 00:40:31 -0700130 Inner,
131}
132
David Tolnayb79ee962016-09-04 09:39:20 -0700133/// A compile-time attribute item.
134///
135/// E.g. `#[test]`, `#[derive(..)]` or `#[feature = "foo"]`
David Tolnay9bf4af82017-01-07 11:17:46 -0800136#[derive(Debug, Clone, Eq, PartialEq, Hash)]
David Tolnayb79ee962016-09-04 09:39:20 -0700137pub enum MetaItem {
138 /// Word meta item.
139 ///
140 /// E.g. `test` as in `#[test]`
Arnavion95f8a7a2017-04-19 03:29:56 -0700141 Word(Ident),
Clar Charrd22b5702017-03-10 15:24:56 -0500142
David Tolnayb79ee962016-09-04 09:39:20 -0700143 /// List meta item.
144 ///
145 /// E.g. `derive(..)` as in `#[derive(..)]`
Arnavion95f8a7a2017-04-19 03:29:56 -0700146 List(Ident, Vec<NestedMetaItem>),
Clar Charrd22b5702017-03-10 15:24:56 -0500147
148 /// Name-value meta item.
David Tolnayb79ee962016-09-04 09:39:20 -0700149 ///
150 /// E.g. `feature = "foo"` as in `#[feature = "foo"]`
Arnavion95f8a7a2017-04-19 03:29:56 -0700151 NameValue(Ident, Lit),
152}
153
154impl MetaItem {
155 /// Name of the item.
156 ///
157 /// E.g. `test` as in `#[test]`, `derive` as in `#[derive(..)]`, and
158 /// `feature` as in `#[feature = "foo"]`.
159 pub fn name(&self) -> &str {
160 match *self {
161 MetaItem::Word(ref name) |
162 MetaItem::List(ref name, _) |
163 MetaItem::NameValue(ref name, _) => name.as_ref(),
164 }
165 }
David Tolnay8e661e22016-09-27 00:00:04 -0700166}
167
David Tolnayb7fa2b62016-10-30 10:50:47 -0700168/// Possible values inside of compile-time attribute lists.
169///
170/// E.g. the '..' in `#[name(..)]`.
David Tolnay9bf4af82017-01-07 11:17:46 -0800171#[derive(Debug, Clone, Eq, PartialEq, Hash)]
David Tolnayb7fa2b62016-10-30 10:50:47 -0700172pub enum NestedMetaItem {
Clar Charrd22b5702017-03-10 15:24:56 -0500173 /// A full `MetaItem`.
David Tolnayb7fa2b62016-10-30 10:50:47 -0700174 ///
Arnavion95f8a7a2017-04-19 03:29:56 -0700175 /// E.g. `Copy` in `#[derive(Copy)]` would be a `MetaItem::Word(Ident::from("Copy"))`.
176 MetaItem(MetaItem),
Clar Charrd22b5702017-03-10 15:24:56 -0500177
178 /// A Rust literal.
179 ///
180 /// E.g. `"name"` in `#[rename("name")]`.
David Tolnayb7fa2b62016-10-30 10:50:47 -0700181 Literal(Lit),
182}
183
David Tolnay4a51dc72016-10-01 00:40:31 -0700184pub trait FilterAttrs<'a> {
185 type Ret: Iterator<Item = &'a Attribute>;
186
187 fn outer(self) -> Self::Ret;
188 fn inner(self) -> Self::Ret;
189}
190
David Tolnaydaaf7742016-10-03 11:11:43 -0700191impl<'a, T> FilterAttrs<'a> for T
192 where T: IntoIterator<Item = &'a Attribute>
193{
David Tolnay4a51dc72016-10-01 00:40:31 -0700194 type Ret = iter::Filter<T::IntoIter, fn(&&Attribute) -> bool>;
195
196 fn outer(self) -> Self::Ret {
197 fn is_outer(attr: &&Attribute) -> bool {
198 attr.style == AttrStyle::Outer
199 }
200 self.into_iter().filter(is_outer)
201 }
202
203 fn inner(self) -> Self::Ret {
204 fn is_inner(attr: &&Attribute) -> bool {
205 attr.style == AttrStyle::Inner
206 }
207 self.into_iter().filter(is_inner)
208 }
209}
210
David Tolnay86eca752016-09-04 11:26:41 -0700211#[cfg(feature = "parsing")]
David Tolnay9d8f1972016-09-04 11:58:48 -0700212pub mod parsing {
213 use super::*;
Arnavionbb252732017-04-15 16:15:15 -0700214 use lit::{Lit, StrStyle};
215 use mac::{Token, TokenTree};
Arnavioneaa2cff2017-04-15 03:32:13 -0700216 use mac::parsing::token_trees;
David Tolnay5fe14fc2017-01-27 16:22:08 -0800217 use synom::space::{block_comment, whitespace};
Arnavione43b1b02017-04-19 02:47:45 -0700218 use ty::parsing::path;
David Tolnayb79ee962016-09-04 09:39:20 -0700219
David Tolnay2a2e67c2016-10-01 14:02:01 -0700220 #[cfg(feature = "full")]
David Tolnay14cbdeb2016-10-01 12:13:59 -0700221 named!(pub inner_attr -> Attribute, alt!(
222 do_parse!(
223 punct!("#") >>
224 punct!("!") >>
Arnavione43b1b02017-04-19 02:47:45 -0700225 path_and_tts: delimited!(
Arnavionbf395bf2017-04-15 15:35:22 -0700226 punct!("["),
Arnavione43b1b02017-04-19 02:47:45 -0700227 tuple!(path, token_trees),
Arnavionbf395bf2017-04-15 15:35:22 -0700228 punct!("]")
229 ) >>
230 ({
Arnavione43b1b02017-04-19 02:47:45 -0700231 let (path, tts) = path_and_tts;
Arnavionbf395bf2017-04-15 15:35:22 -0700232
233 Attribute {
234 style: AttrStyle::Inner,
Arnavione43b1b02017-04-19 02:47:45 -0700235 path: path,
Arnavionbf395bf2017-04-15 15:35:22 -0700236 tts: tts,
237 is_sugared_doc: false,
238 }
David Tolnay14cbdeb2016-10-01 12:13:59 -0700239 })
240 )
241 |
242 do_parse!(
243 punct!("//!") >>
244 content: take_until!("\n") >>
245 (Attribute {
246 style: AttrStyle::Inner,
Arnavione43b1b02017-04-19 02:47:45 -0700247 path: "doc".into(),
Arnavionbf395bf2017-04-15 15:35:22 -0700248 tts: vec![
249 TokenTree::Token(Token::Eq),
250 TokenTree::Token(Token::Literal(Lit::Str(format!("//!{}", content).into(), StrStyle::Cooked))),
251 ],
David Tolnay14cbdeb2016-10-01 12:13:59 -0700252 is_sugared_doc: true,
253 })
254 )
255 |
256 do_parse!(
257 option!(whitespace) >>
258 peek!(tag!("/*!")) >>
259 com: block_comment >>
260 (Attribute {
261 style: AttrStyle::Inner,
Arnavione43b1b02017-04-19 02:47:45 -0700262 path: "doc".into(),
Arnavionbf395bf2017-04-15 15:35:22 -0700263 tts: vec![
264 TokenTree::Token(Token::Eq),
265 TokenTree::Token(Token::Literal(Lit::Str(com.into(), StrStyle::Cooked))),
266 ],
David Tolnay14cbdeb2016-10-01 12:13:59 -0700267 is_sugared_doc: true,
268 })
269 )
David Tolnay4a51dc72016-10-01 00:40:31 -0700270 ));
271
272 named!(pub outer_attr -> Attribute, alt!(
David Tolnay9d8f1972016-09-04 11:58:48 -0700273 do_parse!(
274 punct!("#") >>
Arnavione43b1b02017-04-19 02:47:45 -0700275 path_and_tts: delimited!(
Arnavionbf395bf2017-04-15 15:35:22 -0700276 punct!("["),
Arnavione43b1b02017-04-19 02:47:45 -0700277 tuple!(path, token_trees),
Arnavionbf395bf2017-04-15 15:35:22 -0700278 punct!("]")
Arnavioneaa2cff2017-04-15 03:32:13 -0700279 ) >>
Arnavionbf395bf2017-04-15 15:35:22 -0700280 ({
Arnavione43b1b02017-04-19 02:47:45 -0700281 let (path, tts) = path_and_tts;
Arnavionbf395bf2017-04-15 15:35:22 -0700282
283 Attribute {
284 style: AttrStyle::Outer,
Arnavione43b1b02017-04-19 02:47:45 -0700285 path: path,
Arnavionbf395bf2017-04-15 15:35:22 -0700286 tts: tts,
287 is_sugared_doc: false,
288 }
Arnavioneaa2cff2017-04-15 03:32:13 -0700289 })
290 )
291 |
292 do_parse!(
David Tolnay9d8f1972016-09-04 11:58:48 -0700293 punct!("///") >>
David Tolnay1f16b602017-02-07 20:06:55 -0500294 not!(tag!("/")) >>
David Tolnayb5a7b142016-09-13 22:46:39 -0700295 content: take_until!("\n") >>
David Tolnay9d8f1972016-09-04 11:58:48 -0700296 (Attribute {
David Tolnay4a51dc72016-10-01 00:40:31 -0700297 style: AttrStyle::Outer,
Arnavione43b1b02017-04-19 02:47:45 -0700298 path: "doc".into(),
Arnavionbf395bf2017-04-15 15:35:22 -0700299 tts: vec![
300 TokenTree::Token(Token::Eq),
301 TokenTree::Token(Token::Literal(Lit::Str(format!("///{}", content).into(), StrStyle::Cooked))),
302 ],
David Tolnay9d8f1972016-09-04 11:58:48 -0700303 is_sugared_doc: true,
304 })
305 )
David Tolnay14cbdeb2016-10-01 12:13:59 -0700306 |
307 do_parse!(
308 option!(whitespace) >>
David Tolnay84aa0752016-10-02 23:01:13 -0700309 peek!(tuple!(tag!("/**"), not!(tag!("*")))) >>
David Tolnay14cbdeb2016-10-01 12:13:59 -0700310 com: block_comment >>
311 (Attribute {
312 style: AttrStyle::Outer,
Arnavione43b1b02017-04-19 02:47:45 -0700313 path: "doc".into(),
Arnavionbf395bf2017-04-15 15:35:22 -0700314 tts: vec![
315 TokenTree::Token(Token::Eq),
316 TokenTree::Token(Token::Literal(Lit::Str(com.into(), StrStyle::Cooked))),
317 ],
David Tolnay14cbdeb2016-10-01 12:13:59 -0700318 is_sugared_doc: true,
319 })
320 )
David Tolnay9d8f1972016-09-04 11:58:48 -0700321 ));
David Tolnay9d8f1972016-09-04 11:58:48 -0700322}
David Tolnay87d0b442016-09-04 11:52:12 -0700323
324#[cfg(feature = "printing")]
325mod printing {
326 use super::*;
David Tolnayc91dd672016-10-01 01:03:56 -0700327 use lit::{Lit, StrStyle};
Arnavionbb252732017-04-15 16:15:15 -0700328 use mac::{Token, TokenTree};
David Tolnay87d0b442016-09-04 11:52:12 -0700329 use quote::{Tokens, ToTokens};
Arnaviona115eba2017-04-19 03:05:52 -0700330 use ty::Path;
David Tolnay87d0b442016-09-04 11:52:12 -0700331
332 impl ToTokens for Attribute {
333 fn to_tokens(&self, tokens: &mut Tokens) {
Arnavion44d2bf32017-04-19 02:47:55 -0700334 // If this was a sugared doc, emit it in its original form instead of `#[doc = "..."]`
Arnavione43b1b02017-04-19 02:47:45 -0700335 match *self {
336 Attribute {
337 style,
338 path: Path { global: false, ref segments },
339 ref tts,
340 is_sugared_doc: true,
341 } if segments.len() == 1 &&
342 segments[0].ident == "doc" &&
343 segments[0].parameters.is_empty() &&
344 tts.len() == 2 =>
345 {
346 match (&self.tts[0], &self.tts[1]) {
Arnavionbf395bf2017-04-15 15:35:22 -0700347 (&TokenTree::Token(Token::Eq),
Arnavione43b1b02017-04-19 02:47:45 -0700348 &TokenTree::Token(Token::Literal(Lit::Str(ref value, StrStyle::Cooked)))) =>
349 {
Arnavionbf395bf2017-04-15 15:35:22 -0700350 match style {
351 AttrStyle::Inner if value.starts_with("//!") => {
352 tokens.append(&format!("{}\n", value));
353 return;
354 }
355 AttrStyle::Inner if value.starts_with("/*!") => {
356 tokens.append(value);
357 return;
358 }
359 AttrStyle::Outer if value.starts_with("///") => {
360 tokens.append(&format!("{}\n", value));
361 return;
362 }
363 AttrStyle::Outer if value.starts_with("/**") => {
364 tokens.append(value);
365 return;
366 }
367 _ => {}
368 }
David Tolnay14cbdeb2016-10-01 12:13:59 -0700369 }
Arnavionbf395bf2017-04-15 15:35:22 -0700370
David Tolnay14cbdeb2016-10-01 12:13:59 -0700371 _ => {}
David Tolnay4a51dc72016-10-01 00:40:31 -0700372 }
David Tolnayc91dd672016-10-01 01:03:56 -0700373 }
Arnavione43b1b02017-04-19 02:47:45 -0700374
375 _ => {}
David Tolnayc91dd672016-10-01 01:03:56 -0700376 }
David Tolnay14cbdeb2016-10-01 12:13:59 -0700377
378 tokens.append("#");
379 if let AttrStyle::Inner = self.style {
380 tokens.append("!");
381 }
382 tokens.append("[");
Arnavione43b1b02017-04-19 02:47:45 -0700383 self.path.to_tokens(tokens);
Arnavionbf395bf2017-04-15 15:35:22 -0700384 tokens.append_all(&self.tts);
David Tolnay14cbdeb2016-10-01 12:13:59 -0700385 tokens.append("]");
David Tolnay87d0b442016-09-04 11:52:12 -0700386 }
387 }
388
389 impl ToTokens for MetaItem {
390 fn to_tokens(&self, tokens: &mut Tokens) {
391 match *self {
Arnavion95f8a7a2017-04-19 03:29:56 -0700392 MetaItem::Word(ref ident) => {
393 ident.to_tokens(tokens);
394 }
395 MetaItem::List(ref ident, ref inner) => {
396 ident.to_tokens(tokens);
David Tolnay87d0b442016-09-04 11:52:12 -0700397 tokens.append("(");
David Tolnay94ebdf92016-09-04 13:33:16 -0700398 tokens.append_separated(inner, ",");
David Tolnay87d0b442016-09-04 11:52:12 -0700399 tokens.append(")");
400 }
Arnavion95f8a7a2017-04-19 03:29:56 -0700401 MetaItem::NameValue(ref name, ref value) => {
402 name.to_tokens(tokens);
David Tolnay87d0b442016-09-04 11:52:12 -0700403 tokens.append("=");
404 value.to_tokens(tokens);
405 }
406 }
407 }
408 }
David Tolnayb7fa2b62016-10-30 10:50:47 -0700409
410 impl ToTokens for NestedMetaItem {
411 fn to_tokens(&self, tokens: &mut Tokens) {
412 match *self {
Arnavion95f8a7a2017-04-19 03:29:56 -0700413 NestedMetaItem::MetaItem(ref nested) => {
David Tolnayb7fa2b62016-10-30 10:50:47 -0700414 nested.to_tokens(tokens);
415 }
416 NestedMetaItem::Literal(ref lit) => {
417 lit.to_tokens(tokens);
418 }
419 }
420 }
421 }
David Tolnay87d0b442016-09-04 11:52:12 -0700422}