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}