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. 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 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
Here, since the
$a parameter has a
string type hint, PhpStorm can infer the Complete type
Incomplete types are types that need additional information from other project files besides the containing file.
Suppose we have two files:
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
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.
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.
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.
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
To check that a type is Complete, use
To resolve the Incomplete type, use
global(). This method shouldn't be used during indexing, namely inside
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
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 also
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.
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
PhpUnitFiledInitializedInSetUpMethodsTP implementation is registered with the IntelliJ Platform in the plugin configuration file using the
com.intellij.php.typeProvider4 extension point.