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

DocBlockTypeResolver::getDocBlocTypeHint()   C

Complexity

Conditions 13
Paths 18

Size

Total Lines 71
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 13
eloc 38
c 1
b 0
f 0
nc 18
nop 1
dl 0
loc 71
rs 6.6166

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
                    fn (TypeNode $node) => $this->resolveTypeFromTypeNode($node, $reflector),
158
                    $type->genericTypes[$valuesIndex]->types,
159
                );
160
            } else {
161
                $valueType = $this->resolveTypeFromTypeNode($type->genericTypes[$valuesIndex], $reflector);
162
                $valueTypes = [$valueType];
163
            }
164
165
            $valueType = implode('|', $valueTypes);
166
167
            if (null === $keyType) {
168
                return sprintf('array<%s>', $valueType);
169
            }
170
171
            return sprintf('array<%s, %s>', $keyType, $valueType);
172
        }
173
174
        // Primitives and class names: Collection | \Foo\Bar\Product | string
175
        return $this->resolveTypeFromTypeNode($type, $reflector);
176
    }
177
178
    /**
179
     * Returns a flat list of types of the given var tags. Union types are flattened as well.
180
     *
181
     * @param ReturnTagValueNode[]|VarTagValueNode[] $tagValues
182
     *
183
     * @return TypeNode[]
184
     */
185
    private function flattenTagValueTypes(array $tagValues): array
186
    {
187
        if ([] === $tagValues) {
188
            return [];
189
        }
190
191
        return array_merge(...array_map(static function ($node) {
192
            if ($node->type instanceof UnionTypeNode) {
193
                return $node->type->types;
194
            }
195
196
            return [$node->type];
197
        }, $tagValues));
198
    }
199
200
    /**
201
     * Returns a flat list of types of the given param tags. Union types are flattened as well.
202
     *
203
     * @param ParamTagValueNode[] $varTagValues
204
     *
205
     * @return TypeNode[]
206
     */
207
    private function flattenParamTagValueTypes(string $parameterName, array $varTagValues): array
208
    {
209
        if ([] === $varTagValues) {
210
            return [];
211
        }
212
213
        $parameterName = sprintf('$%s', $parameterName);
214
        $types = [];
215
        foreach ($varTagValues as $node) {
216
            if ($parameterName !== $node->parameterName) {
217
                continue;
218
            }
219
220
            $types[] = $node->type;
221
        }
222
223
        return $types;
224
    }
225
226
    /**
227
     * Filters the null type from the given types array. If no null type is found, the array is returned unchanged.
228
     *
229
     * @param TypeNode[] $types
230
     *
231
     * @return TypeNode[]
232
     */
233
    private function filterNullFromTypes(array $types): array
234
    {
235
        return array_values(array_filter(array_map(fn (TypeNode $node) => $this->isNullType($node) ? null : $node, $types)));
236
    }
237
238
    /**
239
     * Determines if the given type is a null type.
240
     *
241
     * @param TypeNode $typeNode
242
     *
243
     * @return bool
244
     */
245
    private function isNullType(TypeNode $typeNode): bool
246
    {
247
        return $this->isSimpleType($typeNode, 'null');
248
    }
249
250
    /**
251
     * Determines if the given node represents a simple type.
252
     *
253
     * @param TypeNode $typeNode
254
     * @param string $simpleType
255
     *
256
     * @return bool
257
     */
258
    private function isSimpleType(TypeNode $typeNode, string $simpleType): bool
259
    {
260
        return $typeNode instanceof IdentifierTypeNode && $typeNode->name === $simpleType;
261
    }
262
263
    /**
264
     * Attempts to resolve the fully qualified type from the given node. If the node is not suitable for type
265
     * retrieval, an exception is thrown.
266
     *
267
     * @param TypeNode $typeNode
268
     * @param \ReflectionMethod|\ReflectionProperty $reflector
269
     *
270
     * @return string
271
     *
272
     * @throws \InvalidArgumentException
273
     */
274
    private function resolveTypeFromTypeNode(TypeNode $typeNode, $reflector): string
