{"record":{"id":"6cbbf36796e1065c","repo":"astral-sh/ruff","slug":"optional-field-cannot-be-sequence-or-slice-self","errorCode":null,"errorMessage":"optional field cannot be sequence or slice: {self.rule}","messagePattern":"optional field cannot be sequence or slice: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"crates/ruff_python_ast/generate.py","lineNumber":280,"sourceCode":"class 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:\n                raise ValueError(f\"`?` must be at the end: {rule}\")\n            self.optional = True\n            rule = rule[:-1]\n\n        self.sequence_kind, self.name = split_sequence_type(rule)\n        if self.optional and self.sequence_kind is not None:\n            raise ValueError(f\"optional field cannot be sequence or slice: {self.rule}\")\n        if self.sequence_kind is not None and (\n            not self.name or any(ch in self.name for ch in \"?*&[]<>\")\n        ):\n            raise ValueError(f\"Invalid collection element type: {rule}\")\n\n        self.inner = extract_type_argument(self.name)\n\n\n# ------------------------------------------------------------------------------\n# Preamble\n\n\ndef write_preamble(out: list[str]) -> None:\n    out.append(\"\"\"\n    // This is a generated file. Don't modify it by hand!\n    // Run `crates/ruff_python_ast/generate.py` to re-generate the file.\n\n    use crate::name::Name;","sourceCodeStart":262,"sourceCodeEnd":298,"githubUrl":"https://github.com/astral-sh/ruff/blob/d1087a4b9e03d253a88703f34e0869ee4b805456/crates/ruff_python_ast/generate.py#L262-L298","documentation":"Optionality and sequences are mutually exclusive in the generated AST. FieldType.__init__ rejects rules that combine a trailing '?' with any collection form (T*, Vec<T>, ThinVec<T>, Box<[T]>), because the generated accessors, visitors and constructors have no representation for an optional sequence.","triggerScenarios":"Writing a rule such as 'Stmt*?' or 'Vec<Expr>?' — a trailing '?' on a rule that split_sequence_type() classifies as a sequence.","commonSituations":"Trying to model 'maybe-empty-or-absent' lists; porting hand-written structs that used Option<Vec<...>>; making a formerly-optional field a list during a grammar change.","solutions":["Drop the '?': sequences are inherently empty-able, so 'Box<[T]>' or 'Vec<T>' (defaulting to empty) is the intended modeling.","If absence vs. emptiness genuinely differs, restructure the node (e.g. a wrapper node or an enum) instead of an optional sequence.","Re-run the generator after fixing the rule."],"exampleFix":"# before\ndocstring: Box<[str]>?\n\n# after (sequences cannot be optional; empty means absent)\ndocstring: Box<[str]>","handlingStrategy":"validation","validationCode":"SEQUENCE_PREFIXES = ('Vec<', 'ThinVec<', 'Box<[')\n\n\ndef rule_ok(rule: str) -> bool:\n    base = rule.rstrip('?')\n    return not (rule.endswith('?') and base.startswith(SEQUENCE_PREFIXES))","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Model 'maybe empty' with an empty sequence, not Option<Vec<...>>.","If absence must differ from emptiness, restructure the node instead of fighting the DSL."],"tags":["codegen","rust-ast","optional-fields","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-14T00:17:10.932Z"}