astral-sh/ruff · error · ValueError

optional field cannot be sequence or slice: {self.rule}

Error message

optional field cannot be sequence or slice: {self.rule}

What it means

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.

Source

Thrown at crates/ruff_python_ast/generate.py:280

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:
                raise ValueError(f"`?` must be at the end: {rule}")
            self.optional = True
            rule = rule[:-1]

        self.sequence_kind, self.name = split_sequence_type(rule)
        if self.optional and self.sequence_kind is not None:
            raise ValueError(f"optional field cannot be sequence or slice: {self.rule}")
        if self.sequence_kind is not None and (
            not self.name or any(ch in self.name for ch in "?*&[]<>")
        ):
            raise ValueError(f"Invalid collection element type: {rule}")

        self.inner = extract_type_argument(self.name)


# ------------------------------------------------------------------------------
# Preamble


def write_preamble(out: list[str]) -> None:
    out.append("""
    // This is a generated file. Don't modify it by hand!
    // Run `crates/ruff_python_ast/generate.py` to re-generate the file.

    use crate::name::Name;

View on GitHub (pinned to d1087a4b9e)

Solutions

  1. Drop the '?': sequences are inherently empty-able, so 'Box<[T]>' or 'Vec<T>' (defaulting to empty) is the intended modeling.
  2. If absence vs. emptiness genuinely differs, restructure the node (e.g. a wrapper node or an enum) instead of an optional sequence.
  3. Re-run the generator after fixing the rule.

Example fix

# before
docstring: Box<[str]>?

# after (sequences cannot be optional; empty means absent)
docstring: Box<[str]>
Defensive patterns

Strategy: validation

Validate before calling

SEQUENCE_PREFIXES = ('Vec<', 'ThinVec<', 'Box<[')


def rule_ok(rule: str) -> bool:
    base = rule.rstrip('?')
    return not (rule.endswith('?') and base.startswith(SEQUENCE_PREFIXES))

Prevention

When it happens

Trigger: Writing a rule such as 'Stmt*?' or 'Vec<Expr>?' — a trailing '?' on a rule that split_sequence_type() classifies as a sequence.

Common situations: 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.

Related errors


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