[Bugfix] Let an optional Literal flag accept the None it advertises (#52370)

Signed-off-by: Vineeth Sai <vineethsai4444@gmail.com> Signed-off-by: Harry Mellor <19981378+hmellor@users.noreply.github.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Harry Mellor <19981378+hmellor@users.noreply.github.com>

Vineeth Sai Varikuntla committed Sep 16, 2026 at 05:14 UTC 903285fbcc4e51332d562499bd90ec9a1513c813
2 files changed +25 -6
tests/engine/test_arg_utils.py
+23 -4
@@ -254,10 +254,13 @@ def test_get_type_hints(type_hint, expected):
254 assert get_type_hints(type_hint) == expected
255
256
257 -def test_get_kwargs():
258 - kwargs = get_kwargs(DummyConfig)
259 - print(kwargs)
257 +@pytest.fixture
258 +def dummy_config_kwargs():
259 + return get_kwargs(DummyConfig)
260
261 +
262 +def test_get_kwargs(dummy_config_kwargs):
263 + kwargs = dummy_config_kwargs
264 # bools should not have their type set
265 assert kwargs["regular_bool"].get("type") is None
266 assert kwargs["optional_bool"].get("type") is None
@@ -267,7 +270,7 @@ def test_get_kwargs():
270 assert kwargs["optional_bool_or_str"]["const"] is True
271 assert "action" not in kwargs["optional_bool_or_str"]
272 # optional literals should have None as a choice
270 - assert kwargs["optional_literal"]["choices"] == ["x", "y", "None"]
273 + assert kwargs["optional_literal"]["choices"] == ["x", "y", None]
274 # tuples should have the correct nargs
275 assert kwargs["tuple_n"]["nargs"] == "+"
276 assert kwargs["tuple_2"]["nargs"] == 2
@@ -293,6 +296,22 @@ def test_get_kwargs():
296 assert kwargs["nested_config"]["type"]('{"field": 2}') == NestedConfig(2) # type: ignore[call-arg]
297
298
299 +@pytest.mark.parametrize(
300 + ("args", "expected"),
301 + [
302 + (["--optional-literal", "None"], None),
303 + (["--optional-literal", ""], None),
304 + (["--optional-literal", "x"], "x"),
305 + ],
306 +)
307 +def test_optional_handling(args, expected, dummy_config_kwargs):
308 + parser = FlexibleArgumentParser()
309 + parser.add_argument("--optional-literal", **dummy_config_kwargs["optional_literal"])
310 +
311 + assert parser.parse_args(args).optional_literal is expected
312 + assert "None" in parser.format_help()
313 +
314 +
315 def test_jit_monitor_verbose_arg():
316 parser = EngineArgs.add_cli_args(FlexibleArgumentParser())
317 args = parser.parse_args(["--jit-monitor-verbose"])
vllm/engine/arg_utils.py
+2 -2
@@ -411,7 +411,7 @@ def _compute_kwargs(cls: ConfigType) -> dict[str, dict[str, Any]]:
411 if type(None) in type_hints and not contains_type(type_hints, bool):
412 kwargs[name]["type"] = optional_type(kwargs[name]["type"])
413 if kwargs[name].get("choices"):
414 - kwargs[name]["choices"].append("None")
414 + kwargs[name]["choices"].append(None)
415 return kwargs
416
417
@@ -1526,7 +1526,7 @@ class EngineArgs:
1526 )
1527 # TODO: generalise this special case
1528 choices = observability_kwargs["collect_detailed_traces"]["choices"]
1529 - metavar = f"{{{','.join(choices)}}}"
1529 + metavar = f"{{{','.join(str(c) for c in choices)}}}"
1530 observability_kwargs["collect_detailed_traces"]["metavar"] = metavar
1531 observability_kwargs["collect_detailed_traces"]["choices"] += [
1532 ",".join(p) for p in permutations(get_args(DetailedTraceModules), r=2)