prosto_derive 0.11.24

Procedural macros for proto_rs, Rust as a first-class citizen in the protobuf ecosystem
Documentation
use std::collections::BTreeSet;

use syn::GenericArgument;
use syn::Generics;
use syn::Ident;
use syn::PathArguments;
use syn::Type;
use syn::parse_quote;

use super::unified_field_handler::FieldInfo;
use super::unified_field_handler::uses_proto_wire_directly;

pub fn add_proto_wire_bounds<'a>(generics: &Generics, fields: impl IntoIterator<Item = &'a FieldInfo<'a>>) -> Generics {
    let type_params: BTreeSet<Ident> = generics.type_params().map(|param| param.ident.clone()).collect();
    if type_params.is_empty() {
        return generics.clone();
    }

    let mut used_lifetimes = BTreeSet::new();
    let mut used_encode = BTreeSet::new();
    let mut bound_types = Vec::new();

    for info in fields {
        if !uses_proto_wire_directly(info) {
            continue;
        }
        collect_type_params(&info.parsed.rust_type, &type_params, &mut used_lifetimes);
        if info.parsed.map_kind.is_some() {
            collect_type_params(&info.parsed.proto_rust_type, &type_params, &mut used_encode);
        } else {
            collect_type_params(&info.parsed.rust_type, &type_params, &mut used_encode);
        }
        if info.parsed.is_option {
            bound_types.push(info.parsed.elem_type.clone());
        } else {
            bound_types.push(info.proto_ty.clone());
        }
    }

    if used_lifetimes.is_empty() && used_encode.is_empty() && bound_types.is_empty() {
        return generics.clone();
    }

    let mut bounded = generics.clone();
    let where_clause = bounded.make_where_clause();
    for ident in &used_lifetimes {
        where_clause.predicates.push(parse_quote!(for<'__proto> #ident: '__proto));
    }
    for ident in used_encode {
        where_clause.predicates.push(
            parse_quote!(#ident: ::proto_rs::ProtoEncode + ::proto_rs::ProtoDecode + ::proto_rs::ProtoDefault + ::proto_rs::ProtoFieldMerge + ::proto_rs::ProtoExt),
        );
        where_clause
            .predicates
            .push(parse_quote!(for<'__proto> <#ident as ::proto_rs::ProtoEncode>::Shadow<'__proto>: ::proto_rs::ProtoArchive + ::proto_rs::ProtoExt));
    }
    for ty in bound_types {
        where_clause
            .predicates
            .push(parse_quote!(#ty: ::proto_rs::ProtoEncode + ::proto_rs::ProtoDecode + ::proto_rs::ProtoDefault + ::proto_rs::ProtoFieldMerge + ::proto_rs::ProtoExt));
        where_clause.predicates.push(
            parse_quote!(for<'__proto> <#ty as ::proto_rs::ProtoEncode>::Shadow<'__proto>: ::proto_rs::ProtoArchive + ::proto_rs::ProtoExt),
        );
    }

    bounded
}

fn collect_type_params(ty: &Type, params: &BTreeSet<Ident>, used: &mut BTreeSet<Ident>) {
    match ty {
        Type::Path(type_path) => {
            if type_path.qself.is_none() && type_path.path.segments.len() == 1 {
                let ident = &type_path.path.segments[0].ident;
                if params.contains(ident) {
                    used.insert(ident.clone());
                }
            }
            for segment in &type_path.path.segments {
                match &segment.arguments {
                    PathArguments::None => {}
                    PathArguments::AngleBracketed(args) => {
                        for arg in &args.args {
                            match arg {
                                GenericArgument::Type(inner_ty) => {
                                    collect_type_params(inner_ty, params, used);
                                }
                                GenericArgument::AssocType(assoc) => {
                                    collect_type_params(&assoc.ty, params, used);
                                }
                                GenericArgument::Constraint(constraint) => {
                                    for bound in &constraint.bounds {
                                        if let syn::TypeParamBound::Trait(trait_bound) = bound {
                                            for segment in &trait_bound.path.segments {
                                                if let PathArguments::AngleBracketed(args) = &segment.arguments {
                                                    for arg in &args.args {
                                                        if let GenericArgument::Type(inner_ty) = arg {
                                                            collect_type_params(inner_ty, params, used);
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                                GenericArgument::Lifetime(_) | GenericArgument::Const(_) | GenericArgument::AssocConst(_) | _ => {}
                            }
                        }
                    }
                    PathArguments::Parenthesized(args) => {
                        for input in &args.inputs {
                            collect_type_params(input, params, used);
                        }
                        if let syn::ReturnType::Type(_, output) = &args.output {
                            collect_type_params(output, params, used);
                        }
                    }
                }
            }
        }
        Type::Reference(reference) => collect_type_params(&reference.elem, params, used),
        Type::Array(array) => collect_type_params(&array.elem, params, used),
        Type::Slice(slice) => collect_type_params(&slice.elem, params, used),
        Type::Tuple(tuple) => {
            for elem in &tuple.elems {
                collect_type_params(elem, params, used);
            }
        }
        Type::Paren(paren) => collect_type_params(&paren.elem, params, used),
        Type::Group(group) => collect_type_params(&group.elem, params, used),
        Type::Ptr(ptr) => collect_type_params(&ptr.elem, params, used),
        Type::BareFn(bare_fn) => {
            for input in &bare_fn.inputs {
                collect_type_params(&input.ty, params, used);
            }
            if let syn::ReturnType::Type(_, output) = &bare_fn.output {
                collect_type_params(output, params, used);
            }
        }
        Type::ImplTrait(impl_trait) => {
            for bound in &impl_trait.bounds {
                if let syn::TypeParamBound::Trait(trait_bound) = bound {
                    for segment in &trait_bound.path.segments {
                        if let PathArguments::AngleBracketed(args) = &segment.arguments {
                            for arg in &args.args {
                                if let GenericArgument::Type(inner_ty) = arg {
                                    collect_type_params(inner_ty, params, used);
                                }
                            }
                        }
                    }
                }
            }
        }
        Type::TraitObject(trait_object) => {
            for bound in &trait_object.bounds {
                if let syn::TypeParamBound::Trait(trait_bound) = bound {
                    for segment in &trait_bound.path.segments {
                        if let PathArguments::AngleBracketed(args) = &segment.arguments {
                            for arg in &args.args {
                                if let GenericArgument::Type(inner_ty) = arg {
                                    collect_type_params(inner_ty, params, used);
                                }
                            }
                        }
                    }
                }
            }
        }
        _ => {}
    }
}