swiftlang / swift

The Swift Programming Language
https://swift.org
Apache License 2.0
67.51k stars 10.35k forks source link

[SR-6689] Compiler permits more than one property implementation with same name but differing types via protocol extensions #49238

Open swift-ci opened 6 years ago

swift-ci commented 6 years ago
Previous ID SR-6689
Radar None
Original Reporter marcpalmer (JIRA User)
Type Bug
Environment Xcode 9.2 GM
Additional Detail from JIRA | | | |------------------|-----------------| |Votes | 0 | |Component/s | Compiler | |Labels | Bug | |Assignee | None | |Priority | Medium | md5: 1273b80bd7c1605a99edfd258f9430bd

Issue Description:

This is a source of hard to find and potentially very dangerous bugs.

Take the following code:

protocol P {}
extension P {
  static var x: Int { return 2 }
}
struct S: P {
  static var x = ""
}

// Outputs "S.x is: [], with type String"
print("S.x is: [\(S.x)], with type \(type(of: S.x))")

let viaProtocol: P.Type = S.self
// Outputs "S.x is: [2], with type Int"
print("S.x is: [\(viaProtocol.x)], with type \(type(of: viaProtocol.x))")

The result is a type S with "two implementations" of x with different types, depending on how you access the property.

The compiler considers S to conform to P because of the protocol extension, but silently ignores the declaration specific to S which has a different type.

This results in different call sites in the app seeing entirely different values and due to static compilation and type safety, everything seems fine!

There appear to be two failings:

1. Extensions are deemed sufficient to satisfy protocol conformance, even if a Type provides its own value/implementation with a different type

2. The compiler permits redeclaration of a property with a different type, when there is an extension that already declares a different type for that property (in the case where the protocol does not have the property as a requirement)

Note that the above problem still happens even if P defines x as a requirement, which is even more surprising given that S redeclares it with a different type and the protocol is clearly mandating a different type.

UPDATE
This is even more pernicious if the difference in type is just optionality:

protocol P {
  static var x: String?
}
extension P {
  static var x: String? { return nil }
}
struct S: P {
  static var x: String = ""
}

In this case, due to type inference the results can be particularly confusing depending on the context in which you access the property, with the compiler making choices for your based on the context and returning different values.

swift-ci commented 6 years ago

Comment by Marc Palmer (JIRA)

Here's another concrete use case where type inference breaks this and leads to unexpected behaviour:

protocol Feature {
  static var parent: Feature.Type? { get }
}
extension Feature {
  static var parent: Feature.Type? { return nil }
}

class MyFeature: Feature {
   public static let parent = RemoteControlFeature.self
}

This code compiles fine, but `parent` is nil when read from a `Feature.Type`. Relying on type inference has allowed it to appear to do the right thing but it has merely shadowed the extension's implementation. Explicitly typing the property solves this. This seems pretty bad as avoiding explicit typing is encouraged by Swift.

class MyFeature: Feature {
   public static let parent: Feature.Type? = RemoteControlFeature.self
}
swift-ci commented 6 years ago

Comment by Marc Palmer (JIRA)

The above actually means you cannot use type inference at all for properties where the property is optional and has an extension implementing it