use nom::{
Err as NomErr, IResult, Input, Parser, branch::alt, combinator::all_consuming,
combinator::value, error::Error as NomError, error::ErrorKind,
};
use syn::{Expr, GenericArgument, Lifetime, Path, PathArguments, PathSegment, Type, parse_quote};
use crate::path_segment_input::PathSegmentInput;
#[derive(Clone, Copy)]
pub(crate) enum FallbackBorrowMode {
NeedsBorrow,
AlreadyBorrowed,
}
pub(crate) struct FieldMapping {
pub(crate) borrowed_type: Type,
pub(crate) generation_expression: Expr,
}
pub(crate) fn field_mapping(
source_type: &Type,
borrow_lifetime: &Lifetime,
source_expression: Expr,
mode: FallbackBorrowMode,
) -> FieldMapping {
let type_shape = TypeShape::classify(source_type);
FieldMapping {
borrowed_type: type_shape.borrowed_type(borrow_lifetime),
generation_expression: type_shape.generation_expression(source_expression, mode),
}
}
#[derive(Clone, Copy)]
enum TypeShape<'a> {
Option(&'a Type),
String,
Vec(&'a Type),
Array(&'a Type),
Box(&'a Type),
PathBuf,
ImmutableReference(&'a Type),
Fallback(&'a Type),
}
impl<'a> TypeShape<'a> {
fn classify(source_type: &'a Type) -> Self {
match source_type {
Type::Array(array) => Self::Array(array.elem.as_ref()),
Type::Reference(reference) if reference.mutability.is_none() => {
Self::ImmutableReference(source_type)
}
Type::Path(type_path) if type_path.qself.is_none() => {
let Some(path_kind) = path_kind(&type_path.path) else {
return Self::Fallback(source_type);
};
match path_kind {
PathKind::Option => single_type_argument(&type_path.path)
.map_or(Self::Fallback(source_type), Self::Option),
PathKind::String => Self::String,
PathKind::Vec => single_type_argument(&type_path.path)
.map_or(Self::Fallback(source_type), Self::Vec),
PathKind::Box => single_type_argument(&type_path.path)
.map_or(Self::Fallback(source_type), Self::Box),
PathKind::PathBuf => Self::PathBuf,
}
}
_ => Self::Fallback(source_type),
}
}
fn borrowed_type(self, borrow_lifetime: &Lifetime) -> Type {
match self {
Self::Option(inner_type) => {
let inner_borrowed_type = Self::classify(inner_type).borrowed_type(borrow_lifetime);
parse_quote!(::core::option::Option<#inner_borrowed_type>)
}
Self::String => parse_quote!(&#borrow_lifetime str),
Self::Vec(inner_type) | Self::Array(inner_type) => {
parse_quote!(&#borrow_lifetime [#inner_type])
}
Self::Box(inner_type) => parse_quote!(&#borrow_lifetime #inner_type),
Self::PathBuf => parse_quote!(&#borrow_lifetime ::std::path::Path),
Self::ImmutableReference(source_type) => source_type.clone(),
Self::Fallback(source_type) => parse_quote!(&#borrow_lifetime #source_type),
}
}
fn generation_expression(self, source_expression: Expr, mode: FallbackBorrowMode) -> Expr {
match self {
Self::Option(inner_type) => {
let inner_expression = Self::classify(inner_type).generation_expression(
parse_quote!(value),
FallbackBorrowMode::AlreadyBorrowed,
);
parse_quote!(#source_expression.as_ref().map(|value| #inner_expression))
}
Self::String => parse_quote!(#source_expression.as_str()),
Self::Vec(_) => parse_quote!(#source_expression.as_slice()),
Self::Array(_) => parse_quote!(&#source_expression[..]),
Self::Box(_) => parse_quote!(#source_expression.as_ref()),
Self::PathBuf => parse_quote!(#source_expression.as_path()),
Self::ImmutableReference(_) => source_expression,
Self::Fallback(_) => match mode {
FallbackBorrowMode::NeedsBorrow => parse_quote!(&#source_expression),
FallbackBorrowMode::AlreadyBorrowed => source_expression,
},
}
}
}
fn single_type_argument(path: &Path) -> Option<&Type> {
let last_segment = path.segments.last()?;
let PathArguments::AngleBracketed(arguments) = &last_segment.arguments else {
return None;
};
let mut type_arguments = arguments.args.iter().filter_map(|argument| match argument {
GenericArgument::Type(type_argument) => Some(type_argument),
_ => None,
});
let type_argument = type_arguments.next()?;
if type_arguments.next().is_some() {
return None;
}
Some(type_argument)
}
#[derive(Clone, Copy)]
enum PathKind {
Option,
String,
Vec,
Box,
PathBuf,
}
fn path_kind(path: &Path) -> Option<PathKind> {
parse_path_kind(PathSegmentInput::new(path))
.ok()
.map(|(_remaining, path_kind)| path_kind)
}
type SegmentParseResult<'a, Output> = IResult<PathSegmentInput<'a>, Output>;
fn parse_path_kind(input: PathSegmentInput<'_>) -> SegmentParseResult<'_, PathKind> {
all_consuming(alt((
value(PathKind::Option, segment("Option")),
value(
PathKind::Option,
(segment("std"), segment("option"), segment("Option")),
),
value(
PathKind::Option,
(segment("core"), segment("option"), segment("Option")),
),
value(PathKind::String, segment("String")),
value(
PathKind::String,
(segment("std"), segment("string"), segment("String")),
),
value(
PathKind::String,
(segment("alloc"), segment("string"), segment("String")),
),
value(PathKind::Vec, segment("Vec")),
value(
PathKind::Vec,
(segment("std"), segment("vec"), segment("Vec")),
),
value(
PathKind::Vec,
(segment("alloc"), segment("vec"), segment("Vec")),
),
value(PathKind::Box, segment("Box")),
value(
PathKind::Box,
(segment("std"), segment("boxed"), segment("Box")),
),
value(
PathKind::Box,
(segment("alloc"), segment("boxed"), segment("Box")),
),
value(PathKind::PathBuf, segment("PathBuf")),
value(
PathKind::PathBuf,
(segment("std"), segment("path"), segment("PathBuf")),
),
)))
.parse(input)
}
fn segment<'a>(
expected_segment: &'static str,
) -> impl Parser<PathSegmentInput<'a>, Output = &'a PathSegment, Error = NomError<PathSegmentInput<'a>>>
{
move |input: PathSegmentInput<'a>| {
let Some(segment) = input.iter_elements().next() else {
return Err(NomErr::Error(NomError::new(input, ErrorKind::Tag)));
};
if segment.ident == expected_segment {
Ok((input.take_from(1), segment))
} else {
Err(NomErr::Error(NomError::new(input, ErrorKind::Tag)))
}
}
}