python/cpython · error · ValueError

Unsupported major version: {major}

Error message

Unsupported major version: {major}

What it means

ast.parse accepts feature_version to select the grammar of an older Python 3.x; when it is given as a tuple, only (3, minor) is accepted — any other major version (2, 4, ...) raises ValueError immediately. The value is then reduced to the minor int and forwarded to compile() via _feature_version, so passing a bare non-3 tuple never reaches the compiler.

Source

Thrown at Lib/ast.py:43

def parse(source, filename='<unknown>', mode='exec', *,
          type_comments=False, feature_version=None, optimize=-1, module=None):
    """
    Parse the source into an AST node.
    Equivalent to compile(source, filename, mode, PyCF_ONLY_AST).
    Pass type_comments=True to get back type comments where the syntax allows.
    """
    flags = PyCF_ONLY_AST
    if optimize > 0:
        flags |= PyCF_OPTIMIZED_AST
    if type_comments:
        flags |= PyCF_TYPE_COMMENTS
    if feature_version is None:
        feature_version = -1
    elif isinstance(feature_version, tuple):
        major, minor = feature_version  # Should be a 2-tuple.
        if major != 3:
            raise ValueError(f"Unsupported major version: {major}")
        feature_version = minor
    # Else it should be an int giving the minor version for 3.x.
    return compile(source, filename, mode, flags,
                   _feature_version=feature_version, optimize=optimize,
                   module=module)


def literal_eval(node_or_string):
    """
    Evaluate an expression node or a string containing only a Python
    expression.  The string or node provided may only consist of the following
    Python literal structures: strings, bytes, numbers, tuples, lists, dicts,
    sets, booleans, and None.

    Caution: A complex expression can overflow the C stack and cause a crash.
    """
    if isinstance(node_or_string, str):
        node_or_string = parse(node_or_string.lstrip(" \t"), mode='eval').body

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Use a 3-tuple: feature_version=(3, 9), or pass the minor int directly (feature_version=9), or None for the running interpreter's grammar.
  2. For Python 2 sources use a dedicated tool (e.g. lib2to3-based or external parsers); ast.parse cannot parse Python 2 syntax under any feature_version.
  3. Validate external version inputs before calling ast.parse: require major == 3.

Example fix

# before
ast.parse(src, feature_version=(2, 7))  # ValueError

# after
ast.parse(src, feature_version=(3, 7))  # or feature_version=7 / None
Defensive patterns

Strategy: validation

Validate before calling

def feature_version_of(v):
    if v is None:
        return None
    if isinstance(v, int):
        return v
    major, minor = v
    if major != 3:
        raise ValueError(f'only Python 3.x grammars supported, got {v!r}')
    return minor

ast.parse(src, feature_version=feature_version_of(cfg_version))

Try / catch

try:
    tree = ast.parse(src, feature_version=tuple(map(int, ver.split('.'))))
except ValueError as e:
    if 'Unsupported major version' in str(e):
        tree = ast.parse(src)  # fall back to the running interpreter's grammar

Prevention

When it happens

Trigger: ast.parse(src, feature_version=(2, 7)) to 'parse Python 2 code'; passing a full sys.version_info-like tuple whose first element is not 3; code that builds the tuple from a config file where the major version is user-controlled.

Common situations: Linters/formatters (ast is their backbone) trying to support legacy Python 2 sources; tools that accept a 'python_version' setting and pass it through unvalidated; confusion because feature_version expects (3, minor) or just the minor int, not (major, minor) generally.

Related errors


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