Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement the generic map functions from the WDL standardy library. #257

Merged
merged 7 commits into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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