use std::borrow::Cow;
use proc_macro2::{Ident, Span};
use syn::spanned::Spanned;
use syn::{GenericArgument, Path, PathArguments, PathSegment, Type, TypePath};
use crate::schema_type::SchemaType;
use crate::{DiagLevel, DiagResult, Diagnostic};
#[derive(Debug)]
enum TypeTreeValue<'t> {
TypePath(&'t TypePath),
Path(&'t Path),
Array(Vec<Self>, Span),
UnitType,
Tuple(Vec<Self>, Span),
}
impl PartialEq for TypeTreeValue<'_> {
fn eq(&self, other: &Self) -> bool {
match self {
Self::Path(_) | Self::TypePath(_) | Self::UnitType => self == other,
Self::Array(array, _) => matches!(other, Self::Array(other, _) if other == array),
Self::Tuple(tuple, _) => matches!(other, Self::Tuple(other, _) if other == tuple),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct TypeTree<'t> {
pub(crate) path: Option<Cow<'t, Path>>,
pub(crate) value_type: ValueType,
pub(crate) generic_type: Option<GenericType>,
pub(crate) children: Option<Vec<Self>>,
}
impl<'t> TypeTree<'t> {
pub(crate) fn from_type(ty: &'t Type) -> DiagResult<Self> {
Self::from_type_paths(Self::get_type_paths(ty)?)
}
fn get_type_paths(ty: &Type) -> DiagResult<Vec<TypeTreeValue<'_>>> {
let type_tree_values = match ty {
Type::Path(path) => {
vec![TypeTreeValue::TypePath(path)]
}
Type::Reference(reference) => Self::get_type_paths(reference.elem.as_ref())?,
Type::Group(group) => Self::get_type_paths(group.elem.as_ref())?,
Type::Slice(slice) => vec![TypeTreeValue::Array(
Self::get_type_paths(&slice.elem)?,
slice.bracket_token.span.join(),
)],
Type::Array(array) => vec![TypeTreeValue::Array(
Self::get_type_paths(&array.elem)?,
array.bracket_token.span.join(),
)],
Type::Tuple(tuple) => {
if tuple.elems.is_empty() {
return Ok(vec![TypeTreeValue::UnitType]);
}
vec![TypeTreeValue::Tuple(
tuple
.elems
.iter()
.map(Self::get_type_paths)
.collect::<DiagResult<Vec<_>>>()?
.into_iter()
.flatten()
.collect(),
tuple.span(),
)]
}
Type::TraitObject(trait_object) => trait_object
.bounds
.iter()
.find_map(|bound| match &bound {
syn::TypeParamBound::Trait(trait_bound) => Some(&trait_bound.path),
syn::TypeParamBound::Lifetime(_) | syn::TypeParamBound::Verbatim(_) => None,
_ => panic!("TypeTree trait object found unrecognized TypeParamBound"),
})
.map(|path| vec![TypeTreeValue::Path(path)])
.unwrap_or_else(Vec::new),
unexpected => {
return Err(Diagnostic::spanned(
unexpected.span(),
DiagLevel::Error,
"unexpected type in component part get type path, expected one of: Path, Tuple, Reference, Group, Array, Slice, TraitObject",
));
}
};
Ok(type_tree_values)
}
fn from_type_paths(paths: Vec<TypeTreeValue<'t>>) -> DiagResult<Self> {
if paths.len() > 1 {
Ok(TypeTree {
path: None,
children: Some(match Self::convert_types(paths) {
Ok(children) => children.collect(),
Err(diag) => return Err(diag),
}),
generic_type: None,
value_type: ValueType::Tuple,
})
} else {
Ok(Self::convert_types(paths)?
.next()
.expect("TypeTreeValue from_type_paths expected at least one TypePath"))
}
}
fn convert_types(
paths: Vec<TypeTreeValue<'t>>,
) -> DiagResult<impl Iterator<Item = TypeTree<'t>>> {
paths
.into_iter()
.map(|value| {
let path = match value {
TypeTreeValue::TypePath(type_path) => &type_path.path,
TypeTreeValue::Path(path) => path,
TypeTreeValue::Array(value, span) => {
let array: Path = Ident::new("Array", span).into();
return Ok(TypeTree {
path: Some(Cow::Owned(array)),
value_type: ValueType::Object,
generic_type: Some(GenericType::Vec),
children: Some(vec![Self::from_type_paths(value)?]),
});
}
TypeTreeValue::Tuple(tuple, _span) => {
return Ok(TypeTree {
path: None,
generic_type: None,
value_type: ValueType::Tuple,
children: Some(match Self::convert_types(tuple) {
Ok(converted_values) => converted_values.collect(),
Err(diag) => return Err(diag),
}),
});
}
TypeTreeValue::UnitType => {
return Ok(TypeTree {
path: None,
value_type: ValueType::Tuple,
generic_type: None,
children: None,
});
}
};
let last_segment = path
.segments
.last()
.expect("at least one segment within path in TypeTree::convert_types");
if last_segment.arguments.is_empty() {
Ok(Self::convert(path, last_segment))
} else {
Self::resolve_schema_type(path, last_segment)
}
})
.collect::<DiagResult<Vec<TypeTree<'t>>>>()
.map(IntoIterator::into_iter)
}
fn resolve_schema_type(path: &'t Path, last_segment: &'t PathSegment) -> DiagResult<Self> {
if last_segment.arguments.is_empty() {
return Err(Diagnostic::spanned(
last_segment.ident.span(),
DiagLevel::Error,
"expected at least one angle bracket argument but was 0",
));
};
let mut generic_schema_type = Self::convert(path, last_segment);
let mut generic_types = match &last_segment.arguments {
PathArguments::AngleBracketed(angle_bracketed_args) => {
if angle_bracketed_args.args.iter().all(|arg| {
matches!(
arg,
GenericArgument::Lifetime(_) | GenericArgument::Const(_)
)
}) {
None
} else {
Some(
angle_bracketed_args
.args
.iter()
.filter(|arg| {
!matches!(
arg,
GenericArgument::Lifetime(_) | GenericArgument::Const(_)
)
})
.map(|arg| match arg {
GenericArgument::Type(arg) => Ok(arg),
_ => Err(Diagnostic::spanned(
arg.span(),
DiagLevel::Error,
"expected generic argument type or generic argument lifetime",
)),
})
.collect::<DiagResult<Vec<_>>>()?
.into_iter(),
)
}
}
_ => {
return Err(Diagnostic::spanned(
last_segment.ident.span(),
DiagLevel::Error,
"unexpected path argument, expected angle bracketed path argument",
));
}
};
generic_schema_type.children = generic_types
.as_mut()
.map(|generic_type| generic_type.map(Self::from_type).collect::<DiagResult<_>>())
.transpose()?;
Ok(generic_schema_type)
}
fn convert(path: &'t Path, last_segment: &'t PathSegment) -> Self {
let generic_type = Self::get_generic_type(last_segment);
let schema_type = SchemaType {
path,
nullable: matches!(generic_type, Some(GenericType::Option)),
};
Self {
path: Some(Cow::Borrowed(path)),
value_type: if schema_type.is_primitive() {
ValueType::Primitive
} else if schema_type.is_value() {
ValueType::Value
} else {
ValueType::Object
},
generic_type,
children: None,
}
}
fn get_generic_type(segment: &PathSegment) -> Option<GenericType> {
match &*segment.ident.to_string() {
"HashMap" | "Map" | "BTreeMap" => Some(GenericType::Map),
#[cfg(feature = "indexmap")]
"IndexMap" => Some(GenericType::Map),
"Vec" => Some(GenericType::Vec),
"BTreeSet" | "HashSet" => Some(GenericType::Set),
"LinkedList" => Some(GenericType::LinkedList),
#[cfg(feature = "smallvec")]
"SmallVec" => Some(GenericType::SmallVec),
"Option" => Some(GenericType::Option),
"Cow" => Some(GenericType::Cow),
"Box" => Some(GenericType::Box),
"Arc" => Some(GenericType::Arc),
"Rc" => Some(GenericType::Rc),
"RefCell" => Some(GenericType::RefCell),
_ => None,
}
}
pub(crate) fn is(&self, s: &str) -> bool {
let mut is = self
.path
.as_ref()
.map(|path| {
path.segments
.last()
.expect("expected at least one segment in TreeTypeValue path")
.ident
== s
})
.unwrap_or(false);
if let Some(ref children) = self.children {
is = is || children.iter().any(|child| child.is(s));
}
is
}
pub(crate) fn is_object(&self) -> bool {
self.is("Object")
}
pub(crate) fn is_value(&self) -> bool {
self.is("Value")
}
pub(crate) fn is_option(&self) -> bool {
matches!(self.generic_type, Some(GenericType::Option))
}
pub(crate) fn is_map(&self) -> bool {
matches!(self.generic_type, Some(GenericType::Map))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum ValueType {
Primitive,
Object,
Tuple,
Value,
}
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub(crate) enum GenericType {
Vec,
LinkedList,
Set,
#[cfg(feature = "smallvec")]
SmallVec,
Map,
Option,
Cow,
Box,
RefCell,
Arc,
Rc,
}