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