Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SR-1996] Accessing count inline on a filtered Lazy{Map,Filter}Collection causes ambiguous use of filter() #44605

Closed
ffried opened this issue Jul 6, 2016 · 18 comments
Assignees
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself standard library Area: Standard library umbrella

Comments

@ffried
Copy link
Contributor

ffried commented Jul 6, 2016

Previous ID SR-1996
Radar rdar://problem/27198177
Original Reporter @ffried
Type Bug
Status Resolved
Resolution Done
Environment

Xcode 7.3.1 (7D1014)
OS X 10.11.5 (15F34)

Additional Detail from JIRA
Votes 1
Component/s Compiler, Standard Library
Labels Bug
Assignee @xedin
Priority Medium

md5: ffe0ef4741407bbc4173d4fc97cb3ebd

duplicates:

  • SR-851 Result of dropFirst differs between 2.1.1 and 2.2

Issue Description:

When accessing the count property of a LazyFilterCollection generated by filtering a LazyMapCollection generated by accessing values of a Dictionary<K, V> causes an error about an ambiguous use of filter().

Sample Code:

let dict = [
    "Key1": "ABCDEFG",
    "Key2": "HIJKLMN",
    "Key3": "OPQRSTU",
    "Key4": "VWXYZ"
]

dict.values.filter { $0.hasPrefix("ABC") }.count // <- Error: Ambiguous use of 'filter'

Full Error Message:

error: ambiguous use of 'filter'
dict.values.filter { $0.hasPrefix("ABC") }.count
     ^
Swift.SequenceType:11:17: note: found this candidate
    public func filter(@noescape includeElement: (Self.Generator.Element) throws -> Bool) rethrows -> [Self.Generator.Element]
                ^
Swift.LazyCollectionType:9:17: note: found this candidate
    public func filter(predicate: (Self.Elements.Generator.Element) -> Bool) -> LazyFilterCollection<Self.Elements>

Workaround

Filtering the Dictionary<K,V> directly works fine.
Also, it works when storing the results of filter() in a variable and accessing count on this variable.
So does converting either the LazyFilterCollection or the LazyMapCollection into an Array<T> before using filter(), or count respectively.

Samples:

dict.filter { $1.hasPrefix("ABC") }.count

let filtered = dict.values.filter { $0.hasPrefix("ABC") }
filtered.count

Array(dict.values.filter { $0.hasPrefix("ABC") }).count
Array(dict.values).filter { $0.hasPrefix("ABC") }.count

Update 1

In Swift 4, this can no longer be reproduced using a Dictionary, since values of a Dictionary is no longer lazy.
The underlying issue always was with LazyMapCollection or LazyFilterCollection. This code reproduces this in Swift 4:

let arr = ["A", "B", "C"]
let lazy: LazyMapCollection = arr.lazy.map { $0 }
lazy.filter { $0 > "A" }.count // <- Error: Ambiguous use of 'filter'
@swift-ci
Copy link
Collaborator

swift-ci commented Jul 6, 2016

Comment by Zenny Chen (JIRA)

There're two overloaded filter methods here and both have the same parameter list but different return types. So, you should specify the definite type of the method you want to invoke.

The following code is OK:

        let dict = [
            "Key1": "ABCDEFG",
            "Key2": "HIJKLMN",
            "Key3": "OPQRSTU",
            "Key4": "VWXYZ"
        ]
        
        // Here, use `as` to specify the right method to invoke
        let count = (dict.values.filter as ((String) -> Bool) -> LazyFilterCollection<LazyMapCollection<[String:String], String>>)() { $0.hasPrefix("ABC") }.count
        
        print("count = \(count)")

It works though the type using `as` to specify is too complicated... 🙂

@ffried
Copy link
Contributor Author

ffried commented Jul 6, 2016

Interestingly it only happens when accessing count. Using filter() alone or even something else like map() or first works fine.
How does it choose an overload in one of these other cases?

@swift-ci
Copy link
Collaborator

swift-ci commented Jul 6, 2016

Comment by Zenny Chen (JIRA)

Without accessing the property `count`, the method `filter` doesn't need the result and it may choose an either method. Because there is no return value and no other side effects, either of these methods is OK. I suppose it is an optimization.

There's another easy way to work around this problem:

        let values = [String](dict.values)
        let count = values.filter() {
            return $0.hasPrefix("ABC")
        }.count

Convert the LazyMapCollection type to an array, and that'll be simpler.

@ffried
Copy link
Contributor Author

ffried commented Jul 6, 2016

Yea. This is one of the workarounds I mentioned.

@swift-ci
Copy link
Collaborator

swift-ci commented Jul 6, 2016

Comment by Zenny Chen (JIRA)

What's weird, the following code has no problem:

        let tmp = dict.values.filter() {
            return $0.hasPrefix("ABC")
        }

