use std::collections::HashSet;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote_spanned;
use syn::visit::Visit;
use syn::{
Error, FnArg, GenericArgument, GenericParam, Ident, Path, PathArguments, PathSegment, Receiver,
Result, ReturnType, Signature, Type, TypeParamBound, TypePath,
};
use crate::options::TraitSpec;
pub(crate) fn bare_ident(ty: &Type) -> Option<&Ident> {
single_segment(ty)
.filter(|segment| segment.arguments.is_none())
.map(|segment| &segment.ident)
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum Conversion {
Keep,
Rewrap,
Into,
Map(Box<Conversion>),
MapErr(Box<Conversion>, Box<Conversion>),
Tuple(Vec<Conversion>),
}
impl Conversion {
pub(crate) fn rewraps(&self) -> bool {
match self {
Conversion::Keep | Conversion::Into => false,
Conversion::Rewrap => true,
Conversion::Map(inner) => inner.rewraps(),
Conversion::MapErr(value, error) => value.rewraps() || error.rewraps(),
Conversion::Tuple(items) => items.iter().any(Conversion::rewraps),
}
}
pub(crate) fn unwrapped(self) -> Self {
match self {
Conversion::Map(inner) | Conversion::MapErr(inner, _) => *inner,
other => other,
}
}
fn is_a_path(&self) -> bool {
matches!(
self,
Conversion::Keep | Conversion::Rewrap | Conversion::Into
)
}
fn simplified(self) -> Self {
match self {
Conversion::Map(inner) if *inner == Conversion::Keep => Conversion::Keep,
Conversion::MapErr(value, error)
if *value == Conversion::Keep && *error == Conversion::Keep =>
{
Conversion::Keep
}
Conversion::Tuple(items) if items.iter().all(|item| *item == Conversion::Keep) => {
Conversion::Keep
}
other => other,
}
}
}
pub(crate) fn single_segment(ty: &Type) -> Option<&PathSegment> {
match ty {
Type::Path(TypePath { qself: None, path }) if path.segments.len() == 1 => {
Some(&path.segments[0])
}
_ => None,
}
}
pub(crate) fn is_self(ty: &Type) -> bool {
single_segment(ty).is_some_and(|segment| segment.ident == "Self" && segment.arguments.is_none())
}
pub(crate) fn self_assoc(ty: &Type) -> Option<&Ident> {
match ty {
Type::Path(TypePath { qself: None, path })
if path.segments.len() == 2 && path.segments[0].ident == "Self" =>
{
Some(&path.segments[1].ident)
}
_ => None,
}
}
pub(crate) fn generic_type_args(segment: &PathSegment) -> Vec<&Type> {
match &segment.arguments {
PathArguments::AngleBracketed(arguments) => arguments
.args
.iter()
.filter_map(|argument| match argument {
GenericArgument::Type(ty) => Some(ty),
_ => None,
})
.collect(),
_ => vec![],
}
}
pub(crate) fn last_segment(ty: &Type) -> Option<&PathSegment> {
match ty {
Type::Path(TypePath { qself: None, path }) => path.segments.last(),
_ => None,
}
}
#[derive(Clone)]
pub(crate) enum FutureShape {
Opaque,
Boxed(Path),
}
pub(crate) fn future_output(ty: &Type) -> Option<(Option<&Type>, FutureShape)> {
let (bounds, shape) = match ty {
Type::ImplTrait(impl_trait) => (&impl_trait.bounds, FutureShape::Opaque),
_ => {
let pin = last_segment(ty).filter(|segment| segment.ident == "Pin")?;
let pin_arguments = generic_type_args(pin);
let [boxed] = pin_arguments.as_slice() else {
return None;
};
let boxed: &Type = boxed;
let box_segment = last_segment(boxed).filter(|segment| segment.ident == "Box")?;
let box_arguments = generic_type_args(box_segment);
let [pointee] = box_arguments.as_slice() else {
return None;
};
let Type::TraitObject(object) = *pointee else {
return None;
};
let Type::Path(TypePath { path, .. }) = boxed else {
return None;
};
let mut box_path = path.clone();
if let Some(last) = box_path.segments.last_mut() {
last.arguments = PathArguments::None;
}
(&object.bounds, FutureShape::Boxed(box_path))
}
};
bounds.iter().find_map(|bound| {
let TypeParamBound::Trait(bound) = bound else {
return None;
};
let last = bound.path.segments.last()?;
if last.ident != "Future" {
return None;
}
let PathArguments::AngleBracketed(arguments) = &last.arguments else {
return Some((None, shape.clone()));
};
let output = arguments.args.iter().find_map(|argument| match argument {
GenericArgument::AssocType(assoc) if assoc.ident == "Output" => Some(&assoc.ty),
_ => None,
});
Some((output, shape.clone()))
})
}
pub(crate) struct ReturnSite<'a> {
pub(crate) subject: &'a Ident,
pub(crate) one_target: bool,
pub(crate) by_hand: &'a str,
}
pub(crate) fn classify_return(
output: &ReturnType,
spec: Option<&TraitSpec>,
site: &ReturnSite,
) -> Result<(Conversion, Option<FutureShape>)> {
let ReturnType::Type(_, ty) = output else {
return Ok((Conversion::Keep, None));
};
let (ty, future) = match future_output(ty) {
Some((Some(output), shape)) => (output, Some(shape)),
Some((None, FutureShape::Opaque)) => {
return Ok((
Conversion::Keep,
(!site.one_target).then_some(FutureShape::Opaque),
));
}
Some((None, FutureShape::Boxed(_))) => return Ok((Conversion::Keep, None)),
None => (&**ty, None),
};
if site.one_target {
if let Type::ImplTrait(_) = ty {
return Ok((Conversion::Keep, None));
}
let conversion = return_model(ty, spec, site)?;
let awaited = future.filter(|_| conversion != Conversion::Keep);
return Ok((conversion, awaited));
}
if let Some(opaque) = first_impl_trait(ty) {
return Err(Error::new_spanned(
opaque,
"a return-position `impl Trait` cannot be dispatched by a `match` because every variant returns a different opaque type; return a concrete type or a boxed trait object, or `impl Future<Output = T>`, which is awaited like an `async fn`",
));
}
let conversion = return_model(ty, spec, site)?;
let future = match future {
Some(FutureShape::Boxed(_)) if conversion == Conversion::Keep => None,
other => other,
};
Ok((conversion, future))
}
fn first_impl_trait(ty: &Type) -> Option<&Type> {
struct Finder<'ast>(Option<&'ast Type>);
impl<'ast> Visit<'ast> for Finder<'ast> {
fn visit_type(&mut self, ty: &'ast Type) {
if self.0.is_some() {
return;
}
if let Type::ImplTrait(_) = ty {
self.0 = Some(ty);
return;
}
syn::visit::visit_type(self, ty);
}
}
let mut finder = Finder(None);
finder.visit_type(ty);
finder.0
}
pub(crate) fn return_model(
ty: &Type,
spec: Option<&TraitSpec>,
site: &ReturnSite,
) -> Result<Conversion> {
let conversion = classify(ty, ty, spec, site)?;
check_expressible(&conversion, ty, site)?;
Ok(conversion)
}
fn classify(
ty: &Type,
whole: &Type,
spec: Option<&TraitSpec>,
site: &ReturnSite,
) -> Result<Conversion> {
if is_self(ty) {
return Ok(Conversion::Rewrap);
}
if let Some(name) = self_assoc(ty) {
let overridden = spec.is_some_and(|spec| spec.is_overridden(name));
return Ok(if overridden {
Conversion::Into
} else {
Conversion::Keep
});
}
if let Some(segment) = last_segment(ty) {
let arguments = generic_type_args(segment);
let wrapped = match (segment.ident.to_string().as_str(), arguments.as_slice()) {
("Option", [inner]) => Some(Conversion::Map(Box::new(classify(
inner, whole, spec, site,
)?))),
("Result", [inner]) => Some(Conversion::Map(Box::new(classify(
inner, whole, spec, site,
)?))),
("Result", [inner, error]) => Some(Conversion::MapErr(
Box::new(classify(inner, whole, spec, site)?),
Box::new(classify(error, whole, spec, site)?),
)),
_ => None,
};
if let Some(conversion) = wrapped {
return Ok(conversion.simplified());
}
}
if let Type::Tuple(tuple) = ty {
let items = tuple
.elems
.iter()
.map(|item| classify(item, whole, spec, site))
.collect::<Result<Vec<_>>>()?;
return Ok(Conversion::Tuple(items).simplified());
}
if mentions_self(ty) {
return Err(self_limit(whole, site));
}
Ok(Conversion::Keep)
}
fn self_limit(whole: &Type, site: &ReturnSite) -> Error {
let ReturnSite {
subject, by_hand, ..
} = site;
Error::new_spanned(
whole,
format!("this return type mentions `Self` where `whichever` cannot wrap the payload's result back into `{subject}`; return `Self`, `Option<Self>`, `Result<Self, E>` or a tuple of those, spelled with those names, or {by_hand}"),
)
}
fn check_expressible(conversion: &Conversion, whole: &Type, site: &ReturnSite) -> Result<()> {
match conversion {
Conversion::Keep | Conversion::Rewrap | Conversion::Into => Ok(()),
Conversion::Map(inner) if inner.is_a_path() => Ok(()),
Conversion::MapErr(value, error)
if value.is_a_path() && matches!(**error, Conversion::Keep | Conversion::Into) =>
{
Ok(())
}
Conversion::Tuple(items) => items
.iter()
.try_for_each(|item| check_expressible(item, whole, site)),
_ => Err(self_limit(whole, site)),
}
}
pub(crate) fn mentions_self(ty: &Type) -> bool {
let mut finder = SelfFinder(false);
finder.visit_type(ty);
finder.0
}
pub(crate) struct SelfFinder(bool);
impl<'ast> Visit<'ast> for SelfFinder {
fn visit_type_path(&mut self, type_path: &'ast TypePath) {
if let Some(qself) = &type_path.qself {
if !is_self(&qself.ty) {
self.visit_type(&qself.ty);
}
self.visit_path(&type_path.path);
return;
}
if type_path.path.segments.len() == 1 && type_path.path.segments[0].ident == "Self" {
self.0 = true;
}
syn::visit::visit_type_path(self, type_path);
}
}
#[derive(Clone, Copy)]
pub(crate) enum Access {
Shared,
Exclusive,
Owned,
}
pub(crate) enum ArgumentKind {
Plain,
SameVariant(Access),
}
#[derive(Clone, Copy)]
pub(crate) enum ReceiverKind {
Value(Access),
Boxed,
Pinned(Access),
}
pub(crate) fn reference_access(reference: &syn::TypeReference) -> Access {
if reference.mutability.is_some() {
Access::Exclusive
} else {
Access::Shared
}
}
pub(crate) fn classify_receiver(receiver: &Receiver) -> Result<ReceiverKind> {
let ty = &*receiver.ty;
if is_self(ty) {
return Ok(ReceiverKind::Value(Access::Owned));
}
if let Type::Reference(reference) = ty {
if is_self(&reference.elem) {
return Ok(ReceiverKind::Value(reference_access(reference)));
}
}
if let Some(segment) = last_segment(ty) {
if let [argument] = generic_type_args(segment).as_slice() {
if segment.ident == "Box" && is_self(argument) {
return Ok(ReceiverKind::Boxed);
}
if segment.ident == "Pin" {
if let Type::Reference(reference) = argument {
if is_self(&reference.elem) {
return Ok(ReceiverKind::Pinned(reference_access(reference)));
}
}
}
}
}
Err(Error::new_spanned(
ty,
"this receiver type is not supported; a receiver is `self`, `&self`, `&mut self`, `self: Box<Self>`, `self: Pin<&Self>` or `self: Pin<&mut Self>`",
))
}
pub(crate) fn turbofish(signature: &Signature) -> Option<TokenStream2> {
let mut named = IdentCollector::default();
for input in &signature.inputs {
if let FnArg::Typed(pat_type) = input {
named.visit_type(&pat_type.ty);
}
}
let parameters: Vec<&Ident> = signature
.generics
.params
.iter()
.filter_map(|param| match param {
GenericParam::Type(param) => Some(¶m.ident),
GenericParam::Const(param) => Some(¶m.ident),
GenericParam::Lifetime(_) => None,
})
.collect();
let inferred = parameters
.iter()
.all(|parameter| named.0.contains(¶meter.to_string()));
if parameters.is_empty() || inferred {
return None;
}
let site = crate::at(signature.ident.span());
Some(quote_spanned!(site => ::<#(#parameters),*>))
}
#[derive(Default)]
struct IdentCollector(HashSet<String>);
impl<'ast> Visit<'ast> for IdentCollector {
fn visit_ident(&mut self, ident: &'ast Ident) {
self.0.insert(ident.to_string());
}
}
pub(crate) fn takes_self_argument(signature: &Signature) -> bool {
signature.inputs.iter().any(|input| match input {
FnArg::Typed(pat_type) => {
matches!(
argument_kind(&pat_type.ty),
Ok(ArgumentKind::SameVariant(_))
)
}
FnArg::Receiver(_) => false,
})
}
pub(crate) fn argument_kind(ty: &Type) -> Result<ArgumentKind> {
let (inner, access) = match ty {
Type::Reference(reference) if reference.mutability.is_some() => {
(&*reference.elem, Access::Exclusive)
}
Type::Reference(reference) => (&*reference.elem, Access::Shared),
other => (other, Access::Owned),
};
if is_self(inner) {
return Ok(ArgumentKind::SameVariant(access));
}
if mentions_self(ty) {
return Err(Error::new_spanned(
ty,
"only `Self`, `&Self` and `&mut Self` arguments can be dispatched; this type mentions `Self` elsewhere",
));
}
Ok(ArgumentKind::Plain)
}
#[cfg(test)]
mod tests {
use quote::{quote, ToTokens};
use syn::parse_quote;
use super::*;
fn site(subject: &Ident, one_target: bool) -> ReturnSite<'_> {
ReturnSite {
subject,
one_target,
by_hand: "implement the method by hand",
}
}
fn kind() -> Ident {
Ident::new("Kind", proc_macro2::Span::call_site())
}
fn model(ty: Type, spec: Option<&TraitSpec>) -> Result<Conversion> {
return_model(&ty, spec, &site(&kind(), false))
}
fn overriding(name: &str) -> TraitSpec {
syn::parse_str(&format!("Trait, {name} = Fixed")).expect("a valid spec")
}
#[test]
fn wrappers_are_recognised_by_their_last_segment() {
assert_eq!(model(parse_quote!(u32), None).unwrap(), Conversion::Keep);
assert_eq!(model(parse_quote!(Self), None).unwrap(), Conversion::Rewrap);
assert_eq!(
model(parse_quote!(io::Result<Self>), None).unwrap(),
Conversion::Map(Box::new(Conversion::Rewrap))
);
assert_eq!(
model(parse_quote!(::core::option::Option<Self>), None).unwrap(),
Conversion::Map(Box::new(Conversion::Rewrap))
);
assert_eq!(
model(parse_quote!(Option<u32>), None).unwrap(),
Conversion::Keep,
"a wrapper around nothing to convert is nothing to convert"
);
}
#[test]
fn an_overridden_associated_type_converts_in_every_position() {
let spec = overriding("Error");
let spec = Some(&spec);
assert_eq!(
model(parse_quote!(Self::Error), spec).unwrap(),
Conversion::Into
);
assert_eq!(
model(parse_quote!(Option<Self::Error>), spec).unwrap(),
Conversion::Map(Box::new(Conversion::Into))
);
assert_eq!(
model(parse_quote!(Result<Self, Self::Error>), spec).unwrap(),
Conversion::MapErr(Box::new(Conversion::Rewrap), Box::new(Conversion::Into))
);
assert_eq!(
model(parse_quote!(Self::Other), spec).unwrap(),
Conversion::Keep
);
}
#[test]
fn a_tuple_converts_element_by_element() {
let spec = overriding("Out");
assert_eq!(
model(parse_quote!((Self, u32)), None).unwrap(),
Conversion::Tuple(vec![Conversion::Rewrap, Conversion::Keep])
);
assert_eq!(
model(parse_quote!((Self::Out, Option<Self>)), Some(&spec)).unwrap(),
Conversion::Tuple(vec![
Conversion::Into,
Conversion::Map(Box::new(Conversion::Rewrap))
])
);
assert_eq!(
model(parse_quote!((u32, bool)), None).unwrap(),
Conversion::Keep
);
assert!(
model(parse_quote!(Option<(Self, u32)>), None).is_err(),
"a tuple under a wrapper needs a closure"
);
}
#[test]
fn self_anywhere_else_is_a_limit() {
assert!(model(parse_quote!(Vec<Self>), None).is_err());
assert!(model(parse_quote!(Result<u32, Self>), None).is_err());
assert!(model(parse_quote!(Option<Option<Self>>), None).is_err());
}
#[test]
fn receivers_are_classified_by_their_type() {
let receiver = |receiver: Receiver| classify_receiver(&receiver);
assert!(matches!(
receiver(parse_quote!(&self)),
Ok(ReceiverKind::Value(Access::Shared))
));
assert!(matches!(
receiver(parse_quote!(&mut self)),
Ok(ReceiverKind::Value(Access::Exclusive))
));
assert!(matches!(
receiver(parse_quote!(self)),
Ok(ReceiverKind::Value(Access::Owned))
));
assert!(matches!(
receiver(parse_quote!(self: Box<Self>)),
Ok(ReceiverKind::Boxed)
));
assert!(matches!(
receiver(parse_quote!(self: Pin<&mut Self>)),
Ok(ReceiverKind::Pinned(Access::Exclusive))
));
assert!(matches!(
receiver(parse_quote!(self: Pin<&Self>)),
Ok(ReceiverKind::Pinned(Access::Shared))
));
assert!(receiver(parse_quote!(self: Rc<Self>)).is_err());
}
#[test]
fn self_arguments_are_told_from_the_rest() {
assert!(matches!(
argument_kind(&parse_quote!(&Self)),
Ok(ArgumentKind::SameVariant(Access::Shared))
));
assert!(matches!(
argument_kind(&parse_quote!(&mut Self)),
Ok(ArgumentKind::SameVariant(Access::Exclusive))
));
assert!(matches!(
argument_kind(&parse_quote!(Self)),
Ok(ArgumentKind::SameVariant(Access::Owned))
));
assert!(matches!(
argument_kind(&parse_quote!(u32)),
Ok(ArgumentKind::Plain)
));
assert!(argument_kind(&parse_quote!(Option<&Self>)).is_err());
}
#[test]
fn futures_are_awaited_and_other_opaque_returns_are_limits() {
let kind = kind();
let returns = |output: ReturnType| classify_return(&output, None, &site(&kind, false));
let (model, future) = returns(parse_quote!(-> impl Future<Output = Self> + Send)).unwrap();
assert!(matches!(future, Some(FutureShape::Opaque)));
assert_eq!(model, Conversion::Rewrap);
let (model, future) = returns(parse_quote!(-> impl Future)).unwrap();
assert!(matches!(future, Some(FutureShape::Opaque)));
assert_eq!(model, Conversion::Keep);
assert!(returns(parse_quote!(-> impl Iterator<Item = u32>)).is_err());
assert!(
returns(parse_quote!(-> Result<impl Iterator<Item = u32>, E>)).is_err(),
"an opaque type is found at any depth"
);
let (_, future) = returns(parse_quote!()).unwrap();
assert!(future.is_none());
}
#[test]
fn a_boxed_future_is_awaited_only_to_convert_its_output() {
let kind = kind();
let spec = overriding("Error");
let returns = |output: ReturnType| {
classify_return(&output, Some(&spec), &site(&kind, false)).unwrap()
};
let (model, future) = returns(parse_quote!(
-> ::core::pin::Pin<Box<dyn ::core::future::Future<Output = u32> + Send + 'async_trait>>
));
assert_eq!(model, Conversion::Keep);
assert!(
future.is_none(),
"one type for every arm, forwarded as it is"
);
let (model, future) = returns(parse_quote!(
-> Pin<alloc::boxed::Box<dyn Future<Output = Result<u32, Self::Error>> + Send + 'a>>
));
assert_eq!(
model,
Conversion::MapErr(Box::new(Conversion::Keep), Box::new(Conversion::Into))
);
let Some(FutureShape::Boxed(path)) = future else {
panic!("a converted output is awaited in a boxed block");
};
assert_eq!(
path.to_token_stream().to_string().replace(' ', ""),
"alloc::boxed::Box"
);
let (_, future) = returns(parse_quote!(-> Pin<Box<dyn Iterator<Item = u32>>>));
assert!(future.is_none(), "a boxed trait object that is no future");
}
#[test]
fn one_target_forwards_an_opaque_return_as_it_is() {
let kind = kind();
let returns =
|output: ReturnType| classify_return(&output, None, &site(&kind, true)).unwrap();
let (model, future) = returns(parse_quote!(-> impl Iterator<Item = u32>));
assert!(model == Conversion::Keep && future.is_none());
let (model, future) = returns(parse_quote!(-> impl Future<Output = u32> + Send));
assert!(
model == Conversion::Keep && future.is_none(),
"a future whose output needs nothing is forwarded without an `async` block"
);
let (model, future) = returns(parse_quote!(-> impl Future<Output = Self>));
assert!(
matches!(future, Some(FutureShape::Opaque)),
"a future whose output is rewrapped is awaited in an `async` block"
);
assert_eq!(model, Conversion::Rewrap);
}
#[test]
fn the_self_limit_names_the_subject_and_the_way_out() {
let error = model(parse_quote!(Vec<Self>), None)
.unwrap_err()
.to_string();
assert!(error.contains("back into `Kind`"), "{error}");
assert!(
error.ends_with("or implement the method by hand"),
"{error}"
);
}
#[test]
fn a_turbofish_is_written_only_when_no_argument_fixes_a_parameter() {
let spelled = |signature: Signature| turbofish(&signature).map(|tokens| tokens.to_string());
assert_eq!(
spelled(parse_quote!(fn parse<T: FromStr>(&self) -> Option<T>)),
Some(quote!(::<T>).to_string())
);
assert_eq!(
spelled(parse_quote!(fn apply<F: Fn(u32) -> u32>(&self, f: F) -> u32)),
None
);
assert_eq!(
spelled(parse_quote!(fn zeros<const N: usize>(&self) -> [u8; N])),
Some(quote!(::<N>).to_string())
);
assert_eq!(
spelled(parse_quote!(fn pick<'a, T>(&'a self, from: &'a [T]) -> &'a T)),
None
);
assert_eq!(
spelled(parse_quote!(fn both<T, U>(&self, t: T) -> U)),
Some(quote!(::<T, U>).to_string())
);
}
}