TypeParser::getTypeFromTree()   F
last analyzed

Complexity

Conditions 139
Paths 1510

Size

Total Lines 775

Duplication

Lines 78
Ratio 10.06 %

Importance

Changes 0
Metric Value
cc 139
nc 1510
nop 5
dl 78
loc 775
rs 0
c 0
b 0
f 0

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
namespace Psalm\Internal\Type;
3
4
use function array_keys;
5
use function array_map;
6
use function array_shift;
7
use function array_unshift;
8
use function array_values;
9
use function count;
10
use function explode;
11
use function get_class;
12
use function in_array;
13
use function preg_match;
14
use function preg_replace;
15
use Psalm\Codebase;
16
use Psalm\Exception\TypeParseTreeException;
17
use Psalm\Internal\Analyzer\ProjectAnalyzer;
18
use Psalm\Storage\FunctionLikeParameter;
19
use Psalm\Type\Atomic;
20
use Psalm\Type\Atomic\ObjectLike;
21
use Psalm\Type\Atomic\TArray;
22
use Psalm\Type\Atomic\TArrayKey;
23
use Psalm\Type\Atomic\TCallable;
24
use Psalm\Type\Atomic\TClassString;
25
use Psalm\Type\Atomic\TClassStringMap;
26
use Psalm\Type\Atomic\TFn;
27
use Psalm\Type\Atomic\TGenericObject;
28
use Psalm\Type\Atomic\TIterable;
29
use Psalm\Type\Atomic\TList;
30
use Psalm\Type\Atomic\TLiteralFloat;
31
use Psalm\Type\Atomic\TLiteralInt;
32
use Psalm\Type\Atomic\TLiteralString;
33
use Psalm\Type\Atomic\TMixed;
34
use Psalm\Type\Atomic\TNamedObject;
35
use Psalm\Type\Atomic\TNonEmptyArray;
36
use Psalm\Type\Atomic\TNonEmptyList;
37
use Psalm\Type\Atomic\TNull;
38
use Psalm\Type\Atomic\TObject;
39
use Psalm\Type\Atomic\TObjectWithProperties;
40
use Psalm\Type\Atomic\TTemplateParam;
41
use Psalm\Type\Atomic\TTypeAlias;
42
use Psalm\Type\Union;
43
use function array_key_exists;
44
use function strlen;
45
use function strpos;
46
use function strtolower;
47
use function substr;
48
49
class TypeParser
50
{
51
    /**
52
     * Parses a string type representation
53
     *
54
     * @param  list<array{0: string, 1: int}> $type_tokens
55
     * @param  array{int,int}|null   $php_version
0 ignored issues
show
Documentation introduced by
The doc-type array{int,int}|null could not be parsed: Unknown type name "array{int" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
56
     * @param  array<string, array<string, array{Union}>> $template_type_map
57
     * @param  array<string, TypeAlias> $type_aliases
58
     *
59
     * @return Union
60
     */
61
    public static function parseTokens(
62
        array $type_tokens,
63
        array $php_version = null,
64
        array $template_type_map = [],
65
        array $type_aliases = []
66
    ) {
67
        if (count($type_tokens) === 1) {
68
            $only_token = $type_tokens[0];
69
70
            // Note: valid identifiers can include class names or $this
71
            if (!preg_match('@^(\$this|\\\\?[a-zA-Z_\x7f-\xff][\\\\\-0-9a-zA-Z_\x7f-\xff]*)$@', $only_token[0])) {
72
                if (!\is_numeric($only_token[0])
73
                    && strpos($only_token[0], '\'') !== false
74
                    && strpos($only_token[0], '"') !== false
75
                ) {
76
                    throw new TypeParseTreeException("Invalid type '$only_token[0]'");
77
                }
78
            } else {
79
                $only_token[0] = TypeTokenizer::fixScalarTerms($only_token[0], $php_version);
80
81
                $atomic = Atomic::create($only_token[0], $php_version, $template_type_map, $type_aliases);
82
                $atomic->offset_start = 0;
83
                $atomic->offset_end = strlen($only_token[0]);
84
85
                return new Union([$atomic]);
86
            }
87
        }
88
89
        $parse_tree = (new ParseTreeCreator($type_tokens))->create();
90
        $codebase = ProjectAnalyzer::getInstance()->getCodebase();
91
        $parsed_type = self::getTypeFromTree(
92
            $parse_tree,
93
            $codebase,
94
            $php_version,
95
            $template_type_map,
96
            $type_aliases
97
        );
98
99
        if (!($parsed_type instanceof Union)) {
100
            $parsed_type = new Union([$parsed_type]);
101
        }
102
103
        return $parsed_type;
104
    }
105
106
    /**
107
     * @param  ParseTree $parse_tree
108
     * @param  array{int,int}|null   $php_version
0 ignored issues
show
Documentation introduced by
The doc-type array{int,int}|null could not be parsed: Unknown type name "array{int" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
109
     * @param  array<string, array<string, array{Union}>> $template_type_map
110
     * @param  array<string, TypeAlias> $type_aliases
111
     *
112
     * @return  Atomic|Union
113
     */
114
    public static function getTypeFromTree(
115
        ParseTree $parse_tree,
116
        Codebase $codebase,
117
        array $php_version = null,
118
        array $template_type_map = [],
119
        array $type_aliases = []
120
    ) {
121
        if ($parse_tree instanceof ParseTree\GenericTree) {
122
            $generic_type = $parse_tree->value;
123
124
            $generic_params = [];
125
126
            foreach ($parse_tree->children as $i => $child_tree) {
127
                $tree_type = self::getTypeFromTree(
128
                    $child_tree,
129
                    $codebase,
130
                    null,
131
                    $template_type_map,
132
                    $type_aliases
133
                );
134
135
                if ($generic_type === 'class-string-map'
136
                    && $i === 0
137
                ) {
138
                    if ($tree_type instanceof TTemplateParam) {
139
                        $template_type_map[$tree_type->param_name] = ['class-string-map' => [$tree_type->as]];
140
                    } elseif ($tree_type instanceof TNamedObject) {
141
                        $template_type_map[$tree_type->value] = ['class-string-map' => [\Psalm\Type::getObject()]];
142
                    }
143
                }
144
145
                $generic_params[] = $tree_type instanceof Union ? $tree_type : new Union([$tree_type]);
146
            }
147
148
            $generic_type_value = TypeTokenizer::fixScalarTerms($generic_type);
149
150
            if (($generic_type_value === 'array'
151
                    || $generic_type_value === 'non-empty-array'
152
                    || $generic_type_value === 'associative-array')
153
                && count($generic_params) === 1
154
            ) {
155
                array_unshift($generic_params, new Union([new TArrayKey]));
156
            } elseif (in_array($generic_type_value, ['iterable', 'Traversable', 'Iterator', 'IteratorAggregate'], true)
157
                && count($generic_params) === 1
158
            ) {
159
                array_unshift($generic_params, new Union([new TMixed]));
160
            } elseif ($generic_type_value === 'Generator') {
161
                if (count($generic_params) === 1) {
162
                    array_unshift($generic_params, new Union([new TMixed]));
163
                }
164
165
                for ($i = 0, $l = 4 - count($generic_params); $i < $l; ++$i) {
166
                    $generic_params[] = new Union([new TMixed]);
167
                }
168
            }
169
170
            if (!$generic_params) {
171
                throw new TypeParseTreeException('No generic params provided for type');
172
            }
173
174
            if ($generic_type_value === 'array' || $generic_type_value === 'associative-array') {
175
                if ($generic_params[0]->isMixed()) {
176
                    $generic_params[0] = \Psalm\Type::getArrayKey();
177
                }
178
179
                return new TArray($generic_params);
180
            }
181
182
            if ($generic_type_value === 'non-empty-array') {
183
                if ($generic_params[0]->isMixed()) {
184
                    $generic_params[0] = \Psalm\Type::getArrayKey();
185
                }
186
187
                return new TNonEmptyArray($generic_params);
188
            }
189
190
            if ($generic_type_value === 'iterable') {
191
                return new TIterable($generic_params);
192
            }
193
194
            if ($generic_type_value === 'list') {
195
                return new TList($generic_params[0]);
196
            }
197
198
            if ($generic_type_value === 'non-empty-list') {
199
                return new TNonEmptyList($generic_params[0]);
200
            }
201
202
            if ($generic_type_value === 'class-string') {
203
                $class_name = (string) $generic_params[0];
204
205 View Code Duplication
                if (isset($template_type_map[$class_name])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
206
                    $first_class = array_keys($template_type_map[$class_name])[0];
207
208
                    return self::getGenericParamClass(
209
                        $class_name,
210
                        $template_type_map[$class_name][$first_class][0],
211
                        $first_class
212
                    );
213
                }
214
215
                $param_union_types = array_values($generic_params[0]->getAtomicTypes());
216
217
                if (count($param_union_types) > 1) {
218
                    throw new TypeParseTreeException('Union types are not allowed in class string param');
219
                }
220
221
                if (!$param_union_types[0] instanceof TNamedObject) {
222
                    throw new TypeParseTreeException('Class string param should be a named object');
223
                }
224
225
                return new TClassString($class_name, $param_union_types[0]);
226
            }
227
228
            if ($generic_type_value === 'class-string-map') {
229
                if (count($generic_params) !== 2) {
230
                    throw new TypeParseTreeException(
231
                        'There should only be two params for class-string-map, '
232
                            . count($generic_params) . ' provided'
233
                    );
234
                }
235
236
                $template_marker_parts = array_values($generic_params[0]->getAtomicTypes());
237
238
                $template_marker = $template_marker_parts[0];
239
240
                $template_as_type = null;
241
242
                if ($template_marker instanceof TNamedObject) {
243
                    $template_param_name = $template_marker->value;
244
                } elseif ($template_marker instanceof Atomic\TTemplateParam) {
245
                    $template_param_name = $template_marker->param_name;
246
                    $template_as_type = array_values($template_marker->as->getAtomicTypes())[0];
247
248
                    if (!$template_as_type instanceof TNamedObject) {
249
                        throw new TypeParseTreeException(
250
                            'Unrecognised as type'
251
                        );
252
                    }
253
                } else {
254
                    throw new TypeParseTreeException(
255
                        'Unrecognised class-string-map templated param'
256
                    );
257
                }
258
259
                return new TClassStringMap(
260
                    $template_param_name,
261
                    $template_as_type,
262
                    $generic_params[1]
263
                );
264
            }
265
266 View Code Duplication
            if ($generic_type_value === 'key-of') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
267
                $param_name = (string) $generic_params[0];
268
269
                if (isset($template_type_map[$param_name])) {
270
                    $defining_class = array_keys($template_type_map[$param_name])[0];
271
272
                    return new Atomic\TTemplateKeyOf(
273
                        $param_name,
274
                        $defining_class,
275
                        $template_type_map[$param_name][$defining_class][0]
276
                    );
277
                }
278
279
                $param_union_types = array_values($generic_params[0]->getAtomicTypes());
280
281
                if (count($param_union_types) > 1) {
282
                    throw new TypeParseTreeException('Union types are not allowed in key-of type');
283
                }
284
285
                if (!$param_union_types[0] instanceof Atomic\TScalarClassConstant) {
286
                    throw new TypeParseTreeException(
287
                        'Untemplated key-of param ' . $param_name . ' should be a class constant'
288
                    );
289
                }
290
291
                return new Atomic\TKeyOfClassConstant(
292
                    $param_union_types[0]->fq_classlike_name,
293
                    $param_union_types[0]->const_name
294
                );
295
            }
296
297 View Code Duplication
            if ($generic_type_value === 'value-of') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
298
                $param_name = (string) $generic_params[0];
299
300
                if (isset($template_type_map[$param_name])) {
301
                    $defining_class = array_keys($template_type_map[$param_name])[0];
302
303
                    return new Atomic\TTemplateKeyOf(
304
                        $param_name,
305
                        $defining_class,
306
                        $template_type_map[$param_name][$defining_class][0]
307
                    );
308
                }
309
310
                $param_union_types = array_values($generic_params[0]->getAtomicTypes());
311
312
                if (count($param_union_types) > 1) {
313
                    throw new TypeParseTreeException('Union types are not allowed in value-of type');
314
                }
315
316
                if (!$param_union_types[0] instanceof Atomic\TScalarClassConstant) {
317
                    throw new TypeParseTreeException(
318
                        'Untemplated value-of param ' . $param_name . ' should be a class constant'
319
                    );
320
                }
321
322
                return new Atomic\TValueOfClassConstant(
323
                    $param_union_types[0]->fq_classlike_name,
324
                    $param_union_types[0]->const_name
325
                );
326
            }
327
328
            if (isset(TypeTokenizer::PSALM_RESERVED_WORDS[$generic_type_value])
329
                && $generic_type_value !== 'self'
330
                && $generic_type_value !== 'static'
331
            ) {
332
                throw new TypeParseTreeException('Cannot create generic object with reserved word');
333
            }
334
335
            return new TGenericObject($generic_type_value, $generic_params);
336
        }
