Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: added support for generics #5

Closed
wants to merge 61 commits into from
Closed
Changes from 1 commit
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
c01af26
initial implementation
i582 Mar 14, 2022
d373f2f
fixed situation when it was necessary to resolve a function during ty…
i582 Mar 14, 2022
1d35cff
added initial support for class generics
i582 Mar 15, 2022
df53c16
fixed type inference for shape with unresolved types
i582 Mar 15, 2022
19943d0
fixed type inference for non-template classes
i582 Mar 15, 2022
015d276
fixed NullPointerException when generic comment contains syntacticall…
i582 Mar 15, 2022
ebfa25c
added class TypeTestBase for type testing and added several tests for…
i582 Mar 15, 2022
3c0674a
fixed class-string comparison with other types
i582 Mar 15, 2022
6b749b7
added more tests
i582 Mar 15, 2022
f8220bc
wip
i582 Mar 27, 2022
26102d0
fixed tests
i582 Mar 27, 2022
87ce1f2
extended generic inspections for method and constructor calls and add…
i582 Mar 27, 2022
62b1369
added initial support for generic types bounds
i582 Mar 28, 2022
afa6442
removed old generic type provider
i582 Mar 28, 2022
9c274bc
added Map and Pair generic classes
i582 Mar 28, 2022
a248737
update
i582 Apr 22, 2022
cba4ab1
implementation of generics instantiation is redesigned through the in…
i582 May 23, 2022
48f300a
fixed tests
i582 May 23, 2022
afc47aa
update (wip)
i582 May 26, 2022
6887870
added initial support for default generic types
i582 May 26, 2022
c2b5be2
added AddExplicitInstantiationComment quick fix
i582 May 27, 2022
dec32ed
added support for static methods
i582 May 27, 2022
96c502f
reworked complex type resolution system, now all tests are green
i582 May 28, 2022
829cd14
added optimisation for non-generic symbols
i582 May 28, 2022
18b65d5
added more tests
i582 May 28, 2022
7c12652
added checks for generic type duplication with class types and added …
i582 May 28, 2022
b746f71
fixed bug with class-string
i582 May 28, 2022
8c9feab
added type inference for parameters when they have a generic type tha…
i582 May 28, 2022
c1140f7
wip
i582 May 29, 2022
707a475
update
i582 May 29, 2022
1dc21d7
added support for generic name in generic isntantiation list
i582 May 29, 2022
43a2bb5
refactor
i582 May 30, 2022
f78a639
refactor
i582 May 30, 2022
969e0f7
small refactor
i582 May 30, 2022
8511964
dropped kphp-template-class support
i582 May 30, 2022
b806f49
Merge branch 'master' into pmakhnev/generic
i582 May 30, 2022
2f846c8
added fields tests
i582 May 30, 2022
3d83f60
fixed @var tag
i582 May 30, 2022
96af2be
added tests for callback
i582 May 30, 2022
00d923a
now generic names in static fields are treated as a usual class inste…
i582 May 30, 2022
57f898a
small fix
i582 Jun 6, 2022
897f64d
update
i582 Jun 7, 2022
02dc6a7
update
i582 Jun 7, 2022
6fd074a
fixed stubs
i582 Jun 7, 2022
80500cf
small fixes
i582 Jun 7, 2022
77a4003
added type inference for a class methods parameters
i582 Jun 7, 2022
810956f
fixed type inference for tuples and shapes for simple cases
i582 Jun 8, 2022
3e2195e
fixed check for union extends class
i582 Jun 8, 2022
4e7bac8
added type inference from extends/default types for fields
i582 Jun 8, 2022
37bc52c
added check for generic instantiation count in PHPDoc
i582 Jun 9, 2022
2820bba
added highlight for @kphp-generic parts
i582 Jun 9, 2022
5f203d3
small rename
i582 Jun 9, 2022
7b6f053
added initial support for @kphp-inherit tag
i582 Jun 9, 2022
363a80e
small fixes
i582 Jun 9, 2022
25a39b8
added more checks and tests
i582 Jun 10, 2022
f7b998d
added initial support for class inherit
i582 Jun 10, 2022
cbc91b1
refactored
i582 Jun 12, 2022
37d5b3a
added generics T names settings for color scheme
i582 Jun 12, 2022
d967da2
fixed several KPHP tests
i582 Jun 12, 2022
80330d1
improved test bases
i582 Jun 12, 2022
9da0627
added support for depth inheritance
i582 Jun 13, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
update
  • Loading branch information
