Passed
Pull Request — master (#1552)
by
unknown
04:04
created

DocBlockTypeResolver::getDocBlocTypeHint()   C

Complexity

Conditions 14
Paths 18

Size

Total Lines 79
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 14
eloc 42
c 1
b 0
f 0
nc 18
nop 1
dl 0
loc 79
rs 6.2666

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
use function sprintf;
26
27
/**
28
 * @internal
29
 */
30
final class DocBlockTypeResolver
31
{
32
    /** resolve single use statements */
33
    private const SINGLE_USE_STATEMENTS_REGEX = '/^[^\S\r\n]*use[\s]*([^;\n]*)[\s]*;$/m';
34
35
    /** resolve group use statements */
36
    private const GROUP_USE_STATEMENTS_REGEX = '/^[^\S\r\n]*use[[\s]*([^;\n]*)[\s]*{([a-zA-Z0-9\s\n\r,]*)};$/m';
37
    private const GLOBAL_NAMESPACE_PREFIX = '\\';
38
    private const PHPSTAN_ARRAY_SHAPE = '/^([^\s]*) array{.*/m';
39
    private const PHPSTAN_ARRAY_TYPE = '/^([^\s]*) array<(.*)>/m';
40
41
    /**
42
     * @var PhpDocParser
43
     */
44
    protected $phpDocParser;
45
46
    /**
47
     * @var Lexer
48
     */
49
    protected $lexer;
50
51
    public function __construct()
52
    {
53
        // PHPStan PHPDoc Parser 2
54
        if (class_exists(ParserConfig::class)) {
55
            $config = new ParserConfig(['lines' => true, 'indexes' => true]);
56
57
            $constExprParser = new ConstExprParser($config);
58
            $typeParser = new TypeParser($config, $constExprParser);
59
60
            $this->phpDocParser = new PhpDocParser($config, $typeParser, $constExprParser);
61
            $this->lexer = new Lexer($config);
62
        } else {
63
            // @phpstan-ignore arguments.count
64
            $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

64
            $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...
65
            // @phpstan-ignore arguments.count
66
            $typeParser = new TypeParser($constExprParser);
0 ignored issues
show
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

66
            $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...
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

66
            $typeParser = new TypeParser(/** @scrutinizer ignore-type */ $constExprParser);
Loading history...
67
            // @phpstan-ignore arguments.count
68
            $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

68
            $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

68
            $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

68
            $this->phpDocParser = new PhpDocParser($typeParser, /** @scrutinizer ignore-type */ $constExprParser);
Loading history...
69
            // @phpstan-ignore arguments.count
70
            $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

70
            $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...
71
        }
72
    }
73
74
    /**
75
     * Attempts to retrieve additional type information from a PhpDoc block. Throws in case of ambiguous type
76
     * information and will return null if no helpful type information could be retrieved.
77
     *
78
     * @param \ReflectionMethod $reflectionMethod
79
     *
80
     * @return string|null
81
     */
82
    public function getMethodDocblockTypeHint(\ReflectionMethod $reflectionMethod): ?string
83
    {
84
        return $this->getDocBlocTypeHint($reflectionMethod);
85
    }
86
87
    /**
88
     * Attempts to retrieve additional type information from a PhpDoc block. Throws in case of ambiguous type
89
     * information and will return null if no helpful type information could be retrieved.
90
     *
91
     * @param \ReflectionProperty $reflectionProperty
92
     *
93
     * @return string|null
94
     */
95
    public function getPropertyDocblockTypeHint(\ReflectionProperty $reflectionProperty): ?string
96
    {
97
        return $this->getDocBlocTypeHint($reflectionProperty);
98
    }
99
100
    /**
101
     * @param \ReflectionMethod|\ReflectionProperty $reflector
102
     *
103
     * @return string|null
104
     */
105
    private function getDocBlocTypeHint($reflector): ?string
106
    {
107
        $types = $this->resolveTypeFromDocblock($reflector);
108
109
        // The PhpDoc does not contain additional type information.
110
        if (0 === count($types)) {
111
            return null;
112
        }
113
114
        // The PhpDoc contains multiple non-null types which produces ambiguity when deserializing.
115
        if (count($types) > 1) {
116
            return null;
117
        }
118
119
        // Only one type is left, so we only need to differentiate between arrays, generics and other types.
120
        $type = $types[0];
121
122
        // Simple array without concrete type: array
123
        if ($this->isSimpleType($type, 'array') || $this->isSimpleType($type, 'list')) {
124
            return null;
125
        }
126
127
        // Normal array syntax: Product[] | \Foo\Bar\Product[]
128
        if ($type instanceof ArrayTypeNode) {
129
            $resolvedType = $this->resolveTypeFromTypeNode($type->type, $reflector);
130
131
            return 'array<' . $resolvedType . '>';
132
        }
133
134
        // Generic array syntax: array<Product> | array<\Foo\Bar\Product> | array<int,Product>
135
        if ($type instanceof GenericTypeNode) {
136
            $isSimpleTypeArray = $this->isSimpleType($type->type, 'array');
137
            $isSimpleTypeList = $this->isSimpleType($type->type, 'list');
138
            if (!$isSimpleTypeArray && !$isSimpleTypeList) {
139
                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()));
140
            }
141
142
            if ($isSimpleTypeList) {
143
                $keyType = 'int';
144
                $valuesIndex = 0;
145
            } else {
146
                if (1 === count($type->genericTypes)) {
147
                    $keyType = null;
148
                    $valuesIndex = 0;
149
                } else {
150
                    $keyType = $this->resolveTypeFromTypeNode($type->genericTypes[0], $reflector);
151
                    $valuesIndex = 1;
152
                }
153
            }
154
155
            if ($type->genericTypes[$valuesIndex] instanceof UnionTypeNode) {
156
                $valueTypes = array_map(
157
                    function (TypeNode $node) use ($reflector) {
158
                        if ($node instanceof ArrayTypeNode) {
159
                            $resolvedType = $this->resolveTypeFromTypeNode($node->type, $reflector);
160
161
                            return 'array<' . $resolvedType . '>';
162
                        }
163
164
                        return $this->resolveTypeFromTypeNode($node, $reflector);
165
                    },
166
                    $type->genericTypes[$valuesIndex]->types,
167
                );
168
            } else {
169
                $valueType = $this->resolveTypeFromTypeNode($type->genericTypes[$valuesIndex], $reflector);
170
                $valueTypes = [$valueType];
171
            }
172
173
            $valueType = implode('|', $valueTypes);
174
175
            if (null === $keyType) {
176
                return sprintf('array<%s>', $valueType);
177
            }
178
179
            return sprintf('array<%s, %s>', $keyType, $valueType);
180
        }
181
182
        // Primitives and class names: Collection | \Foo\Bar\Product | string
183
        return $this->resolveTypeFromTypeNode($type, $reflector);
184
    }
