Skip to content

Commit 31f5bee

Browse files
committed
Actualize type resolver documentation
1 parent 8c6f6d9 commit 31f5bee

2 files changed

Lines changed: 74 additions & 75 deletions

File tree

Writerside/tl.tree

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@
3939
<toc-element topic="parser.md" toc-title="Type Parser">
4040
<toc-element topic="tolerant-mode.md" />
4141
<toc-element topic="features.md" />
42+
<toc-element topic="type-resolver.md" />
4243
<toc-element topic="visitors.md">
43-
<toc-element topic="type-resolver.md" />
4444
<toc-element topic="matcher-visitor.md"/>
4545
<toc-element topic="class-name-matcher-visitor.md"/>
4646
<toc-element topic="dumper-visitor.md"/>

Writerside/topics/parser/type-resolver.md

Lines changed: 73 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -15,49 +15,22 @@ is the utility for it.
1515

1616
## How It Works
1717

18-
`TypeResolver::resolve()` walks the whole node graph (via the same
19-
[`Traverser`](visitors.md) machinery used for [`TypeMapVisitor`](type-map-visitor.md))
20-
and, for every `TypeLang\Type\Name` it finds, calls your `$transform`
21-
callback. If the callback returns a `Name`, it replaces the original one **in
22-
place**; if it returns `null`, the original name is left untouched.
18+
`TypeResolver` is an immutable builder. You register the imports it should
19+
resolve against — through the fluent `withTypeImport()` / `withTypeImportAs()`
20+
methods (or the constructor) — and then call `resolve()`. It walks the whole
21+
node graph (via the same [`Traverser`](visitors.md) machinery used by
22+
[`TypeMapVisitor`](type-map-visitor.md)) and rewrites every matching
23+
`TypeLang\Type\Name` it finds **in place**.
2324

2425
```php
25-
interface TypeResolverInterface
26+
final readonly class TypeResolver
2627
{
27-
/**
28-
* @param callable(Name): (Name|null) $transform
29-
*/
30-
public function resolve(
31-
TypeNode $type,
32-
callable $transform,
33-
): TypeNode;
34-
}
35-
```
36-
37-
```php
38-
use TypeLang\Parser\TypeParser;
39-
use TypeLang\Parser\TypeResolver;
40-
use TypeLang\Type\Name;
41-
42-
43-
$type = new TypeParser()
44-
->parse('User|Admin');
45-
46-
47-
new TypeResolver()
48-
->resolve($type, static function (Name $name): ?Name {
49-
if ($name->toLowerString() === 'user') {
50-
return Name::createFromString('App\\Example\\User');
51-
}
52-
53-
return null;
54-
});
28+
public function withTypeImport(string $name): self;
5529

30+
public function withTypeImportAs(string $name, string $alias): self;
5631

57-
// App\Example\User
58-
echo $type->statements[0]->name->toString();
59-
// Admin
60-
echo $type->statements[1]->name->toString();
32+
public function resolve(TypeNode $type): TypeNode;
33+
}
6134
```
6235

6336
> `resolve()` **mutates** and returns the **same** `$type` instance you passed
@@ -67,10 +40,9 @@ echo $type->statements[1]->name->toString();
6740
6841
## Resolving Against PHP `use` Statements
6942

70-
The most common use case — resolving relative names the same way PHP resolves
71-
class references against the `use` statements of the file a phpdoc comment
72-
lives in — is covered by the ready-made `TypeResolver\PhpUseStatementsTransformer`,
73-
an implementation of `TransformerInterface` (`__invoke(Name $name): ?Name`).
43+
The most common use case is resolving relative names the same way PHP resolves
44+
class references against the `use` statements of the file a phpdoc comment lives
45+
in.
7446

7547
Given PHP source that declares:
7648

@@ -79,44 +51,43 @@ use TypeLang\Parser\Node;
7951
use TypeLang\Parser\Exception as Error;
8052
```
8153

82-
build the equivalent transformer by listing the plain imports as values and
83-
the aliased ones as `alias => target` pairs:
54+
register the plain imports with `withTypeImport()` and the aliased ones with
55+
`withTypeImportAs()`:
8456

8557
```php
86-
use TypeLang\Parser\TypeResolver\PhpUseStatementsTransformer;
58+
use TypeLang\Parser\TypeResolver;
8759

