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        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 a valid `Map` key type.
189#[derive(Debug, Copy, Clone)]
190pub struct MapKeyConstraint;
191
192impl Constraint for MapKeyConstraint {
193    fn description(&self) -> &'static str {
194        "any non-optional primitive type"
195    }
196
197    fn satisfied(&self, ty: &Type) -> bool {
198        match ty {
199            Type::Union | Type::Primitive(_, false) => true,
200            Type::Primitive(_, true)
201            | Type::Compound(..)
202            | Type::None
203            | Type::Object
204            | Type::OptionalObject
205            | Type::Hidden(_)
206            | Type::Call(_)
207            | Type::TypeNameRef(_) => false,
208        }
209    }
210}
211
212/// Represents a constraint that ensures the type is any enumeration variant.
213#[derive(Debug, Copy, Clone)]
214pub struct EnumVariantConstraint;
215
216impl Constraint for EnumVariantConstraint {
217    fn description(&self) -> &'static str {
218        "any enum variant"
219    }
220
221    fn satisfied(&self, ty: &Type) -> bool {
222        matches!(
223            ty,
224            Type::Compound(CompoundType::Custom(CustomType::Enum(_)), _)
225        )
226    }
227}
228
229#[cfg(test)]
230mod test {
231    use super::*;
232    use crate::types::ArrayType;
233    use crate::types::MapType;
234    use crate::types::Optional;
235    use crate::types::PairType;
236    use crate::types::PrimitiveType;
237    use crate::types::StructType;
238
239    #[test]
240    fn test_sizable_constraint() {
241        let constraint = SizeableConstraint;
242        assert!(!constraint.satisfied(&Type::from(PrimitiveType::Boolean).optional()));
243        assert!(!constraint.satisfied(&Type::from(PrimitiveType::Integer).optional()));
244        assert!(!constraint.satisfied(&Type::from(PrimitiveType::Float).optional()));
245        assert!(!constraint.satisfied(&Type::from(PrimitiveType::String).optional()));
246        assert!(constraint.satisfied(&Type::from(PrimitiveType::File).optional()));
247        assert!(constraint.satisfied(&Type::from(PrimitiveType::Directory).optional()));
248        assert!(!constraint.satisfied(&PrimitiveType::Boolean.into()));
249        assert!(!constraint.satisfied(&PrimitiveType::Integer.into()));
250        assert!(!constraint.satisfied(&PrimitiveType::Float.into()));
251        assert!(!constraint.satisfied(&PrimitiveType::String.into()));
252        assert!(constraint.satisfied(&PrimitiveType::File.into()));
253        assert!(constraint.satisfied(&PrimitiveType::Directory.into()));
254        assert!(constraint.satisfied(&Type::OptionalObject));
255        assert!(constraint.satisfied(&Type::Object));
256        assert!(constraint.satisfied(&Type::Union));
257        assert!(!constraint.satisfied(&ArrayType::new(PrimitiveType::String).into()));
258        assert!(constraint.satisfied(&ArrayType::new(PrimitiveType::File).into()));
259        assert!(
260            !constraint
261                .satisfied(&PairType::new(PrimitiveType::String, PrimitiveType::String).into())
262        );
263        assert!(
264            constraint.satisfied(&PairType::new(PrimitiveType::String, PrimitiveType::File).into())
265        );
266        assert!(
267            constraint.satisfied(
268                &Type::from(PairType::new(
269                    PrimitiveType::Directory,
270                    PrimitiveType::String
271                ))
272                .optional()
273            )
274        );
275        assert!(
276            !constraint.satisfied(
277                &Type::from(MapType::new(
278                    PrimitiveType::String,
279                    ArrayType::new(PrimitiveType::String)
280                ))
281                .optional()
282            )
283        );
284        assert!(
285            constraint.satisfied(
286                &MapType::new(
287                    PrimitiveType::String,
288                    Type::from(ArrayType::new(PrimitiveType::File)).optional()
289                )
290                .into()
291            )
292        );
293        assert!(
294            constraint.satisfied(
295                &Type::from(MapType::new(
296                    PrimitiveType::Directory,
297                    PrimitiveType::String
298                ))
299                .optional()
300            )
301        );
302        assert!(
303            !constraint.satisfied(&StructType::new("Foo", [("foo", PrimitiveType::String)]).into())
304        );
305        assert!(constraint.satisfied(
306            &Type::from(StructType::new("Foo", [("foo", PrimitiveType::File)])).optional()
307        ));
308        assert!(
309            constraint
310                .satisfied(&StructType::new("Foo", [("foo", PrimitiveType::Directory,)]).into())
311        );
312    }
313
314    #[test]
315    fn test_struct_constraint() {
316        let constraint = StructConstraint;
317        assert!(!constraint.satisfied(&Type::from(PrimitiveType::Boolean).optional()));
318        assert!(!constraint.satisfied(&Type::from(PrimitiveType::Integer).optional()));
319        assert!(!constraint.satisfied(&Type::from(PrimitiveType::Float).optional()));
320        assert!(!constraint.satisfied(&Type::from(PrimitiveType::String).optional()));
321        assert!(!constraint.satisfied(&Type::from(PrimitiveType::File).optional()));
322        assert!(!constraint.satisfied(&Type::from(PrimitiveType::Directory).optional()));
323        assert!(!constraint.satisfied(&PrimitiveType::Boolean.into()));
324        assert!(!constraint.satisfied(&PrimitiveType::Integer.into()));
325        assert!(!constraint.satisfied(&PrimitiveType::Float.into()));
326        assert!(!constraint.satisfied(&PrimitiveType::String.into()));
327        assert!(!constraint.satisfied(&PrimitiveType::File.into()));
328        assert!(!constraint.satisfied(&PrimitiveType::Directory.into()));
329        assert!(!constraint.satisfied(&Type::OptionalObject));
330        assert!(!constraint.satisfied(&Type::Object));
331        assert!(!constraint.satisfied(&Type::Union));
332        assert!(!constraint.satisfied(&ArrayType::non_empty(PrimitiveType::String).into()));
333        assert!(!constraint.satisfied(
334            &Type::from(PairType::new(PrimitiveType::String, PrimitiveType::String)).optional()
335        ));
336        assert!(
337            !constraint
338                .satisfied(&MapType::new(PrimitiveType::String, PrimitiveType::String,).into())
339        );
340        assert!(constraint.satisfied(
341            &Type::from(StructType::new("Foo", [("foo", PrimitiveType::String)])).optional()
342        ));
343    }
344
345    #[test]
346    fn test_json_constraint() {
347        let constraint = JsonSerializableConstraint;
348        assert!(constraint.satisfied(&Type::from(PrimitiveType::Boolean).optional()));
349        assert!(constraint.satisfied(&Type::from(PrimitiveType::Integer).optional()));
350        assert!(constraint.satisfied(&Type::from(PrimitiveType::Float).optional()));
351        assert!(constraint.satisfied(&Type::from(PrimitiveType::String).optional()));
352        assert!(constraint.satisfied(&Type::from(PrimitiveType::File).optional()));
353        assert!(constraint.satisfied(&Type::from(PrimitiveType::Directory).optional()));
354        assert!(constraint.satisfied(&PrimitiveType::Boolean.into()));
355        assert!(constraint.satisfied(&PrimitiveType::Integer.into()));
356        assert!(constraint.satisfied(&PrimitiveType::Float.into()));
357        assert!(constraint.satisfied(&PrimitiveType::String.into()));
358        assert!(constraint.satisfied(&PrimitiveType::File.into()));
359        assert!(constraint.satisfied(&PrimitiveType::Directory.into()));
360        assert!(constraint.satisfied(&Type::OptionalObject));
361        assert!(constraint.satisfied(&Type::Object));
362        assert!(constraint.satisfied(&Type::Union));
363        assert!(
364            constraint.satisfied(&Type::from(ArrayType::new(PrimitiveType::String)).optional())
365        );
366        assert!(
367            !constraint
368                .satisfied(&PairType::new(PrimitiveType::String, PrimitiveType::String,).into())
369        );
370        assert!(constraint.satisfied(
371            &Type::from(MapType::new(PrimitiveType::String, PrimitiveType::String)).optional()
372        ));
373        assert!(
374            !constraint
375                .satisfied(&MapType::new(PrimitiveType::Integer, PrimitiveType::String).into())
376        );
377        assert!(constraint.satisfied(
378            &Type::from(StructType::new("Foo", [("foo", PrimitiveType::String)])).optional()
379        ));
380    }
381
382    #[test]
383    fn test_map_key_constraint() {
384        let constraint = MapKeyConstraint;
385        assert!(constraint.satisfied(&PrimitiveType::Boolean.into()));
386        assert!(constraint.satisfied(&PrimitiveType::Integer.into()));
387        assert!(constraint.satisfied(&PrimitiveType::Float.into()));
388        assert!(constraint.satisfied(&PrimitiveType::String.into()));
389        assert!(constraint.satisfied(&PrimitiveType::File.into()));
390        assert!(constraint.satisfied(&PrimitiveType::Directory.into()));
391        assert!(constraint.satisfied(&Type::Union));
392        assert!(!constraint.satisfied(&Type::from(PrimitiveType::Boolean).optional()));
393        assert!(!constraint.satisfied(&Type::from(PrimitiveType::Integer).optional()));
394        assert!(!constraint.satisfied(&Type::from(PrimitiveType::Float).optional()));
395        assert!(!constraint.satisfied(&Type::from(PrimitiveType::String).optional()));
396        assert!(!constraint.satisfied(&Type::from(PrimitiveType::File).optional()));
397        assert!(!constraint.satisfied(&Type::from(PrimitiveType::Directory).optional()));
398        assert!(!constraint.satisfied(&Type::OptionalObject));
399        assert!(!constraint.satisfied(&Type::Object));
400    }
401
402    #[test]
403    fn test_primitive_constraint() {
404        let constraint = PrimitiveTypeConstraint;
405        assert!(constraint.satisfied(&Type::from(PrimitiveType::Boolean).optional()));
406        assert!(constraint.satisfied(&Type::from(PrimitiveType::Integer).optional()));
407        assert!(constraint.satisfied(&Type::from(PrimitiveType::Float).optional()));
408        assert!(constraint.satisfied(&Type::from(PrimitiveType::String).optional()));
409        assert!(constraint.satisfied(&Type::from(PrimitiveType::File).optional()));
410        assert!(constraint.satisfied(&Type::from(PrimitiveType::Directory).optional()));
411        assert!(constraint.satisfied(&PrimitiveType::Boolean.into()));
412        assert!(constraint.satisfied(&PrimitiveType::Integer.into()));
413        assert!(constraint.satisfied(&PrimitiveType::Float.into()));
414        assert!(constraint.satisfied(&PrimitiveType::String.into()));
415        assert!(constraint.satisfied(&PrimitiveType::File.into()));
416        assert!(constraint.satisfied(&PrimitiveType::Directory.into()));
417        assert!(!constraint.satisfied(&Type::OptionalObject));
418        assert!(!constraint.satisfied(&Type::Object));
419        assert!(constraint.satisfied(&Type::Union));
420        assert!(constraint.satisfied(&Type::None));
421        assert!(!constraint.satisfied(&ArrayType::non_empty(PrimitiveType::String).into()));
422        assert!(!constraint.satisfied(
423            &Type::from(PairType::new(PrimitiveType::String, PrimitiveType::String)).optional()
424        ));
425        assert!(
426            !constraint
427                .satisfied(&MapType::new(PrimitiveType::String, PrimitiveType::String,).into())
428        );
429        assert!(!constraint.satisfied(
430            &Type::from(StructType::new("Foo", [("foo", PrimitiveType::String)])).optional()
431        ));
432    }
433}