Repository navigation
Conversation
Mutating a method of a Protocol adds the generated mutant methods (xǁFooǁbar__mutmut_orig, xǁFooǁbar__mutmut_1, ...) as members of the protocol. Classes that implement it structurally then no longer conform, so the type checker reports an error wherever one is used as the protocol. An error in an original method body crashes the type check filter with "Could not find mutant for type error"; an error inside a mutant function discards that unrelated mutant as caught by the type checker. Method stubs are never executed either, so mutants of their default arguments could not be killed. Skip any class that lists typing.Protocol or typing_extensions.Protocol among its bases, plain or subscripted. The base is resolved through the module's imports (aliases and star imports included), so other classes named Protocol, such as asyncio.Protocol or Twisted's, are still mutated, and so is a subclass of a protocol that does not list Protocol itself. Fixes boxed#590
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #590.
Mutating a method of a
Protocoladds the generated mutant methods (xǁSourceǁread__mutmut_orig,xǁSourceǁread__mutmut_1) as members of the protocol. Classes that implement it structurally then no longer conform, so the type checker reports an error wherever one is used as the protocol. An error in an original method body crashesfilter_mutants_with_type_checkerwith "Could not find mutant for type error"; an error inside a mutant function discards that unrelated mutant as caught by the type checker.This skips any class that lists
typing.Protocolortyping_extensions.Protocolamong its bases, plain or subscripted (Protocol[T]), decorated with@runtime_checkableor not.How the base is recognised. Matching the name
Protocolalone would also skipasyncio.Protocoland Twisted'sProtocol, which are ordinary base classes with real methods. libcst'sQualifiedNameProviderresolves this correctly but madecreate_mutationsabout 65% slower on the larger files in this repository. So the visitor records howtyping,typing_extensionsand theirProtocolare imported (aliases, star imports and imports underif TYPE_CHECKING:included) and resolves the bases against that. A subclass of a protocol that does not listProtocolitself is a regular class and is still mutated.Trade-offs and limitations, so you can decide whether any of them should change:
from typing import Protocolinside a function also counts at module level. I have not seen this in real code.Protocolre-exported through a project module (from mypkg.compat import Protocol) or imported relatively is not recognised, so such a protocol is still mutated as before.mutants/directory keeps the old mutated copy of a source file until that file changes, somutants/needs to be removed to pick up the fix.Verification (Linux, Python 3.10 and 3.12):
main, the 13 protocol cases fail on their assertions and the 4 negative cases (an explicit subclass,asyncio.Protocol, Twisted'sProtocol, a relative import) pass. I also mutated the new code ten ways (each import form, the subscript unwrap, the relative import check, matching by name only); each mutation fails at least one case.main; the difference is the new tests. pre-commit (ruff, ruff format, mypy) passes.filter_mutants_with_type_checker, 3.8.0 raises on the 140 type errors caused by the rewritten protocol; with this branch they are gone and the filter completes.mainand with this branch. 694 produce byte-identical output, and every mutant removed in the other 10 lies inside one of the 105typingprotocols found byQualifiedNameProvider, which I used as an independent oracle.create_mutationstiming onsrc/mutmut/__main__.pyandtests/mutation/test_mutation.pyis within 3% ofmain.