sway_parse/
attribute.rs

1use crate::priv_prelude::{Peek, Peeker};
2use crate::{Parse, ParseBracket, ParseResult, ParseToEnd, Parser, ParserConsumed};
3
4use sway_ast::attribute::{Annotated, Attribute, AttributeArg, AttributeDecl, AttributeHashKind};
5use sway_ast::brackets::{Parens, SquareBrackets};
6use sway_ast::keywords::{EqToken, HashBangToken, HashToken, StorageToken, Token};
7use sway_ast::literal::LitBool;
8use sway_ast::punctuated::Punctuated;
9use sway_ast::token::{DocComment, DocStyle};
10use sway_ast::Literal;
11use sway_error::parser_error::ParseErrorKind;
12use sway_types::constants::DOC_COMMENT_ATTRIBUTE_NAME;
13use sway_types::{Ident, Spanned};
14
15impl Peek for DocComment {
16    fn peek(peeker: Peeker<'_>) -> Option<DocComment> {
17        peeker.peek_doc_comment().ok().cloned()
18    }
19}
20
21impl Parse for DocComment {
22    fn parse(parser: &mut Parser) -> ParseResult<DocComment> {
23        match parser.take::<DocComment>() {
24            Some(doc_comment) => Ok(doc_comment),
25            None => Err(parser.emit_error(ParseErrorKind::ExpectedDocComment)),
26        }
27    }
28}
29
30impl<T: Parse> Parse for Annotated<T> {
31    fn parse(parser: &mut Parser) -> ParseResult<Self> {
32        // Parse the attribute list.
33        let mut attribute_list = Vec::new();
34        while let Some(DocComment {
35            doc_style: DocStyle::Outer,
36            ..
37        }) = parser.peek()
38        {
39            let doc_comment = parser.parse::<DocComment>()?;
40            // TODO: Use a Literal instead of an Ident when Attribute args
41            // start supporting them and remove `Ident::new_no_trim`.
42            let name = Ident::new_no_trim(doc_comment.content_span.clone());
43            attribute_list.push(AttributeDecl {
44                hash_kind: AttributeHashKind::Outer(HashToken::new(doc_comment.span.clone())),
45                attribute: SquareBrackets::new(
46                    Punctuated::single(Attribute {
47                        name: Ident::new_with_override(
48                            DOC_COMMENT_ATTRIBUTE_NAME.to_string(),
49                            doc_comment.span.clone(),
50                        ),
51                        args: Some(Parens::new(
52                            Punctuated::single(AttributeArg { name, value: None }),
53                            doc_comment.content_span,
54                        )),
55                    }),
56                    doc_comment.span,
57                ),
58            });
59        }
60
61        while let Some(attr) = parser.guarded_parse::<HashToken, _>()? {
62            attribute_list.push(attr);
63        }
64
65        if parser.check_empty().is_some() {
66            let error = parser.emit_error(ParseErrorKind::ExpectedAnItemAfterDocComment);
67            Err(error)
68        } else {
69            // Parse the `T` value.
70            let value = match parser.parse_with_recovery() {
71                Ok(value) => value,
72                Err(r) => {
73                    let (spans, error) =
74                        r.recover_at_next_line_with_fallback_error(ParseErrorKind::InvalidItem);
75                    if let Some(error) = T::error(spans, error) {
76                        error
77                    } else {
78                        Err(error)?
79                    }
80                }
81            };
82
83            Ok(Annotated {
84                attribute_list,
85                value,
86            })
87        }
88    }
89
90    fn error(
91        spans: Box<[sway_types::Span]>,
92        error: sway_error::handler::ErrorEmitted,
93    ) -> Option<Self>
94    where
95        Self: Sized,
96    {
97        T::error(spans, error).map(|value| Annotated {
98            attribute_list: vec![],
99            value,
100        })
101    }
102}
103
104impl Parse for AttributeDecl {
105    fn parse(parser: &mut Parser) -> ParseResult<Self> {
106        Ok(AttributeDecl {
107            hash_kind: parser.parse()?,
108            attribute: parser.parse()?,
109        })
110    }
111}
112
113impl Parse for AttributeHashKind {
114    fn parse(parser: &mut Parser) -> ParseResult<Self> {
115        match parser.take::<HashBangToken>() {
116            Some(hash_bang_token) => Ok(AttributeHashKind::Inner(hash_bang_token)),
117            None => match parser.take::<HashToken>() {
118                Some(hash_token) => Ok(AttributeHashKind::Outer(hash_token)),
119                None => Err(parser.emit_error(ParseErrorKind::ExpectedAnAttribute)),
120            },
121        }
122    }
123}
124
125impl Parse for AttributeArg {
126    fn parse(parser: &mut Parser) -> ParseResult<Self> {
127        let name = parser.parse()?;
128        match parser.take::<EqToken>() {
129            Some(_) => {
130                let value = match parser.take::<Ident>() {
131                    Some(ident) if ident.as_str() == "true" => Literal::Bool(LitBool {
132                        span: ident.span(),
133                        kind: sway_ast::literal::LitBoolType::True,
134                    }),
135                    Some(ident) if ident.as_str() == "false" => Literal::Bool(LitBool {
136                        span: ident.span(),
137                        kind: sway_ast::literal::LitBoolType::False,
138                    }),
139                    _ => parser.parse()?,
140                };
141
142                Ok(AttributeArg {
143                    name,
144                    value: Some(value),
145                })
146            }
147            None => Ok(AttributeArg { name, value: None }),
148        }
149    }
150}
151
152impl Parse for Attribute {
153    fn parse(parser: &mut Parser) -> ParseResult<Self> {
154        let name = if let Some(storage) = parser.take::<StorageToken>() {
155            Ident::from(storage)
156        } else {
157            parser.parse()?
158        };
159        let args = Parens::try_parse(parser)?;
160        Ok(Attribute { name, args })
161    }
162}
163
164impl ParseToEnd for Attribute {
165    fn parse_to_end<'a, 'e>(mut parser: Parser<'a, '_>) -> ParseResult<(Self, ParserConsumed<'a>)> {
166        let attrib = parser.parse()?;
167        match parser.check_empty() {
168            Some(consumed) => Ok((attrib, consumed)),
169            None => Err(parser.emit_error(ParseErrorKind::UnexpectedTokenAfterAttribute)),
170        }
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177    use crate::test_utils::parse;
178    use insta::*;
179    use sway_ast::ItemFn;
180
181    #[test]
182    fn parse_annotated_fn() {
183        assert_ron_snapshot!(parse::<Annotated<ItemFn>>(r#"
184            // I will be ignored.
185            //! I will be ignored.
186            /// This is a doc comment.
187            #[storage(read)]
188            fn main() {
189                ()
190            }
191        "#,), @r#"
192        Annotated(
193          attribute_list: [
194            AttributeDecl(
195              hash_kind: Outer(HashToken(
196                span: Span(
197                  src: "\n            // I will be ignored.\n            //! I will be ignored.\n            /// This is a doc comment.\n            #[storage(read)]\n            fn main() {\n                ()\n            }\n        ",
198                  start: 82,
199                  end: 108,
200                  source_id: None,
201                ),
202              )),
203              attribute: SquareBrackets(
204                inner: Punctuated(
205                  value_separator_pairs: [],
206                  final_value_opt: Some(Attribute(
207                    name: BaseIdent(
208                      name_override_opt: Some("doc-comment"),
209                      span: Span(
210                        src: "\n            // I will be ignored.\n            //! I will be ignored.\n            /// This is a doc comment.\n            #[storage(read)]\n            fn main() {\n                ()\n            }\n        ",
211                        start: 82,
212                        end: 108,
213                        source_id: None,
214                      ),
215                      is_raw_ident: false,
216                    ),
217                    args: Some(Parens(
218                      inner: Punctuated(
219                        value_separator_pairs: [],
220                        final_value_opt: Some(AttributeArg(
221                          name: BaseIdent(
222                            name_override_opt: None,
223                            span: Span(
224                              src: "\n            // I will be ignored.\n            //! I will be ignored.\n            /// This is a doc comment.\n            #[storage(read)]\n            fn main() {\n                ()\n            }\n        ",
225                              start: 85,
226                              end: 108,
227                              source_id: None,
228                            ),
229                            is_raw_ident: false,
230                          ),
231                          value: None,
232                        )),
233                      ),
234                      span: Span(
235                        src: "\n            // I will be ignored.\n            //! I will be ignored.\n            /// This is a doc comment.\n            #[storage(read)]\n            fn main() {\n                ()\n            }\n        ",
236                        start: 85,
237                        end: 108,
238                        source_id: None,
239                      ),
240                    )),
241                  )),
242                ),
243                span: Span(
244                  src: "\n            // I will be ignored.\n            //! I will be ignored.\n            /// This is a doc comment.\n            #[storage(read)]\n            fn main() {\n                ()\n            }\n        ",
245                  start: 82,
246                  end: 108,
247                  source_id: None,
248                ),
249              ),
250            ),
251            AttributeDecl(
252              hash_kind: Outer(HashToken(
253                span: Span(
254                  src: "\n            // I will be ignored.\n            //! I will be ignored.\n            /// This is a doc comment.\n            #[storage(read)]\n            fn main() {\n                ()\n            }\n        ",
255                  start: 121,
256                  end: 122,
257                  source_id: None,
258                ),
259              )),
260              attribute: SquareBrackets(
261                inner: Punctuated(
262                  value_separator_pairs: [],
263                  final_value_opt: Some(Attribute(
264                    name: BaseIdent(
265                      name_override_opt: None,
266                      span: Span(
267                        src: "\n            // I will be ignored.\n            //! I will be ignored.\n            /// This is a doc comment.\n            #[storage(read)]\n            fn main() {\n                ()\n            }\n        ",
268                        start: 123,
269                        end: 130,
270                        source_id: None,
271                      ),
272                      is_raw_ident: false,
273                    ),
274                    args: Some(Parens(
275                      inner: Punctuated(
276                        value_separator_pairs: [],
277                        final_value_opt: Some(AttributeArg(
278                          name: BaseIdent(
279                            name_override_opt: None,
280                            span: Span(
281                              src: "\n            // I will be ignored.\n            //! I will be ignored.\n            /// This is a doc comment.\n            #[storage(read)]\n            fn main() {\n                ()\n            }\n        ",
282                              start: 131,
283                              end: 135,
284                              source_id: None,
285                            ),
286                            is_raw_ident: false,
287                          ),
288                          value: None,
289                        )),
290                      ),
291                      span: Span(
292                        src: "\n            // I will be ignored.\n            //! I will be ignored.\n            /// This is a doc comment.\n            #[storage(read)]\n            fn main() {\n                ()\n            }\n        ",
293                        start: 130,
294                        end: 136,
295                        source_id: None,
296                      ),
297                    )),
298                  )),
299                ),
300                span: Span(
301                  src: "\n            // I will be ignored.\n            //! I will be ignored.\n            /// This is a doc comment.\n            #[storage(read)]\n            fn main() {\n                ()\n            }\n        ",
302                  start: 122,
303                  end: 137,
304                  source_id: None,
305                ),
306              ),
307            ),
308          ],
309          value: ItemFn(
310            fn_signature: FnSignature(
311              visibility: None,
312              fn_token: FnToken(
313                span: Span(
314                  src: "\n            // I will be ignored.\n            //! I will be ignored.\n            /// This is a doc comment.\n            #[storage(read)]\n            fn main() {\n                ()\n            }\n        ",
315                  start: 150,
316                  end: 152,
317                  source_id: None,
318                ),
319              ),
320              name: BaseIdent(
321                name_override_opt: None,
322                span: Span(
323                  src: "\n            // I will be ignored.\n            //! I will be ignored.\n            /// This is a doc comment.\n            #[storage(read)]\n            fn main() {\n                ()\n            }\n        ",
324                  start: 153,
325                  end: 157,
326                  source_id: None,
327                ),
328                is_raw_ident: false,
329              ),
330              generics: None,
331              arguments: Parens(
332                inner: Static(Punctuated(
333                  value_separator_pairs: [],
334                  final_value_opt: None,
335                )),
336                span: Span(
337                  src: "\n            // I will be ignored.\n            //! I will be ignored.\n            /// This is a doc comment.\n            #[storage(read)]\n            fn main() {\n                ()\n            }\n        ",
338                  start: 157,
339                  end: 159,
340                  source_id: None,
341                ),
342              ),
343              return_type_opt: None,
344              where_clause_opt: None,
345            ),
346            body: Braces(
347              inner: CodeBlockContents(
348                statements: [],
349                final_expr_opt: Some(Tuple(Parens(
350                  inner: Nil,
351                  span: Span(
352                    src: "\n            // I will be ignored.\n            //! I will be ignored.\n            /// This is a doc comment.\n            #[storage(read)]\n            fn main() {\n                ()\n            }\n        ",
353                    start: 178,
354                    end: 180,
355                    source_id: None,
356                  ),
357                ))),
358                span: Span(
359                  src: "\n            // I will be ignored.\n            //! I will be ignored.\n            /// This is a doc comment.\n            #[storage(read)]\n            fn main() {\n                ()\n            }\n        ",
360                  start: 161,
361                  end: 193,
362                  source_id: None,
363                ),
364              ),
365              span: Span(
366                src: "\n            // I will be ignored.\n            //! I will be ignored.\n            /// This is a doc comment.\n            #[storage(read)]\n            fn main() {\n                ()\n            }\n        ",
367                start: 160,
368                end: 194,
369                source_id: None,
370              ),
371            ),
372          ),
373        )
374        "#);
375    }
376
377    #[test]
378    fn parse_attribute() {
379        assert_ron_snapshot!(parse::<Attribute>(r#"
380            name(arg1, arg2 = "value", arg3)
381        "#,), @r#"
382        Attribute(
383          name: BaseIdent(
384            name_override_opt: None,
385            span: Span(
386              src: "\n            name(arg1, arg2 = \"value\", arg3)\n        ",
387              start: 13,
388              end: 17,
389              source_id: None,
390            ),
391            is_raw_ident: false,
392          ),
393          args: Some(Parens(
394            inner: Punctuated(
395              value_separator_pairs: [
396                (AttributeArg(
397                  name: BaseIdent(
398                    name_override_opt: None,
399                    span: Span(
400                      src: "\n            name(arg1, arg2 = \"value\", arg3)\n        ",
401                      start: 18,
402                      end: 22,
403                      source_id: None,
404                    ),
405                    is_raw_ident: false,
406                  ),
407                  value: None,
408                ), CommaToken(
409                  span: Span(
410                    src: "\n            name(arg1, arg2 = \"value\", arg3)\n        ",
411                    start: 22,
412                    end: 23,
413                    source_id: None,
414                  ),
415                )),
416                (AttributeArg(
417                  name: BaseIdent(
418                    name_override_opt: None,
419                    span: Span(
420                      src: "\n            name(arg1, arg2 = \"value\", arg3)\n        ",
421                      start: 24,
422                      end: 28,
423                      source_id: None,
424                    ),
425                    is_raw_ident: false,
426                  ),
427                  value: Some(String(LitString(
428                    span: Span(
429                      src: "\n            name(arg1, arg2 = \"value\", arg3)\n        ",
430                      start: 31,
431                      end: 38,
432                      source_id: None,
433                    ),
434                    parsed: "value",
435                  ))),
436                ), CommaToken(
437                  span: Span(
438                    src: "\n            name(arg1, arg2 = \"value\", arg3)\n        ",
439                    start: 38,
440                    end: 39,
441                    source_id: None,
442                  ),
443                )),
444              ],
445              final_value_opt: Some(AttributeArg(
446                name: BaseIdent(
447                  name_override_opt: None,
448                  span: Span(
449                    src: "\n            name(arg1, arg2 = \"value\", arg3)\n        ",
450                    start: 40,
451                    end: 44,
452                    source_id: None,
453                  ),
454                  is_raw_ident: false,
455                ),
456                value: None,
457              )),
458            ),
459            span: Span(
460              src: "\n            name(arg1, arg2 = \"value\", arg3)\n        ",
461              start: 17,
462              end: 45,
463              source_id: None,
464            ),
465          )),
466        )
467        "#);
468    }
469}