use derive_where::derive_where;
use proc_macro2::{Ident, TokenStream, TokenTree};
use quote::{quote, ToTokens};
use syn::{
braced,
ext::IdentExt,
parse::{Parse, ParseStream},
token::Brace,
Error, Result, Token,
};
#[derive_where(Debug)]
#[derive(Clone)]
pub enum IdentOrPounded {
Ident(Ident),
Pounded(#[derive_where(skip)] Token![#], TokenTree),
}
impl IdentOrPounded {
fn is_self(&self) -> bool {
if let Self::Ident(ident) = self {
ident == "self"
} else {
false
}
}
fn is_ident(&self) -> bool {
matches!(self, Self::Ident(_))
}
}
impl ToTokens for IdentOrPounded {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
IdentOrPounded::Ident(ident) => ident.to_tokens(tokens),
IdentOrPounded::Pounded(pound, tt) => {
pound.to_tokens(tokens);
tt.to_tokens(tokens);
}
}
}
}
impl Parse for IdentOrPounded {
fn parse(input: ParseStream) -> Result<Self> {
Ident::parse_any(input)
.map(Self::Ident)
.or_else(|_| Ok(Self::Pounded(input.parse()?, input.parse()?)))
}
}
#[derive(Clone, Debug, Default)]
pub struct Path(Vec<IdentOrPounded>);
impl Path {
fn push(&mut self, value: IdentOrPounded) {
self.0.push(value);
}
fn pop_self(&mut self) -> bool {
self.0.last().map_or(false, IdentOrPounded::is_self) && {
self.pop();
true
}
}
fn get_ident(&self) -> Result<&Ident> {
match self.0.last().expect("path should contain a segment") {
IdentOrPounded::Ident(ident) => Ok(ident),
IdentOrPounded::Pounded(pound, _) => Err(Error::new_spanned(
pound,
"expected ident as last path segment",
)),
}
}
fn pop_ident(&mut self) -> Result<Ident> {
match self.0.pop().expect("path should contain a segment") {
IdentOrPounded::Ident(ident) => Ok(ident),
IdentOrPounded::Pounded(pound, _) => Err(Error::new_spanned(
pound,
"expected ident as last path segment",
)),
}
}
fn pop(&mut self) {
self.0
.pop()
.expect("path should contain at least one segment");
}
}
impl ToTokens for Path {
fn to_tokens(&self, tokens: &mut TokenStream) {
let first = self.0.first().expect("path should contain a segment");
let colons = first.is_ident().then_some(quote!(::));
let tail = &self.0[1..];
quote!(#colons #first #(::#tail)*).to_tokens(tokens)
}
}
#[derive(Clone, Debug)]
pub struct Use(pub Path, pub Ident);
#[derive(Clone, Debug, Default)]
pub struct UseItem(pub Vec<Use>);
fn parse_use_segment(
parent: &Path,
input: ParseStream,
output: &mut Vec<Use>,
inner: bool,
) -> Result<()> {
let mut path = parent.clone();
let end = || {
Ok(if input.peek(Token![;]) {
if inner {
return Err(input.error("expected ident, `,`, `::` or `{`"));
} else {
true
}
} else {
input.is_empty()
})
};
loop {
if end()? {
break;
} else if input.peek(Brace) {
let inner;
braced!(inner in input);
parse_use_segment(&path, &inner, output, true)?;
if end()? {
break;
} else {
<Token![,]>::parse(input)?;
path = parent.clone();
}
} else {
path.push(input.parse()?);
if <Token![,]>::parse(input).is_ok() || end()? {
if path.pop_self() {
output.push(Use(path.clone(), path.get_ident()?.clone()));
} else {
output.push(Use(path.clone(), path.pop_ident()?));
}
if !end()? {
path = parent.clone();
}
} else if <Token![as]>::parse(input).is_ok() {
let was_self = path.pop_self();
output.push(Use(path.clone(), input.parse()?));
if !was_self {
path.pop();
}
if end()? {
break;
} else {
<Token![,]>::parse(input)?;
path = parent.clone();
}
} else {
<Token![::]>::parse(input)?;
}
}
}
Ok(())
}
impl Parse for UseItem {
fn parse(input: ParseStream) -> Result<Self> {
if input.is_empty() {
return Ok(Self::default());
}
let mut output = Vec::new();
<Token![use]>::parse(input)?;
Option::<Token![::]>::parse(input)?;
parse_use_segment(&Default::default(), input, &mut output, false)?;
<Token![;]>::parse(input)?;
Ok(Self(output))
}
}
#[cfg(test)]
mod test {
use pretty_assertions::assert_eq;
use quote::ToTokens;
use syn::{parse::Parser, parse_str};
use super::*;
macro_rules! assert_use_item {
($use:literal, $($path:literal as $ident:ident),* $(,)*) => {
let UseItem(uses) = parse_str($use).unwrap();
let mut uses = uses.into_iter();
$(
let Use(path, ident) = uses.next().unwrap();
assert_eq!(path.into_token_stream().to_string().replace(' ', ""), $path);
assert_eq!(ident, stringify!($ident));
)*
};
}
#[test]
fn use_item() {
assert_use_item!("use ::a::b;", "::a::b" as b);
assert_use_item!(
"use a::{c, self, b};",
"::a::c" as c,
"::a" as a,
"::a::b" as b
);
assert_use_item!("use a::{self as c, b as a};", "::a" as c, "::a::b" as a);
assert_use_item!(
"use a::{b::{a, b}, c};",
"::a::b::a" as a,
"::a::b::b" as b,
"::a::c" as c
);
assert_use_item!("use #var::a;", "#var::a" as a);
assert_use_item!("use ::a::#var::a;", "::a::#var::a" as a);
assert_use_item!("use ::a::#var as a;", "::a::#var" as a);
}
macro_rules! assert_error {
($use:literal) => {
UseItem::parse.parse_str($use).unwrap_err();
};
}
#[test]
fn error() {
assert_error!("use ::a::#b;");
}
}