185
186
    /**
187
     * Returns a flat list of types of the given var tags. Union types are flattened as well.
188
     *
189
     * @param ReturnTagValueNode[]|VarTagValueNode[] $tagValues
190
     *
191
     * @return TypeNode[]
192
     */
193
    private function flattenTagValueTypes(array $tagValues): array
194
    {
195
        if ([] === $tagValues) {
196
            return [];
197
        }
198
199
        return array_merge(...array_map(static function ($node) {
200
            if ($node->type instanceof UnionTypeNode) {
201
                return $node->type->types;
202
            }
203
204
            return [$node->type];
205
        }, $tagValues));
206
    }
207
208
    /**
209
     * Returns a flat list of types of the given param tags. Union types are flattened as well.
210
     *
211
     * @param ParamTagValueNode[] $varTagValues
212
     *
213
     * @return TypeNode[]
214
     */
215
    private function flattenParamTagValueTypes(string $parameterName, array $varTagValues): array
216
    {
217
        if ([] === $varTagValues) {
218
            return [];
219
        }
220
221
        $parameterName = sprintf('$%s', $parameterName);
222
        $types = [];
223
        foreach ($varTagValues as $node) {
224
            if ($parameterName !== $node->parameterName) {
225
                continue;
226
            }
227
228
            $types[] = $node->type;
229
        }
230
231
        return $types;
232
    }
233
234
    /**
235
     * Filters the null type from the given types array. If no null type is found, the array is returned unchanged.
236
     *
237
     * @param TypeNode[] $types
238
     *
239
     * @return TypeNode[]
240
     */
241
    private function filterNullFromTypes(array $types): array
242
    {
243
        return array_values(array_filter(array_map(fn (TypeNode $node) => $this->isNullType($node) ? null : $node, $types)));
244
    }
245
246
    /**
247
     * Determines if the given type is a null type.
248
     *
249
     * @param TypeNode $typeNode
250
     *
251
     * @return bool
252
     */
253
    private function isNullType(TypeNode $typeNode): bool
254
    {
255
        return $this->isSimpleType($typeNode, 'null');
256
    }
257
258
    /**
259
     * Determines if the given node represents a simple type.
260
     *
261
     * @param TypeNode $typeNode
262
     * @param string $simpleType
263
     *
264
     * @return bool
265
     */
266
    private function isSimpleType(TypeNode $typeNode, string $simpleType): bool