337
338
        if ($parse_tree instanceof ParseTree\UnionTree) {
339
            $has_null = false;
340
341
            $atomic_types = [];
342
343
            foreach ($parse_tree->children as $child_tree) {
344
                if ($child_tree instanceof ParseTree\NullableTree) {
345
                    if (!isset($child_tree->children[0])) {
346
                        throw new TypeParseTreeException('Invalid ? character');
347
                    }
348
349
                    $atomic_type = self::getTypeFromTree(
350
                        $child_tree->children[0],
351
                        $codebase,
352
                        null,
353
                        $template_type_map,
354
                        $type_aliases
355
                    );
356
                    $has_null = true;
357
                } else {
358
                    $atomic_type = self::getTypeFromTree(
359
                        $child_tree,
360
                        $codebase,
361
                        null,
362
                        $template_type_map,
363
                        $type_aliases
364
                    );
365
                }
366
367
                if ($atomic_type instanceof Union) {
368
                    foreach ($atomic_type->getAtomicTypes() as $type) {
369
                        $atomic_types[] = $type;
370
                    }
371
372
                    continue;
373
                }
374
375
                $atomic_types[] = $atomic_type;
376
            }
377
378
            if ($has_null) {
379
                $atomic_types[] = new TNull;
380
            }
381
382
            if (!$atomic_types) {
383
                throw new TypeParseTreeException(
384
                    'No atomic types found'
385
                );
386
            }
387
388
            return TypeCombination::combineTypes($atomic_types);
389
        }
390
391
        if ($parse_tree instanceof ParseTree\IntersectionTree) {
392
            $intersection_types = array_map(
393
                /**
394
                 * @return Atomic
395
                 */
396
                function (ParseTree $child_tree) use ($codebase, $template_type_map, $type_aliases) {
397
                    $atomic_type = self::getTypeFromTree(
398
                        $child_tree,
399
                        $codebase,
400
                        null,
401
                        $template_type_map,
402
                        $type_aliases
403
                    );
404
405
                    if (!$atomic_type instanceof Atomic) {
406
                        throw new TypeParseTreeException(
407
                            'Intersection types cannot contain unions'
408
                        );
409
                    }
410
411
                    return $atomic_type;
412
                },
413
                $parse_tree->children
414
            );
415
416
            $onlyObjectLike = true;
417
            foreach ($intersection_types as $intersection_type) {
418
                if (!$intersection_type instanceof ObjectLike) {
419
                    $onlyObjectLike = false;
420
                    break;
421
                }
422
            }
423
424
            if ($onlyObjectLike) {
425
                /** @var non-empty-array<string|int, Union> */
426
                $properties = [];
427
                /** @var ObjectLike $intersection_type */
428
                foreach ($intersection_types as $intersection_type) {
429
                    foreach ($intersection_type->properties as $property => $property_type) {
430
                        if (!array_key_exists($property, $properties)) {
431
                            $properties[$property] = clone $property_type;
432
                            continue;
433
                        }
434
435
                        $intersection_type = \Psalm\Type::intersectUnionTypes(
436
                            $properties[$property],
437
                            $property_type,
438
                            $codebase
439
                        );
440
                        if ($intersection_type === null) {
441
                            throw new TypeParseTreeException(
442
                                'Incompatible intersection types for "' . $property . '", '
443
                                    . $properties[$property] . ' and ' . $property_type
444
                                    . ' provided'
445
                            );
446
                        }
447
                        $properties[$property] = $intersection_type;
448
                    }
449
                }
450
                return new ObjectLike($properties);
451
            }
452
453
            $keyed_intersection_types = [];
454
455
            if ($intersection_types[0] instanceof TTypeAlias) {
456
                foreach ($intersection_types as $intersection_type) {
457
                    if (!$intersection_type instanceof TTypeAlias) {
458
                        throw new TypeParseTreeException(
459
                            'Intersection types with a type alias can only be comprised of other type aliases, '
460
                                . get_class($intersection_type) . ' provided'
461
                        );
462
                    }
463
464
                    $keyed_intersection_types[$intersection_type->getKey()] = $intersection_type;
465
                }
466
467
                $first_type = array_shift($keyed_intersection_types);
468
469
                if ($keyed_intersection_types) {
470
                    $first_type->extra_types = $keyed_intersection_types;
471
                }
472
            } else {
473
                foreach ($intersection_types as $intersection_type) {
474
                    if (!$intersection_type instanceof TIterable
475
                        && !$intersection_type instanceof TNamedObject
476
                        && !$intersection_type instanceof TTemplateParam
477
                        && !$intersection_type instanceof TObjectWithProperties
478
                    ) {
479
                        throw new TypeParseTreeException(
480
                            'Intersection types must be all objects or all object-like arrays, '
481
                                . get_class($intersection_type) . ' provided'
482
                        );
483
                    }
484
485
                    $keyed_intersection_types[
486
                        $intersection_type instanceof TIterable
487
                            ? $intersection_type->getId()
488
                            : $intersection_type->getKey()
489
                        ] = $intersection_type;
490
                }
491
492
                $intersect_static = false;
493
494
                if (isset($keyed_intersection_types['static'])) {
495
                    unset($keyed_intersection_types['static']);
496
                    $intersect_static = true;
497
                }
498
499
                if (!$keyed_intersection_types && $intersect_static) {
500
                    return new TNamedObject('static');
501
                }
502
503
                $first_type = array_shift($keyed_intersection_types);
504
505
                if ($intersect_static
506
                    && $first_type instanceof TNamedObject
507
                ) {
508
                    $first_type->was_static = true;
509
                }
510
511
                if ($keyed_intersection_types) {
512
                    $first_type->extra_types = $keyed_intersection_types;
513
                }
514
            }
515
516
            return $first_type;
517
        }
