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