Plugins: type system internals
Psalm's type system represents the types of variables within a program using different classes. Plugins both receive, and can update this type information.
Union types
All type information you are likely to use will be wrapped in a Union Type.
The Union
class constructor takes an array of Atomic
types, and can represent one or more of these types at a time. They correspond to a vertical bar in a doc comment.
new Union([new TNamedObject('Foo\\Bar\\SomeClass')]); // equivalent to Foo\Bar\SomeClass in docblock
new Union([new TString(), new TInt()]); // equivalent to string|int in docblock
Atomic types
Primitive types like floats, integers and strings, plus arrays, and classes. You can find all of these in src/Psalm/Types/Atomic
.
Note that all non-abstract classes in this folder are valid types. They are all prefixed by 'T'.
The classes are as follows:
Misc
TVoid
- denotes the void
type, normally just used to annotate a function/method that returns nothing
TNull
- denotes the null
type
TNever
- denotes the no-return
/never-return
type for functions that never return, either throwing an exception or terminating (like the builtin exit()
). Also used for union types that can have no possible types (impossible intersections for example). Empty arrays []
have the type array<never, never>
.
TMixed
- denotes the mixed
type, used when you don’t know the type of an expression.
TNonEmptyMixed
- as above, but not empty. Generated for $x
inside the if
statement if ($x) {...}
when $x
is mixed
outside.
TEmptyMixed
- as above, but empty. Generated for $x
inside the if
statement if (!$x) {...}
when $x
is mixed
outside.
TIterable
- denotes the iterable
type (which can also result from an is_iterable
check).
TResource
- denotes the resource
type (e.g. a file handle).
TClosedResource
- denotes the resource
type that has been closed (e.g. a file handle through fclose()
).
TAssertionFalsy
- Represents any value reduced to false when computed in boolean context. This is used for assertions
TConditional
- Internal representation of a conditional return type in phpdoc. For example ($param1 is int ? int : string)
TIntMask
- Represents the type that is the result of a bitmask combination of its parameters. int-mask<1, 2, 4>
corresponds to 1|2|3|4|5|6|7
TIntMaskOf
- as above, but used with a reference to constants in code int-mask-of<MyClass::CLASS_CONSTANT_*>
will corresponds to 1|2|3|4|5|6|7
if there are three constant 1, 2 and 4
TKeyOf
- Represents an offset of an array (e.g. key-of<MyClass::CLASS_CONSTANT>
).
TValueOf
- Represents a value of an array or enum (e.g. value-of<MyClass::CLASS_CONSTANT>
).
TTemplateIndexedAccess
- To be documented
TTemplateKeyOf
- Represents the type used when using TKeyOf when the type of the array is a template
TTemplateValueOf
- Represents the type used when using TValueOf when the type of the array or enum is a template
TPropertiesOf
- Represents properties and their types of a class as a keyed array (e.g. properties-of<MyClass>
)
TTemplatePropertiesOf
- Represents the type used when using TPropertiesOf when type of the class is a template
TTypeAlias
- To be documented
Scalar supertype
TScalar
- denotes the scalar
super type (which can also result from an is_scalar
check). This type encompasses float
, int
, bool
and string
.
TEmptyScalar
- denotes a scalar
type that is also empty.
TNonEmptyScalar
- denotes a scalar
type that is also non-empty.
Numeric supertype
TNumeric
- denotes the numeric
type (which can also result from an is_numeric
check).
TEmptyNumeric
- denotes the numeric
type that's also empty (which can also result from an is_numeric
and empty
check).
Scalar types
All scalar types have literal versions e.g. int
vs int(5)
.
Ints
TInt
- denotes the int
type, where the exact value is unknown.
TLiteralInt
- is used to represent an integer value where the exact numeric value is known.
TIntRange
- allows to describe an int with bounded values (ie. int<1, 5>
).
Floats
TFloat
- denotes the float
type, where the exact value is unknown.
TLiteralFloat
- is used to represent a floating point value where the exact numeric value is known.
Bools
TBool
, TFalse
, TTrue
TBool
- denotes the bool
type where the exact value is unknown.
TFalse
- denotes the false
value type
TTrue
- denotes the true
value type
/** @return string|false false when string is empty, first char of the parameter otherwise */
function firstChar(string $s) { return empty($s) ? false : $s[0]; }
Here, the function may never return true, but if you had to replace false with bool, Psalm would have to consider true as a possible return value. With narrower type it's able to report meaningless code like this (https://psalm.dev/r/037291351d):
$first = firstChar("sdf");
if (true === $first) {
echo "This is actually dead code";
}
Strings
TString
- denotes the string
type, where the exact value is unknown.
TNonEmptyString
- denotes a string, that is also non-empty
TNumericString
- denotes a string that's also a numeric value e.g. "5"
. It can result from is_string($s) && is_numeric($s)
.
TLiteralString
- is used to represent a string whose value is known.
TClassString
- denotes the class-string
type, used to describe a string representing a valid PHP class. The parent type from which the classes descend may or may not be specified in the constructor.
TLiteralClassString
- denotes a specific class string, generated by expressions like A::class
.
TTraitString
- denotes the trait-string
type, used to describe a string representing a valid PHP trait.
TDependentGetClass
- Represents a string whose value is a fully-qualified class found by get_class($var)
TDependentGetDebugType
- Represents a string whose value is that of a type found by get_debug_type($var)
TDependentGetType
- Represents a string whose value is that of a type found by gettype($var)
TCallableString
- denotes the callable-string
type, used to represent an unknown string that is also callable
.
TSqlSelectString
- this is a special type, specifically for consumption by plugins.
TLowercaseString
- denotes a string where every character is lowercased. (which can also result from a strtolower
call).
TNonEmptyLowercaseString
- denotes a non-empty-string where every character is lowercased. (which can also result from a strtolower
call).
TSingleLetter
- denotes a string that has a length of 1
Scalar class constants
TScalarClassConstant
- denotes a class constant whose value might not yet be known.
Array key supertype
TArrayKey
- denotes the array-key
type, used for something that could be the offset of an array
.
Arrays
TArray
- denotes a simple array of the form array<TKey, TValue>
. It expects an array with two elements, both union types.
TNonEmptyArray
- as above, but denotes an array known to be non-empty.
TKeyedArray
represents an 'object-like array' - an array with known keys.
$x = ["a" => 1, "b" => 2]; // is TKeyedArray, array{a: int, b: int}
$y = rand(0, 1) ? ["a" => null] : ["a" => 1, "b" => "b"]; // is TKeyedArray with optional keys/values, array{a: ?int, b?: string}
This type is also used to represent lists (instead of the now-deprecated TList
type).
Note that not all associative arrays are considered object-like. If the keys are not known, the array is treated as a mapping between two types.
$a = [];
foreach (range(1,1) as $_) $a[(string)rand(0,1)] = rand(0,1); // array<string,int>
TCallableKeyedArray
- denotes an object-like array that is also callable
.
TClassStringMap
- Represents an array where the type of each value is a function of its string key value
Callables & closures
TCallable
- denotes the callable
type. Can result from an is_callable
check.
TClosure
- denotes a Closure
type.
TCallable
and TClosure
can optionally be defined with parameters and return types, too
Object supertypes
TObject
- denotes the object
type
TObjectWithProperties
- an object with specified member variables e.g. object{foo:int, bar:string}
.
Object types
TNamedObject
- denotes an object type where the type of the object is known e.g. Exception
, Throwable
, Foo\Bar
TGenericObject
- denotes an object type that has generic parameters e.g. ArrayObject<string, Foo\Bar>
TCallableObject
- denotes an object that is also callable
(i.e. it has __invoke
defined).
TAnonymousClassInstance
- Denotes an anonymous class (i.e. new class{}
) with potential methods
Template
TTemplateParam
- denotes a template parameter that has been previously specified in a @template
tag.
TTemplateParamClass
- denotes a class-string
corresponding to a template parameter previously specified in a @template
tag.
Creating type object instances
There are two ways of creating the object instances which describe a given type. They can be created directly using new, or created declaratively from a doc string. Normally, you'd want to use the second option. However, understanding the structure of this data will help you understand types passed into a plugin.
Note that these classes do sometimes change, so Type::parseString
is always going to be the more robust option.
Creating type object instances directly
The following example constructs types representing a string, a floating-point number, and a class called 'Foo\Bar\SomeClass'.
new TLiteralString('A text string')
new TLiteralFloat(3.142)
new TNamedObject('Foo\Bar\SomeClass')
Types within Psalm are always wrapped in a union as a convenience feature. Almost anywhere you may expect a type, you can get a union as well (property types, return types, argument types, etc). So wrapping a single atomic type (like TInt) in a union container allows to uniformly handle that type elsewhere, without repetitive checks like this:
if ($type instanceof Union)
foreach ($types->getTypes() as $atomic)
handleAtomic($atomic);
else handleAtomic($type);
// with union container it becomes
foreach ($types->getTypes() as $atomic)
handleAtomic($atomic);
Also, union trees are always shallow, because Psalm will flatten union of unions into a single-level union ((A|B)|(C|D) => A|B|C|D)
.
More complex types can be constructed as follows. The following represents an associative array with 3 keys. Psalm calls these 'object-like arrays', and represents them with the 'TKeyedArray' class.
new Union([
new TKeyedArray([
'key_1' => new Union([new TString()]),
'key_2' => new Union([new TInt()]),
'key_3' => new Union([new TBool()])])]);
The Type object includes some static helper methods, which automatically wrap the type in a Union. Thus this can be written more tersely:
new Union([
new Type\Atomic\TKeyedArray([
'first' => Type::getInt(),
'second' => Type::getString()])]);
You can also use Type::getInt(5)
to generate a union type corresponding to the literal int value 5.
Creating type object instances from doc string types
Another way of creating these instances is to use the class Psalm\Type
which includes a static method parseString
. You may pass any doc string type description to this, and it will return the corresponding object representation.
\Psalm\Type::parseString('int|null');
You can find how Psalm would represent a given type as objects, by specifying the type as an input to this function, and calling var_dump
on the result.