{"record":{"id":"5964b76f9355b2fc","repo":"astral-sh/ruff","slug":"unclosed-collection-type-rule","errorCode":null,"errorMessage":"Unclosed collection type: {rule}","messagePattern":"Unclosed collection type: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"crates/ruff_python_ast/generate.py","lineNumber":255,"sourceCode":"\n\ndef split_sequence_type(rule: str) -> tuple[SequenceKind | None, str]:\n    if \"&\" in rule:\n        raise ValueError(f\"`&T*` is unsupported; use `Box<[T]>`: {rule}\")\n\n    if \"*\" in rule:\n        if rule.endswith(\"*\") and rule.count(\"*\") == 1:\n            return SequenceKind.VEC, rule[:-1]\n        raise ValueError(f\"`*` must be at the end: {rule}\")\n\n    for prefix, suffix, sequence_kind in (\n        (\"Vec<\", \">\", SequenceKind.VEC),\n        (\"ThinVec<\", \">\", SequenceKind.THIN_VEC),\n        (\"Box<[\", \"]>\", SequenceKind.BOXED_SLICE),\n    ):\n        if rule.startswith(prefix):\n            if not rule.endswith(suffix):\n                raise ValueError(f\"Unclosed collection type: {rule}\")\n            return sequence_kind, rule[len(prefix) : -len(suffix)]\n\n    return None, rule\n\n\n@dataclass\nclass FieldType:\n    rule: str\n    name: str\n    inner: str\n    sequence_kind: SequenceKind | None = None\n    optional: bool = False\n\n    def __init__(self, rule: str) -> None:\n        self.rule = rule\n        self.optional = False\n        if \"?\" in rule:\n            if not rule.endswith(\"?\") or rule.count(\"?\") != 1:","sourceCodeStart":237,"sourceCodeEnd":273,"githubUrl":"https://github.com/astral-sh/ruff/blob/d1087a4b9e03d253a88703f34e0869ee4b805456/crates/ruff_python_ast/generate.py#L237-L273","documentation":"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.","triggerScenarios":"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.","commonSituations":"Hand-writing Box<[T]> rules for the first time; converting a Vec<T> rule to a boxed slice and forgetting to change the suffix.","solutions":["Close Vec<...> and ThinVec<...> with '>', and Box<[...> with ']>'.","Re-run the generator and check the produced Rust type matches your intent.","Run cargo check -p ruff_python_ast after regeneration."],"exampleFix":"# before\nbody: Box<[Stmt>\n\n# after\nbody: Box<[Stmt]>","handlingStrategy":"validation","validationCode":"COLLECTIONS = (('Vec<', '>'), ('ThinVec<', '>'), ('Box<[', ']>'))\n\n\ndef rule_ok(rule: str) -> bool:\n    return all(\n        not rule.startswith(p) or rule.endswith(s)\n        for p, s in COLLECTIONS\n    )","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Box<[T]> closes with ']>', not '>'.","Regenerate immediately after editing collection rules to catch typos early."],"tags":["codegen","rust-ast","syntax","developer-tooling"],"backgroundTag":"codegen-dsl-parse-error","analyzedSha":"d1087a4b9e03d253a88703f34e0869ee4b805456","analyzedAt":"2026-08-20T16:33:49.445Z","contentChangedAt":"2026-08-20T16:33:49.445Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}