Skip to content

Commit df6e6fd

Browse files
committed
Filter mutants with type checking
1 parent bf3a2e8 commit df6e6fd

16 files changed

Lines changed: 685 additions & 241 deletions

README.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,26 @@ If you only want to mutate lines that are called (according to coverage.py), you
167167
mutate_only_covered_lines=true
168168
169169
170+
Filter generated mutants with type checker
171+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
172+
173+
When your project is type checked, you can also use it to filter out invalid mutants.
174+
For instance, mutmut mutates `x: str = 'foo'` to `x: str = None` which can easily caught by type checkers.
175+
176+
To enable this filtering, configure the `type_check_command` to output json results as follows:
177+
178+
.. code-block::
179+
180+
# for pyrefly
181+
type_check_command = ['pyrefly', 'check', '--output-format=json']
182+
# for mypy
183+
type_check_command = ['mypy', 'traces_parser', '--output', 'json']
184+
185+
Currently, only `pyrefly` and `mypy` are supported.
186+
With `pyright` and `ty`, mutating a class method `Foo.bar()` can break the types of all methods of `Foo`,
187+
and therefore mutmut cannot match the type error with the mutant that caused the type error.
188+
189+
170190
Enable debug output (increase verbosity)
171191
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
172192

e2e_projects/type_checking/.gitignore

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This project uses type checking to detect invalid mutants.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
[project]
2+
name = "type-checking"
3+
version = "0.1.0"
4+
description = "Add your description here"
5+
readme = "README.md"
6+
authors = []
7+
requires-python = ">=3.10"
8+
dependencies = []
9+
10+
[build-system]
11+
requires = ["uv_build>=0.9.18,<0.10.0"]
12+
build-backend = "uv_build"
13+
14+
[dependency-groups]
15+
dev = [
16+
"pyrefly>=0.52.0",
17+
"pyright>=1.1.408",
18+
"pytest>=8.2.0",
19+
]
20+
21+
[tool.mutmut]
22+
debug = true
23+
type_check_command = ["pyrefly", "check", "--output-format=json"]
24+
25+
[tool.pyrefly]
26+
project-includes = [
27+
"**/*.py*",
28+
"**/*.ipynb",
29+
]
30+
31+
[tool.pyright]
32+
typeCheckingMode = "strict"
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
def hello() -> str:
2+
greeting: str = "Hello from type-checking!"
3+
return greeting
4+
5+
def a_hello_wrapper() -> str:
6+
# verify that hello() keeps the return type str
7+
# (if not, this will type error and not be mutated)
8+
return hello() + "2"
9+
10+
class Person:
11+
def set_name(self, name: str):
12+
self.name = name
13+
14+
def get_name(self):
15+
# return type should be inferred as "str"
16+
return self.name
17+
18+
def mutate_me():
19+
p = Person()
20+
p.set_name('charlie')
21+
# Verify that p.get_name keeps the return type str
22+
name: str = p.get_name()
23+
return name
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from type_checking import *
2+
3+
def test_hello():
4+
assert hello() == "Hello from type-checking!"
5+
6+
def test_a_hello_wrapper():
7+
assert isinstance(a_hello_wrapper(), str)
8+
9+
def test_mutate_me():
10+
assert mutate_me() == "charlie"

e2e_projects/type_checking/uv.lock

Lines changed: 200 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ source-include = ["HISTORY.rst"]
5454
[dependency-groups]
5555
dev = [
5656
"inline-snapshot>=0.32.0",
57+
"pyrefly>=0.52.0",
58+
"pyright>=1.1.408",
5759
"pytest-asyncio>=1.0.0",
5860
"ruff>=0.15.1",
5961
]

0 commit comments

Comments
 (0)