275
    {
276
        if (!($typeNode instanceof IdentifierTypeNode)) {
277
            throw new \InvalidArgumentException(sprintf("Can't use unsupported type %s for collection in %s:%s", (string) $typeNode, $reflector->getDeclaringClass()->getName(), $reflector->getName()));
278
        }
279
280
        return $this->resolveType($typeNode->name, $reflector);
281
    }
282
283
    /**
284
     * @param \ReflectionMethod|\ReflectionProperty $reflector
285
     */
286
    private function expandClassNameUsingUseStatements(string $typeHint, \ReflectionClass $declaringClass, $reflector): string
287
    {
288
        $expandedClassName = $declaringClass->getNamespaceName() . '\\' . $typeHint;
289
        if ($this->isClassOrInterface($expandedClassName)) {
290
            return $expandedClassName;
291
        }
292
293
        $classContents = file_get_contents($declaringClass->getFileName());
294
        $foundUseStatements = $this->gatherGroupUseStatements($classContents);
295
        $foundUseStatements = array_merge($this->gatherSingleUseStatements($classContents), $foundUseStatements);
296
297
        foreach ($foundUseStatements as $statementClassName) {
298
            if ($alias = explode('as', $statementClassName)) {
299
                if (array_key_exists(1, $alias) && trim($alias[1]) === $typeHint) {
300
                    return trim($alias[0]);
301
                }
302
            }
303
304
            if ($this->endsWith($statementClassName, $typeHint)) {
305
                return $statementClassName;
306
            }
307
        }
308
309
        if ($declaringClass->getDocComment()) {
310
            $phpstanArrayType = $this->getPhpstanArrayType($declaringClass, $typeHint, $reflector);
311
312
            if ($phpstanArrayType) {
313
                return $phpstanArrayType;
314
            }
315
        }
316
317
        if ($this->isClassOrInterface($typeHint)) {
318
            return $typeHint;
319
        }
320
321
        throw new \InvalidArgumentException(sprintf("Can't use incorrect type %s for collection in %s:%s", $typeHint, $declaringClass->getName(), $reflector->getName()));
322
    }
323
324
    private function endsWith(string $statementClassToCheck, string $typeHintToSearchFor): bool
325
    {
326
        $typeHintToSearchFor = '\\' . $typeHintToSearchFor;
327
328
        return substr($statementClassToCheck, -strlen($typeHintToSearchFor)) === $typeHintToSearchFor;
329
    }
330
331
    private function isPrimitiveType(string $type): bool
332
    {
333
        return in_array($type, ['int', 'integer', 'float', 'bool', 'boolean', 'double', 'string']);
334
    }
335
336
    private function hasGlobalNamespacePrefix(string $typeHint): bool
337
    {
338
        return self::GLOBAL_NAMESPACE_PREFIX === $typeHint[0];
339
    }
340
341
    private function gatherGroupUseStatements(string $classContents): array
342
    {
343
        $foundUseStatements = [];
344
        preg_match_all(self::GROUP_USE_STATEMENTS_REGEX, $classContents, $foundGroupUseStatements);
345
        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...
346
            foreach (explode(',', $foundGroupUseStatements[2][$useStatementIndex]) as $singleUseStatement) {
347
                $foundUseStatements[] = trim($foundGroupUseStatements[1][$useStatementIndex]) . trim($singleUseStatement);
348
            }
349
        }
350
351
        return $foundUseStatements;
352
    }
353
354
    private function gatherSingleUseStatements(string $classContents): array
355
    {
356
        $foundUseStatements = [];
357
        preg_match_all(self::SINGLE_USE_STATEMENTS_REGEX, $classContents, $foundSingleUseStatements);
358
        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...
359
            $foundUseStatements[] = trim($foundSingleUseStatements[1][$useStatementIndex]);
360
        }
361
362
        return $foundUseStatements;
363
    }
364
365
    /**
366
     * @param \ReflectionMethod|\ReflectionProperty $reflector
367
     */
368
    private function getDeclaringClassOrTrait($reflector): \ReflectionClass
369
    {
370
        foreach ($reflector->getDeclaringClass()->getTraits() as $trait) {
371
            foreach ($trait->getProperties() as $traitProperty) {
372
                if ($traitProperty->getName() === $reflector->getName()) {
373
                    return $this->getDeclaringClassOrTrait($traitProperty);
374
                }
375
            }
376
        }
377
378
        return $reflector->getDeclaringClass();
379
    }
380
381
    /**
382
     * @param \ReflectionMethod|\ReflectionProperty $reflector
383
     */
384
    private function resolveType(string $typeHint, $reflector): string
385
    {
386
        if (!$this->hasGlobalNamespacePrefix($typeHint) && !$this->isPrimitiveType($typeHint)) {
387
            $typeHint = $this->expandClassNameUsingUseStatements($typeHint, $this->getDeclaringClassOrTrait($reflector), $reflector);
388
        }
389
390
        return ltrim($typeHint, '\\');
391
    }
392
393
    private function isClassOrInterface(string $typeHint): bool
394
    {
395
        return class_exists($typeHint) || interface_exists($typeHint);
396
    }
397
398
    /**
399
     * @param \ReflectionMethod|\ReflectionProperty $reflector
400
     */
401
    private function resolveTypeFromDocblock($reflector): array
402
    {
403
        $docComment = $reflector->getDocComment();
404
        if (!$docComment && PHP_VERSION_ID >= 80000 && $reflector instanceof \ReflectionProperty && $reflector->isPromoted()) {
405
            $constructor = $reflector->getDeclaringClass()->getConstructor();
406
            if (!$constructor) {
407
                return [];
408
            }
409
410
            $docComment = $constructor->getDocComment();
411
412
            if (!$docComment) {
413
                return [];
414
            }
415
416
            $tokens = $this->lexer->tokenize($docComment);
417
            $phpDocNode = $this->phpDocParser->parse(new TokenIterator($tokens));
418
419
            return $this->flattenParamTagValueTypes($reflector->getName(), $phpDocNode->getParamTagValues());
420
        }
421
422
        if (!$docComment) {
423
            return [];
424
        }
425
426
        // First we tokenize the PhpDoc comment and parse the tokens into a PhpDocNode.
427
        $tokens = $this->lexer->tokenize($docComment);
428
        $phpDocNode = $this->phpDocParser->parse(new TokenIterator($tokens));
429
430
        if ($reflector instanceof \ReflectionProperty) {
431
            // Then we retrieve a flattened list of annotated types excluding null.
432
            $tagValues = $phpDocNode->getVarTagValues();
433
        } else {
434
            // Then we retrieve a flattened list of annotated types including null.
435
            $tagValues = $phpDocNode->getReturnTagValues();
436
        }
437
438
        $types = $this->flattenTagValueTypes($tagValues);
439
440
        return $this->filterNullFromTypes($types);
441
    }
442
443
    /**
444
     * @param \ReflectionMethod|\ReflectionProperty $reflector
445
     */
446
    private function getPhpstanArrayType(\ReflectionClass $declaringClass, string $typeHint, $reflector): ?string
447
    {
448
        $tokens = $this->lexer->tokenize($declaringClass->getDocComment());
449
        $phpDocNode = $this->phpDocParser->parse(new TokenIterator($tokens));
450
        $self = $this;
451
452
        foreach ($phpDocNode->children as $node) {
453
            if (
454
                $node instanceof PhpDocTagNode
455
                && $node->value instanceof TypeAliasTagValueNode
456
                && $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...
457
            ) {
458
                $phpstanType = $node->value->__toString();
459
                preg_match(self::PHPSTAN_ARRAY_SHAPE, $phpstanType, $foundPhpstanArray);
460
                if (isset($foundPhpstanArray[0])) {
461
                    return 'array';
462
                }
463
464
                preg_match(self::PHPSTAN_ARRAY_TYPE, $phpstanType, $foundPhpstanArray);
465
                if (isset($foundPhpstanArray[0])) {
466
                    $types = explode(',', $foundPhpstanArray[2]);
467
468
                    return sprintf('array<%s>', implode(
469
                        ',',
470
                        array_map(static fn (string $type) => $self->resolveType(trim($type), $reflector), $types),
471
                    ));
472
                }
473
            } elseif ($node instanceof PhpDocTagNode && $node->value instanceof TypeAliasImportTagValueNode) {
474
                $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...
475
476
                return $this->getPhpstanArrayType(
477
                    new \ReflectionClass($importedFromFqn),
478
                    $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...
479
                    $reflector,
480
                );
481
            }
482
        }
483
484
        return null;
485
    }
486
}
487