python/cpython · error · SyntaxError

Forward reference must be an expression -- got {arg!r}

Error message

Forward reference must be an expression -- got {arg!r}

What it means

ForwardRef.__forward_code__ compiles the forward reference string with compile(..., 'eval') to build __code__. If the string is not a valid Python expression (e.g. it contains a statement like an assignment, or a lambda with ';'), the SyntaxError from compile is replaced by this clearer SyntaxError. It means the annotation string stored in a ForwardRef (typically from a quoted annotation or __annotations__ under PEP 563) cannot be evaluated later.

Source

Thrown at Lib/annotationlib.py:266

            if names:
                visitor = _ExtraNameFixer(names)
                ast_expr = ast.parse(resolved_str, mode="eval").body
                node = visitor.visit(ast_expr)
                resolved_str = ast.unparse(node)

            self.__resolved_str_cache__ = resolved_str

        return self.__resolved_str_cache__

    @property
    def __forward_code__(self):
        if self.__code__ is not None:
            return self.__code__
        arg = self.__forward_arg__
        try:
            self.__code__ = compile(_rewrite_star_unpack(arg), "<string>", "eval")
        except SyntaxError:
            raise SyntaxError(f"Forward reference must be an expression -- got {arg!r}")
        return self.__code__

    def __eq__(self, other):
        if not isinstance(other, ForwardRef):
            return NotImplemented
        return (
            self.__forward_arg__ == other.__forward_arg__
            and self.__forward_module__ == other.__forward_module__
            # Use "is" here because we use id() for this in __hash__
            # because dictionaries are not hashable.
            and self.__globals__ is other.__globals__
            and self.__forward_is_class__ == other.__forward_is_class__
            # Two separate cells are always considered unequal in forward refs.
            and (
                {name: id(cell) for name, cell in self.__cell__.items()}
                == {name: id(cell) for name, cell in other.__cell__.items()}
                if isinstance(self.__cell__, dict) and isinstance(other.__cell__, dict)
                else self.__cell__ is other.__cell__

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Fix the annotation string so it is a single valid Python expression (remove assignments, imports, semicolons).
  2. If the string comes from dynamic code, validate it first with ast.parse(s, mode='eval') and reject or repair non-expressions.
  3. Catch SyntaxError around ForwardRef.evaluate()/get_type_hints() and report which annotation string failed (the message includes the offending repr).

Example fix

// before
def f(x: "n = 10"): ...
typing.get_type_hints(f)  # SyntaxError: Forward reference must be an expression -- got 'n = 10'

// after
def f(x: "int"): ...
typing.get_type_hints(f)  # {'x': <class 'int'>}
Defensive patterns

Strategy: try-catch

Validate before calling

import ast

def is_valid_forward_ref_expr(s: str) -> bool:
    try:
        ast.parse(s, mode='eval')
        return True
    except SyntaxError:
        return False

Type guard

from annotationlib import ForwardRef

def is_forward_ref(x) -> bool:
    return isinstance(x, ForwardRef)

Try / catch

from annotationlib import ForwardRef

try:
    value = ref.evaluate()
except SyntaxError as e:
    # e.g. log the offending annotation and skip it
    print(f'skipping bad annotation: {e}')

Prevention

When it happens

Trigger: ForwardRef('x = 1').__forward_code__; ForwardRef('import os'); ForwardRef('x; y'); a quoted annotation like def f(x: "a = b") evaluated via ForwardRef.evaluate() or typing.get_type_hints(); annotations produced by broken source generation or manual string building.

Common situations: Hand-written string annotations containing statements instead of expressions; code generators emitting malformed annotation strings; get_type_hints() on modules whose annotations were tampered with; typo in a quoted annotation such as a stray '='.

Related errors


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/0108ed6e7340aa8c. Report an issue: GitHub.