Overload (or disambiguation) of a typed function?

We can re-declare an existing global function, and callers will prefer our newly declared function over the imported option. This allows us to re-declare something and then call the disambiguated imported one:

func assertionFailure(_ message: String) {
  doSomething()
  Swift.assertionFailure(message) // works
}

Is there a way to do the same for a function declared in a type?

extension View {
  func padding(_ edges: EdgeInsets) -> some View {
    doSomething()
    return self.padding(edges) // Ambiguous use of 'padding'
  }
}

No, not in a general case.
For dynamically dispatched methods this is kind of possible, for example you can switch out the implementation of a method during runtime in the Objective-C, er... well, runtime (that's called swizzling), but for static dispatch (which Swift basically always "prefers") you cannot do that as far as I know.

I'm not even sure if there's an API to swizzle for non-Objective-C types that have dynamically dispatched methods (i.e. classes).

So normally you have to rely on subclassing or defining protocols (and potentially use existentials like any MyProtocol) for places where you need to do that.

What is the motivation behind your question, if I may ask?

I think this is possible, but you'd need to make a new module.

In a new module (I'll call it ViewPaddingShim):

import SwiftUI

extension View {
  public func paddingShim(_ edges: EdgeInsets) -> some View {
    return padding(edges)
  }
}

Then in your original module:

import SwiftUI
import ViewPaddingShim

extension View {
  func padding(_ edges: EdgeInsets) -> some View {
    doSomething()
    return paddingShim(edges)
  }
}

I haven't tested this yet, though, so I can't guarantee it'll work.

I am trying to bridge the gap of the lack of tooling to debug SwiftUI.
Xcode's view debugger does not provide sufficient tooling like it does for UIKit or for exposing AutoLayout constraints. If this ever gets added in future Xcode version that'll be amazing, but for now I am trying to create something that can help in debugging in the meantime...
My thinking was to add some compiler flag like UIDEBUG and gate any changes to the function inside it. When it's active we can expose values for debugging.
The reason I don't want to add a custom version of the existing func is that this will (a) require refactoring all callsites in the codebase, and (b) guard against engineers using the original API (which is endless cat and mouse chase...).

I'd be surprised if that works, since you're now having just one more extension (in a third module) that deals with padding. That circumvents the recursion (perhaps?), but I think the issue lies before that.

Even without the shim, the problem in this specific case is this: The original padding is defined in the SwiftUICore module (which is presumably included in SwiftUI, I guess) in an extension for View. It's even @inlineable, so I think that means it gets inlined in any concrete custom view you (or SwiftUI itself) creates, i.e. it's not dynamically dispatched on the view (i.e. looked up in a witness table and then invoked). But even if it were dynamically looked up, I don't think this can work for below reason:

When you define basically the same extension in your app's module (or any module), how would the runtime of SwiftUI know which one to call?
Unless things have changed since the last time I read about this you cannot syntactically express "use the function with this name defined in this module" in this context. Even if there were a way, SwiftUI would surely choose to use its own function. My guess is that as it is inlined that kind of automatically happens, so the best outcome would be that your module's implementation is just never called.

Worst case you get undefined behavior, as the runtime may call your implementation or the original. If it even compiles.


Ah, an honorable goal, and I see how swizzling could help this (it's kind of similar as many usage tracking frameworks for UIKit work), but as said, I think it can't be done that way. :slightly_frowning_face:
It would be interesting to see how analytics tools like Firebase do this... a quick google search seems to indicate that Firebase actually requires manually adding view identifiers to views (something that was done using swizzling for UIKit), so they seem to have not found a full Swift replacement for swizzling either...

3 Likes

Addendum:

Hm... the more I think about it, the more I doubt my own words (hence a new post).
I phrased this poorly above, as after all, it's not really the runtime that makes any decisions here, this should all happen during compile time...

In the end, I quickly tried it out a bit (without the shim) and indeed the compiler complains about "Ambiguous use of 'padding'". You would need to specify which padding to call in the view, but there is as said no syntax for that. The error appears not in the extension, but the view's implementation.

But even if there were such syntax, in the context of providing a debug toolkit for SwiftUI, it doesn't help as I understand it so that @aviel wants to basically inject code into other modules' views. Not write a view that intentionally and explicitly (with such magic syntax) specifies to use the debug padding, but rather any existing view that doesn't even know about the debug padding.

I might be wrong, but I think Swift intentionally does not allow us to do that (yet).

1 Like

Everything you wrote is currently my understanding as well unfortunately :frowning:

what's interesting is that for global functions, swift happily allows us to easily do this, which actually requires the compiler to provide us 2 things:

  1. Always prefer (without complaining) the function that is declared in current module over the one declared in imported one (even implicitly, like "Swift" module in the case of assertionFailure).
  2. Provide syntax to explicitly prefer the imported version, by doing SomeModule.someFunc()

Both of these are fully supported for global functions, and in the case of assertionFailure this is very useful - we can let engineers assert as usual (no custom function they need to learn about) and we can do something like call Swift.assertionFailure(..) in debug and log to some service in production, for example.

This is why I thought there must be some way to do the same thing to a typed function - which I assumed is inherently the same, just that it's called through a certain value instead of globally with passed parameters?

Yes, as said this is a feature that was discussed in the past, I believe. I only dimly remember, but there were ideas to use :: to specify the module somehow when invoking a function on a type, but I can't exactly recall and there are several downsides to this I think.
It's one of those things that on first glance seem simple, but come with quite a few quirks.
For example, what do you specify? Just the module? Or the type/extension, like in a java package or some other namespace? The latter poses the question: What does that mean for extensions? They're not really a "thing" other than markers in the syntax unlike a full type (or existential). Remember the pains we had to endure before we got any.

In this case here somehow specifying the module would be sufficient, but it's harder to come up with some good syntax for that than a bad one, I believe:

let myView = MyView()
let paddedWithPaddingFromModuleA = myView.ModuleA::padding(EdgeInsets())

Could be one example, but this reads quite ugly to me... the module specifier is in the middle, but of course the relevant type information comes from the myView variable, which is in front of it... so ... = ModuleA::myView.padding(EdgeInsets())? That somehow looks like myView belongs to the module, which it kind of doesn't, as it's a variable... whose type may come from yet another module...

I believe this has stopped the community from going forward with a fitting proposal so far, but an even more important reason is the relatively limited applicability:

As I said above, this does not give you the mechanic to make views you don't write yourself behave in any other way. Which then boils down to just a matter of aesthetics: How terrible is it that I cannot name my function the same as one from an existing framework? Since you must specify wich method to call anyway, just use a name that does not collide.

The only other use case that remains for specifying an instance's function that comes from extensions of two different modules is probably pretty rare: You imported two foreign modules into your code that define the exact same method. While this can happen (I guess), there are ways to work around this (splitting up code into different files/modules, writing shims), so the only real benefit for being able to specify the module is convenience.

Of course, it would still be a nice-to-have. Whether a form of swizzling like in Objective-C is really desirable I don't know... I always found the way e.g. Firebase "magically" invaded frameworks you rely on to do "inject" behavior fishy and intransparent... (and I remember there was once even a legal issue with it basically activating something ad identifier related without you as a coder knowing...).

1 Like

I tested my code out and it seems my code won't work. The compiler does, indeed, complain about the ambiguous use of 'padding'.

I thought that functions defined in the same module were preferred in overload resolution, but it seems that's only the case when a user-defined function shadows a function defined in the standard library.

@inlineable means that a declaration can be inlined, but it doesn't guarantee that it is. @inline(__always) and @_transparent are the (not officially supported) attributes that always inline code.

I don't think this is something Swift can support with pre-compiled modules, due to inlining and static dispatch.

2 Likes

Thank you both for the details!

I found a workaround in this particular case to overcome the ambiguity inside my padding - I make my function take BlahEdgeInsets with the same API, and then call the original version with EdgeInsets param. Unfortunately this just "pushes" the problem - the "naive" caller using padding will still get the "ambiguous use" instead of reaching my function. What's interesting though is that with a global function this is not the case...

I thought that functions defined in the same module were preferred in overload resolution, but it seems that's only the case when a user-defined function shadows a function defined in the standard library.

This would explain what i'm seeing - being able to shadow assertionFailure() from Swift but not padding() from SwiftUICore. Is there any history context to why that is the case? If anything I would expect it should be the opposite — other modules can be shadowed but std can't....?

I'm honestly not sure why the rule only applies to the standard library, and not all imports, but the reason it does apply to the standard library is so that they can add things to it without worrying about changing the meaning of user code. If you couldn't shadow names from the standard library, then we'd be forced to have reserved function names like in C, which is not ideal.

3 Likes

IMO, having module name in the middle makes perfect sense. It does not change the meaning of the variable, it only affects how the name is looked up. So it should apply either to the name, or to a lookup operator.

And also syntax should work for non-applied instance methods. So something like MyModule.Text.padding would not work, because Text is declared in SwiftUI, not in MyModule.

1 Like

oh that makes a lot of sense!

I think it's ok to just add the module as if it was part of the func name honestly:

myView.ModuleA.padding(...)

seems like the most straight forward to disambiguate myView.padding(...)

In theory, it still can be ambiguous - there can be a member named ModuleA.