[SR-7715] FileManager.DirectoryEnumerator from enumerator(at:includingPropertiesForKeys:options:errorHandler:) enumerates Any instead of URL

Hi All,

Before I start, Im new to open source and to process, so please forgive me if I post a wrong question at wrong place. If Im wrong I would appreciate if you guys point me to the right direction.

Im trying to see if I can fix this issue, [SR-7715] FileManager.DirectoryEnumerator from enumerator(at:includingPropertiesForKeys:options:errorHandler:) enumerates Any instead of URL · Issue #3704 · apple/swift-corelibs-foundation · GitHub. After going through the code, these are my observations,

FileManager.DirectoryEnumerator is a subclass of NSEnumerator. The super class implementation returns Any when the iteration calls nextObject() method. Which is why in for or while loops we are getting the superclass's return type Any.

The challenge, as far as I see, is making the DirectoryEnumerator a generic type. The reason we would want generic type is because we are using the same class in two place,

  • enumerator(at:includingPropertiesForKeys:options:errorHandler:)
  • enumerator(atPath:)

So if we were to find a solution for this issue, then the solution should be of generic approach.

This is what Im thinking,
One way to improve the API is by not inheriting DirectoryEnumerator from NSEnumerator. This way DirectoryEnumerator class is not limited by it superclass. This gives advantage to convert the DirectoryEnumerator into a generic class.

extension FileManager {

    open class DirectoryEnumerator<T>: Sequence {
        
        public typealias EnumeratorElement = T
        
        public struct Iterator : IteratorProtocol {
            let enumerator : DirectoryEnumerator<T>
            public func next() -> Any? {
                return enumerator.nextObject()
            }
        }
        
        public func makeIterator() -> Iterator {
            return Iterator(enumerator: self)
        }
        
        open func nextObject() -> EnumeratorElement? {
            NSRequiresConcreteImplementation()
        }
        
    }    

    internal class NSPathDirectoryEnumerator: DirectoryEnumerator<String> {
        let innerEnumerator : DirectoryEnumerator<URL>
        
        override func nextObject() -> EnumeratorElement? {
            // return path which is String type
        }
    }    

    internal class NSURLDirectoryEnumerator : DirectoryEnumerator<URL> {
        override func nextObject() -> EnumeratorElement? {
            // return URL type
        }
    }

}

The methods return the will be updated in FileManager.

class FileManager: NSObject {

    open func enumerator(atPath path: String) -> DirectoryEnumerator<String>? {
        return NSPathDirectoryEnumerator(path: path)
    }    

    open func enumerator(at url: URL, includingPropertiesForKeys keys: [URLResourceKey]?, options mask: DirectoryEnumerationOptions = [], errorHandler handler: (/* @escaping */ (URL, Error) -> Bool)? = nil) -> DirectoryEnumerator<URL>? {
        return NSURLDirectoryEnumerator(url: url, options: mask, errorHandler: handler)
    }    

}

Please let me know if this is correct approach to solve this bug.

1 Like

This looks like a great bug to start with!

I'd love to see more usages of sequences and iterators in Foundation. I would love to see a generic BFS/DFS sequence, and have this directory API use it for the result.

1 Like