Repository: CuyZ/Valinor (php)

Issue:
Types for parameter `Money\Money::__construct($currency)` do not match: `int|string` (docblock) does not accept `Money\Currency` (native).
Hi. Can't hydrate structure using valueobject moneyphp/money 😢



<details>
  <summary>Repoduce code</summary>
  
```php
<?php

declare(strict_types=1);

use CuyZ\Valinor\Mapper\Source\Source;
use Money\Currency;
use Money\Money;
use Symfony\Component\Uid\Uuid;

require_once __DIR__ . '/vendor/autoload.php';


final readonly class CreateApplication
{
    /**
     * @param int<1, 400>                      $term
     * @param non-empty-list<non-empty-string> $banks
     */
    public function __construct(
        public Uuid $clientId,
        public Money $amount,
        public int $term,
        public array $banks,
    ) {}
}



$json = <<<JSON
        {
        "banks": ["test"],
        "term": 20,
        "amount": 0
    }
    JSON;

try {
    $val = (new \CuyZ\Valinor\MapperBuilder())
        ->registerConstructor(
            static fn(string $value, string $currency): Money => new Money($value, new Currency($currency))
        )
        ->mapper()
        ->map(CreateApplication::class, Source::json($json))
    ;

    var_dump($val);
} catch (\CuyZ\Valinor\Mapper\MappingError $error) {
    // Handle the error…
}
```
  
</details>




<details>
    <summary>Stack Trace</summary>


