Support distinctBy intermediate call

This commit is contained in:
Vitaliy.Bibaev
2017-12-25 17:01:29 +03:00
committed by Yan Zhulanow
parent 0cc5e924a7
commit 89abcb82e4
10 changed files with 267 additions and 17 deletions
@@ -5,6 +5,7 @@ import com.intellij.debugger.streams.kotlin.resolve.ChunkedResolver
import com.intellij.debugger.streams.kotlin.resolve.FilteredMapResolver
import com.intellij.debugger.streams.kotlin.resolve.WindowedResolver
import com.intellij.debugger.streams.kotlin.trace.impl.handler.sequence.FilterIsInstanceHandler
import com.intellij.debugger.streams.kotlin.trace.impl.handler.sequence.KotlinDistinctByHandler
import com.intellij.debugger.streams.lib.IntermediateOperation
import com.intellij.debugger.streams.lib.impl.*
import com.intellij.debugger.streams.resolve.AppendResolver
@@ -30,7 +31,7 @@ class KotlinSequencesSupport : LibrarySupportBase() {
addIntermediateOperationsSupport(*sortedOperations("sorted", "sortedBy", "sortedDescending", "sortedWith"))
addIntermediateOperationsSupport(DistinctOperation("distinct", ::DistinctTraceHandler))
// addIntermediateOperationsSupport(DistinctOperation("distinctBy", ::DistinctByKeyHandler)) waiting for a fix in the platform
addIntermediateOperationsSupport(DistinctOperation("distinctBy", ::KotlinDistinctByHandler))
addIntermediateOperationsSupport(ConcatOperation("plus", AppendResolver()))
addIntermediateOperationsSupport(ConcatOperation("plusElement", AppendResolver()))
@@ -0,0 +1,125 @@
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.debugger.streams.kotlin.trace.impl.handler.sequence
import com.intellij.debugger.streams.kotlin.trace.dsl.KotlinTypes
import com.intellij.debugger.streams.trace.dsl.*
import com.intellij.debugger.streams.trace.dsl.impl.TextExpression
import com.intellij.debugger.streams.trace.impl.handler.type.ClassTypeImpl
import com.intellij.debugger.streams.trace.impl.handler.unified.HandlerBase
import com.intellij.debugger.streams.trace.impl.handler.unified.PeekTraceHandler
import com.intellij.debugger.streams.wrapper.CallArgument
import com.intellij.debugger.streams.wrapper.IntermediateStreamCall
import com.intellij.debugger.streams.wrapper.impl.CallArgumentImpl
import com.intellij.debugger.streams.wrapper.impl.IntermediateStreamCallImpl
import one.util.streamex.StreamEx
/**
* Based on com.intellij.debugger.streams.trace.impl.handler.unified.DistinctByKeyHandler
* @author Vitaliy.Bibaev
*/
class KotlinDistinctByHandler(callNumber: Int, private val call: IntermediateStreamCall, dsl: Dsl)
: HandlerBase.Intermediate(dsl) {
private companion object {
val KEY_EXTRACTOR_VARIABLE_PREFIX = "keyExtractor"
val TRANSITIONS_ARRAY_NAME = "transitionsArray"
}
private val peekHandler = PeekTraceHandler(callNumber, "distinctBy", call.typeBefore, call.typeAfter, dsl)
private val keyExtractor: CallArgument
private val extractorVariable: Variable
private val beforeTimes = dsl.list(dsl.types.INT, call.name + callNumber + "BeforeTimes")
private val beforeValues = dsl.list(KotlinTypes.NULLABLE_ANY, call.name + callNumber + "BeforeValues")
private val keys = dsl.list(KotlinTypes.NULLABLE_ANY, call.name + callNumber + "Keys")
private val time2ValueAfter = dsl.linkedMap(dsl.types.INT, KotlinTypes.NULLABLE_ANY, call.name + callNumber + "after")
init {
val arguments = call.arguments
assert(arguments.isNotEmpty(), { "Key extractor is not specified" })
keyExtractor = arguments.first()
extractorVariable = dsl.variable(ClassTypeImpl(keyExtractor.type), KEY_EXTRACTOR_VARIABLE_PREFIX + callNumber)
}
override fun additionalVariablesDeclaration(): List<VariableDeclaration> {
val extractor = dsl.declaration(extractorVariable, TextExpression(keyExtractor.text), false)
val variables =
mutableListOf(extractor, beforeTimes.defaultDeclaration(), beforeValues.defaultDeclaration(),
time2ValueAfter.defaultDeclaration(), keys.defaultDeclaration())
variables.addAll(peekHandler.additionalVariablesDeclaration())
return variables
}
override fun transformCall(call: IntermediateStreamCall): IntermediateStreamCall {
val newKeyExtractor = dsl.lambda("x") {
val key = dsl.variable(KotlinTypes.NULLABLE_ANY, "key")
declare(key, extractorVariable.call("invoke", lambdaArg), false)
statement { beforeTimes.add(dsl.currentTime()) }
statement { beforeValues.add(lambdaArg) }
statement { keys.add(key) }
doReturn(key)
}.toCode()
return call.updateArguments(listOf(CallArgumentImpl(keyExtractor.type, newKeyExtractor)))
}
override fun prepareResult(): CodeBlock {
val keys2TimesBefore = dsl.map(KotlinTypes.NULLABLE_ANY, dsl.types.list(dsl.types.INT), "keys2Times")
val transitions = dsl.map(dsl.types.INT, dsl.types.INT, "transitionsMap")
StreamEx.of(1).distinct().toList()
return dsl.block {
add(peekHandler.prepareResult())
declare(keys2TimesBefore.defaultDeclaration())
declare(transitions.defaultDeclaration())
integerIteration(keys.size(), block@ this) {
val key = declare(variable(KotlinTypes.NULLABLE_ANY, "key"), keys.get(loopVariable), false)
val lst = list(dsl.types.INT, "lst")
declare(lst, keys2TimesBefore.computeIfAbsent(key, lambda("k") {
doReturn(newList(types.INT))
}), false)
statement { lst.add(beforeTimes.get(loopVariable)) }
}
forEachLoop(variable(types.INT, "afterTime"), time2ValueAfter.keys()) {
val afterTime = loopVariable
val valueAfter = declare(variable(KotlinTypes.NULLABLE_ANY, "valueAfter"), time2ValueAfter.get(loopVariable), false)
val key = declare(variable(KotlinTypes.NULLABLE_ANY, "key"), nullExpression, true)
integerIteration(beforeTimes.size(), forEachLoop@ this) {
ifBranch((valueAfter same beforeValues.get(loopVariable)) and !transitions.contains(beforeTimes.get(loopVariable))) {
key assign keys.get(loopVariable)
statement { breakIteration() }
}
}
forEachLoop(variable(types.INT, "beforeTime"), keys2TimesBefore.get(key)) {
statement { transitions.set(loopVariable, afterTime) }
}
}
add(transitions.convertToArray(this, "transitionsArray"))
}
}
override fun getResultExpression(): Expression =
dsl.newArray(dsl.types.ANY, peekHandler.resultExpression, TextExpression(TRANSITIONS_ARRAY_NAME))
override fun additionalCallsBefore(): List<IntermediateStreamCall> = peekHandler.additionalCallsBefore()
override fun additionalCallsAfter(): List<IntermediateStreamCall> {
val callsAfter = ArrayList(peekHandler.additionalCallsAfter())
val lambda = dsl.lambda("x") {
doReturn(time2ValueAfter.set(dsl.currentTime(), lambdaArg))
}
callsAfter.add(dsl.createPeekCall(call.typeAfter, lambda.toCode()))
return callsAfter
}
private fun CodeContext.integerIteration(border: Expression, block: CodeBlock, init: ForLoopBody.() -> Unit) {
block.forLoop(declaration(variable(types.INT, "i"), TextExpression("0"), true),
TextExpression("i < ${border.toCode()}"),
TextExpression("i = i + 1"), init)
}
private fun IntermediateStreamCall.updateArguments(args: List<CallArgument>): IntermediateStreamCall =
IntermediateStreamCallImpl("distinctBy", args, typeBefore, typeAfter, textRange)
}
@@ -13,22 +13,22 @@ toList
after: nothing
mappings for distinctBy
direct:
1 -> nothing
3 -> nothing
5 -> nothing
6 -> nothing
7 -> nothing
8 -> nothing
9 -> nothing
10 -> nothing
11 -> nothing
12 -> nothing
14 -> nothing
1 -> 2
3 -> 4
5 -> 4
6 -> 4
7 -> 4
8 -> 2
9 -> 4
10 -> 2
11 -> 2
12 -> 13
14 -> 15
reverse:
nothing <- 2
nothing <- 4
nothing <- 13
nothing <- 15
1,8,10,11 <- 2
3,5,6,7,9 <- 4
12 <- 13
14 <- 15
mappings for toList
direct:
2 -> nothing
@@ -37,7 +37,6 @@ mappings for toList
15 -> nothing
reverse:
empty
WRONG!
Disconnected from the target VM
Process finished with exit code 0
@@ -0,0 +1,32 @@
LineBreakpoint created at DistinctByNullableElement.kt:5
Run Java
Connected to the target VM
DistinctByNullableElement.kt:5
sequenceOf(null, 1, 2, 3, null)
.distinctBy({ it == null })
.count()
distinctBy
before: 1,3,5,6,7
after: 2,4
count
before: 2,4
after: nothing
mappings for distinctBy
direct:
1 -> 2
3 -> 4
5 -> 4
6 -> 4
7 -> 2
reverse:
1,7 <- 2
3,5,6 <- 4
mappings for count
direct:
2 -> nothing
4 -> nothing
reverse:
empty
Disconnected from the target VM
Process finished with exit code 0
@@ -0,0 +1,36 @@
LineBreakpoint created at DistinctByNullableKey.kt:5
Run Java
Connected to the target VM
DistinctByNullableKey.kt:5
sequenceOf(1, 2, 3, 4, 5)
.distinctBy({ if (it % 2 == 0) null else it })
.count()
distinctBy
before: 1,3,5,7,8
after: 2,4,6,9
count
before: 2,4,6,9
after: nothing
mappings for distinctBy
direct:
1 -> 2
3 -> 4
5 -> 6
7 -> 4
8 -> 9
reverse:
1 <- 2
3,7 <- 4
5 <- 6
8 <- 9
mappings for count
direct:
2 -> nothing
4 -> nothing
6 -> nothing
9 -> nothing
reverse:
empty
Disconnected from the target VM
Process finished with exit code 0
@@ -0,0 +1,35 @@
LineBreakpoint created at DistinctByNullableKeyAndElement.kt:5
Run Java
Connected to the target VM
DistinctByNullableKeyAndElement.kt:5
sequenceOf(1, 2, null, null, 3, 1)
.distinctBy({ if (it == null) 2 else if (it == 3) null else it })
.count()
distinctBy
before: 1,3,5,6,7,9
after: 2,4,8
count
before: 2,4,8
after: nothing
mappings for distinctBy
direct:
1 -> 2
3 -> 4
5 -> 4
6 -> 4
7 -> 8
9 -> 2
reverse:
1,9 <- 2
3,5,6 <- 4
7 <- 8
mappings for count
direct:
2 -> nothing
4 -> nothing
8 -> nothing
reverse:
empty
Disconnected from the target VM
Process finished with exit code 0
@@ -0,0 +1,6 @@
package distinct
fun main(args: Array<String>) {
// Breakpoint!
sequenceOf(null, 1, 2, 3, null).distinctBy { it == null }.count()
}
@@ -0,0 +1,6 @@
package distinct
fun main(args: Array<String>) {
// Breakpoint!
sequenceOf(1, 2, 3, 4, 5).distinctBy { if (it % 2 == 0) null else it }.count()
}
@@ -0,0 +1,6 @@
package distinct
fun main(args: Array<String>) {
// Breakpoint!
sequenceOf(1, 2, null, null, 3, 1).distinctBy { if (it == null) 2 else if (it == 3) null else it }.count()
}
@@ -7,5 +7,9 @@ package com.intellij.debugger.streams.kotlin.exec.sequence
class DistinctOperationsTest : OperationsTestCase("distinct") {
fun testDistinct() = doTestWithResult()
fun testDistinctObjects() = doTestWithResult()
fun testDistinctBy() = doTestWithResult()
fun testDistinctByNullableElement() = doTestWithResult()
fun testDistinctByNullableKey() = doTestWithResult()
fun testDistinctByNullableKeyAndElement() = doTestWithResult()
}