Skip to main content

wdl_analysis/stdlib/
constraints.rs

1//! Represents type constraints to standard library functions.
2
3use std::fmt;
4
5use crate::types::Coercible;
6use crate::types::CompoundType;
7use crate::types::CustomType;
8use crate::types::PrimitiveType;
9use crate::types::Type;
10
11/// A trait implemented by type constraints.
12pub trait Constraint: fmt::Debug + Send + Sync {
13    /// Gets a description of the constraint.
14    fn description(&self) -> &'static str;
15
16    /// Determines if the given type satisfies the constraint.
17    ///
18    /// Returns `true` if the constraint is satisfied or false if not.
19    fn satisfied(&self, ty: &Type) -> bool;
20}
21
22/// Represents a constraint that ensure the type can be used in a file size
23/// calculation.
24///
25/// The constraint checks that the type is a compound type that recursively
26/// contains a `File` or `Directory` type.
27#[derive(Debug, Copy, Clone)]
28pub struct SizeableConstraint;
29
30impl Constraint for SizeableConstraint {
31    fn description(&self) -> &'static str {
32        "any compound type that recursively contains a `File` or `Directory`"
33    }
34
35    fn satisfied(&self, ty: &Type) -> bool {
36        /// Determines if the given primitive type is sizable.
37        fn primitive_type_is_sizable(ty: PrimitiveType) -> bool {
38            matches!(ty, PrimitiveType::File | PrimitiveType::Directory)
39        }
40
41        /// Determines if the given compound type is sizable.
42        fn compound_type_is_sizable(ty: &CompoundType) -> bool {
43            match ty {
44                CompoundType::Array(ty) => type_is_sizable(ty.element_type()),
45                CompoundType::Pair(ty) => {
46                    type_is_sizable(ty.left_type()) | type_is_sizable(ty.right_type())
47                }
48                CompoundType::Map(ty) => {
49                    type_is_sizable(ty.key_type()) | type_is_sizable(ty.value_type())
50                }
51                CompoundType::Custom(CustomType::Struct(s)) => {
52                    s.members().values().any(type_is_sizable)
53                }
54                CompoundType::Custom(CustomType::Enum(_)) => false,
55            }
56        }
57
58        /// Determines if the given type is sizable.
59        fn type_is_sizable(ty: &Type) -> bool {
60            match ty {
61                Type::Primitive(ty, _) => primitive_type_is_sizable(*ty),
62                Type::Compound(ty, _) => compound_type_is_sizable(ty),
63                Type::Object | Type::OptionalObject => {
64                    // Note: checking the types of an object's members is a runtime constraint
65                    true
66                }
67                // Treat unions as sizable as they can only be checked at runtime
68                Type::Union | Type::None => true,
69                Type::Hidden(_) | Type::Call(_) | Type::TypeNameRef(_) => false,
70            }
71        }
72
73        type_is_sizable(ty)
74    }
75}
76
77/// Represents a constraint that ensures the type is any structure.
78#[derive(Debug, Copy, Clone)]
79pub struct StructConstraint;
80
81impl Constraint for StructConstraint {
82    fn description(&self) -> &'static str {
83        "any structure"
84    }
85
86    fn satisfied(&self, ty: &Type) -> bool {
87        ty.as_struct().is_some()
88    }
89}
90
91/// Represents a constraint that ensures the type is any structure that contains
92/// only primitive types.
93#[derive(Debug, Copy, Clone)]
94pub struct PrimitiveStructConstraint;
95
96impl Constraint for PrimitiveStructConstraint {
97    fn description(&self) -> &'static str {
98        "any structure containing only primitive types"
99    }
100
101    fn satisfied(&self, ty: &Type) -> bool {
102        if let Some(ty) = ty.as_struct() {
103            return ty
104                .members()
105                .values()
106                .all(|ty| matches!(ty, Type::Primitive(..)));
107        }
108
109        false
110    }
111}
112
113/// Represents a constraint that ensures the type is JSON serializable.
114#[derive(Debug, Copy, Clone)]
115pub struct JsonSerializableConstraint;
116
117impl Constraint for JsonSerializableConstraint {
118    fn description(&self) -> &'static str {
119        "any JSON-serializable type"
120    }
121
122    fn satisfied(&self, ty: &Type) -> bool {
123        /// Determines if the given compound type is JSON serializable.
124        fn compound_type_is_serializable(ty: &CompoundType) -> bool {
125            match ty {
126                CompoundType::Array(ty) => type_is_serializable(ty.element_type()),
127                CompoundType::Pair(_) => false,
128                CompoundType::Map(ty) => {
129                    ty.key_type().is_coercible_to(&PrimitiveType::String.into())
130                        && type_is_serializable(ty.value_type())
131                }
132                CompoundType::Custom(CustomType::Struct(s)) => {
133                    s.members().values().all(type_is_serializable)
134                }
135                CompoundType::Custom(CustomType::Enum(_)) => {
136                    // Enums always serialize as a string representing the
137                    // variant name.
138                    true
139                }
140            }
141        }
142
143        /// Determines if the given type is JSON serializable.
144        fn type_is_serializable(ty: &Type) -> bool {
145            match ty {
146                // Treat objects and unions as sizable as they can only be checked at runtime
147                Type::Primitive(..)
148                | Type::Object
149                | Type::OptionalObject
150                | Type::Union
151                | Type::None => true,
152                Type::Compound(ty, _) => compound_type_is_serializable(ty),
153                Type::Hidden(_) | Type::Call(_) | Type::TypeNameRef(_) => false,
154            }
155        }
156
157        type_is_serializable(ty)
158    }
159}
160
161/// Represents a constraint that ensures the type is a primitive type.
162#[derive(Debug, Copy, Clone)]
163pub struct PrimitiveTypeConstraint;
164
165impl Constraint for PrimitiveTypeConstraint {
166    fn description(&self) -> &'static str {
167        "any primitive type"
168    }
169
170    fn satisfied(&self, ty: &Type) -> bool {
171        match ty {
172            Type::Primitive(..) => true,
173            // Treat unions as primitive as they can only be checked at runtime
174            Type::Union | Type::None => true,
175            Type::Compound(..)
176            | Type::Object
177            | Type::OptionalObject
178            | Type::Hidden(_)
179            | Type::Call(_)
180            | Type::TypeNameRef(_) => false,
181        }
182    }
183}
184
185/// Represents a constraint that ensures the type is a valid `Map` key type.
186#[derive(Debug, Copy, Clone)]
187pub struct MapKeyConstraint;
188
189impl Constraint for MapKeyConstraint {
190    fn description(&self) -> &'static str {
191        "any non-optional primitive type"
192    }
193
194    fn satisfied(&self, ty: &Type) -> bool {
195        match ty {
196            Type::Union | Type::Primitive(_, false) => true,
197            Type::Primitive(_, true)
198            | Type::Compound(..)
199            | Type::None
200            | Type::Object
201            | Type::OptionalObject
202            | Type::Hidden(_)
203            | Type::Call(_)
204            | Type::TypeNameRef(_) => false,
205        }
206    }
207}
208
209/// Represents a constraint that ensures the type is any enumeration variant.
210#[derive(Debug, Copy, Clone)]
211pub struct EnumVariantConstraint;
212
213impl Constraint for EnumVariantConstraint {
214    fn description(&self) -> &'static str {
215        "any enum variant"
216    }
217
218    fn satisfied(&self, ty: &Type) -> bool {
219        ty.as_enum().is_some()
220    }
221}
222
223#[cfg(test)]
224mod test {
225    use super::*;
226    use crate::types::ArrayType;
227    use crate::types::MapType;
228    use crate::types::Optional;
229    use crate::types::PairType;
230    use crate::types::PrimitiveType;
231    use crate::types::StructType;
232
233    #[test]
234    fn test_sizable_constraint() {
235        let constraint = SizeableConstraint;
236        assert!(!constraint.satisfied(&Type::from(PrimitiveType::Boolean).optional()));
237        assert!(!constraint.satisfied(&Type::from(PrimitiveType::Integer).optional()));
238        assert!(!constraint.satisfied(&Type::from(PrimitiveType::Float).optional()));
239        assert!(!constraint.satisfied(&Type::from(PrimitiveType::String).optional()));
240        assert!(constraint.satisfied(&Type::from(PrimitiveType::File).optional()));
241        assert!(constraint.satisfied(&Type::from(PrimitiveType::Directory).optional()));
242        assert!(!constraint.satisfied(&PrimitiveType::Boolean.into()));
243        assert!(!constraint.satisfied(&PrimitiveType::Integer.into()));
244        assert!(!constraint.satisfied(&PrimitiveType::Float.into()));
245        assert!(!constraint.satisfied(&PrimitiveType::String.into()));
246        assert!(constraint.satisfied(&PrimitiveType::File.into()));
247        assert!(constraint.satisfied(&PrimitiveType::Directory.into()));
248        assert!(constraint.satisfied(&Type::OptionalObject));
249        assert!(constraint.satisfied(&Type::Object));
250        assert!(constraint.satisfied(&Type::Union));
251        assert!(!constraint.satisfied(&ArrayType::new(PrimitiveType::String).into()));
252        assert!(constraint.satisfied(&ArrayType::new(PrimitiveType::File).into()));
253        assert!(
254            !constraint
255                .satisfied(&PairType::new(PrimitiveType::String, PrimitiveType::String).into())
256        );
257        assert!(
258            constraint.satisfied(&PairType::new(PrimitiveType::String, PrimitiveType::File).into())
259        );
260        assert!(
261            constraint.satisfied(
262                &Type::from(PairType::new(
263                    PrimitiveType::Directory,
264                    PrimitiveType::String
265                ))
266                .optional()
267            )
268        );
269        assert!(
270            !constraint.satisfied(
271                &Type::from(MapType::new(
272                    PrimitiveType::String,
273                    ArrayType::new(PrimitiveType::String)
274                ))
275                .optional()
276            )
277        );
278        assert!(
279            constraint.satisfied(
280                &MapType::new(
281                    PrimitiveType::String,
282                    Type::from(ArrayType::new(PrimitiveType::File)).optional()
283                )
284                .into()
285            )
286        );
287        assert!(
288            constraint.satisfied(
289                &Type::from(MapType::new(
290                    PrimitiveType::Directory,
291                    PrimitiveType::String
292                ))
293                .optional()
294            )
295        );
296        assert!(
297            !constraint.satisfied(&StructType::new("Foo", [("foo", PrimitiveType::String)]).into())
298        );
299        assert!(constraint.satisfied(
300            &Type::from(StructType::new("Foo", [("foo", PrimitiveType::File)])).optional()
301        ));
302        assert!(
303            constraint
304                .satisfied(&StructType::new("Foo", [("foo", PrimitiveType::Directory,)]).into())
305        );
306    }
307
308    #[test]
309    fn test_struct_constraint() {
310        let constraint = StructConstraint;
311        assert!(!constraint.satisfied(&Type::from(PrimitiveType::Boolean).optional()));
312        assert!(!constraint.satisfied(&Type::from(PrimitiveType::Integer).optional()));
313        assert!(!constraint.satisfied(&Type::from(PrimitiveType::Float).optional()));
314        assert!(!constraint.satisfied(&Type::from(PrimitiveType::String).optional()));
315        assert!(!constraint.satisfied(&Type::from(PrimitiveType::File).optional()));
316        assert!(!constraint.satisfied(&Type::from(PrimitiveType::Directory).optional()));
317        assert!(!constraint.satisfied(&PrimitiveType::Boolean.into()));
318        assert!(!constraint.satisfied(&PrimitiveType::Integer.into()));
319        assert!(!constraint.satisfied(&PrimitiveType::Float.into()));
320        assert!(!constraint.satisfied(&PrimitiveType::String.into()));
321        assert!(!constraint.satisfied(&PrimitiveType::File.into()));
322        assert!(!constraint.satisfied(&PrimitiveType::Directory.into()));
323        assert!(!constraint.satisfied(&Type::OptionalObject));
324        assert!(!constraint.satisfied(&Type::Object));
325        assert!(!constraint.satisfied(&Type::Union));
326        assert!(!constraint.satisfied(&ArrayType::non_empty(PrimitiveType::String).into()));
327        assert!(!constraint.satisfied(
328            &Type::from(PairType::new(PrimitiveType::String, PrimitiveType::String)).optional()
329        ));
330        assert!(
331            !constraint
332                .satisfied(&MapType::new(PrimitiveType::String, PrimitiveType::String,).into())
333        );
334        assert!(constraint.satisfied(
335            &Type::from(StructType::new("Foo", [("foo", PrimitiveType::String)])).optional()
336        ));
337    }
338
339    #[test]
340    fn test_json_constraint() {
341        let constraint = JsonSerializableConstraint;
342        assert!(constraint.satisfied(&Type::from(PrimitiveType::Boolean).optional()));
343        assert!(constraint.satisfied(&Type::from(PrimitiveType::Integer).optional()));
344        assert!(constraint.satisfied(&Type::from(PrimitiveType::Float).optional()));
345        assert!(constraint.satisfied(&Type::from(PrimitiveType::String).optional()));
346        assert!(constraint.satisfied(&Type::from(PrimitiveType::File).optional()));
347        assert!(constraint.satisfied(&Type::from(PrimitiveType::Directory).optional()));
348        assert!(constraint.satisfied(&PrimitiveType::Boolean.into()));
349        assert!(constraint.satisfied(&PrimitiveType::Integer.into()));
350        assert!(constraint.satisfied(&PrimitiveType::Float.into()));
351        assert!(constraint.satisfied(&PrimitiveType::String.into()));
352        assert!(constraint.satisfied(&PrimitiveType::File.into()));
353        assert!(constraint.satisfied(&PrimitiveType::Directory.into()));
354        assert!(constraint.satisfied(&Type::OptionalObject));
355        assert!(constraint.satisfied(&Type::Object));
356        assert!(constraint.satisfied(&Type::Union));
357        assert!(
358            constraint.satisfied(&Type::from(ArrayType::new(PrimitiveType::String)).optional())
359        );
360        assert!(
361            !constraint
362                .satisfied(&PairType::new(PrimitiveType::String, PrimitiveType::String,).into())
363        );
364        assert!(constraint.satisfied(
365            &Type::from(MapType::new(PrimitiveType::String, PrimitiveType::String)).optional()
366        ));
367        assert!(
368            !constraint
369                .satisfied(&MapType::new(PrimitiveType::Integer, PrimitiveType::String).into())
370        );
371        assert!(constraint.satisfied(
372            &Type::from(StructType::new("Foo", [("foo", PrimitiveType::String)])).optional()
373        ));
374    }
375
376    #[test]
377    fn test_map_key_constraint() {
378        let constraint = MapKeyConstraint;
379        assert!(constraint.satisfied(&PrimitiveType::Boolean.into()));
380        assert!(constraint.satisfied(&PrimitiveType::Integer.into()));
381        assert!(constraint.satisfied(&PrimitiveType::Float.into()));
382        assert!(constraint.satisfied(&PrimitiveType::String.into()));
383        assert!(constraint.satisfied(&PrimitiveType::File.into()));
384        assert!(constraint.satisfied(&PrimitiveType::Directory.into()));
385        assert!(constraint.satisfied(&Type::Union));
386        assert!(!constraint.satisfied(&Type::from(PrimitiveType::Boolean).optional()));
387        assert!(!constraint.satisfied(&Type::from(PrimitiveType::Integer).optional()));
388        assert!(!constraint.satisfied(&Type::from(PrimitiveType::Float).optional()));
389        assert!(!constraint.satisfied(&Type::from(PrimitiveType::String).optional()));
390        assert!(!constraint.satisfied(&Type::from(PrimitiveType::File).optional()));
391        assert!(!constraint.satisfied(&Type::from(PrimitiveType::Directory).optional()));
392        assert!(!constraint.satisfied(&Type::OptionalObject));
393        assert!(!constraint.satisfied(&Type::Object));
394    }
395
396    #[test]
397    fn test_primitive_constraint() {
398        let constraint = PrimitiveTypeConstraint;
399        assert!(constraint.satisfied(&Type::from(PrimitiveType::Boolean).optional()));
400        assert!(constraint.satisfied(&Type::from(PrimitiveType::Integer).optional()));
401        assert!(constraint.satisfied(&Type::from(PrimitiveType::Float).optional()));
402        assert!(constraint.satisfied(&Type::from(PrimitiveType::String).optional()));
403        assert!(constraint.satisfied(&Type::from(PrimitiveType::File).optional()));
404        assert!(constraint.satisfied(&Type::from(PrimitiveType::Directory).optional()));
405        assert!(constraint.satisfied(&PrimitiveType::Boolean.into()));
406        assert!(constraint.satisfied(&PrimitiveType::Integer.into()));
407        assert!(constraint.satisfied(&PrimitiveType::Float.into()));
408        assert!(constraint.satisfied(&PrimitiveType::String.into()));
409        assert!(constraint.satisfied(&PrimitiveType::File.into()));
410        assert!(constraint.satisfied(&PrimitiveType::Directory.into()));
411        assert!(!constraint.satisfied(&Type::OptionalObject));
412        assert!(!constraint.satisfied(&Type::Object));
413        assert!(constraint.satisfied(&Type::Union));
414        assert!(constraint.satisfied(&Type::None));
415        assert!(!constraint.satisfied(&ArrayType::non_empty(PrimitiveType::String).into()));
416        assert!(!constraint.satisfied(
417            &Type::from(PairType::new(PrimitiveType::String, PrimitiveType::String)).optional()
418        ));
419        assert!(
420            !constraint
421                .satisfied(&MapType::new(PrimitiveType::String, PrimitiveType::String,).into())
422        );
423        assert!(!constraint.satisfied(
424            &Type::from(StructType::new("Foo", [("foo", PrimitiveType::String)])).optional()
425        ));
426    }
427}