Inconsistent behavior with "All paths through this function will call itself" warning

When you run the below code in Xcode playground you'll see a single "All paths through this function will call itself" warning. And this is definitely the case at runtime. But this is not the case for the leader property right below it which also calls a property on the same object with the same name. I'm trying to figure out why Swift is treating these two cases differently, and whether the infinite recursion not occurring for the leader property is a bug or a feature.

Can be ran in the playground verbatim:

import Foundation


/// Models
private final class Group {
    fileprivate let people: MutatingArray<Person>
    fileprivate let leader: Mutating<Person?>
    fileprivate init() {
        self.people = .init()
        self.leader = .init(.none)
    }
}
private struct Person {}



/// Model interface conformance.
extension Group: Group_Interface {}
extension Group_Interface {
    var impl: Group { return self as! Group }
    var people: [Person_Interface] { return Array(self.impl.people) }   // Infinite recursion.
    var leader: Leader_Interface { return self.impl.leader }   // No infinite recursion.

}
extension Person: Person_Interface {}
extension Mutating: Leader_Interface where T == Optional<Person> {}



/// Interfaces.
private protocol Group_Interface {
    var people: [Person_Interface] { get }
    var leader: Leader_Interface { get }
}
private protocol Person_Interface {}
private protocol Leader_Interface {}



/// Supporting data types.
private final class MutatingArray<T>: Sequence {
    private var arr: [T]
    fileprivate init() { self.arr = .init() }
}
extension MutatingArray {
    typealias Iterator = Array<T>.Iterator
    func makeIterator() -> Array<T>.Iterator { return self.arr.makeIterator() }
}
private final class Mutating<T> {
    private var val: T
    fileprivate init(_ val: T) { self.val=val }
}
private extension Mutating {
    var accessor: Accessor { return Accessor(self) }
    struct Accessor {
        private let mutating: Mutating<T>
        fileprivate init(_ mutating: Mutating<T>) { self.mutating=mutating }
    }
}

Yeah this is a little subtle, but I think the issue will be clear if I write out the full definition of Group. When you include all conformances and protocol extensions, this is the full definition of Group:

private final class Group: Group_Interface {
    fileprivate let people: MutatingArray<Person>
    fileprivate let leader: Mutating<Person?>
    fileprivate init() {
        self.people = .init()
        self.leader = .init(.none)
    }

    // Group_Interface pieces
    var impl: Group { return self as! Group }
    var people: [PersonInterface] { return Array(self.impl.people) }
}

Note something very subtle here. The Group’s fileprivate let leader: Mutating<Person?> satisfies the protocol requirement for var leader, and so completely disregards the version provided in Group_Interface. Then, any other type that conforms to Group_Interface will may use the version provided in the extension, but as that will immediate cast to Group the problem goes away: we know that Group’s implementation terminates.

However, the same is not true for fileprivate let people: MutatingArray<Person> matching the requirement for [Person_Interface]. This is because MutatingArray is not an Array. So, Group has two properties called people: one returning MutatingArray<Person> and one returning [Person_Interface]. Inside the definition of var people: [PersonInterface]you have an ambiguous reference toself.impl.people. The Swift compiler has chosen what it believes to be the best fit: the version that _already_ returns an Arrayof[Person_Interface]`. Unfortunately, that just so happens to be the same as the property we’re already computing, and we have infinite recursion.

Rather than fix this, I’d suggest this isn’t a great design. Having a protocol to abstract over the idea that self is actually required to be a specific type is an unnecessary indirection.

But if you comment out the leader definition in Group_Interface, the compiler will complain that Group doesn't conform to Group_Interface. So in this case the compiler is clearly not using Group.leader to satisfy Group_Interface.leader. So why does the behavior change when Group_Interface.leader is defined vs undefined?

Which definition are you commenting out? The one in the protocol block or the one in the extension block?

Extension block.

Right, so in that case what is happening instead is that the extension block is satisfying the protocol requirement, and the implementation is loading the stored property of leader from Group and using that as the return value. This is acceptable because Mutating<Person?> conforms to Leader_Interface.

However, the choice of MutatingArray as a return value is less favoured than the Array already present on the Group. This is just the way the compiler's type checker works when it tries to disambiguate duplicate properties.

You can resolve the issue while keeping the behaviour the same by changing the definition of people in the Group_Interface extension block to:

var people: [Person_Interface] { return Array(self.impl.people as MutatingArray<Person>) } 

This tells the type checker which people you wanted with the as clause, and breaks the recursion.

Foregoing your opinion of the overall design, would you lean on the compiler to disambiguate between Group.leader and extension Group_Interface.leader in production code?

I'd probably use the same as clause to request exactly what I want to reduce the risk of it breaking.

Only problem with that is in my actual code I use leader in several places throughout the Group_Interface extension — there's methods that I chose not to show here for simplicity. So with that in mind, would you still use as everywhere leader is used in the extension? — upwards of 10 uses.

Yeah, that's a lot. Can you rename the property instead? That'll also avoid the ambiguity.

It's not an option I want to take. The models and model protocols are defined independently, so of course their property names are as well. Within the model layer, other models refer to property names and things become unintuitive if I start bending the property names of one layer to suit the other — either the model layer property names will be unintuitive, or the code that consumes the model protocols will be. If there really is no alternative, I'll just have to define something like:

private extension Group {
    var safePeople: MutatingArray<People>
    var safeLeader: Mutating<Person?>
}

And use them in the Group_Interface extension instead.

Terms of Service

Privacy Policy

Cookie Policy