ouroboros_macro/
covariance_detection.rs

1use quote::ToTokens;
2use syn::{GenericArgument, PathArguments, Type};
3
4use crate::utils::uses_this_lifetime;
5
6const STD_CONTAINER_TYPES: &[&str] = &["Box", "Arc", "Rc"];
7
8/// Returns Some((type_name, element_type)) if the provided type appears to be Box, Arc, or Rc from
9/// the standard library. Returns None if not.
10pub fn apparent_std_container_type(raw_type: &Type) -> Option<(&'static str, &Type)> {
11    let tpath = if let Type::Path(x) = raw_type {
12        x
13    } else {
14        return None;
15    };
16    let segment = tpath.path.segments.last()?;
17    let args = if let PathArguments::AngleBracketed(args) = &segment.arguments {
18        args
19    } else {
20        return None;
21    };
22    if args.args.len() != 1 {
23        return None;
24    }
25    let arg = args.args.first().unwrap();
26    let eltype = if let GenericArgument::Type(x) = arg {
27        x
28    } else {
29        return None;
30    };
31    for type_name in STD_CONTAINER_TYPES {
32        if segment.ident == type_name {
33            return Some((type_name, eltype));
34        }
35    }
36    None
37}
38
39/// Returns Some(true or false) if the type is known to be covariant / not covariant.
40pub fn type_is_covariant_over_this_lifetime(ty: &syn::Type) -> Option<bool> {
41    use syn::Type::*;
42    // If the type never uses the 'this lifetime, we don't have to
43    // worry about it not being covariant.
44    if !uses_this_lifetime(ty.to_token_stream()) {
45        return Some(true);
46    }
47    match ty {
48        Array(arr) => type_is_covariant_over_this_lifetime(&arr.elem),
49        BareFn(f) => {
50            debug_assert!(uses_this_lifetime(f.to_token_stream()));
51            None
52        }
53        Group(ty) => type_is_covariant_over_this_lifetime(&ty.elem),
54        ImplTrait(..) => None, // Unusable in struct definition.
55        Infer(..) => None,     // Unusable in struct definition.
56        Macro(..) => None,     // We don't know what the macro will resolve to.
57        Never(..) => None,
58        Paren(ty) => type_is_covariant_over_this_lifetime(&ty.elem),
59        Path(path) => {
60            if let Some(qself) = &path.qself {
61                if !type_is_covariant_over_this_lifetime(&qself.ty)? {
62                    return Some(false);
63                }
64            }
65            let mut all_parameters_are_covariant = false;
66            // If the type is Box, Arc, or Rc, we can assume it to be covariant.
67            if apparent_std_container_type(ty).is_some() {
68                all_parameters_are_covariant = true;
69            }
70            for segment in path.path.segments.iter() {
71                let args = &segment.arguments;
72                if let syn::PathArguments::AngleBracketed(args) = &args {
73                    for arg in args.args.iter() {
74                        if let syn::GenericArgument::Type(ty) = arg {
75                            if all_parameters_are_covariant {
76                                if !type_is_covariant_over_this_lifetime(ty)? {
77                                    return Some(false);
78                                }
79                            } else if uses_this_lifetime(ty.to_token_stream()) {
80                                return None;
81                            }
82                        } else if let syn::GenericArgument::Lifetime(lt) = arg {
83                            if lt.ident == "this" && !all_parameters_are_covariant {
84                                return None;
85                            }
86                        }
87                    }
88                } else if let syn::PathArguments::Parenthesized(args) = &args {
89                    for arg in args.inputs.iter() {
90                        if uses_this_lifetime(arg.to_token_stream()) {
91                            return None;
92                        }
93                    }
94                    if let syn::ReturnType::Type(_, ty) = &args.output {
95                        if uses_this_lifetime(ty.to_token_stream()) {
96                            return None;
97                        }
98                    }
99                }
100            }
101            Some(true)
102        }
103        Ptr(ptr) => {
104            if ptr.mutability.is_some() {
105                Some(false)
106            } else {
107                type_is_covariant_over_this_lifetime(&ptr.elem)
108            }
109        }
110        // Ignore the actual lifetime of the reference because Rust can automatically convert those.
111        Reference(rf) => {
112            if rf.mutability.is_some() {
113                Some(!uses_this_lifetime(rf.elem.to_token_stream()))
114            } else {
115                type_is_covariant_over_this_lifetime(&rf.elem)
116            }
117        }
118        Slice(sl) => type_is_covariant_over_this_lifetime(&sl.elem),
119        TraitObject(..) => None,
120        Tuple(tup) => {
121            let mut result = Some(true);
122            for ty in tup.elems.iter() {
123                match type_is_covariant_over_this_lifetime(ty) {
124                    Some(true) => (),
125                    Some(false) => return Some(false),
126                    None => result = None,
127                }
128            }
129            result
130        }
131        // As of writing this, syn parses all the types we could need. However,
132        // just to be safe, return that we don't know if it's covariant.
133        Verbatim(..) => None,
134        _ => None,
135    }
136}