1#![allow(clippy::single_element_loop, clippy::uninlined_format_args)] 2 3#[macro_use] 4mod macros; 5 6use proc_macro2::{Delimiter, Group}; 7use quote::{quote, ToTokens as _}; 8use syn::punctuated::Punctuated; 9use syn::{parse_quote, token, Expr, ExprRange, ExprTuple, Stmt, Token}; 10 11#[test] 12fn test_expr_parse() { 13 let tokens = quote!(..100u32); 14 snapshot!(tokens as Expr, @r###" 15 Expr::Range { 16 limits: RangeLimits::HalfOpen, 17 end: Some(Expr::Lit { 18 lit: 100u32, 19 }), 20 } 21 "###); 22 23 let tokens = quote!(..100u32); 24 snapshot!(tokens as ExprRange, @r###" 25 ExprRange { 26 limits: RangeLimits::HalfOpen, 27 end: Some(Expr::Lit { 28 lit: 100u32, 29 }), 30 } 31 "###); 32} 33 34#[test] 35fn test_await() { 36 // Must not parse as Expr::Field. 37 let tokens = quote!(fut.await); 38 39 snapshot!(tokens as Expr, @r###" 40 Expr::Await { 41 base: Expr::Path { 42 path: Path { 43 segments: [ 44 PathSegment { 45 ident: "fut", 46 }, 47 ], 48 }, 49 }, 50 } 51 "###); 52} 53 54#[rustfmt::skip] 55#[test] 56fn test_tuple_multi_index() { 57 let expected = snapshot!("tuple.0.0" as Expr, @r###" 58 Expr::Field { 59 base: Expr::Field { 60 base: Expr::Path { 61 path: Path { 62 segments: [ 63 PathSegment { 64 ident: "tuple", 65 }, 66 ], 67 }, 68 }, 69 member: Member::Unnamed(Index { 70 index: 0, 71 }), 72 }, 73 member: Member::Unnamed(Index { 74 index: 0, 75 }), 76 } 77 "###); 78 79 for &input in &[ 80 "tuple .0.0", 81 "tuple. 0.0", 82 "tuple.0 .0", 83 "tuple.0. 0", 84 "tuple . 0 . 0", 85 ] { 86 assert_eq!(expected, syn::parse_str(input).unwrap()); 87 } 88 89 for tokens in [ 90 quote!(tuple.0.0), 91 quote!(tuple .0.0), 92 quote!(tuple. 0.0), 93 quote!(tuple.0 .0), 94 quote!(tuple.0. 0), 95 quote!(tuple . 0 . 0), 96 ] { 97 assert_eq!(expected, syn::parse2(tokens).unwrap()); 98 } 99} 100 101#[test] 102fn test_macro_variable_func() { 103 // mimics the token stream corresponding to `$fn()` 104 let path = Group::new(Delimiter::None, quote!(f)); 105 let tokens = quote!(#path()); 106 107 snapshot!(tokens as Expr, @r###" 108 Expr::Call { 109 func: Expr::Group { 110 expr: Expr::Path { 111 path: Path { 112 segments: [ 113 PathSegment { 114 ident: "f", 115 }, 116 ], 117 }, 118 }, 119 }, 120 } 121 "###); 122 123 let path = Group::new(Delimiter::None, quote! { #[inside] f }); 124 let tokens = quote!(#[outside] #path()); 125 126 snapshot!(tokens as Expr, @r###" 127 Expr::Call { 128 attrs: [ 129 Attribute { 130 style: AttrStyle::Outer, 131 meta: Meta::Path { 132 segments: [ 133 PathSegment { 134 ident: "outside", 135 }, 136 ], 137 }, 138 }, 139 ], 140 func: Expr::Group { 141 expr: Expr::Path { 142 attrs: [ 143 Attribute { 144 style: AttrStyle::Outer, 145 meta: Meta::Path { 146 segments: [ 147 PathSegment { 148 ident: "inside", 149 }, 150 ], 151 }, 152 }, 153 ], 154 path: Path { 155 segments: [ 156 PathSegment { 157 ident: "f", 158 }, 159 ], 160 }, 161 }, 162 }, 163 } 164 "###); 165} 166 167#[test] 168fn test_macro_variable_macro() { 169 // mimics the token stream corresponding to `$macro!()` 170 let mac = Group::new(Delimiter::None, quote!(m)); 171 let tokens = quote!(#mac!()); 172 173 snapshot!(tokens as Expr, @r###" 174 Expr::Macro { 175 mac: Macro { 176 path: Path { 177 segments: [ 178 PathSegment { 179 ident: "m", 180 }, 181 ], 182 }, 183 delimiter: MacroDelimiter::Paren, 184 tokens: TokenStream(``), 185 }, 186 } 187 "###); 188} 189 190#[test] 191fn test_macro_variable_struct() { 192 // mimics the token stream corresponding to `$struct {}` 193 let s = Group::new(Delimiter::None, quote! { S }); 194 let tokens = quote!(#s {}); 195 196 snapshot!(tokens as Expr, @r###" 197 Expr::Struct { 198 path: Path { 199 segments: [ 200 PathSegment { 201 ident: "S", 202 }, 203 ], 204 }, 205 } 206 "###); 207} 208 209#[test] 210fn test_macro_variable_unary() { 211 // mimics the token stream corresponding to `$expr.method()` where expr is `&self` 212 let inner = Group::new(Delimiter::None, quote!(&self)); 213 let tokens = quote!(#inner.method()); 214 snapshot!(tokens as Expr, @r###" 215 Expr::MethodCall { 216 receiver: Expr::Group { 217 expr: Expr::Reference { 218 expr: Expr::Path { 219 path: Path { 220 segments: [ 221 PathSegment { 222 ident: "self", 223 }, 224 ], 225 }, 226 }, 227 }, 228 }, 229 method: "method", 230 } 231 "###); 232} 233 234#[test] 235fn test_macro_variable_match_arm() { 236 // mimics the token stream corresponding to `match v { _ => $expr }` 237 let expr = Group::new(Delimiter::None, quote! { #[a] () }); 238 let tokens = quote!(match v { _ => #expr }); 239 snapshot!(tokens as Expr, @r###" 240 Expr::Match { 241 expr: Expr::Path { 242 path: Path { 243 segments: [ 244 PathSegment { 245 ident: "v", 246 }, 247 ], 248 }, 249 }, 250 arms: [ 251 Arm { 252 pat: Pat::Wild, 253 body: Expr::Group { 254 expr: Expr::Tuple { 255 attrs: [ 256 Attribute { 257 style: AttrStyle::Outer, 258 meta: Meta::Path { 259 segments: [ 260 PathSegment { 261 ident: "a", 262 }, 263 ], 264 }, 265 }, 266 ], 267 }, 268 }, 269 }, 270 ], 271 } 272 "###); 273 274 let expr = Group::new(Delimiter::None, quote!(loop {} + 1)); 275 let tokens = quote!(match v { _ => #expr }); 276 snapshot!(tokens as Expr, @r###" 277 Expr::Match { 278 expr: Expr::Path { 279 path: Path { 280 segments: [ 281 PathSegment { 282 ident: "v", 283 }, 284 ], 285 }, 286 }, 287 arms: [ 288 Arm { 289 pat: Pat::Wild, 290 body: Expr::Group { 291 expr: Expr::Binary { 292 left: Expr::Loop { 293 body: Block { 294 stmts: [], 295 }, 296 }, 297 op: BinOp::Add, 298 right: Expr::Lit { 299 lit: 1, 300 }, 301 }, 302 }, 303 }, 304 ], 305 } 306 "###); 307} 308 309// https://github.com/dtolnay/syn/issues/1019 310#[test] 311fn test_closure_vs_rangefull() { 312 #[rustfmt::skip] // rustfmt bug: https://github.com/rust-lang/rustfmt/issues/4808 313 let tokens = quote!(|| .. .method()); 314 snapshot!(tokens as Expr, @r###" 315 Expr::MethodCall { 316 receiver: Expr::Closure { 317 output: ReturnType::Default, 318 body: Expr::Range { 319 limits: RangeLimits::HalfOpen, 320 }, 321 }, 322 method: "method", 323 } 324 "###); 325} 326 327#[test] 328fn test_postfix_operator_after_cast() { 329 syn::parse_str::<Expr>("|| &x as T[0]").unwrap_err(); 330 syn::parse_str::<Expr>("|| () as ()()").unwrap_err(); 331} 332 333#[test] 334fn test_ranges() { 335 syn::parse_str::<Expr>("..").unwrap(); 336 syn::parse_str::<Expr>("..hi").unwrap(); 337 syn::parse_str::<Expr>("lo..").unwrap(); 338 syn::parse_str::<Expr>("lo..hi").unwrap(); 339 340 syn::parse_str::<Expr>("..=").unwrap_err(); 341 syn::parse_str::<Expr>("..=hi").unwrap(); 342 syn::parse_str::<Expr>("lo..=").unwrap_err(); 343 syn::parse_str::<Expr>("lo..=hi").unwrap(); 344 345 syn::parse_str::<Expr>("...").unwrap_err(); 346 syn::parse_str::<Expr>("...hi").unwrap_err(); 347 syn::parse_str::<Expr>("lo...").unwrap_err(); 348 syn::parse_str::<Expr>("lo...hi").unwrap_err(); 349} 350 351#[test] 352fn test_ambiguous_label() { 353 for stmt in [ 354 quote! { 355 return 'label: loop { break 'label 42; }; 356 }, 357 quote! { 358 break ('label: loop { break 'label 42; }); 359 }, 360 quote! { 361 break 1 + 'label: loop { break 'label 42; }; 362 }, 363 quote! { 364 break 'outer 'inner: loop { break 'inner 42; }; 365 }, 366 ] { 367 syn::parse2::<Stmt>(stmt).unwrap(); 368 } 369 370 for stmt in [ 371 // Parentheses required. See https://github.com/rust-lang/rust/pull/87026. 372 quote! { 373 break 'label: loop { break 'label 42; }; 374 }, 375 ] { 376 syn::parse2::<Stmt>(stmt).unwrap_err(); 377 } 378} 379 380#[test] 381fn test_extended_interpolated_path() { 382 let path = Group::new(Delimiter::None, quote!(a::b)); 383 384 let tokens = quote!(if #path {}); 385 snapshot!(tokens as Expr, @r###" 386 Expr::If { 387 cond: Expr::Group { 388 expr: Expr::Path { 389 path: Path { 390 segments: [ 391 PathSegment { 392 ident: "a", 393 }, 394 Token![::], 395 PathSegment { 396 ident: "b", 397 }, 398 ], 399 }, 400 }, 401 }, 402 then_branch: Block { 403 stmts: [], 404 }, 405 } 406 "###); 407 408 let tokens = quote!(#path {}); 409 snapshot!(tokens as Expr, @r###" 410 Expr::Struct { 411 path: Path { 412 segments: [ 413 PathSegment { 414 ident: "a", 415 }, 416 Token![::], 417 PathSegment { 418 ident: "b", 419 }, 420 ], 421 }, 422 } 423 "###); 424 425 let tokens = quote!(#path :: c); 426 snapshot!(tokens as Expr, @r###" 427 Expr::Path { 428 path: Path { 429 segments: [ 430 PathSegment { 431 ident: "a", 432 }, 433 Token![::], 434 PathSegment { 435 ident: "b", 436 }, 437 Token![::], 438 PathSegment { 439 ident: "c", 440 }, 441 ], 442 }, 443 } 444 "###); 445 446 let nested = Group::new(Delimiter::None, quote!(a::b || true)); 447 let tokens = quote!(if #nested && false {}); 448 snapshot!(tokens as Expr, @r###" 449 Expr::If { 450 cond: Expr::Binary { 451 left: Expr::Group { 452 expr: Expr::Binary { 453 left: Expr::Path { 454 path: Path { 455 segments: [ 456 PathSegment { 457 ident: "a", 458 }, 459 Token![::], 460 PathSegment { 461 ident: "b", 462 }, 463 ], 464 }, 465 }, 466 op: BinOp::Or, 467 right: Expr::Lit { 468 lit: Lit::Bool { 469 value: true, 470 }, 471 }, 472 }, 473 }, 474 op: BinOp::And, 475 right: Expr::Lit { 476 lit: Lit::Bool { 477 value: false, 478 }, 479 }, 480 }, 481 then_branch: Block { 482 stmts: [], 483 }, 484 } 485 "###); 486} 487 488#[test] 489fn test_tuple_comma() { 490 let mut expr = ExprTuple { 491 attrs: Vec::new(), 492 paren_token: token::Paren::default(), 493 elems: Punctuated::new(), 494 }; 495 snapshot!(expr.to_token_stream() as Expr, @"Expr::Tuple"); 496 497 expr.elems.push_value(parse_quote!(continue)); 498 // Must not parse to Expr::Paren 499 snapshot!(expr.to_token_stream() as Expr, @r###" 500 Expr::Tuple { 501 elems: [ 502 Expr::Continue, 503 Token![,], 504 ], 505 } 506 "###); 507 508 expr.elems.push_punct(<Token![,]>::default()); 509 snapshot!(expr.to_token_stream() as Expr, @r###" 510 Expr::Tuple { 511 elems: [ 512 Expr::Continue, 513 Token![,], 514 ], 515 } 516 "###); 517 518 expr.elems.push_value(parse_quote!(continue)); 519 snapshot!(expr.to_token_stream() as Expr, @r###" 520 Expr::Tuple { 521 elems: [ 522 Expr::Continue, 523 Token![,], 524 Expr::Continue, 525 ], 526 } 527 "###); 528 529 expr.elems.push_punct(<Token![,]>::default()); 530 snapshot!(expr.to_token_stream() as Expr, @r###" 531 Expr::Tuple { 532 elems: [ 533 Expr::Continue, 534 Token![,], 535 Expr::Continue, 536 Token![,], 537 ], 538 } 539 "###); 540} 541