DocBlockTypeResolver   F
last analyzed

Complexity

Total Complexity 72

Size/Duplication

Total Lines 432
Duplicated Lines 0 %

Importance

Changes 6
Bugs 1 Features 0
Metric Value
eloc 152
c 6
b 1
f 0
dl 0
loc 432
rs 2.64
wmc 72

21 Methods

Rating   Name   Duplication   Size   Complexity  
A resolveType() 0 7 3
B getDocBlocTypeHint() 0 48 9
A hasGlobalNamespacePrefix() 0 3 1
B resolveTypeFromDocblock() 0 40 9
B getPhpstanArrayType() 0 39 9
A resolveTypeFromTypeNode() 0 7 2
A isPrimitiveType() 0 3 1
A flattenParamTagValueTypes() 0 17 4
A filterNullFromTypes() 0 3 2
A getMethodDocblockTypeHint() 0 3 1
A getPropertyDocblockTypeHint() 0 3 1
A isClassOrInterface() 0 3 2
A isSimpleType() 0 3 2
A endsWith() 0 5 1
A getDeclaringClassOrTrait() 0 11 4
A isNullType() 0 3 1
A gatherGroupUseStatements() 0 11 3
B expandClassNameUsingUseStatements() 0 36 10
A gatherSingleUseStatements() 0 9 2
A __construct() 0 20 2
A flattenTagValueTypes() 0 13 3

How to fix   Complexity   

Complex Class

Complex classes like DocBlockTypeResolver often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DocBlockTypeResolver, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace JMS\Serializer\Metadata\Driver\DocBlockDriver;
6
7
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
8
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
9
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
10
use PHPStan\PhpDocParser\Ast\PhpDoc\TypeAliasImportTagValueNode;
11
use PHPStan\PhpDocParser\Ast\PhpDoc\TypeAliasTagValueNode;
12
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
13
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
14
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
15
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
16
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
17
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
18
use PHPStan\PhpDocParser\Lexer\Lexer;
19
use PHPStan\PhpDocParser\Parser\ConstExprParser;
20
use PHPStan\PhpDocParser\Parser\PhpDocParser;
21
use PHPStan\PhpDocParser\Parser\TokenIterator;
22
use PHPStan\PhpDocParser\Parser\TypeParser;
23
use PHPStan\PhpDocParser\ParserConfig;
24
25
/**
26
 * @internal
27
 */