i582 committed Apr 22, 2022
commit a248737ef82ac5094fe921da6e61d4a81113e579
12 changes: 6 additions & 6 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
plugins {
id 'java'
id 'idea'
id 'org.jetbrains.intellij' version '1.3.0' // https://github.com/JetBrains/gradle-intellij-plugin
id 'org.jetbrains.kotlin.jvm' version '1.6.0' // https://plugins.gradle.org/plugin/org.jetbrains.kotlin.jvm
id 'org.jetbrains.intellij' version '1.5.3' // https://github.com/JetBrains/gradle-intellij-plugin
id 'org.jetbrains.kotlin.jvm' version '1.6.10' // https://plugins.gradle.org/plugin/org.jetbrains.kotlin.jvm
}

group 'com.vk'
// important! update changeNotes (below) on version bump
version '1.2.2'
version '1.2.3'

repositories {
mavenCentral()
@@ -40,9 +40,9 @@ intellij {
type = 'IU'
// for release versions: https://www.jetbrains.com/intellij-repository/releases (com.jetbrains.intellij.idea)
// for EAPs: https://www.jetbrains.com/intellij-repository/snapshots
version = '2021.3.1'
version = '2022.1'
plugins = [
'com.jetbrains.php:213.5744.223', // https://plugins.jetbrains.com/plugin/6610-php/versions
'com.jetbrains.php:221.5080.224', // https://plugins.jetbrains.com/plugin/6610-php/versions
]
}
runIde {
@@ -54,7 +54,7 @@ patchPluginXml {
// <change-notes> is only for LATEST version and should be updated on every version bump
changeNotes = """
<ul>
<li>adapt code for 2021.3</li>
<li>adapt code for 2022.1</li>
</ul>
"""
}
52 changes: 46 additions & 6 deletions src/main/kotlin/com/vk/kphpstorm/KphpStormASTFactory.kt
Original file line number Diff line number Diff line change
@@ -1,17 +1,57 @@
package com.vk.kphpstorm

import com.intellij.lang.DefaultASTFactoryImpl
import com.intellij.psi.impl.source.tree.LeafElement
import com.intellij.psi.impl.source.tree.PsiCommentImpl
import com.intellij.lang.ASTFactory
import com.intellij.lang.LanguageParserDefinitions
import com.intellij.psi.impl.source.tree.*
import com.intellij.psi.tree.IElementType
import com.vk.kphpstorm.generics.psi.GenericInstantiationPsiCommentImpl
import com.intellij.psi.tree.IFileElementType
import com.intellij.psi.tree.ILazyParseableElementType
import com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocTokenImpl
import com.jetbrains.php.lang.lexer.PhpTokenTypes

class KphpStormASTFactory : DefaultASTFactoryImpl() {
class KphpStormASTFactory : MyDefaultASTFactoryImpl() {
override fun createComment(type: IElementType, text: CharSequence): LeafElement {
if (!text.startsWith("/*<") && !text.endsWith(">*/")) {
return PsiCommentImpl(type, text)
}

return GenericInstantiationPsiCommentImpl(type, text)
return PhpDocTokenImpl(type, text)
}
}

abstract class MyDefaultASTFactoryImpl : ASTFactory() {
override fun createLazy(type: ILazyParseableElementType, text: CharSequence?): LazyParseableElement {
if (type == PhpTokenTypes.C_STYLE_COMMENT) {
return LazyParseableElement(type, text)
}

return if (type is IFileElementType) {
FileElement(type, text)
} else LazyParseableElement(type, text)
}

override fun createComposite(type: IElementType): CompositeElement {
if (type == PhpTokenTypes.C_STYLE_COMMENT) {
return CompositeElement(type)
}

return if (type is IFileElementType) {
FileElement(type, null)
} else CompositeElement(type)
}

override fun createLeaf(type: IElementType, text: CharSequence): LeafElement {
val lang = type.language
val parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(lang)
if (parserDefinition != null) {
if (parserDefinition.commentTokens.contains(type)) {
return createComment(type, text)
}
}
return LeafPsiElement(type, text)
}

open fun createComment(type: IElementType, text: CharSequence): LeafElement {
return PsiCommentImpl(type, text)
}
}
5 changes: 5 additions & 0 deletions src/main/kotlin/com/vk/kphpstorm/KphpStormParserDefinition.kt
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ import com.intellij.lang.ASTNode
import com.intellij.psi.PsiElement
import com.jetbrains.php.lang.documentation.phpdoc.parser.tags.PhpDocTagParserRegistry
import com.jetbrains.php.lang.documentation.phpdoc.psi.impl.PhpDocTypeImpl
import com.jetbrains.php.lang.lexer.PhpTokenTypes
import com.jetbrains.php.lang.parser.PhpParserDefinition
import com.jetbrains.php.lang.parser.PhpPsiElementCreator
import com.vk.kphpstorm.exphptype.psi.*
@@ -52,6 +53,10 @@ class KphpStormParserDefinition() : PhpParserDefinition() {
ExPhpTypeClassStringPsiImpl.elementType -> ExPhpTypeClassStringPsiImpl(node)
ExPhpTypeForcingPsiImpl.elementType -> ExPhpTypeForcingPsiImpl(node)

PhpTokenTypes.C_STYLE_COMMENT -> {
PhpDocTypeImpl(node)
}

else -> PhpPsiElementCreator.create(node)
}
}
12 changes: 12 additions & 0 deletions src/main/kotlin/com/vk/kphpstorm/generics/GenericFunctionCall.kt
Original file line number Diff line number Diff line change
@@ -115,6 +115,17 @@ class GenericsReifier(val project: Project) {
}
}

if (paramExType is ExPhpTypeCallable) {
if (argExType is ExPhpTypeCallable) {
if (argExType.returnType != null && paramExType.returnType != null) {
reifyArgumentGenericsT(argExType.returnType, paramExType.returnType)
}
for (i in 0 until min(argExType.argTypes.size, paramExType.argTypes.size)) {
reifyArgumentGenericsT(argExType.argTypes[i], paramExType.argTypes[i])
}
}
}

if (paramExType is ExPhpTypeClassString) {
val isPipeWithClassString = argExType is ExPhpTypePipe &&
argExType.items.any { it is ExPhpTypeClassString }
@@ -614,6 +625,7 @@ abstract class GenericCall(val project: Project) {
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() {
Original file line number Diff line number Diff line change
@@ -34,7 +34,7 @@ class GenericInstantiationArgsCountMismatchInspection : PhpInspection() {
private fun checkGenericCall(call: GenericCall) {
if (!call.isResolved()) return

val countGenericNames = call.genericNames().size
val countGenericNames = call.genericNames().size - call.implicitClassSpecializationNameMap.size
val countExplicitSpecs = call.explicitSpecs.size
val explicitSpecsPsi = call.explicitSpecsPsi

4 changes: 4 additions & 0 deletions src/test/fixtures/generics/class_resolving.fixture.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
<?php

$a = mirror/*<GlobalA>*/(new GlobalA());


/** @noinspection FunctionUnnecessaryExplicitGenericInstantiationListInspection */
/** @noinspection PhpUndefinedFunctionInspection */
/** @noinspection PhpExpressionResultUnusedInspection */
123 changes: 123 additions & 0 deletions src/test/fixtures/generics/classes/Vector.fixture.php
Original file line number Diff line number Diff line change
@@ -45,4 +45,127 @@ function filter_is_instance($class) {
function combine_with($other) {
return new Vector/*<T|T1>*/(array_merge($this->data, $other->data));
}


/**
* @kphp-generic T1
* @param callable(T):T1 $fn
* @return Vector<T1>
*/
function map($fn) {
/** @var T1 $new_data */
$new_data = array_map($fn, $this->data);
return new Vector(...$new_data);
}
}

/**
* @kphp-generic T
* @param T ...$els
* @return Vector<T>
*/
function listOf(...$els) {
return new Vector(...$els);
}
//

//listOf(1, 2, 3)->get(10);
//listOf("1", "2", "3")->get(10);

$vec = listOf(1, 2)->map(function ($el): string {
return "$el";
});


/** @noinspection GenericUnnecessaryExplicitInstantiationListInspection */
/** @noinspection PhpUndefinedFunctionInspection */
/** @noinspection PhpExpressionResultUnusedInspection */


//
//$a = mirror(tuple([new GlobalA()], new \Classes\A() ?? new \Classes\C));
//expr_type($a, "tuple(\GlobalA[],\Classes\C|\Classes\A)");
//

//use Classes\Base;
//
//class SerializableItem implements Serializable {
// public function serialize() {}
//
// /**
// * @param mixed $data
// */
// public function unserialize($data) {}
//}
//
///**
// * Not mutable generic collection.
// *
// * @kphp-generic T: Serializable
// */
//class VectorList1 {
// /** @var T[] */
// protected $data = [];
//
// /**
// * @param T ...$els
// */
// public function __construct(...$els) {
// $this->data = $els;
// }
//
// /**
// * @return T
// */
// public function get(int $index) {
// return $this->data[$index];
// }
//}
//
///**
// * @kphp-generic T: Serializable
// * @param T $a
// */
//function acceptSerializable($a) {
// // Сделать тут тип $a == Serializable, чтобы было базовое автодополнение
//}
//
//////$vec = new VectorList1/*<string>*/(1, 2, 3);
//////$vec = new VectorList1/*<GlobalA>*/(1, 2, 3);
////$vec = new VectorList1/*<Base>*/(1, 2, 3);
////$vec = new VectorList1(new Base);
////$vec = new VectorList1/*<SerializableItem>*/(new SerializableItem());
////
////acceptSerializable(new Base);
////acceptSerializable/*<Base>*/(new Base);
////acceptSerializable(new SerializableItem);
//
//class BaseClass {}
//
//class ChildClass extends BaseClass {
// /**
// * @kphp-generic T: self
// * @param T $a
// */
// function f($a) {
//
// }
//}
//
//$a = new ChildClass();
////$a->f(new BaseClass());
//$a->f(new SerializableItem());
//
///**
// * @kphp-generic T1, T2: T1
// * @param T1 $a
// * @param T2 $b
// */
//function g($a, $b) {
//
//}
//
//g/*<SerializableItem, ChildClass>*/();



59 changes: 3 additions & 56 deletions src/test/fixtures/generics/tests.php
Original file line number Diff line number Diff line change
@@ -1,63 +1,10 @@
<?php
/** @noinspection GenericUnnecessaryExplicitInstantiationListInspection */
/** @noinspection PhpUndefinedFunctionInspection */
/** @noinspection PhpExpressionResultUnusedInspection */


//
//$a = mirror(tuple([new GlobalA()], new \Classes\A() ?? new \Classes\C));
//expr_type($a, "tuple(\GlobalA[],\Classes\C|\Classes\A)");
//

use Classes\Base;

class SerializableItem implements Serializable {
public function serialize() {}

/**
* @param mixed $data
*/
public function unserialize($data) {}
}

/**
* Not mutable generic collection.
*
* @kphp-generic T: Serializable
* @param $a GlobalA
*/
class VectorList1 {
/** @var T[] */
protected $data = [];
function f() {

/**
* @param T ...$els
*/
public function __construct(...$els) {
$this->data = $els;
}

/**
* @return T
*/
public function get(int $index) {
return $this->data[$index];
}
}

/**
* @kphp-generic T: Serializable
* @param T $a
*/
function acceptSerializable($a) {
// Сделать тут тип $a == Serializable, чтобы было базовое автодополнение
}
$a = mirror/*<GlobalA>*/(new GlobalA());

//$vec = new VectorList1/*<string>*/(1, 2, 3);
//$vec = new VectorList1/*<GlobalA>*/(1, 2, 3);
$vec = new VectorList1/*<Base>*/(1, 2, 3);
$vec = new VectorList1(new Base);
$vec = new VectorList1/*<SerializableItem>*/(new SerializableItem());

acceptSerializable(new Base);
acceptSerializable/*<Base>*/(new Base);
acceptSerializable(new SerializableItem);