#![allow( clippy::float_cmp, clippy::needless_lifetimes, clippy::needless_raw_string_hashes, clippy::non_ascii_literal, clippy::single_match_else, clippy::uninlined_format_args )] #[macro_use] mod macros; use proc_macro2::{Delimiter, Group, Literal, Span, TokenStream, TokenTree}; use quote::ToTokens; use std::ffi::CStr; use std::str::FromStr; use syn::{Lit, LitFloat, LitInt, LitStr}; #[track_caller] fn lit(s: &str) -> Lit { let mut tokens = TokenStream::from_str(s).unwrap().into_iter(); match tokens.next().unwrap() { TokenTree::Literal(lit) => { assert!(tokens.next().is_none()); Lit::new(lit) } wrong => panic!("{:?}", wrong), } } #[test] fn strings() { #[track_caller] fn test_string(s: &str, value: &str) { let s = s.trim(); match lit(s) { Lit::Str(lit) => { assert_eq!(lit.value(), value); let again = lit.into_token_stream().to_string(); if again != s { test_string(&again, value); } } wrong => panic!("{:?}", wrong), } } test_string(r#" "" "#, ""); test_string(r#" "a" "#, "a"); test_string(r#" "\n" "#, "\n"); test_string(r#" "\r" "#, "\r"); test_string(r#" "\t" "#, "\t"); test_string(r#" "🐕" "#, "🐕"); // NOTE: This is an emoji test_string(r#" "\"" "#, "\""); test_string(r#" "'" "#, "'"); test_string(r#" "\u{1F415}" "#, "\u{1F415}"); test_string(r#" "\u{1_2__3_}" "#, "\u{123}"); test_string( "\"contains\nnewlines\\\nescaped newlines\"", "contains\nnewlinesescaped newlines", ); test_string( "\"escaped newline\\\n \x0C unsupported whitespace\"", "escaped newline\x0C unsupported whitespace", ); test_string("r\"raw\nstring\\\nhere\"", "raw\nstring\\\nhere"); test_string("\"...\"q", "..."); test_string("r\"...\"q", "..."); test_string("r##\"...\"##q", "..."); } #[test] fn byte_strings() { #[track_caller] fn test_byte_string(s: &str, value: &[u8]) { let s = s.trim(); match lit(s) { Lit::ByteStr(lit) => { assert_eq!(lit.value(), value); let again = lit.into_token_stream().to_string(); if again != s { test_byte_string(&again, value); } } wrong => panic!("{:?}", wrong), } } test_byte_string(r#" b"" "#, b""); test_byte_string(r#" b"a" "#, b"a"); test_byte_string(r#" b"\n" "#, b"\n"); test_byte_string(r#" b"\r" "#, b"\r"); test_byte_string(r#" b"\t" "#, b"\t"); test_byte_string(r#" b"\"" "#, b"\""); test_byte_string(r#" b"'" "#, b"'"); test_byte_string( "b\"contains\nnewlines\\\nescaped newlines\"", b"contains\nnewlinesescaped newlines", ); test_byte_string("br\"raw\nstring\\\nhere\"", b"raw\nstring\\\nhere"); test_byte_string("b\"...\"q", b"..."); test_byte_string("br\"...\"q", b"..."); test_byte_string("br##\"...\"##q", b"..."); } #[test] fn c_strings() { #[track_caller] fn test_c_string(s: &str, value: &CStr) { let s = s.trim(); match lit(s) { Lit::CStr(lit) => { assert_eq!(*lit.value(), *value); let again = lit.into_token_stream().to_string(); if again != s { test_c_string(&again, value); } } wrong => panic!("{:?}", wrong), } } test_c_string(r#" c"" "#, c""); test_c_string(r#" c"a" "#, c"a"); test_c_string(r#" c"\n" "#, c"\n"); test_c_string(r#" c"\r" "#, c"\r"); test_c_string(r#" c"\t" "#, c"\t"); test_c_string(r#" c"\\" "#, c"\\"); test_c_string(r#" c"\'" "#, c"'"); test_c_string(r#" c"\"" "#, c"\""); test_c_string( "c\"contains\nnewlines\\\nescaped newlines\"", c"contains\nnewlinesescaped newlines", ); test_c_string("cr\"raw\nstring\\\nhere\"", c"raw\nstring\\\nhere"); test_c_string("c\"...\"q", c"..."); test_c_string("cr\"...\"", c"..."); test_c_string("cr##\"...\"##", c"..."); test_c_string( r#" c"hello\x80我叫\u{1F980}" "#, // from the RFC c"hello\x80我叫\u{1F980}", ); } #[test] fn bytes() { #[track_caller] fn test_byte(s: &str, value: u8) { let s = s.trim(); match lit(s) { Lit::Byte(lit) => { assert_eq!(lit.value(), value); let again = lit.into_token_stream().to_string(); assert_eq!(again, s); } wrong => panic!("{:?}", wrong), } } test_byte(r#" b'a' "#, b'a'); test_byte(r#" b'\n' "#, b'\n'); test_byte(r#" b'\r' "#, b'\r'); test_byte(r#" b'\t' "#, b'\t'); test_byte(r#" b'\'' "#, b'\''); test_byte(r#" b'"' "#, b'"'); test_byte(r#" b'a'q "#, b'a'); } #[test] fn chars() { #[track_caller] fn test_char(s: &str, value: char) { let s = s.trim(); match lit(s) { Lit::Char(lit) => { assert_eq!(lit.value(), value); let again = lit.into_token_stream().to_string(); if again != s { test_char(&again, value); } } wrong => panic!("{:?}", wrong), } } test_char(r#" 'a' "#, 'a'); test_char(r#" '\n' "#, '\n'); test_char(r#" '\r' "#, '\r'); test_char(r#" '\t' "#, '\t'); test_char(r#" '🐕' "#, '🐕'); // NOTE: This is an emoji test_char(r#" '\'' "#, '\''); test_char(r#" '"' "#, '"'); test_char(r#" '\u{1F415}' "#, '\u{1F415}'); test_char(r#" 'a'q "#, 'a'); } #[test] fn ints() { #[track_caller] fn test_int(s: &str, value: u64, suffix: &str) { match lit(s) { Lit::Int(lit) => { assert_eq!(lit.base10_digits().parse::().unwrap(), value); assert_eq!(lit.suffix(), suffix); let again = lit.into_token_stream().to_string(); if again != s { test_int(&again, value, suffix); } } wrong => panic!("{:?}", wrong), } } test_int("5", 5, ""); test_int("5u32", 5, "u32"); test_int("0E", 0, "E"); test_int("0ECMA", 0, "ECMA"); test_int("0o0A", 0, "A"); test_int("5_0", 50, ""); test_int("5_____0_____", 50, ""); test_int("0x7f", 127, ""); test_int("0x7F", 127, ""); test_int("0b1001", 9, ""); test_int("0o73", 59, ""); test_int("0x7Fu8", 127, "u8"); test_int("0b1001i8", 9, "i8"); test_int("0o73u32", 59, "u32"); test_int("0x__7___f_", 127, ""); test_int("0x__7___F_", 127, ""); test_int("0b_1_0__01", 9, ""); test_int("0o_7__3", 59, ""); test_int("0x_7F__u8", 127, "u8"); test_int("0b__10__0_1i8", 9, "i8"); test_int("0o__7__________________3u32", 59, "u32"); test_int("0e1\u{5c5}", 0, "e1\u{5c5}"); } #[test] fn floats() { #[track_caller] fn test_float(s: &str, value: f64, suffix: &str) { match lit(s) { Lit::Float(lit) => { assert_eq!(lit.base10_digits().parse::().unwrap(), value); assert_eq!(lit.suffix(), suffix); let again = lit.into_token_stream().to_string(); if again != s { test_float(&again, value, suffix); } } wrong => panic!("{:?}", wrong), } } test_float("5.5", 5.5, ""); test_float("5.5E12", 5.5e12, ""); test_float("5.5e12", 5.5e12, ""); test_float("1.0__3e-12", 1.03e-12, ""); test_float("1.03e+12", 1.03e12, ""); test_float("9e99e99", 9e99, "e99"); test_float("1e_0", 1.0, ""); test_float("0.0ECMA", 0.0, "ECMA"); } #[test] fn negative() { let span = Span::call_site(); assert_eq!("-1", LitInt::new("-1", span).to_string()); assert_eq!("-1i8", LitInt::new("-1i8", span).to_string()); assert_eq!("-1i16", LitInt::new("-1i16", span).to_string()); assert_eq!("-1i32", LitInt::new("-1i32", span).to_string()); assert_eq!("-1i64", LitInt::new("-1i64", span).to_string()); assert_eq!("-1.5", LitFloat::new("-1.5", span).to_string()); assert_eq!("-1.5f32", LitFloat::new("-1.5f32", span).to_string()); assert_eq!("-1.5f64", LitFloat::new("-1.5f64", span).to_string()); } #[test] fn suffix() { #[track_caller] fn get_suffix(token: &str) -> String { let lit = syn::parse_str::(token).unwrap(); match lit { Lit::Str(lit) => lit.suffix().to_owned(), Lit::ByteStr(lit) => lit.suffix().to_owned(), Lit::CStr(lit) => lit.suffix().to_owned(), Lit::Byte(lit) => lit.suffix().to_owned(), Lit::Char(lit) => lit.suffix().to_owned(), Lit::Int(lit) => lit.suffix().to_owned(), Lit::Float(lit) => lit.suffix().to_owned(), _ => unimplemented!(), } } assert_eq!(get_suffix("\"\"s"), "s"); assert_eq!(get_suffix("r\"\"r"), "r"); assert_eq!(get_suffix("r#\"\"#r"), "r"); assert_eq!(get_suffix("b\"\"b"), "b"); assert_eq!(get_suffix("br\"\"br"), "br"); assert_eq!(get_suffix("br#\"\"#br"), "br"); assert_eq!(get_suffix("c\"\"c"), "c"); assert_eq!(get_suffix("cr\"\"cr"), "cr"); assert_eq!(get_suffix("cr#\"\"#cr"), "cr"); assert_eq!(get_suffix("'c'c"), "c"); assert_eq!(get_suffix("b'b'b"), "b"); assert_eq!(get_suffix("1i32"), "i32"); assert_eq!(get_suffix("1_i32"), "i32"); assert_eq!(get_suffix("1.0f32"), "f32"); assert_eq!(get_suffix("1.0_f32"), "f32"); } #[test] fn test_deep_group_empty() { let tokens = TokenStream::from_iter([TokenTree::Group(Group::new( Delimiter::None, TokenStream::from_iter([TokenTree::Group(Group::new( Delimiter::None, TokenStream::from_iter([TokenTree::Literal(Literal::string("hi"))]), ))]), ))]); snapshot!(tokens as Lit, @r#""hi""# ); } #[test] fn test_error() { let err = syn::parse_str::("...").unwrap_err(); assert_eq!("expected string literal", err.to_string()); let err = syn::parse_str::("5").unwrap_err(); assert_eq!("expected string literal", err.to_string()); }