518
519
        if ($parse_tree instanceof ParseTree\ObjectLikeTree) {
520
            $properties = [];
521
522
            $type = $parse_tree->value;
523
524
            $is_tuple = true;
525
526
            foreach ($parse_tree->children as $i => $property_branch) {
527
                if (!$property_branch instanceof ParseTree\ObjectLikePropertyTree) {
528
                    $property_type = self::getTypeFromTree(
529
                        $property_branch,
530
                        $codebase,
531
                        null,
532
                        $template_type_map,
533
                        $type_aliases
534
                    );
535
                    $property_maybe_undefined = false;
536
                    $property_key = (string)$i;
537
                } elseif (count($property_branch->children) === 1) {
538
                    $property_type = self::getTypeFromTree(
539
                        $property_branch->children[0],
540
                        $codebase,
541
                        null,
542
                        $template_type_map,
543
                        $type_aliases
544
                    );
545
                    $property_maybe_undefined = $property_branch->possibly_undefined;
546
                    $property_key = $property_branch->value;
547
                    $is_tuple = false;
548
                } else {
549
                    throw new TypeParseTreeException(
550
                        'Missing property type'
551
                    );
552
                }
553
554
                if ($property_key[0] === '\'' || $property_key[0] === '"') {
555
                    $property_key = \stripslashes(substr($property_key, 1, -1));
556
                }
557
558
                if (!$property_type instanceof Union) {
559
                    $property_type = new Union([$property_type]);
560
                }
561
562
                if ($property_maybe_undefined) {
563
                    $property_type->possibly_undefined = true;
564
                }
565
566
                $properties[$property_key] = $property_type;
567
            }
568
569
            if ($type !== 'array' && $type !== 'object' && $type !== 'callable-array') {
570
                throw new TypeParseTreeException('Unexpected brace character');
571
            }
572
573
            if (!$properties) {
574
                throw new TypeParseTreeException('No properties supplied for ObjectLike');
575
            }
576
577
            if ($type === 'object') {
578
                return new TObjectWithProperties($properties);
579
            }
580
581
            if ($type === 'callable-array') {
582
                return new Atomic\TCallableObjectLikeArray($properties);
583
            }
584
585
            $object_like = new ObjectLike($properties);
586
587
            if ($is_tuple) {
588
                $object_like->sealed = true;
589
                $object_like->is_list = true;
590
            }
591
592
            return $object_like;
593
        }
594
595
        if ($parse_tree instanceof ParseTree\CallableWithReturnTypeTree) {
596
            $callable_type = self::getTypeFromTree(
597
                $parse_tree->children[0],
598
                $codebase,
599
                null,
600
                $template_type_map,
601
                $type_aliases
602
            );
603
604
            if (!$callable_type instanceof TCallable && !$callable_type instanceof TFn) {
605
                throw new \InvalidArgumentException('Parsing callable tree node should return TCallable');
606
            }
607
608
            if (!isset($parse_tree->children[1])) {
609
                throw new TypeParseTreeException('Invalid return type');
610
            }
611
612
            $return_type = self::getTypeFromTree(
613
                $parse_tree->children[1],
614
                $codebase,
615
                null,
616
                $template_type_map,
617
                $type_aliases
618
            );
619
620
            $callable_type->return_type = $return_type instanceof Union ? $return_type : new Union([$return_type]);
621
622
            return $callable_type;
623
        }
624
625
        if ($parse_tree instanceof ParseTree\CallableTree) {
626
            $params = array_map(
627
                /**
628
                 * @return FunctionLikeParameter
629
                 */
630
                function (ParseTree $child_tree) use ($codebase, $template_type_map, $type_aliases) {
631
                    $is_variadic = false;
632
                    $is_optional = false;
633
634
                    if ($child_tree instanceof ParseTree\CallableParamTree) {
635
                        $tree_type = self::getTypeFromTree(
636
                            $child_tree->children[0],
637
                            $codebase,
638
                            null,
639
                            $template_type_map,
640
                            $type_aliases
641
                        );
642
                        $is_variadic = $child_tree->variadic;
643
                        $is_optional = $child_tree->has_default;
644
                    } else {
645
                        if ($child_tree instanceof ParseTree\Value && strpos($child_tree->value, '$') > 0) {
646
                            $child_tree->value = preg_replace('/(.+)\$.*/', '$1', $child_tree->value);
647
                        }
648
649
                        $tree_type = self::getTypeFromTree(
650
                            $child_tree,
651
                            $codebase,
652
                            null,
653
                            $template_type_map,
654
                            $type_aliases
655
                        );
656
                    }
657
658
                    $tree_type = $tree_type instanceof Union ? $tree_type : new Union([$tree_type]);
659
660
                    $param = new FunctionLikeParameter(
661
                        '',
662
                        false,
663
                        $tree_type,
664
                        null,
665
                        null,
666
                        $is_optional,
667
                        false,
668
                        $is_variadic
669
                    );
670
671
                    // type is not authoratative
672
                    $param->signature_type = null;
673
674
                    return $param;
675
                },
676
                $parse_tree->children
677
            );
678
679
            if (in_array(strtolower($parse_tree->value), ['closure', '\closure'], true)) {
680
                return new TFn('Closure', $params);
681
            }
682
683
            return new TCallable($parse_tree->value, $params);
684
        }
