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.
89//! Provides `UseTracker` as well as `UseMarkable` which is used to
10//! track uses of type variables that need `Arbitrary` bounds in our
11//! impls.
1213use std::borrow::Borrow;
14use std::collections::HashSet;
1516use syn;
1718use crate::attr;
19use crate::error::{Ctx, DeriveResult};
20use crate::util;
2122//==============================================================================
23// API: Type variable use tracking
24//==============================================================================
2526/// `UseTracker` tracks what type variables that have used in `any_with::<Type>`
27/// or similar and thus needs an `Arbitrary` bound added to them.
28pub struct UseTracker {
29/// Tracks 'usage' of a type variable name.
30 /// Allocation of this "map" will happen at once and no further
31 /// allocation will happen after that. Only potential updates
32 /// will happen after initial allocation.
33 /// We need to preserve insertion order, thus using Vec instead of BTreeMap
34 /// or HashMap. A potential alternative would be indexmap crate, but our
35 /// maps are so small that it would not bring any significant benefit.
36used_map: Vec<(syn::Ident, bool)>,
37/// Extra types to bound by `Arbitrary` in the `where` clause.
38where_types: HashSet<syn::Type>,
39/// The generics that we are doing this for.
40 /// This what we will modify later once we're done.
41generics: syn::Generics,
42/// If set to `true`, then `mark_used` has no effect.
43track: bool,
44}
4546/// Models a thing that may have type variables in it that
47/// can be marked as 'used' as defined by `UseTracker`.
48pub trait UseMarkable {
49fn mark_uses(&self, tracker: &mut UseTracker);
50}
5152impl UseTracker {
53/// Constructs the tracker for the given `generics`.
54pub fn new(generics: syn::Generics) -> Self {
55// Construct the map by setting all type variables as being unused
56 // initially. This is the only time we will allocate for the map.
57let used_map = generics
58 .type_params()
59 .map(|v| (v.ident.clone(), false))
60 .collect();
61Self {
62 generics,
63 used_map,
64 where_types: HashSet::default(),
65 track: true,
66 }
67 }
6869/// Stop tracking. `.mark_used` will have no effect.
70pub fn no_track(&mut self) {
71self.track = false;
72 }
7374/// Mark the _potential_ type variable `tyvar` as used.
75 /// If the tracker does not know about the name, it is not
76 /// a type variable and this call has no effect.
77fn use_tyvar(&mut self, tyvar: impl Borrow<syn::Ident>) {
78if self.track {
79if let Some(used) = self
80.used_map
81 .iter_mut()
82 .find_map(|(ty, used)| (ty == tyvar.borrow()).then(|| used))
83 {
84*used = true;
85 }
86 }
87 }
8889/// Returns true iff the type variable given exists.
90fn has_tyvar(&self, ty_var: impl Borrow<syn::Ident>) -> bool {
91self.used_map.iter().any(|(ty, _)| ty == ty_var.borrow())
92 }
9394/// Mark the type as used.
95fn use_type(&mut self, ty: syn::Type) {
96self.where_types.insert(ty);
97 }
9899/// Adds the bound in `for_used` on used type variables and
100 /// the bound in `for_not` (`if .is_some()`) on unused type variables.
101pub fn add_bounds(
102&mut self,
103 ctx: Ctx,
104 for_used: &syn::TypeParamBound,
105 for_not: Option<syn::TypeParamBound>,
106 ) -> DeriveResult<()> {
107 {
108let mut iter = self
109.used_map
110 .iter()
111 .map(|(_, used)| used)
112 .zip(self.generics.type_params_mut());
113if let Some(for_not) = for_not {
114 iter.try_for_each(|(&used, tv)| {
115// Steal the attributes:
116let no_bound = attr::has_no_bound(ctx, &tv.attrs)?;
117let bound = if used && !no_bound {
118 for_used
119 } else {
120&for_not
121 };
122 tv.bounds.push(bound.clone());
123Ok(())
124 })?;
125 } else {
126 iter.for_each(|(&used, tv)| {
127if used {
128 tv.bounds.push(for_used.clone())
129 }
130 })
131 }
132 }
133134self.generics.make_where_clause().predicates.extend(
135self.where_types.iter().cloned().map(|ty| {
136 syn::WherePredicate::Type(syn::PredicateType {
137 lifetimes: None,
138 bounded_ty: ty,
139 colon_token: <Token![:]>::default(),
140 bounds: ::std::iter::once(for_used.clone()).collect(),
141 })
142 }),
143 );
144145Ok(())
146 }
147148/// Consumes the (potentially) modified generics that the
149 /// tracker was originally constructed with and returns it.
150pub fn consume(self) -> syn::Generics {
151self.generics
152 }
153}
154155//==============================================================================
156// Impls
157//==============================================================================
158159impl UseMarkable for syn::Type {
160fn mark_uses(&self, ut: &mut UseTracker) {
161use syn::visit;
162163 visit::visit_type(&mut PathVisitor(ut), self);
164165struct PathVisitor<'ut>(&'ut mut UseTracker);
166167impl<'ut, 'ast> visit::Visit<'ast> for PathVisitor<'ut> {
168fn visit_macro(&mut self, _: &syn::Macro) {}
169170fn visit_type_path(&mut self, tpath: &syn::TypePath) {
171if matches_prj_tyvar(self.0, tpath) {
172self.0.use_type(adjust_simple_prj(tpath).into());
173return;
174 }
175 visit::visit_type_path(self, tpath);
176 }
177178fn visit_path(&mut self, path: &syn::Path) {
179// If path is PhantomData<T> do not mark innards.
180if util::is_phantom_data(path) {
181return;
182 }
183184if let Some(ident) = util::extract_simple_path(path) {
185self.0.use_tyvar(ident);
186 }
187188 visit::visit_path(self, path);
189 }
190 }
191 }
192}
193194fn matches_prj_tyvar(ut: &mut UseTracker, tpath: &syn::TypePath) -> bool {
195let path = &tpath.path;
196let segs = &path.segments;
197198if let Some(qself) = &tpath.qself {
199// < $qself > :: $path
200if let Some(sub_tp) = extract_path(&qself.ty) {
201return sub_tp.qself.is_none()
202 && util::match_singleton(segs.iter().skip(qself.position))
203 .filter(|ps| ps.arguments.is_empty())
204 .and_then(|_| util::extract_simple_path(&sub_tp.path))
205 .filter(|&ident| ut.has_tyvar(ident))
206 .is_some() // < $tyvar as? $path? > :: $path
207|| matches_prj_tyvar(ut, sub_tp);
208 }
209210false
211} else {
212// true => $tyvar :: $projection
213return !util::path_is_global(path)
214 && segs.len() == 2
215&& ut.has_tyvar(&segs[0].ident)
216 && segs[0].arguments.is_empty()
217 && segs[1].arguments.is_empty();
218 }
219}
220221fn adjust_simple_prj(tpath: &syn::TypePath) -> syn::TypePath {
222let segments = tpath
223 .qself
224 .as_ref()
225 .filter(|qp| qp.as_token.is_none())
226 .and_then(|qp| extract_path(&*qp.ty))
227 .filter(|tp| tp.qself.is_none())
228 .map(|tp| &tp.path.segments);
229230if let Some(segments) = segments {
231let tpath = tpath.clone();
232let mut segments = segments.clone();
233 segments.push_punct(<Token![::]>::default());
234 segments.extend(tpath.path.segments.into_pairs());
235 syn::TypePath {
236 qself: None,
237 path: syn::Path {
238 leading_colon: None,
239 segments,
240 },
241 }
242 } else {
243 tpath.clone()
244 }
245}
246247fn extract_path(ty: &syn::Type) -> Option<&syn::TypePath> {
248if let syn::Type::Path(tpath) = ty {
249Some(tpath)
250 } else {
251None
252}
253}