[swift-dev] "Near-miss" warnings for protocol conformances

Johannes Weiß johannesweiss at apple.com
Wed Nov 1 19:25:13 CDT 2017


That sounds awesome! Don't have much time right now to check the details but these 'near misses' have been a real problem for us.

> On 26 Oct 2017, at 9:28 pm, Douglas Gregor via swift-dev <swift-dev at swift.org> wrote:
> 
> I have a pull request up to introduce “near-miss” warnings for protocol conformances:
> 
> 	https://github.com/apple/swift/pull/12645
> 
> 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
> 
> _______________________________________________
> swift-dev mailing list
> swift-dev at swift.org
> https://lists.swift.org/mailman/listinfo/swift-dev



More information about the swift-dev mailing list