Skip to content

Commit be1addf

Browse files
committed
Split type resolver builtin "resolveWith" method to the external transformer
1 parent d44fffe commit be1addf

4 files changed

Lines changed: 106 additions & 63 deletions

File tree

libs/parser/src/Traverser/TypeMapVisitor.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use TypeLang\Node\Node;
99
use TypeLang\Node\Type\CallableTypeNode;
1010
use TypeLang\Node\Type\ClassConstMaskNode;
11+
use TypeLang\Node\Type\ClassConstNode;
1112
use TypeLang\Node\Type\ConstMaskNode;
1213
use TypeLang\Node\Type\NamedTypeNode;
1314

@@ -41,6 +42,7 @@ public function enter(Node $node): ?Command
4142

4243
return null;
4344

45+
case $node instanceof ClassConstNode:
4446
case $node instanceof ClassConstMaskNode:
4547
$node->class = $this->map($node->class);
4648

libs/parser/src/TypeResolver.php

Lines changed: 1 addition & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -4,77 +4,15 @@
44

55
namespace TypeLang\Parser;
66

7-
use TypeLang\Node\Name;
87
use TypeLang\Node\Type\TypeNode;
98
use TypeLang\Parser\Traverser\TypeMapVisitor;
109

1110
final class TypeResolver implements TypeResolverInterface
1211
{
1312
public function resolve(TypeNode $type, callable $transform): TypeNode
1413
{
15-
Traverser::through(
16-
visitor: new TypeMapVisitor($transform(...)),
17-
nodes: [$type],
18-
);
14+
Traverser::through(new TypeMapVisitor($transform(...)), [$type]);
1915

2016
return $type;
2117
}
22-
23-
/**
24-
* Use for example for code like this:
25-
* ```php
26-
* use TypeLang\Parser\Node;
27-
* use TypeLang\Parser\Exception as Error;
28-
*
29-
* $parser = new TypeLang\Parser\Parser();
30-
* $result = $parser->parse(<<<'PHP'
31-
* array { Node, Error\SemanticException }
32-
* PHP);
33-
*
34-
* $resolver = new \TypeLang\Parser\TypeResolver();
35-
* $result = $resolver->resolveWith($expected, [
36-
* 'TypeLang\Parser\Node', // use TypeLang\Parser\Node;
37-
* 'Error' => 'TypeLang\Parser\Exception', // use TypeLang\Parser\Exception as Error;
38-
* ]);
39-
*
40-
* // Expected Output:
41-
* // > array{
42-
* // > TypeLang\Parser\Node,
43-
* // > TypeLang\Parser\Exception\SemanticException
44-
* // > }
45-
* ```
46-
*
47-
* @param array<non-empty-string|array-key, non-empty-string|Name> $replacements
48-
*/
49-
public function resolveWith(TypeNode $type, array $replacements): TypeNode
50-
{
51-
foreach ($replacements as $key => $replacement) {
52-
// normalize value
53-
if (\is_string($replacement)) {
54-
$replacement = Name::createFromString($replacement);
55-
}
56-
57-
// normalize key
58-
if (\is_int($key)) {
59-
unset($replacements[$key]);
60-
61-
$key = $replacement->last->toString();
62-
}
63-
64-
$replacements[\strtolower($key)] = $replacement;
65-
}
66-
67-
/** @var array<non-empty-lowercase-string, Name> $replacements */
68-
return $this->resolve($type, static function (Name $name) use ($replacements) {
69-
$first = \strtolower($name->first->toString());
70-
71-
if (isset($replacements[$first])) {
72-
$prefix = $replacements[$first];
73-
74-
return $prefix->mergeWith($name);
75-
}
76-
77-
return null;
78-
});
79-
}
8018
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypeLang\Parser\TypeResolver;
6+
7+
use TypeLang\Node\Name;
8+
9+
/**
10+
* A set of rules for modifying names defined in the `use` statements of PHP code.
11+
*
12+
* For example, for such code:
13+
* ```
14+
* use TypeLang\Parser\Node;
15+
* use TypeLang\Parser\Exception as Error;
16+
* ```
17+
*
18+
* You need to create a rule like this:
19+
* ```
20+
* $transformer = new PhpUseStatementsTransformer([
21+
* 'TypeLang\Parser\Node', // for `use TypeLang\Parser\Node;`
22+
* 'Error' => 'TypeLang\Parser\Exception', // for `use TypeLang\Parser\Exception as Error;`
23+
* ]);
24+
* ```
25+
*
26+
* Then, if you apply it to the finished result, you will get
27+
* normalized substitutions:
28+
* ```
29+
* $sourceAst = new TypeLang\Parser\Parser()
30+
* ->parse(<<<'PHP'
31+
* array { Node, Error\SemanticException }
32+
* PHP);
33+
*
34+
* $transformedAst = new \TypeLang\Parser\TypeResolver()
35+
* ->resolve($sourceAst, $transformer);
36+
*
37+
* // Expected Output:
38+
* // > array{
39+
* // > TypeLang\Parser\Node,
40+
* // > TypeLang\Parser\Exception\SemanticException
41+
* // > }
42+
* ```
43+
*/
44+
final readonly class PhpUseStatementsTransformer implements TransformerInterface
45+
{
46+
/**
47+
* @var array<non-empty-lowercase-string, Name>
48+
*/
49+
private array $replacements;
50+
51+
/**
52+
* @param iterable<non-empty-string|array-key, non-empty-string|Name> $replacements
53+
*/
54+
public function __construct(iterable $replacements)
55+
{
56+
$this->replacements = $this->format($replacements);
57+
}
58+
59+
/**
60+
* @param iterable<array-key, non-empty-string|Name> $replacements
61+
* @return array<non-empty-lowercase-string, Name>
62+
*/
63+
private function format(iterable $replacements): array
64+
{
65+
$result = [];
66+
67+
foreach ($replacements as $key => $replacement) {
68+
// normalize value
69+
if (\is_string($replacement)) {
70+
$replacement = Name::createFromString($replacement);
71+
}
72+
73+
// normalize key
74+
if (\is_int($key) || $key === '') {
75+
$key = $replacement->last->toString();
76+
}
77+
78+
$result[\strtolower($key)] = $replacement;
79+
}
80+
81+
return $result;
82+
}
83+
84+
public function __invoke(Name $name): ?Name
85+
{
86+
$first = \strtolower($name->first->toString());
87+
$prefix = $this->replacements[$first] ?? null;
88+
89+
return $prefix?->mergeWith($name);
90+
}
91+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypeLang\Parser\TypeResolver;
6+
7+
use TypeLang\Node\Name;
8+
9+
interface TransformerInterface
10+
{
11+
public function __invoke(Name $name): ?Name;
12+
}

0 commit comments

Comments
 (0)