Hacked SliceNullnessAnalyzerBase from IDEA to take into account SliceUsage.isLeaf() and give reasonable and stable results for Kotlin
This commit is contained in:
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.slicer;
|
||||||
|
|
||||||
|
import com.intellij.openapi.project.Project;
|
||||||
|
import com.intellij.slicer.JavaSliceUsage;
|
||||||
|
import com.intellij.slicer.SliceLeafValueRootNode;
|
||||||
|
import com.intellij.slicer.SliceNode;
|
||||||
|
import com.intellij.slicer.SliceUsageCellRendererBase;
|
||||||
|
import com.intellij.ui.SimpleTextAttributes;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
class HackedSliceLeafValueClassNode extends SliceLeafValueRootNode {
|
||||||
|
private final String myClassName;
|
||||||
|
|
||||||
|
HackedSliceLeafValueClassNode(@NotNull Project project, @NotNull SliceNode root, @NotNull String className) {
|
||||||
|
super(project,
|
||||||
|
root,
|
||||||
|
JavaSliceUsage.createRootUsage(root.getValue().getElement(), root.getValue().params),
|
||||||
|
new ArrayList<>());
|
||||||
|
myClassName = className;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canNavigate() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canNavigateToSource() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void customizeCellRenderer(@NotNull SliceUsageCellRendererBase renderer,
|
||||||
|
@NotNull JTree tree,
|
||||||
|
Object value,
|
||||||
|
boolean selected,
|
||||||
|
boolean expanded,
|
||||||
|
boolean leaf,
|
||||||
|
int row,
|
||||||
|
boolean hasFocus) {
|
||||||
|
renderer.append(myClassName, SimpleTextAttributes.DARK_TEXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNodeText() {
|
||||||
|
return myClassName;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,230 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
package org.jetbrains.kotlin.idea.slicer;
|
||||||
|
|
||||||
|
import com.intellij.codeInsight.Nullability;
|
||||||
|
import com.intellij.codeInsight.PsiEquivalenceUtil;
|
||||||
|
import com.intellij.ide.util.treeView.AbstractTreeNode;
|
||||||
|
import com.intellij.ide.util.treeView.AbstractTreeStructure;
|
||||||
|
import com.intellij.openapi.application.ReadAction;
|
||||||
|
import com.intellij.openapi.progress.ProgressIndicator;
|
||||||
|
import com.intellij.openapi.progress.ProgressManager;
|
||||||
|
import com.intellij.openapi.progress.Task;
|
||||||
|
import com.intellij.openapi.util.Ref;
|
||||||
|
import com.intellij.psi.PsiElement;
|
||||||
|
import com.intellij.slicer.*;
|
||||||
|
import com.intellij.util.WalkingState;
|
||||||
|
import com.intellij.util.containers.ContainerUtil;
|
||||||
|
import com.intellij.util.containers.FactoryMap;
|
||||||
|
import gnu.trove.THashSet;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public abstract class HackedSliceNullnessAnalyzerBase {
|
||||||
|
@NotNull
|
||||||
|
private final SliceLeafEquality myLeafEquality;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private final SliceLanguageSupportProvider myProvider;
|
||||||
|
|
||||||
|
public HackedSliceNullnessAnalyzerBase(@NotNull SliceLeafEquality leafEquality,
|
||||||
|
@NotNull SliceLanguageSupportProvider provider) {
|
||||||
|
myLeafEquality = leafEquality;
|
||||||
|
myProvider = provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void groupByNullness(NullAnalysisResult result, SliceRootNode oldRoot, final Map<SliceNode, NullAnalysisResult> map) {
|
||||||
|
SliceRootNode root = createNewTree(result, oldRoot, map);
|
||||||
|
|
||||||
|
SliceUsage rootUsage = oldRoot.getCachedChildren().get(0).getValue();
|
||||||
|
SliceManager.getInstance(root.getProject()).createToolWindow(true, root, true, SliceManager.getElementDescription(null, rootUsage.getElement(), " Grouped by Nullness") );
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public SliceRootNode createNewTree(NullAnalysisResult result, SliceRootNode oldRoot, final Map<SliceNode, NullAnalysisResult> map) {
|
||||||
|
SliceRootNode root = oldRoot.copy();
|
||||||
|
assert oldRoot.getCachedChildren().size() == 1;
|
||||||
|
SliceNode oldRootStart = oldRoot.getCachedChildren().get(0);
|
||||||
|
root.setChanged();
|
||||||
|
root.targetEqualUsages.clear();
|
||||||
|
|
||||||
|
List<HackedSliceLeafValueClassNode> children = new ArrayList<>();
|
||||||
|
ContainerUtil.addIfNotNull(children, createValueRootNode(result, oldRoot, map, root, oldRootStart, "Null Values", NullAnalysisResult.NULLS));
|
||||||
|
ContainerUtil.addIfNotNull(children, createValueRootNode(result, oldRoot, map, root, oldRootStart, "NotNull Values", NullAnalysisResult.NOT_NULLS));
|
||||||
|
ContainerUtil.addIfNotNull(children, createValueRootNode(result, oldRoot, map, root, oldRootStart, "Other Values", NullAnalysisResult.UNKNOWNS));
|
||||||
|
root.setChildren(children);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HackedSliceLeafValueClassNode createValueRootNode(
|
||||||
|
NullAnalysisResult result,
|
||||||
|
SliceRootNode oldRoot,
|
||||||
|
final Map<SliceNode, NullAnalysisResult> map,
|
||||||
|
SliceRootNode root,
|
||||||
|
SliceNode oldRootStart,
|
||||||
|
String nodeName,
|
||||||
|
final int group) {
|
||||||
|
Collection<PsiElement> groupedByValue = result.groupedByValue[group];
|
||||||
|
if (groupedByValue.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
HackedSliceLeafValueClassNode
|
||||||
|
valueRoot = new HackedSliceLeafValueClassNode(root.getProject(), root, nodeName);
|
||||||
|
|
||||||
|
Set<PsiElement> uniqueValues = new THashSet<>(groupedByValue, myLeafEquality);
|
||||||
|
for (final PsiElement expression : uniqueValues) {
|
||||||
|
SliceNode newRoot = SliceLeafAnalyzer.filterTree(oldRootStart, oldNode -> {
|
||||||
|
if (oldNode.getDuplicate() != null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (PsiElement nullSuspect : group(oldNode, map, group)) {
|
||||||
|
if (PsiEquivalenceUtil.areElementsEquivalent(nullSuspect, expression)) {
|
||||||
|
return oldNode.copy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, (node, children) -> {
|
||||||
|
if (!children.isEmpty()) return true;
|
||||||
|
PsiElement element = node.getValue().getElement();
|
||||||
|
if (element == null) return false;
|
||||||
|
return PsiEquivalenceUtil.areElementsEquivalent(element, expression); // leaf can be there only if it's filtering expression
|
||||||
|
});
|
||||||
|
valueRoot.myCachedChildren.add(
|
||||||
|
new SliceLeafValueRootNode(root.getProject(),
|
||||||
|
valueRoot,
|
||||||
|
myProvider.createRootUsage(expression, oldRoot.getValue().params),
|
||||||
|
Collections.singletonList(newRoot))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return valueRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startAnalyzeNullness(@NotNull AbstractTreeStructure treeStructure, @NotNull Runnable finish) {
|
||||||
|
final SliceRootNode root = (SliceRootNode)treeStructure.getRootElement();
|
||||||
|
final Ref<NullAnalysisResult> leafExpressions = Ref.create(null);
|
||||||
|
final Map<SliceNode, NullAnalysisResult> map = createMap();
|
||||||
|
|
||||||
|
String encouragementPiece = " (may very well take the whole day)";
|
||||||
|
ProgressManager.getInstance().run(new Task.Backgroundable(
|
||||||
|
root.getProject(), "Expanding All Nodes..." + encouragementPiece, true) {
|
||||||
|
@Override
|
||||||
|
public void run(@NotNull final ProgressIndicator indicator) {
|
||||||
|
NullAnalysisResult l = calcNullableLeaves(root, treeStructure, map);
|
||||||
|
leafExpressions.set(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCancel() {
|
||||||
|
finish.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess() {
|
||||||
|
try {
|
||||||
|
NullAnalysisResult leaves = leafExpressions.get();
|
||||||
|
if (leaves == null) return; //cancelled
|
||||||
|
|
||||||
|
groupByNullness(leaves, root, map);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
finish.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<SliceNode, NullAnalysisResult> createMap() {
|
||||||
|
return FactoryMap.createMap(k->new NullAnalysisResult(), ContainerUtil::newIdentityTroveMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static NullAnalysisResult node(@NotNull SliceNode node, @NotNull Map<SliceNode, NullAnalysisResult> nulls) {
|
||||||
|
return nulls.get(node);
|
||||||
|
}
|
||||||
|
private static Collection<PsiElement> group(@NotNull SliceNode node, @NotNull Map<SliceNode, NullAnalysisResult> nulls, int group) {
|
||||||
|
return nulls.get(node).groupedByValue[group];
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public NullAnalysisResult calcNullableLeaves(@NotNull final SliceNode root,
|
||||||
|
@NotNull AbstractTreeStructure treeStructure,
|
||||||
|
@NotNull final Map<SliceNode, NullAnalysisResult> map) {
|
||||||
|
final SliceLeafAnalyzer.SliceNodeGuide guide = new SliceLeafAnalyzer.SliceNodeGuide(treeStructure);
|
||||||
|
WalkingState<SliceNode> walkingState = new WalkingState<SliceNode>(guide) {
|
||||||
|
@Override
|
||||||
|
public void visit(@NotNull final SliceNode element) {
|
||||||
|
element.calculateDupNode();
|
||||||
|
node(element, map).clear();
|
||||||
|
SliceNode duplicate = element.getDuplicate();
|
||||||
|
if (duplicate != null) {
|
||||||
|
node(element, map).add(node(duplicate, map));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
final PsiElement value = ReadAction.compute(() -> element.getValue().getElement());
|
||||||
|
boolean canBeLeaf = element.getValue().canBeLeaf();
|
||||||
|
Nullability nullability = canBeLeaf ? ReadAction.compute(() -> checkNullability(value)) : Nullability.UNKNOWN;
|
||||||
|
if (nullability == Nullability.NULLABLE) {
|
||||||
|
group(element, map, NullAnalysisResult.NULLS).add(value);
|
||||||
|
}
|
||||||
|
else if (nullability == Nullability.NOT_NULL) {
|
||||||
|
group(element, map, NullAnalysisResult.NOT_NULLS).add(value);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Collection<? extends AbstractTreeNode> children = ReadAction.compute(element::getChildren);
|
||||||
|
if (children.isEmpty()) {
|
||||||
|
group(element, map, NullAnalysisResult.UNKNOWNS).add(value);
|
||||||
|
}
|
||||||
|
super.visit(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void elementFinished(@NotNull SliceNode element) {
|
||||||
|
SliceNode parent = guide.getParent(element);
|
||||||
|
if (parent != null) {
|
||||||
|
node(parent, map).add(node(element, map));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
walkingState.visit(root);
|
||||||
|
|
||||||
|
return node(root, map);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementors must override this method; default implementation just throws UnsupportedOperationException.
|
||||||
|
*
|
||||||
|
* @param element element to find nullability for
|
||||||
|
* @return element nullability
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
protected Nullability checkNullability(final PsiElement element) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class NullAnalysisResult {
|
||||||
|
static final int NULLS = 0;
|
||||||
|
static final int NOT_NULLS = 1;
|
||||||
|
static final int UNKNOWNS = 2;
|
||||||
|
final Collection<PsiElement>[] groupedByValue = new Collection[] {new THashSet<PsiElement>(),new THashSet<PsiElement>(),new THashSet<PsiElement>()};
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
for (Collection<PsiElement> elements : groupedByValue) {
|
||||||
|
elements.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void add(NullAnalysisResult duplicate) {
|
||||||
|
for (int i = 0; i < groupedByValue.length; i++) {
|
||||||
|
Collection<PsiElement> elements = groupedByValue[i];
|
||||||
|
Collection<PsiElement> other = duplicate.groupedByValue[i];
|
||||||
|
elements.addAll(other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -50,8 +50,8 @@ class KotlinSliceProvider : SliceLanguageSupportProvider, SliceUsageTransformer
|
|||||||
|
|
||||||
val leafAnalyzer by lazy { SliceLeafAnalyzer(LEAF_ELEMENT_EQUALITY, this) }
|
val leafAnalyzer by lazy { SliceLeafAnalyzer(LEAF_ELEMENT_EQUALITY, this) }
|
||||||
|
|
||||||
val nullnessAnalyzer: SliceNullnessAnalyzerBase by lazy {
|
val nullnessAnalyzer: HackedSliceNullnessAnalyzerBase by lazy {
|
||||||
object : SliceNullnessAnalyzerBase(LEAF_ELEMENT_EQUALITY, this) {
|
object : HackedSliceNullnessAnalyzerBase(LEAF_ELEMENT_EQUALITY, this) {
|
||||||
override fun checkNullability(element: PsiElement?): Nullability {
|
override fun checkNullability(element: PsiElement?): Nullability {
|
||||||
val types = when (element) {
|
val types = when (element) {
|
||||||
is KtCallableDeclaration -> listOfNotNull((element.resolveToDescriptorIfAny() as? CallableDescriptor)?.returnType)
|
is KtCallableDeclaration -> listOfNotNull((element.resolveToDescriptorIfAny() as? CallableDescriptor)?.returnType)
|
||||||
|
|||||||
@@ -1,11 +1,28 @@
|
|||||||
[NotNull Values]
|
[NotNull Values]
|
||||||
7 with(123) <bold>{</bold>
|
7 with(<bold>123</bold>) {
|
||||||
3 fun <bold>Any</bold>.extensionFun() {
|
3 fun <bold>Any</bold>.extensionFun() {
|
||||||
7 [LAMBDA CALLS] with(123) <bold>{</bold>
|
7 [LAMBDA CALLS] with(123) <bold>{</bold>
|
||||||
19 [LAMBDA CALLS] withNoInline(1) <bold>{</bold>
|
44 (INLINE CALL with) [LAMBDA CALLS] inline fun <T, R> with(receiver: T, <bold>block: T.() -> R</bold>): R {
|
||||||
|
45 (INLINE CALL with) val result = <bold>receiver</bold>.block()
|
||||||
|
44 (INLINE CALL with) inline fun <T, R> with(<bold>receiver: T</bold>, block: T.() -> R): R {
|
||||||
|
7 with(<bold>123</bold>) {
|
||||||
12 <bold>this</bold>.extensionFun()
|
12 <bold>this</bold>.extensionFun()
|
||||||
3 fun <bold>Any</bold>.extensionFun() {
|
3 fun <bold>Any</bold>.extensionFun() {
|
||||||
12 <bold>this</bold>.extensionFun()
|
12 <bold>this</bold>.extensionFun()
|
||||||
|
19 withNoInline(<bold>1</bold>) {
|
||||||
|
3 fun <bold>Any</bold>.extensionFun() {
|
||||||
|
19 [LAMBDA CALLS] withNoInline(1) <bold>{</bold>
|
||||||
|
53 [LAMBDA CALLS] fun <T, R> withNoInline(receiver: T, <bold>block: T.() -> R</bold>): R {
|
||||||
|
54 val result = <bold>receiver</bold>.block()
|
||||||
|
53 fun <T, R> withNoInline(<bold>receiver: T</bold>, block: T.() -> R): R {
|
||||||
|
19 withNoInline(<bold>1</bold>) {
|
||||||
|
23 withNoInline(<bold>2</bold>) {
|
||||||
|
3 fun <bold>Any</bold>.extensionFun() {
|
||||||
|
19 [LAMBDA CALLS] withNoInline(1) <bold>{</bold>
|
||||||
|
53 [LAMBDA CALLS] fun <T, R> withNoInline(receiver: T, <bold>block: T.() -> R</bold>): R {
|
||||||
|
54 val result = <bold>receiver</bold>.block()
|
||||||
|
53 fun <T, R> withNoInline(<bold>receiver: T</bold>, block: T.() -> R): R {
|
||||||
|
23 withNoInline(<bold>2</bold>) {
|
||||||
36 <bold>it</bold>.extensionFun()
|
36 <bold>it</bold>.extensionFun()
|
||||||
3 fun <bold>Any</bold>.extensionFun() {
|
3 fun <bold>Any</bold>.extensionFun() {
|
||||||
28 <bold>it</bold>.extensionFun()
|
28 <bold>it</bold>.extensionFun()
|
||||||
|
|||||||
@@ -5,9 +5,13 @@
|
|||||||
14 <bold>1</bold>.extensionFun()
|
14 <bold>1</bold>.extensionFun()
|
||||||
8 fun <bold>Any</bold>.extensionFun() {
|
8 fun <bold>Any</bold>.extensionFun() {
|
||||||
14 <bold>1</bold>.extensionFun()
|
14 <bold>1</bold>.extensionFun()
|
||||||
18 with(123) <bold>{</bold>
|
18 with(<bold>123</bold>) {
|
||||||
8 fun <bold>Any</bold>.extensionFun() {
|
8 fun <bold>Any</bold>.extensionFun() {
|
||||||
18 [LAMBDA CALLS] with(123) <bold>{</bold>
|
18 [LAMBDA CALLS] with(123) <bold>{</bold>
|
||||||
|
27 (INLINE CALL with) [LAMBDA CALLS] inline fun <T, R> with(receiver: T, <bold>block: T.() -> R</bold>): R {
|
||||||
|
28 (INLINE CALL with) return <bold>receiver</bold>.block()
|
||||||
|
27 (INLINE CALL with) inline fun <T, R> with(<bold>receiver: T</bold>, block: T.() -> R): R {
|
||||||
|
18 with(<bold>123</bold>) {
|
||||||
24 <bold>"A"</bold>.foo()
|
24 <bold>"A"</bold>.foo()
|
||||||
8 fun <bold>Any</bold>.extensionFun() {
|
8 fun <bold>Any</bold>.extensionFun() {
|
||||||
11 fun <bold>String</bold>.foo() {
|
11 fun <bold>String</bold>.foo() {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.slicer
|
package org.jetbrains.kotlin.idea.slicer
|
||||||
|
|
||||||
import com.intellij.slicer.SliceNullnessAnalyzerBase
|
|
||||||
import com.intellij.slicer.SliceRootNode
|
import com.intellij.slicer.SliceRootNode
|
||||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -14,7 +13,7 @@ abstract class AbstractSlicerNullnessGroupingTest : AbstractSlicerTest() {
|
|||||||
override fun doTest(path: String, sliceProvider: KotlinSliceProvider, rootNode: SliceRootNode) {
|
override fun doTest(path: String, sliceProvider: KotlinSliceProvider, rootNode: SliceRootNode) {
|
||||||
val treeStructure = TestSliceTreeStructure(rootNode)
|
val treeStructure = TestSliceTreeStructure(rootNode)
|
||||||
val analyzer = sliceProvider.nullnessAnalyzer
|
val analyzer = sliceProvider.nullnessAnalyzer
|
||||||
val nullnessByNode = SliceNullnessAnalyzerBase.createMap()
|
val nullnessByNode = HackedSliceNullnessAnalyzerBase.createMap()
|
||||||
val nullness = analyzer.calcNullableLeaves(rootNode, treeStructure, nullnessByNode)
|
val nullness = analyzer.calcNullableLeaves(rootNode, treeStructure, nullnessByNode)
|
||||||
val newRootNode = analyzer.createNewTree(nullness, rootNode, nullnessByNode)
|
val newRootNode = analyzer.createNewTree(nullness, rootNode, nullnessByNode)
|
||||||
val renderedForest = buildString {
|
val renderedForest = buildString {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ internal fun buildTreeRepresentation(rootNode: SliceNode): String {
|
|||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
|
||||||
fun SliceNode.isSliceLeafValueClassNode() = javaClass.name == "com.intellij.slicer.SliceLeafValueClassNode"
|
fun SliceNode.isSliceLeafValueClassNode() = this is HackedSliceLeafValueClassNode
|
||||||
|
|
||||||
fun process(node: SliceNode, indent: Int): String {
|
fun process(node: SliceNode, indent: Int): String {
|
||||||
val usage = node.element!!.value
|
val usage = node.element!!.value
|
||||||
|
|||||||
Reference in New Issue
Block a user