```log
PHP Fatal error:  Uncaught CuyZ\Valinor\Definition\Exception\TypesDoNotMatch: Types for parameter `Money\Money::__construct($currency)` do not match: `int|string` (docblock) does not accept `Money\Currency` (native). in /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionTypeResolver.php:51
Stack trace:
#0 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionParameterDefinitionBuilder.php(28): CuyZ\Valinor\Definition\Repository\Reflection\ReflectionTypeResolver->resolveType(Object(ReflectionParameter))
#1 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionMethodDefinitionBuilder.php(42): CuyZ\Valinor\Definition\Repository\Reflection\ReflectionParameterDefinitionBuilder->for(Object(ReflectionParameter), Object(CuyZ\Valinor\Definition\Repository\Reflection\ReflectionTypeResolver))
#2 [internal function]: CuyZ\Valinor\Definition\Repository\Reflection\ReflectionMethodDefinitionBuilder->CuyZ\Valinor\Definition\Repository\Reflection\{closure}(Object(ReflectionParameter))
#3 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionMethodDefinitionBuilder.php(41): array_map(Object(Closure), Array)
#4 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionClassDefinitionRepository.php(123): CuyZ\Valinor\Definition\Repository\Reflection\ReflectionMethodDefinitionBuilder->for(Object(ReflectionMethod), Object(CuyZ\Valinor\Definition\Repository\Reflection\ReflectionTypeResolver))
#5 [internal function]: CuyZ\Valinor\Definition\Repository\Reflection\ReflectionClassDefinitionRepository->CuyZ\Valinor\Definition\Repository\Reflection\{closure}(Object(ReflectionMethod))
#6 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionClassDefinitionRepository.php(120): array_map(Object(Closure), Array)
#7 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionClassDefinitionRepository.php(72): CuyZ\Valinor\Definition\Repository\Reflection\ReflectionClassDefinitionRepository->methods(Object(CuyZ\Valinor\Type\Types\NativeClassType))
#8 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Definition/Repository/Cache/CacheClassDefinitionRepository.php(34): CuyZ\Valinor\Definition\Repository\Reflection\ReflectionClassDefinitionRepository->for(Object(CuyZ\Valinor\Type\Types\NativeClassType))
#9 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/Tree/Builder/InterfaceNodeBuilder.php(47): CuyZ\Valinor\Definition\Repository\Cache\CacheClassDefinitionRepository->for(Object(CuyZ\Valinor\Type\Types\NativeClassType))
#10 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/Tree/Builder/CasterProxyNodeBuilder.php(24): CuyZ\Valinor\Mapper\Tree\Builder\InterfaceNodeBuilder->build(Object(CuyZ\Valinor\Mapper\Tree\Shell), Object(CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder))
#11 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/Tree/Builder/IterableNodeBuilder.php(26): CuyZ\Valinor\Mapper\Tree\Builder\CasterProxyNodeBuilder->build(Object(CuyZ\Valinor\Mapper\Tree\Shell), Object(CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder))
#12 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/Tree/Builder/StrictNodeBuilder.php(36): CuyZ\Valinor\Mapper\Tree\Builder\IterableNodeBuilder->build(Object(CuyZ\Valinor\Mapper\Tree\Shell), Object(CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder))
#13 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/Tree/Builder/ErrorCatcherNodeBuilder.php(33): CuyZ\Valinor\Mapper\Tree\Builder\StrictNodeBuilder->build(Object(CuyZ\Valinor\Mapper\Tree\Shell), Object(CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder))
#14 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/Tree/Builder/RootNodeBuilder.php(16): CuyZ\Valinor\Mapper\Tree\Builder\ErrorCatcherNodeBuilder->build(Object(CuyZ\Valinor\Mapper\Tree\Shell), Object(CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder))
#15 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/Tree/Builder/ObjectNodeBuilder.php(56): CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder->build(Object(CuyZ\Valinor\Mapper\Tree\Shell))
#16 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/Tree/Builder/ObjectNodeBuilder.php(23): CuyZ\Valinor\Mapper\Tree\Builder\ObjectNodeBuilder->children(Object(CuyZ\Valinor\Mapper\Tree\Shell), Object(CuyZ\Valinor\Mapper\Object\ArgumentsValues), Object(CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder))
#17 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/Tree/Builder/NativeClassNodeBuilder.php(39): CuyZ\Valinor\Mapper\Tree\Builder\ObjectNodeBuilder->build(Object(CuyZ\Valinor\Mapper\Object\FilteredObjectBuilder), Object(CuyZ\Valinor\Mapper\Tree\Shell), Object(CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder))
#18 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/Tree/Builder/CasterNodeBuilder.php(24): CuyZ\Valinor\Mapper\Tree\Builder\NativeClassNodeBuilder->build(Object(CuyZ\Valinor\Mapper\Tree\Shell), Object(CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder))
#19 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/Tree/Builder/UnionNodeBuilder.php(37): CuyZ\Valinor\Mapper\Tree\Builder\CasterNodeBuilder->build(Object(CuyZ\Valinor\Mapper\Tree\Shell), Object(CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder))
#20 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/Tree/Builder/InterfaceNodeBuilder.php(51): CuyZ\Valinor\Mapper\Tree\Builder\UnionNodeBuilder->build(Object(CuyZ\Valinor\Mapper\Tree\Shell), Object(CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder))
#21 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/Tree/Builder/CasterProxyNodeBuilder.php(24): CuyZ\Valinor\Mapper\Tree\Builder\InterfaceNodeBuilder->build(Object(CuyZ\Valinor\Mapper\Tree\Shell), Object(CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder))
#22 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/Tree/Builder/IterableNodeBuilder.php(26): CuyZ\Valinor\Mapper\Tree\Builder\CasterProxyNodeBuilder->build(Object(CuyZ\Valinor\Mapper\Tree\Shell), Object(CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder))
#23 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/Tree/Builder/StrictNodeBuilder.php(36): CuyZ\Valinor\Mapper\Tree\Builder\IterableNodeBuilder->build(Object(CuyZ\Valinor\Mapper\Tree\Shell), Object(CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder))
#24 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/Tree/Builder/ErrorCatcherNodeBuilder.php(33): CuyZ\Valinor\Mapper\Tree\Builder\StrictNodeBuilder->build(Object(CuyZ\Valinor\Mapper\Tree\Shell), Object(CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder))
#25 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/Tree/Builder/RootNodeBuilder.php(16): CuyZ\Valinor\Mapper\Tree\Builder\ErrorCatcherNodeBuilder->build(Object(CuyZ\Valinor\Mapper\Tree\Shell), Object(CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder))
#26 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/TypeTreeMapper.php(44): CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder->build(Object(CuyZ\Valinor\Mapper\Tree\Shell))
#27 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/TypeTreeMapper.php(25): CuyZ\Valinor\Mapper\TypeTreeMapper->node('CreateApplicati...', Object(CuyZ\Valinor\Mapper\Source\Source))
#28 /Users/aza/PhpstormProjects/cash-backend/app.php(44): CuyZ\Valinor\Mapper\TypeTreeMapper->map('CreateApplicati...', Object(CuyZ\Valinor\Mapper\Source\Source))
#29 {main}
  thrown in /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionTypeResolver.php on line 51

Fatal error: Uncaught CuyZ\Valinor\Definition\Exception\TypesDoNotMatch: Types for parameter `Money\Money::__construct($currency)` do not match: `int|string` (docblock) does not accept `Money\Currency` (native). in /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionTypeResolver.php:51
Stack trace:
#0 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionParameterDefinitionBuilder.php(28): CuyZ\Valinor\Definition\Repository\Reflection\ReflectionTypeResolver->resolveType(Object(ReflectionParameter))
#1 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionMethodDefinitionBuilder.php(42): CuyZ\Valinor\Definition\Repository\Reflection\ReflectionParameterDefinitionBuilder->for(Object(ReflectionParameter), Object(CuyZ\Valinor\Definition\Repository\Reflection\ReflectionTypeResolver))
#2 [internal function]: CuyZ\Valinor\Definition\Repository\Reflection\ReflectionMethodDefinitionBuilder->CuyZ\Valinor\Definition\Repository\Reflection\{closure}(Object(ReflectionParameter))
#3 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionMethodDefinitionBuilder.php(41): array_map(Object(Closure), Array)
#4 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionClassDefinitionRepository.php(123): CuyZ\Valinor\Definition\Repository\Reflection\ReflectionMethodDefinitionBuilder->for(Object(ReflectionMethod), Object(CuyZ\Valinor\Definition\Repository\Reflection\ReflectionTypeResolver))
#5 [internal function]: CuyZ\Valinor\Definition\Repository\Reflection\ReflectionClassDefinitionRepository->CuyZ\Valinor\Definition\Repository\Reflection\{closure}(Object(ReflectionMethod))
#6 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionClassDefinitionRepository.php(120): array_map(Object(Closure), Array)
#7 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionClassDefinitionRepository.php(72): CuyZ\Valinor\Definition\Repository\Reflection\ReflectionClassDefinitionRepository->methods(Object(CuyZ\Valinor\Type\Types\NativeClassType))
#8 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Definition/Repository/Cache/CacheClassDefinitionRepository.php(34): CuyZ\Valinor\Definition\Repository\Reflection\ReflectionClassDefinitionRepository->for(Object(CuyZ\Valinor\Type\Types\NativeClassType))
#9 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/Tree/Builder/InterfaceNodeBuilder.php(47): CuyZ\Valinor\Definition\Repository\Cache\CacheClassDefinitionRepository->for(Object(CuyZ\Valinor\Type\Types\NativeClassType))
#10 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/Tree/Builder/CasterProxyNodeBuilder.php(24): CuyZ\Valinor\Mapper\Tree\Builder\InterfaceNodeBuilder->build(Object(CuyZ\Valinor\Mapper\Tree\Shell), Object(CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder))
#11 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/Tree/Builder/IterableNodeBuilder.php(26): CuyZ\Valinor\Mapper\Tree\Builder\CasterProxyNodeBuilder->build(Object(CuyZ\Valinor\Mapper\Tree\Shell), Object(CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder))
#12 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/Tree/Builder/StrictNodeBuilder.php(36): CuyZ\Valinor\Mapper\Tree\Builder\IterableNodeBuilder->build(Object(CuyZ\Valinor\Mapper\Tree\Shell), Object(CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder))
#13 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/Tree/Builder/ErrorCatcherNodeBuilder.php(33): CuyZ\Valinor\Mapper\Tree\Builder\StrictNodeBuilder->build(Object(CuyZ\Valinor\Mapper\Tree\Shell), Object(CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder))
#14 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/Tree/Builder/RootNodeBuilder.php(16): CuyZ\Valinor\Mapper\Tree\Builder\ErrorCatcherNodeBuilder->build(Object(CuyZ\Valinor\Mapper\Tree\Shell), Object(CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder))
#15 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/Tree/Builder/ObjectNodeBuilder.php(56): CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder->build(Object(CuyZ\Valinor\Mapper\Tree\Shell))
#16 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/Tree/Builder/ObjectNodeBuilder.php(23): CuyZ\Valinor\Mapper\Tree\Builder\ObjectNodeBuilder->children(Object(CuyZ\Valinor\Mapper\Tree\Shell), Object(CuyZ\Valinor\Mapper\Object\ArgumentsValues), Object(CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder))
#17 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/Tree/Builder/NativeClassNodeBuilder.php(39): CuyZ\Valinor\Mapper\Tree\Builder\ObjectNodeBuilder->build(Object(CuyZ\Valinor\Mapper\Object\FilteredObjectBuilder), Object(CuyZ\Valinor\Mapper\Tree\Shell), Object(CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder))
#18 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/Tree/Builder/CasterNodeBuilder.php(24): CuyZ\Valinor\Mapper\Tree\Builder\NativeClassNodeBuilder->build(Object(CuyZ\Valinor\Mapper\Tree\Shell), Object(CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder))
#19 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/Tree/Builder/UnionNodeBuilder.php(37): CuyZ\Valinor\Mapper\Tree\Builder\CasterNodeBuilder->build(Object(CuyZ\Valinor\Mapper\Tree\Shell), Object(CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder))
#20 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/Tree/Builder/InterfaceNodeBuilder.php(51): CuyZ\Valinor\Mapper\Tree\Builder\UnionNodeBuilder->build(Object(CuyZ\Valinor\Mapper\Tree\Shell), Object(CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder))
#21 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/Tree/Builder/CasterProxyNodeBuilder.php(24): CuyZ\Valinor\Mapper\Tree\Builder\InterfaceNodeBuilder->build(Object(CuyZ\Valinor\Mapper\Tree\Shell), Object(CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder))
#22 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/Tree/Builder/IterableNodeBuilder.php(26): CuyZ\Valinor\Mapper\Tree\Builder\CasterProxyNodeBuilder->build(Object(CuyZ\Valinor\Mapper\Tree\Shell), Object(CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder))
#23 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/Tree/Builder/StrictNodeBuilder.php(36): CuyZ\Valinor\Mapper\Tree\Builder\IterableNodeBuilder->build(Object(CuyZ\Valinor\Mapper\Tree\Shell), Object(CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder))
#24 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/Tree/Builder/ErrorCatcherNodeBuilder.php(33): CuyZ\Valinor\Mapper\Tree\Builder\StrictNodeBuilder->build(Object(CuyZ\Valinor\Mapper\Tree\Shell), Object(CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder))
#25 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/Tree/Builder/RootNodeBuilder.php(16): CuyZ\Valinor\Mapper\Tree\Builder\ErrorCatcherNodeBuilder->build(Object(CuyZ\Valinor\Mapper\Tree\Shell), Object(CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder))
#26 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/TypeTreeMapper.php(44): CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder->build(Object(CuyZ\Valinor\Mapper\Tree\Shell))
#27 /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Mapper/TypeTreeMapper.php(25): CuyZ\Valinor\Mapper\TypeTreeMapper->node('CreateApplicati...', Object(CuyZ\Valinor\Mapper\Source\Source))
#28 /Users/aza/PhpstormProjects/cash-backend/app.php(44): CuyZ\Valinor\Mapper\TypeTreeMapper->map('CreateApplicati...', Object(CuyZ\Valinor\Mapper\Source\Source))
#29 {main}
  thrown in /Users/aza/PhpstormProjects/cash-backend/vendor/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionTypeResolver.php on line 51
```