88-
$transformer = new PhpUseStatementsTransformer([
60+
$resolver = new TypeResolver()
8961
// use TypeLang\Parser\Node;
90-
'TypeLang\Parser\Node',
62+
->withTypeImport('TypeLang\Parser\Node')
9163
// use TypeLang\Parser\Exception as Error;
92-
'Error' => 'TypeLang\Parser\Exception',
93-
]);
64+
->withTypeImportAs('TypeLang\Parser\Exception', 'Error');
9465
```
9566

96-
Applying it resolves every first segment of a name that matches one of the
97-
imports, and merges in the rest of the name unchanged:
67+
Applying it resolves the first segment of every name that matches one of the
68+
imports (case-insensitively, just like PHP) and merges in the rest of the name
69+
unchanged:
9870

9971
```php
100-
use TypeLang\Parser\TypeResolver\PhpUseStatementsTransformer;
72+
use TypeLang\Parser\TypeParser;
73+
use TypeLang\Parser\TypeResolver;
10174

10275
// parse array shape with 2 named types
103-
$sourceAst = $parser->parse(<<<'PHP'
104-
array{
105-
Node,
106-
Error\SemanticException
107-
}
108-
PHP);
76+
$statement = new TypeParser()
77+
->parse(<<<'PHP'
78+
array{
79+
Node,
80+
Error\SemanticException
81+
}
82+
PHP);
10983

11084
// resolve type names
111-
new TypeResolver()
112-
->resolve($sourceAst, new PhpUseStatementsTransformer([
113-
// use TypeLang\Parser\Node;
114-
'TypeLang\Parser\Node',
115-
// use TypeLang\Parser\Exception as Error;
116-
'Error' => 'TypeLang\Parser\Exception',
117-
]));
118-
119-
foreach ($sourceAst->fields->items as $field) {
85+
$statement = new TypeResolver()
86+
->withTypeImport('TypeLang\Parser\Node')
87+
->withTypeImportAs('TypeLang\Parser\Exception', 'Error')
88+
->resolve($statement);
89+
90+
foreach ($statement->fields->items as $field) {
12091
echo $field->type->name->toString(), "\n";
12192
}
12293

@@ -125,11 +96,39 @@ foreach ($sourceAst->fields->items as $field) {
12596
// TypeLang\Parser\Exception\SemanticException
12697
```
12798

128-
## Writing a Custom Transformer
99+
Because `TypeResolver` is immutable, `withTypeImport()` and
100+
`withTypeImportAs()` return a new instance each time and never mutate the
101+
receiver — the same resolver can be safely shared and extended for different
102+
contexts.
103+
104+
### Passing Imports Through the Constructor
105+
106+
If you already have the whole import list assembled, hand it to the constructor
107+
directly instead of chaining `with*` calls. Plain imports are listed as values
108+
and aliased ones as `alias => target` pairs:
109+
110+
```php
111+
use TypeLang\Parser\TypeResolver;
112+
113+
$resolver = new TypeResolver([
114+
// use TypeLang\Parser\Node;
115+
'TypeLang\Parser\Node',
116+
// use TypeLang\Parser\Exception as Error;
117+
'Error' => 'TypeLang\Parser\Exception',
118+
]);
119+
120+
$statement = $resolver->resolve($statement);
121+
```
122+
123+
For a plain import the alias is inferred from the last segment of the name
124+
(`TypeLang\Parser\Node` becomes reachable as `Node`), which mirrors how a
125+
`use` statement without an explicit `as` behaves.
126+
127+
## Custom Name Rewriting
129128

130-
Because `TransformerInterface` is a single-method contract, any callable
131-
works — a closure, an invokable object backed by a symbol table, a PSR-4
132-
autoloader lookup, and so on. `PhpUseStatementsTransformer` is just the
133-
built-in implementation for the most common case; nothing stops you from
134-
implementing `TransformerInterface` yourself for anything more specific your
135-
application needs (resolving against a runtime class map, a container, etc).
129+
`TypeResolver` is intentionally scoped to `use`-statement semantics. When you
130+
need to rewrite names some other way — against a runtime class map, a DI
131+
container, a PSR-4 lookup, and so on — drop down to
132+
[`TypeMapVisitor`](type-map-visitor.md), which calls a callback of your own for
133+
every `Name` in the AST. `TypeResolver` is simply the ready-made configuration
134+
of that visitor for the most common case.

0 commit comments

Comments
 (0)