Skip to content

Fix: Avoid ast.Name for True/False/None in mark expressions (debug build crash) #27

@rowan-stein

Description

@rowan-stein

User Request

Pytest crashes the interpreter on Python debug builds for 3.8+ when compiling mark expressions that contain certain identifiers.

Short reproducer:

>>> Expression.compile("False")
python: Python/compile.c:3559: compiler_nameop: Assertion `!_PyUnicode_EqualToASCIIString(name, "None") && !_PyUnicode_EqualToASCIIString(name, "True") && !_PyUnicode_EqualToASCIIString(name, "False")' failed.
[1]    29440 abort (core dumped)  python

Related upstream: bpo-40870 — https://bugs.python.org/issue40870

Researcher Specification

Root cause: mark expression parsing builds an AST using ast.Name nodes for identifiers, including the special names "True", "False", and "None". On CPython debug builds (3.8+), compiling such an AST triggers an assertion in compiler_nameop because these are meant to be literal constants, not names.

Proposed fix:

  • Add _ident_to_name(name: str) -> ast.expr in src/_pytest/compat.py that:
    • For "True"/"False"/"None": returns a literal constant node — ast.Constant on Python >= 3.8; ast.NameConstant on Python < 3.8.
    • For any other identifier: returns ast.Name(name, ast.Load()).
  • Update src/_pytest/mark/expression.py to:
    • Use _ident_to_name("False") as the default for an empty expression (instead of ast.NameConstant(False)).
    • Use _ident_to_name(ident.value) wherever identifiers are turned into AST nodes (instead of ast.Name(...)).

Compatibility notes:

  • Aligns literals with Python semantics across versions (3.4–3.7 use ast.NameConstant; 3.8+ use ast.Constant).
  • Slight behavior change: exact identifiers "True", "False", and "None" now behave as literals and are not resolved through the matcher. Lowercase variants remain identifiers.

Observed Failure / Stack Trace

  • As above, CPython debug build (3.8+) assertion failure in compiler_nameop when compiling an expression containing "False" (applies similarly to "True"/"None").

Reproduction Steps

  1. Use a CPython debug build (3.8+): python-dbg or equivalent.
  2. Run:
from _pytest.mark.expression import Expression
Expression.compile("False")
  1. Prior behavior: crashes with an assertion as shown above.
  2. Expected behavior after fix: compiles cleanly; evaluating should yield False.

Testing Plan

  • Add tests asserting that "True"/"False"/"None" are treated as literals and not looked up in the matcher.
  • Ensure lowercase variants (e.g., "true") remain identifiers.
  • Optional: version-aware structural checks to confirm AST node types (ast.Constant vs ast.NameConstant).

Links

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions