Specimen sheetType language for PHPPHP 8.1+
TypeLang is a declarative type language inspired by PHPStan and Psalm. It describes the types you already
write in doc-comments — int[], array<string, T>, Foo::CONST_*,
T is U ? A : B — and ships the parts to work with them: a parser, a printer, a reflection reader
and a PHPDoc parser. The tree below is drawn by a JavaScript port of the reference parser. Type a type.
PrettyTypePrinter
§ 01
Every construct in the language composes with every other. Start from a name and keep wrapping.
Any identifier is a type: a built-in, a class, an alias. TypeLang checks that a type is well-formed, not that it exists — resolution is a separate concern.
Basic types ↗A leading question mark wraps the type in a NullableTypeNode. It binds tighter than |, so ?A|B is (?A)|B.
Angle brackets carry generics. Any type goes inside, trailing commas are fine, and * stands for “whatever”.
Unions are flat: A|B|C is one node with three children, not two nested pairs. Intersections use & and bind tighter.
A second argument describes the key. array-key, int, literal strings — the parser doesn't care which; your tool decides what they mean.
Braces turn a name into a shape: named fields, each with its own type. Keys can be identifiers, numbers, quoted strings or class constants.
Shape types ↗int<1, max> is just a generic with two literal-ish arguments. The trailing ... says “more keys may follow”.
A question mark after the key marks the field optional. Notice how little of the tree has to move.
Shape types ↗Any name followed by parentheses is a callable type. Parameters may be named, variadic, by-reference or optional; the return type follows a colon.
Callable types ↗Callables can declare template parameters with of, super and defaults. The return type here is conditional: T is Paid ? Receipt : null.
§ 02
Every construct the grammar knows, set as a specimen. Hover a bracket to see the node behind it.
§ 03
Every grammar construct is a flag on TypeParserFeatures. Switch some off and the parser
rejects the syntax with a message that says why. Native PHP types only? Turn everything off but names.
§ 04
83 syntactic constructs, each fed to every tool's own parser. A construct counts when the parser returns a node carrying what was written — not a silently truncated one.
Full table with the verdict for every construct, per tool, in the syntax comparison ↗. Two tools answer even where they did not understand the question; the comparison explains how those are told apart.
§ 05
Five packages, one shared vocabulary. Each is published standalone and split from a single development monorepo.
AST node classes — the shared vocabulary of the ecosystem.
composer require type-lang/types
Parses TypeLang syntax into an AST, validating the grammar. Strict or partial mode, feature toggles, readable errors.
composer require type-lang/parser
Renders nodes back to strings: the full pretty form, or a native PHP type declaration.
composer require type-lang/printer
Builds nodes from the types exposed by PHP Reflection objects.
composer require type-lang/reader
Parses /** ... */ comments into a graph of description and tags.
composer require type-lang/phpdoc
// A string in, a tree out.
$parser = new TypeLang\Parser\TypeParser();
$type = $parser->parse('array{ key: int }');
// A tree in, a string out.
echo new TypeLang\Printer\NativeTypePrinter()->print($type); // array
echo new TypeLang\Printer\PrettyTypePrinter()->print($type); // array{ key: int }