blob: be04fdb00f178c806a60f2a12d3bf98613f2e91f [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,
Arnavionbf395bf2017-04-15 15:35:22 -07009 pub name: Ident,
10 pub tts: Vec<TokenTree>,
David Tolnayb79ee962016-09-04 09:39:20 -070011 pub is_sugared_doc: bool,
12}
13
David Tolnay02d77cc2016-10-02 09:52:08 -070014impl Attribute {
Arnavionbf395bf2017-04-15 15:35:22 -070015 pub fn meta_item(&self) -> Option<MetaItem> {
16 if self.tts.is_empty() {
17 return Some(MetaItem::Word);
18 }
19
20 if self.tts.len() == 1 {
21 if let TokenTree::Delimited(Delimited { delim: DelimToken::Paren, ref tts }) = self.tts[0] {
22 fn nested_meta_item_from_tokens(tts: &[TokenTree]) -> Option<(NestedMetaItem, &[TokenTree])> {
23 assert!(!tts.is_empty());
24
25 match tts[0] {
26 TokenTree::Token(Token::Literal(ref lit)) => {
27 Some((NestedMetaItem::Literal(lit.clone()), &tts[1..]))
28 }
29
30 TokenTree::Token(Token::Ident(ref ident)) => {
31 if tts.len() >= 3 {
32 match (&tts[1], &tts[2]) {
33 (&TokenTree::Token(Token::Eq), &TokenTree::Token(Token::Literal(ref lit))) => {
34 return Some((NestedMetaItem::MetaItem(ident.clone(), MetaItem::NameValue(lit.clone())), &tts[3..]));
35 }
36
37 _ => {}
38 }
39 }
40
41 if tts.len() >= 2 {
42 if let TokenTree::Delimited(Delimited { delim: DelimToken::Paren, tts: ref inner_tts }) = tts[1] {
43 return match list_of_nested_meta_items_from_tokens(vec![], inner_tts) {
44 Some(nested_meta_items) => {
45 Some((NestedMetaItem::MetaItem(ident.clone(), MetaItem::List(nested_meta_items)), &tts[2..]))
46 }
47
48 None => None
49 };
50 }
51 }
52
53 Some((NestedMetaItem::MetaItem(ident.clone(), MetaItem::Word), &tts[1..]))
54 }
55
56 _ => None
57 }
58 }
59
60 fn list_of_nested_meta_items_from_tokens(mut result: Vec<NestedMetaItem>, tts: &[TokenTree]) -> Option<Vec<NestedMetaItem>> {
61 if tts.is_empty() {
62 return Some(result);
63 }
64
65 match nested_meta_item_from_tokens(tts) {
66 Some((nested_meta_item, rest)) => {
67 result.push(nested_meta_item);
68 if rest.is_empty() {
69 list_of_nested_meta_items_from_tokens(result, rest)
70 }
71 else {
72 if let TokenTree::Token(Token::Comma) = rest[0] {
73 list_of_nested_meta_items_from_tokens(result, &rest[1..])
74 }
75 else {
76 None
77 }
78 }
79 }
80
81 None => None
82 }
83 }
84
85 if let Some(nested_meta_items) = list_of_nested_meta_items_from_tokens(vec![], tts) {
86 return Some(MetaItem::List(nested_meta_items));
87 }
88 }
89 }
90
91 if self.tts.len() == 2 {
92 match (&self.tts[0], &self.tts[1]) {
93 (&TokenTree::Token(Token::Eq), &TokenTree::Token(Token::Literal(ref lit))) => {
94 return Some(MetaItem::NameValue(lit.clone()));
95 }
96
97 _ => {}
98 }
99 }
100
101 None
David Tolnay02d77cc2016-10-02 09:52:08 -0700102 }
103}
104
David Tolnay4a51dc72016-10-01 00:40:31 -0700105/// Distinguishes between Attributes that decorate items and Attributes that
106/// are contained as statements within items. These two cases need to be
107/// distinguished for pretty-printing.
David Tolnay9bf4af82017-01-07 11:17:46 -0800108#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
David Tolnay4a51dc72016-10-01 00:40:31 -0700109pub enum AttrStyle {
Clar Charrd22b5702017-03-10 15:24:56 -0500110 /// Attribute of the form `#![...]`.
David Tolnay4a51dc72016-10-01 00:40:31 -0700111 Outer,
Clar Charrd22b5702017-03-10 15:24:56 -0500112
113 /// Attribute of the form `#[...]`.
David Tolnay4a51dc72016-10-01 00:40:31 -0700114 Inner,
115}
116
David Tolnayb79ee962016-09-04 09:39:20 -0700117/// A compile-time attribute item.
118///
119/// E.g. `#[test]`, `#[derive(..)]` or `#[feature = "foo"]`
David Tolnay9bf4af82017-01-07 11:17:46 -0800120#[derive(Debug, Clone, Eq, PartialEq, Hash)]
David Tolnayb79ee962016-09-04 09:39:20 -0700121pub enum MetaItem {
122 /// Word meta item.
123 ///
124 /// E.g. `test` as in `#[test]`
Arnavionbf395bf2017-04-15 15:35:22 -0700125 Word,
Clar Charrd22b5702017-03-10 15:24:56 -0500126
David Tolnayb79ee962016-09-04 09:39:20 -0700127 /// List meta item.
128 ///
129 /// E.g. `derive(..)` as in `#[derive(..)]`
Arnavionbf395bf2017-04-15 15:35:22 -0700130 List(Vec<NestedMetaItem>),
Clar Charrd22b5702017-03-10 15:24:56 -0500131
132 /// Name-value meta item.
David Tolnayb79ee962016-09-04 09:39:20 -0700133 ///
134 /// E.g. `feature = "foo"` as in `#[feature = "foo"]`
Arnavionbf395bf2017-04-15 15:35:22 -0700135 NameValue(Lit),
David Tolnay8e661e22016-09-27 00:00:04 -0700136}
137
David Tolnayb7fa2b62016-10-30 10:50:47 -0700138/// Possible values inside of compile-time attribute lists.
139///
140/// E.g. the '..' in `#[name(..)]`.
David Tolnay9bf4af82017-01-07 11:17:46 -0800141#[derive(Debug, Clone, Eq, PartialEq, Hash)]
David Tolnayb7fa2b62016-10-30 10:50:47 -0700142pub enum NestedMetaItem {
Clar Charrd22b5702017-03-10 15:24:56 -0500143 /// A full `MetaItem`.
David Tolnayb7fa2b62016-10-30 10:50:47 -0700144 ///
Arnavionbf395bf2017-04-15 15:35:22 -0700145 /// E.g. `Copy` in `#[derive(Copy)]` would be a `(Ident::from("Copy"), MetaItem::Word)`.
146 MetaItem(Ident, MetaItem),
Clar Charrd22b5702017-03-10 15:24:56 -0500147
148 /// A Rust literal.
149 ///
150 /// E.g. `"name"` in `#[rename("name")]`.
David Tolnayb7fa2b62016-10-30 10:50:47 -0700151 Literal(Lit),
152}
153
David Tolnay4a51dc72016-10-01 00:40:31 -0700154pub trait FilterAttrs<'a> {
155 type Ret: Iterator<Item = &'a Attribute>;
156
157 fn outer(self) -> Self::Ret;
158 fn inner(self) -> Self::Ret;
159}
160
David Tolnaydaaf7742016-10-03 11:11:43 -0700161impl<'a, T> FilterAttrs<'a> for T
162 where T: IntoIterator<Item = &'a Attribute>
163{
David Tolnay4a51dc72016-10-01 00:40:31 -0700164 type Ret = iter::Filter<T::IntoIter, fn(&&Attribute) -> bool>;
165
166 fn outer(self) -> Self::Ret {
167 fn is_outer(attr: &&Attribute) -> bool {
168 attr.style == AttrStyle::Outer
169 }
170 self.into_iter().filter(is_outer)
171 }
172
173 fn inner(self) -> Self::Ret {
174 fn is_inner(attr: &&Attribute) -> bool {
175 attr.style == AttrStyle::Inner
176 }
177 self.into_iter().filter(is_inner)
178 }
179}
180
David Tolnay86eca752016-09-04 11:26:41 -0700181#[cfg(feature = "parsing")]
David Tolnay9d8f1972016-09-04 11:58:48 -0700182pub mod parsing {
183 use super::*;
David Tolnay55337722016-09-11 12:58:56 -0700184 use ident::parsing::ident;
Arnavionbb252732017-04-15 16:15:15 -0700185 use lit::{Lit, StrStyle};
186 use mac::{Token, TokenTree};
Arnavioneaa2cff2017-04-15 03:32:13 -0700187 use mac::parsing::token_trees;
David Tolnay5fe14fc2017-01-27 16:22:08 -0800188 use synom::space::{block_comment, whitespace};
David Tolnayb79ee962016-09-04 09:39:20 -0700189
David Tolnay2a2e67c2016-10-01 14:02:01 -0700190 #[cfg(feature = "full")]
David Tolnay14cbdeb2016-10-01 12:13:59 -0700191 named!(pub inner_attr -> Attribute, alt!(
192 do_parse!(
193 punct!("#") >>
194 punct!("!") >>
Arnavionbf395bf2017-04-15 15:35:22 -0700195 name_and_tts: delimited!(
196 punct!("["),
197 tuple!(ident, token_trees),
198 punct!("]")
199 ) >>
200 ({
201 let (name, tts) = name_and_tts;
202
203 Attribute {
204 style: AttrStyle::Inner,
205 name: name,
206 tts: tts,
207 is_sugared_doc: false,
208 }
David Tolnay14cbdeb2016-10-01 12:13:59 -0700209 })
210 )
211 |
212 do_parse!(
213 punct!("//!") >>
214 content: take_until!("\n") >>
215 (Attribute {
216 style: AttrStyle::Inner,
Arnavionbf395bf2017-04-15 15:35:22 -0700217 name: "doc".into(),
218 tts: vec![
219 TokenTree::Token(Token::Eq),
220 TokenTree::Token(Token::Literal(Lit::Str(format!("//!{}", content).into(), StrStyle::Cooked))),
221 ],
David Tolnay14cbdeb2016-10-01 12:13:59 -0700222 is_sugared_doc: true,
223 })
224 )
225 |
226 do_parse!(
227 option!(whitespace) >>
228 peek!(tag!("/*!")) >>
229 com: block_comment >>
230 (Attribute {
231 style: AttrStyle::Inner,
Arnavionbf395bf2017-04-15 15:35:22 -0700232 name: "doc".into(),
233 tts: vec![
234 TokenTree::Token(Token::Eq),
235 TokenTree::Token(Token::Literal(Lit::Str(com.into(), StrStyle::Cooked))),
236 ],
David Tolnay14cbdeb2016-10-01 12:13:59 -0700237 is_sugared_doc: true,
238 })
239 )
David Tolnay4a51dc72016-10-01 00:40:31 -0700240 ));
241
242 named!(pub outer_attr -> Attribute, alt!(
David Tolnay9d8f1972016-09-04 11:58:48 -0700243 do_parse!(
244 punct!("#") >>
Arnavionbf395bf2017-04-15 15:35:22 -0700245 name_and_tts: delimited!(
246 punct!("["),
247 tuple!(ident, token_trees),
248 punct!("]")
Arnavioneaa2cff2017-04-15 03:32:13 -0700249 ) >>
Arnavionbf395bf2017-04-15 15:35:22 -0700250 ({
251 let (name, tts) = name_and_tts;
252
253 Attribute {
254 style: AttrStyle::Outer,
255 name: name,
256 tts: tts,
257 is_sugared_doc: false,
258 }
Arnavioneaa2cff2017-04-15 03:32:13 -0700259 })
260 )
261 |
262 do_parse!(
David Tolnay9d8f1972016-09-04 11:58:48 -0700263 punct!("///") >>
David Tolnay1f16b602017-02-07 20:06:55 -0500264 not!(tag!("/")) >>
David Tolnayb5a7b142016-09-13 22:46:39 -0700265 content: take_until!("\n") >>
David Tolnay9d8f1972016-09-04 11:58:48 -0700266 (Attribute {
David Tolnay4a51dc72016-10-01 00:40:31 -0700267 style: AttrStyle::Outer,
Arnavionbf395bf2017-04-15 15:35:22 -0700268 name: "doc".into(),
269 tts: vec![
270 TokenTree::Token(Token::Eq),
271 TokenTree::Token(Token::Literal(Lit::Str(format!("///{}", content).into(), StrStyle::Cooked))),
272 ],
David Tolnay9d8f1972016-09-04 11:58:48 -0700273 is_sugared_doc: true,
274 })
275 )
David Tolnay14cbdeb2016-10-01 12:13:59 -0700276 |
277 do_parse!(
278 option!(whitespace) >>
David Tolnay84aa0752016-10-02 23:01:13 -0700279 peek!(tuple!(tag!("/**"), not!(tag!("*")))) >>
David Tolnay14cbdeb2016-10-01 12:13:59 -0700280 com: block_comment >>
281 (Attribute {
282 style: AttrStyle::Outer,
Arnavionbf395bf2017-04-15 15:35:22 -0700283 name: "doc".into(),
284 tts: vec![
285 TokenTree::Token(Token::Eq),
286 TokenTree::Token(Token::Literal(Lit::Str(com.into(), StrStyle::Cooked))),
287 ],
David Tolnay14cbdeb2016-10-01 12:13:59 -0700288 is_sugared_doc: true,
289 })
290 )
David Tolnay9d8f1972016-09-04 11:58:48 -0700291 ));
David Tolnay9d8f1972016-09-04 11:58:48 -0700292}
David Tolnay87d0b442016-09-04 11:52:12 -0700293
294#[cfg(feature = "printing")]
295mod printing {
296 use super::*;
David Tolnayc91dd672016-10-01 01:03:56 -0700297 use lit::{Lit, StrStyle};
Arnavionbb252732017-04-15 16:15:15 -0700298 use mac::{Token, TokenTree};
David Tolnay87d0b442016-09-04 11:52:12 -0700299 use quote::{Tokens, ToTokens};
300
301 impl ToTokens for Attribute {
302 fn to_tokens(&self, tokens: &mut Tokens) {
Arnavionbf395bf2017-04-15 15:35:22 -0700303 if let Attribute { style, ref name, ref tts, is_sugared_doc: true } = *self {
304 if name == "doc" && tts.len() == 2 {
305 match (&tts[0], &tts[1]) {
306 (&TokenTree::Token(Token::Eq),
307 &TokenTree::Token(Token::Literal(Lit::Str(ref value, StrStyle::Cooked)))) => {
308 match style {
309 AttrStyle::Inner if value.starts_with("//!") => {
310 tokens.append(&format!("{}\n", value));
311 return;
312 }
313 AttrStyle::Inner if value.starts_with("/*!") => {
314 tokens.append(value);
315 return;
316 }
317 AttrStyle::Outer if value.starts_with("///") => {
318 tokens.append(&format!("{}\n", value));
319 return;
320 }
321 AttrStyle::Outer if value.starts_with("/**") => {
322 tokens.append(value);
323 return;
324 }
325 _ => {}
326 }
David Tolnay14cbdeb2016-10-01 12:13:59 -0700327 }
Arnavionbf395bf2017-04-15 15:35:22 -0700328
David Tolnay14cbdeb2016-10-01 12:13:59 -0700329 _ => {}
David Tolnay4a51dc72016-10-01 00:40:31 -0700330 }
David Tolnayc91dd672016-10-01 01:03:56 -0700331 }
332 }
David Tolnay14cbdeb2016-10-01 12:13:59 -0700333
334 tokens.append("#");
335 if let AttrStyle::Inner = self.style {
336 tokens.append("!");
337 }
338 tokens.append("[");
Arnavionbf395bf2017-04-15 15:35:22 -0700339 self.name.to_tokens(tokens);
340 tokens.append_all(&self.tts);
David Tolnay14cbdeb2016-10-01 12:13:59 -0700341 tokens.append("]");
David Tolnay87d0b442016-09-04 11:52:12 -0700342 }
343 }
344
345 impl ToTokens for MetaItem {
346 fn to_tokens(&self, tokens: &mut Tokens) {
347 match *self {
Arnavionbf395bf2017-04-15 15:35:22 -0700348 MetaItem::Word => {}
349 MetaItem::List(ref inner) => {
David Tolnay87d0b442016-09-04 11:52:12 -0700350 tokens.append("(");
David Tolnay94ebdf92016-09-04 13:33:16 -0700351 tokens.append_separated(inner, ",");
David Tolnay87d0b442016-09-04 11:52:12 -0700352 tokens.append(")");
353 }
Arnavionbf395bf2017-04-15 15:35:22 -0700354 MetaItem::NameValue(ref value) => {
David Tolnay87d0b442016-09-04 11:52:12 -0700355 tokens.append("=");
356 value.to_tokens(tokens);
357 }
358 }
359 }
360 }
David Tolnayb7fa2b62016-10-30 10:50:47 -0700361
362 impl ToTokens for NestedMetaItem {
363 fn to_tokens(&self, tokens: &mut Tokens) {
364 match *self {
Arnavionbf395bf2017-04-15 15:35:22 -0700365 NestedMetaItem::MetaItem(ref ident, ref nested) => {
366 ident.to_tokens(tokens);
David Tolnayb7fa2b62016-10-30 10:50:47 -0700367 nested.to_tokens(tokens);
368 }
369 NestedMetaItem::Literal(ref lit) => {
370 lit.to_tokens(tokens);
371 }
372 }
373 }
374 }
David Tolnay87d0b442016-09-04 11:52:12 -0700375}