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 Psalm's 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. Most (with the exception of Fn, ObjectLike, GetClassT and GetTypeT) are prefixed '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()).

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.

TEmpty - denotes the empty type, used to describe a type corresponding to no value whatsoever. Empty arrays [] have the type array<empty, empty>.

TIterable - denotes the iterable type (which can also result from an is_iterable check).

TResource - denotes the resource type (e.g. a file handle).

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.

Numeric supertype

TNumeric - denotes the numeric type (which can also result from an is_numeric 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.

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.

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.

TCallableString - denotes the callable-string type, used to represent an unknown string that is also callable.

THtmlEscapedString, TSqlSelectString - these are special types, specifically for consumption by plugins.

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.

ObjectLike represents an 'object-like array' - an array with known keys.

$x = ["a" => 1, "b" => 2]; // is ObjectLike, array{a: int, b: int}
$y = rand(0, 1) ? ["a" => null] : ["a" => 1, "b" => "b"]; // is ObjectLike with optional keys/values, array{a: ?int, b?: string}

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>

TCallableArray - denotes an array that is also callable.

TCallableObjectLikeArray - denotes an object-like array that is also callable.

Callables & closures

TCallable - denotes the callable type. Can result from an is_callable check. Fn - denotes a Closure type.

TCallable and Fn 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).

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. Howeaver, 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 a 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 assosiative array with 3 keys. Psalm calls these 'object-like arrays', and represents them with the 'ObjectLike' class.

        new Union([
            new ObjectLike([
                '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\ObjectLike([
        '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.