astral-sh/ruff · error · ValueError

Unclosed collection type: {rule}

Error message

Unclosed collection type: {rule}

What it means

When an AST field rule starts with a known collection prefix (Vec<, ThinVec<, Box<[), split_sequence_type() requires the matching suffix ('>' for Vec/ThinVec, ']>' for Box<[). A rule like 'Vec<Stmt' or 'Box<[Token>' fails this check and raises ValueError.

Source

Thrown at crates/ruff_python_ast/generate.py:255


def split_sequence_type(rule: str) -> tuple[SequenceKind | None, str]:
    if "&" in rule:
        raise ValueError(f"`&T*` is unsupported; use `Box<[T]>`: {rule}")

    if "*" in rule:
        if rule.endswith("*") and rule.count("*") == 1:
            return SequenceKind.VEC, rule[:-1]
        raise ValueError(f"`*` must be at the end: {rule}")

    for prefix, suffix, sequence_kind in (
        ("Vec<", ">", SequenceKind.VEC),
        ("ThinVec<", ">", SequenceKind.THIN_VEC),
        ("Box<[", "]>", SequenceKind.BOXED_SLICE),
    ):
        if rule.startswith(prefix):
            if not rule.endswith(suffix):
                raise ValueError(f"Unclosed collection type: {rule}")
            return sequence_kind, rule[len(prefix) : -len(suffix)]

    return None, rule


@dataclass
class FieldType:
    rule: str
    name: str
    inner: str
    sequence_kind: SequenceKind | None = None
    optional: bool = False

    def __init__(self, rule: str) -> None:
        self.rule = rule
        self.optional = False
        if "?" in rule:
            if not rule.endswith("?") or rule.count("?") != 1:

View on GitHub (pinned to d1087a4b9e)

Solutions

  1. Close Vec<...> and ThinVec<...> with '>', and Box<[...> with ']>'.
  2. Re-run the generator and check the produced Rust type matches your intent.
  3. Run cargo check -p ruff_python_ast after regeneration.

Example fix

# before
body: Box<[Stmt>

# after
body: Box<[Stmt]>
Defensive patterns

Strategy: validation

Validate before calling

COLLECTIONS = (('Vec<', '>'), ('ThinVec<', '>'), ('Box<[', ']>'))


def rule_ok(rule: str) -> bool:
    return all(
        not rule.startswith(p) or rule.endswith(s)
        for p, s in COLLECTIONS
    )

Prevention

When it happens

Trigger: Writing a collection rule whose closing suffix is missing or wrong (e.g. closing a Box<[...> slice with '>' instead of ']>', or omitting the '>'), then running generate.py.

Common situations: Hand-writing Box<[T]> rules for the first time; converting a Vec<T> rule to a boxed slice and forgetting to change the suffix.

Related errors


AI-assisted analysis of astral-sh/ruff@d1087a4b9e (2026-08-20). Data as JSON: /api/errors/5964b76f9355b2fc. Report an issue: GitHub.