Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

Removing `not` from a parenthesized multi-line expression drops the parentheses (InvalidGeneratedSyntaxException, 3.8.0)

Abierto Apto para principiantes
#602 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
2/5
Tiempo estimado
1-3 horas
Aptitud para principiantes
72/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
python
Área
testing

Línea de trabajo

El error está en operator_remove_unary_ops en src/mutmut/mutation/mutators.py, que devuelve el operando de una expresión not o ~ y descarta los lpar/rpar del nodo UnaryOperation. Reproduce el problema con el fragmento corto de mutate_file_contents del issue y comprueba que ast.parse falla en el mutante. Está terminado cuando el operando conserva los paréntesis externos, el mutante se puede parsear y el ejemplo del issue produce mutantes válidos.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

🤖 Written by Claude (AI assistant). The reproduction below was run on a clean environment.

Summary

operator_remove_unary_ops replaces a not / ~ expression with its operand. In libcst, the parentheses that wrap the whole unary expression belong to the UnaryOperation node (lpar / rpar), so they are dropped together with the operator. When the expression spans several lines (which is how black and ruff format a long boolean return), the mutant is invalid Python and mutmut run aborts with InvalidGeneratedSyntaxException, so no mutant of the run gets tested.

Reproduction (mutmut 3.8.0, Python 3.12)

mylib/core.py:

def is_not_before(a, b):
    return (
        not a
        < b
    )

tests/test_core.py:

from mylib.core import is_not_before


def test_is_not_before():
    assert is_not_before(2, 1) is True
    assert is_not_before(1, 2) is False

pyproject.toml:

[tool.mutmut]
source_paths = ["mylib/"]
pytest_add_cli_args_test_selection = ["tests/"]

mutmut run:

mutmut.__main__.InvalidGeneratedSyntaxException: Mutmut generated invalid python syntax for Mutmut generated invalid python syntax for mutants/mylib/core.py. If the original file has valid python syntax, please file an issue with a minimal reproducible example file.. If the original file has valid python syntax, please file an issue with a minimal reproducible example file.

It also reproduces without running any test:

import ast
from mutmut.mutation.file_mutation import mutate_file_contents

mf = mutate_file_contents("mylib/core.py", open("mylib/core.py").read())
ast.parse(mf.code)  # SyntaxError: unexpected indent

The generated mutant:

def x_is_not_before__mutmut_1(a, b):
    return a
        < b

Cause

src/mutmut/mutation/mutators.py (same on main):

def operator_remove_unary_ops(node: cst.UnaryOperation) -> Iterable[cst.BaseExpression]:
    if isinstance(node.operator, cst.Not | cst.BitInvert):
        yield node.expression

In (not a\n < b), the parentheses are node.lpar / node.rpar, and node.expression is the Comparison without them.

Suggested fix

Carry the parentheses over to the operand:

def operator_remove_unary_ops(node: cst.UnaryOperation) -> Iterable[cst.BaseExpression]:
    if isinstance(node.operator, cst.Not | cst.BitInvert):
        yield node.expression.with_changes(
            lpar=[*node.lpar, *node.expression.lpar],
            rpar=[*node.expression.rpar, *node.rpar],
        )

With this change, the example above generates 2 valid mutants (1 killed, 1 survived), and mutmut show mylib.core.x_is_not_before__mutmut_1 gives:

 def is_not_before(a, b):
     return (
-        not a
+        a
         < b
     )

Side note: the exception message is printed twice ("Mutmut generated invalid python syntax for Mutmut generated invalid python syntax for ...").

Lenguaje dominante
Python
Estrellas
1.5k
Forks
181
Métricas de merge de PR
Sin PR fusionados en 30 d

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de boxed/mutmut

Todos los issues de boxed/mutmut

Issues similares

Más issues de Python

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.