Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

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

オープン 初心者向け
#602 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

評価

難易度
2/5
見積もり時間
1〜3時間
初心者へのやさしさ
72/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
python
領域
testing

調査の方向性

バグは src/mutmut/mutation/mutators.py の operator_remove_unary_ops にあります。この関数は not または ~ 式のオペランドを yield し、UnaryOperation ノードの lpar/rpar を落としています。issue 内の短い mutate_file_contents のスニペットで再現し、ミュータントに対して ast.parse が失敗することを確認してください。オペランドが外側の括弧を保持し、ミュータントがパースでき、issue の例で有効なミュータントが生成されれば完了です。

索引モデルが issue の本文から書いたものです。

説明

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

主要言語
Python
スター
1.5k
フォーク
181
平均マージ
2日 7時間
マージ済み PR(30日)
1

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

boxed/mutmut のほかの issue

boxed/mutmut の issue をすべて見る

似ている issue

Python の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。