Snap for 6686656 from 2712e5727d219ddccacef4e2481d310ce25c7450 to sdk-release

Change-Id: I31bf6cb990714fde4405540a74e081bb471f7dc5
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
index 6492333..77d0b53 100644
--- a/.cargo_vcs_info.json
+++ b/.cargo_vcs_info.json
@@ -1,5 +1,5 @@
 {
   "git": {
-    "sha1": "6091cbe972d57a2a706b71da3aca82c64150ef69"
+    "sha1": "ca72ba450ad4859c5a7557371560a022649b1b1e"
   }
 }
diff --git a/Android.bp b/Android.bp
index dce00a0..b65ed9d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,23 +1,14 @@
-// This file is generated by cargo2android.py.
+// This file is generated by cargo2android.py --run --dependencies.
 
 rust_proc_macro {
     name: "libpaste_impl",
     crate_name: "paste_impl",
     srcs: ["src/lib.rs"],
     edition: "2018",
-    rustlibs: [
-        "libproc_macro2",
-        "libquote",
-        "libsyn",
-    ],
     proc_macros: [
         "libproc_macro_hack",
     ],
 }
 
 // dependent_library ["feature_list"]
-//   proc-macro-hack-0.5.12
-//   proc-macro2-1.0.9 "default,proc-macro"
-//   quote-1.0.3 "default,proc-macro"
-//   syn-1.0.16 "clone-impls,default,derive,parsing,printing,proc-macro,quote"
-//   unicode-xid-0.2.0 "default"
+//   proc-macro-hack-0.5.16
diff --git a/Cargo.toml b/Cargo.toml
index fa93a77..ddd3931 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,7 +13,7 @@
 [package]
 edition = "2018"
 name = "paste-impl"
-version = "0.1.16"
+version = "0.1.18"
 authors = ["David Tolnay <dtolnay@gmail.com>"]
 description = "Implementation detail of the `paste` crate"
 license = "MIT OR Apache-2.0"
@@ -25,12 +25,3 @@
 proc-macro = true
 [dependencies.proc-macro-hack]
 version = "0.5"
-
-[dependencies.proc-macro2]
-version = "1.0"
-
-[dependencies.quote]
-version = "1.0"
-
-[dependencies.syn]
-version = "1.0"
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
index 1bc8a57..a630055 100644
--- a/Cargo.toml.orig
+++ b/Cargo.toml.orig
@@ -1,6 +1,6 @@
 [package]
 name = "paste-impl"
-version = "0.1.16"
+version = "0.1.18"
 authors = ["David Tolnay <dtolnay@gmail.com>"]
 edition = "2018"
 license = "MIT OR Apache-2.0"
@@ -12,9 +12,6 @@
 
 [dependencies]
 proc-macro-hack = "0.5"
-proc-macro2 = "1.0"
-quote = "1.0"
-syn = "1.0"
 
 [package.metadata.docs.rs]
 targets = ["x86_64-unknown-linux-gnu"]
diff --git a/METADATA b/METADATA
index ca77c1d..49cfc8f 100644
--- a/METADATA
+++ b/METADATA
@@ -9,11 +9,11 @@
     type: GIT
     value: "https://github.com/dtolnay/paste"
   }
-  version: "0.1.16"
+  version: "0.1.18"
   license_type: NOTICE
   last_upgrade_date {
     year: 2020
-    month: 6
-    day: 3
+    month: 7
+    day: 10
   }
 }
diff --git a/src/enum_hack.rs b/src/enum_hack.rs
index 626b265..36ab1ad 100644
--- a/src/enum_hack.rs
+++ b/src/enum_hack.rs
@@ -1,10 +1,7 @@
+use proc_macro::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree};
 use std::collections::hash_map::DefaultHasher;
 use std::hash::{Hash, Hasher};
