1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-30 04:39:00 +01:00

feat: ignore static properties in properties-of<T>

This commit is contained in:
Patrick Remy 2022-02-22 19:49:03 +01:00
parent 9c6deacc69
commit 6e783d863d
No known key found for this signature in database
GPG Key ID: FE25C0B14C0500CD
2 changed files with 114 additions and 54 deletions

View File

@ -43,7 +43,6 @@ use function array_filter;
use function array_keys;
use function array_map;
use function array_merge;
use function array_push;
use function array_values;
use function count;
use function get_class;
@ -307,60 +306,13 @@ class TypeExpander
}
if ($return_type instanceof TPropertiesOf) {
if ($return_type->fq_classlike_name === 'self' && $self_class) {
$return_type->fq_classlike_name = $self_class;
}
if ($return_type->fq_classlike_name === 'static' && $self_class) {
$return_type->fq_classlike_name = is_string($static_class_type) ? $static_class_type : $self_class;
}
if (!$codebase->classExists($return_type->fq_classlike_name)) {
return [$return_type];
}
// Get and merge all properties from parent classes
$class_storage = $codebase->classlike_storage_provider->get($return_type->fq_classlike_name);
$properties_types = $class_storage->properties;
foreach ($class_storage->parent_classes as $parent_class) {
if (!$codebase->classOrInterfaceExists($parent_class)) {
continue;
}
$parent_class_storage = $codebase->classlike_storage_provider->get($parent_class);
$properties_types = array_merge(
$properties_types,
$parent_class_storage->properties
);
}
// Filter properties if configured
if ($return_type->visibility_filter !== null) {
$properties_types = array_filter(
$properties_types,
function (PropertyStorage $property) use ($return_type): bool {
return $property->visibility === $return_type->visibility_filter;
}
);
}
// Return property names as literal string
$properties = array_map(
function (PropertyStorage $property): ?Union {
return $property->type;
},
$properties_types
return self::expandPropertiesOf(
$codebase,
$return_type,
$self_class,
$static_class_type,
$parent_class
);
$properties = array_filter(
$properties,
function (?Union $property_type): bool {
return $property_type !== null;
}
);
if ($properties === []) {
return $return_type;
}
return [new TKeyedArray($properties)];
}
if ($return_type instanceof TTypeAlias) {
@ -944,6 +896,77 @@ class TypeExpander
return [$return_type];
}
/**
* @param string|TNamedObject|TTemplateParam|null $static_class_type
* @return non-empty-list<Atomic>
*/
private static function expandPropertiesOf(
Codebase $codebase,
TPropertiesOf &$return_type,
?string $self_class,
$static_class_type,
?string $parent_class
): array {
if ($return_type->fq_classlike_name === 'self' && $self_class) {
$return_type->fq_classlike_name = $self_class;
}
if ($return_type->fq_classlike_name === 'static' && $self_class) {
$return_type->fq_classlike_name = is_string($static_class_type) ? $static_class_type : $self_class;
}
if (!$codebase->classExists($return_type->fq_classlike_name)) {
return [$return_type];
}
// Get and merge all properties from parent classes
$class_storage = $codebase->classlike_storage_provider->get($return_type->fq_classlike_name);
$properties_types = $class_storage->properties;
foreach ($class_storage->parent_classes as $parent_class) {
if (!$codebase->classOrInterfaceExists($parent_class)) {
continue;
}
$parent_class_storage = $codebase->classlike_storage_provider->get($parent_class);
$properties_types = array_merge(
$properties_types,
$parent_class_storage->properties
);
}
// Filter only non-static properties, and check visibility filter
$properties_types = array_filter(
$properties_types,
function (PropertyStorage $property) use ($return_type): bool {
if (
$return_type->visibility_filter !== null
&& $property->visibility !== $return_type->visibility_filter
) {
return false;
}
return !$property->is_static;
}
);
// Return property names as literal string
$properties = array_map(
function (PropertyStorage $property): ?Union {
return $property->type;
},
$properties_types
);
$properties = array_filter(
$properties,
function (?Union $property_type): bool {
return $property_type !== null;
}
);
if ($properties === []) {
return [$return_type];
}
return [new TKeyedArray($properties)];
}
/**
* @param TKeyOfArray|TValueOfArray $return_type
* @param string|TNamedObject|TTemplateParam|null $static_class_type

View File

@ -88,7 +88,30 @@ class PropertiesOfTest extends TestCase
}
',
],
'allPropertiesOfContainsNoStatic' => [
'code' => '<?php
class A {
/** @var bool */
public static $imStatic = true;
/** @var bool */
public $foo = false;
/** @var string */
private $bar = "";
/** @var int */
protected $adams = 42;
}
/** @return properties-of<A> */
function returnPropertyOfA(int $visibility) {
return [
"foo" => true,
"bar" => "foo",
"adams" => 1
];
}
',
],
'usePropertiesOfSelfAsArrayKey' => [
'code' => '<?php
class A {
@ -194,6 +217,20 @@ class PropertiesOfTest extends TestCase
',
'error_message' => 'InvalidDocblock',
],
'propertiesOfPicksNoStatic' => [
'code' => '<?php
class A {
/** @var mixed */
public static $foo;
}
/** @return properties-of<A> */
function returnPropertyOfA() {
return ["foo" => true];
}
',
'error_message' => 'InvalidReturnStatement'
],
'publicPropertiesOfPicksNoPrivate' => [
'code' => '<?php
class A {