Skip to content

Fix UniqueIterable comparing the whole iterable instead of its items - #816

Closed
CorvusSharp wants to merge 2 commits into
yiisoft:masterfrom
CorvusSharp:fix-unique-iterable-comparison
Closed

Fix UniqueIterable comparing the whole iterable instead of its items#816
CorvusSharp wants to merge 2 commits into
yiisoft:masterfrom
CorvusSharp:fix-unique-iterable-comparison

Conversation

@CorvusSharp

@CorvusSharp CorvusSharp commented Sep 11, 2026

Copy link
Copy Markdown
Q A
Is bugfix? ✔️
New feature?
Breaks BC?

UniqueIterableHandler never compares the items of the iterable. Inside the loop the
stack is filled with $value, the whole iterable, instead of $item:

if ($value instanceof Stringable) {
    $stack[] = (string) $value;
} elseif ($value instanceof DateTimeInterface) {
    $stack[] = $value->getTimestamp();
} else {
    $stack[] = $value;
}

So the stack holds N copies of one and the same iterable, and the uniqueness check fires
as soon as it holds two of them, regardless of the content. The check also runs before
the current item is pushed, so it is always one iteration behind.

The result is that the rule reports the opposite of the truth for most inputs:

Value Current Expected
['a', 'a'] valid not unique
['a', 'b', 'c'] not unique valid
[1, 1] valid not unique
[1, 2, 3, 4] not unique valid

Any iterable with three or more items is reported as containing duplicates, and a real
duplicate in a pair passes.

The existing tests do not catch it because of how the data sets are shaped: every case in
dataValidationPassed has exactly two items, and every duplicate case in
dataValidationFailed has three or more. Both then pass for the wrong reason.

The fix

  • push $item into the stack instead of $value;
  • move the uniqueness check after the push, so the current item takes part in it.

Two things are deliberately left as they were, to keep the change limited to the bug:

  • the order of the Stringable and DateTimeInterface branches. Swapping them would
    change how an object implementing both interfaces is compared, which is unrelated to
    this bug;
  • the check stays inside the loop rather than after it. That preserves the original error
    precedence: a later item with a disallowed value or a different type is still reported
    before a duplicate found earlier would be, and the rule still returns as soon as the
    duplicate is complete.

Tests

Eleven data sets added, covering both sides of the failure for every supported item type:

  • five in dataValidationPassed: unique lists of three and four items — strings,
    integers, floats, Stringable, DateTimeInterface;
  • six in dataValidationFailed: two equal items — strings, integers, floats, booleans,
    Stringable, DateTimeInterface.

All eleven fail on the current handler and pass with the fix. The full suite stays green:
1943 tests, 4014 assertions. Psalm reports no issues.

Copilot AI lite review requested due to automatic review settings September 11, 2026 04:57
@codecov

codecov Bot commented Sep 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.35%. Comparing base (e596872) to head (27e48fc).

Additional details and impacted files
@@             Coverage Diff              @@
##             master     #816      +/-   ##
============================================
+ Coverage     96.29%   96.35%   +0.05%     
+ Complexity     1154     1153       -1     
============================================
  Files           124      124              
  Lines          3540     3540              
============================================
+ Hits           3409     3411       +2     
+ Misses          131      129       -2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No unresolved issues were identified, and the fix includes regression coverage.

Pull request overview

Fixes UniqueIterableHandler to compare normalized items correctly and detect duplicates after each item is added.

Changes:

  • Correct uniqueness validation logic.
  • Add regression coverage.
  • Document the bug fix.
File summaries
File Description
tests/Rule/UniqueIterableTest.php Adds regression coverage for unique and duplicate values.
src/Rule/UniqueIterableHandler.php Corrects uniqueness validation logic.
CHANGELOG.md Documents the bug fix.
Review details
  • Files reviewed: 3/3 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants