I have a pull request up to introduce “near-miss” warnings for protocol conformances:
A “near-miss” warning fires when there is a protocol conformance for which:
1) One of the requirements is satisfied by a “default” definition (e.g., one from a protocol extension), and
2) There is a member of the same nominal type declaration (or extension declaration) that declared the conformance that has the same name as the requirement and isn’t satisfying another requirement.
These are heuristics, of course, and we can tune the heuristics over time. By way of experiment, here are the results of running this on the standard library. Here’s the first one:
/Users/dgregor/Projects/swift/swift/stdlib/public/core/DoubleWidth.swift.gyb:27:3: warning: initializer 'init' nearly matches defaulted requirement 'init' of protocol 'LosslessStringConvertible'
init(_ _value: (High, Low)) {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/DoubleWidth.swift.gyb:27:3: note: candidate has non-matching type '(DoubleWidth.High, DoubleWidth.Low)'
init(_ _value: (High, Low)) {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/DoubleWidth.swift.gyb:27:3: note: move 'init' to an extension to silence this warning
init(_ _value: (High, Low)) {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/OutputStream.swift:182:3: note: requirement 'init' declared here
init?(_ description: String)
^
Unlabeled single-value initializers are probably going to cause a number of false positives, because we can’t figure out which one we meant.
/Users/dgregor/Projects/swift/swift/stdlib/public/core/HashedCollections.swift.gyb:835:15: warning: instance method 'filter' nearly matches defaulted requirement 'filter' of protocol 'Sequence'
public func filter(
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/HashedCollections.swift.gyb:835:15: note: candidate has non-matching type '<Element> ((Set.Element) throws -> Bool) throws -> Set<Element>' (aka '<τ_0_0> ((τ_0_0) throws -> Bool) throws -> Set<τ_0_0>') [with Element = Element, Iterator = SetIterator<Element>, SubSequence = Slice<Set<Element>>]
public func filter(
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/HashedCollections.swift.gyb:835:15: note: move 'filter' to an extension to silence this warning
public func filter(
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/Sequence.swift:383:8: note: requirement 'filter' declared here
func filter(
^
The filter method it’s warning on produces a Set, whereas the requirement expects an Array<Element>. This is a case of intentional overloading, but IMO it’s a reasonable thing to warn about.
/Users/dgregor/Projects/swift/swift/stdlib/public/core/HashedCollections.swift.gyb:2021:10: warning: subscript 'subscript' nearly matches defaulted requirement 'subscript' of protocol 'Collection'
public subscript(key: Key) -> Value? {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/HashedCollections.swift.gyb:2021:10: note: candidate has non-matching type '<Key, Value where Key : Hashable> (Dictionary.Key) -> Dictionary.Value?' (aka '<τ_0_0, τ_0_1 where τ_0_0 : Hashable> (τ_0_0) -> Optional<τ_0_1>') [with IndexDistance = Int, Iterator = DictionaryIterator<Key, Value>, SubSequence = Slice<Dictionary<Key, Value>>, Indices = DefaultIndices<Dictionary<Key, Value>>]
public subscript(key: Key) -> Value? {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/HashedCollections.swift.gyb:2021:10: note: move 'subscript' to an extension to silence this warning
public subscript(key: Key) -> Value? {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/Collection.swift:454:3: note: requirement 'subscript' declared here
subscript(bounds: Range<Index>) -> SubSequence { get }
^
The defaulted Range subscript operation for Dictionary is reasonable, but it has a few other subscripts. I think this is a reasonable diagnostic, despite being a false positive.
/Users/dgregor/Projects/swift/swift/stdlib/public/core/HashedCollections.swift.gyb:2063:15: warning: instance method 'filter' nearly matches defaulted requirement 'filter' of protocol 'Sequence'
public func filter(
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/HashedCollections.swift.gyb:2063:15: note: candidate has non-matching type '<Key, Value> ((Dictionary.Element) throws -> Bool) throws -> [Dictionary.Key : Dictionary.Value]' (aka '<τ_0_0, τ_0_1> (((key: τ_0_0, value: τ_0_1)) throws -> Bool) throws -> Dictionary<τ_0_0, τ_0_1>') [with Iterator = DictionaryIterator<Key, Value>, SubSequence = Slice<Dictionary<Key, Value>>]
public func filter(
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/HashedCollections.swift.gyb:2063:15: note: move 'filter' to an extension to silence this warning
public func filter(
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/Sequence.swift:383:8: note: requirement 'filter' declared here
func filter(
^
Same as the “filter” example above, but for Dictionary. This is a reasonable diagnostic.
/Users/dgregor/Projects/swift/swift/stdlib/public/core/Integers.swift.gyb:2962:10: warning: initializer 'init' nearly matches defaulted requirement 'init' of protocol 'LosslessStringConvertible'
public init(_ source: Float) {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/Integers.swift.gyb:2962:10: note: candidate has non-matching type 'Float'
public init(_ source: Float) {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/Integers.swift.gyb:2962:10: note: move 'init' to an extension to silence this warning
public init(_ source: Float) {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/OutputStream.swift:182:3: note: requirement 'init' declared here
init?(_ description: String)
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/Integers.swift.gyb:2962:10: warning: initializer 'init' nearly matches defaulted requirement 'init' of protocol 'LosslessStringConvertible'
public init(_ source: Float) {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/Integers.swift.gyb:2962:10: note: candidate has non-matching type 'Float'
public init(_ source: Float) {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/Integers.swift.gyb:2962:10: note: move 'init' to an extension to silence this warning
public init(_ source: Float) {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/OutputStream.swift:182:3: note: requirement 'init' declared here
init?(_ description: String)
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/Integers.swift.gyb:2962:10: warning: initializer 'init' nearly matches defaulted requirement 'init' of protocol 'LosslessStringConvertible'
public init(_ source: Float) {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/Integers.swift.gyb:2962:10: note: candidate has non-matching type 'Float'
public init(_ source: Float) {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/Integers.swift.gyb:2962:10: note: move 'init' to an extension to silence this warning
public init(_ source: Float) {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/OutputStream.swift:182:3: note: requirement 'init' declared here
init?(_ description: String)
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/Integers.swift.gyb:2962:10: warning: initializer 'init' nearly matches defaulted requirement 'init' of protocol 'LosslessStringConvertible'
public init(_ source: Float) {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/Integers.swift.gyb:2962:10: note: candidate has non-matching type 'Float'
public init(_ source: Float) {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/Integers.swift.gyb:2962:10: note: move 'init' to an extension to silence this warning
public init(_ source: Float) {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/OutputStream.swift:182:3: note: requirement 'init' declared here
init?(_ description: String)
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/Integers.swift.gyb:2962:10: warning: initializer 'init' nearly matches defaulted requirement 'init' of protocol 'LosslessStringConvertible'
public init(_ source: Float) {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/Integers.swift.gyb:2962:10: note: candidate has non-matching type 'Float'
public init(_ source: Float) {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/Integers.swift.gyb:2962:10: note: move 'init' to an extension to silence this warning
public init(_ source: Float) {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/OutputStream.swift:182:3: note: requirement 'init' declared here
init?(_ description: String)
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/Integers.swift.gyb:2962:10: warning: initializer 'init' nearly matches defaulted requirement 'init' of protocol 'LosslessStringConvertible'
public init(_ source: Float) {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/Integers.swift.gyb:2962:10: note: candidate has non-matching type 'Float'
public init(_ source: Float) {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/Integers.swift.gyb:2962:10: note: move 'init' to an extension to silence this warning
public init(_ source: Float) {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/OutputStream.swift:182:3: note: requirement 'init' declared here
init?(_ description: String)
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/Integers.swift.gyb:2962:10: warning: initializer 'init' nearly matches defaulted requirement 'init' of protocol 'LosslessStringConvertible'
public init(_ source: Float) {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/Integers.swift.gyb:2962:10: note: candidate has non-matching type 'Float'
public init(_ source: Float) {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/Integers.swift.gyb:2962:10: note: move 'init' to an extension to silence this warning
public init(_ source: Float) {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/OutputStream.swift:182:3: note: requirement 'init' declared here
init?(_ description: String)
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/Integers.swift.gyb:2962:10: warning: initializer 'init' nearly matches defaulted requirement 'init' of protocol 'LosslessStringConvertible'
public init(_ source: Float) {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/Integers.swift.gyb:2962:10: note: candidate has non-matching type 'Float'
public init(_ source: Float) {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/Integers.swift.gyb:2962:10: note: move 'init' to an extension to silence this warning
public init(_ source: Float) {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/OutputStream.swift:182:3: note: requirement 'init' declared here
init?(_ description: String)
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/Integers.swift.gyb:2962:10: warning: initializer 'init' nearly matches defaulted requirement 'init' of protocol 'LosslessStringConvertible'
public init(_ source: Float) {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/Integers.swift.gyb:2962:10: note: candidate has non-matching type 'Float'
public init(_ source: Float) {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/Integers.swift.gyb:2962:10: note: move 'init' to an extension to silence this warning
public init(_ source: Float) {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/OutputStream.swift:182:3: note: requirement 'init' declared here
init?(_ description: String)
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/Integers.swift.gyb:2962:10: warning: initializer 'init' nearly matches defaulted requirement 'init' of protocol 'LosslessStringConvertible'
public init(_ source: Float) {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/Integers.swift.gyb:2962:10: note: candidate has non-matching type 'Float'
public init(_ source: Float) {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/Integers.swift.gyb:2962:10: note: move 'init' to an extension to silence this warning
public init(_ source: Float) {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/OutputStream.swift:182:3: note: requirement 'init' declared here
init?(_ description: String)
^
LosslessStringConvertible’s single-argument, unlabeled initializer is causing a *lot* of false positives :(.
/Users/dgregor/Projects/swift/swift/stdlib/public/core/UIntBuffer.swift:202:24: warning: instance method 'removeFirst()' nearly matches defaulted requirement 'removeFirst()' of protocol 'RangeReplaceableCollection'
public mutating func removeFirst() {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/UIntBuffer.swift:202:24: note: candidate has non-matching type '<Storage, Element> () -> ()'
public mutating func removeFirst() {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/UIntBuffer.swift:202:24: note: move 'removeFirst()' to another extension to silence this warning
public mutating func removeFirst() {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/RangeReplaceableCollection.swift.gyb:323:17: note: requirement 'removeFirst()' declared here
mutating func removeFirst() -> Element
^
Smells like a bug: we end up getting the default removeFirst() implementation, because the one written within the struct doesn’t return the element type.
/Users/dgregor/Projects/swift/swift/stdlib/public/core/ValidUTF8Buffer.swift:178:24: warning: instance method 'removeFirst()' nearly matches defaulted requirement 'removeFirst()' of protocol 'RangeReplaceableCollection'
public mutating func removeFirst() {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/ValidUTF8Buffer.swift:178:24: note: candidate has non-matching type '<Storage> () -> ()'
public mutating func removeFirst() {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/ValidUTF8Buffer.swift:178:24: note: move 'removeFirst()' to another extension to silence this warning
public mutating func removeFirst() {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/RangeReplaceableCollection.swift.gyb:323:17: note: requirement 'removeFirst()' declared here
mutating func removeFirst() -> Element
^
Also looks like a bug!
/Users/dgregor/Projects/swift/swift/stdlib/public/core/ValidUTF8Buffer.swift:205:24: warning: instance method 'append(contentsOf:)' nearly matches defaulted requirement 'append(contentsOf:)' of protocol 'RangeReplaceableCollection'
public mutating func append<T>(contentsOf other: _ValidUTF8Buffer<T>) {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/ValidUTF8Buffer.swift:205:24: note: candidate has non-matching type '<Storage, T> (contentsOf: _ValidUTF8Buffer<T>) -> ()'
public mutating func append<T>(contentsOf other: _ValidUTF8Buffer<T>) {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/ValidUTF8Buffer.swift:205:24: note: move 'append(contentsOf:)' to another extension to silence this warning
public mutating func append<T>(contentsOf other: _ValidUTF8Buffer<T>) {
^
/Users/dgregor/Projects/swift/swift/stdlib/public/core/RangeReplaceableCollection.swift.gyb:200:17: note: requirement 'append(contentsOf:)' declared here
mutating func append<S : Sequence>(contentsOf newElements: S)
^
It… might… be a false positive? I don’t really understand why _ValidUTF8Buffer’s append here takes a parameterized _ValidUTF8Buffer. At best, it’d likely be more efficient to implement a customization of append(contentsOf:) in _ValidUTF8Buffer, which would suppress the diagnostic.
Thoughts?
- Doug