tyro icon indicating copy to clipboard operation
tyro copied to clipboard

[Feature] Enable --flag / --no-flag syntax for DisallowNone[bool | None]

Open emcd opened this issue 2 months ago • 2 comments

When using DisallowNone[bool | None] for boolean fields with None defaults, tyro currently generates choice-based syntax ({True,False}) instead of the standard boolean flag syntax (--flag / --no-flag). This creates a UX inconsistency where plain bool fields get natural flag syntax, but nullable booleans with DisallowNone require explicit values.

Current Behavior

from dataclasses import dataclass
from typing import Annotated
import tyro

@dataclass
class Config:
    # Plain bool: gets --flag / --no-flag syntax
    plain: bool = False

    # DisallowNone bool: requires explicit True/False
    configurable: Annotated[
        tyro.conf.DisallowNone[bool | None],
        tyro.conf.arg(aliases=('--cfg',)),
    ] = None

tyro.cli(Config)

Help output:

--flag, --no-flag, --plain, --no-plain
    Description (default: False)
--cfg {True,False}, --configurable {True,False}
    Description (default: None)

Required usage:

./script.py --configurable True   # verbose, non-standard

Desired Behavior

Since DisallowNone prevents users from explicitly passing None via the CLI, the type effectively becomes a two-state boolean from the CLI's perspective (even though None remains valid as a default value). It would be more intuitive if tyro treated this as equivalent to a plain bool for CLI syntax purposes:

Desired help output:

--cfg, --no-cfg, --configurable, --no-configurable
    Description (default: None)

Desired usage:

./script.py --configurable        # sets to True
./script.py --no-configurable     # sets to False
./script.py                       # uses None default

Use Case

This pattern is particularly valuable for CLIs with configurable config file locations (e.g., --configfile). You need three-state logic:

  • None = "consult the config file" (default)
  • True/False = "override the config file value"

But you can't load config defaults before parsing CLI args (chicken-and-egg problem with --configfile). DisallowNone[bool | None] currently solves the semantic problem but creates a UX friction point.

Related Issues

This relates to #254 (detecting provided vs default values), but represents a simpler, more targeted enhancement that could be implemented independently. While #254 would provide a more general solution, this feature would address the specific UX pain point for boolean flags in the meantime.


Co-Authored-By: Claude [email protected]

emcd avatar Nov 11 '25 04:11 emcd

Just made a PR with a fix, thanks @emcd!

brentyi avatar Nov 22 '25 10:11 brentyi

Awesome! Thank you, @brentyi . Yet another iteration towards convergence on CLI generator perfection.

emcd avatar Nov 22 '25 18:11 emcd