Skip to content

Commit cc72587

Browse files
committed
fix: enforce correct type rules for placeholder options
1 parent 173c0c2 commit cc72587

File tree

12 files changed

+102
-30
lines changed

12 files changed

+102
-30
lines changed

wdl-analysis/src/types/v1.rs

+41-24
Original file line numberDiff line numberDiff line change
@@ -640,34 +640,51 @@ impl<'a, C: EvaluationContext> ExprTypeEvaluator<'a, C> {
640640
}
641641

642642
/// Checks a placeholder expression.
643-
pub(crate) fn check_placeholder(&mut self, placeholder: &Placeholder) {
644-
self.placeholders += 1;
645-
let expr = placeholder.expr();
646-
/// Evaluate the placeholder expression and check that the resulting type is
647-
/// coercible to string for interpolation
648-
if let Some(ty) = self.evaluate_expr(&expr) {
649-
match ty {
650-
Type::Primitive(..) | Type::Union | Type::None => { /* Already coercible */ }
651-
_ => {
652-
let coercible = match placeholder.option() {
653-
Some(PlaceholderOption::Sep(_)) => matches!(ty, Type::Compound(CompoundType::Array(element_type), _)
654-
if matches!(element_type.as_ref(), Type::Primitive(_, false))),
655-
656-
Some(PlaceholderOption::Default(_)) => matches!(ty, Type::Primitive(_, true)),
657-
658-
Some(PlaceholderOption::TrueFalse(_)) => matches!(ty, Type::Primitive(PrimitiveType::Boolean, _)),
659-
660-
None => true, // Default case: Always coercible
661-
};
662-
if !coercible {
663-
self.context
664-
.add_diagnostic(cannot_coerce_to_string(&ty, expr.span()));
643+
pub(crate) fn check_placeholder(&mut self, placeholder: &Placeholder) {
644+
self.placeholders += 1; // Track placeholder processing count
645+
646+
let expr = placeholder.expr(); // Extract expression inside placeholder
647+
648+
// Evaluate the placeholder expression and check that the resulting type is
649+
// coercible to string for interpolation
650+
if let Some(ty) = self.evaluate_expr(&expr) {
651+
match ty {
652+
// If the type is already coercible, do nothing
653+
Type::Primitive(..) | Type::Union | Type::None => {}
654+
655+
// Otherwise, check placeholder options
656+
_ => {
657+
let coercible = match placeholder.option() {
658+
// `Sep` option: Only `Array[P]` where `P` is a non-optional primitive is valid
659+
Some(PlaceholderOption::Sep(_)) => matches!(ty,
660+
Type::Compound(CompoundType::Array(ref element_type), _)
661+
if matches!(element_type.element_type(), Type::Primitive(_, false))),
662+
663+
// `Default` option: Only optional primitive types are valid
664+
Some(PlaceholderOption::Default(_)) => matches!(ty,
665+
Type::Primitive(_, true)),
666+
667+
// `TrueFalse` option: Only Boolean type is valid
668+
Some(PlaceholderOption::TrueFalse(_)) => matches!(ty,
669+
Type::Primitive(PrimitiveType::Boolean, _)),
670+
671+
// If no option is specified, allow all types
672+
None => true,
673+
};
674+
675+
// If coercion is not possible, add a diagnostic error
676+
if !coercible {
677+
self.context
678+
.add_diagnostic(cannot_coerce_to_string(&ty, expr.span()));
679+
}
665680
}
666681
}
667682
}
683+
684+
self.placeholders -= 1; // Decrement placeholder tracking
668685
}
669-
self.placeholders -= 1;
670-
}
686+
687+
671688
/// Evaluates the type of a literal array expression.
672689
fn evaluate_literal_array(&mut self, expr: &LiteralArray) -> Type {
673690
// Look at the first array element to determine the element type
Original file line numberDiff line numberDiff line change
@@ -1,6 +0,0 @@
1-
error: cannot coerce type `Array[String]` to `String`
2-
┌─ tests/analysis/cannot-coerce-string/source.wdl:10:23
3-
4-
10 │ String x = "foo ${a}"
5-
│ ^ this is type `Array[String]`
6-

wdl-analysis/tests/analysis/placeholder-options/source.diagnostics

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
version 1.2
2+
3+
task valid_case {
4+
input {
5+
Array[String] arr
6+
}
7+
command <<< ~{sep=", " arr} >>>
8+
}
9+
10+
task invalid_case {
11+
command <<< ~{sep=", " true} >>>
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
error: call to function `read_json` failed: failed to open file `attempts/0/work/not-array.json`
2+
3+
Caused by:
4+
No such file or directory (os error 2)
5+
┌─ tests/tasks/invalid-placeholder-sep/source.wdl:6:31
6+
7+
6 │ String a = "~{sep=',' read_json('not-array.json')}"
8+
│ ^^^^^^^^^
9+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
version 1.2
2+
3+
task test {
4+
command<<<>>>
5+
output {
6+
String a = "~{sep=',' read_json('not-array.json')}"
7+
}
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
error: failed to evaluate output `x` for task `write_file_task`: file `attempts/0/work/testdir/hello.txt` does not exist
2+
┌─ tests/tasks/primitive-literals/source.wdl:14:10
3+
4+
14 │ File x = "testdir/hello.txt"
5+
│ ^
6+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
error: call to function `read_string` failed: failed to read file `attempts/0/work/my/path/to/something.txt`
2+
3+
Caused by:
4+
Permission denied (os error 13)
5+
┌─ tests/tasks/relative-and-absolute-task/source.wdl:10:24
6+
7+
10 │ String something = read_string("my/path/to/something.txt")
8+
│ ^^^^^^^^^^^
9+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
error: call to function `read_string` failed: failed to read file `attempts/0/work/mydir/mydata.txt`
2+
3+
Caused by:
4+
Permission denied (os error 13)
5+
┌─ tests/tasks/test-join-paths/source.wdl:31:21
6+
7+
31 │ String result = read_string(data)
8+
│ ^^^^^^^^^^^
9+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
error: call to task `write_file_task` in `tests/workflows/primitive-literals/source.wdl` failed: failed to evaluate output `x` for task `write_file_task`: file `calls/write_file_task/attempts/0/work/testdir/hello.txt` does not exist
2+
┌─ tests/workflows/primitive-literals/source.wdl:16:8
3+
4+
16 │ call write_file_task
5+
│ ^^^^^^^^^^^^^^^
6+

0 commit comments

Comments
 (0)