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