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 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 let error = if attributes
71 .iter()
72 .all(|attr| attr.is_inner() && attr.is_doc_comment())
73 {
74 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 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}