Skip to content

Commit

Permalink
update (wip)
Browse files Browse the repository at this point in the history
  • Loading branch information
i582 committed May 26, 2022
1 parent 48f300a commit afc47aa
Show file tree
Hide file tree
Showing 82 changed files with 1,951 additions and 1,490 deletions.
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,10 @@ intellij {
// for release versions: https://www.jetbrains.com/intellij-repository/releases (com.jetbrains.intellij.idea)
// for EAPs: https://www.jetbrains.com/intellij-repository/snapshots
version = '2022.1.1'
// version = '2021.3.1'
plugins = [
'com.jetbrains.php:221.5591.58', // https://plugins.jetbrains.com/plugin/6610-php/versions
// 'com.jetbrains.php:213.5744.223',
]
}
runIde {
Expand Down
5 changes: 5 additions & 0 deletions src/main/kotlin/com/vk/kphpstorm/KphpStormASTFactory.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package com.vk.kphpstorm

import com.intellij.lang.DefaultASTFactoryImpl
import com.intellij.psi.impl.source.tree.CompositeElement
import com.intellij.psi.impl.source.tree.LeafElement
import com.intellij.psi.tree.IElementType
import com.vk.kphpstorm.generics.psi.GenericInstantiationPsiCommentImpl

class KphpStormASTFactory : DefaultASTFactoryImpl() {
override fun createComposite(type: IElementType): CompositeElement {
return super.createComposite(type)
}

override fun createComment(type: IElementType, text: CharSequence): LeafElement {
if (text.startsWith("/*<") && text.endsWith(">*/")) {
return GenericInstantiationPsiCommentImpl(type, text)
Expand Down
39 changes: 5 additions & 34 deletions src/main/kotlin/com/vk/kphpstorm/exphptype/ExPhpTypeClassString.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import com.jetbrains.php.lang.psi.elements.PhpPsiElement
import com.jetbrains.php.lang.psi.resolve.types.PhpType

/**
* Type of special class constant (`Foo::class`).
* Type of special class constant (e.g. `Foo::class`).
*/
class ExPhpTypeClassString(val inner: ExPhpType) : ExPhpType {
override fun toString() = "class-string($inner)"
Expand All @@ -16,13 +16,9 @@ class ExPhpTypeClassString(val inner: ExPhpType) : ExPhpType {

override fun hashCode() = 35

override fun toPhpType(): PhpType {
return PhpType().add("class-string($inner)")
}
override fun toPhpType() = PhpType().add("class-string(${inner.toPhpType()})")

override fun getSubkeyByIndex(indexKey: String): ExPhpType? {
return this
}
override fun getSubkeyByIndex(indexKey: String) = this

override fun instantiateGeneric(nameMap: Map<String, ExPhpType>): ExPhpType {
// TODO: подумать тут
Expand All @@ -36,34 +32,9 @@ class ExPhpTypeClassString(val inner: ExPhpType) : ExPhpType {
}

override fun isAssignableFrom(rhs: ExPhpType, project: Project): Boolean = when (rhs) {
// нативный вывод типов дает тип string|class-string<T> для T::class, поэтому
// необходимо обработать этот случай отдельно
is ExPhpTypePipe -> {
val containsString = rhs.items.any { it == ExPhpType.STRING }
if (rhs.items.size == 2 && containsString) {
val otherType = rhs.items.find { it != ExPhpType.STRING }
if (otherType == null) false
else isAssignableFrom(otherType, project)
} else false
}
// class-string<T> совместим только с class-string<E> при условии
// что класс E является допустимым для класса T.
// class-string<T> is only compatible with class-string<E>
// if class E is compatible with class T.
is ExPhpTypeClassString -> inner.isAssignableFrom(rhs.inner, project)
else -> false
}

companion object {
// нативный вывод типов дает тип string|class-string<T> для T::class,
// из-за этого в некоторых местах нужна дополнительная логика.
fun isNativePipeWithString(pipe: ExPhpTypePipe): Boolean {
if (pipe.items.size != 2) return false
val otherType = pipe.items.find { it != ExPhpType.STRING }

return otherType is ExPhpTypeClassString
}

fun getClassFromNativePipeWithString(pipe: ExPhpTypePipe): ExPhpType {
return pipe.items.find { it != ExPhpType.STRING }!!
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class ExPhpTypeGenericsT(val nameT: String) : ExPhpType {
override fun toHumanReadable(expr: PhpPsiElement) = "%$nameT"

override fun toPhpType(): PhpType {
return PhpType().add("%$nameT")
return PhpType.EMPTY
}

override fun getSubkeyByIndex(indexKey: String): ExPhpType? {
Expand Down
4 changes: 0 additions & 4 deletions src/main/kotlin/com/vk/kphpstorm/exphptype/ExPhpTypePipe.kt
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,6 @@ class ExPhpTypePipe(val items: List<ExPhpType>) : ExPhpType {
})
ok = true
}

if (ExPhpTypeClassString.isNativePipeWithString(this)) {
return lhs.isAssignableFrom(ExPhpTypeClassString.getClassFromNativePipeWithString(this), project)
}
}

return ok
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.vk.kphpstorm.exphptype

import com.intellij.openapi.project.Project
import com.jetbrains.php.PhpClassHierarchyUtils
import com.jetbrains.php.PhpIndex
import com.jetbrains.php.codeInsight.PhpCodeInsightUtil
import com.jetbrains.php.lang.psi.elements.PhpPsiElement
import com.jetbrains.php.lang.psi.resolve.types.PhpType
Expand Down Expand Up @@ -31,6 +33,20 @@ class ExPhpTypeTplInstantiation(val classFqn: String, val specializationList: Li
// not finished
is ExPhpTypePipe -> rhs.items.any { it.isAssignableFrom(this, project) }
is ExPhpTypeTplInstantiation -> classFqn == rhs.classFqn && specializationList.size == rhs.specializationList.size

is ExPhpTypeInstance -> rhs.fqn == classFqn || run {
val phpIndex = PhpIndex.getInstance(project)
val lhsClass = phpIndex.getAnyByFQN(classFqn).firstOrNull() ?: return false
var rhsIsChild = false
phpIndex.getAnyByFQN(rhs.fqn).forEach { rhsClass ->
PhpClassHierarchyUtils.processSuperWithoutMixins(rhsClass, true, true) { clazz ->
if (PhpClassHierarchyUtils.classesEqual(lhsClass, clazz))
rhsIsChild = true
!rhsIsChild
}
}
rhsIsChild
}
else -> false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -302,9 +302,11 @@ object PhpTypeToExPhpTypeParsing {
private fun parseTypeExpression(builder: ExPhpTypeBuilder): ExPhpType? {
val lhs = parseTypeArray(builder) ?: return null
// wrap with ExPhpTypePipe only 'T1|T2', leaving 'T' being as is
if (!builder.compare('|') && !builder.compare('/'))
return if (lhs is ExPhpTypeForcing) lhs.inner else lhs

if (!builder.compare('|') && !builder.compare('/')) {
// TODO: здесь была строчка, точно ли она не нужна?
// return if (lhs is ExPhpTypeForcing) lhs.inner else lhs
return lhs
}
val pipeItems = mutableListOf(lhs)
while (builder.compareAndEat('|') || builder.compareAndEat('/')) {
val rhs = parseTypeArray(builder) ?: break
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ object PsiToExPhpType {
if (type is ExPhpTypePipe) {
val items = type.items.mapNotNull { dropGenerics(it) }
if (items.isEmpty()) return null
if (items.size == 1) return items.first()

return ExPhpTypePipe(items)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.jetbrains.php.lang.documentation.phpdoc.psi.impl.PhpDocTypeImpl
import com.jetbrains.php.lang.psi.resolve.types.PhpType
import com.vk.kphpstorm.exphptype.KphpPrimitiveTypes
import com.vk.kphpstorm.generics.GenericUtil
import com.vk.kphpstorm.helpers.toExPhpType

/**
* class-string<Foo> — psi is class-string(Foo) corresponding type of Foo::class
Expand Down Expand Up @@ -38,6 +39,12 @@ class ExPhpTypeClassStringPsiImpl(node: ASTNode) : PhpDocTypeImpl(node) {

override fun getType(): PhpType {
if (className.isEmpty()) return KphpPrimitiveTypes.PHP_TYPE_ANY
return PhpType().add("class-string($className)")
if (className.startsWith("%")) return PhpType().add("class-string($className)")

val classPsi = firstChild?.nextSibling?.nextSibling as? ExPhpTypeInstancePsiImpl
?: return KphpPrimitiveTypes.PHP_TYPE_ANY

val innerType = getType(classPsi, text).toExPhpType()
return PhpType().add("class-string($innerType)")
}
}
182 changes: 182 additions & 0 deletions src/main/kotlin/com/vk/kphpstorm/generics/GenericCall.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package com.vk.kphpstorm.generics

import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.util.parentOfType
import com.jetbrains.php.lang.psi.elements.*
import com.jetbrains.php.lang.psi.elements.Function
import com.vk.kphpstorm.exphptype.ExPhpType
import com.vk.kphpstorm.generics.GenericUtil.isGeneric
import com.vk.kphpstorm.generics.psi.GenericInstantiationPsiCommentImpl
import com.vk.kphpstorm.helpers.toExPhpType
import com.vk.kphpstorm.kphptags.psi.KphpDocGenericParameterDecl

/**
* Ввиду причин описанных в [IndexingGenericFunctionCall], мы не можем использовать
* объединенный класс для обработки вызова во время вывода типов. Однако в других
* местах мы можем использовать индекс и поэтому нам не нужно паковать данные и
* потом их распаковывать, мы можем делать все за раз.
*
* Данный класс является объединением [IndexingGenericFunctionCall] и
* [ResolvingGenericFunctionCall] и может быть использован для обработки шаблонных
* вызовов в других местах;
*
* Класс инкапсулирующий в себе всю логику обработки шаблонных вызовов.
*
* Он выводит неявные типы, когда нет явного определения списка типов при инстанциации.
*
* Например:
*
* ```php
* /**
* * @kphp-generic T1, T2
* * @param T1 $a
* * @param T2 $b
* */
* function f($a, $b) {}
*
* f(new A, new B); // => T1 = A, T2 = B
* ```
*
* В случае когда есть явный список типов при инстанциации, он собирает типы
* из него.
*
* Например:
*
* ```php
* f/*<C, D>*/(new A, new B); // => T1 = C, T2 = D
* ```
*/
abstract class GenericCall(val project: Project) {
abstract val callArgs: Array<PsiElement>
abstract val explicitSpecsPsi: GenericInstantiationPsiCommentImpl?
abstract val argumentsTypes: List<ExPhpType?>
protected var contextType: ExPhpType? = null

abstract fun element(): PsiElement?
abstract fun function(): Function?
abstract fun isResolved(): Boolean
abstract fun genericNames(): List<KphpDocGenericParameterDecl>
abstract fun isGeneric(): Boolean

val genericTs = mutableListOf<KphpDocGenericParameterDecl>()
private val parameters = mutableListOf<Parameter>()

protected val extractor = GenericInstantiationExtractor()
protected val reifier = GenericsReifier(project)

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

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

val genericNames = genericNames()

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

// Если текущий вызов находится в return или является аргументом
// функции, то мы можем извлечь дополнительные подсказки по типам.
calcContextType(element)

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

// В первую очередь, выводим все типы шаблонов из аргументов функции (при наличии)
reifier.reifyAllGenericsT(null, function.parameters, genericNames, argumentsTypes, contextType)
// Далее, выводим все типы шаблонов из явного списка типов (при наличии)
extractor.extractExplicitGenericsT(genericNames(), explicitSpecsPsi)
}

private fun calcContextType(element: PsiElement) {
val parent = element.parent
if (parent is PhpReturn) {
val parentFunction = parent.parentOfType<Function>()
if (parentFunction != null) {
val returnType = parentFunction.docComment?.returnTag?.type
contextType = returnType?.toExPhpType()
}

return
}

if (parent is ParameterList) {
val calledInFunctionCall = parent.parentOfType<FunctionReference>()
if (calledInFunctionCall != null) {
val calledFunction = calledInFunctionCall.resolve() as? Function
if (calledFunction != null) {
val index = parent.parameters.indexOf(element)
calledFunction.getParameter(index)?.let {
contextType = it.type.toExPhpType()
}
}
}
}
}

fun withExplicitSpecs(): Boolean {
return explicitSpecsPsi != null
}

/**
* Имея следующую функцию:
*
* ```php
* /**
* * @kphp-generic T
* * @param T $arg
* */
* function f($arg) {}
* ```
*
* И следующий вызов:
*
* ```php
* f/*<Foo>*/(new Foo);
* ```
*
* Нам необходимо вывести тип `$arg`, для того чтобы проверить, что
* переданное выражение `new Foo` имеет правильный тип.
*
* Так как функция может вызываться с разными шаблонными типа, нам
* необходимо найти тип `$arg` для каждого конкретного вызова.
* В примере выше в результате будет возвращен тип `Foo`.
*/
fun typeOfParam(index: Int): ExPhpType? {
val function = function() ?: return null

val param = function.getParameter(index) ?: return null
val paramType = param.type
if (paramType.isGeneric(genericNames())) {
val usedNameMap = extractor.specializationNameMap.ifEmpty {
reifier.implicitSpecializationNameMap
}
return paramType.toExPhpType()?.instantiateGeneric(usedNameMap)
}

return null
}

fun isNotEnoughInformation(): KphpDocGenericParameterDecl? {
if (explicitSpecsPsi != null) return null

genericNames().forEach { decl ->
val resolved = implicitSpecializationNameMap.contains(decl.name)

if (!resolved) {
return decl
}
}

return null
}

abstract override fun toString(): String
}
Loading

0 comments on commit afc47aa

Please sign in to comment.