28
final class DocBlockTypeResolver
29
{
30
    /** resolve single use statements */
31
    private const SINGLE_USE_STATEMENTS_REGEX = '/^[^\S\r\n]*use[\s]*([^;\n]*)[\s]*;$/m';
32
33
    /** resolve group use statements */
34
    private const GROUP_USE_STATEMENTS_REGEX = '/^[^\S\r\n]*use[[\s]*([^;\n]*)[\s]*{([a-zA-Z0-9\s\n\r,]*)};$/m';
35
    private const GLOBAL_NAMESPACE_PREFIX = '\\';
36
    private const PHPSTAN_ARRAY_SHAPE = '/^([^\s]*) array{.*/m';
37
    private const PHPSTAN_ARRAY_TYPE = '/^([^\s]*) array<(.*)>/m';
38
39
    /**
40
     * @var PhpDocParser
41
     */
42
    protected $phpDocParser;
43
44
    /**
45
     * @var Lexer
46
     */
47
    protected $lexer;
48
49
    public function __construct()
50
    {
51
        // PHPStan PHPDoc Parser 2
52
        if (class_exists(ParserConfig::class)) {
53
            $config = new ParserConfig(['lines' => true, 'indexes' => true]);
54
55
            $constExprParser = new ConstExprParser($config);
56
            $typeParser = new TypeParser($config, $constExprParser);
57
58
            $this->phpDocParser = new PhpDocParser($config, $typeParser, $constExprParser);
59
            $this->lexer = new Lexer($config);
60
        } else {
61
            // @phpstan-ignore arguments.count
62
            $constExprParser = new ConstExprParser();
0 ignored issues
show
Bug introduced by
The call to PHPStan\PhpDocParser\Par...prParser::__construct() has too few arguments starting with config. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

62
            $constExprParser = /** @scrutinizer ignore-call */ new ConstExprParser();

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
63
            // @phpstan-ignore arguments.count
64
            $typeParser = new TypeParser($constExprParser);
0 ignored issues
show
Bug introduced by
$constExprParser of type PHPStan\PhpDocParser\Parser\ConstExprParser is incompatible with the type PHPStan\PhpDocParser\ParserConfig expected by parameter $config of PHPStan\PhpDocParser\Par...peParser::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

64
            $typeParser = new TypeParser(/** @scrutinizer ignore-type */ $constExprParser);
Loading history...
Bug introduced by
The call to PHPStan\PhpDocParser\Par...peParser::__construct() has too few arguments starting with constExprParser. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

64
            $typeParser = /** @scrutinizer ignore-call */ new TypeParser($constExprParser);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
65
            // @phpstan-ignore arguments.count
66
            $this->phpDocParser = new PhpDocParser($typeParser, $constExprParser);
0 ignored issues
show
Bug introduced by
$typeParser of type PHPStan\PhpDocParser\Parser\TypeParser is incompatible with the type PHPStan\PhpDocParser\ParserConfig expected by parameter $config of PHPStan\PhpDocParser\Par...ocParser::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

66
            $this->phpDocParser = new PhpDocParser(/** @scrutinizer ignore-type */ $typeParser, $constExprParser);
Loading history...
Bug introduced by
The call to PHPStan\PhpDocParser\Par...ocParser::__construct() has too few arguments starting with constantExprParser. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

66
            $this->phpDocParser = /** @scrutinizer ignore-call */ new PhpDocParser($typeParser, $constExprParser);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
Bug introduced by
$constExprParser of type PHPStan\PhpDocParser\Parser\ConstExprParser is incompatible with the type PHPStan\PhpDocParser\Parser\TypeParser expected by parameter $typeParser of PHPStan\PhpDocParser\Par...ocParser::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

66
            $this->phpDocParser = new PhpDocParser($typeParser, /** @scrutinizer ignore-type */ $constExprParser);
Loading history...
67
            // @phpstan-ignore arguments.count
68
            $this->lexer = new Lexer();
0 ignored issues
show
Bug introduced by
The call to PHPStan\PhpDocParser\Lexer\Lexer::__construct() has too few arguments starting with config. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

68
            $this->lexer = /** @scrutinizer ignore-call */ new Lexer();

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
69
        }
70
    }
71
72
    /**
73
     * Attempts to retrieve additional type information from a PhpDoc block. Throws in case of ambiguous type
74
     * information and will return null if no helpful type information could be retrieved.
75
     *
76
     * @param \ReflectionMethod $reflectionMethod
77
     *
78
     * @return string|null
79
     */
80
    public function getMethodDocblockTypeHint(\ReflectionMethod $reflectionMethod): ?string
81
    {
82
        return $this->getDocBlocTypeHint($reflectionMethod);
83
    }
84
85
    /**
86
     * Attempts to retrieve additional type information from a PhpDoc block. Throws in case of ambiguous type
87
     * information and will return null if no helpful type information could be retrieved.
88
     *
89
     * @param \ReflectionProperty $reflectionProperty
90
     *
91
     * @return string|null
92
     */
93
    public function getPropertyDocblockTypeHint(\ReflectionProperty $reflectionProperty): ?string
94
    {
95
        return $this->getDocBlocTypeHint($reflectionProperty);
96
    }
97
98
    /**
99
     * @param \ReflectionMethod|\ReflectionProperty $reflector
100
     *
101
     * @return string|null
102
     */
103
    private function getDocBlocTypeHint($reflector): ?string
104
    {
105
        $types = $this->resolveTypeFromDocblock($reflector);
106
107
        // The PhpDoc does not contain additional type information.
108
        if (0 === count($types)) {
109
            return null;
110
        }
111
112
        // The PhpDoc contains multiple non-null types which produces ambiguity when deserializing.
113
        if (count($types) > 1) {
114
            return null;
115
        }
116
117
        // Only one type is left, so we only need to differentiate between arrays, generics and other types.
118
        $type = $types[0];
119
120
        // Simple array without concrete type: array
121
        if ($this->isSimpleType($type, 'array') || $this->isSimpleType($type, 'list')) {
122
            return null;
123
        }
124
125
        // Normal array syntax: Product[] | \Foo\Bar\Product[]
126
        if ($type instanceof ArrayTypeNode) {
127
            $resolvedType = $this->resolveTypeFromTypeNode($type->type, $reflector);
128
129
            return 'array<' . $resolvedType . '>';
130
        }
131
132
        // Generic array syntax: array<Product> | array<\Foo\Bar\Product> | array<int,Product>
133
        if ($type instanceof GenericTypeNode) {
134
            if ($this->isSimpleType($type->type, 'array')) {
135
                $resolvedTypes = array_map(fn (TypeNode $node) => $this->resolveTypeFromTypeNode($node, $reflector), $type->genericTypes);
136
137
                return 'array<' . implode(',', $resolvedTypes) . '>';
138
            }
139
140
            if ($this->isSimpleType($type->type, 'list')) {
141
                $resolvedTypes = array_map(fn (TypeNode $node) => $this->resolveTypeFromTypeNode($node, $reflector), $type->genericTypes);
142
143
                return 'list<' . implode(',', $resolvedTypes) . '>';
144
            }
145
146
            throw new \InvalidArgumentException(sprintf("Can't use non-array generic type %s for collection in %s:%s", (string) $type->type, $reflector->getDeclaringClass()->getName(), $reflector->getName()));
147
        }
148
149
        // Primitives and class names: Collection | \Foo\Bar\Product | string
150
        return $this->resolveTypeFromTypeNode($type, $reflector);
151
    }
152
153
    /**
154
     * Returns a flat list of types of the given var tags. Union types are flattened as well.
155
     *
156
     * @param ReturnTagValueNode[]|VarTagValueNode[] $tagValues
157
     *
158
     * @return TypeNode[]
159
     */
160
    private function flattenTagValueTypes(array $tagValues): array
161
    {
162
        if ([] === $tagValues) {
163
            return [];
164
        }
165
166
        return array_merge(...array_map(static function ($node) {
167
            if ($node->type instanceof UnionTypeNode) {
168
                return $node->type->types;
169
            }
170
171
            return [$node->type];
172
        }, $tagValues));
173
    }
174
175
    /**
176
     * Returns a flat list of types of the given param tags. Union types are flattened as well.
177
     *
178
     * @param ParamTagValueNode[] $varTagValues
179
     *
180
     * @return TypeNode[]
181
     */
182
    private function flattenParamTagValueTypes(string $parameterName, array $varTagValues): array
183
    {
184
        if ([] === $varTagValues) {
185
            return [];
186
        }
187
188
        $parameterName = sprintf('$%s', $parameterName);
189
        $types = [];
190
        foreach ($varTagValues as $node) {
191
            if ($parameterName !== $node->parameterName) {
192
                continue;
193
            }
194
195
            $types[] = $node->type;
196
        }
197
198
        return $types;
199
    }
200
201
    /**
202
     * Filters the null type from the given types array. If no null type is found, the array is returned unchanged.
203
     *
204
     * @param TypeNode[] $types
205
     *
206
     * @return TypeNode[]
207
     */
208
    private function filterNullFromTypes(array $types): array
209
    {
210
        return array_values(array_filter(array_map(fn (TypeNode $node) => $this->isNullType($node) ? null : $node, $types)));
211
    }
212
213
    /**
214
     * Determines if the given type is a null type.
215
     *
216
     * @param TypeNode $typeNode
217
     *
218
     * @return bool
219
     */
220
    private function isNullType(TypeNode $typeNode): bool
221
    {
222
        return $this->isSimpleType($typeNode, 'null');
223
    }
224
225
    /**
226
     * Determines if the given node represents a simple type.
227
     *
228
     * @param TypeNode $typeNode
229
     * @param string $simpleType
230
     *
231
     * @return bool
232
     */
233
    private function isSimpleType(TypeNode $typeNode, string $simpleType): bool
234
    {
235
        return $typeNode instanceof IdentifierTypeNode && $typeNode->name === $simpleType;
236
    }
237
238
    /**
239
     * Attempts to resolve the fully qualified type from the given node. If the node is not suitable for type
240
     * retrieval, an exception is thrown.
241
     *
242
     * @param TypeNode $typeNode
243
     * @param \ReflectionMethod|\ReflectionProperty $reflector
244
     *
245
     * @return string
246
     *
247
     * @throws \InvalidArgumentException
248
     */
249
    private function resolveTypeFromTypeNode(TypeNode $typeNode, $reflector): string
250
    {
251
        if (!($typeNode instanceof IdentifierTypeNode)) {
252
            throw new \InvalidArgumentException(sprintf("Can't use unsupported type %s for collection in %s:%s", (string) $typeNode, $reflector->getDeclaringClass()->getName(), $reflector->getName()));
253
        }
254
255
        return $this->resolveType($typeNode->name, $reflector);
256
    }
257
258
    /**
259
     * @param \ReflectionMethod|\ReflectionProperty $reflector
260
     */
261
    private function expandClassNameUsingUseStatements(string $typeHint, \ReflectionClass $declaringClass, $reflector): string
262
    {
263
        $expandedClassName = $declaringClass->getNamespaceName() . '\\' . $typeHint;
264
        if ($this->isClassOrInterface($expandedClassName)) {
265
            return $expandedClassName;
266
        }
267
268
        $classContents = file_get_contents($declaringClass->getFileName());
269
        $foundUseStatements = $this->gatherGroupUseStatements($classContents);
270
        $foundUseStatements = array_merge($this->gatherSingleUseStatements($classContents), $foundUseStatements);
271
272
        foreach ($foundUseStatements as $statementClassName) {
273
            if ($alias = explode('as', $statementClassName)) {
274
                if (array_key_exists(1, $alias) && trim($alias[1]) === $typeHint) {
275
                    return trim($alias[0]);
276
                }
277
            }
278
279
            if ($this->endsWith($statementClassName, $typeHint)) {
280
                return $statementClassName;
281
            }
282
        }
283
284
        if ($declaringClass->getDocComment()) {
285
            $phpstanArrayType = $this->getPhpstanArrayType($declaringClass, $typeHint, $reflector);
286
287
            if ($phpstanArrayType) {
288
                return $phpstanArrayType;
289
            }
290
        }
291
292
        if ($this->isClassOrInterface($typeHint)) {
293
            return $typeHint;
294
        }
295
296
        throw new \InvalidArgumentException(sprintf("Can't use incorrect type %s for collection in %s:%s", $typeHint, $declaringClass->getName(), $reflector->getName()));
297
    }
298
299
    private function endsWith(string $statementClassToCheck, string $typeHintToSearchFor): bool
300
    {
301
        $typeHintToSearchFor = '\\' . $typeHintToSearchFor;
302
303
        return substr($statementClassToCheck, -strlen($typeHintToSearchFor)) === $typeHintToSearchFor;
304
    }
305
306
    private function isPrimitiveType(string $type): bool
307
    {
308
        return in_array($type, ['int', 'integer', 'float', 'bool', 'boolean', 'double', 'string']);
309
    }
310
311
    private function hasGlobalNamespacePrefix(string $typeHint): bool
312
    {
313
        return self::GLOBAL_NAMESPACE_PREFIX === $typeHint[0];
314
    }
315
316
    private function gatherGroupUseStatements(string $classContents): array
317
    {
318
        $foundUseStatements = [];
319
        preg_match_all(self::GROUP_USE_STATEMENTS_REGEX, $classContents, $foundGroupUseStatements);
320
        for ($useStatementIndex = 0; $useStatementIndex < count($foundGroupUseStatements[0]); $useStatementIndex++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
321
            foreach (explode(',', $foundGroupUseStatements[2][$useStatementIndex]) as $singleUseStatement) {
322
                $foundUseStatements[] = trim($foundGroupUseStatements[1][$useStatementIndex]) . trim($singleUseStatement);
323
            }
324
        }
325
326
        return $foundUseStatements;
327
    }
328
329
    private function gatherSingleUseStatements(string $classContents): array
330
    {
331
        $foundUseStatements = [];
332
        preg_match_all(self::SINGLE_USE_STATEMENTS_REGEX, $classContents, $foundSingleUseStatements);
333
        for ($useStatementIndex = 0; $useStatementIndex < count($foundSingleUseStatements[0]); $useStatementIndex++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
334
            $foundUseStatements[] = trim($foundSingleUseStatements[1][$useStatementIndex]);
335
        }
336
337
        return $foundUseStatements;
338
    }
339
340
    /**
341
     * @param \ReflectionMethod|\ReflectionProperty $reflector
342
     */
343
    private function getDeclaringClassOrTrait($reflector): \ReflectionClass
344
    {
345
        foreach ($reflector->getDeclaringClass()->getTraits() as $trait) {
346
            foreach ($trait->getProperties() as $traitProperty) {
347
                if ($traitProperty->getName() === $reflector->getName()) {
348
                    return $this->getDeclaringClassOrTrait($traitProperty);
349
                }
350
            }
351
        }
352
353
        return $reflector->getDeclaringClass();
354
    }
355
356
    /**
357
     * @param \ReflectionMethod|\ReflectionProperty $reflector
358
     */
359
    private function resolveType(string $typeHint, $reflector): string
360
    {
361
        if (!$this->hasGlobalNamespacePrefix($typeHint) && !$this->isPrimitiveType($typeHint)) {
362
            $typeHint = $this->expandClassNameUsingUseStatements($typeHint, $this->getDeclaringClassOrTrait($reflector), $reflector);
363
        }
364
365
        return ltrim($typeHint, '\\');
366
    }
367
368
    private function isClassOrInterface(string $typeHint): bool
369
    {
370
        return class_exists($typeHint) || interface_exists($typeHint);
371
    }
372
373
    /**
374
     * @param \ReflectionMethod|\ReflectionProperty $reflector
375
     */
376
    private function resolveTypeFromDocblock($reflector): array
377
    {
378
        $docComment = $reflector->getDocComment();
379
        if (!$docComment && PHP_VERSION_ID >= 80000 && $reflector instanceof \ReflectionProperty && $reflector->isPromoted()) {
380
            $constructor = $reflector->getDeclaringClass()->getConstructor();
381
            if (!$constructor) {
382
                return [];
383
            }
384
385
            $docComment = $constructor->getDocComment();
386
387
            if (!$docComment) {
388
                return [];
389
            }
390
391
            $tokens = $this->lexer->tokenize($docComment);
392
            $phpDocNode = $this->phpDocParser->parse(new TokenIterator($tokens));
393
394
            return $this->flattenParamTagValueTypes($reflector->getName(), $phpDocNode->getParamTagValues());
395
        }
396
397
        if (!$docComment) {
398
            return [];
399
        }
400
401
        // First we tokenize the PhpDoc comment and parse the tokens into a PhpDocNode.
402
        $tokens = $this->lexer->tokenize($docComment);
403
        $phpDocNode = $this->phpDocParser->parse(new TokenIterator($tokens));
404
405
        if ($reflector instanceof \ReflectionProperty) {
406
            // Then we retrieve a flattened list of annotated types excluding null.
407
            $tagValues = $phpDocNode->getVarTagValues();
408
        } else {
409
            // Then we retrieve a flattened list of annotated types including null.
410
            $tagValues = $phpDocNode->getReturnTagValues();
411
        }
412
413
        $types = $this->flattenTagValueTypes($tagValues);
414
415
        return $this->filterNullFromTypes($types);
416
    }
417
418
    /**
419
     * @param \ReflectionMethod|\ReflectionProperty $reflector
420
     */
421
    private function getPhpstanArrayType(\ReflectionClass $declaringClass, string $typeHint, $reflector): ?string
422
    {
423
        $tokens = $this->lexer->tokenize($declaringClass->getDocComment());
424
        $phpDocNode = $this->phpDocParser->parse(new TokenIterator($tokens));
425
        $self = $this;
426
427
        foreach ($phpDocNode->children as $node) {
428
            if (
429
                $node instanceof PhpDocTagNode
430
                && $node->value instanceof TypeAliasTagValueNode
431
                && $node->value->alias === $typeHint
0 ignored issues
show
Bug introduced by
Accessing alias on the interface PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
432
            ) {
433
                $phpstanType = $node->value->__toString();
434
                preg_match(self::PHPSTAN_ARRAY_SHAPE, $phpstanType, $foundPhpstanArray);
435
                if (isset($foundPhpstanArray[0])) {
436
                    return 'array';
437
                }
438
439
                preg_match(self::PHPSTAN_ARRAY_TYPE, $phpstanType, $foundPhpstanArray);
440
                if (isset($foundPhpstanArray[0])) {
441
                    $types = explode(',', $foundPhpstanArray[2]);
442
443
                    return sprintf('array<%s>', implode(
444
                        ',',
445
                        array_map(static fn (string $type) => $self->resolveType(trim($type), $reflector), $types),
446
                    ));
447
                }
448
            } elseif ($node instanceof PhpDocTagNode && $node->value instanceof TypeAliasImportTagValueNode) {
449
                $importedFromFqn = $this->resolveType($node->value->importedFrom->name, $reflector);
0 ignored issues
show
Bug introduced by
Accessing importedFrom on the interface PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
450
451
                return $this->getPhpstanArrayType(
452
                    new \ReflectionClass($importedFromFqn),
453
                    $node->value->importedAlias,
0 ignored issues
show
Bug introduced by
Accessing importedAlias on the interface PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
454
                    $reflector,
455
                );
456
            }
457
        }
458
459
        return null;
460
    }
461
}
462