Removing `not` from a parenthesized multi-line expression drops the parentheses (InvalidGeneratedSyntaxException, 3.8.0)
まだ誰も着手していません。
評価
調査の方向性
バグは 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
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートなし
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
boxed/mutmut のほかの issue
-
`run` with a `name__mutmut_*` pattern runs the whole test suite in the clean-test check対応中かも @Ishaan400 が 8 日前に担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
-
Invalid unary plus generated in match statement対応中かも @GhostCoder6969 が 16 日前に担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 64/100
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 65/100
noontide-co/mainbranch#1211 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
[1.x Bug] run-operation --sql reports success but rolls back writes on transaction-capable adaptersオープンtriage
難易度 2/5 1〜3時間 初心者へのやさしさ 64/100
dbt-labs/dbt#16784 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
bug needs-triage
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
メンテナーはふだん 1 日以内に返信
-
bug settings ui
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
DewaldOosthuizen/py-tray-command-launcher#162 ·
メンテナーはふだん 1 日以内に返信
-
run_all sleeps after the final job despite documenting delays between jobs対応中かも @Gambit-Checkmate が今日担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 74/100