Identifier resolution: guarantee that closure property has precedence over a method with identical signature?

Hello,

The code base I'm currently working on has a few "editor" protocols that look like the following:

protocol IntEditor {
    var initialValue: Int? { get }
    func setValue(_ value: Int)
}

As a convenience, they all come with a companion type-erased struct:

struct AnyIntEditor: IntEditor {
    var initialValue: Int?
    var setValue: (_ value: Int) -> Void
    
    func setValue(_ value: Int) {
        setValue(value)
    }
}

Those work quite well:

let editor: IntEditor = AnyIntEditor(
    initialValue: nil,
    setValue: { print($0) })
editor.setValue(12) // prints "12"

Now, the definition of AnyIntEditor.setValue(_:) is very troubling, because it looks like it starts an infinite loop. But it does not, as it actually calls the property (Xcode 11.3):

struct AnyIntEditor: IntEditor {
    var initialValue: Int?
    var setValue: (_ value: Int) -> Void
    
    func setValue(_ value: Int) {
        setValue(value) // calls the property
    }
}

So here is my question: is it a guaranteed behavior, or some unintended side-effect of an implementation detail in the compiler that no one should rely on?

I found this in the compiler source code, in ConstraintSystem::compareSolutions:

[...] a property is better than a method (because a method is usually immediately invoked).

Of course this piece of code may be unrelated to my question. But if it is, it looks that the answer is a positive yes. I admit that I'd prefer finding something which is more convincing, such as a test, or a confirmation from a knowledgable person. May I summon @Slava_Pestov?

This should be what I was looking for:

let ambValue = ambiguousWithVar // no-warning - var preferred
let ambValueChecked: Int = ambValue
ambiguousWithVar = 42    // no-warning
ambiguousWithVar(true)   // no-warning

So... I consider my question answered :slightly_smiling_face:

It is worth noting that this behavior does not extend to function signatures that have an empty argument list:

protocol IntEditor {
    func initialValue() -> Int?
    func setValue(_ value: Int)
}

struct AnyIntEditor: IntEditor {
    var initialValue: () -> Int?
    var setValue: (_ value: Int) -> Void
    
    // Compiler error: Invalid redeclaration of 'initialValue()
    func initialValue() -> Int? { initialValue() }
    // OK
    func setValue(_ value: Int) { setValue(value) }
}

This is an inconsistency in the behavior of the compiler, which may well deserve an issue. I won't open any, because this inconsistency may well be required as a support for other more important compiler behaviors. I just don't know.

Yet, I find this topic interesting, because the current state of the compiler gives an objective argument against sharing the same name for a closure property and a method, which is that this sharing is not guaranteed to be accepted by the compiler for all signatures. The pattern does not always work well.

There is a verbose workaround which preserves the pattern, though:

protocol IntEditor {
    func initialValue() -> Int?
    func setValue(_ value: Int)
}

struct AnyIntEditor: IntEditor {
    private var _initialValue: () -> Int?
    private var _setValue: (_ value: Int) -> Void
    
    init(
        initialValue: @escaping () -> Int?,
        setValue: @escaping (_ value: Int) -> Void)
    {
        _initialValue = initialValue
        _setValue = setValue
    }
    
    func initialValue() -> Int? { _initialValue() }
    func setValue(_ value: Int) { _setValue(value) }
}

let editor: IntEditor = AnyIntEditor(
    initialValue: { nil },
    setValue: { print($0) })
editor.setValue(12) // prints "12"

Maybe a clarification might come into existence if an issue were created.