Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

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

Open Beginner friendly
#602 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
72/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
python
Domain
testing

Research direction

The bug is in operator_remove_unary_ops in src/mutmut/mutation/mutators.py, which yields the operand of a not or ~ expression and drops the lpar/rpar on the UnaryOperation node. Reproduce with the short mutate_file_contents snippet in the issue and check that ast.parse fails on the mutant. Done when the operand keeps the outer parentheses, the mutant parses, and the example in the issue produces valid mutants.

Written by the indexing model from the issue text.

Description

🤖 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 ...").

Dominant language
Python
Stars
1.5k
Forks
181
PR merge metrics
No merged PRs in 30d

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from boxed/mutmut

All issues in boxed/mutmut

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.