astral-sh/ruff · error · ValueError

`?` must be at the end: {rule}

Error message

`?` must be at the end: {rule}

What it means

A trailing '?' marks an optional field (generated as Option<T>). FieldType.__init__ accepts exactly one '?' and only at the end of the rule; shapes like '?Expr', 'Expr??', or 'Vec<X>?' with the marker misplaced raise this ValueError.

Source

Thrown at crates/ruff_python_ast/generate.py:274

            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:
                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

View on GitHub (pinned to d1087a4b9e)

Solutions

  1. Place a single '?' at the very end: 'Expr?'.
  2. Remove any duplicate '?' characters.
  3. Re-run the generator to confirm parsing succeeds.

Example fix

# before
value: ?Expr

# after
value: Expr?
Defensive patterns

Strategy: validation

Validate before calling

def rule_ok(rule: str) -> bool:
    return '?' not in rule or (rule.endswith('?') and rule.count('?') == 1)

Prevention

When it happens

Trigger: Adding an optional field where the '?' is not the last character or appears more than once, then running the AST generator.

Common situations: Copy-paste typos; prefix-style optional markers borrowed from regex-like DSLs; editing rules in a hurry without the shapes cheatsheet.

Related errors


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