proptest_derive/
util.rs

1// Copyright 2018 The proptest developers
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9//! Mostly useful utilities for syn used in the crate.
10
11use std::borrow::Borrow;
12
13use syn;
14
15//==============================================================================
16// General AST manipulation and types
17//==============================================================================
18
19/// Extract the list of fields from a `Fields` from syn.
20/// We don't care about the style, we always and uniformly use {} in
21/// struct literal syntax for making struct and enum variant values.
22pub fn fields_to_vec(fields: syn::Fields) -> Vec<syn::Field> {
23    use syn::Fields::*;
24    match fields {
25        Named(fields) => fields.named.into_iter().collect(),
26        Unnamed(fields) => fields.unnamed.into_iter().collect(),
27        Unit => vec![],
28    }
29}
30
31/// Returns true iff the given type is the literal unit type `()`.
32/// This is treated the same way by `syn` as a 0-tuple.
33pub fn is_unit_type<T: Borrow<syn::Type>>(ty: T) -> bool {
34    ty.borrow() == &parse_quote!(())
35}
36
37/// Returns the `Self` type (in the literal syntactic sense).
38pub fn self_ty() -> syn::Type {
39    parse_quote!(Self)
40}
41
42//==============================================================================
43// Paths:
44//==============================================================================
45
46type CommaPS = syn::punctuated::Punctuated<syn::PathSegment, Token![::]>;
47
48/// Returns true iff the path is simple, i.e:
49/// just a :: separated list of identifiers.
50fn is_path_simple(path: &syn::Path) -> bool {
51    path.segments.iter().all(|ps| ps.arguments.is_empty())
52}
53
54/// Returns true iff lhs matches the rhs.
55fn eq_simple_pathseg(lhs: &str, rhs: &CommaPS) -> bool {
56    lhs.split("::")
57        .filter(|s| !s.trim().is_empty())
58        .eq(rhs.iter().map(|ps| ps.ident.to_string()))
59}
60
61/// Returns true iff lhs matches the given simple Path.
62pub fn eq_simple_path(mut lhs: &str, rhs: &syn::Path) -> bool {
63    if !is_path_simple(rhs) {
64        return false;
65    }
66
67    if rhs.leading_colon.is_some() {
68        if !lhs.starts_with("::") {
69            return false;
70        }
71        lhs = &lhs[2..];
72    }
73
74    eq_simple_pathseg(lhs, &rhs.segments)
75}
76
77/// Returns true iff the given path matches any of given
78/// paths specified as string slices.
79pub fn match_pathsegs(path: &syn::Path, against: &[&str]) -> bool {
80    against.iter().any(|needle| eq_simple_path(needle, path))
81}
82
83/// Returns true iff the given `PathArguments` is one that has one type
84/// applied to it.
85fn pseg_has_single_tyvar(pp: &syn::PathSegment) -> bool {
86    use syn::GenericArgument::Type;
87    use syn::PathArguments::AngleBracketed;
88    if let AngleBracketed(ab) = &pp.arguments {
89        if let Some(Type(_)) = match_singleton(ab.args.iter()) {
90            return true;
91        }
92    }
93    false
94}
95
96/// Returns true iff the given type is of the form `PhantomData<TY>` where
97/// `TY` can be substituted for any type, including type variables.
98pub fn is_phantom_data(path: &syn::Path) -> bool {
99    let segs = &path.segments;
100    if segs.is_empty() {
101        return false;
102    }
103
104    let mut path = path.clone();
105    let lseg = path.segments.pop().unwrap().into_value();
106
107    &lseg.ident == "PhantomData"
108        && pseg_has_single_tyvar(&lseg)
109        && match_pathsegs(
110            &path,
111            &[
112                // We hedge a bet that user will never declare
113                // their own type named PhantomData.
114                // This may give errors, but is worth it usability-wise.
115                "",
116                "marker",
117                "std::marker",
118                "core::marker",
119                "::std::marker",
120                "::core::marker",
121            ],
122        )
123}
124
125/// Extracts a simple non-global path of length 1.
126pub fn extract_simple_path(path: &syn::Path) -> Option<&syn::Ident> {
127    match_singleton(&path.segments)
128        .filter(|f| !path_is_global(path) && f.arguments.is_empty())
129        .map(|f| &f.ident)
130}
131
132/// Does the path have a leading `::`?
133pub fn path_is_global(path: &syn::Path) -> bool {
134    path.leading_colon.is_some()
135}
136
137//==============================================================================
138// General Rust utilities:
139//==============================================================================
140
141/// Returns `Some(x)` iff the iterable is singleton and otherwise None.
142pub fn match_singleton<T>(it: impl IntoIterator<Item = T>) -> Option<T> {
143    let mut it = it.into_iter();
144    it.next().filter(|_| it.next().is_none())
145}