Skip to content

Commit 64ea795

Browse files
authored
Fix stream double-consumption in CommandSpecParser (#1448)
The `choices` stream was consumed eagerly for metavar construction, then captured in a lambda for later validation—which promptly fell over with `IllegalStateException`. Materialise to a `List` straightaway.
1 parent cac3e48 commit 64ea795

File tree

2 files changed

+30
-4
lines changed

2 files changed

+30
-4
lines changed

pkl-core/src/main/java/org/pkl/core/runtime/CommandSpecParser.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
import java.util.function.BiFunction;
3333
import java.util.function.Function;
3434
import java.util.function.Supplier;
35-
import java.util.stream.Collectors;
3635
import org.graalvm.collections.EconomicMap;
3736
import org.pkl.core.CommandSpec;
3837
import org.pkl.core.CommandSpec.Argument;
@@ -771,18 +770,18 @@ private boolean resolvePrimitive(TypeNode typeNode) {
771770
return true;
772771
} else if (typeNode
773772
instanceof TypeNode.UnionOfStringLiteralsTypeNode unionOfStringLiteralsTypeNode) {
774-
var choices = unionOfStringLiteralsTypeNode.getStringLiterals().stream().sorted();
773+
var choices = unionOfStringLiteralsTypeNode.getStringLiterals().stream().sorted().toList();
775774
if (each == null)
776775
each =
777776
(rawValue, workingDirUri) -> {
778777
if (!unionOfStringLiteralsTypeNode.getStringLiterals().contains(rawValue)) {
779-
throw BadValue.invalidChoice(rawValue, choices.toList());
778+
throw BadValue.invalidChoice(rawValue, choices);
780779
}
781780
return rawValue;
782781
};
783782
if (all == null) all = this::allChooseLast;
784783
if (multiple == null) multiple = false;
785-
if (metavar == null) metavar = "[" + choices.collect(Collectors.joining(", ")) + "]";
784+
if (metavar == null) metavar = "[" + String.join(", ", choices) + "]";
786785
if (completionCandidates == null)
787786
completionCandidates = new Fixed(unionOfStringLiteralsTypeNode.getStringLiterals());
788787
return true;

pkl-core/src/test/kotlin/org/pkl/core/runtime/CommandSpecParserTest.kt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -787,4 +787,31 @@ class CommandSpecParserTest {
787787
.contains("Option `foo` with annotation `@CountedFlag` has invalid type `String`.")
788788
assertThat(exc.message).contains("Expected type: `Int`")
789789
}
790+
791+
@Test
792+
fun `union typed option validates invalid choice without stream error`() {
793+
val moduleUri =
794+
writePklFile(
795+
"cmd.pkl",
796+
renderOptions +
797+
"""
798+
class Options {
799+
format: "json" | "yaml" | "toml"
800+
}
801+
"""
802+
.trimIndent(),
803+
)
804+
805+
val spec = parse(moduleUri)
806+
val flag = spec.options.first() as CommandSpec.Flag
807+
808+
assertThat(flag.metavar()).isEqualTo("[json, toml, yaml]")
809+
810+
val apply =
811+
assertThrows<CommandSpec.Option.BadValue> {
812+
flag.transformEach().apply("xml", URI("file:///tmp"))
813+
}
814+
assertThat(apply.message).contains("invalid choice")
815+
assertThat(apply.message).contains("xml")
816+
}
790817
}

0 commit comments

Comments
 (0)