Skip to content

fix(isEqual): track ancestors, not every value the walk has seen - #817

Open
savkaoleg wants to merge 1 commit into
react-component:masterfrom
savkaoleg:fix/isequal-sibling-reference
Open

savkaoleg wants to merge 1 commit into
react-component:masterfrom
savkaoleg:fix/isequal-sibling-reference

Conversation

@savkaoleg

@savkaoleg savkaoleg commented Sep 16, 2026

Copy link
Copy Markdown

Closes #816.

The problem

isEqual returns false, and logs Warning: There may be circular references, for two objects that are deeply equal — whenever one of them holds the same reference in two different keys:

const shared = [];
isEqual({ errors: shared, warnings: shared }, { errors: [], warnings: [] });
// → false, and "Warning: There may be circular references"

Neither object contains a cycle.

Why

refSet records every value the walk visits and never releases anything when the walk leaves a branch. A cycle is a value reachable from itself — a value that is its own ancestor on the current path — so the set needs to hold the path, not the history. As written, a reference legitimately reached twice in two sibling branches is indistinguishable from a cycle: the second visit finds it in the set, warns, and returns false.

The change

refSet.delete(a) in a finally, so entries are released as the walk unwinds. Genuine cycles are still caught, because a self-referencing value is still on the path when it is reached again.

Why it shows up in practice

rc-field-form's Field keeps one shared constant in two places:

const EMPTY_ERRORS: any[] = [];
public errors: string[] = EMPTY_ERRORS;
public warnings: string[] = EMPTY_ERRORS;

and triggerMetaEvent compares the previous meta with the next through isEqual. Once one field has validated — its errors becoming a fresh [] — the comparison hits exactly the case above. The visible result in any antd app is this warning in the dev console after a programmatic form.setFieldValue(...), pointing at application code that holds no circular data, plus an onMetaChange that fires when nothing changed.

Tests

Four cases added to src/test/isEqual.test.ts:

  • a reference reused across sibling keys compares equal and warns nothing
  • an object reused across sibling keys, likewise
  • a reference repeated inside one array, likewise
  • a cycle reached through an array is still detected and still warns

Every test already in that file passes unchanged, including should not equal 6, which is the existing cyclic case.

case before after
one array held in two keys false, warns true, silent
one object reused across sibling keys false, warns true, silent
same reference twice inside an array true, silent true, silent
a genuine cycle (a.self = a) false, warns false, warns
a cycle through an array false, warns false, warns
plainly equal / unequal values unchanged unchanged

Summary by CodeRabbit

  • Bug 修复
    • 改进深度相等比较对循环引用的处理。
    • 正确支持同一对象或数组引用在多个位置复用,减少误报的循环引用警告。
    • 对实际存在的循环引用进行更准确的识别,并保持正确的比较结果与警告行为。

A reference repeated in two sibling branches is not a circular reference,
but refSet never released anything once the walk left a branch. The second
visit found it in the set, warned, and returned false for deeply-equal input:

  const shared = [];
  isEqual({ errors: shared, warnings: shared }, { errors: [], warnings: [] });
  // false, and "Warning: There may be circular references"

A cycle is a value reachable from itself, so the set has to hold the current
path rather than the whole history. Entries are now released as the walk
unwinds. Genuine cycles are still detected, because a self-referencing value
is still its own ancestor when it is reached again.

The tests gain a resetWarned() in beforeEach: warning is warningOnce, so a
message emitted by an earlier case is suppressed in every later one, and
asserting that nothing warned would otherwise prove nothing.

Closes react-component#816
@vercel

vercel Bot commented Sep 16, 2026

Copy link
Copy Markdown

@savkaoleg is attempting to deploy a commit to the afc163's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Sep 16, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 424d37a7-4e32-4d7e-ad87-0e9552ca5186

📥 Commits

Reviewing files that changed from the base of the PR and between 9f2ff96 and d78c21d.

📒 Files selected for processing (2)
  • src/isEqual.ts
  • src/test/isEqual.test.ts

Included review availability: Your plan provides up to 4 included reviews per hour; 3 remain after this review.


Walkthrough

Changes

isEqual 循环引用处理

Layer / File(s) Summary
祖先集跟踪
src/isEqual.ts
refSet 现在只保存当前比较路径上的祖先值。分支遍历完成后,代码通过 finally 移除当前值。
循环引用测试
src/test/isEqual.test.ts
测试在每个用例前重置一次性警告状态,并验证兄弟分支中的重复引用与数组自引用。

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~15 minutes

Change: Bug fix · Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to d78c2

The ancestor-path cleanup and accompanying cycle/reference tests align with the intended behavior, with no remaining merge-blocking issue identified.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 2 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题准确概括了主要变更:将 isEqual 的引用跟踪从所有已访问值改为仅跟踪当前祖先引用。标题简洁且与代码和测试变更一致。
Linked Issues check ✅ Passed PR 满足直接关联问题 #816 的编码要求。src/isEqual.ts 在进入分支时加入 a,并在 finally 中删除 a,因此集合只表示当前遍历路径。共享数组、共享对象、数组内重复引用的测试验证了兄弟分支重复引用返回 true 且不产生循环警告。数组路径循环测试验证了真实循环仍返回 false 并产生警告。已有普通相等、不等和浅比较测试继续覆盖原有行为。该修复也保…
Out of Scope Changes check ✅ Passed 未发现超出 #816 范围的变更。生产代码仅调整 isEqual 的祖先引用跟踪。新增测试直接覆盖共享引用、真实循环和警告行为。测试辅助的 resetWarned() 用于保证警告断言独立,属于该修复的测试支持。
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Some tools did not complete. Review the errors below.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install timed out. The project may have too many dependencies for the sandbox.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

小兔检查 refSet,
祖先路径留清晰。
兄弟引用不再误报,
数组自环仍警醒。
测试逐项敲响铃。

Comment @coderabbitai help to get the list of available commands.

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.

isEqual: false 'circular references' warning for a reference repeated across sibling keys

1 participant