From 068ac1617afae609548550f25278dcbaaf010778 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 3 Nov 2025 08:39:58 +0100 Subject: [PATCH 1/5] Implement IsSuperTypeOfCalleeAndArgumentMutator --- .../IsSuperTypeOfCalleeAndArgumentMutator.php | 96 ++++++++++ ...uperTypeOfCalleeAndArgumentMutatorTest.php | 176 ++++++++++++++++++ 2 files changed, 272 insertions(+) create mode 100644 src/Infection/IsSuperTypeOfCalleeAndArgumentMutator.php create mode 100644 tests/Infection/IsSuperTypeOfCalleeAndArgumentMutatorTest.php diff --git a/src/Infection/IsSuperTypeOfCalleeAndArgumentMutator.php b/src/Infection/IsSuperTypeOfCalleeAndArgumentMutator.php new file mode 100644 index 0000000..ffe7721 --- /dev/null +++ b/src/Infection/IsSuperTypeOfCalleeAndArgumentMutator.php @@ -0,0 +1,96 @@ + + */ +final class IsSuperTypeOfCalleeAndArgumentMutator implements Mutator +{ + + public static function getDefinition(): Definition + { + return new Definition( + <<<'TXT' + Replaces TrinaryLogic->yes() with !TrinaryLogic->no() and vice versa. + TXT + , + MutatorCategory::ORTHOGONAL_REPLACEMENT, + null, + <<<'DIFF' + - $type->isBoolean()->yes(); + + !$type->isBoolean()->no(); + DIFF, + ); + } + + public function getName(): string + { + return self::class; + } + + public function canMutate(Node $node): bool + { + if (!$node instanceof Node\Expr\MethodCall) { + return false; + } + + if ( + !$node->var instanceof Node\Expr\Variable + && !$node->var instanceof Node\Expr\PropertyFetch + && !$node->var instanceof Node\Expr\MethodCall + && !$node->var instanceof Node\Expr\StaticCall + && !$node->var instanceof Node\Expr\New_ + ) { + return false; + } + + if (!$node->name instanceof Node\Identifier) { + return false; + } + + if (!in_array($node->name->name, ['isSuperTypeOf'], true)) { + return false; + } + + $args = $node->getArgs(); + if (count($args) !== 1) { + return false; + } + + if ( + !$args[0]->value instanceof Node\Expr\Variable + && !$args[0]->value instanceof Node\Expr\PropertyFetch + && !$args[0]->value instanceof Node\Expr\MethodCall + && !$args[0]->value instanceof Node\Expr\StaticCall + && !$args[0]->value instanceof Node\Expr\New_ + ) { + return false; + } + + return true; + } + + public function mutate(Node $node): iterable + { + $args = $node->getArgs(); + if (count($args) !== 1) { + throw new RuntimeException(); + } + + yield new Node\Expr\MethodCall( + $args[0]->value, + $node->name, + [new Node\Arg($node->var)], + ); + } + +} diff --git a/tests/Infection/IsSuperTypeOfCalleeAndArgumentMutatorTest.php b/tests/Infection/IsSuperTypeOfCalleeAndArgumentMutatorTest.php new file mode 100644 index 0000000..65aa95f --- /dev/null +++ b/tests/Infection/IsSuperTypeOfCalleeAndArgumentMutatorTest.php @@ -0,0 +1,176 @@ +assertMutatesInput($input, $expected); + } + + /** + * @return iterable + */ + public static function mutationsProvider(): iterable + { + yield 'It mutates isSuperTypeOf' => [ + <<<'PHP' + isSuperTypeOf($b); + PHP +, + <<<'PHP' + isSuperTypeOf($a); + PHP +, + ]; + + yield 'It mutates isSuperTypeOf with property fetch' => [ + <<<'PHP' + a->isSuperTypeOf($b); + PHP +, + <<<'PHP' + isSuperTypeOf($this->a); + PHP +, + ]; + + yield 'It mutates isSuperTypeOf with property fetch (reversed)' => [ + <<<'PHP' + isSuperTypeOf($this->b); + PHP +, + <<<'PHP' + b->isSuperTypeOf($a); + PHP +, + ]; + + yield 'It mutates isSuperTypeOf with method-call' => [ + <<<'PHP' + isSuperTypeOf($this->call()); + PHP +, + <<<'PHP' + call()->isSuperTypeOf($a); + PHP +, + ]; + + yield 'It mutates isSuperTypeOf with method-call (reversed)' => [ + <<<'PHP' + call()->isSuperTypeOf($a); + PHP +, + <<<'PHP' + isSuperTypeOf($this->call()); + PHP +, + ]; + + yield 'It mutates isSuperTypeOf with static method call' => [ + <<<'PHP' + isSuperTypeOf($a); + PHP +, + <<<'PHP' + isSuperTypeOf(IntegerRangeType::fromInterval(0, null)); + PHP +, + ]; + + yield 'It mutates isSuperTypeOf with static method call (reversed)' => [ + <<<'PHP' + isSuperTypeOf(IntegerRangeType::fromInterval(0, null)); + PHP +, + <<<'PHP' + isSuperTypeOf($a); + PHP +, + ]; + + yield 'It mutates isSuperTypeOf with new' => [ + <<<'PHP' + isSuperTypeOf($delimiterType); + PHP +, + <<<'PHP' + isSuperTypeOf(new ConstantStringType('')); + PHP +, + ]; + + yield 'It mutates isSuperTypeOf with new (reversed)' => [ + <<<'PHP' + isSuperTypeOf(new ConstantStringType('')); + PHP +, + <<<'PHP' + isSuperTypeOf($delimiterType); + PHP +, + ]; + + yield 'skip isSuperTypeOf with more arguments' => [ + <<<'PHP' + isSuperTypeOf($b, $c); + PHP +, + ]; + } + + protected function getTestedMutatorClassName(): string + { + return IsSuperTypeOfCalleeAndArgumentMutator::class; + } + +} From 8751c827f8df6701923ca1534b41ef8e097ac31a Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 3 Nov 2025 08:43:35 +0100 Subject: [PATCH 2/5] fix --- resources/infection.json5 | 3 ++- tests/Infection/IsSuperTypeOfCalleeAndArgumentMutatorTest.php | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/resources/infection.json5 b/resources/infection.json5 index c26e837..70d7195 100644 --- a/resources/infection.json5 +++ b/resources/infection.json5 @@ -13,7 +13,8 @@ "mutators": { "@default": false, "PHPStan\\Infection\\LooseBooleanMutator": true, - "PHPStan\\Infection\\TrinaryLogicMutator": true + "PHPStan\\Infection\\TrinaryLogicMutator": true, + "PHPStan\\Infection\\IsSuperTypeOfCalleeAndArgumentMutator": true }, "bootstrap": "build-infection/vendor/autoload.php" } diff --git a/tests/Infection/IsSuperTypeOfCalleeAndArgumentMutatorTest.php b/tests/Infection/IsSuperTypeOfCalleeAndArgumentMutatorTest.php index 65aa95f..5156158 100644 --- a/tests/Infection/IsSuperTypeOfCalleeAndArgumentMutatorTest.php +++ b/tests/Infection/IsSuperTypeOfCalleeAndArgumentMutatorTest.php @@ -162,6 +162,7 @@ public static function mutationsProvider(): iterable yield 'skip isSuperTypeOf with more arguments' => [ <<<'PHP' isSuperTypeOf($b, $c); PHP , From 67346994ea723245ec10d7511029a96424875f3f Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 3 Nov 2025 08:46:34 +0100 Subject: [PATCH 3/5] Update IsSuperTypeOfCalleeAndArgumentMutator.php --- src/Infection/IsSuperTypeOfCalleeAndArgumentMutator.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Infection/IsSuperTypeOfCalleeAndArgumentMutator.php b/src/Infection/IsSuperTypeOfCalleeAndArgumentMutator.php index ffe7721..a2aaecd 100644 --- a/src/Infection/IsSuperTypeOfCalleeAndArgumentMutator.php +++ b/src/Infection/IsSuperTypeOfCalleeAndArgumentMutator.php @@ -20,14 +20,14 @@ public static function getDefinition(): Definition { return new Definition( <<<'TXT' - Replaces TrinaryLogic->yes() with !TrinaryLogic->no() and vice versa. + Replaces the callee and the argument of a isSuperTypeOf() method call. TXT , MutatorCategory::ORTHOGONAL_REPLACEMENT, null, <<<'DIFF' - - $type->isBoolean()->yes(); - + !$type->isBoolean()->no(); + - $a->isSuperTypeOf($b); + + $b->isSuperTypeOf($a); DIFF, ); } From 12cd9b2ea7cc59ca9a4f9ce9cd99ad0d2a2c7fcf Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 3 Nov 2025 08:49:39 +0100 Subject: [PATCH 4/5] fix --- tests/phpt/infection-config-default.phpt | 3 ++- tests/phpt/infection-config.phpt | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/phpt/infection-config-default.phpt b/tests/phpt/infection-config-default.phpt index ddb44db..aff41c7 100644 --- a/tests/phpt/infection-config-default.phpt +++ b/tests/phpt/infection-config-default.phpt @@ -20,7 +20,8 @@ echo shell_exec($bin); "mutators": { "@default": false, "PHPStan\\Infection\\LooseBooleanMutator": true, - "PHPStan\\Infection\\TrinaryLogicMutator": true + "PHPStan\\Infection\\TrinaryLogicMutator": true, + "PHPStan\\Infection\\IsSuperTypeOfCalleeAndArgumentMutator": true }, "bootstrap": "build-infection\/vendor\/autoload.php" } diff --git a/tests/phpt/infection-config.phpt b/tests/phpt/infection-config.phpt index 99db7ff..b6cbd04 100644 --- a/tests/phpt/infection-config.phpt +++ b/tests/phpt/infection-config.phpt @@ -22,6 +22,7 @@ echo shell_exec($bin." --source-directory='more/files/' --timeout=180 --mutator- "@default": false, "PHPStan\\Infection\\LooseBooleanMutator": true, "PHPStan\\Infection\\TrinaryLogicMutator": true, + "PHPStan\\Infection\\IsSuperTypeOfCalleeAndArgumentMutator": true, "My\\Class": true }, "bootstrap": "build-infection\/vendor\/autoload.php" From f0405e6cc9a931582a171442f29c90e0c2d9242f Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 3 Nov 2025 11:17:25 +0100 Subject: [PATCH 5/5] kill escaped mutant --- .../IsSuperTypeOfCalleeAndArgumentMutatorTest.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/Infection/IsSuperTypeOfCalleeAndArgumentMutatorTest.php b/tests/Infection/IsSuperTypeOfCalleeAndArgumentMutatorTest.php index 5156158..3c361c0 100644 --- a/tests/Infection/IsSuperTypeOfCalleeAndArgumentMutatorTest.php +++ b/tests/Infection/IsSuperTypeOfCalleeAndArgumentMutatorTest.php @@ -165,6 +165,15 @@ public static function mutationsProvider(): iterable $a->isSuperTypeOf($b, $c); PHP +, + ]; + + yield 'skip other method calls' => [ + <<<'PHP' + isConstantValue($b); + PHP , ]; }