-
-use proc_macro2::{Ident, Span, TokenStream, TokenTree};
-use quote::quote;
-use syn::parse::{Parse, ParseStream, Result};
-use syn::{braced, parenthesized, parse_macro_input, Token};
+use std::iter::FromIterator;
 
 pub fn wrap(output: TokenStream) -> TokenStream {
     let mut hasher = DefaultHasher::default();
@@ -12,50 +9,78 @@
     let mangled_name = format!("_paste_{}", hasher.finish());
     let ident = Ident::new(&mangled_name, Span::call_site());
 
-    quote! {
-        #[derive(paste::EnumHack)]
-        enum #ident {
-            Value = (stringify! {
-                #output
-            }, 0).1,
-        }
-    }
+    // #[derive(paste::EnumHack)]
+    // enum #ident {
+    //     Value = (stringify! {
+    //         #output
+    //     }, 0).1,
+    // }
+    TokenStream::from_iter(vec![
+        TokenTree::Punct(Punct::new('#', Spacing::Alone)),
+        TokenTree::Group(Group::new(
+            Delimiter::Bracket,
+            TokenStream::from_iter(vec![
+                TokenTree::Ident(Ident::new("derive", Span::call_site())),
+                TokenTree::Group(Group::new(
+                    Delimiter::Parenthesis,
+                    TokenStream::from_iter(vec![
+                        TokenTree::Ident(Ident::new("paste", Span::call_site())),
+                        TokenTree::Punct(Punct::new(':', Spacing::Joint)),
+                        TokenTree::Punct(Punct::new(':', Spacing::Alone)),
+                        TokenTree::Ident(Ident::new("EnumHack", Span::call_site())),
+                    ]),
+                )),
+            ]),
+        )),
+        TokenTree::Ident(Ident::new("enum", Span::call_site())),
+        TokenTree::Ident(ident),
+        TokenTree::Group(Group::new(
+            Delimiter::Brace,
+            TokenStream::from_iter(vec![
+                TokenTree::Ident(Ident::new("Value", Span::call_site())),
+                TokenTree::Punct(Punct::new('=', Spacing::Alone)),
+                TokenTree::Group(Group::new(
+                    Delimiter::Parenthesis,
+                    TokenStream::from_iter(vec![
+                        TokenTree::Ident(Ident::new("stringify", Span::call_site())),
+                        TokenTree::Punct(Punct::new('!', Spacing::Alone)),
+                        TokenTree::Group(Group::new(Delimiter::Brace, output)),
+                        TokenTree::Punct(Punct::new(',', Spacing::Alone)),
+                        TokenTree::Literal(Literal::usize_unsuffixed(0)),
+                    ]),
+                )),
+                TokenTree::Punct(Punct::new('.', Spacing::Alone)),
+                TokenTree::Literal(Literal::usize_unsuffixed(1)),
+                TokenTree::Punct(Punct::new(',', Spacing::Alone)),
+            ]),
+        )),
+    ])
 }
 