267
    {
268
        return $typeNode instanceof IdentifierTypeNode && $typeNode->name === $simpleType;
269
    }
270
271
    /**
272
     * Attempts to resolve the fully qualified type from the given node. If the node is not suitable for type
273
     * retrieval, an exception is thrown.
274
     *
275
     * @param TypeNode $typeNode
276
     * @param \ReflectionMethod|\ReflectionProperty $reflector
277
     *
278
     * @return string
279
     *
280
     * @throws \InvalidArgumentException
281
     */
282
    private function resolveTypeFromTypeNode(TypeNode $typeNode, $reflector): string
283
    {
284
        if (!($typeNode instanceof IdentifierTypeNode)) {
285
            throw new \InvalidArgumentException(sprintf("Can't use unsupported type %s for collection in %s:%s", (string) $typeNode, $reflector->getDeclaringClass()->getName(), $reflector->getName()));
286
        }
287
288
        return $this->resolveType($typeNode->name, $reflector);
289
    }
290
291
    /**
292
     * @param \ReflectionMethod|\ReflectionProperty $reflector
293
     */
294
    private function expandClassNameUsingUseStatements(string $typeHint, \ReflectionClass $declaringClass, $reflector): string
295
    {
296
        $expandedClassName = $declaringClass->getNamespaceName() . '\\' . $typeHint;
297
        if ($this->isClassOrInterface($expandedClassName)) {
298
            return $expandedClassName;
299
        }
300
301
        $classContents = file_get_contents($declaringClass->getFileName());
302
        $foundUseStatements = $this->gatherGroupUseStatements($classContents);
303
        $foundUseStatements = array_merge($this->gatherSingleUseStatements($classContents), $foundUseStatements);
304
305
        foreach ($foundUseStatements as $statementClassName) {
306
            if ($alias = explode('as', $statementClassName)) {
307
                if (array_key_exists(1, $alias) && trim($alias[1]) === $typeHint) {
308
                    return trim($alias[0]);
309
                }
310
            }
311
312
            if ($this->endsWith($statementClassName, $typeHint)) {
313
                return $statementClassName;
314
            }
315
        }
316
317
        if ($declaringClass->getDocComment()) {
318
            $phpstanArrayType = $this->getPhpstanArrayType($declaringClass, $typeHint, $reflector);
319
320
            if ($phpstanArrayType) {
321
                return $phpstanArrayType;
322
            }
323
        }
324
325
        if ($this->isClassOrInterface($typeHint)) {
326
            return $typeHint;
327
        }
328
329
        throw new \InvalidArgumentException(sprintf("Can't use incorrect type %s for collection in %s:%s", $typeHint, $declaringClass->getName(), $reflector->getName()));
330
    }
331
332
    private function endsWith(string $statementClassToCheck, string $typeHintToSearchFor): bool
333
    {
334
        $typeHintToSearchFor = '\\' . $typeHintToSearchFor;
335
336
        return substr($statementClassToCheck, -strlen($typeHintToSearchFor)) === $typeHintToSearchFor;
337
    }
338
339
    private function isPrimitiveType(string $type): bool
340
    {
341
        return in_array($type, ['int', 'integer', 'float', 'bool', 'boolean', 'double', 'string']);
342
    }
343
344
    private function hasGlobalNamespacePrefix(string $typeHint): bool
345
    {
346
        return self::GLOBAL_NAMESPACE_PREFIX === $typeHint[0];
347
    }
348
349
    private function gatherGroupUseStatements(string $classContents): array
350
    {
351
        $foundUseStatements = [];
352
        preg_match_all(self::GROUP_USE_STATEMENTS_REGEX, $classContents, $foundGroupUseStatements);
353
        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...
354
            foreach (explode(',', $foundGroupUseStatements[2][$useStatementIndex]) as $singleUseStatement) {
355
                $foundUseStatements[] = trim($foundGroupUseStatements[1][$useStatementIndex]) . trim($singleUseStatement);
356
            }
357
        }
358
359
        return $foundUseStatements;
360
    }
361
362
    private function gatherSingleUseStatements(string $classContents): array
363
    {
364
        $foundUseStatements = [];
365
        preg_match_all(self::SINGLE_USE_STATEMENTS_REGEX, $classContents, $foundSingleUseStatements);
366
        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...
367
            $foundUseStatements[] = trim($foundSingleUseStatements[1][$useStatementIndex]);
368
        }
369
370
        return $foundUseStatements;
371
    }
372
373
    /**
374
     * @param \ReflectionMethod|\ReflectionProperty $reflector
375
     */
376
    private function getDeclaringClassOrTrait($reflector): \ReflectionClass
