here was a really subtle bug i encountered today:
protocol Nominal
{
var name:String? { get }
}
extension Nominal
{
var name:String? { nil }
}
struct Struct:Nominal
{
var name:String
}
let x:any Nominal = Struct.init(name: "x")
print(x.name as Any)
remarkably, this prints nil, as the compiler prefers the default nil-returning implementation over the actual stored property.
the bug arose from a fairly innocuous change:
struct Struct:Nominal
{
- var name:String?
+ var name:String
}
there should really be a warning when this happens?
6 Likes
Jumhyn
(Frederick Kellison-Linn)
2
This sort of near-miss bug for witnesses of protocol requirements with default implementations is really pernicious. Something narrowly tailored for the optional case seems potentially valuable, though weâd need a way to silence the diagnosticâbackticks, I guess?
Iâve also seen this arise in cases where a defaulted requirement f is removed as a customization point (the default implementation remains), but a previous witness of f now becomes a shadowing declaration with different semantics. I would like if there were some way to say âthis declaration witnesses some requirementâ so that youâd get a warning/error in these sorts of scenarios. IMO dovetails nicely with formalizing @_implements as well.
2 Likes
jrose
(Jordan Rose)
3
I used to have the SR number for this memorized. 
Default implementations definitely make this rougherâŚ
1 Like
This is reasonable. We already have something similar for @objc optional requirements, where a near-miss can also cause silent confusion; we silence the warning by moving the near-miss witness to a different context (extension vs nominal type) than the conformance itself. Another case we might want to warn about is when a subclass attempts to "override" a protocol requirement witnessed by a default implementation; in this case there's no vtable slot in the superclass:
protocol P { func f() }
extension P { func f() {} }
class C: P {}
class D: C { func f() {} } // not what you expect
4 Likes
Jumhyn
(Frederick Kellison-Linn)
5
This does have the downside that you wouldnât be able to opt into the warning on a stored property if thereâs a general policy of âdeclare conformances and their witnesses as separate extensionsâ since the stored properties would then ~always be separated from the extension where the conformance is declared.
1 Like
This is especially annoying because inits are able to do this, and it seems natural that this should work for functions too.
jrose
(Jordan Rose)
7
Iâm not going to defend return type overloading, but thatâs why things are different here: inits do not allow overloading by failability but methods do. Of course, properties donât allow overloading at all, so this isnât a principled justification by any means; itâs just an explanation of why it happens to work for inits in the compiler implementation.
4 Likes
I don't suppose there's any way (in theory on paper) to get this to work despite the existance of return type overloading?
jrose
(Jordan Rose)
9
You can read the discussion in the Issue. It works for method overrides, so it could certainly work for protocol requirements; the trouble is itâs a source-breaking change, and a very subtle one at that. So at the very least it would have to be done in a new swift-version.
2 Likes
Nobody1707
(Nobody1707)
10
Well at least it's not ABI breaking, I'll just wait for Swift 7 then. :P
tera
11
Is optionality important here? I'm getting the same behaviour in the following example:
protocol Nominal {
var name: String { get }
}
extension Nominal {
var name: String { "default implementation" }
}
struct Struct: Nominal {
var name: Int
}
let x: Nominal = Struct(name: 0)
print(x.name) // "default implementation"
Ditto when using methods instead of vars
protocol Nominal {
func name() -> String
}
extension Nominal {
func name() -> String { "default implementation" }
}
struct Struct: Nominal {
func name() -> Int { 42 }
}
let x: Nominal = Struct()
print(x.name()) // "default implementation"
1 Like
the difference with Int is that the type checker would likely prevent you from accidentally calling the wrong overload. whereas there is nothing preventing the wrong overload from being called here:
let name:String? = x.name
tera
13
Int/String was just an example, here's another one:
class I {}
class S: I {}
protocol Nominal {
var name: S { get }
}
extension Nominal {
var name: S { S() }
}
struct Struct: Nominal {
var name: I { I() }
}
let x: Nominal = Struct()
let name1: S = x.name
let name2: I = x.name
print(type(of: name1)) // S
print(type(of: name2)) // S
// compiler is happy, no warnings
The point of these examples to show it's not just about Optional.