blob: d10c3ed6b502899dd37a70ee67fd9df851f90ced [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 {
Arnavion24770612017-04-19 11:41:12 -070050 if let TokenTree::Token(Token::Eq) = tts[1] {
51 if let TokenTree::Token(Token::Literal(ref lit)) = tts[2] {
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 }
Arnavionbf395bf2017-04-15 15:35:22 -070054 }
55 }
56
57 if tts.len() >= 2 {
58 if let TokenTree::Delimited(Delimited { delim: DelimToken::Paren, tts: ref inner_tts }) = tts[1] {
59 return match list_of_nested_meta_items_from_tokens(vec![], inner_tts) {
60 Some(nested_meta_items) => {
Arnavion95f8a7a2017-04-19 03:29:56 -070061 Some((NestedMetaItem::MetaItem(MetaItem::List(ident.clone(), nested_meta_items)), &tts[2..]))
Arnavionbf395bf2017-04-15 15:35:22 -070062 }
63
64 None => None
65 };
66 }
67 }
68
Arnavion95f8a7a2017-04-19 03:29:56 -070069 Some((NestedMetaItem::MetaItem(MetaItem::Word(ident.clone())), &tts[1..]))
Arnavionbf395bf2017-04-15 15:35:22 -070070 }
71
72 _ => None
73 }
74 }
75
76 fn list_of_nested_meta_items_from_tokens(mut result: Vec<NestedMetaItem>, tts: &[TokenTree]) -> Option<Vec<NestedMetaItem>> {
77 if tts.is_empty() {
78 return Some(result);
79 }
80
81 match nested_meta_item_from_tokens(tts) {
82 Some((nested_meta_item, rest)) => {
83 result.push(nested_meta_item);
84 if rest.is_empty() {
85 list_of_nested_meta_items_from_tokens(result, rest)
86 }
Arnavionaebc06e2017-04-15 16:17:13 -070087 else if let TokenTree::Token(Token::Comma) = rest[0] {
88 list_of_nested_meta_items_from_tokens(result, &rest[1..])
89 }
Arnavionbf395bf2017-04-15 15:35:22 -070090 else {
Arnavionaebc06e2017-04-15 16:17:13 -070091 None
Arnavionbf395bf2017-04-15 15:35:22 -070092 }
93 }
94
95 None => None
96 }
97 }
98
99 if let Some(nested_meta_items) = list_of_nested_meta_items_from_tokens(vec![], tts) {
Arnavion95f8a7a2017-04-19 03:29:56 -0700100 return Some(MetaItem::List(name.clone(), nested_meta_items));
Arnavionbf395bf2017-04-15 15:35:22 -0700101 }
102 }
103 }
104
105 if self.tts.len() == 2 {
Arnavion24770612017-04-19 11:41:12 -0700106 if let TokenTree::Token(Token::Eq) = self.tts[0] {
107 if let TokenTree::Token(Token::Literal(ref lit)) = self.tts[1] {
Arnavion95f8a7a2017-04-19 03:29:56 -0700108 return Some(MetaItem::NameValue(name.clone(), lit.clone()));
Arnavionbf395bf2017-04-15 15:35:22 -0700109 }
Arnavionbf395bf2017-04-15 15:35:22 -0700110 }
111 }
112
113 None
David Tolnay02d77cc2016-10-02 09:52:08 -0700114 }
115}
116
David Tolnay4a51dc72016-10-01 00:40:31 -0700117/// Distinguishes between Attributes that decorate items and Attributes that
118/// are contained as statements within items. These two cases need to be
119/// distinguished for pretty-printing.
David Tolnay9bf4af82017-01-07 11:17:46 -0800120#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
David Tolnay4a51dc72016-10-01 00:40:31 -0700121pub enum AttrStyle {
Clar Charrd22b5702017-03-10 15:24:56 -0500122 /// Attribute of the form `#![...]`.
David Tolnay4a51dc72016-10-01 00:40:31 -0700123 Outer,
Clar Charrd22b5702017-03-10 15:24:56 -0500124
125 /// Attribute of the form `#[...]`.
David Tolnay4a51dc72016-10-01 00:40:31 -0700126 Inner,
127}
128
David Tolnayb79ee962016-09-04 09:39:20 -0700129/// A compile-time attribute item.
130///
131/// E.g. `#[test]`, `#[derive(..)]` or `#[feature = "foo"]`
David Tolnay9bf4af82017-01-07 11:17:46 -0800132#[derive(Debug, Clone, Eq, PartialEq, Hash)]
David Tolnayb79ee962016-09-04 09:39:20 -0700133pub enum MetaItem {
134 /// Word meta item.
135 ///
136 /// E.g. `test` as in `#[test]`
Arnavion95f8a7a2017-04-19 03:29:56 -0700137 Word(Ident),
Clar Charrd22b5702017-03-10 15:24:56 -0500138
David Tolnayb79ee962016-09-04 09:39:20 -0700139 /// List meta item.
140 ///
141 /// E.g. `derive(..)` as in `#[derive(..)]`
Arnavion95f8a7a2017-04-19 03:29:56 -0700142 List(Ident, Vec<NestedMetaItem>),
Clar Charrd22b5702017-03-10 15:24:56 -0500143
144 /// Name-value meta item.
David Tolnayb79ee962016-09-04 09:39:20 -0700145 ///
146 /// E.g. `feature = "foo"` as in `#[feature = "foo"]`
Arnavion95f8a7a2017-04-19 03:29:56 -0700147 NameValue(Ident, Lit),
148}
149
150impl MetaItem {
151 /// Name of the item.
152 ///
153 /// E.g. `test` as in `#[test]`, `derive` as in `#[derive(..)]`, and
154 /// `feature` as in `#[feature = "foo"]`.
155 pub fn name(&self) -> &str {
156 match *self {
157 MetaItem::Word(ref name) |
158 MetaItem::List(ref name, _) |
159 MetaItem::NameValue(ref name, _) => name.as_ref(),
160 }
161 }
David Tolnay8e661e22016-09-27 00:00:04 -0700162}
163
David Tolnayb7fa2b62016-10-30 10:50:47 -0700164/// Possible values inside of compile-time attribute lists.
165///
166/// E.g. the '..' in `#[name(..)]`.
David Tolnay9bf4af82017-01-07 11:17:46 -0800167#[derive(Debug, Clone, Eq, PartialEq, Hash)]
David Tolnayb7fa2b62016-10-30 10:50:47 -0700168pub enum NestedMetaItem {
Clar Charrd22b5702017-03-10 15:24:56 -0500169 /// A full `MetaItem`.
David Tolnayb7fa2b62016-10-30 10:50:47 -0700170 ///
Arnavion95f8a7a2017-04-19 03:29:56 -0700171 /// E.g. `Copy` in `#[derive(Copy)]` would be a `MetaItem::Word(Ident::from("Copy"))`.
172 MetaItem(MetaItem),
Clar Charrd22b5702017-03-10 15:24:56 -0500173
174 /// A Rust literal.
175 ///
176 /// E.g. `"name"` in `#[rename("name")]`.
David Tolnayb7fa2b62016-10-30 10:50:47 -0700177 Literal(Lit),
178}
179
David Tolnay4a51dc72016-10-01 00:40:31 -0700180pub trait FilterAttrs<'a> {
181 type Ret: Iterator<Item = &'a Attribute>;
182
183 fn outer(self) -> Self::Ret;
184 fn inner(self) -> Self::Ret;
185}
186
David Tolnaydaaf7742016-10-03 11:11:43 -0700187impl<'a, T> FilterAttrs<'a> for T
188 where T: IntoIterator<Item = &'a Attribute>
189{
David Tolnay4a51dc72016-10-01 00:40:31 -0700190 type Ret = iter::Filter<T::IntoIter, fn(&&Attribute) -> bool>;
191
192 fn outer(self) -> Self::Ret {
193 fn is_outer(attr: &&Attribute) -> bool {
194 attr.style == AttrStyle::Outer
195 }
196 self.into_iter().filter(is_outer)
197 }
198
199 fn inner(self) -> Self::Ret {
200 fn is_inner(attr: &&Attribute) -> bool {
201 attr.style == AttrStyle::Inner
202 }
203 self.into_iter().filter(is_inner)
204 }
205}
206
David Tolnay86eca752016-09-04 11:26:41 -0700207#[cfg(feature = "parsing")]
David Tolnay9d8f1972016-09-04 11:58:48 -0700208pub mod parsing {
209 use super::*;
Arnavionbb252732017-04-15 16:15:15 -0700210 use lit::{Lit, StrStyle};
211 use mac::{Token, TokenTree};
Arnavioneaa2cff2017-04-15 03:32:13 -0700212 use mac::parsing::token_trees;
David Tolnay5fe14fc2017-01-27 16:22:08 -0800213 use synom::space::{block_comment, whitespace};
Arnavione43b1b02017-04-19 02:47:45 -0700214 use ty::parsing::path;
David Tolnayb79ee962016-09-04 09:39:20 -0700215
David Tolnay2a2e67c2016-10-01 14:02:01 -0700216 #[cfg(feature = "full")]
David Tolnay14cbdeb2016-10-01 12:13:59 -0700217 named!(pub inner_attr -> Attribute, alt!(
218 do_parse!(
219 punct!("#") >>
220 punct!("!") >>
Arnavione43b1b02017-04-19 02:47:45 -0700221 path_and_tts: delimited!(
Arnavionbf395bf2017-04-15 15:35:22 -0700222 punct!("["),
Arnavione43b1b02017-04-19 02:47:45 -0700223 tuple!(path, token_trees),
Arnavionbf395bf2017-04-15 15:35:22 -0700224 punct!("]")
225 ) >>
226 ({
Arnavione43b1b02017-04-19 02:47:45 -0700227 let (path, tts) = path_and_tts;
Arnavionbf395bf2017-04-15 15:35:22 -0700228
229 Attribute {
230 style: AttrStyle::Inner,
Arnavione43b1b02017-04-19 02:47:45 -0700231 path: path,
Arnavionbf395bf2017-04-15 15:35:22 -0700232 tts: tts,
233 is_sugared_doc: false,
234 }
David Tolnay14cbdeb2016-10-01 12:13:59 -0700235 })
236 )
237 |
238 do_parse!(
239 punct!("//!") >>
240 content: take_until!("\n") >>
241 (Attribute {
242 style: AttrStyle::Inner,
Arnavione43b1b02017-04-19 02:47:45 -0700243 path: "doc".into(),
Arnavionbf395bf2017-04-15 15:35:22 -0700244 tts: vec![
245 TokenTree::Token(Token::Eq),
246 TokenTree::Token(Token::Literal(Lit::Str(format!("//!{}", content).into(), StrStyle::Cooked))),
247 ],
David Tolnay14cbdeb2016-10-01 12:13:59 -0700248 is_sugared_doc: true,
249 })
250 )
251 |
252 do_parse!(
253 option!(whitespace) >>
254 peek!(tag!("/*!")) >>
255 com: block_comment >>
256 (Attribute {
257 style: AttrStyle::Inner,
Arnavione43b1b02017-04-19 02:47:45 -0700258 path: "doc".into(),
Arnavionbf395bf2017-04-15 15:35:22 -0700259 tts: vec![
260 TokenTree::Token(Token::Eq),
261 TokenTree::Token(Token::Literal(Lit::Str(com.into(), StrStyle::Cooked))),
262 ],
David Tolnay14cbdeb2016-10-01 12:13:59 -0700263 is_sugared_doc: true,
264 })
265 )
David Tolnay4a51dc72016-10-01 00:40:31 -0700266 ));
267
268 named!(pub outer_attr -> Attribute, alt!(
David Tolnay9d8f1972016-09-04 11:58:48 -0700269 do_parse!(
270 punct!("#") >>
Arnavione43b1b02017-04-19 02:47:45 -0700271 path_and_tts: delimited!(
Arnavionbf395bf2017-04-15 15:35:22 -0700272 punct!("["),
Arnavione43b1b02017-04-19 02:47:45 -0700273 tuple!(path, token_trees),
Arnavionbf395bf2017-04-15 15:35:22 -0700274 punct!("]")
Arnavioneaa2cff2017-04-15 03:32:13 -0700275 ) >>
Arnavionbf395bf2017-04-15 15:35:22 -0700276 ({
Arnavione43b1b02017-04-19 02:47:45 -0700277 let (path, tts) = path_and_tts;
Arnavionbf395bf2017-04-15 15:35:22 -0700278
279 Attribute {
280 style: AttrStyle::Outer,
Arnavione43b1b02017-04-19 02:47:45 -0700281 path: path,
Arnavionbf395bf2017-04-15 15:35:22 -0700282 tts: tts,
283 is_sugared_doc: false,
284 }
Arnavioneaa2cff2017-04-15 03:32:13 -0700285 })
286 )
287 |
288 do_parse!(
David Tolnay9d8f1972016-09-04 11:58:48 -0700289 punct!("///") >>
David Tolnay1f16b602017-02-07 20:06:55 -0500290 not!(tag!("/")) >>
David Tolnayb5a7b142016-09-13 22:46:39 -0700291 content: take_until!("\n") >>
David Tolnay9d8f1972016-09-04 11:58:48 -0700292 (Attribute {
David Tolnay4a51dc72016-10-01 00:40:31 -0700293 style: AttrStyle::Outer,
Arnavione43b1b02017-04-19 02:47:45 -0700294 path: "doc".into(),
Arnavionbf395bf2017-04-15 15:35:22 -0700295 tts: vec![
296 TokenTree::Token(Token::Eq),
297 TokenTree::Token(Token::Literal(Lit::Str(format!("///{}", content).into(), StrStyle::Cooked))),
298 ],
David Tolnay9d8f1972016-09-04 11:58:48 -0700299 is_sugared_doc: true,
300 })
301 )
David Tolnay14cbdeb2016-10-01 12:13:59 -0700302 |
303 do_parse!(
304 option!(whitespace) >>
David Tolnay84aa0752016-10-02 23:01:13 -0700305 peek!(tuple!(tag!("/**"), not!(tag!("*")))) >>
David Tolnay14cbdeb2016-10-01 12:13:59 -0700306 com: block_comment >>
307 (Attribute {
308 style: AttrStyle::Outer,
Arnavione43b1b02017-04-19 02:47:45 -0700309 path: "doc".into(),
Arnavionbf395bf2017-04-15 15:35:22 -0700310 tts: vec![
311 TokenTree::Token(Token::Eq),
312 TokenTree::Token(Token::Literal(Lit::Str(com.into(), StrStyle::Cooked))),
313 ],
David Tolnay14cbdeb2016-10-01 12:13:59 -0700314 is_sugared_doc: true,
315 })
316 )
David Tolnay9d8f1972016-09-04 11:58:48 -0700317 ));
David Tolnay9d8f1972016-09-04 11:58:48 -0700318}
David Tolnay87d0b442016-09-04 11:52:12 -0700319
320#[cfg(feature = "printing")]
321mod printing {
322 use super::*;
David Tolnayc91dd672016-10-01 01:03:56 -0700323 use lit::{Lit, StrStyle};
Arnavionbb252732017-04-15 16:15:15 -0700324 use mac::{Token, TokenTree};
David Tolnay87d0b442016-09-04 11:52:12 -0700325 use quote::{Tokens, ToTokens};
Arnaviona115eba2017-04-19 03:05:52 -0700326 use ty::Path;
David Tolnay87d0b442016-09-04 11:52:12 -0700327
328 impl ToTokens for Attribute {
329 fn to_tokens(&self, tokens: &mut Tokens) {
Arnavion44d2bf32017-04-19 02:47:55 -0700330 // If this was a sugared doc, emit it in its original form instead of `#[doc = "..."]`
Arnavione43b1b02017-04-19 02:47:45 -0700331 match *self {
332 Attribute {
333 style,
334 path: Path { global: false, ref segments },
335 ref tts,
336 is_sugared_doc: true,
337 } if segments.len() == 1 &&
338 segments[0].ident == "doc" &&
339 segments[0].parameters.is_empty() &&
340 tts.len() == 2 =>
341 {
Arnavion24770612017-04-19 11:41:12 -0700342 if let TokenTree::Token(Token::Eq) = self.tts[0] {
343 if let TokenTree::Token(Token::Literal(Lit::Str(ref value, StrStyle::Cooked))) = self.tts[1] {
Arnavionbf395bf2017-04-15 15:35:22 -0700344 match style {
345 AttrStyle::Inner if value.starts_with("//!") => {
346 tokens.append(&format!("{}\n", value));
347 return;
348 }
349 AttrStyle::Inner if value.starts_with("/*!") => {
350 tokens.append(value);
351 return;
352 }
353 AttrStyle::Outer if value.starts_with("///") => {
354 tokens.append(&format!("{}\n", value));
355 return;
356 }
357 AttrStyle::Outer if value.starts_with("/**") => {
358 tokens.append(value);
359 return;
360 }
361 _ => {}
362 }
David Tolnay14cbdeb2016-10-01 12:13:59 -0700363 }
David Tolnay4a51dc72016-10-01 00:40:31 -0700364 }
David Tolnayc91dd672016-10-01 01:03:56 -0700365 }
Arnavione43b1b02017-04-19 02:47:45 -0700366
367 _ => {}
David Tolnayc91dd672016-10-01 01:03:56 -0700368 }
David Tolnay14cbdeb2016-10-01 12:13:59 -0700369
370 tokens.append("#");
371 if let AttrStyle::Inner = self.style {
372 tokens.append("!");
373 }
374 tokens.append("[");
Arnavione43b1b02017-04-19 02:47:45 -0700375 self.path.to_tokens(tokens);
Arnavionbf395bf2017-04-15 15:35:22 -0700376 tokens.append_all(&self.tts);
David Tolnay14cbdeb2016-10-01 12:13:59 -0700377 tokens.append("]");
David Tolnay87d0b442016-09-04 11:52:12 -0700378 }
379 }
380
381 impl ToTokens for MetaItem {
382 fn to_tokens(&self, tokens: &mut Tokens) {
383 match *self {
Arnavion95f8a7a2017-04-19 03:29:56 -0700384 MetaItem::Word(ref ident) => {
385 ident.to_tokens(tokens);
386 }
387 MetaItem::List(ref ident, ref inner) => {
388 ident.to_tokens(tokens);
David Tolnay87d0b442016-09-04 11:52:12 -0700389 tokens.append("(");
David Tolnay94ebdf92016-09-04 13:33:16 -0700390 tokens.append_separated(inner, ",");
David Tolnay87d0b442016-09-04 11:52:12 -0700391 tokens.append(")");
392 }
Arnavion95f8a7a2017-04-19 03:29:56 -0700393 MetaItem::NameValue(ref name, ref value) => {
394 name.to_tokens(tokens);
David Tolnay87d0b442016-09-04 11:52:12 -0700395 tokens.append("=");
396 value.to_tokens(tokens);
397 }
398 }
399 }
400 }
David Tolnayb7fa2b62016-10-30 10:50:47 -0700401
402 impl ToTokens for NestedMetaItem {
403 fn to_tokens(&self, tokens: &mut Tokens) {
404 match *self {
Arnavion95f8a7a2017-04-19 03:29:56 -0700405 NestedMetaItem::MetaItem(ref nested) => {
David Tolnayb7fa2b62016-10-30 10:50:47 -0700406 nested.to_tokens(tokens);
407 }
408 NestedMetaItem::Literal(ref lit) => {
409 lit.to_tokens(tokens);
410 }
411 }
412 }
413 }
David Tolnay87d0b442016-09-04 11:52:12 -0700414}