use std::collections::HashSet;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::visit::Visit;
use syn::{
Error, FnArg, GenericArgument, GenericParam, Ident, 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, Copy, Debug, PartialEq, Eq)]
pub(crate) struct ReturnModel {
pub(crate) wrapper: Wrapper,
pub(crate) value: Value,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Wrapper {
None,
Option,
Result { error: Conversion },
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Value {
Plain,
Rewrap,
Into,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Conversion {
Keep,
Into,
}
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,
}
}
pub(crate) fn future_output(ty: &Type) -> Option<Option<&Type>> {
let Type::ImplTrait(impl_trait) = ty else {
return None;
};
impl_trait.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);
};
Some(arguments.args.iter().find_map(|argument| match argument {
GenericArgument::AssocType(assoc) if assoc.ident == "Output" => Some(&assoc.ty),
_ => None,
}))
})
}
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<(ReturnModel, bool)> {
let ReturnType::Type(_, ty) = output else {
return Ok((ReturnModel::PLAIN, false));
};
let (ty, returns_future) = match future_output(ty) {
Some(Some(output)) => (output, true),
Some(None) => return Ok((ReturnModel::PLAIN, !site.one_target)),
None => (&**ty, false),
};
if site.one_target {
if let Type::ImplTrait(_) = ty {
return Ok((ReturnModel::PLAIN, false));
}
let model = return_model(ty, spec, site)?;
return Ok((model, returns_future && model != ReturnModel::PLAIN));
}
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`",
));
}
Ok((return_model(ty, spec, site)?, returns_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
}
impl ReturnModel {
const PLAIN: Self = Self {
wrapper: Wrapper::None,
value: Value::Plain,
};
}
pub(crate) fn return_model(
ty: &Type,
spec: Option<&TraitSpec>,
site: &ReturnSite,
) -> Result<ReturnModel> {
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((Wrapper::Option, *inner)),
("Result", [inner]) => Some((
Wrapper::Result {
error: Conversion::Keep,
},
*inner,
)),
("Result", [inner, error]) => Some((
Wrapper::Result {
error: conversion(error, ty, spec, site)?,
},
*inner,
)),
_ => None,
};
if let Some((wrapper, inner)) = wrapped {
return Ok(ReturnModel {
wrapper,
value: value(inner, ty, spec, site)?,
});
}
}
Ok(ReturnModel {
wrapper: Wrapper::None,
value: value(ty, ty, spec, site)?,
})
}
fn value(ty: &Type, whole: &Type, spec: Option<&TraitSpec>, site: &ReturnSite) -> Result<Value> {
if is_self(ty) {
return Ok(Value::Rewrap);
}
Ok(match conversion(ty, whole, spec, site)? {
Conversion::Keep => Value::Plain,
Conversion::Into => Value::Into,
})
}
fn conversion(
ty: &Type,
whole: &Type,
spec: Option<&TraitSpec>,
site: &ReturnSite,
) -> Result<Conversion> {
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 mentions_self(ty) {
let ReturnSite {
subject, by_hand, ..
} = site;
return Err(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>` or `Result<Self, E>`, spelled with those names, or {by_hand}"),
));
}
Ok(Conversion::Keep)
}
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;
}
Some(quote!(::<#(#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 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<ReturnModel> {
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() {
let plain = ReturnModel {
wrapper: Wrapper::None,
value: Value::Plain,
};
assert_eq!(model(parse_quote!(u32), None).unwrap(), plain);
assert_eq!(
model(parse_quote!(Self), None).unwrap(),
ReturnModel {
wrapper: Wrapper::None,
value: Value::Rewrap,
}
);
assert_eq!(
model(parse_quote!(io::Result<Self>), None).unwrap(),
ReturnModel {
wrapper: Wrapper::Result {
error: Conversion::Keep,
},
value: Value::Rewrap,
}
);
assert_eq!(
model(parse_quote!(::core::option::Option<Self>), None)
.unwrap()
.wrapper,
Wrapper::Option
);
}
#[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().value,
Value::Into
);
assert_eq!(
model(parse_quote!(Option<Self::Error>), spec).unwrap(),
ReturnModel {
wrapper: Wrapper::Option,
value: Value::Into,
}
);
assert_eq!(
model(parse_quote!(Result<Self, Self::Error>), spec).unwrap(),
ReturnModel {
wrapper: Wrapper::Result {
error: Conversion::Into,
},
value: Value::Rewrap,
}
);
assert_eq!(
model(parse_quote!(Self::Other), spec).unwrap().value,
Value::Plain
);
}
#[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!(future);
assert_eq!(model.value, Value::Rewrap);
let (model, future) = returns(parse_quote!(-> impl Future)).unwrap();
assert!(future);
assert_eq!(model, ReturnModel::PLAIN);
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);
}
#[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();
assert_eq!(
returns(parse_quote!(-> impl Iterator<Item = u32>)),
(ReturnModel::PLAIN, false)
);
assert_eq!(
returns(parse_quote!(-> impl Future<Output = u32> + Send)),
(ReturnModel::PLAIN, false),
"a future whose output needs nothing is forwarded without an `async` block"
);
let (model, future) = returns(parse_quote!(-> impl Future<Output = Self>));
assert!(
future,
"a future whose output is rewrapped is awaited in an `async` block"
);
assert_eq!(model.value, Value::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())
);
}
}