Skip to content

Commit

Permalink
feat: implement the file functions from the WDL standard library. (#254)
Browse files Browse the repository at this point in the history
Co-authored-by: Andrew Frantz <andrew.frantz@stjude.org>
  • Loading branch information
peterhuene and a-frantz authored Nov 19, 2024
1 parent 71d4e77 commit 37f2a42
Show file tree
Hide file tree
Showing 55 changed files with 7,269 additions and 2,332 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ dirs = "5.0.1"
faster-hex = "0.9.0"
futures = "0.3.30"
git2 = "0.18.3"
glob = "0.3.1"
id-arena = "2.2.1"
indexmap = { version = "2.2.6", features = ["serde"] }
indicatif = "0.17.8"
Expand Down
1,580 changes: 800 additions & 780 deletions Gauntlet.toml

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions gauntlet/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ pub async fn gauntlet(args: Args) -> Result<()> {
};

report
.register(document_identifier, status, elapsed)
.register(document_identifier, status)
.context("failed to register report status")?;
}

Expand All @@ -330,7 +330,7 @@ pub async fn gauntlet(args: Args) -> Result<()> {
.next_section()
.context("failed to transition to next report section")?;
report
.footer(repository_identifier)
.footer(repository_identifier, elapsed)
.context("failed to write report footer")?;
report
.next_section()
Expand Down
6 changes: 3 additions & 3 deletions gauntlet/src/report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,6 @@ impl<T: std::io::Write> Report<T> {
&mut self,
identifier: document::Identifier,
status: Status,
elapsed: Duration,
) -> std::io::Result<()> {
if self.section != Section::Summary {
panic!(
Expand All @@ -185,7 +184,7 @@ impl<T: std::io::Write> Report<T> {
);
}

writeln!(self.inner, "{status} {identifier} ({elapsed:?})")?;
writeln!(self.inner, "{status} {identifier}")?;
self.results.insert(identifier, status);
self.printed = true;

Expand Down Expand Up @@ -238,6 +237,7 @@ impl<T: std::io::Write> Report<T> {
pub fn footer(
&mut self,
repository_identifier: &repository::Identifier,
elapsed: Duration,
) -> std::io::Result<()> {
if self.section != Section::Footer {
panic!(
Expand Down Expand Up @@ -292,7 +292,7 @@ impl<T: std::io::Write> Report<T> {

writeln!(
self.inner,
" ({:.1}%)",
" ({:.1}%) analyzed in {elapsed:?}",
(passed as f64 / considered as f64) * 100.0
)?;
self.printed = true;
Expand Down
9 changes: 9 additions & 0 deletions wdl-analysis/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Refactored expression type evaluator to provide context via a trait ([#249](https://github.com/stjude-rust-labs/wdl/pull/249)).
* Removed `PartialEq`, `Eq`, and `Hash` from WDL-type-related types ([#249](https://github.com/stjude-rust-labs/wdl/pull/249)).

### Fixed

* 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
containing only primitive members ([#254](https://github.com/stjude-rust-labs/wdl/pull/254)).
* Fixed signatures and minimum required versions for certain standard library
functions ([#254](https://github.com/stjude-rust-labs/wdl/pull/254)).

## 0.5.0 - 10-22-2024

### Changed
Expand Down
106 changes: 86 additions & 20 deletions wdl-analysis/src/stdlib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1651,14 +1651,22 @@ pub struct StandardLibrary {
types: Types,
/// A map of function name to function definition.
functions: IndexMap<&'static str, Function>,
/// The type for `Array[String]`.
pub(crate) array_string: Type,
/// The type for `Array[Int]`.
pub(crate) array_int: Type,
/// The type for `Map[String, Int]`.
pub(crate) map_string_int: Type,
array_int: Type,
/// The type for `Array[String]`.
array_string: Type,
/// The type for `Array[File]`.
array_file: Type,
/// The type for `Array[Object]`.
array_object: Type,
/// The type for `Array[String]+`.
array_string_non_empty: Type,
/// The type for `Array[Array[String]]`.
array_array_string: Type,
/// The type for `Map[String, String]`.
pub(crate) map_string_string: Type,
map_string_string: Type,
/// The type for `Map[String, Int]`.
map_string_int: Type,
}

impl StandardLibrary {
Expand All @@ -1676,6 +1684,46 @@ impl StandardLibrary {
pub fn functions(&self) -> impl ExactSizeIterator<Item = (&'static str, &Function)> {
self.functions.iter().map(|(n, f)| (*n, f))
}

/// Gets the type for `Array[Int]`.
pub fn array_int_type(&self) -> Type {
self.array_int
}

/// Gets the type for `Array[String]`.
pub fn array_string_type(&self) -> Type {
self.array_string
}

/// Gets the type for `Array[File]`.
pub fn array_file_type(&self) -> Type {
self.array_file
}

/// Gets the type for `Array[Object]`.
pub fn array_object_type(&self) -> Type {
self.array_object
}

/// Gets the type for `Array[String]+`.
pub fn array_string_non_empty_type(&self) -> Type {
self.array_string_non_empty
}

/// Gets the type for `Array[Array[String]]`.
pub fn array_array_string_type(&self) -> Type {
self.array_array_string
}

/// Gets the type for `Map[String, String]`.
pub fn map_string_string_type(&self) -> Type {
self.map_string_string
}

/// Gets the type for `Map[String, Int]`.
pub fn map_string_int_type(&self) -> Type {
self.map_string_int
}
}

/// Represents the WDL standard library.
Expand Down Expand Up @@ -1958,6 +2006,15 @@ pub static STDLIB: LazyLock<StandardLibrary> = LazyLock::new(|| {
.insert(
"size",
PolymorphicFunction::new(vec![
// This overload isn't explicitly in the spec, but it fixes an ambiguity in 1.2
// when passed a literal `None` value.
FunctionSignature::builder()
.min_version(SupportedVersion::V1(V1::Two))
.required(1)
.parameter(Type::None)
.parameter(PrimitiveTypeKind::String)
.ret(PrimitiveTypeKind::Float)
.build(),
FunctionSignature::builder()
.required(1)
.parameter(PrimitiveType::optional(PrimitiveTypeKind::File))
Expand All @@ -1969,6 +2026,7 @@ pub static STDLIB: LazyLock<StandardLibrary> = LazyLock::new(|| {
// `String` overload is required as `String` may coerce to either `File` or
// `Directory`, which is ambiguous.
FunctionSignature::builder()
.min_version(SupportedVersion::V1(V1::Two))
.required(1)
.parameter(PrimitiveType::optional(PrimitiveTypeKind::String))
.parameter(PrimitiveTypeKind::String)
Expand Down Expand Up @@ -2131,11 +2189,13 @@ pub static STDLIB: LazyLock<StandardLibrary> = LazyLock::new(|| {
.ret(array_array_string)
.build(),
FunctionSignature::builder()
.min_version(SupportedVersion::V1(V1::Two))
.parameter(PrimitiveTypeKind::File)
.parameter(PrimitiveTypeKind::Boolean)
.ret(array_object)
.build(),
FunctionSignature::builder()
.min_version(SupportedVersion::V1(V1::Two))
.parameter(PrimitiveTypeKind::File)
.parameter(PrimitiveTypeKind::Boolean)
.parameter(array_string)
Expand All @@ -2158,18 +2218,16 @@ pub static STDLIB: LazyLock<StandardLibrary> = LazyLock::new(|| {
.ret(PrimitiveTypeKind::File)
.build(),
FunctionSignature::builder()
.type_parameter("S", StructConstraint)
.parameter(GenericArrayType::new(GenericType::Parameter("S")))
.ret(PrimitiveTypeKind::File)
.build(),
FunctionSignature::builder()
.min_version(SupportedVersion::V1(V1::Two))
.parameter(array_array_string)
.parameter(PrimitiveTypeKind::Boolean)
.parameter(array_string)
.ret(PrimitiveTypeKind::File)
.build(),
FunctionSignature::builder()
.type_parameter("S", StructConstraint)
.min_version(SupportedVersion::V1(V1::Two))
.type_parameter("S", PrimitiveStructConstraint)
.required(1)
.parameter(GenericArrayType::new(GenericType::Parameter("S")))
.parameter(PrimitiveTypeKind::Boolean)
.parameter(array_string)
Expand Down Expand Up @@ -2289,7 +2347,8 @@ pub static STDLIB: LazyLock<StandardLibrary> = LazyLock::new(|| {
.ret(PrimitiveTypeKind::File)
.build(),
FunctionSignature::builder()
.type_parameter("S", StructConstraint)
.min_version(SupportedVersion::V1(V1::One))
.type_parameter("S", PrimitiveStructConstraint)
.parameter(GenericType::Parameter("S"))
.ret(PrimitiveTypeKind::File)
.build(),
Expand All @@ -2310,7 +2369,8 @@ pub static STDLIB: LazyLock<StandardLibrary> = LazyLock::new(|| {
.ret(PrimitiveTypeKind::File)
.build(),
FunctionSignature::builder()
.type_parameter("S", StructConstraint)
.min_version(SupportedVersion::V1(V1::One))
.type_parameter("S", PrimitiveStructConstraint)
.parameter(GenericArrayType::new(GenericType::Parameter("S")))
.ret(PrimitiveTypeKind::File)
.build(),
Expand Down Expand Up @@ -2853,10 +2913,14 @@ pub static STDLIB: LazyLock<StandardLibrary> = LazyLock::new(|| {
StandardLibrary {
types,
functions,
array_string,
array_int,
map_string_int,
array_string,
array_file,
array_object,
array_string_non_empty,
array_array_string,
map_string_string,
map_string_int,
}
});

Expand Down Expand Up @@ -2912,6 +2976,7 @@ mod test {
"join_paths(File, Array[String]+) -> File",
"join_paths(Array[String]+) -> File",
"glob(String) -> Array[File]",
"size(None, <String>) -> Float",
"size(File?, <String>) -> Float",
"size(String?, <String>) -> Float",
"size(Directory?, <String>) -> Float",
Expand All @@ -2929,19 +2994,20 @@ mod test {
"read_tsv(File, Boolean) -> Array[Object]",
"read_tsv(File, Boolean, Array[String]) -> Array[Object]",
"write_tsv(Array[Array[String]]) -> File",
"write_tsv(Array[S]) -> File where `S`: any structure",
"write_tsv(Array[Array[String]], Boolean, Array[String]) -> File",
"write_tsv(Array[S], Boolean, Array[String]) -> File where `S`: any structure",
"write_tsv(Array[S], <Boolean>, <Array[String]>) -> File where `S`: any structure \
containing only primitive types",
"read_map(File) -> Map[String, String]",
"write_map(Map[String, String]) -> File",
"read_json(File) -> Union",
"write_json(X) -> File where `X`: any JSON-serializable type",
"read_object(File) -> Object",
"read_objects(File) -> Array[Object]",
"write_object(Object) -> File",
"write_object(S) -> File where `S`: any structure",
"write_object(S) -> File where `S`: any structure containing only primitive types",
"write_objects(Array[Object]) -> File",
"write_objects(Array[S]) -> File where `S`: any structure",
"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",
Expand Down
26 changes: 24 additions & 2 deletions wdl-analysis/src/stdlib/constraints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
use std::fmt;

use crate::types::Coercible;
use crate::types::CompoundType;
use crate::types::CompoundTypeDef;
use crate::types::Optional;
Expand Down Expand Up @@ -114,6 +115,27 @@ impl Constraint for StructConstraint {
}
}

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

impl Constraint for PrimitiveStructConstraint {
fn description(&self) -> &'static str {
"any structure containing only primitive types"
}

fn satisfied(&self, types: &Types, ty: Type) -> bool {
if let Type::Compound(ty) = ty {
if let CompoundTypeDef::Struct(s) = types.type_definition(ty.definition()) {
return s.members().values().all(|ty| ty.as_primitive().is_some());
}
}

false
}
}

/// Represents a constraint that ensures the type is JSON serializable.
#[derive(Debug, Copy, Clone)]
pub struct JsonSerializableConstraint;
Expand All @@ -130,8 +152,8 @@ impl Constraint for JsonSerializableConstraint {
CompoundTypeDef::Array(ty) => type_is_serializable(types, ty.element_type()),
CompoundTypeDef::Pair(_) => false,
CompoundTypeDef::Map(ty) => {
!ty.key_type().is_optional()
&& matches!(ty.key_type(), Type::Primitive(ty) if ty.kind() == PrimitiveTypeKind::String)
ty.key_type()
.is_coercible_to(types, &PrimitiveTypeKind::String.into())
&& type_is_serializable(types, ty.value_type())
}
CompoundTypeDef::Struct(s) => s
Expand Down
Loading

0 comments on commit 37f2a42

Please sign in to comment.