-struct EnumHack {
-    token_stream: TokenStream,
-}
-
-impl Parse for EnumHack {
-    fn parse(input: ParseStream) -> Result<Self> {
-        input.parse::<Token![enum]>()?;
-        input.parse::<Ident>()?;
-
-        let braces;
-        braced!(braces in input);
-        braces.parse::<Ident>()?;
-        braces.parse::<Token![=]>()?;
-
-        let parens;
-        parenthesized!(parens in braces);
-        parens.parse::<Ident>()?;
-        parens.parse::<Token![!]>()?;
-
-        let inner;
-        braced!(inner in parens);
-        let token_stream: TokenStream = inner.parse()?;
-
-        parens.parse::<Token![,]>()?;
-        parens.parse::<TokenTree>()?;
-        braces.parse::<Token![.]>()?;
-        braces.parse::<TokenTree>()?;
-        braces.parse::<Token![,]>()?;
-
-        Ok(EnumHack { token_stream })
-    }
-}
-
-pub fn extract(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
-    let inner = parse_macro_input!(input as EnumHack);
-    proc_macro::TokenStream::from(inner.token_stream)
+pub fn extract(input: TokenStream) -> TokenStream {
+    let mut tokens = input.into_iter();
+    let _ = tokens.next().expect("enum");
+    let _ = tokens.next().expect("#ident");
+    let mut braces = match tokens.next().expect("{...}") {
+        TokenTree::Group(group) => group.stream().into_iter(),
+        _ => unreachable!("{...}"),
+    };
+    let _ = braces.next().expect("Value");
+    let _ = braces.next().expect("=");
+    let mut parens = match braces.next().expect("(...)") {
+        TokenTree::Group(group) => group.stream().into_iter(),
+        _ => unreachable!("(...)"),
+    };
+    let _ = parens.next().expect("stringify");
+    let _ = parens.next().expect("!");
+    let token_stream = match parens.next().expect("{...}") {
+        TokenTree::Group(group) => group.stream(),
+        _ => unreachable!("{...}"),
+    };
+    let _ = parens.next().expect(",");
+    let _ = parens.next().expect("0");
+    let _ = braces.next().expect(".");
+    let _ = braces.next().expect("1");
+    let _ = braces.next().expect(",");
+    token_stream
 }
diff --git a/src/error.rs b/src/error.rs
new file mode 100644
index 0000000..7c5badb
--- /dev/null
+++ b/src/error.rs
@@ -0,0 +1,47 @@
+use proc_macro::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree};
+use std::iter::FromIterator;
+
+pub type Result<T> = std::result::Result<T, Error>;
+
+pub struct Error {
+    begin: Span,
+    end: Span,
+    msg: String,
+}
+
+impl Error {
+    pub fn new(span: Span, msg: &str) -> Self {
+        Self::new2(span, span, msg)
+    }
+
+    pub fn new2(begin: Span, end: Span, msg: &str) -> Self {
+        Error {
+            begin,
+            end,
+            msg: msg.to_owned(),
+        }
+    }
+
+    pub fn to_compile_error(&self) -> TokenStream {
+        // compile_error! { $msg }
+        TokenStream::from_iter(vec![
+            TokenTree::Ident(Ident::new("compile_error", self.begin)),
+            TokenTree::Punct({
+                let mut punct = Punct::new('!', Spacing::Alone);
+                punct.set_span(self.begin);
+                punct
+            }),
+            TokenTree::Group({
+                let mut group = Group::new(Delimiter::Brace, {
+                    TokenStream::from_iter(vec![TokenTree::Literal({
+                        let mut string = Literal::string(&self.msg);
+                        string.set_span(self.end);
+                        string
+                    })])
+                });
+                group.set_span(self.end);
+                group
+            }),
+        ])
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 2feee4c..f84715c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,73 +1,84 @@
 extern crate proc_macro;
 
 mod enum_hack;
+mod error;
 
-use proc_macro2::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree};
+use crate::error::{Error, Result};
+use proc_macro::{
+    token_stream, Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree,
+};
 use proc_macro_hack::proc_macro_hack;
-use quote::{quote, ToTokens};
-use std::iter::FromIterator;
-use syn::parse::{Error, Parse, ParseStream, Parser, Result};
-use syn::{parenthesized, parse_macro_input, Lit, LitStr, Token};
+use std::iter::{self, FromIterator, Peekable};
+use std::panic;
 
 #[proc_macro]
-pub fn item(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
-    let input = parse_macro_input!(input as PasteInput);
-    proc_macro::TokenStream::from(input.expanded)
+pub fn item(input: TokenStream) -> TokenStream {
+    expand_paste(input)
 }
 
 #[proc_macro]
-pub fn item_with_macros(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
-    let input = parse_macro_input!(input as PasteInput);
-    proc_macro::TokenStream::from(enum_hack::wrap(input.expanded))
+pub fn item_with_macros(input: TokenStream) -> TokenStream {
+    enum_hack::wrap(expand_paste(input))
 }
 
 #[proc_macro_hack]
-pub fn expr(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
-    let input = parse_macro_input!(input as PasteInput);
-    let output = input.expanded;
-    proc_macro::TokenStream::from(quote!({ #output }))
+pub fn expr(input: TokenStream) -> TokenStream {
+    TokenStream::from(TokenTree::Group(Group::new(
+        Delimiter::Brace,
+        expand_paste(input),
+    )))
 }
 
 #[doc(hidden)]
 #[proc_macro_derive(EnumHack)]
-pub fn enum_hack(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+pub fn enum_hack(input: TokenStream) -> TokenStream {
     enum_hack::extract(input)
 }
 
-struct PasteInput {
-    expanded: TokenStream,
-}
-
-impl Parse for PasteInput {
-    fn parse(input: ParseStream) -> Result<Self> {
-        let mut contains_paste = false;
-        let expanded = parse(input, &mut contains_paste)?;
-        Ok(PasteInput { expanded })
+fn expand_paste(input: TokenStream) -> TokenStream {
+    let mut contains_paste = false;
+    match expand(input, &mut contains_paste) {
+        Ok(expanded) => expanded,
+        Err(err) => err.to_compile_error(),
     }
 }
 
-fn parse(input: ParseStream, contains_paste: &mut bool) -> Result<TokenStream> {
+fn expand(input: TokenStream, contains_paste: &mut bool) -> Result<TokenStream> {
     let mut expanded = TokenStream::new();
-    let (mut prev_colons, mut colons) = (false, false);
-    while !input.is_empty() {
-        let save = input.fork();
-        match input.parse()? {
-            TokenTree::Group(group) => {
+    let (mut prev_colon, mut colon) = (false, false);
+    let mut prev_none_group = None::<Group>;
+    let mut tokens = input.into_iter().peekable();
+    loop {
+        let token = tokens.next();
+        if let Some(group) = prev_none_group.take() {
+            if match (&token, tokens.peek()) {
+                (Some(TokenTree::Punct(fst)), Some(TokenTree::Punct(snd))) => {
+                    fst.as_char() == ':' && snd.as_char() == ':' && fst.spacing() == Spacing::Joint
+                }
+                _ => false,
+            } {
+                expanded.extend(group.stream());
+                *contains_paste = true;
+            } else {
+                expanded.extend(iter::once(TokenTree::Group(group)));
+            }
+        }
+        match token {
+            Some(TokenTree::Group(group)) => {
                 let delimiter = group.delimiter();
                 let content = group.stream();
                 let span = group.span();
                 if delimiter == Delimiter::Bracket && is_paste_operation(&content) {
-                    let segments = parse_bracket_as_segments.parse2(content)?;
+                    let segments = parse_bracket_as_segments(content, span)?;
                     let pasted = paste_segments(span, &segments)?;
-                    pasted.to_tokens(&mut expanded);
+                    expanded.extend(pasted);
                     *contains_paste = true;
                 } else if is_none_delimited_flat_group(delimiter, &content) {
-                    content.to_tokens(&mut expanded);
+                    expanded.extend(content);
                     *contains_paste = true;
                 } else {
                     let mut group_contains_paste = false;
-                    let nested = (|input: ParseStream| parse(input, &mut group_contains_paste))
-                        .parse2(content)?;
+                    let nested = expand(content, &mut group_contains_paste)?;
                     let group = if group_contains_paste {
                         let mut group = Group::new(delimiter, nested);
                         group.set_span(span);
@@ -76,26 +87,34 @@
                     } else {
                         group.clone()
                     };
-                    let in_path = prev_colons || input.peek(Token![::]);
-                    if in_path && delimiter == Delimiter::None {
-                        group.stream().to_tokens(&mut expanded);
+                    if delimiter != Delimiter::None {
+                        expanded.extend(iter::once(TokenTree::Group(group)));
+                    } else if prev_colon {
+                        expanded.extend(group.stream());
                         *contains_paste = true;
                     } else {
-                        group.to_tokens(&mut expanded);
+                        prev_none_group = Some(group);
                     }
                 }
+                prev_colon = false;
+                colon = false;
             }
-            other => other.to_tokens(&mut expanded),
+            Some(other) => {
+                match &other {
+                    TokenTree::Punct(punct) if punct.as_char() == ':' => {
+                        prev_colon = colon;
+                        colon = punct.spacing() == Spacing::Joint;
+                    }
+                    _ => {
+                        prev_colon = false;
+                        colon = false;
+                    }
+                }
+                expanded.extend(iter::once(other));
+            }
+            None => return Ok(expanded),
         }
-        prev_colons = colons;
-        colons = save.peek(Token![::]);
     }
-    Ok(expanded)
-}
-
-fn is_paste_operation(input: &TokenStream) -> bool {
-    let input = input.clone();
-    parse_bracket_as_segments.parse2(input).is_ok()
 }
 
 // https://github.com/dtolnay/paste/issues/26
@@ -140,61 +159,173 @@
     state == State::Ident || state == State::Literal || state == State::Lifetime
 }
 
+struct LitStr {
+    value: String,
+    span: Span,
+}
+
+struct Colon {
+    span: Span,
+}
+
 enum Segment {
     String(String),
     Apostrophe(Span),
     Env(LitStr),
-    Modifier(Token![:], Ident),
+    Modifier(Colon, Ident),
 }
 
-fn parse_bracket_as_segments(input: ParseStream) -> Result<Vec<Segment>> {
-    input.parse::<Token![<]>()?;
+fn is_paste_operation(input: &TokenStream) -> bool {
+    let mut tokens = input.clone().into_iter();
 
-    let segments = parse_segments(input)?;
-
-    input.parse::<Token![>]>()?;
-    if !input.is_empty() {
-        return Err(input.error("invalid input"));
+    match &tokens.next() {
+        Some(TokenTree::Punct(punct)) if punct.as_char() == '<' => {}
+        _ => return false,
     }
-    Ok(segments)
+
+    let mut has_token = false;
+    loop {
+        match &tokens.next() {
+            Some(TokenTree::Punct(punct)) if punct.as_char() == '>' => {
+                return has_token && tokens.next().is_none();
+            }
+            Some(_) => has_token = true,
+            None => return false,
+        }
+    }
 }
 
-fn parse_segments(input: ParseStream) -> Result<Vec<Segment>> {
+fn parse_bracket_as_segments(input: TokenStream, scope: Span) -> Result<Vec<Segment>> {
+    let mut tokens = input.into_iter().peekable();
+
+    match &tokens.next() {
+        Some(TokenTree::Punct(punct)) if punct.as_char() == '<' => {}
+        Some(wrong) => return Err(Error::new(wrong.span(), "expected `<`")),
+        None => return Err(Error::new(scope, "expected `[< ... >]`")),
+    }
+
+    let segments = parse_segments(&mut tokens, scope)?;
+
+    match &tokens.next() {
+        Some(TokenTree::Punct(punct)) if punct.as_char() == '>' => {}
+        Some(wrong) => return Err(Error::new(wrong.span(), "expected `>`")),
+        None => return Err(Error::new(scope, "expected `[< ... >]`")),
+    }
+
+    match tokens.next() {
+        Some(unexpected) => Err(Error::new(
+            unexpected.span(),
+            "unexpected input, expected `[< ... >]`",
+        )),
+        None => Ok(segments),
+    }
+}
+
+fn parse_segments(
+    tokens: &mut Peekable<token_stream::IntoIter>,
+    scope: Span,
+) -> Result<Vec<Segment>> {
     let mut segments = Vec::new();
-    while !(input.is_empty() || input.peek(Token![>])) {
-        match input.parse()? {
+    while match tokens.peek() {
+        None => false,
+        Some(TokenTree::Punct(punct)) => punct.as_char() != '>',
+        Some(_) => true,
+    } {
+        match tokens.next().unwrap() {
             TokenTree::Ident(ident) => {
                 let mut fragment = ident.to_string();
                 if fragment.starts_with("r#") {
                     fragment = fragment.split_off(2);
                 }
-                if fragment == "env" && input.peek(Token![!]) {
-                    input.parse::<Token![!]>()?;
-                    let arg;
-                    parenthesized!(arg in input);
-                    let var: LitStr = arg.parse()?;
-                    segments.push(Segment::Env(var));
+                if fragment == "env"
+                    && match tokens.peek() {
+                        Some(TokenTree::Punct(punct)) => punct.as_char() == '!',
+                        _ => false,
+                    }
+                {
+                    tokens.next().unwrap(); // `!`
+                    let expect_group = tokens.next();
+                    let parenthesized = match &expect_group {
+                        Some(TokenTree::Group(group))
+                            if group.delimiter() == Delimiter::Parenthesis =>
+                        {
+                            group
+                        }
+                        Some(wrong) => return Err(Error::new(wrong.span(), "expected `(`")),
+                        None => return Err(Error::new(scope, "expected `(` after `env!`")),
+                    };
+                    let mut inner = parenthesized.stream().into_iter();
+                    let lit = match inner.next() {
+                        Some(TokenTree::Literal(lit)) => lit,
+                        Some(wrong) => {
+                            return Err(Error::new(wrong.span(), "expected string literal"))
+                        }
+                        None => {
+                            return Err(Error::new2(
+                                ident.span(),
+                                parenthesized.span(),
+                                "expected string literal as argument to env! macro",
+                            ))
+                        }
+                    };
+                    let lit_string = lit.to_string();
+                    if lit_string.starts_with('"')
+                        && lit_string.ends_with('"')
+                        && lit_string.len() >= 2
+                    {
+                        // TODO: maybe handle escape sequences in the string if
+                        // someone has a use case.
+                        segments.push(Segment::Env(LitStr {
+                            value: lit_string[1..lit_string.len() - 1].to_owned(),
+                            span: lit.span(),
+                        }));
+                    } else {
+                        return Err(Error::new(lit.span(), "expected string literal"));
+                    }
+                    if let Some(unexpected) = inner.next() {
+                        return Err(Error::new(
+                            unexpected.span(),
+                            "unexpected token in env! macro",
+                        ));
+                    }
                 } else {
                     segments.push(Segment::String(fragment));
                 }
             }
             TokenTree::Literal(lit) => {
-                let value = match syn::parse_str(&lit.to_string())? {
-                    Lit::Str(string) => string.value().replace('-', "_"),
-                    Lit::Int(_) => lit.to_string(),
-                    _ => return Err(Error::new(lit.span(), "unsupported literal")),
-                };
-                segments.push(Segment::String(value));
+                let mut lit_string = lit.to_string();
+                if lit_string.contains(&['#', '\\', '.', '+'][..]) {
+                    return Err(Error::new(lit.span(), "unsupported literal"));
+                }
+                lit_string = lit_string
+                    .replace('"', "")
+                    .replace('\'', "")
+                    .replace('-', "_");
+                segments.push(Segment::String(lit_string));
             }
             TokenTree::Punct(punct) => match punct.as_char() {
-                '_' => segments.push(Segment::String("_".to_string())),
+                '_' => segments.push(Segment::String("_".to_owned())),
                 '\'' => segments.push(Segment::Apostrophe(punct.span())),
-                ':' => segments.push(Segment::Modifier(Token![:](punct.span()), input.parse()?)),
+                ':' => {
+                    let colon = Colon { span: punct.span() };
+                    let ident = match tokens.next() {
+                        Some(TokenTree::Ident(ident)) => ident,
+                        wrong => {
+                            let span = wrong.as_ref().map_or(scope, TokenTree::span);
+                            return Err(Error::new(span, "expected identifier after `:`"));
+                        }
+                    };
+                    segments.push(Segment::Modifier(colon, ident));
+                }
                 _ => return Err(Error::new(punct.span(), "unexpected punct")),
             },
             TokenTree::Group(group) => {
                 if group.delimiter() == Delimiter::None {
-                    let nested = parse_segments.parse2(group.stream())?;
+                    let mut inner = group.stream().into_iter().peekable();
+                    let nested = parse_segments(&mut inner, group.span())?;
+                    if let Some(unexpected) = inner.next() {
+                        return Err(Error::new(unexpected.span(), "unexpected token"));
+                    }
                     segments.extend(nested);
                 } else {
                     return Err(Error::new(group.span(), "unexpected token"));
@@ -221,65 +352,87 @@
                 is_lifetime = true;
             }
             Segment::Env(var) => {
-                let resolved = match std::env::var(var.value()) {
+                let resolved = match std::env::var(&var.value) {
                     Ok(resolved) => resolved,
                     Err(_) => {
-                        return Err(Error::new(var.span(), "no such env var"));
+                        return Err(Error::new(
+                            var.span,
+                            &format!("no such env var: {:?}", var.value),
+                        ));
                     }
                 };
                 let resolved = resolved.replace('-', "_");
                 evaluated.push(resolved);
             }
             Segment::Modifier(colon, ident) => {
-                let span = quote!(#colon #ident);
                 let last = match evaluated.pop() {
                     Some(last) => last,
-                    None => return Err(Error::new_spanned(span, "unexpected modifier")),
+                    None => {
+                        return Err(Error::new2(colon.span, ident.span(), "unexpected modifier"))
+                    }
                 };
-                if ident == "lower" {
-                    evaluated.push(last.to_lowercase());
-                } else if ident == "upper" {
-                    evaluated.push(last.to_uppercase());
-                } else if ident == "snake" {
-                    let mut acc = String::new();
-                    let mut prev = '_';
-                    for ch in last.chars() {
-                        if ch.is_uppercase() && prev != '_' {
-                            acc.push('_');
-                        }
-                        acc.push(ch);
-                        prev = ch;
+                match ident.to_string().as_str() {
+                    "lower" => {
+                        evaluated.push(last.to_lowercase());
                     }
-                    evaluated.push(acc.to_lowercase());
-                } else if ident == "camel" {
-                    let mut acc = String::new();
-                    let mut prev = '_';
-                    for ch in last.chars() {
-                        if ch != '_' {
-                            if prev == '_' {
-                                for chu in ch.to_uppercase() {
-                                    acc.push(chu);
-                                }
-                            } else if prev.is_uppercase() {
-                                for chl in ch.to_lowercase() {
-                                    acc.push(chl);
-                                }
-                            } else {
-                                acc.push(ch);
+                    "upper" => {
+                        evaluated.push(last.to_uppercase());
+                    }
+                    "snake" => {
+                        let mut acc = String::new();
+                        let mut prev = '_';
+                        for ch in last.chars() {
+                            if ch.is_uppercase() && prev != '_' {
+                                acc.push('_');
                             }
+                            acc.push(ch);
+                            prev = ch;
                         }
-                        prev = ch;
+                        evaluated.push(acc.to_lowercase());
                     }
-                    evaluated.push(acc);
-                } else {
-                    return Err(Error::new_spanned(span, "unsupported modifier"));
+                    "camel" => {
+                        let mut acc = String::new();
+                        let mut prev = '_';
+                        for ch in last.chars() {
+                            if ch != '_' {
+                                if prev == '_' {
+                                    for chu in ch.to_uppercase() {
+                                        acc.push(chu);
+                                    }
+                                } else if prev.is_uppercase() {
+                                    for chl in ch.to_lowercase() {
+                                        acc.push(chl);
+                                    }
+                                } else {
+                                    acc.push(ch);
+                                }
+                            }
+                            prev = ch;
+                        }
+                        evaluated.push(acc);
+                    }
+                    _ => {
+                        return Err(Error::new2(
+                            colon.span,
+                            ident.span(),
+                            "unsupported modifier",
+                        ));
+                    }
                 }
             }
         }
     }
 
     let pasted = evaluated.into_iter().collect::<String>();
-    let ident = TokenTree::Ident(Ident::new(&pasted, span));
+    let ident = match panic::catch_unwind(|| Ident::new(&pasted, span)) {
+        Ok(ident) => TokenTree::Ident(ident),
+        Err(_) => {
+            return Err(Error::new(
+                span,
+                &format!("`{:?}` is not a valid identifier", pasted),
+            ));
+        }
+    };
     let tokens = if is_lifetime {
         let apostrophe = TokenTree::Punct(Punct::new('\'', Spacing::Joint));
         vec![apostrophe, ident]