| 1 | #![allow ( |
| 2 | clippy::assertions_on_result_states, |
| 3 | clippy::non_ascii_literal, |
| 4 | clippy::uninlined_format_args |
| 5 | )] |
| 6 | |
| 7 | #[macro_use ] |
| 8 | mod macros; |
| 9 | |
| 10 | use proc_macro2::{Delimiter, Group, Ident, Span, TokenStream, TokenTree}; |
| 11 | use quote::{quote, ToTokens as _}; |
| 12 | use syn::parse::Parser as _; |
| 13 | use syn::{Block, Stmt}; |
| 14 | |
| 15 | #[test] |
| 16 | fn test_raw_operator() { |
| 17 | let stmt = syn::parse_str::<Stmt>("let _ = &raw const x;" ).unwrap(); |
| 18 | |
| 19 | snapshot!(stmt, @r###" |
| 20 | Stmt::Local { |
| 21 | pat: Pat::Wild, |
| 22 | init: Some(LocalInit { |
| 23 | expr: Expr::Verbatim(`& raw const x`), |
| 24 | }), |
| 25 | } |
| 26 | "### ); |
| 27 | } |
| 28 | |
| 29 | #[test] |
| 30 | fn test_raw_variable() { |
| 31 | let stmt = syn::parse_str::<Stmt>("let _ = &raw;" ).unwrap(); |
| 32 | |
| 33 | snapshot!(stmt, @r###" |
| 34 | Stmt::Local { |
| 35 | pat: Pat::Wild, |
| 36 | init: Some(LocalInit { |
| 37 | expr: Expr::Reference { |
| 38 | expr: Expr::Path { |
| 39 | path: Path { |
| 40 | segments: [ |
| 41 | PathSegment { |
| 42 | ident: "raw", |
| 43 | }, |
| 44 | ], |
| 45 | }, |
| 46 | }, |
| 47 | }, |
| 48 | }), |
| 49 | } |
| 50 | "### ); |
| 51 | } |
| 52 | |
| 53 | #[test] |
| 54 | fn test_raw_invalid() { |
| 55 | assert!(syn::parse_str::<Stmt>("let _ = &raw x;" ).is_err()); |
| 56 | } |
| 57 | |
| 58 | #[test] |
| 59 | fn test_none_group() { |
| 60 | // <Ø async fn f() {} Ø> |
| 61 | let tokens = TokenStream::from_iter(vec![TokenTree::Group(Group::new( |
| 62 | Delimiter::None, |
| 63 | TokenStream::from_iter(vec![ |
| 64 | TokenTree::Ident(Ident::new("async" , Span::call_site())), |
| 65 | TokenTree::Ident(Ident::new("fn" , Span::call_site())), |
| 66 | TokenTree::Ident(Ident::new("f" , Span::call_site())), |
| 67 | TokenTree::Group(Group::new(Delimiter::Parenthesis, TokenStream::new())), |
| 68 | TokenTree::Group(Group::new(Delimiter::Brace, TokenStream::new())), |
| 69 | ]), |
| 70 | ))]); |
| 71 | snapshot!(tokens as Stmt, @r###" |
| 72 | Stmt::Item(Item::Fn { |
| 73 | vis: Visibility::Inherited, |
| 74 | sig: Signature { |
| 75 | asyncness: Some, |
| 76 | ident: "f", |
| 77 | generics: Generics, |
| 78 | output: ReturnType::Default, |
| 79 | }, |
| 80 | block: Block { |
| 81 | stmts: [], |
| 82 | }, |
| 83 | }) |
| 84 | "### ); |
| 85 | |
| 86 | let tokens = Group::new(Delimiter::None, quote!(let None = None)).to_token_stream(); |
| 87 | let stmts = Block::parse_within.parse2(tokens).unwrap(); |
| 88 | snapshot!(stmts, @r###" |
| 89 | [ |
| 90 | Stmt::Expr( |
| 91 | Expr::Group { |
| 92 | expr: Expr::Let { |
| 93 | pat: Pat::Ident { |
| 94 | ident: "None", |
| 95 | }, |
| 96 | expr: Expr::Path { |
| 97 | path: Path { |
| 98 | segments: [ |
| 99 | PathSegment { |
| 100 | ident: "None", |
| 101 | }, |
| 102 | ], |
| 103 | }, |
| 104 | }, |
| 105 | }, |
| 106 | }, |
| 107 | None, |
| 108 | ), |
| 109 | ] |
| 110 | "### ); |
| 111 | } |
| 112 | |
| 113 | #[test] |
| 114 | fn test_let_dot_dot() { |
| 115 | let tokens = quote! { |
| 116 | let .. = 10; |
| 117 | }; |
| 118 | |
| 119 | snapshot!(tokens as Stmt, @r###" |
| 120 | Stmt::Local { |
| 121 | pat: Pat::Rest, |
| 122 | init: Some(LocalInit { |
| 123 | expr: Expr::Lit { |
| 124 | lit: 10, |
| 125 | }, |
| 126 | }), |
| 127 | } |
| 128 | "### ); |
| 129 | } |
| 130 | |
| 131 | #[test] |
| 132 | fn test_let_else() { |
| 133 | let tokens = quote! { |
| 134 | let Some(x) = None else { return 0; }; |
| 135 | }; |
| 136 | |
| 137 | snapshot!(tokens as Stmt, @r###" |
| 138 | Stmt::Local { |
| 139 | pat: Pat::TupleStruct { |
| 140 | path: Path { |
| 141 | segments: [ |
| 142 | PathSegment { |
| 143 | ident: "Some", |
| 144 | }, |
| 145 | ], |
| 146 | }, |
| 147 | elems: [ |
| 148 | Pat::Ident { |
| 149 | ident: "x", |
| 150 | }, |
| 151 | ], |
| 152 | }, |
| 153 | init: Some(LocalInit { |
| 154 | expr: Expr::Path { |
| 155 | path: Path { |
| 156 | segments: [ |
| 157 | PathSegment { |
| 158 | ident: "None", |
| 159 | }, |
| 160 | ], |
| 161 | }, |
| 162 | }, |
| 163 | diverge: Some(Expr::Block { |
| 164 | block: Block { |
| 165 | stmts: [ |
| 166 | Stmt::Expr( |
| 167 | Expr::Return { |
| 168 | expr: Some(Expr::Lit { |
| 169 | lit: 0, |
| 170 | }), |
| 171 | }, |
| 172 | Some, |
| 173 | ), |
| 174 | ], |
| 175 | }, |
| 176 | }), |
| 177 | }), |
| 178 | } |
| 179 | "### ); |
| 180 | } |
| 181 | |
| 182 | #[test] |
| 183 | fn test_macros() { |
| 184 | let tokens = quote! { |
| 185 | fn main() { |
| 186 | macro_rules! mac {} |
| 187 | thread_local! { static FOO } |
| 188 | println!("" ); |
| 189 | vec![] |
| 190 | } |
| 191 | }; |
| 192 | |
| 193 | snapshot!(tokens as Stmt, @r###" |
| 194 | Stmt::Item(Item::Fn { |
| 195 | vis: Visibility::Inherited, |
| 196 | sig: Signature { |
| 197 | ident: "main", |
| 198 | generics: Generics, |
| 199 | output: ReturnType::Default, |
| 200 | }, |
| 201 | block: Block { |
| 202 | stmts: [ |
| 203 | Stmt::Item(Item::Macro { |
| 204 | ident: Some("mac"), |
| 205 | mac: Macro { |
| 206 | path: Path { |
| 207 | segments: [ |
| 208 | PathSegment { |
| 209 | ident: "macro_rules", |
| 210 | }, |
| 211 | ], |
| 212 | }, |
| 213 | delimiter: MacroDelimiter::Brace, |
| 214 | tokens: TokenStream(``), |
| 215 | }, |
| 216 | }), |
| 217 | Stmt::Macro { |
| 218 | mac: Macro { |
| 219 | path: Path { |
| 220 | segments: [ |
| 221 | PathSegment { |
| 222 | ident: "thread_local", |
| 223 | }, |
| 224 | ], |
| 225 | }, |
| 226 | delimiter: MacroDelimiter::Brace, |
| 227 | tokens: TokenStream(`static FOO`), |
| 228 | }, |
| 229 | }, |
| 230 | Stmt::Macro { |
| 231 | mac: Macro { |
| 232 | path: Path { |
| 233 | segments: [ |
| 234 | PathSegment { |
| 235 | ident: "println", |
| 236 | }, |
| 237 | ], |
| 238 | }, |
| 239 | delimiter: MacroDelimiter::Paren, |
| 240 | tokens: TokenStream(`""`), |
| 241 | }, |
| 242 | semi_token: Some, |
| 243 | }, |
| 244 | Stmt::Expr( |
| 245 | Expr::Macro { |
| 246 | mac: Macro { |
| 247 | path: Path { |
| 248 | segments: [ |
| 249 | PathSegment { |
| 250 | ident: "vec", |
| 251 | }, |
| 252 | ], |
| 253 | }, |
| 254 | delimiter: MacroDelimiter::Bracket, |
| 255 | tokens: TokenStream(``), |
| 256 | }, |
| 257 | }, |
| 258 | None, |
| 259 | ), |
| 260 | ], |
| 261 | }, |
| 262 | }) |
| 263 | "### ); |
| 264 | } |
| 265 | |
| 266 | #[test] |
| 267 | fn test_early_parse_loop() { |
| 268 | // The following is an Expr::Loop followed by Expr::Tuple. It is not an |
| 269 | // Expr::Call. |
| 270 | let tokens = quote! { |
| 271 | loop {} |
| 272 | () |
| 273 | }; |
| 274 | |
| 275 | let stmts = Block::parse_within.parse2(tokens).unwrap(); |
| 276 | |
| 277 | snapshot!(stmts, @r###" |
| 278 | [ |
| 279 | Stmt::Expr( |
| 280 | Expr::Loop { |
| 281 | body: Block { |
| 282 | stmts: [], |
| 283 | }, |
| 284 | }, |
| 285 | None, |
| 286 | ), |
| 287 | Stmt::Expr( |
| 288 | Expr::Tuple, |
| 289 | None, |
| 290 | ), |
| 291 | ] |
| 292 | "### ); |
| 293 | |
| 294 | let tokens = quote! { |
| 295 | 'a: loop {} |
| 296 | () |
| 297 | }; |
| 298 | |
| 299 | let stmts = Block::parse_within.parse2(tokens).unwrap(); |
| 300 | |
| 301 | snapshot!(stmts, @r###" |
| 302 | [ |
| 303 | Stmt::Expr( |
| 304 | Expr::Loop { |
| 305 | label: Some(Label { |
| 306 | name: Lifetime { |
| 307 | ident: "a", |
| 308 | }, |
| 309 | }), |
| 310 | body: Block { |
| 311 | stmts: [], |
| 312 | }, |
| 313 | }, |
| 314 | None, |
| 315 | ), |
| 316 | Stmt::Expr( |
| 317 | Expr::Tuple, |
| 318 | None, |
| 319 | ), |
| 320 | ] |
| 321 | "### ); |
| 322 | } |
| 323 | |