From 17136d23f954f7820e75fcfa87dec3a1ed16b3ea Mon Sep 17 00:00:00 2001 From: daboe01 Date: Thu, 25 Sep 2025 07:27:17 +0200 Subject: [PATCH] NEW: for..of iterator for CPDictionary (#3113) --- Foundation/CPDictionary.j | 2 +- Objective-J/CFDictionary.js | 19 ++++++++++++++ Tests/Foundation/CPDictionaryTest.j | 39 +++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/Foundation/CPDictionary.j b/Foundation/CPDictionary.j index d2cd8f229..316816235 100755 --- a/Foundation/CPDictionary.j +++ b/Foundation/CPDictionary.j @@ -774,4 +774,4 @@ if (CFMutableDictionary.prototype.isa !== CPMutableDictionary) writable: true } }); -} \ No newline at end of file +} diff --git a/Objective-J/CFDictionary.js b/Objective-J/CFDictionary.js index c3ff941ae..f61412d37 100644 --- a/Objective-J/CFDictionary.js +++ b/Objective-J/CFDictionary.js @@ -133,6 +133,25 @@ CFDictionary.prototype.valueForKey = function(/*String*/ aKey) DISPLAY_NAME(CFDictionary.prototype.valueForKey); +// This allows the use of 'for...of' loops directly on CFDictionary instances. +CFDictionary.prototype[Symbol.iterator] = function*() +{ + // Access the internal storage directly for maximum performance. + // 'this._keys' is the internal array of keys. + const keys = this._keys; + + // 'this._buckets' is the internal hash map of key -> value. + const buckets = this._buckets; + + // A 'for...of' loop on an array is itself a lazy and efficient way to iterate. + for (const key of keys) + { + // 'yield' pauses the generator and returns the [key, value] pair + // to the consumer (the for...of loop). + yield [key, buckets[key]]; + } +}; + CFDictionary.prototype.toString = function() { var string = "{\n", diff --git a/Tests/Foundation/CPDictionaryTest.j b/Tests/Foundation/CPDictionaryTest.j index 505a83c07..a94e05a38 100644 --- a/Tests/Foundation/CPDictionaryTest.j +++ b/Tests/Foundation/CPDictionaryTest.j @@ -484,4 +484,43 @@ [self assert:5 equals:[dict objectForKey:@"aKey"]]; } +- (void)testForOfIteration +{ + var dict = @{ @"a": 1, @"b": 2, @"c": 3 }; + var result = [CPMutableDictionary dictionary]; + + // Test basic for...of iteration + for (var [key, value] of dict) + { + [result setObject:value forKey:key]; + } + + [self assert:dict equals:result message:@"Dictionary should be equal after for...of iteration"]; + + // Test with spread syntax, a common use for iterables + var entries = [...dict]; + [self assert:3 equals:entries.length message:@"Spread syntax should produce 3 entries"]; + + // The order is not guaranteed, so we check the contents by converting to a dictionary + var spreadDict = [CPMutableDictionary dictionary]; + for (var entry of entries) + { + [spreadDict setObject:entry[1] forKey:entry[0]]; + } + [self assert:dict equals:spreadDict message:@"Dictionary rebuilt from spread entries should be equal"]; + + // Test on an empty dictionary + var emptyDict = @{}; + var iterations = 0; + for (var entry of emptyDict) + { + iterations++; + } + [self assert:0 equals:iterations message:@"for...of on an empty dictionary should not iterate"]; + + // Test with spread on empty dictionary + var emptyEntries = [...emptyDict]; + [self assert:0 equals:emptyEntries.length message:@"Spread on an empty dictionary should produce an empty array"]; +} + @end