use crate::constants::pagination;
use crate::fetch::{generate_fetch_call_expr, generate_pool_expr};
use crate::type_system::{FetchMethod, QueryType, ReturnType};
use syn::spanned::Spanned;
use syn::{GenericArgument, PathArguments, Type};
pub struct TypeAnalyzer;
impl TypeAnalyzer {
pub fn analyze_type(ty: &Type) -> Result<ReturnType, syn::Error> {
match ty {
Type::Path(tp) => Self::analyze_path(tp),
Type::Tuple(t) => Ok(Self::analyze_tuple(t)),
Type::ImplTrait(impl_trait) => Self::analyze_impl_trait(impl_trait),
_ => Ok(ReturnType::Unknown {
name: "UnknownType".to_string(),
}),
}
}
fn analyze_path(tp: &syn::TypePath) -> Result<ReturnType, syn::Error> {
let path = &tp.path;
if path.segments.is_empty() {
return Ok(ReturnType::Unknown {
name: "empty_path".to_string(),
});
}
let Some(last) = path.segments.last() else {
return Ok(ReturnType::Unknown {
name: "EmptyPath".to_string(),
});
};
match &last.arguments {
PathArguments::AngleBracketed(args) => Self::analyze_generic(path, last, args),
_ => Self::analyze_non_generic(path),
}
}
fn analyze_generic(
path: &syn::Path,
segment: &syn::PathSegment,
args: &syn::AngleBracketedGenericArguments,
) -> Result<ReturnType, syn::Error> {
let inner = || -> Result<Option<ReturnType>, syn::Error> {
for a in &args.args {
if let GenericArgument::Type(t) = a {
return Ok(Some(Self::analyze_type(t)?));
}
}
Ok(None)
};
match segment.ident.to_string().as_str() {
"Vec" => match inner()? {
Some(t) => Ok(ReturnType::Vec {
element_type: Box::new(t),
}),
None => Ok(ReturnType::Unknown {
name: "Vec<?>".to_string(),
}),
},
"Option" => match inner()? {
Some(t) => Ok(ReturnType::Option {
inner_type: Box::new(t),
}),
None => Ok(ReturnType::Unknown {
name: "Option<?>".to_string(),
}),
},
"Result" => {
let mut types = Vec::new();
for a in &args.args {
if let GenericArgument::Type(t) = a {
types.push(Self::analyze_type(t)?);
}
}
match types.len() {
2 => {
let mut iter = types.into_iter();
let ok_type = iter.next().ok_or_else(|| {
syn::Error::new(
proc_macro2::Span::call_site(),
"Result type must have at least one generic parameter",
)
})?;
let err_type = iter.next().ok_or_else(|| {
syn::Error::new(
proc_macro2::Span::call_site(),
"Result type with 2 parameters must have error type",
)
})?;
Ok(ReturnType::Result {
ok_type: Box::new(ok_type),
err_type: Box::new(err_type),
})
}
1 => {
let ok_type = types.into_iter().next().ok_or_else(|| {
syn::Error::new(
proc_macro2::Span::call_site(),
"Result type must have at least one generic parameter",
)
})?;
Ok(ReturnType::Result {
ok_type: Box::new(ok_type),
err_type: Box::new(ReturnType::Unknown {
name: "sqlx_data::Error".to_string(),
}),
})
}
_ => Ok(ReturnType::Unknown {
name: Self::path_to_string(path),
}),
}
}
"Page" => Ok(ReturnType::Unknown {
name: "Page_deprecated".to_string(),
}),
"HashMap" | "BTreeMap" | "IndexMap" | "HashSet" => {
Ok(ReturnType::Struct {
name: syn::Ident::new(&segment.ident.to_string(), segment.ident.span()),
})
}
name if pagination::ALL_TYPES.contains(&name) => {
Ok(ReturnType::Struct {
name: syn::Ident::new(&segment.ident.to_string(), segment.ident.span()),
})
}
"DateTime" => {
Ok(ReturnType::Scalar {
name: syn::Ident::new(&segment.ident.to_string(), segment.ident.span()),
})
}
"Json" => {
Ok(ReturnType::Scalar {
name: syn::Ident::new(&segment.ident.to_string(), segment.ident.span()),
})
}
#[cfg(feature = "postgres")]
"PgRange" | "PgInterval" | "PgMoney" | "PgLTree" | "PgLQuery" | "PgCiText"
| "PgCube" | "PgPoint" | "PgLine" | "PgLSeg" | "PgBox" | "PgPath"
| "PgPolygon" | "PgCircle" | "PgHstore" | "PgTimeTz" => {
Ok(ReturnType::Scalar {
name: syn::Ident::new(&segment.ident.to_string(), segment.ident.span()),
})
}
_ => Ok(ReturnType::Unknown {
name: Self::path_to_string(path),
}),
}
}
fn analyze_non_generic(path: &syn::Path) -> Result<ReturnType, syn::Error> {
if Self::is_scalar(path)? {
Ok(ReturnType::Scalar {
name: Self::last_ident(path)?.clone(),
})
} else {
Ok(ReturnType::Struct {
name: Self::last_ident(path)?.clone(),
})
}
}
fn analyze_tuple(tuple: &syn::TypeTuple) -> ReturnType {
if tuple.elems.is_empty() {
return ReturnType::Unit;
}
let mut elements = Vec::new();
for elem in &tuple.elems {
match Self::analyze_type(elem) {
Ok(t) => elements.push(t),
Err(_) => elements.push(ReturnType::Unknown {
name: "tuple_element_error".to_string(),
}),
}
}
ReturnType::Tuple { elements }
}
fn analyze_impl_trait(impl_trait: &syn::TypeImplTrait) -> Result<ReturnType, syn::Error> {
for bound in &impl_trait.bounds {
let syn::TypeParamBound::Trait(trait_bound) = bound else {
continue;
};
if !Self::path_ends_with(&trait_bound.path, "Stream") {
continue;
}
if let Some(item_type) = Self::extract_stream_item_type(&trait_bound.path) {
return Ok(ReturnType::Stream {
item_type: Box::new(Self::analyze_type(&item_type)?),
});
}
}
Ok(ReturnType::Unknown {
name: "ImplTrait".to_string(),
})
}
fn extract_stream_item_type(path: &syn::Path) -> Option<syn::Type> {
let last_segment = path.segments.last()?;
let syn::PathArguments::AngleBracketed(args) = &last_segment.arguments else {
return None;
};
for arg in &args.args {
let syn::GenericArgument::AssocType(assoc_type) = arg else {
continue;
};
if assoc_type.ident == "Item" {
return Some(assoc_type.ty.clone());
}
}
None
}
pub fn is_scalar(path: &syn::Path) -> Result<bool, syn::Error> {
let is_basic = Self::is_basic_scalar_type(path);
if is_basic {
return Ok(true);
}
Ok(Self::is_complex_scalar_type(path))
}
fn is_basic_scalar_type(path: &syn::Path) -> bool {
Self::ends_with_any(
path,
&[
"i8", "i16", "i32", "i64", "i128", "u8", "u16", "u32", "u64", "u128", "f32", "f64",
"bool", "char", "usize", "isize", "String",
],
)
}
fn is_complex_scalar_type(path: &syn::Path) -> bool {
let is_common = Self::ends_with_any(
path,
&[
"Cow",
"Uuid",
"Hyphenated",
"Simple",
"Decimal",
"BigDecimal",
"Decimal",
"Time",
"Date",
"DateTime",
"NaiveDateTime",
"NaiveDate",
"NaiveTime",
"PrimitiveDateTime",
"OffsetDateTime",
"MacAddress",
"Blob",
"IpNet",
"Ipv4Net",
"Ipv6Net",
"IpNetwork",
"BitVec",
"BString",
"JsonValue",
],
);
if is_common {
return true;
}
#[cfg(feature = "postgres")]
if Self::is_postgres_scalar_type(path) {
return true;
}
false
}
#[cfg(feature = "postgres")]
fn is_postgres_scalar_type(path: &syn::Path) -> bool {
Self::ends_with_any(
path,
&[
"PgInterval",
"PgMoney",
"PgLTree",
"PgLQuery",
"PgCiText",
"PgCube",
"PgPoint",
"PgLine",
"PgLSeg",
"PgBox",
"PgPath",
"PgPolygon",
"PgCircle",
"PgHstore",
"PgTimeTz",
],
)
}
pub fn determine_query_strategy(rust_type: &ReturnType) -> syn::Result<QueryType> {
match rust_type {
ReturnType::Result { ok_type, .. } => Self::determine_query_strategy(ok_type),
ReturnType::Unit => Ok(QueryType::Query),
ReturnType::Scalar { .. } => Ok(QueryType::QueryScalar),
ReturnType::Struct { .. } => Ok(QueryType::QueryAs),
ReturnType::Tuple { .. } => Ok(QueryType::QueryAs),
ReturnType::Vec { element_type } => Self::determine_query_strategy(element_type),
ReturnType::Option { inner_type } => Self::determine_query_strategy(inner_type),
ReturnType::Stream { item_type } => Self::determine_query_strategy(item_type),
_ => Ok(QueryType::Query),
}
}
pub fn determine_fetch_method(rust_type: &ReturnType) -> FetchMethod {
match rust_type {
ReturnType::Result { ok_type, .. } => Self::determine_fetch_method(ok_type),
ReturnType::Unit => FetchMethod::Execute,
ReturnType::Struct { name }
if pagination::ALL_TYPES.contains(&name.to_string().as_str()) =>
{
FetchMethod::FetchAll
}
ReturnType::Struct { name } if name.to_string().as_str().ends_with("QueryResult") => {
FetchMethod::Execute
}
ReturnType::Struct { name: _ } => FetchMethod::FetchOne,
ReturnType::Vec { .. } => FetchMethod::FetchAll,
ReturnType::Option { .. } => FetchMethod::FetchOptional,
ReturnType::Stream { .. } => FetchMethod::Fetch,
_ => FetchMethod::FetchOne,
}
}
pub fn determine_pool_expr(method: &crate::dml::DmlMethod) -> proc_macro2::TokenStream {
generate_pool_expr(method)
}
pub fn determine_fetch_call(
fetch_method: &FetchMethod,
pool_expr: &proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
generate_fetch_call_expr(fetch_method, pool_expr)
}
pub fn is_bytes_type(param_type: &syn::Type) -> bool {
let syn::Type::Path(type_path) = param_type else {
return false;
};
Self::path_ends_with(&type_path.path, "Bytes")
}
pub fn get_vec_inner_type(ty: &syn::Type) -> Option<&syn::Type> {
let syn::Type::Path(syn::TypePath { path, .. }) = ty else {
return None;
};
let segment = path.segments.last()?;
if segment.ident != "Vec" {
return None;
}
let syn::PathArguments::AngleBracketed(args) = &segment.arguments else {
return None;
};
let Some(syn::GenericArgument::Type(inner)) = args.args.first() else {
return None;
};
Some(inner)
}
pub fn get_iterable_inner_type(ty: &syn::Type) -> Option<&syn::Type> {
match ty {
syn::Type::Path(_) => Self::get_vec_inner_type(ty),
syn::Type::Reference(type_ref) => {
let syn::Type::Slice(slice_type) = &*type_ref.elem else {
return None;
};
Some(&*slice_type.elem)
}
syn::Type::Array(array_type) => Some(&*array_type.elem),
syn::Type::ImplTrait(impl_trait) => {
Self::extract_into_iterator_item_type(&impl_trait.bounds)
}
_ => None,
}
}
fn extract_into_iterator_item_type(
bounds: &syn::punctuated::Punctuated<syn::TypeParamBound, syn::token::Plus>,
) -> Option<&syn::Type> {
for bound in bounds {
let syn::TypeParamBound::Trait(trait_bound) = bound else {
continue;
};
if !Self::path_ends_with(&trait_bound.path, "IntoIterator") {
continue;
}
let last_segment = trait_bound.path.segments.last()?;
let syn::PathArguments::AngleBracketed(args) = &last_segment.arguments else {
continue;
};
for arg in &args.args {
let syn::GenericArgument::AssocType(assoc_type) = arg else {
continue;
};
if assoc_type.ident == "Item" {
return Some(&assoc_type.ty);
}
}
}
None
}
pub fn is_tuple_iterable_type(ty: &syn::Type, generics: &syn::Generics) -> bool {
if let Some(inner_type) = Self::get_iterable_inner_type(ty) {
return Self::is_tuple_type(inner_type);
}
if let syn::Type::Path(path) = ty
&& path.path.segments.len() == 1
{
let param_name = &path.path.segments[0].ident.to_string();
return Self::extract_tuple_from_where_clause(generics, param_name).is_some();
}
false
}
pub fn is_tuple_type(ty: &syn::Type) -> bool {
matches!(ty, syn::Type::Tuple(_))
}
pub fn is_vec_u8_type(ty: &syn::Type) -> bool {
let Some(inner) = Self::get_vec_inner_type(ty) else {
return false;
};
Self::is_path_u8(inner)
}
fn is_path_u8(ty: &syn::Type) -> bool {
let syn::Type::Path(syn::TypePath { path, .. }) = ty else {
return false;
};
let Some(segment) = path.segments.last() else {
return false;
};
segment.ident == "u8"
}
pub fn is_already_json_wrapped(param_type: &syn::Type) -> bool {
let syn::Type::Path(type_path) = param_type else {
return false;
};
Self::path_ends_with(&type_path.path, "Json")
}
pub fn extract_impl_into_type(param_type: &syn::Type) -> Option<syn::Type> {
let syn::Type::ImplTrait(impl_trait) = param_type else {
return None;
};
for bound in &impl_trait.bounds {
let syn::TypeParamBound::Trait(trait_bound) = bound else {
continue;
};
let is_into_trait = Self::path_ends_with(&trait_bound.path, "Into");
if !is_into_trait {
continue;
}
let Some(last_segment) = trait_bound.path.segments.last() else {
continue;
};
let syn::PathArguments::AngleBracketed(args) = &last_segment.arguments else {
continue;
};
let Some(syn::GenericArgument::Type(inner_type)) = args.args.first() else {
continue;
};
return Some(inner_type.clone());
}
None
}
pub fn extract_tuple_from_where_clause(
generics: &syn::Generics,
param_name: &str,
) -> Option<syn::Type> {
let where_clause = generics.where_clause.as_ref()?;
for predicate in &where_clause.predicates {
let syn::WherePredicate::Type(type_predicate) = predicate else {
continue;
};
let syn::Type::Path(bounded_type) = &type_predicate.bounded_ty else {
continue;
};
let Some(last_segment) = bounded_type.path.segments.last() else {
continue;
};
if last_segment.ident != param_name {
continue;
}
for bound in &type_predicate.bounds {
let syn::TypeParamBound::Trait(trait_bound) = bound else {
continue;
};
let is_into_iterator = Self::path_ends_with(&trait_bound.path, "IntoIterator");
if !is_into_iterator {
continue;
}
let Some(last_trait_segment) = trait_bound.path.segments.last() else {
continue;
};
let syn::PathArguments::AngleBracketed(args) = &last_trait_segment.arguments else {
continue;
};
for arg in &args.args {
let syn::GenericArgument::AssocType(binding) = arg else {
continue;
};
if binding.ident != "Item" {
continue;
}
if Self::is_tuple_type(&binding.ty) {
return Some(binding.ty.clone());
}
if let syn::Type::Reference(ref_type) = &binding.ty
&& Self::is_tuple_type(&ref_type.elem)
{
return Some(binding.ty.clone());
}
}
}
}
None
}
fn path_to_string(path: &syn::Path) -> String {
path.segments
.iter()
.map(|s| s.ident.to_string())
.collect::<Vec<_>>()
.join("::")
}
fn last_ident(path: &syn::Path) -> Result<&syn::Ident, syn::Error> {
path.segments
.last()
.map(|seg| &seg.ident)
.ok_or_else(|| syn::Error::new(path.span(), "Path has no segments"))
}
fn last_ident_safe(path: &syn::Path) -> Option<&syn::Ident> {
path.segments.last().map(|seg| &seg.ident)
}
pub fn path_ends_with(path: &syn::Path, name: &str) -> bool {
Self::last_ident(path)
.map(|ident| ident == name)
.unwrap_or(false)
}
pub fn ends_with_any(path: &syn::Path, names: &[&str]) -> bool {
names.iter().any(|n| TypeAnalyzer::path_ends_with(path, n))
}
pub fn is_vec_option_type(return_type: &syn::Type) -> bool {
let analyzed = match TypeAnalyzer::analyze_type(return_type) {
Ok(t) => t,
Err(_) => {
log::warn!(
"Failed to analyze return type for Vec<Option<T>> detection: {:?}",
return_type
);
return false;
}
};
match analyzed {
crate::type_system::ReturnType::Result { ok_type, .. } => {
matches!(ok_type.as_ref(),
crate::type_system::ReturnType::Vec { element_type }
if matches!(element_type.as_ref(), crate::type_system::ReturnType::Option { .. })
)
}
_ => false,
}
}
pub fn types_equal(a: &syn::Type, b: &syn::Type) -> bool {
quote::ToTokens::to_token_stream(a).to_string()
== quote::ToTokens::to_token_stream(b).to_string()
}
}
pub struct TypeCastingAnalyzer;
impl TypeCastingAnalyzer {
pub fn needs_casting(target_type: &syn::Type) -> bool {
let native_type = Self::native_type(target_type);
!TypeAnalyzer::types_equal(target_type, &native_type)
}
pub fn extract_option_type(ty: &syn::Type) -> Option<syn::Type> {
let syn::Type::Path(syn::TypePath { path, .. }) = ty else {
return None;
};
if !TypeAnalyzer::path_ends_with(path, "Option") {
return None;
}
let last_segment = path.segments.last()?;
let syn::PathArguments::AngleBracketed(args) = &last_segment.arguments else {
return None;
};
let syn::GenericArgument::Type(inner_type) = args.args.first()? else {
return None;
};
Some(inner_type.clone())
}
pub fn native_type(ty: &syn::Type) -> syn::Type {
match ty {
syn::Type::Path(syn::TypePath { path, .. }) => {
let Some(ident) = TypeAnalyzer::last_ident_safe(path) else {
return ty.clone();
};
match ident {
#[cfg(feature = "sqlite")]
ident
if ident == "u64"
|| ident == "u32"
|| ident == "u16"
|| ident == "u8"
|| ident == "usize"
|| ident == "i32"
|| ident == "i16"
|| ident == "i8"
|| ident == "isize" =>
{
syn::parse_quote!(i64)
}
#[cfg(feature = "sqlite")]
ident if ident == "f32" => syn::parse_quote!(f64),
#[cfg(feature = "sqlite")]
ident if ident == "bool" => syn::parse_quote!(i64),
_ => ty.clone(),
}
}
_ => ty.clone(),
}
}
}
pub fn extract_column_name(full_name: &str) -> &str {
let base_name = if let Some(colon_pos) = full_name.find(':') {
full_name[..colon_pos].trim()
} else {
full_name
};
base_name.trim_end_matches('!')
}
pub fn extract_sqlx_explicit_type(column_name: &str) -> Option<syn::Type> {
let colon_pos = column_name.find(':')?;
let type_part = column_name[colon_pos + 1..].trim();
syn::parse_str(type_part).ok()
}
pub fn has_explicit_sqlx_type(column_name: &str) -> bool {
column_name.contains(':')
}
pub fn clean_sqlx_cast_syntax_for_runtime(sql: &str) -> String {
use crate::constants::regex::SQLX_CAST_CLEANER;
SQLX_CAST_CLEANER.replace_all(sql, "$2").to_string()
}
#[cfg(test)]
mod tests {
use super::*;
use syn::parse_quote;
#[test]
fn test_scalar_type_analysis() {
let ty: Type = parse_quote!(i64);
let analyzed = TypeAnalyzer::analyze_type(&ty).unwrap();
match analyzed {
ReturnType::Scalar { name } => {
assert_eq!(name, "i64");
}
_ => panic!("Expected scalar type"),
}
}
#[test]
fn test_extract_sqlx_explicit_type() {
assert!(extract_sqlx_explicit_type("name: String").is_some());
assert!(extract_sqlx_explicit_type("age: u8").is_some());
assert!(extract_sqlx_explicit_type("count!: i64").is_some());
assert!(extract_sqlx_explicit_type("name : String ").is_some());
assert!(extract_sqlx_explicit_type("name").is_none());
assert!(extract_sqlx_explicit_type("age_value").is_none());
}
#[test]
fn test_has_explicit_sqlx_type() {
assert!(has_explicit_sqlx_type("name: String"));
assert!(has_explicit_sqlx_type("age!: u8"));
assert!(has_explicit_sqlx_type("count : i64"));
assert!(!has_explicit_sqlx_type("name"));
assert!(!has_explicit_sqlx_type("age_value"));
assert!(!has_explicit_sqlx_type("user_count"));
}
#[test]
fn test_option_type_analysis() {
let ty: Type = parse_quote!(Option<String>);
let analyzed = TypeAnalyzer::analyze_type(&ty).unwrap();
match analyzed {
ReturnType::Option { inner_type } => match inner_type.as_ref() {
ReturnType::Scalar { name, .. } => assert_eq!(name, "String"),
_ => panic!("Expected inner scalar type"),
},
_ => panic!("Expected Option type"),
}
}
#[test]
fn test_vec_type_analysis() {
let ty: Type = parse_quote!(Vec<User>);
let analyzed = TypeAnalyzer::analyze_type(&ty).unwrap();
match analyzed {
ReturnType::Vec { element_type } => match element_type.as_ref() {
ReturnType::Struct { name } => assert_eq!(name, "User"),
_ => panic!("Expected inner struct type"),
},
_ => panic!("Expected Vec type"),
}
}
#[test]
fn test_query_strategy_determination() {
use syn::parse_quote;
let ty: Type = parse_quote!(Result<Vec<User>>);
let analyzed = TypeAnalyzer::analyze_type(&ty).unwrap();
let query_type = TypeAnalyzer::determine_query_strategy(&analyzed);
match query_type {
Ok(QueryType::QueryAs) => {
}
_ => panic!("Expected QueryAs strategy"),
}
}
#[test]
fn test_is_scalar_comprehensive() {
use syn::parse_quote;
assert!(TypeAnalyzer::is_scalar(&parse_quote!(i8)).unwrap());
assert!(TypeAnalyzer::is_scalar(&parse_quote!(i16)).unwrap());
assert!(TypeAnalyzer::is_scalar(&parse_quote!(i32)).unwrap());
assert!(TypeAnalyzer::is_scalar(&parse_quote!(i64)).unwrap());
assert!(TypeAnalyzer::is_scalar(&parse_quote!(i128)).unwrap());
assert!(TypeAnalyzer::is_scalar(&parse_quote!(u8)).unwrap());
assert!(TypeAnalyzer::is_scalar(&parse_quote!(u16)).unwrap());
assert!(TypeAnalyzer::is_scalar(&parse_quote!(u32)).unwrap());
assert!(TypeAnalyzer::is_scalar(&parse_quote!(u64)).unwrap());
assert!(TypeAnalyzer::is_scalar(&parse_quote!(u128)).unwrap());
assert!(TypeAnalyzer::is_scalar(&parse_quote!(f32)).unwrap());
assert!(TypeAnalyzer::is_scalar(&parse_quote!(f64)).unwrap());
assert!(TypeAnalyzer::is_scalar(&parse_quote!(bool)).unwrap());
assert!(TypeAnalyzer::is_scalar(&parse_quote!(char)).unwrap());
assert!(TypeAnalyzer::is_scalar(&parse_quote!(usize)).unwrap());
assert!(TypeAnalyzer::is_scalar(&parse_quote!(isize)).unwrap());
assert!(TypeAnalyzer::is_scalar(&parse_quote!(String)).unwrap());
assert!(TypeAnalyzer::is_scalar(&parse_quote!(NaiveDateTime)).unwrap());
assert!(TypeAnalyzer::is_scalar(&parse_quote!(NaiveDate)).unwrap());
assert!(TypeAnalyzer::is_scalar(&parse_quote!(NaiveTime)).unwrap());
assert!(TypeAnalyzer::is_scalar(&parse_quote!(DateTime<Utc>)).unwrap());
assert!(TypeAnalyzer::is_scalar(&parse_quote!(Uuid)).unwrap());
assert!(TypeAnalyzer::is_scalar(&parse_quote!(Decimal)).unwrap());
assert!(TypeAnalyzer::is_scalar(&parse_quote!(Blob)).unwrap());
assert!(TypeAnalyzer::is_scalar(&parse_quote!(Cow<str>)).unwrap());
assert!(TypeAnalyzer::is_scalar(&parse_quote!(std::string::String)).unwrap());
assert!(
TypeAnalyzer::is_scalar(&parse_quote!(sqlx::types::chrono::NaiveDateTime)).unwrap()
);
assert!(TypeAnalyzer::is_scalar(&parse_quote!(chrono::NaiveDate)).unwrap());
assert!(TypeAnalyzer::is_scalar(&parse_quote!(::std::primitive::i64)).unwrap());
assert!(!TypeAnalyzer::is_scalar(&parse_quote!(Vec<i32>)).unwrap());
assert!(!TypeAnalyzer::is_scalar(&parse_quote!(Option<String>)).unwrap());
assert!(!TypeAnalyzer::is_scalar(&parse_quote!(Result<i64, String>)).unwrap());
assert!(!TypeAnalyzer::is_scalar(&parse_quote!(HashMap<String, i64>)).unwrap());
assert!(!TypeAnalyzer::is_scalar(&parse_quote!(User)).unwrap());
assert!(!TypeAnalyzer::is_scalar(&parse_quote!(CustomStruct)).unwrap());
assert!(!TypeAnalyzer::is_scalar(&parse_quote!(Box<i64>)).unwrap());
assert!(!TypeAnalyzer::is_scalar(&parse_quote!(Arc<String>)).unwrap());
assert!(!TypeAnalyzer::is_scalar(&parse_quote!(Rc<NaiveDateTime>)).unwrap());
}
}