@@ -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
7547Given PHP source that declares:
7648
@@ -79,44 +51,43 @@ use TypeLang\Parser\Node;
7951use 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