Why don't extensions outside a type's defining module prevent you from redeclaring methods and properties?

An example to demonstrate:

import Foundation

extension Date {
	func timeIntervalSince(_ date: Date) -> TimeInterval { return 0.0 }
}

let d1 = Date(timeIntervalSinceReferenceDate: 0)
let d2 = Date(timeIntervalSinceReferenceDate: 100)
print(d1.timeIntervalSince(d2)) // prints 0.0

Without the extension, it will print the expected -100.0.

This was surprising to me - it seems desirable to get a compilation error here so that you don't inadvertently redeclare a method or property you didn't realize existed, and prevent yourself from accessing its true implementation.

One justification I can think of for this being allowed is that a compilation error could lead to something like the following:

  1. I have a public type X in my library
  2. An application depending on my library extends X to add a method foo
  3. I release a new version of my library where X has a new public method foo. this is an additive change and I don't expect to break backwards compatibility for my users, but it does for that user, since their extension won't compile anymore.

I think that is fair justification for allowing the behavior, but I'd argue that it should at least generate a compiler warning. As a programmer working on the application in the above scenario, I can imagine not knowing (or forgetting) that the extension exists, going to the most recent docs for type X and seeing a method foo there, and then being very confused about why when I call foo the actual library method isn't being used. And even if my foo did the same exact thing as the official foo and it would make no functional difference, I'd want a nudge to use the official library implementation rather than continuing to maintain my own.

Curious to hear any thoughts on this, or if anyone has any insight into why things ended up this way.

1 Like

Assuming you are using Darwin, because you are using Foundation classes, everything like Date derives from NSObject, the primo Objective-C class. If the code was pure Swift, you might get a warning/error since Swift does not, in general, support overriding methods in type extensions (according to the language reference, have not actually seen it in practice). However, if Objective-C classes derived from NSObject are involved, the Swift compiler gives a pass, since Objective-C dynamic behaviors allow for method overrides in Objective-C categories, from which the ideas for extensions came from. Extensions in conjunction with Objective-C classes are the mechanism that Objective-C categories are supported in Swift.

Not sure if this all applies to Linux, do most of my work on Mac OSX.

Have you tried this code in "pure" Swift, i.e., no use of Foundation, Cocoa, Appkit, etc., and see what happens.

This happens with a basic empty struct as well:

in ModuleA:

public struct X {
	public init() {}
	public func foo() { print("original foo") }
}

In ModuleB:

import ModuleA

extension X {
	public func foo() { print("new foo") }
}

let x = X()
x.foo() // prints "new foo"

Edited to add: Also, I was under the impression that Date is a struct and is just bridgeable to an Objective-C class (NSDate) if needed. Would just the bridge-ability make it inherit that override behavior?

No. You are correct. For clarity:

  • Date is just a structure.

  • Bridgeability affects type resolution, but it does not provide direct member access. (Consider how String does not have access to NSString’s NSRange‐based API, and vice‐versa.)

  • These are not method overrides we are talking about. None of this has anything to do with classes or protocols, let alone Objective‐C. The compiler dispatches all the provided sample code directly. Nothing is affected by any runtime.

That said, I cannot answer your original question. I don’t know how intentional the behaviour you describe is.

Global functions are intentionally shadowable, but they are namespaced according to module, and thus can be disambiguated. I suspect the behaviour for methods is mostly just a side‐effect of the design for functions, despite the whole system breaking down for them due to the lack of a namespace:

A related, more common, and more pressing question is “When ModuleA and ModuleB both extend SomeType to provide someMethod(), the compiler cannot tell the difference in my ModuleC. How do I disambiguate in order to call someMethod()?” Many workarounds for that involve declaring an additional shadow in the current module, which the compiler then selects over the other two.