@@ -137,7 +137,13 @@ const IteratorSymbol = Symbol.iterator;
137137
138138const hasIterator = ( object : any ) =>
139139 ! ! ( object != null && object [ IteratorSymbol ] ) ;
140- export const iterableEquality = ( a : any , b : any ) => {
140+
141+ export const iterableEquality = (
142+ a : any ,
143+ b : any ,
144+ aStack : Array < any > = [ ] ,
145+ bStack : Array < any > = [ ] ,
146+ ) => {
141147 if (
142148 typeof a !== 'object' ||
143149 typeof b !== 'object' ||
@@ -152,6 +158,24 @@ export const iterableEquality = (a: any, b: any) => {
152158 return false ;
153159 }
154160
161+ let length = aStack . length ;
162+ while ( length -- ) {
163+ // Linear search. Performance is inversely proportional to the number of
164+ // unique nested structures.
165+ // circular references at same depth are equal
166+ // circular reference is not equal to non-circular one
167+ if ( aStack [ length ] === a ) {
168+ return bStack [ length ] === b ;
169+ } else if ( bStack [ length ] === b ) {
170+ return false ;
171+ }
172+ }
173+ aStack . push ( a ) ;
174+ bStack . push ( b ) ;
175+
176+ const iterableEqualityWithStack = ( a : any , b : any ) =>
177+ iterableEquality ( a , b , aStack , bStack ) ;
178+
155179 if ( a . size !== undefined ) {
156180 if ( a . size !== b . size ) {
157181 return false ;
@@ -161,7 +185,7 @@ export const iterableEquality = (a: any, b: any) => {
161185 if ( ! b . has ( aValue ) ) {
162186 let has = false ;
163187 for ( const bValue of b ) {
164- const isEqual = equals ( aValue , bValue , [ iterableEquality ] ) ;
188+ const isEqual = equals ( aValue , bValue , [ iterableEqualityWithStack ] ) ;
165189 if ( isEqual === true ) {
166190 has = true ;
167191 }
@@ -173,25 +197,30 @@ export const iterableEquality = (a: any, b: any) => {
173197 }
174198 }
175199 }
176- if ( allFound ) {
177- return true ;
178- }
200+
201+ aStack . pop ( ) ;
202+ bStack . pop ( ) ;
203+
204+ return allFound ;
179205 } else if ( isA ( 'Map' , a ) || isImmutableUnorderedKeyed ( a ) ) {
180206 let allFound = true ;
181207 for ( const aEntry of a ) {
182208 if (
183209 ! b . has ( aEntry [ 0 ] ) ||
184- ! equals ( aEntry [ 1 ] , b . get ( aEntry [ 0 ] ) , [ iterableEquality ] )
210+ ! equals ( aEntry [ 1 ] , b . get ( aEntry [ 0 ] ) , [ iterableEqualityWithStack ] )
185211 ) {
186212 let has = false ;
187213 for ( const bEntry of b ) {
188- const matchedKey = equals ( aEntry [ 0 ] , bEntry [ 0 ] , [ iterableEquality ] ) ;
214+ const matchedKey = equals ( aEntry [ 0 ] , bEntry [ 0 ] , [
215+ iterableEqualityWithStack ,
216+ ] ) ;
189217
190218 let matchedValue = false ;
191219 if ( matchedKey === true ) {
192- matchedValue = equals ( aEntry [ 1 ] , bEntry [ 1 ] , [ iterableEquality ] ) ;
220+ matchedValue = equals ( aEntry [ 1 ] , bEntry [ 1 ] , [
221+ iterableEqualityWithStack ,
222+ ] ) ;
193223 }
194-
195224 if ( matchedValue === true ) {
196225 has = true ;
197226 }
@@ -203,23 +232,31 @@ export const iterableEquality = (a: any, b: any) => {
203232 }
204233 }
205234 }
206- if ( allFound ) {
207- return true ;
208- }
235+ aStack . pop ( ) ;
236+ bStack . pop ( ) ;
237+ return allFound ;
209238 }
210239 }
211240
212241 const bIterator = b [ IteratorSymbol ] ( ) ;
213242
214243 for ( const aValue of a ) {
215244 const nextB = bIterator . next ( ) ;
216- if ( nextB . done || ! equals ( aValue , nextB . value , [ iterableEquality ] ) ) {
245+ if (
246+ nextB . done ||
247+ ! equals ( aValue , nextB . value , [
248+ ( a : any , b : any ) => iterableEquality ( a , b , aStack , bStack ) ,
249+ ] )
250+ ) {
217251 return false ;
218252 }
219253 }
220254 if ( ! bIterator . next ( ) . done ) {
221255 return false ;
222256 }
257+
258+ aStack . pop ( ) ;
259+ bStack . pop ( ) ;
223260 return true ;
224261} ;
225262
0 commit comments