According to the debugger, the type of `tmp` is LazyFilterCollection.
We'll wait for the core Swift developers or experts to explain.

@ffried
Copy link
Contributor Author

ffried commented Jul 6, 2016

Yep. That's exactly what I meant before. Since it's able to choose an implementation in this case, it should also be able to choose an implementation in case I call count.

@belkadan
Copy link
Contributor

belkadan commented Jul 6, 2016

@gribozavr, @moiseev, there was another one like this recently, right?

@gribozavr
Copy link
Collaborator

I can't find it, but yes, we have lots of similar issues, that are caused by the same root issue.

@moiseev
Copy link
Mannequin

moiseev mannequin commented Jul 6, 2016

https://bugs.swift.org/browse/SR-461 and https://bugs.swift.org/browse/SR-1856 and https://bugs.swift.org/browse/SR-851 are believed to be caused by the same overload resolution behavior.

@ffried
Copy link
Contributor Author

ffried commented Sep 4, 2017

There's no error in Swift 4 anymore. Has this been fixed?

@moiseev
Copy link
Mannequin

moiseev mannequin commented Sep 5, 2017

@ffried The type of `Dictionary.Values` is different between Swift 3 and Swift 4. See https://github.com/apple/swift/blob/master/stdlib/public/core/HashedCollections.swift.gyb#L2406 and https://github.com/apple/swift/blob/master/stdlib/public/core/HashedCollections.swift.gyb#L2449

The described problem arises whenever you're calling .filter { ... }.count on a lazy sequence/collection. In Swift 4 `Dictionary.Values` is no longer lazy.

@ffried
Copy link
Contributor Author

ffried commented Sep 6, 2017

Thanks for the info, @moiseev. Didn't notice this change.

So, is there an issue tracking this particular version of the issue (.filter { ... }.count on a lazy sequence / collection)? Otherwise I'd update this report to better reflect the actual issue.

These few lines are enough to reproduce it:

let arr = ["A", "B", "C"]
let lazy: LazyMapCollection = arr.lazy.map { $0 }
lazy.filter { $0 > "A" }.count // <- Error: Ambiguous use of 'filter'

@moiseev
Copy link
Mannequin

moiseev mannequin commented Sep 6, 2017

@ffried I don't know of a JIRA that this can be duped to, so I'd leave it as is. Please update the description with a shorter reproduction case. Thanks!

@swift-ci
Copy link
Collaborator

Comment by Chris Aljoudi (JIRA)

This also occurs, similarly, when accessing `.first`

See realm/realm-swift#5319

@xedin
Copy link
Member

xedin commented Nov 17, 2017

This is resolved by #12520 going to prefer LazyFilterCollection over Array for `filter` call. @ffried please verify and resolve. I'm going to add test-case to the suite as well to make sure this doesn't regress.

@ffried
Copy link
Contributor Author

ffried commented Nov 22, 2017

@xedin I've just downloaded the latest development snapshot from https://swift.org/download/#snapshots (created on Nov 21th) but it still gives me an ambiguous use of filter for the simplified reproduction case:

let arr = ["A", "B", "C"]
let lazy: LazyMapCollection = arr.lazy.map { $0 }
lazy.filter { $0 > "A" }.count // <- Error: Ambiguous use of 'filter'

Two candidates are found: one in LazyCollectionProtocol and one in Sequence.

Am I missing something?

@xedin
Copy link
Member

xedin commented Nov 22, 2017

@ffried You should try with "Trunk Snapshot", here is what I get:

Welcome to Apple Swift version 4.1-dev (LLVM fe49d8f2ca, Clang 839070845c, Swift c0c04864db). Type :help for assistance.
  1> let arr = ["A", "B", "C"]
  2. let lazy: LazyMapCollection = arr.lazy.map { $0 }
  3. lazy.filter { $0 > "A" }.count
$R0: Int = 2
arr: [String] = 3 values {
  [0] = "A"
  [1] = "B"
  [2] = "C"
}
lazy: LazyMapCollection<[String], String> = {
  _base = 3 values {
    [0] = "A"
    [1] = "B"
    [2] = "C"
  }
  _transform = 0x00000001017224d0 $__lldb_expr2`partial apply forwarder for reabstraction thunk helper from @escaping @callee_guaranteed (@owned Swift.String) -> (@owned Swift.String) to @escaping @callee_guaranteed (@in Swift.String) -> (@out Swift.String) at repl1-b88c27..swift
}
  4>

@ffried
Copy link
Contributor Author

ffried commented Dec 5, 2017

@xedin Yep, you're right. Seems like I used a Swift 4.1 Snapshot.

Works fine now - fix confirmed

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself standard library Area: Standard library umbrella
Projects
None yet
Development

No branches or pull requests

5 participants