TypeLang

Specimen sheetType language for PHPPHP 8.1+

The type syntax of PHPDoc,
parsed.

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.

0 tokens0 nodesdepth 0

PrettyTypePrinter


    

§ 01

One type, grown.

Every construct in the language composes with every other. Start from a name and keep wrapping.

  1. A name

    Any identifier is a type: a built-in, a class, an alias, even a hyphenated non-empty-string. The parser reads it as a NamedTypeNode. It checks that a type is well-formed, not that it exists — resolution is a separate concern.

    Basic types ↗
  2. Nullable

    A leading question mark wraps the type in a NullableTypeNode. It binds tighter than |, so ?A|B is (?A)|B.

    Logical types ↗
  3. Template arguments

    Angle brackets carry generics. Any type goes inside, trailing commas are fine, * stands for “whatever”, and covariant, contravariant or invariant may precede an argument.

    Generic types ↗
  4. Union

    Unions are flat: A|B|C is one node with three children, not two nested pairs. Intersections use & and bind tighter.

    Logical types ↗
  5. Keys

    A second argument describes the key. array-key, int, literal strings — the parser doesn't care which; your tool decides what they mean.

    Generic types ↗
  6. Shape

    Braces turn a name into a shape: a list of fields, each with its own type. Keys can be identifiers, numbers, quoted strings or class constants; a field with no key is implicit.

    Shape types ↗
  7. Ranges and unsealed shapes

    int<1, max> is just a generic with two literal-ish arguments. The trailing ... says “more keys may follow”, and may even describe them: ...<string, mixed>.

    Shape types ↗
  8. Optional fields

    A question mark after the key marks the field optional. Notice how little of the tree has to move.

    Shape types ↗
  9. Callable

    Any name followed by parentheses is a callable type. Parameters may be named, variadic, by-reference or carry a default; the return type follows a colon and binds tightly: callable(): A|B is (callable(): A)|B.

    Callable types ↗
  10. Generic callables and conditions

    A named callable like Closure can declare template parameters: variance, of / as and super bounds, and defaults. The return type here is conditional: T is Paid ? Receipt : null, or T is not Paid ? … to negate it.

    Conditional types ↗

§ 02

Vocabulary.

Every construct the grammar knows, set as a specimen. Hover a bracket to see the node behind it.

Names

Nullable

Union

Intersection

Generics

Variance hints

Wildcard argument

Legacy list

Offset access

Literals

Constants and masks

Shapes

Callables

Generic callables

Defaults

Unsealed shapes

Conditional

$this

§ 03

Strict subsets.

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

Coverage.

239 syntactic constructs, the basic ones and those on which PHPStan, Psalm and TypeLang disagree, each fed to the tool's own parser. A construct counts when the parser returns a node carrying what was written, not a silently truncated one.

TypeLang type-lang/parser 2.0236/239
PHPStan phpstan/phpdoc-parser 2.4180/239
Psalm vimeo/psalm 7.0108/239
Phan phan/phan 6.099/239

Full table with the verdict for every construct, per tool, in the syntax comparison ↗. Phan is not in that table; it was run over the same constructs. The table also lists 34 malformed inputs that must be rejected: TypeLang rejects 33, PHPStan 29, Phan 29 and Psalm 5. Psalm answers even where it did not understand the question; the comparison explains how those answers are told apart.

§ 05

Parts.

Five packages, one shared vocabulary. Each is published standalone and split from a single development monorepo.

Reflection "array<K, V>" /** @param */ reader parser phpdoc typesTypeLang\Type\* printer "array"
Everything speaks the same AST. Parse a string, read a reflection, print back — or walk the nodes with a visitor.

type-lang/types

AST node classes — the shared vocabulary of the ecosystem.

composer require type-lang/types

type-lang/parser

Parses TypeLang syntax into an AST, validating the grammar. Strict or partial mode, feature toggles, readable errors.

composer require type-lang/parser

type-lang/printer

Renders nodes back to strings: the full pretty form, or a native PHP type declaration.

composer require type-lang/printer

type-lang/reader

Builds nodes from the types exposed by PHP Reflection objects.

composer require type-lang/reader

type-lang/phpdoc

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 }