use std::fmt::{Debug, Formatter};
use proc_macro_error2::abort;
use quote::{quote, ToTokens};
use syn::{Generics, GenericParam, TypePath, Path, PathArguments, Expr, Lit, LitStr, ExprLit, Macro, parse_str, Attribute, PathSegment, Type, MetaList, TypeParam};
use syn::spanned::Spanned;
use syn::token::PathSep;
use syn::visit_mut::VisitMut;
use crate::args::{CondParams, TraitGen};
use crate::lib_macros::{VERBOSE, VERBOSE_TF};
use crate::utils::{path_prefix_len, pathname, replace_str};
const TRAIT_GEN: &str = "trait_gen";
const TYPE_GEN: &str = if cfg!(feature = "no_type_gen") { TRAIT_GEN } else { "type_gen" };
const TRAIT_GEN_IF: &str = "trait_gen_if";
const TYPE_GEN_IF: &str = if cfg!(feature = "no_type_gen") { TRAIT_GEN_IF } else { "type_gen_if" };
#[derive(Clone, PartialEq)]
pub enum SubstType {
Path(Path),
Type(Type)
}
impl ToTokens for SubstType {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
match self {
SubstType::Path(path) => path.to_tokens(tokens),
SubstType::Type(ty) => ty.to_tokens(tokens)
}
}
}
impl Debug for SubstType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
SubstType::Path(p) => write!(f, "Path({})", pathname(p)),
SubstType::Type(t) => write!(f, "Type({})", pathname(t)),
}
}
}
pub(crate) fn to_subst_types(mut types: Vec<Type>) -> (bool, Vec<SubstType>) {
let mut visitor = TurboFish;
for ty in types.iter_mut() {
visitor.visit_type_mut(ty);
}
let is_path = types.iter().all(|ty| matches!(ty, Type::Path(_)));
let subst_types = types.into_iter()
.map(|ty|
if is_path {
if let Type::Path(p) = ty {
SubstType::Path(p.path)
} else {
panic!("this should match Type::Path: {:?}", ty)
}
} else {
SubstType::Type(ty)
})
.collect::<Vec<_>>();
(is_path, subst_types)
}
struct TurboFish;
impl VisitMut for TurboFish {
fn visit_path_mut(&mut self, node: &mut Path) {
if VERBOSE_TF {
println!("TURBOF: path '{}'", pathname(node));
}
for segment in &mut node.segments {
if let PathArguments::AngleBracketed(generic_args) = &mut segment.arguments {
generic_args.colon2_token = Some(PathSep::default());
}
}
}
}
#[derive(Clone)]
pub(crate) struct Subst<'a> {
pub generic_arg: Path,
pub types: Vec<SubstType>,
pub is_path: bool,
pub can_subst_path: Vec<bool>,
pub type_helper: Option<&'a Vec<SubstType>>
}
impl<'a> Subst<'a> {
pub fn from_trait_gen(attribute: TraitGen, generic_arg: Path) -> Self {
let (is_path, types) = to_subst_types(attribute.types);
Subst {
generic_arg,
types,
is_path,
can_subst_path: vec![],
type_helper: None
}
}
fn can_subst_path(&self) -> bool {
*self.can_subst_path.last().unwrap_or(&true)
}
fn get_example_types(&self) -> String {
let mut examples = self.type_helper.unwrap_or(&Vec::new()).iter().map(pathname).take(3).collect::<Vec<_>>();
while examples.len() < 3 {
examples.push(format!("Type{}", examples.len() + 1));
}
examples.join(", ")
}
}
impl Debug for Subst<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "PathTypes {{ generic_arg: {}, types: [{}], is_path: {}, enabled: {}, type_helper: {} }}",
pathname(&self.generic_arg),
self.types.iter().map(pathname).collect::<Vec<_>>().join(", "),
self.is_path,
self.can_subst_path.iter().map(|e| e.to_string()).collect::<Vec<_>>().join(", "),
self.type_helper.unwrap_or(&Vec::new()).iter().map(pathname).collect::<Vec<_>>().join(", "),
)
}
}
impl VisitMut for Subst<'_> {
fn visit_attribute_mut(&mut self, node: &mut Attribute) {
if let Some(PathSegment { ident, .. }) = node.path().segments.last() {
let ident = ident.to_string();
match ident.as_str() {
#[allow(unreachable_patterns)]
TRAIT_GEN_IF | TYPE_GEN_IF => {
match node.parse_args::<CondParams>() {
Ok(cond) => {
let mut output = proc_macro2::TokenStream::new();
if VERBOSE { println!("- {} -> {}", pathname(&self.generic_arg), pathname(self.types.first().unwrap())); }
let mut g = cond.generic_arg;
self.visit_type_mut(&mut g);
if cond.is_negated {
output.extend(quote!(!#g in));
} else {
output.extend(quote!(#g in));
}
let mut first = true;
for mut ty in cond.types {
self.visit_type_mut(&mut ty);
if !first {
output.extend(quote!(, ));
}
output.extend(quote!(#ty));
first = false;
}
let original = pathname(&node);
if let syn::Meta::List(MetaList { ref mut tokens, .. }) = node.meta {
if VERBOSE { println!(" {original} => {}", pathname(&output)); }
*tokens = output;
return;
} else {
abort!(node.meta.span(), "invalid {} attribute format", ident;
help = "The expected format is: #[{}({} -> {})]", ident, pathname(&self.generic_arg), self.get_example_types());
};
}
Err(err) => {
abort!(err.span(), err;
help = "The expected format is: #[{}({} in {})]", ident, pathname(&self.generic_arg), self.get_example_types());
}
};
}
#[allow(unreachable_patterns)]
TRAIT_GEN | TYPE_GEN => {
match node.parse_args::<TraitGen>() {
Ok(mut types) => {
let mut output = proc_macro2::TokenStream::new();
let g = types.args;
output.extend(quote!(#g -> ));
let mut first = true;
for ty in &mut types.types {
self.visit_type_mut(ty);
if !first {
output.extend(quote!(, ));
}
output.extend(quote!(#ty));
first = false;
}
if let syn::Meta::List(MetaList { ref mut tokens, .. }) = node.meta {
*tokens = output;
return;
} else {
abort!(node.meta.span(), "invalid {} attribute format", ident;
help = "The expected format is: #[{}({} -> {})]", ident, pathname(&self.generic_arg), self.get_example_types());
};
}
Err(err) => {
abort!(err.span(), err;
help = "The expected format is: #[{}({} -> {})]", ident, pathname(&self.generic_arg), self.get_example_types());
}
};
}
_ => ()
}
}
syn::visit_mut::visit_attribute_mut(self, node);
}
fn visit_expr_mut(&mut self, node: &mut Expr) {
let mut enabled = self.can_subst_path();
match node {
Expr::Call(_) => enabled = true,
Expr::Cast(_) => enabled = true,
Expr::Struct(_) => enabled = true,
Expr::Path(_) => { }
_ => enabled = false,
};
self.can_subst_path.push(enabled);
syn::visit_mut::visit_expr_mut(self, node);
self.can_subst_path.pop();
}
fn visit_expr_lit_mut(&mut self, node: &mut ExprLit) {
if let Lit::Str(lit) = &node.lit {
if let Some(ts_str) = replace_str(
&lit.to_token_stream().to_string(),
&format!("${{{}}}", pathname(&self.generic_arg)),
&pathname(self.types.first().unwrap()))
{
let new_lit: LitStr = parse_str(&ts_str).unwrap_or_else(|_| panic!("parsing LitStr failed: {}", ts_str));
node.lit = Lit::Str(new_lit);
} else {
syn::visit_mut::visit_expr_lit_mut(self, node);
}
}
}
fn visit_generics_mut(&mut self, i: &mut Generics) {
if let Some(segment) = self.generic_arg.segments.first() {
let current_ident = &segment.ident;
for t in i.params.iter() {
match &t {
GenericParam::Type(TypeParam { ident, .. }) => {
if ident == current_ident {
abort!(ident.span(),
"Type '{}' is reserved for the substitution.", current_ident.to_string();
help = "Use another identifier for this local generic type."
);
}
}
_ => {}
}
}
}
syn::visit_mut::visit_generics_mut(self, i);
}
fn visit_macro_mut(&mut self, node: &mut Macro) {
if let Some(ts_str) = replace_str(
&node.tokens.to_string(),
&format!("${{{}}}", pathname(&self.generic_arg)),
&pathname(self.types.first().unwrap()))
{
let new_ts: proc_macro2::TokenStream = ts_str.parse().unwrap_or_else(|_| panic!("parsing Macro failed: {}", ts_str));
node.tokens = new_ts;
} else {
syn::visit_mut::visit_macro_mut(self, node);
}
}
fn visit_path_mut(&mut self, path: &mut Path) {
let path_name = pathname(path);
let path_length = path.segments.len();
if let Some(length) = path_prefix_len(&self.generic_arg, path) {
if length < path_length || self.can_subst_path() {
if VERBOSE { print!("[path] path: {} length = {}", path_name, length); }
match self.types.first().unwrap() {
SubstType::Path(p) => {
let mut new_seg = p.segments.clone();
for seg in path.segments.iter().skip(length) {
new_seg.push(seg.clone());
}
let nth_new_seg = new_seg.last_mut().unwrap();
let nth_seg = path.segments.iter().nth(length - 1).unwrap();
if nth_new_seg.arguments.is_empty() && !nth_seg.arguments.is_empty() {
nth_new_seg.arguments = nth_seg.arguments.clone();
}
path.segments = new_seg;
if VERBOSE { println!(" -> {}", pathname(path)); }
}
SubstType::Type(ty) => {
if VERBOSE { println!(" -> Path '{}' cannot be substituted by type '{}'", path_name, pathname(ty)); }
abort!(ty.span(), "Path '{}' cannot be substituted by type '{}'", path_name, pathname(ty));
}
}
} else {
if VERBOSE { println!("disabled path: {}", path_name); }
syn::visit_mut::visit_path_mut(self, path);
}
} else {
if VERBOSE { println!("path: {} mismatch", path_name); }
syn::visit_mut::visit_path_mut(self, path);
}
}
fn visit_type_mut(&mut self, node: &mut Type) {
if !self.is_path {
match node {
Type::Path(TypePath { path, .. }) => {
let path_name = pathname(path);
let path_length = path.segments.len();
if let Some(length) = path_prefix_len(&self.generic_arg, path) {
if self.can_subst_path() {
assert!(length == path_length, "length={length}, path_length={path_length}");
if VERBOSE {
print!("[type] type path: {} length = {length}, path length = {path_length} {} -> ",
path_name, if self.can_subst_path() { ", can_subst" } else { "" });
}
*node = if let SubstType::Type(ty) = self.types.first().unwrap() {
if VERBOSE { println!("{}", pathname(ty)); }
ty.clone()
} else {
panic!("found path item instead of type in SubstType")
};
}
} else {
syn::visit_mut::visit_type_mut(self, node);
}
}
_ => syn::visit_mut::visit_type_mut(self, node),
}
} else {
syn::visit_mut::visit_type_mut(self, node);
}
}
fn visit_type_path_mut(&mut self, typepath: &mut TypePath) {
self.can_subst_path.push(true);
let TypePath { path, .. } = typepath;
if VERBOSE { println!("typepath: {}", pathname(path)); }
syn::visit_mut::visit_type_path_mut(self, typepath);
self.can_subst_path.pop();
}
}