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 nullnessAnalyzer: SliceNullnessAnalyzerBase by lazy {
|
||||
object : SliceNullnessAnalyzerBase(LEAF_ELEMENT_EQUALITY, this) {
|
||||
val nullnessAnalyzer: HackedSliceNullnessAnalyzerBase by lazy {
|
||||
object : HackedSliceNullnessAnalyzerBase(LEAF_ELEMENT_EQUALITY, this) {
|
||||
override fun checkNullability(element: PsiElement?): Nullability {
|
||||
val types = when (element) {
|
||||
is KtCallableDeclaration -> listOfNotNull((element.resolveToDescriptorIfAny() as? CallableDescriptor)?.returnType)
|
||||
|
||||
@@ -1,11 +1,28 @@
|
||||
[NotNull Values]
|
||||
7 with(123) <bold>{</bold>
|
||||
7 with(<bold>123</bold>) {
|
||||
3 fun <bold>Any</bold>.extensionFun() {
|
||||
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()
|
||||
3 fun <bold>Any</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()
|
||||
3 fun <bold>Any</bold>.extensionFun() {
|
||||
28 <bold>it</bold>.extensionFun()
|
||||
|
||||
@@ -5,9 +5,13 @@
|
||||
14 <bold>1</bold>.extensionFun()
|
||||
8 fun <bold>Any</bold>.extensionFun() {
|
||||
14 <bold>1</bold>.extensionFun()
|
||||
18 with(123) <bold>{</bold>
|
||||
18 with(<bold>123</bold>) {
|
||||
8 fun <bold>Any</bold>.extensionFun() {
|
||||
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()
|
||||
8 fun <bold>Any</bold>.extensionFun() {
|
||||
11 fun <bold>String</bold>.foo() {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.slicer
|
||||
|
||||
import com.intellij.slicer.SliceNullnessAnalyzerBase
|
||||
import com.intellij.slicer.SliceRootNode
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
import java.io.File
|
||||
@@ -14,7 +13,7 @@ abstract class AbstractSlicerNullnessGroupingTest : AbstractSlicerTest() {
|
||||
override fun doTest(path: String, sliceProvider: KotlinSliceProvider, rootNode: SliceRootNode) {
|
||||
val treeStructure = TestSliceTreeStructure(rootNode)
|
||||
val analyzer = sliceProvider.nullnessAnalyzer
|
||||
val nullnessByNode = SliceNullnessAnalyzerBase.createMap()
|
||||
val nullnessByNode = HackedSliceNullnessAnalyzerBase.createMap()
|
||||
val nullness = analyzer.calcNullableLeaves(rootNode, treeStructure, nullnessByNode)
|
||||
val newRootNode = analyzer.createNewTree(nullness, rootNode, nullnessByNode)
|
||||
val renderedForest = buildString {
|
||||
|
||||
@@ -38,7 +38,7 @@ internal fun buildTreeRepresentation(rootNode: SliceNode): String {
|
||||
return text
|
||||
}
|
||||
|
||||
fun SliceNode.isSliceLeafValueClassNode() = javaClass.name == "com.intellij.slicer.SliceLeafValueClassNode"
|
||||
fun SliceNode.isSliceLeafValueClassNode() = this is HackedSliceLeafValueClassNode
|
||||
|
||||
fun process(node: SliceNode, indent: Int): String {
|
||||
val usage = node.element!!.value
|
||||
|
||||
Reference in New Issue
Block a user