Keep imported phpstan/psalm types unqualified (#1797)

A type brought in with @phpstan-import-type is a local alias, not a class
in the model's namespace, but it was resolved as one:

    @property-read \App\Models\ArrayShape $some_array

Collect the aliases declared with @phpstan-type/@psalm-type or imported
with @phpstan-import-type/@psalm-import-type on the model and its parents,
and return them as written.

Fixes #1773
This commit is contained in:
Hugo Mayonobe
2026-08-20 16:16:03 +02:00
committed by GitHub
parent cc26683e8e
commit 20497cb988
5 changed files with 152 additions and 0 deletions
+47
View File
@@ -132,6 +132,10 @@ class ModelsCommand extends Command
* @var array<string, Context>
*/
protected $contextCache = [];
/**
* @var array<string, array<int, string>>
*/
protected $localTypeAliasCache = [];
/**
* @var array<string, true>
*/
@@ -1485,12 +1489,55 @@ class ModelsCommand extends Command
return $typeAlias;
}
$localTypeAlias = strtok(trim($returnTag->getContent()), " \t\n\r");
if ($localTypeAlias !== false
&& in_array($localTypeAlias, $this->getLocalTypeAliases($reflection->getDeclaringClass()), true)
) {
return $localTypeAlias;
}
$type = $phpdoc->getTagsByName('return')[0]->getType();
}
return $type;
}
/**
* Get the type aliases declared or imported on the given class and its parents.
*
* These are local names rather than classes, so they must be left as-is
* instead of being resolved against the class namespace.
*
* @return array<int, string>
*/
protected function getLocalTypeAliases(ReflectionClass $class): array
{
$key = $class->getName();
if (isset($this->localTypeAliasCache[$key])) {
return $this->localTypeAliasCache[$key];
}
$aliases = [];
for ($current = $class; $current !== false; $current = $current->getParentClass()) {
if (($docComment = $current->getDocComment()) === false) {
continue;
}
preg_match_all(
'/@(?:phpstan|psalm)-(?:import-)?type\s+([A-Za-z_\x80-\xff][A-Za-z0-9_\x80-\xff]*)/',
$docComment,
$matches
);
$aliases = array_merge($aliases, $matches[1]);
}
return $this->localTypeAliasCache[$key] = array_values(array_unique($aliases));
}
protected function getDocBlockContext(\Reflector $reflector): Context
{
if ($reflector instanceof \ReflectionMethod) {