PHP TypeLang Help

Syntax Comparison

The language is defined by a syntax that is based on the grammar of popular static code analysis tools: PHPStan and Psalm.

General table across all type parsing capabilities

  TypeLang

  Psalm

  PHPStan

  Phan

  phpDocumentor

Methodology

Each verdict below is the answer of the tool's own parser, asked directly, so the table says what the tools do rather than what they are said to do.

Tool

Version

A type is supported when

 TypeLang

type-lang/parser 2.x

TypeParser::parse() returns a node

 Psalm

vimeo/psalm 6.5

Psalm\Type::parseString() returns a type carrying what was written

 PHPStan

phpstan/phpdoc-parser 2.3

TypeParser::parse() reads the source whole, leaving no trailing input

 Phan

phan/phan 5.5

UnionType::fromStringInContext() returns something other than a class name made of the whole source

 phpDocumentor

phpdocumentor/type-resolver 2.0

TypeResolver::resolve() returns a type, the source read whole

Basic Types

Below is a list of simple, logical and other common types.

  TypeLang

  Psalm

  PHPStan

  Phan

  phpDocumentor

Class or type name (including FQN)

Fully\Qualified\Name

Logical union types

T | U | V

Logical intersection types

T & U & V

Logical nullable types

?T

Grouping parentheses

(T | U)[]

Legacy list types syntax

User[]

Type offsets (offset access)

ExampleShape['key'] ClassName::CONSTANT[0]

Template arguments (Generics)

ExampleCollection<array-key, User>

Trailing comma not supported 1

1. Psalm does NOT support arguments ending with a comma
ExampleCollection<array-key, User,>

Open in psalm.dev

Template argument hints

ExampleCollection<in array-key, out User>

Not Supported

Other keywords 1

Other keywords 1

1. PHPStan spells call-site variance out in words

The in and out hints are not read, but the same thing is said with contravariant and covariant.

Collection<covariant Animal>

Open in phpstan.org

Wildcard template arguments

ExampleCollection<*>

Read as a bivariant mixed 1

Read as a bivariant mixed 1

1. PHPStan keeps no wildcard of its own

An asterisk is read as a mixed carrying a bivariant hint, so the argument parses, but nothing of the wildcard itself is left in the tree.

// Written ExampleCollection<*>
// Read ExampleCollection<bivariant mixed>

The $this type

$this

Class constant types

ClassName::CONSTANT_NAME

Prefixed class constant mask types

ClassName::CONSTANT_*

Non-prefixed class constant mask types

ClassName::*

Suffixed class constant mask types

ClassName::*_SUFFIX

Class constant mask types carrying both ends

ClassName::PREFIX_*_SUFFIX

Global constant mask types

JSON_*

Not Supported

Not Supported

Suffixed global constant mask types

*_SUFFIX

Namespaced constant mask types

Path\To\JSON_* Path\To\*

Comments inside a type

int /* comment */ | string

Conditional Types

Below is a list of conditional types.

  TypeLang

  Psalm

  PHPStan

  Phan

  phpDocumentor

Conditional positive equality types

T is A ? B : C

Brackets required 1

Brackets required 1

Brackets required 1

1. Psalm and PHPStan require the condition to be parenthesised

A condition is written inside brackets and is only read there, so the bare form below is read as the type T alone.

(T is A ? B : C)

Conditional negative equality types

T is not A ? B : C

Brackets required 1

Brackets required 1

Brackets required 1

1. Psalm and PHPStan require the condition to be parenthesised

A condition is written inside brackets and is only read there, so the bare form below is read as the type T alone.

(T is A ? B : C)

Conditional referenced types

$var is A ? B : C $var is not A ? B : C

Brackets required 1

Brackets required 1

Brackets required 1

1. Psalm and PHPStan require the condition to be parenthesised

A condition is written inside brackets and is only read there, so the bare form below is read as the type T alone.

(T is A ? B : C)

Conditional referenced types in Yoda-style

A is $var ? B : C A is not $var ? B : C

Not Supported

Not Supported

Functions in conditional types

foo() is A ? B : C foo() is not A ? B : C

List of supported functions 1

No call syntax 2

No call syntax 2

1. Psalm supports the following functions
  • define()

  • array_map()

  • array_filter()

  • func_get_arg()

  • func_get_args()

  • func_num_args()

  • is_a()

  • is_subclass_of()

  • class_alias()

2. The PHPStan grammar carries no call of any kind

A parenthesis behind a name ends the type, whatever the name is, so the functions PHPStan reads in a condition are read by rules of its own rather than by the type language.

(func_num_args() is 1 ? A : B) // Unexpected token "(", expected ')' at offset 14

Functions in conditional types in Yoda-style

A is foo() ? B : C A is not foo() ? B : C

Not Supported

Not Supported

Literal Types

Below is a list of literal types/lexemes.

  TypeLang

  Psalm

  PHPStan

  Phan

  phpDocumentor

Boolean true and false literals

true false

The null literals

null

Single-quoted string literals

'single-quoted string'

Double-quoted string literals

"double-quoted string"

Escape sequences in a double-quoted string literals

"string with \n new line \n delimiters"

Not Supported

Hexadecimal sequences in a double-quoted string literals

"\xDE\xAD\xBE\xEF"

Not Supported

Unicode sequences in a double-quoted string literals

"This is smile \u{1F60A}"

Not Supported

Integer literals

42

BigInteger (PHP_INT_MAX + 1 or PHP_INT_MIN - 1) literals

9999999999999999999

Kept in full 1

Works with restrictions 2

Kept in full 3

Works with restrictions 4

Works with restrictions 5

1. TypeLang keeps the number whole, however large it is

The $decimal carries the value written out in base 10, so a number too large for the platform's int is still readable in full. The $value beside it is the native one, and that is the only one a platform limit applies to.

// Input 9999999999999999999
// Stored TypeLang\Type\Literal\IntLiteralNode { +offset: 0 +value: 9223372036854775807 +raw: "9999999999999999999" +decimal: "9999999999999999999" }
2. Psalm limits value to min/max int
// Input 9999999999999999999
// Stored 9223372036854775807

Open in psalm.dev

3. PHPStan reads the number whole, and loses it afterwards

The literal is kept as it was written, so nothing is lost while the type is read. The number does not survive the analysis that follows, though: what comes out of it is the nearest int the platform carries.

// Input 9999999999999999999
// Stored PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode { value: "9999999999999999999" }
// Analysed \PHPStan\dumpType($value); // Dumped type: 9223372036854775807

Open in phpstan.org

4. Phan turns the number into a float
// Input 9999999999999999999
// Stored 1.0E+19
5. phpDocumentor limits value to min/max int
// Input 9999999999999999999
// Stored 9223372036854775807

Integer literals in binary format

0b10101010

Not Supported

Value read as 0 1

1. phpDocumentor reads the number as 0

The number parses, but the value behind it is built with a plain cast, so nothing of the base survives.

// Written 0b10101010
// Read 0

Integer literals in octal format

0o42

Not Supported

Value read as 0 1

1. phpDocumentor reads the number as 0

The number parses, but the value behind it is built with a plain cast, so nothing of the base survives.

// Written 0o42
// Read 0

Integer literals in legacy octal format

042

Not Supported

Value read as decimal 1

1. phpDocumentor reads the number as 42

The number parses, but the value behind it is built with a plain cast, so nothing of the base survives.

// Written 042
// Read 42

Integer literals in hexadecimal format

0xDEAD_BEEF

Not Supported

Value read as 0 1

1. phpDocumentor reads the number as 0

The number parses, but the value behind it is built with a plain cast, so nothing of the base survives.

// Written 0xDEAD_BEEF
// Read 0

Underscore (_) separators in integer literals

42_04

Explicitly signed number literals

+42

Float literals

0.42

Float literals without leading zero

.42

Not Supported

Float literals without trailing zero

42.

Not Supported

Float literals in scientific notation

2e2

Not Supported

Float literals in scientific notation carrying a signed exponent

-1.5e+3

Not Supported

Hexadecimal integer literals carrying an e

0x42e2

Not Supported

Value read as 0 1

1. phpDocumentor reads the number as 0

The number parses, but the value behind it is built with a plain cast, so nothing of the base survives.

// Written 0x42e2
// Read 0

Shape Types

Below is a list of grammar of shaped types.

  TypeLang

  Psalm

  PHPStan

  Phan

  phpDocumentor

Explicit shape types

array { key: ValueType }

Trailing comma in explicit shape types

array { key: ValueType, }

Implicit shape types

array { ValueType }

Arrays and lists only 1

Arrays and lists only 1

1. PHPStan reads a key-less field in an array or a list alone
// OK array { ValueType } list { ValueType }
// Syntax Error object { ValueType }

