Skip to content

Commit

Permalink
extended generic inspections for method and constructor calls and add…
Browse files Browse the repository at this point in the history
…ed some tests for class constructor
  • Loading branch information
i582 committed Mar 27, 2022
1 parent 26102d0 commit 87ce1f2
Show file tree
Hide file tree
Showing 21 changed files with 615 additions and 105 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class ExPhpTypeTplInstantiation(val classFqn: String, val specializationList: Li
override fun isAssignableFrom(rhs: ExPhpType, project: Project): Boolean = when (rhs) {
is ExPhpTypeAny -> true
// not finished
is ExPhpTypePipe -> rhs.items.any { it.isAssignableFrom(this, project) }
is ExPhpTypeTplInstantiation -> classFqn == rhs.classFqn && specializationList.size == rhs.specializationList.size
else -> false
}
Expand Down
23 changes: 17 additions & 6 deletions src/main/kotlin/com/vk/kphpstorm/exphptype/PsiToExPhpType.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,31 @@ object PsiToExPhpType {
else -> null
}

fun dropGenerics(type: ExPhpType): ExPhpType {
fun dropGenerics(type: ExPhpType): ExPhpType? {
// TODO: добавить все типы
if (type is ExPhpTypePipe) {
return ExPhpTypePipe(type.items.filter {
it !is ExPhpTypeGenericsT && (it !is ExPhpTypeArray || it.inner !is ExPhpTypeGenericsT)
}.map { dropGenerics(it) })
val items = type.items.mapNotNull { dropGenerics(it) }
if (items.isEmpty()) return null

return ExPhpTypePipe(items)
}

if (type is ExPhpTypeTplInstantiation) {
val list = type.specializationList.mapNotNull { dropGenerics(it) }
if (list.isEmpty()) return null
return ExPhpTypeTplInstantiation(type.classFqn, list)
}

if (type is ExPhpTypeNullable) {
return ExPhpTypeNullable(dropGenerics(type.inner))
return dropGenerics(type.inner)?.let { ExPhpTypeNullable(it) }
}

if (type is ExPhpTypeArray) {
return ExPhpTypeArray(dropGenerics(type.inner))
return dropGenerics(type.inner)?.let { ExPhpTypeArray(it) }
}

if (type is ExPhpTypeGenericsT) {
return null
}

return type
Expand Down
189 changes: 160 additions & 29 deletions src/main/kotlin/com/vk/kphpstorm/generics/GenericFunctionCall.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.vk.kphpstorm.generics
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.jetbrains.php.PhpIndex
import com.jetbrains.php.lang.psi.PhpPsiElementFactory
import com.jetbrains.php.lang.psi.elements.*
import com.jetbrains.php.lang.psi.elements.Function
import com.jetbrains.php.lang.psi.resolve.types.PhpType
Expand Down Expand Up @@ -76,6 +77,25 @@ class GenericsReifier(val project: Project) {
}

if (paramExType is ExPhpTypePipe) {
// если случай paramExType это Vector|Vector<%T> и argExType это Vector|Vector<A>
val instantiationParamType =
paramExType.items.find { it is ExPhpTypeTplInstantiation } as ExPhpTypeTplInstantiation?
if (instantiationParamType != null && argExType is ExPhpTypePipe) {
val instantiationArgType =
argExType.items.find { it is ExPhpTypeTplInstantiation } as ExPhpTypeTplInstantiation?
if (instantiationArgType != null) {
for (i in 0 until min(
instantiationArgType.specializationList.size,
instantiationParamType.specializationList.size
)) {
reifyArgumentGenericsT(
instantiationArgType.specializationList[i],
instantiationParamType.specializationList[i]
)
}
}
}

// если случай T|false
if (paramExType.items.size == 2 && paramExType.items.any { it == ExPhpType.FALSE }) {
if (argExType is ExPhpTypePipe) {
Expand Down Expand Up @@ -377,6 +397,125 @@ class ResolvingGenericMethodCall(project: Project) : ResolvingGenericBase(projec
}
}

class GenericConstructorCall(call: NewExpression) : GenericCall(call.project) {
override val callArgs: Array<PsiElement> = call.parameters
override val argumentsTypes: List<ExPhpType?> = callArgs
.filterIsInstance<PhpTypedElement>().map { it.type.global(project).toExPhpType() }
override val explicitSpecsPsi = findInstantiationComment(call)

private val klass: PhpClass?
private val method: Method?

init {
val className = call.classReference?.fqn
klass = PhpIndex.getInstance(project).getClassesByFQN(className).firstOrNull()
val constructor = klass?.constructor

// Если у класса нет конструктора, то создаем его псевдо версию
method = constructor ?: createPseudoConstructor(project, klass?.name ?: "Foo")

init()
}

override fun function() = method

override fun isResolved() = method != null && klass != null

override fun genericNames(): List<String> {
val methodsNames = method?.genericNames() ?: emptyList()
val classesNames = klass?.genericNames() ?: emptyList()

return mutableListOf<String>()
.apply { addAll(methodsNames) }
.apply { addAll(classesNames) }
.toList()
}

override fun isGeneric() = genericNames().isNotEmpty()

override fun toString(): String {
val function = function()
val explicit = explicitSpecs.joinToString(",")
val implicit = implicitSpecs.joinToString(",")
return "${klass?.fqn ?: "UnknownClass"}->__construct<$explicit>($implicit)"
}

private fun createPseudoConstructor(project: Project, className: String): Method {
return PhpPsiElementFactory.createPhpPsiFromText(
project,
Method::class.java, "class $className{ public function __construct() {} }"
)
}
}

class GenericMethodCall(call: MethodReference) : GenericCall(call.project) {
override val callArgs: Array<PsiElement> = call.parameters
override val argumentsTypes: List<ExPhpType?> = callArgs
.filterIsInstance<PhpTypedElement>().map { it.type.global(project).toExPhpType() }
override val explicitSpecsPsi = findInstantiationComment(call)

private val klass: PhpClass?
private val method: Method?

init {
// val classTypes = call.classReference?.type?.global(project)?.toExPhpType()
//
// val classType = if (classTypes is ExPhpTypePipe) {
// classTypes.items.firstOrNull { it is ExPhpTypeInstance } as? ExPhpTypeInstance
// } else {
// classTypes as? ExPhpTypeInstance
// }

method = call.resolve() as? Method
klass = method?.containingClass

init()
}

override fun function() = method

override fun isResolved() = method != null && klass != null

override fun genericNames() = method?.genericNames() ?: emptyList()

override fun isGeneric() = genericNames().isNotEmpty()

override fun toString(): String {
val function = function()
val explicit = explicitSpecs.joinToString(",")
val implicit = implicitSpecs.joinToString(",")
return "${klass?.fqn ?: "UnknownClass"}->${function?.name ?: "UnknownMethod"}<$explicit>($implicit)"
}
}

class GenericFunctionCall(call: FunctionReference) : GenericCall(call.project) {
override val callArgs: Array<PsiElement> = call.parameters
override val argumentsTypes: List<ExPhpType?> = callArgs
.filterIsInstance<PhpTypedElement>().map { it.type.global(project).toExPhpType() }
override val explicitSpecsPsi = findInstantiationComment(call)

private val function: Function? = call.resolve() as? Function

init {
init()
}

override fun function() = function

override fun isResolved() = function != null

override fun genericNames() = function?.genericNames() ?: emptyList()

override fun isGeneric() = function()?.isGeneric() == true

override fun toString(): String {
val function = function()
val explicit = explicitSpecs.joinToString(",")
val implicit = implicitSpecs.joinToString(",")
return "${function?.fqn ?: "UnknownFunction"}<$explicit>($implicit)"
}
}

/**
* Ввиду причин описанных в [IndexingGenericFunctionCall], мы не можем использовать
* объединенный класс для обработки вызова во время вывода типов. Однако в других
Expand Down Expand Up @@ -413,41 +552,42 @@ class ResolvingGenericMethodCall(project: Project) : ResolvingGenericBase(projec
* f/*<C, D>*/(new A, new B); // => T1 = C, T2 = D
* ```
*/
class GenericFunctionCall(call: FunctionReference) {
val project = call.project
val function = call.resolve() as? Function
abstract class GenericCall(val project: Project) {
abstract val callArgs: Array<PsiElement>
abstract val explicitSpecsPsi: GenericInstantiationPsiCommentImpl?
abstract val argumentsTypes: List<ExPhpType?>

abstract fun function(): Function?
abstract fun isResolved(): Boolean
abstract fun genericNames(): List<String>
abstract fun isGeneric(): Boolean

val genericTs = mutableListOf<String>()
private val parameters = mutableListOf<Parameter>()
private val callArgs = call.parameters
private val argumentsTypes =
callArgs.filterIsInstance<PhpTypedElement>().map { it.type.global(project).toExPhpType() }

val explicitSpecsPsi = findInstantiationComment(call)

private val extractor = GenericInstantiationExtractor()
private val reifier = GenericsReifier(call.project)
protected val extractor = GenericInstantiationExtractor()
protected val reifier = GenericsReifier(project)

val explicitSpecs get() = extractor.explicitSpecs
val implicitSpecs get() = reifier.implicitSpecs
val implicitSpecializationNameMap get() = reifier.implicitSpecializationNameMap
val implicitSpecializationErrors get() = reifier.implicitSpecializationErrors

init {
init()
}
protected fun init() {
val function = function() ?: return
if (!isGeneric()) return

private fun init() {
if (function == null || !isGeneric()) return
val genericNames = genericNames()

parameters.addAll(function.parameters)
genericTs.addAll(function.genericNames())
genericTs.addAll(genericNames)

// Несмотря на то, что явный список является превалирующим над
// типами выведенными из аргументов функций, нам все равно
// необходимы обв списка для дальнейших инспекций

// В первую очередь, выводим все типы шаблонов из аргументов функции (при наличии)
reifier.reifyAllGenericsT(function.parameters, function.genericNames(), argumentsTypes)
reifier.reifyAllGenericsT(function.parameters, genericNames, argumentsTypes)
// Далее, выводим все типы шаблонов из явного списка типов (при наличии)
extractor.extractExplicitGenericsT(function, explicitSpecsPsi)
}
Expand All @@ -456,10 +596,6 @@ class GenericFunctionCall(call: FunctionReference) {
return explicitSpecsPsi != null
}

fun isGeneric(): Boolean {
return function?.isGeneric() == true
}

/**
* Функция проверяющая, что явно указанные шаблонные типы
* соответствуют автоматически выведенным типам и их можно
Expand All @@ -478,10 +614,9 @@ class GenericFunctionCall(call: FunctionReference) {
* ```
*/
fun isNoNeedExplicitSpec(): Boolean {
if (function == null) return false
if (explicitSpecsPsi == null) return false

val countGenericNames = function.genericNames().size
val countGenericNames = genericNames().size
val countExplicitSpecs = explicitSpecs.size
val countImplicitSpecs = implicitSpecs.size

Expand Down Expand Up @@ -523,7 +658,7 @@ class GenericFunctionCall(call: FunctionReference) {
* В примере выше в результате будет возвращен тип `Foo`.
*/
fun typeOfParam(index: Int): ExPhpType? {
if (function == null) return null
val function = function() ?: return null

val param = function.getParameter(index) ?: return null
val paramType = param.type
Expand All @@ -537,9 +672,5 @@ class GenericFunctionCall(call: FunctionReference) {
return null
}

override fun toString(): String {
val explicit = explicitSpecs.joinToString(",")
val implicit = implicitSpecs.joinToString(",")
return "${function?.fqn ?: "UnknownFunction"}<$explicit>($implicit)"
}
abstract override fun toString(): String
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ class KphpStormTypeInfoProvider : ExpressionTypeProvider<PhpTypedElement>() {

override fun getInformationHint(element: PhpTypedElement): String {
val phpType = element.type.global(element.project)
return phpType.toExPhpType()?.let { PsiToExPhpType.dropGenerics(it).toHumanReadable(element) } ?: phpType.toString()
val exPhpType = phpType.toExPhpType() ?: return phpType.toString()
return PsiToExPhpType.dropGenerics(exPhpType)?.toHumanReadable(element) ?: phpType.toString()
}

override fun getExpressionsAt(elementAt: PsiElement): List<PhpTypedElement> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.refactoring.suggested.endOffset
import com.jetbrains.php.lang.psi.elements.FunctionReference
import com.jetbrains.php.lang.psi.elements.MethodReference
import com.vk.kphpstorm.generics.GenericFunctionCall
import com.vk.kphpstorm.generics.GenericUtil.genericNames

@Suppress("UnstableApiUsage")
class InlayHintsCollector(
Expand Down Expand Up @@ -41,11 +41,17 @@ class InlayHintsCollector(
return
}

val genericNames = call.function!!.genericNames().joinToString(", ")
val genericNames = call.genericNames().joinToString(", ")
val simplePresentation = myHintsFactory.inlayHint("<$genericNames>")

val namePsi = element.firstChild
val offset = if (element is MethodReference) {
// offset after "->method_name"
namePsi.nextSibling.nextSibling.endOffset
} else {
namePsi.endOffset
}

sink.addInlineElement(namePsi.endOffset, false, simplePresentation, false)
sink.addInlineElement(offset, false, simplePresentation, false)
}
}

This file was deleted.

Loading

0 comments on commit 87ce1f2

Please sign in to comment.