Repository: dtolnay/paste (rust)

Issue:
Can not use in literals?
<!--  ❤️  -->


```rust
use paste::paste;
macro_rules! vec_insert {
    ($bit: tt) => {
        paste! {
            fn [<vector_insert_ $bit _bit>](insert_size: usize) {
                let mut [<vec_ $bit>] = Vec::new();
                for _ in 0..insert_size {
                    [<vec_ $bit>].insert(0, [<1_u $bit>]);
                }
            }
        }
    };
}

vec_insert!(32);
```

expected to expand into

```rust
fn vector_insert_32_bit(insert_size: usize) {
    let mut vec_32 = Vec::new();
    for _ in 0..insert_size {
        vec_32.insert(0, 1_u32);
    }
}
```

but it show error:
\`"1_u32"\` is not a valid identifier

Can paste used in literal such as 1_u32?

Pull request description:
Support Literal as result of paste
Closes #91. This is made possible by https://github.com/rust-lang/rust/pull/84717 on Rust 1.54+.

Patch:
diff --git a/build.rs b/build.rs
new file mode 100644
index 0000000..349b5fc
--- /dev/null
+++ b/build.rs
@@ -0,0 +1,33 @@
+use std::env;
+use std::process::Command;
+use std::str;
+
+fn main() {
+    println!("cargo:rerun-if-changed=build.rs");
+
+    let version = match rustc_version() {
+        Some(version) => version,
+        None => return,
+    };
+
+    if version.minor < 54 {
+        // https://github.com/rust-lang/rust/pull/84717
+        println!("cargo:rustc-cfg=no_literal_fromstr");
+    }
+}
+
+struct RustcVersion {
+    minor: u32,
+}
+
+fn rustc_version() -> Option<RustcVersion> {
+    let rustc = env::var_os("RUSTC")?;
+    let output = Command::new(rustc).arg("--version").output().ok()?;
+    let version = str::from_utf8(&output.stdout).ok()?;
+    let mut pieces = version.split('.');
+    if pieces.next() != Some("rustc 1") {
+        return None;
+    }
+    let minor = pieces.next()?.parse().ok()?;
+    Some(RustcVersion { minor })
+}
diff --git a/src/lib.rs b/src/lib.rs
index 983ba06..80c1b98 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -411,6 +411,26 @@ fn parse_bracket_as_segments(input: TokenStream, scope: Span) -> Result<Vec<Segm
 fn pasted_to_tokens(mut pasted: String, span: Span) -> Result<TokenStream> {
     let mut tokens = TokenStream::new();
 
+    #[cfg(not(no_literal_fromstr))]
+    {
+        use proc_macro::{LexError, Literal};
+        use std::str::FromStr;
+
+        if pasted.starts_with(|ch: char| ch.is_ascii_digit()) {
+            let literal = match panic::catch_unwind(|| Literal::from_str(&pasted)) {
+                Ok(Ok(literal)) => TokenTree::Literal(literal),
+                Ok(Err(LexError { .. })) | Err(_) => {
+                    return Err(Error::new(
+                        span,
+                        &format!("`{:?}` is not a valid literal", pasted),
+                    ));
+                }
+            };
+            tokens.extend(iter::once(literal));
+            return Ok(tokens);
+        }
+    }
+
     if pasted.starts_with('\'') {
         let mut apostrophe = TokenTree::Punct(Punct::new('\'', Spacing::Joint));
         apostrophe.set_span(span);


Test patch:
diff --git a/tests/test_expr.rs b/tests/test_expr.rs
index 8ad5de9..5ce2549 100644
--- a/tests/test_expr.rs
+++ b/tests/test_expr.rs
@@ -26,7 +26,7 @@ fn test_repeat() {
 }
 
 #[test]
-fn test_literals() {
+fn test_literal_to_identifier() {
     const CONST0: &str = "const0";
 
     let pasted = paste!([<CONST 0>]);
@@ -45,6 +45,17 @@ fn test_literals() {
     assert_eq!(pasted, CONST0);
 }
 
+#[test]
+fn test_literal_suffix() {
+    macro_rules! literal {
+        ($bit:tt) => {
+            paste!([<1_u $bit>])
+        };
+    }
+
+    assert_eq!(literal!(32), 1);
+}
+
 #[test]
 fn test_underscore() {
     paste! {
diff --git a/tests/ui/invalid-ident.stderr b/tests/ui/invalid-ident.stderr
index f32d484..28593fb 100644
--- a/tests/ui/invalid-ident.stderr
+++ b/tests/ui/invalid-ident.stderr
@@ -1,8 +1,12 @@
-error: `"0f"` is not a valid identifier
- --> tests/ui/invalid-ident.rs:4:8
+error: expected identifier, found `0f`
+ --> tests/ui/invalid-ident.rs:3:1
   |
-4 |     fn [<0 f>]() {}
-  |        ^^^^^^^
+3 | / paste! {
+4 | |     fn [<0 f>]() {}
+5 | | }
+  | |_^ expected identifier
+  |
+  = note: this error originates in the macro `paste` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 error: `"f\""` is not a valid identifier
  --> tests/ui/invalid-ident.rs:8:8