diff --git a/src/main/kotlin/com/vk/kphpstorm/exphptype/PsiToExPhpType.kt b/src/main/kotlin/com/vk/kphpstorm/exphptype/PsiToExPhpType.kt index c8d5e04..94e2cf3 100644 --- a/src/main/kotlin/com/vk/kphpstorm/exphptype/PsiToExPhpType.kt +++ b/src/main/kotlin/com/vk/kphpstorm/exphptype/PsiToExPhpType.kt @@ -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 diff --git a/src/main/kotlin/com/vk/kphpstorm/generics/GenericFunctionCall.kt b/src/main/kotlin/com/vk/kphpstorm/generics/GenericFunctionCall.kt index d61d8a9..f8db13d 100644 --- a/src/main/kotlin/com/vk/kphpstorm/generics/GenericFunctionCall.kt +++ b/src/main/kotlin/com/vk/kphpstorm/generics/GenericFunctionCall.kt @@ -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 @@ -76,6 +77,25 @@ class GenericsReifier(val project: Project) { } if (paramExType is ExPhpTypePipe) { + // если случай paramExType это Vector|Vector<%T> и argExType это Vector|Vector + 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) { @@ -377,6 +397,85 @@ class ResolvingGenericMethodCall(project: Project) : ResolvingGenericBase(projec } } +class GenericConstructorCall(call: NewExpression) : GenericCall(call.project) { + override val callArgs: Array = call.parameters + override val argumentsTypes: List = callArgs + .filterIsInstance().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 ?: createMethod(project, klass?.name ?: "Foo", "__construct") + + init() + } + + override fun function() = method + + override fun isResolved() = method != null && klass != null + + override fun genericNames(): List { + val methodsNames = method?.genericNames() ?: emptyList() + val classesNames = klass?.genericNames() ?: emptyList() + + return mutableListOf() + .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"}->${function?.name ?: "UnknownMethod"}<$explicit>($implicit)" + } + + private fun createMethod(project: Project, className: String, name: String): Method { + return PhpPsiElementFactory.createPhpPsiFromText( + project, + Method::class.java, "class $className{ public function $name() {} }" + ) + } +} + +class GenericFunctionCall(call: FunctionReference) : GenericCall(call.project) { + override val callArgs: Array = call.parameters + override val argumentsTypes: List = callArgs + .filterIsInstance().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], мы не можем использовать * объединенный класс для обработки вызова во время вывода типов. Однако в других @@ -413,41 +512,42 @@ class ResolvingGenericMethodCall(project: Project) : ResolvingGenericBase(projec * f/**/(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 + abstract val explicitSpecsPsi: GenericInstantiationPsiCommentImpl? + abstract val argumentsTypes: List + + abstract fun function(): Function? + abstract fun isResolved(): Boolean + abstract fun genericNames(): List + abstract fun isGeneric(): Boolean + val genericTs = mutableListOf() private val parameters = mutableListOf() - private val callArgs = call.parameters - private val argumentsTypes = - callArgs.filterIsInstance().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) } @@ -456,10 +556,6 @@ class GenericFunctionCall(call: FunctionReference) { return explicitSpecsPsi != null } - fun isGeneric(): Boolean { - return function?.isGeneric() == true - } - /** * Функция проверяющая, что явно указанные шаблонные типы * соответствуют автоматически выведенным типам и их можно @@ -478,7 +574,7 @@ class GenericFunctionCall(call: FunctionReference) { * ``` */ fun isNoNeedExplicitSpec(): Boolean { - if (function == null) return false + val function = function() ?: return false if (explicitSpecsPsi == null) return false val countGenericNames = function.genericNames().size @@ -523,7 +619,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 @@ -537,9 +633,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 } diff --git a/src/main/kotlin/com/vk/kphpstorm/highlighting/KphpStormTypeInfoProvider.kt b/src/main/kotlin/com/vk/kphpstorm/highlighting/KphpStormTypeInfoProvider.kt index 702ba65..45d59c6 100644 --- a/src/main/kotlin/com/vk/kphpstorm/highlighting/KphpStormTypeInfoProvider.kt +++ b/src/main/kotlin/com/vk/kphpstorm/highlighting/KphpStormTypeInfoProvider.kt @@ -18,7 +18,8 @@ class KphpStormTypeInfoProvider : ExpressionTypeProvider() { 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 { diff --git a/src/main/kotlin/com/vk/kphpstorm/highlighting/hints/InlayHintsCollector.kt b/src/main/kotlin/com/vk/kphpstorm/highlighting/hints/InlayHintsCollector.kt index 0f81325..2128d47 100644 --- a/src/main/kotlin/com/vk/kphpstorm/highlighting/hints/InlayHintsCollector.kt +++ b/src/main/kotlin/com/vk/kphpstorm/highlighting/hints/InlayHintsCollector.kt @@ -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( @@ -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) } } diff --git a/src/main/kotlin/com/vk/kphpstorm/inspections/FunctionGenericInstantiationArgsCountMismatchInspection.kt b/src/main/kotlin/com/vk/kphpstorm/inspections/FunctionGenericInstantiationArgsCountMismatchInspection.kt index ae44934..6c29594 100644 --- a/src/main/kotlin/com/vk/kphpstorm/inspections/FunctionGenericInstantiationArgsCountMismatchInspection.kt +++ b/src/main/kotlin/com/vk/kphpstorm/inspections/FunctionGenericInstantiationArgsCountMismatchInspection.kt @@ -5,24 +5,41 @@ import com.intellij.codeInspection.ProblemsHolder import com.intellij.psi.PsiElementVisitor import com.jetbrains.php.lang.inspections.PhpInspection import com.jetbrains.php.lang.psi.elements.FunctionReference +import com.jetbrains.php.lang.psi.elements.NewExpression import com.jetbrains.php.lang.psi.visitors.PhpElementVisitor +import com.vk.kphpstorm.generics.GenericConstructorCall import com.vk.kphpstorm.generics.GenericFunctionCall -import com.vk.kphpstorm.generics.GenericUtil.genericNames class FunctionGenericInstantiationArgsCountMismatchInspection : PhpInspection() { override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { return object : PhpElementVisitor() { + override fun visitPhpNewExpression(expression: NewExpression) { + val call = GenericConstructorCall(expression) + if (!call.isResolved()) return + + val countGenericNames = call.genericNames().size + val countExplicitSpecs = call.explicitSpecs.size + + if (countGenericNames != countExplicitSpecs && call.explicitSpecsPsi != null) { + holder.registerProblem( + call.explicitSpecsPsi, + "$countGenericNames type arguments expected for ${call.function()!!.fqn}", + ProblemHighlightType.GENERIC_ERROR + ) + } + } + override fun visitPhpFunctionCall(reference: FunctionReference) { val call = GenericFunctionCall(reference) - if (call.function == null) return + if (!call.isResolved()) return - val countGenericNames = call.function.genericNames().size + val countGenericNames = call.genericNames().size val countExplicitSpecs = call.explicitSpecs.size if (countGenericNames != countExplicitSpecs && call.explicitSpecsPsi != null) { holder.registerProblem( call.explicitSpecsPsi, - "$countGenericNames type arguments expected for ${call.function.fqn}", + "$countGenericNames type arguments expected for ${call.function()!!.fqn}", ProblemHighlightType.GENERIC_ERROR ) } diff --git a/src/main/kotlin/com/vk/kphpstorm/inspections/FunctionGenericNoEnoughInformationInspection.kt b/src/main/kotlin/com/vk/kphpstorm/inspections/FunctionGenericNoEnoughInformationInspection.kt index dab4535..f919aa9 100644 --- a/src/main/kotlin/com/vk/kphpstorm/inspections/FunctionGenericNoEnoughInformationInspection.kt +++ b/src/main/kotlin/com/vk/kphpstorm/inspections/FunctionGenericNoEnoughInformationInspection.kt @@ -5,18 +5,44 @@ import com.intellij.codeInspection.ProblemsHolder import com.intellij.psi.PsiElementVisitor import com.jetbrains.php.lang.inspections.PhpInspection import com.jetbrains.php.lang.psi.elements.FunctionReference +import com.jetbrains.php.lang.psi.elements.NewExpression import com.jetbrains.php.lang.psi.visitors.PhpElementVisitor +import com.vk.kphpstorm.generics.GenericConstructorCall import com.vk.kphpstorm.generics.GenericFunctionCall -import com.vk.kphpstorm.generics.GenericUtil.genericNames class FunctionGenericNoEnoughInformationInspection : PhpInspection() { override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { return object : PhpElementVisitor() { + override fun visitPhpNewExpression(expression: NewExpression) { + val call = GenericConstructorCall(expression) + if (!call.isResolved()) return + + val genericNames = call.genericNames() + + if (call.explicitSpecsPsi == null) { + genericNames.any { + val resolved = call.implicitSpecializationNameMap.contains(it) + + if (!resolved) { + holder.registerProblem( + expression, + "Not enough information to infer generic $it", + ProblemHighlightType.GENERIC_ERROR + ) + + return@any true + } + + false + } + } + } + override fun visitPhpFunctionCall(reference: FunctionReference) { val call = GenericFunctionCall(reference) - if (call.function == null) return + if (!call.isResolved()) return - val genericNames = call.function.genericNames() + val genericNames = call.genericNames() if (call.explicitSpecsPsi == null) { genericNames.any { diff --git a/src/main/kotlin/com/vk/kphpstorm/inspections/FunctionGenericSeveralGenericTypesInspection.kt b/src/main/kotlin/com/vk/kphpstorm/inspections/FunctionGenericSeveralGenericTypesInspection.kt index ccfc76b..cc25141 100644 --- a/src/main/kotlin/com/vk/kphpstorm/inspections/FunctionGenericSeveralGenericTypesInspection.kt +++ b/src/main/kotlin/com/vk/kphpstorm/inspections/FunctionGenericSeveralGenericTypesInspection.kt @@ -14,7 +14,7 @@ class FunctionGenericSeveralGenericTypesInspection : PhpInspection() { return object : PhpElementVisitor() { override fun visitPhpFunctionCall(reference: FunctionReference) { val call = GenericFunctionCall(reference) - if (call.function == null) return + if (!call.isResolved()) return // В случае даже если есть ошибки, то мы показываем их только // в случае когда нет явного определения шаблона для вызова функции. diff --git a/src/main/kotlin/com/vk/kphpstorm/inspections/FunctionUnnecessaryExplicitGenericInstantiationListInspection.kt b/src/main/kotlin/com/vk/kphpstorm/inspections/FunctionUnnecessaryExplicitGenericInstantiationListInspection.kt index 4c23bb5..b94a075 100644 --- a/src/main/kotlin/com/vk/kphpstorm/inspections/FunctionUnnecessaryExplicitGenericInstantiationListInspection.kt +++ b/src/main/kotlin/com/vk/kphpstorm/inspections/FunctionUnnecessaryExplicitGenericInstantiationListInspection.kt @@ -14,7 +14,7 @@ class FunctionUnnecessaryExplicitGenericInstantiationListInspection : PhpInspect return object : PhpElementVisitor() { override fun visitPhpFunctionCall(reference: FunctionReference) { val call = GenericFunctionCall(reference) - if (call.function == null) return + if (!call.isResolved()) return if (call.explicitSpecsPsi == null) return if (call.isNoNeedExplicitSpec()) { diff --git a/src/test/fixtures/generics/classes/Vector.fixture.php b/src/test/fixtures/generics/classes/Vector.fixture.php new file mode 100644 index 0000000..49085f1 --- /dev/null +++ b/src/test/fixtures/generics/classes/Vector.fixture.php @@ -0,0 +1,48 @@ +data = $els; + } + + /** + * @return T + */ + public function get(int $index) { + return $this->data[$index]; + } + + /** + * @param T $data + */ + public function add($data): void { + $this->data[] = $data; + } + + /** + * @kphp-generic T1 + * @param class-string $class + * @return T1[] + */ + function filter_is_instance($class) { + return array_filter($this->data, fn($el) => is_a($el, $class)); + } + + /** + * @kphp-generic T1 + * @param Vector $other + * @return Vector + */ + function combine_with($other) { + return new Vector/**/(array_merge($this->data, $other->data)); + } +} diff --git a/src/test/fixtures/generics/classes/instantiation_args_mismatch.inspection.fixture.php b/src/test/fixtures/generics/classes/instantiation_args_mismatch.inspection.fixture.php new file mode 100644 index 0000000..0e9fd60 --- /dev/null +++ b/src/test/fixtures/generics/classes/instantiation_args_mismatch.inspection.fixture.php @@ -0,0 +1,41 @@ +/*<>*/(); + new GenericT/**/(); // ok +} + +"Класс с двумя шаблоннымм типом"; { + /** @kphp-generic T1, T2 */ + class GenericT1T2 {} + + new GenericT1T2/*<>*/(); + new GenericT1T2/**/(); + new GenericT1T2/**/(); // ok +} + +"Класс с одним шаблонным типом с конструктором с еще одним шаблонным типом"; { + /** @kphp-generic T */ + class GenericExplicitConstructorT1AndT { + /** + * @kphp-generic T1 + * @param T1 $el + */ + function __construct($el) {} + } + +// new GenericExplicitConstructorT1AndT/*<>*/(""); +// new GenericExplicitConstructorT1AndT/**/(""); + new GenericExplicitConstructorT1AndT/**/(""); +} diff --git a/src/test/fixtures/generics/classes/no_enough_information.inspection.fixture.php b/src/test/fixtures/generics/classes/no_enough_information.inspection.fixture.php new file mode 100644 index 0000000..0dae705 --- /dev/null +++ b/src/test/fixtures/generics/classes/no_enough_information.inspection.fixture.php @@ -0,0 +1,85 @@ +new GenericT(); + new GenericT(100); // нет зависимости от типа аргумента +} + +"Класс с одним шаблонным типом с нестандартным именем"; { + /** @kphp-generic TKey */ + class GenericTKey {} + + new GenericTKey(); +} + +"Класс с двумя шаблоннымм типом"; { + /** @kphp-generic T1, T2 */ + class GenericT1T2 {} + + // Выводим ошибку только для первого шаблонного типа + new GenericT1T2(); +} + +"Класс с одним шаблонным типом с конструктором с аргументом шаблонного типа Т"; { + /** @kphp-generic T */ + class GenericExplicitConstructorT { + /** @param T $el */ + function __construct($el) {} + } + + new GenericExplicitConstructorT(); + new GenericExplicitConstructorT(100); // в отличии от класса GenericT тут зависимость есть +} + +"Класс с одним шаблонным типом с конструктором с еще одним шаблонным типом"; { + /** @kphp-generic T */ + class GenericExplicitConstructorT1AndT { + /** + * @kphp-generic T1 + * @param T1 $el + */ + function __construct($el) {} + } + + new GenericExplicitConstructorT1AndT(100); + new GenericExplicitConstructorT1AndT/**/(100); // ok +} + +"Класс с одним шаблонным типом с конструктором с еще одним шаблонным типом оба используемые в аргументах"; { + /** @kphp-generic T */ + class GenericExplicitConstructorT1AndExplicitT { + /** + * @kphp-generic T1 + * @param T $el + * @param T1 $el2 + */ + function __construct($el, $el2) {} + } + + new GenericExplicitConstructorT1AndExplicitT(100); + new GenericExplicitConstructorT1AndExplicitT(100, ""); // ok +} + +"Класс с двумя шаблоннымм типом один из которых используется в конструкторе"; { + /** @kphp-generic T1, T2 */ + class GenericExplicitConstructorT1AndImplicitT2 { + /** @param T1 $el */ + function __construct($el) {} + } + + // Выводим ошибку только для первого шаблонного типа + new GenericExplicitConstructorT1AndImplicitT2(); + new GenericExplicitConstructorT1AndImplicitT2(100); + new GenericExplicitConstructorT1AndImplicitT2/**/(100); +} diff --git a/src/test/fixtures/generics/classes/simple_classes.fixture.php b/src/test/fixtures/generics/classes/simple_classes.fixture.php new file mode 100644 index 0000000..c23dbd0 --- /dev/null +++ b/src/test/fixtures/generics/classes/simple_classes.fixture.php @@ -0,0 +1,33 @@ +*/(); +// $vec->add(new GlobalA); +// expr_type($vec->get(0), "\GlobalA"); +// +// $filtered = $vec->filter_is_instance(GlobalD::class); +// expr_type($filtered[0], "\GlobalD"); +} + +"Неявные типы"; { +// $vec1 = new Vector(new GlobalA, new GlobalA); +// $vec1->add(new GlobalA); +// expr_type($vec1->get(0), "\GlobalA"); +// +// $filtered1 = $vec1->filter_is_instance(GlobalA::class); +// expr_type($filtered1[0], "\GlobalA"); +// +// $vec2 = new Vector(new GlobalA()); + $vec3 = new Vector(new GlobalD()); +// +// $combine = $vec2->combine_with($vec3); +// expr_type($combine, "\Vector|\Vector(\GlobalD|\GlobalA)"); + + $vec4 = new Vector(100); + $combine1 = $vec4->combine_with($vec3); +} + + diff --git a/src/test/fixtures/generics/tests.php b/src/test/fixtures/generics/tests.php index 7841b48..275cbd6 100644 --- a/src/test/fixtures/generics/tests.php +++ b/src/test/fixtures/generics/tests.php @@ -22,8 +22,8 @@ function nullable($arg) { function mirror($arg) { return $arg; } - -$a = mirror(tuple([new GlobalA()], new \Classes\A() ?? new \Classes\C)); -expr_type($a, "tuple(\GlobalA[],\Classes\C|\Classes\A)"); - +// +//$a = mirror(tuple([new GlobalA()], new \Classes\A() ?? new \Classes\C)); +//expr_type($a, "tuple(\GlobalA[],\Classes\C|\Classes\A)"); +// diff --git a/src/test/kotlin/com/vk/kphpstorm/testing/infrastructure/InspectionTestBase.kt b/src/test/kotlin/com/vk/kphpstorm/testing/infrastructure/InspectionTestBase.kt index b4f4aab..981f2d5 100644 --- a/src/test/kotlin/com/vk/kphpstorm/testing/infrastructure/InspectionTestBase.kt +++ b/src/test/kotlin/com/vk/kphpstorm/testing/infrastructure/InspectionTestBase.kt @@ -22,17 +22,19 @@ abstract class InspectionTestBase( * Run inspection on file.fixture.php and check that all and match * If file.qf.php exists, apply quickfixes and compare result to file.qf.php */ - protected fun runFixture(fixtureFile: String) { + protected fun runFixture(vararg fixtureFiles: String) { // Highlighting test KphpStormConfiguration.saveThatSetupForProjectDone(project) - myFixture.configureByFile(fixtureFile) + myFixture.configureByFiles(*fixtureFiles) myFixture.testHighlighting(true, false, true) // Quick-fix test - val qfFile = fixtureFile.replace(".fixture.php", ".qf.php") - if (File(myFixture.testDataPath + "/" + qfFile).exists()) { - myFixture.getAllQuickFixes().forEach { myFixture.launchAction(it) } - myFixture.checkResultByFile(qfFile) + fixtureFiles.forEach { fixtureFile -> + val qfFile = fixtureFile.replace(".fixture.php", ".qf.php") + if (File(myFixture.testDataPath + "/" + qfFile).exists()) { + myFixture.getAllQuickFixes().forEach { myFixture.launchAction(it) } + myFixture.checkResultByFile(qfFile) + } } } } diff --git a/src/test/kotlin/com/vk/kphpstorm/testing/infrastructure/TypeTestBase.kt b/src/test/kotlin/com/vk/kphpstorm/testing/infrastructure/TypeTestBase.kt index 06f932e..72102b3 100644 --- a/src/test/kotlin/com/vk/kphpstorm/testing/infrastructure/TypeTestBase.kt +++ b/src/test/kotlin/com/vk/kphpstorm/testing/infrastructure/TypeTestBase.kt @@ -32,9 +32,9 @@ abstract class TypeTestBase : BasePlatformTestCase() { return result } - protected fun runFixture(fixtureFile: String) { + protected fun runFixture(vararg fixtureFiles: String) { KphpStormConfiguration.saveThatSetupForProjectDone(project) - myFixture.configureByFile(fixtureFile) + myFixture.configureByFiles(*fixtureFiles) val exprTypeCalls = findExprTypeCalls() diff --git a/src/test/kotlin/com/vk/kphpstorm/testing/tests/GenericsInstantiationArgsMismatchInspectionsTest.kt b/src/test/kotlin/com/vk/kphpstorm/testing/tests/GenericsInstantiationArgsMismatchInspectionsTest.kt new file mode 100644 index 0000000..4b786fb --- /dev/null +++ b/src/test/kotlin/com/vk/kphpstorm/testing/tests/GenericsInstantiationArgsMismatchInspectionsTest.kt @@ -0,0 +1,13 @@ +package com.vk.kphpstorm.testing.tests + +import com.vk.kphpstorm.inspections.FunctionGenericInstantiationArgsCountMismatchInspection +import com.vk.kphpstorm.testing.infrastructure.InspectionTestBase + +class GenericsInstantiationArgsMismatchInspectionsTest : InspectionTestBase(FunctionGenericInstantiationArgsCountMismatchInspection()) { + fun testClasses() { + runFixture( + "generics/classes/instantiation_args_mismatch.inspection.fixture.php", + "generics/classes/Vector.fixture.php" + ) + } +} diff --git a/src/test/kotlin/com/vk/kphpstorm/testing/tests/GenericsNoEnoughInformationInspectionsTest.kt b/src/test/kotlin/com/vk/kphpstorm/testing/tests/GenericsNoEnoughInformationInspectionsTest.kt new file mode 100644 index 0000000..97f42bb --- /dev/null +++ b/src/test/kotlin/com/vk/kphpstorm/testing/tests/GenericsNoEnoughInformationInspectionsTest.kt @@ -0,0 +1,13 @@ +package com.vk.kphpstorm.testing.tests + +import com.vk.kphpstorm.inspections.FunctionGenericNoEnoughInformationInspection +import com.vk.kphpstorm.testing.infrastructure.InspectionTestBase + +class GenericsNoEnoughInformationInspectionsTest : InspectionTestBase(FunctionGenericNoEnoughInformationInspection()) { + fun testClasses() { + runFixture( + "generics/classes/no_enough_information.inspection.fixture.php", + "generics/classes/Vector.fixture.php" + ) + } +} diff --git a/src/test/kotlin/com/vk/kphpstorm/testing/tests/GenericsTypesTest.kt b/src/test/kotlin/com/vk/kphpstorm/testing/tests/GenericsTypesTest.kt index 1e0360f..6f26d20 100644 --- a/src/test/kotlin/com/vk/kphpstorm/testing/tests/GenericsTypesTest.kt +++ b/src/test/kotlin/com/vk/kphpstorm/testing/tests/GenericsTypesTest.kt @@ -26,4 +26,10 @@ class GenericsTypesTest : TypeTestBase() { fun testClassString() { runFixture("generics/class-string.fixture.php") } + + // Classes + + fun testSimpleClasses() { + runFixture("generics/classes/simple_classes.fixture.php", "generics/classes/Vector.fixture.php") + } }