Uast: conversion possibility check optimisation

This commit is contained in:
Nicolay Mitropolsky
2019-01-10 11:06:00 +03:00
parent c2993e111e
commit 7dcfbccc08
8 changed files with 84 additions and 58 deletions
@@ -11,23 +11,20 @@ import org.jetbrains.annotations.Nullable;
import org.jetbrains.uast.*;
import org.jetbrains.uast.visitor.AbstractUastNonRecursiveVisitor;
import java.util.Arrays;
import java.util.List;
public abstract class AbstractBaseUastLocalInspectionTool extends LocalInspectionTool {
private static final Condition<PsiElement> PROBLEM_ELEMENT_CONDITION =
Conditions.and(Conditions.instanceOf(PsiFile.class, PsiClass.class, PsiMethod.class, PsiField.class),
Conditions.notInstanceOf(PsiTypeParameter.class));
private final List<Class<? extends UElement>> myUElementsTypesHint;
private final Class<? extends UElement>[] myUElementsTypesHint;
protected AbstractBaseUastLocalInspectionTool() {
myUElementsTypesHint = Arrays.asList(UFile.class, UClass.class, UMethod.class, UField.class);
this(UFile.class, UClass.class, UMethod.class, UField.class);
}
protected AbstractBaseUastLocalInspectionTool(Class<? extends UElement>... uElementsTypesHint) {
myUElementsTypesHint = Arrays.asList(uElementsTypesHint);
myUElementsTypesHint = uElementsTypesHint;
}
/**
@@ -11,7 +11,7 @@ import org.jetbrains.uast.visitor.AbstractUastNonRecursiveVisitor
open class UastHintedVisitorAdapter(private val plugin: UastLanguagePlugin,
private val visitor: AbstractUastNonRecursiveVisitor,
private val directOnly: Boolean,
private val uElementTypesHint: List<Class<out UElement>>
private val uElementTypesHint: Array<Class<out UElement>>
) : PsiElementVisitor() {
override fun visitElement(element: PsiElement) {
@@ -27,7 +27,7 @@ open class UastHintedVisitorAdapter(private val plugin: UastLanguagePlugin,
@JvmOverloads
fun create(language: Language,
visitor: AbstractUastNonRecursiveVisitor,
uElementTypesHint: List<Class<out UElement>>,
uElementTypesHint: Array<Class<out UElement>>,
directOnly: Boolean = true): PsiElementVisitor {
val uastLanguagePlugin = UastLanguagePlugin.byLanguage(language) ?: return EMPTY_VISITOR
return UastHintedVisitorAdapter(uastLanguagePlugin, visitor, directOnly, uElementTypesHint)
@@ -116,7 +116,7 @@ fun <T : UElement> PsiElement?.toUElement(cls: Class<out T>): T? =
fun <T : UElement> PsiElement?.toUElementOfExpectedTypes(vararg clss: Class<out T>): T? =
this?.let {
ServiceManager.getService(project, UastContext::class.java)
.convertElementWithParent(this, if (clss.isNotEmpty()) clss.toList() else DEFAULT_TYPES_LIST) as T?
.convertElementWithParent(this, if (clss.isNotEmpty()) clss else DEFAULT_TYPES_LIST) as T?
}
@@ -150,7 +150,7 @@ fun <T : UElement> PsiElement?.getUastParentOfType(cls: Class<out T>, strict: Bo
inline fun <reified T : UElement> PsiElement?.getUastParentOfType(strict: Boolean = false): T? = getUastParentOfType(T::class.java, strict)
@JvmField
val DEFAULT_TYPES_LIST: List<Class<out UElement>> = listOf(UElement::class.java)
val DEFAULT_TYPES_LIST: Array<Class<out UElement>> = arrayOf(UElement::class.java)
@JvmField
val DEFAULT_EXPRESSION_TYPES_LIST: List<Class<out UExpression>> = listOf(UExpression::class.java)
val DEFAULT_EXPRESSION_TYPES_LIST: Array<Class<out UExpression>> = arrayOf(UExpression::class.java)
@@ -106,7 +106,7 @@ interface UastLanguagePlugin {
fun isExpressionValueUsed(element: UExpression): Boolean
@JvmDefault
fun <T : UElement> convertElementWithParent(element: PsiElement, requiredTypes: List<Class<out T>>): T? =
fun <T : UElement> convertElementWithParent(element: PsiElement, requiredTypes: Array<out Class<out T>>): T? =
when {
requiredTypes.isEmpty() -> convertElementWithParent(element, null)
requiredTypes.size == 1 -> convertElementWithParent(element, requiredTypes.single())
@@ -116,7 +116,7 @@ interface UastLanguagePlugin {
@JvmDefault
fun <T : UElement> convertToAlternatives(element: PsiElement, requiredTypes: List<Class<out T>>): Sequence<T> =
fun <T : UElement> convertToAlternatives(element: PsiElement, requiredTypes: Array<out Class<out T>>): Sequence<T> =
sequenceOf(convertElementWithParent(element, requiredTypes)).filterNotNull()
}
@@ -9,28 +9,26 @@ class UElementToPsiElementMapping(val baseMapping: Map<Class<out UElement>, Clas
constructor(vararg mapping: Pair<Class<out UElement>, ClassSet>) : this(mapOf(*mapping))
private val internalMapping = ConcurrentHashMap<Class<out UElement>, List<ClassSet>>()
private val internalMapping = ConcurrentHashMap<Class<out UElement>, ClassSet>()
private operator fun get(uCls: Class<out UElement>): List<ClassSet> {
operator fun get(uCls: Class<out UElement>): ClassSet {
internalMapping[uCls]?.let { return it }
var result = mutableListOf<ClassSet>()
val applicableClassSets = mutableListOf<ClassSet>()
for ((key, set) in baseMapping.entries) {
if (uCls.isAssignableFrom(key)) result.add(set)
}
if (result.size > MERGING_CLASS_SET_LIMIT) {
result = mutableListOf(ClassSet(*result.flatMap { it.initialClasses.toList() }.toTypedArray()))
if (uCls.isAssignableFrom(key)) applicableClassSets.add(set)
}
val result = mergeClassSets(applicableClassSets)
internalMapping[uCls] = result
return result
}
fun canConvert(psiCls: Class<out PsiElement>, targets: List<Class<out UElement>>): Boolean {
fun canConvert(psiCls: Class<out PsiElement>, targets: Array<out Class<out UElement>>): Boolean {
for (target in targets) {
if (this[target].any { it.contains(psiCls) })
if (this[target].contains(psiCls))
return true
}
return false
@@ -39,17 +37,30 @@ class UElementToPsiElementMapping(val baseMapping: Map<Class<out UElement>, Clas
}
private const val MERGING_CLASS_SET_LIMIT = 5
private const val SIMPLE_CLASS_SET_LIMIT = 5
private fun mergeClassSets(result: List<ClassSet>) = result.singleOrNull() ?: ClassSet(
*result.flatMap { it.initialClasses.asIterable() }.toTypedArray())
class ClassSet(vararg val initialClasses: Class<*>) {
private val internalMapping = ConcurrentHashMap<Class<*>, Boolean>().apply {
for (initialClass in initialClasses) {
this[initialClass] = true
}
private val isSimple = initialClasses.size <= SIMPLE_CLASS_SET_LIMIT
private lateinit var internalMapping: ConcurrentHashMap<Class<*>, Boolean>
init {
if (!isSimple)
internalMapping = ConcurrentHashMap<Class<*>, Boolean>().apply {
for (initialClass in initialClasses) {
this[initialClass] = true
}
}
}
fun contains(cls: Class<*>): Boolean =
internalMapping[cls] ?: initialClasses.any { it.isAssignableFrom(cls) }.also { internalMapping[cls] = it }
if (isSimple) anyAssignable(cls) else internalMapping[cls] ?: anyAssignable(cls).also { internalMapping[cls] = it }
private fun anyAssignable(cls: Class<*>): Boolean = initialClasses.any { it.isAssignableFrom(cls) }
}
@@ -18,7 +18,6 @@ package org.jetbrains.uast.java
import com.intellij.lang.Language
import com.intellij.lang.java.JavaLanguage
import com.intellij.openapi.util.registry.Registry
import com.intellij.psi.*
import com.intellij.psi.impl.source.tree.java.PsiLiteralExpressionImpl
import com.intellij.psi.util.PsiTreeUtil
@@ -29,8 +28,6 @@ import org.jetbrains.uast.java.expressions.JavaUSynchronizedExpression
class JavaUastLanguagePlugin : UastLanguagePlugin {
private val checkCanConvert = Registry.`is`("uast.java.use.psi.type.precheck")
override val priority: Int = 0
override fun isFileSupported(fileName: String): Boolean = fileName.endsWith(".java", ignoreCase = true)
@@ -99,20 +96,19 @@ class JavaUastLanguagePlugin : UastLanguagePlugin {
}
@Suppress("UNCHECKED_CAST")
fun <T : UElement> convertElement(element: PsiElement, parent: UElement?, requiredTypes: List<Class<out T>>): T? {
fun <T : UElement> convertElement(element: PsiElement, parent: UElement?, requiredTypes: Array<out Class<out T>>): T? {
val nonEmptyRequiredTypes = requiredTypes.nonEmptyOr(DEFAULT_TYPES_LIST)
if (checkCanConvert && !canConvert(element.javaClass, nonEmptyRequiredTypes)) return null
if (!canConvert(element.javaClass, requiredTypes)) return null
return (convertDeclaration(element, parent, nonEmptyRequiredTypes)
?: JavaConverter.convertPsiElement(element, parent, nonEmptyRequiredTypes)) as? T
}
override fun <T : UElement> convertElementWithParent(element: PsiElement, requiredTypes: List<Class<out T>>): T? {
override fun <T : UElement> convertElementWithParent(element: PsiElement, requiredTypes: Array<out Class<out T>>): T? {
return convertElement(element, null, requiredTypes)
}
override fun <T : UElement> convertToAlternatives(element: PsiElement, requiredTypes: List<Class<out T>>) = when (element) {
is `PsiMethodCallExpression` ->
override fun <T : UElement> convertToAlternatives(element: PsiElement, requiredTypes: Array<out Class<out T>>) = when (element) {
is PsiMethodCallExpression ->
JavaConverter.psiMethodCallConversionAlternatives(element,
null,
requiredTypes.nonEmptyOr(DEFAULT_EXPRESSION_TYPES_LIST)) as Sequence<T>
@@ -121,7 +117,7 @@ class JavaUastLanguagePlugin : UastLanguagePlugin {
private fun convertDeclaration(element: PsiElement,
givenParent: UElement?,
requiredType: List<Class<out UElement>>): UElement? {
requiredType: Array<out Class<out UElement>>): UElement? {
fun <P : PsiElement> build(ctor: (P, UElement?) -> UElement): () -> UElement? {
return fun(): UElement? {
return ctor(element as P, givenParent)
@@ -155,15 +151,15 @@ internal inline fun <reified ActualT : UElement> Class<*>?.el(f: () -> UElement?
return if (this == null || isAssignableFrom(ActualT::class.java)) f() else null
}
internal inline fun <reified ActualT : UElement> List<Class<out UElement>>.el(f: () -> UElement?): UElement? {
internal inline fun <reified ActualT : UElement> Array<out Class<out UElement>>.el(f: () -> UElement?): UElement? {
return if (isAssignableFrom(ActualT::class.java)) f() else null
}
internal inline fun <reified ActualT : UElement> List<Class<out UElement>>.expr(f: () -> UExpression?): UExpression? {
internal inline fun <reified ActualT : UElement> Array<out Class<out UElement>>.expr(f: () -> UExpression?): UExpression? {
return if (isAssignableFrom(ActualT::class.java)) f() else null
}
internal fun List<Class<out UElement>>.isAssignableFrom(cls: Class<*>) = any { it.isAssignableFrom(cls) }
internal fun Array<out Class<out UElement>>.isAssignableFrom(cls: Class<*>) = any { it.isAssignableFrom(cls) }
internal object JavaConverter {
@@ -181,7 +177,7 @@ internal object JavaConverter {
internal fun convertPsiElement(el: PsiElement,
givenParent: UElement?,
requiredType: List<Class<out UElement>> = DEFAULT_TYPES_LIST): UElement? {
requiredType: Array<out Class<out UElement>> = DEFAULT_TYPES_LIST): UElement? {
fun <P : PsiElement> build(ctor: (P, UElement?) -> UElement): () -> UElement? {
return fun(): UElement? {
@@ -214,7 +210,7 @@ internal object JavaConverter {
internal fun convertReference(reference: PsiJavaCodeReferenceElement,
givenParent: UElement?,
requiredType: List<Class<out UElement>> = DEFAULT_TYPES_LIST): UExpression? {
requiredType: Array<out Class<out UElement>> = DEFAULT_TYPES_LIST): UExpression? {
return with(requiredType) {
if (reference.isQualified) {
expr<UQualifiedReferenceExpression> { JavaUQualifiedReferenceExpression(reference, givenParent) }
@@ -228,7 +224,7 @@ internal object JavaConverter {
internal fun convertExpression(el: PsiExpression,
givenParent: UElement?,
requiredType: List<Class<out UElement>> = DEFAULT_EXPRESSION_TYPES_LIST): UExpression? {
requiredType: Array<out Class<out UElement>> = DEFAULT_EXPRESSION_TYPES_LIST): UExpression? {
fun <P : PsiElement> build(ctor: (P, UElement?) -> UExpression): () -> UExpression? {
return fun(): UExpression? {
return ctor(el as P, givenParent)
@@ -271,7 +267,7 @@ internal object JavaConverter {
internal fun psiMethodCallConversionAlternatives(element: PsiMethodCallExpression,
givenParent: UElement?,
requiredTypes: List<Class<out UElement>>): Sequence<UExpression> {
requiredTypes: Array<out Class<out UElement>>): Sequence<UExpression> {
if (element.methodExpression.qualifierExpression == null) {
return sequenceOf(requiredTypes.expr<UCallExpression> { JavaUCallExpression(element, givenParent) }).filterNotNull()
}
@@ -293,7 +289,7 @@ internal object JavaConverter {
internal fun convertStatement(el: PsiStatement,
givenParent: UElement?,
requiredType: List<Class<out UElement>> = DEFAULT_EXPRESSION_TYPES_LIST): UExpression? {
requiredType: Array<out Class<out UElement>> = DEFAULT_EXPRESSION_TYPES_LIST): UExpression? {
fun <P : PsiElement> build(ctor: (P, UElement?) -> UExpression): () -> UExpression? {
return fun(): UExpression? {
return ctor(el as P, givenParent)
@@ -373,8 +369,9 @@ internal object JavaConverter {
}
}
private fun expressionTypes(requiredType: Class<out UElement>?) = requiredType?.let { listOf(it) } ?: DEFAULT_EXPRESSION_TYPES_LIST
private fun expressionTypes(requiredType: Class<out UElement>?) = requiredType?.let { arrayOf(it) } ?: DEFAULT_EXPRESSION_TYPES_LIST
private fun elementTypes(requiredType: Class<out UElement>?) = requiredType?.let { listOf(it) } ?: DEFAULT_TYPES_LIST
private fun elementTypes(requiredType: Class<out UElement>?) = requiredType?.let { arrayOf(it) } ?: DEFAULT_TYPES_LIST
private fun <T : UElement> List<Class<out T>>.nonEmptyOr(default: List<Class<out UElement>>) = takeIf { it.isNotEmpty() } ?: default
private fun <T : UElement> Array<out Class<out T>>.nonEmptyOr(default: Array<out Class<out UElement>>) = takeIf { it.isNotEmpty() }
?: default
@@ -1,15 +1,30 @@
// Copyright 2000-2018 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 org.jetbrains.uast.java
import com.intellij.openapi.util.registry.Registry
import com.intellij.psi.*
import com.intellij.psi.impl.source.tree.java.PsiLiteralExpressionImpl
import org.jetbrains.uast.*
import org.jetbrains.uast.internal.ClassSet
import org.jetbrains.uast.internal.UElementToPsiElementMapping
private val checkCanConvert = Registry.`is`("uast.java.use.psi.type.precheck")
internal fun canConvert(psiCls: Class<out PsiElement>, targets: List<Class<out UElement>>) =
conversionMapping.canConvert(psiCls, targets)
internal fun canConvert(psiCls: Class<out PsiElement>, targets: Array<out Class<out UElement>>): Boolean {
if (!checkCanConvert) return true
if (targets.size == 1) {
// checking the most popular cases before looking up in hashtable
when (targets.single()) {
UElement::class.java -> uElementClassSet.contains(psiCls)
ULiteralExpression::class.java -> uLiteralClassSet.contains(psiCls)
UCallExpression::class.java -> uCallClassSet.contains(psiCls)
}
}
return conversionMapping.canConvert(psiCls, targets)
}
private val conversionMapping = UElementToPsiElementMapping(
UClass::class.java to ClassSet(PsiClass::class.java),
@@ -83,3 +98,9 @@ private val conversionMapping = UElementToPsiElementMapping(
UExpression::class.java to ClassSet(PsiExpressionStatement::class.java),
USwitchClauseExpression::class.java to ClassSet(PsiSwitchLabelStatementBase::class.java)
)
val uElementClassSet = ClassSet(*conversionMapping.baseMapping.flatMap { it.value.initialClasses.asIterable() }.toTypedArray())
val uLiteralClassSet: ClassSet = conversionMapping[ULiteralExpression::class.java]
val uCallClassSet: ClassSet = conversionMapping[UCallExpression::class.java]
@@ -84,21 +84,21 @@ class JavaUastApiTest : AbstractJavaUastTest() {
val javaUastLanguagePlugin = UastLanguagePlugin.byLanguage(callExpression.language)!!
javaUastLanguagePlugin.convertToAlternatives(callExpression, listOf(UCallExpression::class.java)).let {
javaUastLanguagePlugin.convertToAlternatives(callExpression, arrayOf(UCallExpression::class.java)).let {
assertEquals("format(\"q\")", it.joinToString(transform = UExpression::asRenderString))
}
javaUastLanguagePlugin.convertToAlternatives<UExpression>(callExpression, listOf(UQualifiedReferenceExpression::class.java,
UCallExpression::class.java)).let {
javaUastLanguagePlugin.convertToAlternatives<UExpression>(callExpression, arrayOf(UQualifiedReferenceExpression::class.java,
UCallExpression::class.java)).let {
assertEquals("String.format(\"q\"), format(\"q\")", it.joinToString(transform = UExpression::asRenderString))
}
javaUastLanguagePlugin.convertToAlternatives<UExpression>(callExpression, listOf(UCallExpression::class.java,
UQualifiedReferenceExpression::class.java)).let {
javaUastLanguagePlugin.convertToAlternatives<UExpression>(callExpression, arrayOf(UCallExpression::class.java,
UQualifiedReferenceExpression::class.java)).let {
assertEquals("format(\"q\"), String.format(\"q\")", it.joinToString(transform = UExpression::asRenderString))
}
javaUastLanguagePlugin.convertToAlternatives(callExpression, listOf(UExpression::class.java)).let {
javaUastLanguagePlugin.convertToAlternatives(callExpression, arrayOf(UExpression::class.java)).let {
assertEquals("String.format(\"q\"), format(\"q\")", it.joinToString(transform = UExpression::asRenderString))
}