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 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 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 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}