From 708bb4e581d415c9f0924f150d03fd6ecdeb1c81 Mon Sep 17 00:00:00 2001 From: James Buncle Date: Wed, 1 Apr 2026 20:50:59 +0100 Subject: [PATCH] Add ordering false-positive regressions --- src/Signature/ContractIdentity.php | 20 +++++-- src/Signature/TraitUseIdentity.php | 25 +++++++-- tests/run.php | 86 ++++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+), 6 deletions(-) diff --git a/src/Signature/ContractIdentity.php b/src/Signature/ContractIdentity.php index c53fed9..813f796 100644 --- a/src/Signature/ContractIdentity.php +++ b/src/Signature/ContractIdentity.php @@ -32,7 +32,7 @@ public function toIdentityKey(): string { 'kind:' . $this->kind, 'types:[' . implode(',', array_map(function (IdentityKey $type): string { return $type->toIdentityKey(); - }, $this->types)) . ']', + }, $this->getNormalisedTypes())) . ']', ]); } @@ -41,12 +41,26 @@ public function equals(IdentityKey $other): bool { return false; } - foreach ($this->types as $index => $type) { - if (!$type->equals($other->types[$index])) { + $left = $this->getNormalisedTypes(); + $right = $other->getNormalisedTypes(); + foreach ($left as $index => $type) { + if (!$type->equals($right[$index])) { return false; } } return true; } + + /** + * @return IdentityKey[] + */ + private function getNormalisedTypes(): array { + $types = $this->types; + usort($types, function (IdentityKey $left, IdentityKey $right): int { + return strcmp($left->toIdentityKey(), $right->toIdentityKey()); + }); + + return $types; + } } diff --git a/src/Signature/TraitUseIdentity.php b/src/Signature/TraitUseIdentity.php index e714543..667dbfe 100644 --- a/src/Signature/TraitUseIdentity.php +++ b/src/Signature/TraitUseIdentity.php @@ -50,7 +50,7 @@ public function toIdentityKey(): string { 'kind:' . $this->kind, 'traits:[' . implode(',', array_map(function (IdentityKey $trait): string { return $trait->toIdentityKey(); - }, $this->traits)) . ']', + }, $this->getNormalisedTraits())) . ']', 'method:' . ($this->method ?? ''), 'newName:' . ($this->newName ?? ''), 'newModifier:' . ($this->newModifier ?? ''), @@ -67,12 +67,31 @@ public function equals(IdentityKey $other): bool { return false; } - foreach ($this->traits as $index => $trait) { - if (!$trait->equals($other->traits[$index])) { + $left = $this->getNormalisedTraits(); + $right = $other->getNormalisedTraits(); + foreach ($left as $index => $trait) { + if (!$trait->equals($right[$index])) { return false; } } return true; } + + /** + * @return IdentityKey[] + */ + private function getNormalisedTraits(): array { + if ($this->kind !== 'precedence' || count($this->traits) <= 2) { + return $this->traits; + } + + $selected = $this->traits[0]; + $insteadOfTraits = array_slice($this->traits, 1); + usort($insteadOfTraits, function (IdentityKey $left, IdentityKey $right): int { + return strcmp($left->toIdentityKey(), $right->toIdentityKey()); + }); + + return array_merge([$selected], $insteadOfTraits); + } } diff --git a/tests/run.php b/tests/run.php index 3182996..b84d699 100644 --- a/tests/run.php +++ b/tests/run.php @@ -787,6 +787,89 @@ function testNamespaceConstantOrderingDoesNotBumpVersion(): void { assertSameValue('Reordering namespace constants without changing values should remain PATCH.', 'PATCH', $diff->diff('HEAD', 'WC')->getIncrement()); } + +function testImplementedContractOrderingDoesNotBumpVersion(): void { + $root = createRepository('implements-ordering', [ + 'src/Worker.php' => <<<'PHP' +diff('HEAD', 'WC')->getIncrement()); +} + +function testExtendedInterfaceOrderingDoesNotBumpVersion(): void { + $root = createRepository('interface-extends-ordering', [ + 'src/Contract.php' => <<<'PHP' +diff('HEAD', 'WC')->getIncrement()); +} + +function testTraitPrecedenceOrderingDoesNotBumpVersion(): void { + $root = createRepository('trait-precedence-ordering', [ + 'src/Worker.php' => <<<'PHP' +diff('HEAD', 'WC')->getIncrement()); +} + function testIncludePathsRestrictTheSurface(): void { $root = createRepository('include-paths', [ 'src/Foo.php' => "