From 89abcb82e42b028ef3c4dd2d185975b5ea4529b2 Mon Sep 17 00:00:00 2001 From: "Vitaliy.Bibaev" Date: Mon, 25 Dec 2017 17:01:29 +0300 Subject: [PATCH] Support distinctBy intermediate call --- .../lib/sequence/KotlinSequencesSupport.kt | 3 +- .../sequence/KotlinDistinctByHandler.kt | 125 ++++++++++++++++++ .../exec/sequence/outs/distinctBy.out | 31 +++-- .../outs/distinctByNullableElement.out | 32 +++++ .../sequence/outs/distinctByNullableKey.out | 36 +++++ .../outs/distinctByNullableKeyAndElement.out | 35 +++++ .../src/distinct/DistinctByNullableElement.kt | 6 + .../src/distinct/DistinctByNullableKey.kt | 6 + .../DistinctByNullableKeyAndElement.kt | 6 + .../exec/sequence/DistinctOperationsTest.kt | 4 + 10 files changed, 267 insertions(+), 17 deletions(-) create mode 100644 idea/idea-jvm/src/com/intellij/debugger/streams/kotlin/trace/impl/handler/sequence/KotlinDistinctByHandler.kt create mode 100644 idea/testData/debugger/sequence/exec/sequence/outs/distinctByNullableElement.out create mode 100644 idea/testData/debugger/sequence/exec/sequence/outs/distinctByNullableKey.out create mode 100644 idea/testData/debugger/sequence/exec/sequence/outs/distinctByNullableKeyAndElement.out create mode 100644 idea/testData/debugger/sequence/exec/sequence/src/distinct/DistinctByNullableElement.kt create mode 100644 idea/testData/debugger/sequence/exec/sequence/src/distinct/DistinctByNullableKey.kt create mode 100644 idea/testData/debugger/sequence/exec/sequence/src/distinct/DistinctByNullableKeyAndElement.kt diff --git a/idea/idea-jvm/src/com/intellij/debugger/streams/kotlin/lib/sequence/KotlinSequencesSupport.kt b/idea/idea-jvm/src/com/intellij/debugger/streams/kotlin/lib/sequence/KotlinSequencesSupport.kt index 8db6d852847..37358f4a394 100644 --- a/idea/idea-jvm/src/com/intellij/debugger/streams/kotlin/lib/sequence/KotlinSequencesSupport.kt +++ b/idea/idea-jvm/src/com/intellij/debugger/streams/kotlin/lib/sequence/KotlinSequencesSupport.kt @@ -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())) diff --git a/idea/idea-jvm/src/com/intellij/debugger/streams/kotlin/trace/impl/handler/sequence/KotlinDistinctByHandler.kt b/idea/idea-jvm/src/com/intellij/debugger/streams/kotlin/trace/impl/handler/sequence/KotlinDistinctByHandler.kt new file mode 100644 index 00000000000..e88ca38cc4d --- /dev/null +++ b/idea/idea-jvm/src/com/intellij/debugger/streams/kotlin/trace/impl/handler/sequence/KotlinDistinctByHandler.kt @@ -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 { + 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 = peekHandler.additionalCallsBefore() + + override fun additionalCallsAfter(): List { + 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): IntermediateStreamCall = + IntermediateStreamCallImpl("distinctBy", args, typeBefore, typeAfter, textRange) +} \ No newline at end of file diff --git a/idea/testData/debugger/sequence/exec/sequence/outs/distinctBy.out b/idea/testData/debugger/sequence/exec/sequence/outs/distinctBy.out index 0a5a93b0263..f372fb05b76 100644 --- a/idea/testData/debugger/sequence/exec/sequence/outs/distinctBy.out +++ b/idea/testData/debugger/sequence/exec/sequence/outs/distinctBy.out @@ -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 diff --git a/idea/testData/debugger/sequence/exec/sequence/outs/distinctByNullableElement.out b/idea/testData/debugger/sequence/exec/sequence/outs/distinctByNullableElement.out new file mode 100644 index 00000000000..c8aec425859 --- /dev/null +++ b/idea/testData/debugger/sequence/exec/sequence/outs/distinctByNullableElement.out @@ -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 diff --git a/idea/testData/debugger/sequence/exec/sequence/outs/distinctByNullableKey.out b/idea/testData/debugger/sequence/exec/sequence/outs/distinctByNullableKey.out new file mode 100644 index 00000000000..c7b2041f9ce --- /dev/null +++ b/idea/testData/debugger/sequence/exec/sequence/outs/distinctByNullableKey.out @@ -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 diff --git a/idea/testData/debugger/sequence/exec/sequence/outs/distinctByNullableKeyAndElement.out b/idea/testData/debugger/sequence/exec/sequence/outs/distinctByNullableKeyAndElement.out new file mode 100644 index 00000000000..f16e59c5c00 --- /dev/null +++ b/idea/testData/debugger/sequence/exec/sequence/outs/distinctByNullableKeyAndElement.out @@ -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 diff --git a/idea/testData/debugger/sequence/exec/sequence/src/distinct/DistinctByNullableElement.kt b/idea/testData/debugger/sequence/exec/sequence/src/distinct/DistinctByNullableElement.kt new file mode 100644 index 00000000000..5824c18e286 --- /dev/null +++ b/idea/testData/debugger/sequence/exec/sequence/src/distinct/DistinctByNullableElement.kt @@ -0,0 +1,6 @@ +package distinct + +fun main(args: Array) { + // Breakpoint! + sequenceOf(null, 1, 2, 3, null).distinctBy { it == null }.count() +} \ No newline at end of file diff --git a/idea/testData/debugger/sequence/exec/sequence/src/distinct/DistinctByNullableKey.kt b/idea/testData/debugger/sequence/exec/sequence/src/distinct/DistinctByNullableKey.kt new file mode 100644 index 00000000000..a08e7e2c86d --- /dev/null +++ b/idea/testData/debugger/sequence/exec/sequence/src/distinct/DistinctByNullableKey.kt @@ -0,0 +1,6 @@ +package distinct + +fun main(args: Array) { + // Breakpoint! + sequenceOf(1, 2, 3, 4, 5).distinctBy { if (it % 2 == 0) null else it }.count() +} diff --git a/idea/testData/debugger/sequence/exec/sequence/src/distinct/DistinctByNullableKeyAndElement.kt b/idea/testData/debugger/sequence/exec/sequence/src/distinct/DistinctByNullableKeyAndElement.kt new file mode 100644 index 00000000000..0c0aaed82ec --- /dev/null +++ b/idea/testData/debugger/sequence/exec/sequence/src/distinct/DistinctByNullableKeyAndElement.kt @@ -0,0 +1,6 @@ +package distinct + +fun main(args: Array) { + // Breakpoint! + sequenceOf(1, 2, null, null, 3, 1).distinctBy { if (it == null) 2 else if (it == 3) null else it }.count() +} \ No newline at end of file diff --git a/idea/tests/com/intellij/debugger/streams/kotlin/exec/sequence/DistinctOperationsTest.kt b/idea/tests/com/intellij/debugger/streams/kotlin/exec/sequence/DistinctOperationsTest.kt index 6bdd5080589..c5a7017eed0 100644 --- a/idea/tests/com/intellij/debugger/streams/kotlin/exec/sequence/DistinctOperationsTest.kt +++ b/idea/tests/com/intellij/debugger/streams/kotlin/exec/sequence/DistinctOperationsTest.kt @@ -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() } \ No newline at end of file