Skip to content

Commit e791814

Browse files
committed
[Debug][DebugClassLoader] Handle return types
1 parent f8664e7 commit e791814

File tree

7 files changed

+1135
-0
lines changed

7 files changed

+1135
-0
lines changed

src/Symfony/Component/Debug/DebugClassLoader.php

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,32 @@
2727
*/
2828
class DebugClassLoader
2929
{
30+
private const COMMON_NON_OBJECT_RETURNED_TYPES = [
31+
'false',
32+
'mixed',
33+
'null',
34+
'resource',
35+
'static',
36+
'true',
37+
'$this'
38+
];
39+
40+
private const NON_NULLABLE_RETURNABLE_TYPES = [
41+
'void' => '"void"',
42+
];
43+
44+
private const NULLABLE_RETURNABLE_TYPES = [
45+
'array' => 'an array',
46+
'bool' => 'a boolean',
47+
'callable' => 'a callable',
48+
'float' => 'a float',
49+
'int' => 'an integer',
50+
'iterable' => 'an iterable',
51+
'object' => 'an object',
52+
'string' => 'a string',
53+
'self' => 'an instance of itself',
54+
];
55+
3056
private $classLoader;
3157
private $isFinder;
3258
private $loaded = [];
@@ -40,6 +66,7 @@ class DebugClassLoader
4066
private static $annotatedParameters = [];
4167
private static $darwinCache = ['/' => ['/', []]];
4268
private static $method = [];
69+
private static $returnTypes = [];
4370

4471
public function __construct(callable $classLoader)
4572
{
@@ -309,6 +336,8 @@ public function checkAnnotations(\ReflectionClass $refl, $class)
309336
}
310337
}
311338

339+
$this->checkReturnTypes($class, $parent, $refl, $ns, $len, $deprecations);
340+
312341
if (\trait_exists($class)) {
313342
return $deprecations;
314343
}
@@ -372,6 +401,8 @@ public function checkAnnotations(\ReflectionClass $refl, $class)
372401
}
373402
}
374403

404+
$this->saveReturnType($class, $method, $doc);
405+
375406
if ($finalOrInternal || $method->isConstructor() || false === \strpos($doc, '@param') || StatelessInvocation::class === $class) {
376407
continue;
377408
}
@@ -522,4 +553,152 @@ private function getOwnInterfaces($class, $parent)
522553

523554
return $ownInterfaces;
524555
}
556+
557+
private function saveReturnType(string $class, \ReflectionMethod $method, string $doc): void
558+
{
559+
if ($method->getReturnType() instanceof \ReflectionType) {
560+
return;
561+
}
562+
563+
if (false === \strpos($doc, 'return')) {
564+
return;
565+
}
566+
567+
if (!preg_match_all('/\n\s+\* @return +(\S+)/', $doc,$matches, PREG_SET_ORDER)) {
568+
return;
569+
}
570+
571+
$types = explode('|', end($matches)[1], 3);
572+
573+
$nullable = false;
574+
575+
switch (count($types)) {
576+
case 1:
577+
$type = $types[0];
578+
579+
break;
580+
case 2:
581+
// fastest way to handle the null|null edge case
582+
$type = 'null';
583+
584+
foreach (array_unique(array_map([$this, 'normalizeType'], $types)) as $i => $normalizedType) {
585+
if ('null' === $normalizedType) {
586+
$nullable = true;
587+
} else {
588+
$type = $types[$i];
589+
}
590+
}
591+
592+
if (!$nullable) {
593+
return;
594+
}
595+
596+
break;
597+
default:
598+
return;
599+
}
600+
601+
$normalizedType = $this->normalizeType($type);
602+
if (in_array($normalizedType, self::COMMON_NON_OBJECT_RETURNED_TYPES)) {
603+
return;
604+
}
605+
606+
if (isset(self::NULLABLE_RETURNABLE_TYPES[$normalizedType])) {
607+
$normalizedDocReturnType = self::NULLABLE_RETURNABLE_TYPES[$normalizedType];
608+
$hintedReturnType = $normalizedType;
609+
} elseif (isset(self::NON_NULLABLE_RETURNABLE_TYPES[$normalizedType])) {
610+
if ($nullable) {
611+
return;
612+
}
613+
614+
$normalizedDocReturnType = self::NON_NULLABLE_RETURNABLE_TYPES[$normalizedType];
615+
$hintedReturnType = $normalizedType;
616+
} else {
617+
$normalizedDocReturnType = sprintf('an instance of "%s"', $type);
618+
$hintedReturnType = $type;
619+
}
620+
621+
if ($nullable) {
622+
$normalizedDocReturnType .= ' or null';
623+
$hintedReturnType = '?'.$hintedReturnType;
624+
}
625+
626+
self::$returnTypes[$class][$method->name] = [$normalizedDocReturnType, $hintedReturnType];
627+
}
628+
629+
/**
630+
* @param string $class
631+
* @param string|false $parent
632+
* @param \ReflectionClass $refl
633+
* @param string $ns
634+
* @param int $len
635+
* @param array $deprecations
636+
*/
637+
private function checkReturnTypes(string $class, $parent, \ReflectionClass $refl, string $ns, int $len, &$deprecations): void
638+
{
639+
$parentsAndInterfacesThatHaveMethodsThatReturnsADocType = [];
640+
foreach (array_merge($parent ? array_merge([$parent], class_parents($parent, false)) : [], class_implements($class, false)) as $use) {
641+
if (isset(self::$returnTypes[$use]) && 0 !== \strncmp($ns, \str_replace('_', '\\', $use), $len)) {
642+
$parentsAndInterfacesThatHaveMethodsThatReturnsADocType[] = $use;
643+
}
644+
}
645+
646+
if (empty($parentsAndInterfacesThatHaveMethodsThatReturnsADocType)) {
647+
return;
648+
}
649+
650+
foreach ($refl->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $method) {
651+
if ($method->class !== $class || $method->getReturnType() instanceof \ReflectionType) {
652+
continue;
653+
}
654+
655+
foreach ($parentsAndInterfacesThatHaveMethodsThatReturnsADocType as $name) {
656+
if (!isset(self::$returnTypes[$name][$method->name])) {
657+
continue;
658+
}
659+
660+
list($normalizedDocReturnType, $hintedReturnType) = self::$returnTypes[$name][$method->name];
661+
$deprecations[] = sprintf(
662+
'The "%s::%s()" parent method returns %s. You could safely add the ": %s" return type to your method to be compatible with the next major version of its parent.',
663+
$class,
664+
$method->name,
665+
$normalizedDocReturnType,
666+
$hintedReturnType
667+
);
668+
669+
break;
670+
}
671+
}
672+
}
673+
674+
private function normalizeType(string $type): string
675+
{
676+
$lcType = mb_strtolower($type);
677+
678+
if (
679+
isset(self::NULLABLE_RETURNABLE_TYPES[$lcType]) ||
680+
isset(self::NON_NULLABLE_RETURNABLE_TYPES[$lcType]) ||
681+
isset(self::COMMON_NON_OBJECT_RETURNED_TYPES[$lcType]))
682+
{
683+
return $lcType;
684+
}
685+
686+
if ('[]' === substr($lcType, -2)) {
687+
return 'array';
688+
}
689+
690+
if ('integer' === $lcType) {
691+
return 'int';
692+
}
693+
694+
if ('boolean' === $lcType) {
695+
return 'bool';
696+
}
697+
698+
if (1 === preg_match('/^callable *\(/', $lcType)) {
699+
return 'callable';
700+
}
701+
702+
return $lcType;
703+
}
525704
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy