9

I have the some code:

directoryChooser.title = "Select the directory"
val file = directoryChooser.showDialog(null)
if (file != null) {
    var files = Files.list(file.toPath())
            .filter { f ->
                f.fileName.endsWith("zip") && f.fileName.endsWith("ZIP")
                        && (f.fileName.startsWith("1207") || f.fileName.startsWith("4407") || f.fileName.startsWith("1507") || f.fileName.startsWith("9007") || f.fileName.startsWith("1807"))
            }
    for (f in files) {
        textArea.appendText(f.toString() + "\n")
    }
}

If I call collect(Collectors.toList()) at the end of filter, I get:

Error:(22, 13) Kotlin: [Internal Error] org.jetbrains.kotlin.codegen.CompilationException: Back-end (JVM) Internal error: no descriptor for type constructor of ('Captured(in ('Path'..'Path?'))'..'CapturedTypeConstructor(in ('Path'..'Path?'))?')
Cause: no descriptor for type constructor of ('Captured(in ('Path'..'Path?'))'..'CapturedTypeConstructor(in ('Path'..'Path?'))?')
File being compiled and position: (22,13) in D:/My/devel/ListOfReestrs/src/Controller.kt
PsiElement: var files = Files.list(file.toPath())
                    .filter { f ->
                        f.fileName.endsWith("zip") && f.fileName.endsWith("ZIP")
                                && (f.fileName.startsWith("1207") || f.fileName.startsWith("4407") || f.fileName.startsWith("1507") || f.fileName.startsWith("9007") || f.fileName.startsWith("1807"))
                    }.collect(Collectors.toList())
The root cause was thrown at: JetTypeMapper.java:430

If I don't do this, I get the f with the type [error: Error] in my for-loop.

Jayson Minard
  • 84,842
  • 38
  • 184
  • 227
NCNecros
  • 2,395
  • 3
  • 16
  • 20
  • 2
    Also, regardless of the answers below, it looks like you have a compiler bug, you should report this to https://youtrack.jetbrains.com, or check there for duplicates. – Jayson Minard Mar 01 '16 at 11:53
  • 1
    This was an issue, now marked obsolete: https://youtrack.jetbrains.com/issue/KT-5190 – Jayson Minard Mar 01 '16 at 12:12
  • note: The bug KT-5190 is fixed in upcoming Kotlin 1.0.1 which will be released soon (it is in final stages of EAP now) – Jayson Minard Mar 11 '16 at 14:22
  • This issue is now fixed in the released Kotlin 1.0.1 (previously was [KT-5190](https://youtrack.jetbrains.com/issue/KT-5190)). No work around is needed. – Jayson Minard Mar 17 '16 at 13:53

2 Answers2

15

UPDATE: This issue is now fixed in Kotlin 1.0.1 (previously was KT-5190). No work around is needed.


Workarounds

Workaround #1:

Create this extension function, then use it simply as .toList() on the Stream:

fun <T: Any> Stream<T>.toList(): List<T> = this.collect(Collectors.toList<T>())

usage:

Files.list(Paths.get(file)).filter { /* filter clause */ }.toList()

This adds a more explicit generic parameter to the Collectors.toList() call, preventing the bug which occurs during inference of the generics (which are somewhat convoluted for that method return type Collector<T, ?, List<T>>, eeeks!?!).

Workaround #2:

Add the correct type parameter to your call as Collectors.toList<Path>() to avoid type inference of that parameter:

Files.list(Paths.get(file)).filter { /* filter clause */ }.collect(Collectors.toList<Path>())

But the extension function in workaround #1 is more re-usable and more concise.


Staying Lazy

Another way around the bug is to not collect the Stream. You can stay lazy, and convert the Stream to a Kotlin Sequence or Iterator, here is an extension function for making a Sequence:

fun <T: Any> Stream<T>.asSequence(): Sequence<T> = this.iterator().asSequence()

Now you have forEach and many other functions available to you while still consuming the Stream lazily and only once. Using myStream.iterator() is another way but may not have as much functionality as a Sequence.

And of course at the end of some processing on the Sequence you can toList() or toSet() or use any other of the Kotlin extensions for changing collection types.

And with this, I would create an extensions for listing files to avoid the bad API design of Paths, Path, Files, File:

fun Path.list(): Sequence<Path> = Files.list(this).iterator().asSequence()

which would at least flow nicely from left to right:

File(someDir).toPath().list().forEach { println(it) }
Paths.get(dirname).list().forEach { println(it) }

Alternatives to using Java 8 Streams:

We can change your code slightly to get the file list from File instead, and you would just use toList() at the end:

file.listFiles().filter { /* filter clause */ }.toList()

or

file.listFiles { file, name ->  /* filter clause */ }.toList()

Unfortunately the Files.list(...) that you originally used returns a Stream and doesn't give you the opportunity to use a traditional collection. This change avoids that by starting with a function that returns an Array or collection.

In General:

In most cases you can avoid Java 8 streams, and use native Kotlin stdlib functions and extensions to Java collections. Kotlin does indeed use Java collections, via compile-time readonly and mutable interfaces. But then it adds extension functions to provide more functionality. Therefore you have the same performance but with many more capabilities.

See Also:

You should review the API reference for knowing what is available in the stdlib.

Community
  • 1
  • 1
Jayson Minard
  • 84,842
  • 38
  • 184
  • 227
2

This is another work around if you are for some reason stuck on older beta version of Kotlin which requires a more brutal workaround...

Workaround #3: (ugly, an old workaround ONLY for old versions of Kotlin)

Add this Java class to your project:

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class CollectFix {
    public static <T> List<T> streamToList(Stream<T> s) {
        return s.collect(Collectors.toList());
    }
}

And this one Kotlin extension function:

fun <T: Any> Stream<T>.toList(): List<T> = CollectFix.streamToList(this)

And then any time you have this case, use this new extension:

Files.list(Paths.get(file)).filter { /* filter clause */ }.toList()
Jayson Minard
  • 84,842
  • 38
  • 184
  • 227