Skip to content

Commit

Permalink
feat: implement the generic map functions from the WDL standardy libr…
Browse files Browse the repository at this point in the history
…ary. (#257)
  • Loading branch information
peterhuene authored Nov 22, 2024
1 parent 8d90374 commit 0fe42e1
Show file tree
Hide file tree
Showing 27 changed files with 1,452 additions and 202 deletions.
11 changes: 11 additions & 0 deletions wdl-analysis/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

* The "required primitive type" constraint has been removed as every place the
constraint was used should allow for optional primitive types as well;
consequently, the AnyPrimitiveTypeConstraint was renamed to simply
`PrimitiveTypeConstraint` ([#257](https://github.com/stjude-rust-labs/wdl/pull/257)).
* The common type calculation now favors the "left-hand side" of the
calculation rather than the right, making it more intuitive to use. For
example, a calculation of `File | String` is now `File` rather than
`String` ([#257](https://github.com/stjude-rust-labs/wdl/pull/257)).
* Refactored function call binding information to aid with call evaluation in
`wdl-engine` ([#251](https://github.com/stjude-rust-labs/wdl/pull/251)).
* Made diagnostic creation functions public ([#249](https://github.com/stjude-rust-labs/wdl/pull/249)).
Expand All @@ -23,6 +31,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

* Common type calculation now supports discovering common types between the
compound types containing Union and None as inner types, e.g.
`Array[String] | Array[None] -> Array[String?]` ([#257](https://github.com/stjude-rust-labs/wdl/pull/257)).
* Static analysis of expressions within object literal members now takes place ([#254](https://github.com/stjude-rust-labs/wdl/pull/254)).
* Certain standard library functions with an existing constraint on generic
parameters that take structs are further constrained to take structs
Expand Down
66 changes: 33 additions & 33 deletions wdl-analysis/src/stdlib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2387,7 +2387,7 @@ pub static STDLIB: LazyLock<StandardLibrary> = LazyLock::new(|| {
"prefix",
MonomorphicFunction::new(
FunctionSignature::builder()
.type_parameter("P", RequiredPrimitiveTypeConstraint)
.type_parameter("P", PrimitiveTypeConstraint)
.parameter(PrimitiveTypeKind::String)
.parameter(GenericArrayType::new(GenericType::Parameter("P")))
.ret(array_string)
Expand All @@ -2406,7 +2406,7 @@ pub static STDLIB: LazyLock<StandardLibrary> = LazyLock::new(|| {
MonomorphicFunction::new(
FunctionSignature::builder()
.min_version(SupportedVersion::V1(V1::One))
.type_parameter("P", RequiredPrimitiveTypeConstraint)
.type_parameter("P", PrimitiveTypeConstraint)
.parameter(PrimitiveTypeKind::String)
.parameter(GenericArrayType::new(GenericType::Parameter("P")))
.ret(array_string)
Expand All @@ -2425,7 +2425,7 @@ pub static STDLIB: LazyLock<StandardLibrary> = LazyLock::new(|| {
MonomorphicFunction::new(
FunctionSignature::builder()
.min_version(SupportedVersion::V1(V1::One))
.type_parameter("P", RequiredPrimitiveTypeConstraint)
.type_parameter("P", PrimitiveTypeConstraint)
.parameter(GenericArrayType::new(GenericType::Parameter("P")))
.ret(array_string)
.build(),
Expand All @@ -2443,7 +2443,7 @@ pub static STDLIB: LazyLock<StandardLibrary> = LazyLock::new(|| {
MonomorphicFunction::new(
FunctionSignature::builder()
.min_version(SupportedVersion::V1(V1::One))
.type_parameter("P", RequiredPrimitiveTypeConstraint)
.type_parameter("P", PrimitiveTypeConstraint)
.parameter(GenericArrayType::new(GenericType::Parameter("P")))
.ret(array_string)
.build(),
Expand All @@ -2461,7 +2461,7 @@ pub static STDLIB: LazyLock<StandardLibrary> = LazyLock::new(|| {
MonomorphicFunction::new(
FunctionSignature::builder()
.min_version(SupportedVersion::V1(V1::One))
.type_parameter("P", RequiredPrimitiveTypeConstraint)
.type_parameter("P", PrimitiveTypeConstraint)
.parameter(PrimitiveTypeKind::String)
.parameter(GenericArrayType::new(GenericType::Parameter("P")))
.ret(PrimitiveTypeKind::String)
Expand Down Expand Up @@ -2586,7 +2586,7 @@ pub static STDLIB: LazyLock<StandardLibrary> = LazyLock::new(|| {
MonomorphicFunction::new(
FunctionSignature::builder()
.min_version(SupportedVersion::V1(V1::Two))
.type_parameter("P", AnyPrimitiveTypeConstraint)
.type_parameter("P", PrimitiveTypeConstraint)
.parameter(GenericArrayType::new(GenericType::Parameter("P")))
.parameter(GenericType::Parameter("P"))
.ret(PrimitiveTypeKind::Boolean)
Expand Down Expand Up @@ -2685,7 +2685,7 @@ pub static STDLIB: LazyLock<StandardLibrary> = LazyLock::new(|| {
MonomorphicFunction::new(
FunctionSignature::builder()
.min_version(SupportedVersion::V1(V1::One))
.type_parameter("K", RequiredPrimitiveTypeConstraint)
.type_parameter("K", PrimitiveTypeConstraint)
.any_type_parameter("V")
.parameter(GenericMapType::new(
GenericType::Parameter("K"),
Expand All @@ -2710,7 +2710,7 @@ pub static STDLIB: LazyLock<StandardLibrary> = LazyLock::new(|| {
MonomorphicFunction::new(
FunctionSignature::builder()
.min_version(SupportedVersion::V1(V1::One))
.type_parameter("K", RequiredPrimitiveTypeConstraint)
.type_parameter("K", PrimitiveTypeConstraint)
.any_type_parameter("V")
.parameter(GenericArrayType::new(GenericPairType::new(
GenericType::Parameter("K"),
Expand All @@ -2735,7 +2735,7 @@ pub static STDLIB: LazyLock<StandardLibrary> = LazyLock::new(|| {
PolymorphicFunction::new(vec![
FunctionSignature::builder()
.min_version(SupportedVersion::V1(V1::One))
.type_parameter("K", RequiredPrimitiveTypeConstraint)
.type_parameter("K", PrimitiveTypeConstraint)
.any_type_parameter("V")
.parameter(GenericMapType::new(
GenericType::Parameter("K"),
Expand All @@ -2744,13 +2744,13 @@ pub static STDLIB: LazyLock<StandardLibrary> = LazyLock::new(|| {
.ret(GenericArrayType::new(GenericType::Parameter("K")))
.build(),
FunctionSignature::builder()
.min_version(SupportedVersion::V1(V1::One))
.min_version(SupportedVersion::V1(V1::Two))
.type_parameter("S", StructConstraint)
.parameter(GenericType::Parameter("S"))
.ret(array_string)
.build(),
FunctionSignature::builder()
.min_version(SupportedVersion::V1(V1::One))
.min_version(SupportedVersion::V1(V1::Two))
.parameter(Type::Object)
.ret(array_string)
.build(),
Expand All @@ -2768,7 +2768,7 @@ pub static STDLIB: LazyLock<StandardLibrary> = LazyLock::new(|| {
PolymorphicFunction::new(vec![
FunctionSignature::builder()
.min_version(SupportedVersion::V1(V1::Two))
.type_parameter("K", RequiredPrimitiveTypeConstraint)
.type_parameter("K", PrimitiveTypeConstraint)
.any_type_parameter("V")
.parameter(GenericMapType::new(
GenericType::Parameter("K"),
Expand Down Expand Up @@ -2820,7 +2820,7 @@ pub static STDLIB: LazyLock<StandardLibrary> = LazyLock::new(|| {
MonomorphicFunction::new(
FunctionSignature::builder()
.min_version(SupportedVersion::V1(V1::Two))
.type_parameter("K", RequiredPrimitiveTypeConstraint)
.type_parameter("K", PrimitiveTypeConstraint)
.any_type_parameter("V")
.parameter(GenericMapType::new(
GenericType::Parameter("K"),
Expand All @@ -2842,7 +2842,7 @@ pub static STDLIB: LazyLock<StandardLibrary> = LazyLock::new(|| {
MonomorphicFunction::new(
FunctionSignature::builder()
.min_version(SupportedVersion::V1(V1::One))
.type_parameter("K", RequiredPrimitiveTypeConstraint)
.type_parameter("K", PrimitiveTypeConstraint)
.any_type_parameter("V")
.parameter(GenericArrayType::new(GenericPairType::new(
GenericType::Parameter("K"),
Expand Down Expand Up @@ -3008,11 +3008,11 @@ mod test {
"write_objects(Array[Object]) -> File",
"write_objects(Array[S]) -> File where `S`: any structure containing only primitive \
types",
"prefix(String, Array[P]) -> Array[String] where `P`: any required primitive type",
"suffix(String, Array[P]) -> Array[String] where `P`: any required primitive type",
"quote(Array[P]) -> Array[String] where `P`: any required primitive type",
"squote(Array[P]) -> Array[String] where `P`: any required primitive type",
"sep(String, Array[P]) -> String where `P`: any required primitive type",
"prefix(String, Array[P]) -> Array[String] where `P`: any primitive type",
"suffix(String, Array[P]) -> Array[String] where `P`: any primitive type",
"quote(Array[P]) -> Array[String] where `P`: any primitive type",
"squote(Array[P]) -> Array[String] where `P`: any primitive type",
"sep(String, Array[P]) -> String where `P`: any primitive type",
"range(Int) -> Array[Int]",
"transpose(Array[Array[X]]) -> Array[Array[X]]",
"cross(Array[X], Array[Y]) -> Array[Pair[X, Y]]",
Expand All @@ -3023,19 +3023,18 @@ mod test {
"flatten(Array[Array[X]]) -> Array[X]",
"select_first(Array[X], <X>) -> X where `X`: any optional type",
"select_all(Array[X]) -> Array[X] where `X`: any optional type",
"as_pairs(Map[K, V]) -> Array[Pair[K, V]] where `K`: any required primitive type",
"as_map(Array[Pair[K, V]]) -> Map[K, V] where `K`: any required primitive type",
"keys(Map[K, V]) -> Array[K] where `K`: any required primitive type",
"as_pairs(Map[K, V]) -> Array[Pair[K, V]] where `K`: any primitive type",
"as_map(Array[Pair[K, V]]) -> Map[K, V] where `K`: any primitive type",
"keys(Map[K, V]) -> Array[K] where `K`: any primitive type",
"keys(S) -> Array[String] where `S`: any structure",
"keys(Object) -> Array[String]",
"contains_key(Map[K, V], K) -> Boolean where `K`: any required primitive type",
"contains_key(Map[K, V], K) -> Boolean where `K`: any primitive type",
"contains_key(Object, String) -> Boolean",
"contains_key(Map[String, V], Array[String]) -> Boolean",
"contains_key(S, Array[String]) -> Boolean where `S`: any structure",
"contains_key(Object, Array[String]) -> Boolean",
"values(Map[K, V]) -> Array[V] where `K`: any required primitive type",
"collect_by_key(Array[Pair[K, V]]) -> Map[K, Array[V]] where `K`: any required \
primitive type",
"values(Map[K, V]) -> Array[V] where `K`: any primitive type",
"collect_by_key(Array[Pair[K, V]]) -> Map[K, Array[V]] where `K`: any primitive type",
"defined(X) -> Boolean where `X`: any optional type",
"length(Array[X]) -> Int",
"length(Map[K, V]) -> Int",
Expand Down Expand Up @@ -3135,7 +3134,7 @@ mod test {
.expect_err("bind should fail");
assert_eq!(e, FunctionBindError::ArgumentTypeMismatch {
index: 0,
expected: "`Map[K, V]` where `K`: any required primitive type".into()
expected: "`Map[K, V]` where `K`: any primitive type".into()
});

// Check for Union (i.e. indeterminate)
Expand Down Expand Up @@ -3178,13 +3177,14 @@ mod test {
PrimitiveType::optional(PrimitiveTypeKind::String),
PrimitiveTypeKind::Boolean,
));
let e = f
let binding = f
.bind(SupportedVersion::V1(V1::Two), &mut types, &[ty])
.expect_err("bind should fail");
assert_eq!(e, FunctionBindError::ArgumentTypeMismatch {
index: 0,
expected: "`Map[K, Boolean]` where `K`: any required primitive type".into()
});
.expect("bind should succeed");
assert_eq!(binding.index(), 0);
assert_eq!(
binding.return_type().display(&types).to_string(),
"Array[Boolean]"
);
}

#[test]
Expand Down
99 changes: 5 additions & 94 deletions wdl-analysis/src/stdlib/constraints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,37 +182,11 @@ impl Constraint for JsonSerializableConstraint {
}
}

/// Represents a constraint that ensures the type is a required primitive type.
/// Represents a constraint that ensures the type is a primitive type.
#[derive(Debug, Copy, Clone)]
pub struct RequiredPrimitiveTypeConstraint;
pub struct PrimitiveTypeConstraint;

impl Constraint for RequiredPrimitiveTypeConstraint {
fn description(&self) -> &'static str {
"any required primitive type"
}

fn satisfied(&self, _: &Types, ty: Type) -> bool {
match ty {
Type::Primitive(ty) => !ty.is_optional(),
// Treat unions as primitive as they can only be checked at runtime
Type::Union => true,
Type::Compound(_)
| Type::Object
| Type::OptionalObject
| Type::None
| Type::Task
| Type::Hints
| Type::Input
| Type::Output => false,
}
}
}

/// Represents a constraint that ensures the type is any primitive type.
#[derive(Debug, Copy, Clone)]
pub struct AnyPrimitiveTypeConstraint;

impl Constraint for AnyPrimitiveTypeConstraint {
impl Constraint for PrimitiveTypeConstraint {
fn description(&self) -> &'static str {
"any primitive type"
}
Expand Down Expand Up @@ -542,71 +516,8 @@ mod test {
}

#[test]
fn test_required_primitive_constraint() {
let constraint = RequiredPrimitiveTypeConstraint;
let mut types = Types::default();

assert!(!constraint.satisfied(
&types,
PrimitiveType::optional(PrimitiveTypeKind::Boolean).into()
));
assert!(!constraint.satisfied(
&types,
PrimitiveType::optional(PrimitiveTypeKind::Integer).into()
));
assert!(!constraint.satisfied(
&types,
PrimitiveType::optional(PrimitiveTypeKind::Float).into()
));
assert!(!constraint.satisfied(
&types,
PrimitiveType::optional(PrimitiveTypeKind::String).into()
));
assert!(!constraint.satisfied(
&types,
PrimitiveType::optional(PrimitiveTypeKind::File).into()
));
assert!(!constraint.satisfied(
&types,
PrimitiveType::optional(PrimitiveTypeKind::Directory).into()
));
assert!(constraint.satisfied(&types, PrimitiveTypeKind::Boolean.into()));
assert!(constraint.satisfied(&types, PrimitiveTypeKind::Integer.into()));
assert!(constraint.satisfied(&types, PrimitiveTypeKind::Float.into()));
assert!(constraint.satisfied(&types, PrimitiveTypeKind::String.into()));
assert!(constraint.satisfied(&types, PrimitiveTypeKind::File.into()));
assert!(constraint.satisfied(&types, PrimitiveTypeKind::Directory.into()));
assert!(!constraint.satisfied(&types, Type::Object));
assert!(!constraint.satisfied(&types, Type::OptionalObject));
assert!(constraint.satisfied(&types, Type::Union));
assert!(!constraint.satisfied(&types, Type::None));

let ty = types.add_array(ArrayType::non_empty(PrimitiveTypeKind::String));
assert!(!constraint.satisfied(&types, ty));

let ty = types
.add_pair(PairType::new(
PrimitiveTypeKind::String,
PrimitiveTypeKind::String,
))
.optional();
assert!(!constraint.satisfied(&types, ty));

let ty = types.add_map(MapType::new(
PrimitiveTypeKind::String,
PrimitiveTypeKind::String,
));
assert!(!constraint.satisfied(&types, ty));

let ty = types
.add_struct(StructType::new("Foo", [("foo", PrimitiveTypeKind::String)]))
.optional();
assert!(!constraint.satisfied(&types, ty));
}

#[test]
fn test_any_primitive_constraint() {
let constraint = AnyPrimitiveTypeConstraint;
fn test_primitive_constraint() {
let constraint = PrimitiveTypeConstraint;
let mut types = Types::default();

assert!(constraint.satisfied(
Expand Down
Loading

0 comments on commit 0fe42e1

Please sign in to comment.