</details>








Pull request description:
fix: strengthen type tokens extraction
This commit refactors the way tokens are extracted from a raw type string, by using a better approach that consists in splitting first into tokens, and then detecting texts (aka string values).

This aims to fix edge cases like the following example, where the `$bar` annotation would previously have confused the lexer, leading to the type of `$foo` being used for `$bar` as well.

```php
final class SomeClass
{
    /**
      * @param non-empty-string $foo Some description containing $bar
      *      which is the next parameter name
      */
     public function __construct(
         public string $foo,
         public int $bar,
     ) {}
}
```

Fixes #494

Patch:
diff --git a/src/Type/Parser/Lexer/TokensExtractor.php b/src/Type/Parser/Lexer/TokensExtractor.php
new file mode 100644
index 0000000..aa21974
--- /dev/null
+++ b/src/Type/Parser/Lexer/TokensExtractor.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace CuyZ\Valinor\Type\Parser\Lexer;
+
+use function array_map;
+use function array_shift;
+use function implode;
+use function preg_split;
+
+/** @internal */
+final class TokensExtractor
+{
+    private const TOKEN_PATTERNS = [
+        'Anonymous class' => '[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++',
+        'Double colons' => '\:\:',
+        'Triple dots' => '\.\.\.',
+        'Dollar sign' => '\$',
+        'Whitespace' => '\s',
+        'Union' => '\|',
+        'Intersection' => '&',
+        'Opening bracket' => '\<',
+        'Closing bracket' => '\>',
+        'Opening square bracket' => '\[',
+        'Closing square bracket' => '\]',
+        'Opening curly bracket' => '\{',
+        'Closing curly bracket' => '\}',
+        'Colon' => '\:',
+        'Question mark' => '\?',
+        'Comma' => ',',
+        'Single quote' => "'",
+        'Double quote' => '"',
+    ];
+
+    /** @var list<string> */
+    private array $symbols = [];
+
+    public function __construct(string $string)
+    {
+        $pattern = '/(' . implode('|', self::TOKEN_PATTERNS) . ')' . '/';
+        $tokens = preg_split($pattern, $string, flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
+
+        $quote = null;
+        $text = null;
+
+        while (($token = array_shift($tokens)) !== null) {
+            if ($token === $quote) {
+                if ($text !== null) {
+                    $this->symbols[] = $text;
+                }
+
+                $this->symbols[] = $token;
+
+                $text = null;
+                $quote = null;
+            } elseif ($quote !== null) {
+                $text .= $token;
+            } elseif ($token === '"' || $token === "'") {
+                $quote = $token;
+
+                $this->symbols[] = $token;
+            } else {
+                $this->symbols[] = $token;
+            }
+        }
+
+        if ($text !== null) {
+            $this->symbols[] = $text;
+        }
+
+        $this->symbols = array_map('trim', $this->symbols);
+        $this->symbols = array_filter($this->symbols, static fn ($value) => $value !== '');
+        $this->symbols = array_values($this->symbols);
+    }
+
+    /**
+     * @return list<string>
+     */
+    public function all(): array
+    {
+        return $this->symbols;
+    }
+}
diff --git a/src/Type/Parser/LexingParser.php b/src/Type/Parser/LexingParser.php
index 116de4c..10ecb35 100644
--- a/src/Type/Parser/LexingParser.php
+++ b/src/Type/Parser/LexingParser.php
@@ -2,6 +2,7 @@
 
 namespace CuyZ\Valinor\Type\Parser;
 
+use CuyZ\Valinor\Type\Parser\Lexer\TokensExtractor;
 use CuyZ\Valinor\Type\Parser\Lexer\TokenStream;
 use CuyZ\Valinor\Type\Parser\Lexer\TypeLexer;
 use CuyZ\Valinor\Type\Type;
@@ -13,7 +14,7 @@ class LexingParser implements TypeParser
 
     public function parse(string $raw): Type
     {
-        $symbols = new ParserSymbols($raw);
+        $symbols = new TokensExtractor($raw);
 
         $tokens = array_map(
             fn (string $symbol) => $this->lexer->tokenize($symbol),
diff --git a/src/Type/Parser/ParserSymbols.php b/src/Type/Parser/ParserSymbols.php
deleted file mode 100644
index 15053ae..0000000
--- a/src/Type/Parser/ParserSymbols.php
+++ /dev/null
@@ -1,82 +0,0 @@
-<?php
-
-namespace CuyZ\Valinor\Type\Parser;
-
-/** @internal */
-final class ParserSymbols
-{
-    private const OPERATORS = [' ', '|', '&', '<', '>', '[', ']', '{', '}', ':', '?', ',', "'", '"'];
-
-    /** @var list<string> */
-    private array $symbols = [];
-
-    public function __construct(string $string)
-    {
-        $current = null;
-        $quote = null;
-
-        foreach (str_split($string) as $char) {
-            if ($char === $quote) {
-                $quote = null;
-            } elseif ($char === '"' || $char === "'") {
-                $quote = $char;
-            } elseif ($quote !== null || ! in_array($char, self::OPERATORS, true)) {
-                $current .= $char;
-                continue;
-            }
-
-            if ($current !== null) {
-                $this->symbols[] = $current;
-                $current = null;
-            }
-
-            $this->symbols[] = $char;
-        }
-
-        if ($current !== null) {
-            $this->symbols[] = $current;
-        }
-
-        $this->symbols = array_map('trim', $this->symbols);
-        $this->symbols = array_filter($this->symbols, static fn ($value) => $value !== '');
-
-        $this->mergeDoubleColons();
-        $this->detectAnonymousClass();
-    }
-
-    /**
-     * @return list<string>
-     */
-    public function all(): array
-    {
-        return $this->symbols;
-    }
-
-    private function mergeDoubleColons(): void
-    {
-        foreach ($this->symbols as $key => $symbol) {
-            /** @infection-ignore-all should not happen so it is not tested */
-            if ($key === 0) {
-                continue;
-            }
-
-            if ($symbol === ':' && $this->symbols[$key - 1] === ':') {
-                $this->symbols[$key - 1] = '::';
-                unset($this->symbols[$key]);
-            }
-        }
-    }
-
-    private function detectAnonymousClass(): void
-    {
-        foreach ($this->symbols as $key => $symbol) {
-            if (! str_starts_with($symbol, "class@anonymous\0")) {
-                continue;
-            }
-
-            $this->symbols[$key] = $symbol . $this->symbols[$key + 1] . $this->symbols[$key + 2];
-
-            array_splice($this->symbols, $key + 1, 2);
-        }
-    }
-}
diff --git a/src/Utility/Reflection/DocParser.php b/src/Utility/Reflection/DocParser.php
index 43968fe..b0cf76f 100644
--- a/src/Utility/Reflection/DocParser.php
+++ b/src/Utility/Reflection/DocParser.php
@@ -3,6 +3,7 @@
 namespace CuyZ\Valinor\Utility\Reflection;
 
 use CuyZ\Valinor\Type\Parser\Exception\Template\DuplicatedTemplateName;
+use CuyZ\Valinor\Type\Parser\Lexer\TokensExtractor;
 use ReflectionClass;
 use ReflectionFunctionAbstract;
 use ReflectionParameter;
@@ -10,9 +11,13 @@ use ReflectionProperty;
 
 use function array_key_exists;
 use function array_merge;
+use function array_search;
+use function array_shift;
+use function array_splice;
 use function assert;
 use function end;
 use function explode;
+use function in_array;
 use function preg_match;
 use function preg_match_all;
 use function str_replace;
@@ -43,11 +48,25 @@ final class DocParser
             return null;
         }
 
-        if (! preg_match("/(?<type>.*)\\$$reflection->name(\s|\z)/s", $doc, $matches)) {
-            return null;
+        $parameters = [];
+
+        $tokens = (new TokensExtractor($doc))->all();
+
+        while (($token = array_shift($tokens)) !== null) {
+            if (! in_array($token, ['@param', '@phpstan-param', '@psalm-param'], true)) {
+                continue;
+            }
+
+            $dollarSignKey = (int)array_search('$', $tokens, true);
+            $name = $tokens[$dollarSignKey + 1] ?? null;
+
+            $parameters[$name][$token] = implode('', array_splice($tokens, 0, $dollarSignKey));
         }
 
-        return self::annotationType($matches['type'], 'param');
+        return $parameters[$reflection->name]['@phpstan-param']
+            ?? $parameters[$reflection->name]['@psalm-param']
+            ?? $parameters[$reflection->name]['@param']
+            ?? null;
     }
 
     public static function functionReturnType(ReflectionFunctionAbstract $reflection): ?string


Test patch:
diff --git a/tests/Integration/Mapping/DocBlockParameterWithDescriptionTest.php b/tests/Integration/Mapping/DocBlockParameterWithDescriptionTest.php
new file mode 100644
index 0000000..add0ce3
--- /dev/null
+++ b/tests/Integration/Mapping/DocBlockParameterWithDescriptionTest.php
@@ -0,0 +1,32 @@
+<?php
+
+declare(strict_types=1);
+
+namespace CuyZ\Valinor\Tests\Integration\Mapping;
+
+use CuyZ\Valinor\Tests\Integration\IntegrationTestCase;
+
+final class DocBlockParameterWithDescriptionTest extends IntegrationTestCase
+{
+    public function test_parameter_doc_block_description_containing_name_of_other_parameter_is_parsed_properly(): void
+    {
+        $class = new class ('foo', 42) {
+            /**
+             * @param non-empty-string $foo Some description containing $bar
+             *      which is the next parameter name
+             */
+            public function __construct(
+                public string $foo,
+                public int $bar,
+            ) {}
+        };
+
+        $result = $this->mapperBuilder()->mapper()->map($class::class, [
+            'foo' => 'foo',
+            'bar' => 42,
+        ]);
+
+        self::assertSame('foo', $result->foo);
+        self::assertSame(42, $result->bar);
+    }
+}