"Near-miss" warnings for protocol conformances

I have a pull request up to introduce “near-miss” warnings for protocol conformances:

  Warn on “near-misses” when defaults are used for protocol witnesses. by DougGregor · Pull Request #12645 · apple/swift · GitHub

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

Thoughts?

It’s a real problem, but I think there are better ways to fight it… none the less, it’s the best countermeasure that is implemented now, so: Is there any impact on performance?

Hi Doug,

This all looks very promising. Do you have any thoughts about how one would go about suppressing false positives?

Ted

···

On Oct 26, 2017, at 9:28 PM, Douglas Gregor via swift-dev <swift-dev@swift.org> wrote:

Unlabeled single-value initializers are probably going to cause a number of false positives, because we can’t figure out which one we meant.

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@swift.org> wrote:

I have a pull request up to introduce “near-miss” warnings for protocol conformances:

  Warn on “near-misses” when defaults are used for protocol witnesses. by DougGregor · Pull Request #12645 · apple/swift · GitHub

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@swift.org
https://lists.swift.org/mailman/listinfo/swift-dev

Unlabeled single-value initializers are probably going to cause a number of false positives, because we can’t figure out which one we meant.

When we're dealing with unlabeled initializers, can we use tighter rules? For instance, perhaps it should only warn if the near miss has the same parameter types, but different fallibility/throwing/etc., or if the parameter types are closely related (e.g. the concrete parameter type is a subtype of the required parameter type).

Alternatively, perhaps we should have an annotation saying that a given default implementation is expected to be used by concrete types and it's not surprising for them to miss. Or we could disable near-miss warnings if a refinement provides a default implementation for a base protocol requirement.

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.

Would it make sense to suppress the warning if the requirement has some fixed return type, but the implementation you're warning about returns (some specialization of) `Self`? Or do you think this is too specific to `Collection` and these kinds of return-type overloads are rare in practice?

···

On Oct 26, 2017, at 9:28 PM, Douglas Gregor via swift-dev <swift-dev@swift.org> wrote:

--
Brent Royal-Gordon
Architechies

No measurable impact on performance.

  - Doug

···

On Oct 29, 2017, at 10:02 AM, Tino Heth <2th@gmx.de> wrote:

Thoughts?

It’s a real problem, but I think there are better ways to fight it… none the less, it’s the best countermeasure that is implemented now, so: Is there any impact on performance?

Yes, you can move the declaration it’s warning about to a different extension to suppress the false positive.

  - Doug

···

On Oct 31, 2017, at 11:08 PM, Ted Kremenek <kremenek@apple.com> wrote:

On Oct 26, 2017, at 9:28 PM, Douglas Gregor via swift-dev <swift-dev@swift.org> wrote:

Unlabeled single-value initializers are probably going to cause a number of false positives, because we can’t figure out which one we meant.

Hi Doug,

This all looks very promising. Do you have any thoughts about how one would go about suppressing false positives?

Unlabeled single-value initializers are probably going to cause a number of false positives, because we can’t figure out which one we meant.

When we're dealing with unlabeled initializers, can we use tighter rules? For instance, perhaps it should only warn if the near miss has the same parameter types, but different fallibility/throwing/etc., or if the parameter types are closely related (e.g. the concrete parameter type is a subtype of the required parameter type).

Yeah, I’ll suppress the diagnostic for an initializer with unlabeled parameters. Also for unlabeled subscripts, I think, because they have the same general issue where the base name is fixed by the language. This is implemented in the last commit of https://github.com/apple/swift/pull/12808\.

Alternatively, perhaps we should have an annotation saying that a given default implementation is expected to be used by concrete types and it's not surprising for them to miss. Or we could disable near-miss warnings if a refinement provides a default implementation for a base protocol requirement.

I’m really trying to avoid growing the language with additional annotations.

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.

Would it make sense to suppress the warning if the requirement has some fixed return type, but the implementation you're warning about returns (some specialization of) `Self`? Or do you think this is too specific to `Collection` and these kinds of return-type overloads are rare in practice?

I suspect that these are too specific to Collection

  - Doug

···

On Nov 2, 2017, at 6:07 PM, Brent Royal-Gordon <brent@architechies.com> wrote:

On Oct 26, 2017, at 9:28 PM, Douglas Gregor via swift-dev <swift-dev@swift.org> wrote:

Unlabeled single-value initializers are probably going to cause a number of false positives, because we can’t figure out which one we meant.

Hi Doug,

This all looks very promising. Do you have any thoughts about how one would go about suppressing false positives?

Yes, you can move the declaration it’s warning about to a different extension to suppress the false positive.

Cool. Will that be reflected in a ‘note’?

···

On Oct 31, 2017, at 11:09 PM, Douglas Gregor <dgregor@apple.com> wrote:

On Oct 31, 2017, at 11:08 PM, Ted Kremenek <kremenek@apple.com> wrote:

On Oct 26, 2017, at 9:28 PM, Douglas Gregor via swift-dev <swift-dev@swift.org> wrote:

   - Doug

… it’s up there in my original post:

/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>) {
                      ^

  - Doug

···

On Nov 2, 2017, at 7:47 AM, Ted Kremenek <kremenek@apple.com> wrote:

On Oct 31, 2017, at 11:09 PM, Douglas Gregor <dgregor@apple.com> wrote:

On Oct 31, 2017, at 11:08 PM, Ted Kremenek <kremenek@apple.com> wrote:

On Oct 26, 2017, at 9:28 PM, Douglas Gregor via swift-dev <swift-dev@swift.org> wrote:

Unlabeled single-value initializers are probably going to cause a number of false positives, because we can’t figure out which one we meant.

Hi Doug,

This all looks very promising. Do you have any thoughts about how one would go about suppressing false positives?

Yes, you can move the declaration it’s warning about to a different extension to suppress the false positive.

Cool. Will that be reflected in a ‘note’?