How to find out which function is being dispatched, especially for opaque types

Hello, new to the Swift community here and coming from Julia language background. On my little adventure with the Swift Programming Language official book I have been going through Protocols, Generics, Extensions, and finally Opaque Types. I stumbled upon the interesting example from the book where it defines a brand new protocol (if I understand correctly) called Container and then it extends the known Array type to conform to that protocol.

protocol Container {
    associatedtype Item
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}
extension Array: Container { }

The example then proceeds to demonstrate some interesting aspects of opaque types. Something like (after some edits by me)

extension Container {
  var isContainer: Bool { true }
}

func makeOpaqueContainer<T>(item: T) -> some Container {
    return [item]
}

let opaqueContainer = makeOpaqueContainer(item: 12)
print(type(of: opaqueContainer)) // Array<Int>
let twelve = opaqueContainer[0]
print(opaqueContainer.isContainer) // true
print(type(of: twelve)) // Int

At this point, again coming from Julia language background, a question came to mind: how can we tell at run-time which function exactly is being called. In Julia there exists a popular macro @which that can be placed on the same line with a function call to print out which method is being dispatched, and that can greatly help with debugging and with understanding how the compiler is reasoning about the code. Is there a Swift equivalent of such @which functionality? My quick search led me to this thread which is discussing some topics way over my head for a beginner, yet this particular comment helped me explore more:

extension Container {
  func whatAmI() { print("Container") }
}
extension Array {
  func whatAmI() { print("Array") }
}
func whatIsIt<C: Container>(_ value: C) {
  value.whatAmI()
}

let x = [1, 2, 3]
x.whatAmI() // Array
whatIsIt(x) // Container
let y: some Container = [1, 2, 3]
y.whatAmI() // Container
print(type(of: y)) // Array<Int>
print(y[1]) // 2

I'm particularly curious about the very last line. Is there a way to annotate that line with @which-similar construct to let us know what subscript function exactly is being called for that some Container, together with the exact concrete type that is being resolved by the compiler? For example

print(@which y[1]) // Array<Int>.subscript

Having such feature in the Julia language was really helpful and I'd very much like to have a similar feature to help me validate my reasoning about Swift code! Thanks in advance!

3 Likes

Opaque types are more complicated than just a syntactic sugar. The function dispatch for them is different than for the underlying type:

func test<T: Collection>(_ arg: T) {
    print("generic")
}
func test(_ arg: [Int]) {
    print("normal")
}

let opaque: some Collection = [1, 2, 3]
let specific: [Int] = [1, 2, 3]
test(opaque) // prints generic
test(specific) // prints normal
1 Like

In an IDE like Xcode, you can use "jump to definition" at a call site, and it will take you to the declaration that was picked for that call site. This is a static property of code in Swift so you don't need to execute the program to know what declaration a call site refers to.

More and more often it seems that feature returns multiple results, often times for unrelated symbols. If there a logic to the ordering that can help inform which one will actually be called?

3 Likes

If it returns multiple results in Xcode, I believe that either indicates a failure of indexing, so Xcode is falling back to by-name lookup, or it indicates the result maps to a protocol requirement or class method with multiple dynamically-dispatched implementations.

Jump to definition can show multiple results in this case. Quick Help will always show the (single) declaration that was chosen by the type checker.

2 Likes

I understand it's being resolved statically at compile time (and quite similarly in Julia but using JIT compilation). Yet, would be great to have access to such information programmatically to validate the assumptions (even outside of Xcode, for example with Linux server-side Swift), and to help with the debugging/troubleshooting in complex projects. From further reading, it seems that Mirror is a similarly good tool to help reason about types, and would be helpful if something exists along these lines but for function dispatch.

3 Likes

I agree! I had a similar idea for a Google Summer of Code 2020 project, and I'm still very interested in helping someone work on such a feature.

3 Likes

Interesting, thanks. Guess that means doc comments are even more important so we can tell things apart, as there's no way to navigation to declaration from the quick help, just the file that contains it.