PHP Type Providers
Type inference in PhpStorm is built on top of type providers, each of which is responsible for inferring the types of specific PSI elements. For example, com.jetbrains.php.lang.psi.resolve.types.PhpArrayAccessTP
is responsible for inferring the types of expressions like $arr[10]
. There are dozens of such providers, and they all work one after another to provide type information when needed.
All providers inherit from com.jetbrains.php.lang.psi.resolve.types.PhpTypeProvider4
, which is registered in the com.jetbrains.php.typeProvider4
extension point.
Types in PhpStorm
The first phase of type inference takes place at the indexing stage. At this phase, PhpStorm calls PhpTypeProvider4.getType()
on each type provider. PhpStorm only has access to local information from the current file and can't use information from other files as well as indexes because it does't yet build them. Sometimes, it can deduce the exact type from this information, but in other cases this is impossible because PhpStorm requires information from other files.
Because of this, there are two kinds of types in PhpStorm:
Complete types
Incomplete types
Complete types
Complete types are types that are known exactly based on only the local information of the current file.
The type of the expression that's assigned to the $a
variable is Complete type int
, since PhpStorm can definitely infer that a numeric literal is of type int
.
Here, since the $a
parameter has a string
type hint, PhpStorm can infer the Complete type string
.
Incomplete types
Incomplete types are types that need additional information from other project files besides the containing file.
Suppose we have two files:
foo.php:
main.php:
In the main.php file, we call the foo()
, which is defined in another foo.php file. Because of this, PhpStorm won't be able to infer the type of the $a
variable during the indexing stage, since it depends on the definition of the foo()
from another file.
For such cases, PhpStorm will create an Incomplete type in which writes all the necessary information to resolve the type when it finishes indexing. In this case, it's the name of the function being called, so PhpStorm will create an Incomplete type #F\foo
.
Incomplete Types Structure
At the beginning there is a #
character, which is a marker that the type is Incomplete.
It's followed by the type provider's unique key. PhpStorm uses this key to decide which type provider to pass the Incomplete type to resolve.
The rest of the line is the encoded information. In the example above, this is the fully qualified name of the function.
PhpStorm will further pass the created Incomplete type after it finishes the indexing to resolve it into a Complete type. It will choose instance of PhpTypeProvider4
that have PhpTypeProvider4.getKey()
equal to the character after #
. In other words, same provider is supposed to provide incomplete type starting with #
+ result of call PhpTypeProvider4.getKey()
, this will result in that this type will be passes to the same PhpTypeProvider4.complete()
instance during resolve.
During indexing, PhpStorm collects information about all types of elements in this way and stores them in the index. When there is a need for the type of some expression, PhpStorm passes the Incomplete type obtained at the indexing stage for resolving.
Incomplete Types Resolving
The second phase of type inference is the global Incomplete type resolution. At this phase, PhpStorm calls PhpTypeProvider4.complete()
of each type provider. All Incomplete types are passed to the providers that created them. At this point, PhpStorm can access any information from other files to resolve the Incomplete type.
Union Types
Since PHP is a dynamically typed language, at the type inference stage PhpStorm can get a situation where the type can be either one or the other.
For example:
In (1), the variable $a
will be of type int|string
because PhpStorm can't deduce exactly which branch the execution will take.
PhpStorm stores this types as separate strings inside the PhpType
class. Each of the types can be either Complete or Incomplete. In the Incomplete type resolving process, PhpStorm will resolve each union type individually.
Since some providers may return types for the same PSI element, union types may appear for some elements.
PhpType
PhpStorm uses the com.jetbrains.php.lang.psi.resolve.types.PhpType
class to work with types.
To add types to it, use add()
, which can take either another PhpType
or a string
.
To check that a type is Complete, use isComplete()
.
To resolve the Incomplete type, use global()
. This method shouldn't be used during indexing, namely inside PhpTypeProvider4.getType()
.
How to get PhpType from PSI?
In PhpStorm, PSI elements with types implement the com.jetbrains.php.lang.psi.elements.PhpTypedElement
interface. To get the type of element, use the getType()
.
PhpTypeProvider4 Implementation
PhpTypeProvider4
interface:
To implement PhpTypeProvider4
, you need to override 4 methods:
getKey()
returns a character that will be unique for this type provider. This can be any character, as long as it's unique, for example, PhpStorm uses hieroglyphs. See alsocom.jetbrains.php.lang.psi.resolve.types.PhpCharBasedTypeKey
.getType()
returns the type of the expression for the given element. It's called at the indexing stage, and therefore its implementation can't access any information from the index and must rely only on local information. If you need some information, then pack the required data into a string and return an Incomplete type based on this string.complete()
resolves an Incomplete type into a Complete type. All strings of Incomplete types are sequentially passed to this method, it should return a Complete type for them.getBySignature()
provides additional elements or references.
You can also override the emptyResultIsComplete()
, which indicates whether the null
returned from the complete()
is a valid result, which means that PhpStorm won't add the mixed
to the resulting type.
Example Implementation
The goal of this example is to provide types for field references assigned in setUp
method if containing class is PHPUnit one.
Define a PhpUnitFiledInitializedInSetUpMethodsTP
Register the PhpUnitFiledInitializedInSetUpMethodsTP
The PhpUnitFiledInitializedInSetUpMethodsTP
implementation is registered with the IntelliJ Platform in the plugin configuration file using the com.jetbrains.php.typeProvider4
extension point.