377
    {
378
        foreach ($reflector->getDeclaringClass()->getTraits() as $trait) {
379
            foreach ($trait->getProperties() as $traitProperty) {
380
                if ($traitProperty->getName() === $reflector->getName()) {
381
                    return $this->getDeclaringClassOrTrait($traitProperty);
382
                }
383
            }
384
        }
385
386
        return $reflector->getDeclaringClass();
387
    }
388
389
    /**
390
     * @param \ReflectionMethod|\ReflectionProperty $reflector
391
     */
392
    private function resolveType(string $typeHint, $reflector): string
393
    {
394
        if (!$this->hasGlobalNamespacePrefix($typeHint) && !$this->isPrimitiveType($typeHint)) {
395
            $typeHint = $this->expandClassNameUsingUseStatements($typeHint, $this->getDeclaringClassOrTrait($reflector), $reflector);
396
        }
397
398
        return ltrim($typeHint, '\\');
399
    }
400
401
    private function isClassOrInterface(string $typeHint): bool
402
    {
403
        return class_exists($typeHint) || interface_exists($typeHint);
404
    }
405
406
    /**
407
     * @param \ReflectionMethod|\ReflectionProperty $reflector
408
     */
409
    private function resolveTypeFromDocblock($reflector): array
410
    {
411
        $docComment = $reflector->getDocComment();
412
        if (!$docComment && PHP_VERSION_ID >= 80000 && $reflector instanceof \ReflectionProperty && $reflector->isPromoted()) {
413
            $constructor = $reflector->getDeclaringClass()->getConstructor();
414
            if (!$constructor) {
415
                return [];
416
            }
417
418
            $docComment = $constructor->getDocComment();
419
420
            if (!$docComment) {
421
                return [];
422
            }
423
424
            $tokens = $this->lexer->tokenize($docComment);
425
            $phpDocNode = $this->phpDocParser->parse(new TokenIterator($tokens));
426
427
            return $this->flattenParamTagValueTypes($reflector->getName(), $phpDocNode->getParamTagValues());
428
        }
429
430
        if (!$docComment) {
431
            return [];
432
        }
433
434
        // First we tokenize the PhpDoc comment and parse the tokens into a PhpDocNode.
435
        $tokens = $this->lexer->tokenize($docComment);
436
        $phpDocNode = $this->phpDocParser->parse(new TokenIterator($tokens));
437
438
        if ($reflector instanceof \ReflectionProperty) {
439
            // Then we retrieve a flattened list of annotated types excluding null.
440
            $tagValues = $phpDocNode->getVarTagValues();
441
        } else {
442
            // Then we retrieve a flattened list of annotated types including null.
443
            $tagValues = $phpDocNode->getReturnTagValues();
444
        }
445
446
        $types = $this->flattenTagValueTypes($tagValues);
447
448
        return $this->filterNullFromTypes($types);
449
    }
450
451
    /**
452
     * @param \ReflectionMethod|\ReflectionProperty $reflector
453
     */
454
    private function getPhpstanArrayType(\ReflectionClass $declaringClass, string $typeHint, $reflector): ?string
455
    {
456
        $tokens = $this->lexer->tokenize($declaringClass->getDocComment());
457
        $phpDocNode = $this->phpDocParser->parse(new TokenIterator($tokens));
458
        $self = $this;
459
460
        foreach ($phpDocNode->children as $node) {
461
            if (
462
                $node instanceof PhpDocTagNode
463
                && $node->value instanceof TypeAliasTagValueNode
464
                && $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...
465
            ) {
466
                $phpstanType = $node->value->__toString();
467
                preg_match(self::PHPSTAN_ARRAY_SHAPE, $phpstanType, $foundPhpstanArray);
468
                if (isset($foundPhpstanArray[0])) {
469
                    return 'array';
470
                }
471
472
                preg_match(self::PHPSTAN_ARRAY_TYPE, $phpstanType, $foundPhpstanArray);
473
                if (isset($foundPhpstanArray[0])) {
474
                    $types = explode(',', $foundPhpstanArray[2]);
475
476
                    return sprintf('array<%s>', implode(
477
                        ',',
478
                        array_map(static fn (string $type) => $self->resolveType(trim($type), $reflector), $types),
479
                    ));
480
                }
481
            } elseif ($node instanceof PhpDocTagNode && $node->value instanceof TypeAliasImportTagValueNode) {
482
                $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...
483
484
                return $this->getPhpstanArrayType(
485
                    new \ReflectionClass($importedFromFqn),
486
                    $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...
487
                    $reflector,
488
                );
489
            }
490
        }
491
492
        return null;
493
    }
494
}
495