Trailing comma in implicit shape types

array { ValueType, }

Not Supported

Optional keys in explicit shape types

array { key?: ValueType }

Empty (closed) shape types

array {}

Unsealed shape types

array { ... }

Read as an empty array 1

1. Psalm loses the unsealed part where no field stands in front of it

A shape carrying fields keeps what follows them, but one made of the ellipsis alone comes back sealed and empty.

// Written array { ... }
// Read array<never, never>

Explicit unsealed shape types

array { key: ValueType, ... }

Implicit unsealed shape types

array { ValueType, ... }

Typed unsealed shape types

array { ...<array-key, ValueType> }

Class constant shape keys

array { Path\To\ClassName::CONSTANT_NAME: string, }

Constant mask shape keys

array { Path\To\ClassName::PREFIX_*: string, JSON_*: string, }

String literal shape keys

array { 'some key': ValueType, }

Numeric shape keys

array { 0: ValueType, }

Shapes of an arbitrary type name

Custom\ObjectType { key: ValueType }

Fixed set of names 1

Fixed set of names 2

Fixed set of names 2

1. Psalm names the types a shape may be written of

A shape follows an array, a list, an object or a callable-array, and no other name.

Open in psalm.dev

2. PHPStan names the types a shape may be written of

A shape follows an array, a list or an object, and no other name.

Callable Types

Below is a list of grammar of callable (function) types.

  TypeLang

  Psalm

  PHPStan

  Phan

  phpDocumentor

Non-typed callable types

callable()

Not Supported

Typed callable types

callable(): Type

Callable with typed parameters

callable(Type): T

Trailing comma in the parameter list

callable(Type,): T

Callable with optional parameters

callable(Type=): T

Callable with named parameters

callable(Type $name): T

Callable with optional named parameters

callable(Type $name=): T

Not Supported

Callable with output parameters

callable(T&): T

Works with restrictions 1

1. Psalm reads an output parameter as an intersection
// OK callable(T&): U
// Bug: Intersection types must be all objects, // Psalm\Type\Atomic\TInt provided in docblock callable(int&): U

Open in psalm.dev

Callable with output optional parameters

callable(T&=): T

Works with restrictions 1

1. Psalm reads an output parameter as an intersection
// OK callable(T&): U
// Bug: Intersection types must be all objects, // Psalm\Type\Atomic\TInt provided in docblock callable(int&): U

Open in psalm.dev

Callable with output optional named parameters

callable(T &$name=): T

Not Supported

Callable with suffixed variadic parameters

callable(Type...): T

Callable with named variadic parameters

callable(Type ...$name): T

Callable with output named variadic parameters

callable(Type &...$name): T

Callable with prefixed variadic parameters

callable(...Type): T

Suffix form only 1

Not Supported

Parameter dropped 2

1. TypeLang carries one spelling of a variadic, and it is the suffix

The ellipsis stands behind the type, the way PHP itself writes it. The prefix form was read by earlier versions of the parser and is a syntax error now.

callable(Type...): T
2. Phan drops the parameter rather than reporting it
// Written callable(...Type): T
// Read callable(): T

Callable with optional variadic parameters

callable(Type...=): T callable(Type ...$name=): T

Error depends on the spelling 1

No Error

No Error

No Error

1. Psalm error depends on parameter definition syntax
// OK: Cannot have variadic param with a default in docblock callable(...T=): T
// Bug: Cannot have duplicate tokens in docblock callable(T...=): T

Open in psalm.dev

Callable carrying the variadic marker twice

callable(...Type ...$name): T

Internal error 1

No Error

No Error

1. Psalm throws an internal error instead of a valid error message
callable(...int ...$name) // Internal Psalm error on line ...: // Unrecognised parse tree type Psalm\Internal\Type\ParseTree\CallableParamTree

Open in psalm.dev

The $this return type

callable(): $this

Callable declaring template parameters

callable<T>(T): T

Upper bound of a template parameter

callable<T of Some>(T): T callable<T as Some>(T): T

Both words read as one 1

Both words read as one 1

1. PHPStan keeps no record of which word was written

An as comes back as an of, where TypeLang keeps the bound as it was written.

// Written callable<T as Some>(T): T
// Read callable<T of Some>(T): T

Lower bound of a template parameter

callable<T super Some>(T): T

Default of a template parameter

callable<T = int>(T): T

12 September 2026