From 2d89dc761237b055ddb8c6dc817c46c0cd6dd44d Mon Sep 17 00:00:00 2001 From: nsfisis Date: Thu, 23 Jul 2026 22:27:02 +0900 Subject: fix(plugin-class-classifier): reach docblock-only generic payload types The reachability closure only consults @phpstan-return/@param/@var docblocks when the native type is array/iterable/mixed/object/absent. For a concrete wrapper class whose payload is expressed only via a phpstan generic (PromiseInterface), the payload type was silently dropped: Symfony\Component\Process\Process never appeared as reached even though ProcessExecutor::executeAsync() hands one to plugin callbacks. Treat known generic wrapper types the same as array/iterable/mixed/object so their docblock payload is folded into the closure. --- .../plugin-class-classifier/src/SourceParser.php | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) (limited to 'scripts/plugin-class-classifier/src') diff --git a/scripts/plugin-class-classifier/src/SourceParser.php b/scripts/plugin-class-classifier/src/SourceParser.php index 591c54b1..bb293914 100644 --- a/scripts/plugin-class-classifier/src/SourceParser.php +++ b/scripts/plugin-class-classifier/src/SourceParser.php @@ -309,10 +309,21 @@ final class SourceParser return null; } + /** + * Native class types that carry a phpstan-only generic payload + * (`@phpstan-return PromiseInterface`). The native signature + * names the wrapper, not the payload, so the payload type is invisible + * unless the docblock is consulted too — same reason array/iterable/ + * mixed/object trigger docblock refinement below. + */ + private const GENERIC_WRAPPER_TYPES = [ + 'React\\Promise\\PromiseInterface', + ]; + /** * Extracts class-like FQCNs from a native type node and reports whether - * the type invites docblock refinement (array/iterable/mixed/object or - * no type at all). + * the type invites docblock refinement (array/iterable/mixed/object, + * no type at all, or a generic wrapper type from GENERIC_WRAPPER_TYPES). * * @return array{0: list, 1: bool} */ @@ -337,7 +348,7 @@ final class SourceParser $expandable = true; } } elseif ($t instanceof Name) { - $name = $t->toString(); + $name = ltrim($t->toString(), '\\'); $lower = strtolower($name); if ($lower === 'self' || $lower === 'static') { $classes[] = $currentClass; @@ -346,7 +357,10 @@ final class SourceParser $classes[] = $parentClass; } } else { - $classes[] = ltrim($name, '\\'); + $classes[] = $name; + if (in_array($name, self::GENERIC_WRAPPER_TYPES, true)) { + $expandable = true; + } } } }; -- cgit v1.3.1