685
686
        if ($parse_tree instanceof ParseTree\EncapsulationTree) {
687
            return self::getTypeFromTree(
688
                $parse_tree->children[0],
689
                $codebase,
690
                null,
691
                $template_type_map,
692
                $type_aliases
693
            );
694
        }
695
696
        if ($parse_tree instanceof ParseTree\NullableTree) {
697
            if (!isset($parse_tree->children[0])) {
698
                throw new TypeParseTreeException('Misplaced question mark');
699
            }
700
701
            $non_nullable_type = self::getTypeFromTree(
702
                $parse_tree->children[0],
703
                $codebase,
704
                null,
705
                $template_type_map,
706
                $type_aliases
707
            );
708
709
            if ($non_nullable_type instanceof Union) {
710
                $non_nullable_type->addType(new TNull);
711
712
                return $non_nullable_type;
713
            }
714
715
            return TypeCombination::combineTypes([
716
                new TNull,
717
                $non_nullable_type,
718
            ]);
719
        }
720
721
        if ($parse_tree instanceof ParseTree\MethodTree
722
            || $parse_tree instanceof ParseTree\MethodWithReturnTypeTree
723
        ) {
724
            throw new TypeParseTreeException('Misplaced brackets');
725
        }
726
727
        if ($parse_tree instanceof ParseTree\IndexedAccessTree) {
728
            if (!isset($parse_tree->children[0]) || !$parse_tree->children[0] instanceof ParseTree\Value) {
729
                throw new TypeParseTreeException('Unrecognised indexed access');
730
            }
731
732
            $offset_param_name = $parse_tree->value;
733
            $array_param_name = $parse_tree->children[0]->value;
734
735
            if (!isset($template_type_map[$offset_param_name])) {
736
                throw new TypeParseTreeException('Unrecognised template param ' . $offset_param_name);
737
            }
738
739
            if (!isset($template_type_map[$array_param_name])) {
740
                throw new TypeParseTreeException('Unrecognised template param ' . $array_param_name);
741
            }
742
743
            $offset_template_data = $template_type_map[$offset_param_name];
744
745
            $offset_defining_class = array_keys($offset_template_data)[0];
746
747
            if (!$offset_defining_class
748
                && isset($offset_template_data[''])
749
                && $offset_template_data[''][0]->isSingle()
750
            ) {
751
                $offset_template_type = array_values($offset_template_data[''][0]->getAtomicTypes())[0];
752
753
                if ($offset_template_type instanceof Atomic\TTemplateKeyOf) {
754
                    $offset_defining_class = (string) $offset_template_type->defining_class;
755
                }
756
            }
757
758
            $array_defining_class = array_keys($template_type_map[$array_param_name])[0];
759
760
            if ($offset_defining_class !== $array_defining_class
761
                && substr($offset_defining_class, 0, 3) !== 'fn-'
762
            ) {
763
                throw new TypeParseTreeException('Template params are defined in different locations');
764
            }
765
766
            return new Atomic\TTemplateIndexedAccess(
767
                $array_param_name,
768
                $offset_param_name,
769
                $array_defining_class
770
            );
771
        }
772
773
        if ($parse_tree instanceof ParseTree\TemplateAsTree) {
774
            return new Atomic\TTemplateParam(
775
                $parse_tree->param_name,
776
                new Union([new TNamedObject($parse_tree->as)]),
777
                'class-string-map'
778
            );
779
        }
780
781
        if ($parse_tree instanceof ParseTree\ConditionalTree) {
782
            $template_param_name = $parse_tree->condition->param_name;
783
784
            if (!isset($template_type_map[$template_param_name])) {
785
                throw new TypeParseTreeException('Unrecognized template \'' . $template_param_name . '\'');
786
            }
787
788
            if (count($parse_tree->children) !== 2) {
789
                throw new TypeParseTreeException('Invalid conditional');
790
            }
791
792
            $first_class = array_keys($template_type_map[$template_param_name])[0];
793
794
            $conditional_type = self::getTypeFromTree(
795
                $parse_tree->condition->children[0],
796
                $codebase,
797
                null,
798
                $template_type_map,
799
                $type_aliases
800
            );
801
802
            $if_type = self::getTypeFromTree(
803
                $parse_tree->children[0],
804
                $codebase,
805
                null,
806
                $template_type_map,
807
                $type_aliases
808
            );
809
810
            $else_type = self::getTypeFromTree(
811
                $parse_tree->children[1],
812
                $codebase,
813
                null,
814
                $template_type_map,
815
                $type_aliases
816
            );
817
818
            if ($conditional_type instanceof Atomic) {
819
                $conditional_type = new Union([$conditional_type]);
820
            }
821
822
            if ($if_type instanceof Atomic) {
823
                $if_type = new Union([$if_type]);
824
            }
825
826
            if ($else_type instanceof Atomic) {
827
                $else_type = new Union([$else_type]);
828
            }
829
830
            return new Atomic\TConditional(
831
                $template_param_name,
832
                $first_class,
833
                $template_type_map[$template_param_name][$first_class][0],
834
                $conditional_type,
835
                $if_type,
836
                $else_type
837
            );
838
        }
839
840
        if (!$parse_tree instanceof ParseTree\Value) {
841
            throw new \InvalidArgumentException('Unrecognised parse tree type ' . get_class($parse_tree));
842
        }
843
844
        if ($parse_tree->value[0] === '"' || $parse_tree->value[0] === '\'') {
845
            return new TLiteralString(substr($parse_tree->value, 1, -1));
846
        }
847
848
        if (strpos($parse_tree->value, '::')) {
849
            list($fq_classlike_name, $const_name) = explode('::', $parse_tree->value);
850
851 View Code Duplication
            if (isset($template_type_map[$fq_classlike_name]) && $const_name === 'class') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
852
                $first_class = array_keys($template_type_map[$fq_classlike_name])[0];
853
854
                return self::getGenericParamClass(
855
                    $fq_classlike_name,
856
                    $template_type_map[$fq_classlike_name][$first_class][0],
857
                    $first_class
858
                );
859
            }
860
861
            if ($const_name === 'class') {
862
                return new Atomic\TLiteralClassString($fq_classlike_name);
863
            }
864
865
            return new Atomic\TScalarClassConstant($fq_classlike_name, $const_name);
866
        }
867
868
        if (preg_match('/^\-?(0|[1-9][0-9]*)(\.[0-9]{1,})$/', $parse_tree->value)) {
869
            return new TLiteralFloat((float) $parse_tree->value);
870
        }
871
872
        if (preg_match('/^\-?(0|[1-9][0-9]*)$/', $parse_tree->value)) {
873
            return new TLiteralInt((int) $parse_tree->value);
874
        }
875
876
        if (!preg_match('@^(\$this|\\\\?[a-zA-Z_\x7f-\xff][\\\\\-0-9a-zA-Z_\x7f-\xff]*)$@', $parse_tree->value)) {
877
            throw new TypeParseTreeException('Invalid type \'' . $parse_tree->value . '\'');
878
        }
879
880
        $atomic_type_string = TypeTokenizer::fixScalarTerms($parse_tree->value, $php_version);
881
882
        $atomic_type = Atomic::create($atomic_type_string, $php_version, $template_type_map, $type_aliases);
883
884
        $atomic_type->offset_start = $parse_tree->offset_start;
885
        $atomic_type->offset_end = $parse_tree->offset_end;
886
887
        return $atomic_type;
888
    }
889
890
    private static function getGenericParamClass(
891
        string $param_name,
892
        Union $as,
893
        string $defining_class
894
    ) : Atomic\TTemplateParamClass {
895
        if ($as->hasMixed()) {
896
            return new Atomic\TTemplateParamClass(
897
                $param_name,
898
                'object',
899
                null,
900
                $defining_class
901
            );
902
        }
903
904
        if (!$as->isSingle()) {
905
            throw new TypeParseTreeException(
906
                'Invalid templated classname \'' . $as . '\''
907
            );
908
        }
909
910
        foreach ($as->getAtomicTypes() as $t) {
911
            if ($t instanceof TObject) {
912
                return new Atomic\TTemplateParamClass(
913
                    $param_name,
914
                    'object',
915
                    null,
916
                    $defining_class
917
                );
918
            }
919
920
            if ($t instanceof TIterable) {
921
                $traversable = new TGenericObject(
922
                    'Traversable',
923
                    $t->type_params
924
                );
925
926
                $as->substitute(new Union([$t]), new Union([$traversable]));
927
928
                return new Atomic\TTemplateParamClass(
929
                    $param_name,
930
                    $traversable->value,
931
                    $traversable,
932
                    $defining_class
933
                );
934
            }
935
936
            if ($t instanceof Atomic\TTemplateParam) {
937
                $t_atomic_types = $t->as->getAtomicTypes();
938
                $t_atomic_type = \count($t_atomic_types) === 1 ? \reset($t_atomic_types) : null;
939
940
                if (!$t_atomic_type instanceof TNamedObject) {
941
                    $t_atomic_type = null;
942
                }
943
944
                return new Atomic\TTemplateParamClass(
945
                    $t->param_name,
946
                    $t_atomic_type ? $t_atomic_type->value : 'object',
947
                    $t_atomic_type,
948
                    $t->defining_class
949
                );
950
            }
951
952
            if (!$t instanceof TNamedObject) {
953
                throw new TypeParseTreeException(
954
                    'Invalid templated classname \'' . $t->getId() . '\''
955
                );
956
            }
957
958
            return new Atomic\TTemplateParamClass(
959
                $param_name,
960
                $t->value,
961
                $t,
962
                $defining_class
963
            );
964
        }
965
966
        throw new \LogicException('Should never get here');
967
    }
968
}
969