mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 20:34:47 +01:00
Slow down Psalm by checking parent initialisations too
This commit is contained in:
parent
fe6f0c073b
commit
d8654b8389
@ -563,10 +563,6 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
|
||||
}
|
||||
|
||||
foreach ($storage->appearing_property_ids as $property_name => $appearing_property_id) {
|
||||
if (explode('::', $appearing_property_id)[0] !== $fq_class_name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$property_class_name = self::getDeclaringClassForProperty($appearing_property_id);
|
||||
$property_class_storage = self::$storage[strtolower((string)$property_class_name)];
|
||||
$property_class_name = self::getDeclaringClassForProperty($appearing_property_id);
|
||||
@ -662,10 +658,6 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
|
||||
$uninitialized_properties = [];
|
||||
|
||||
foreach ($storage->appearing_property_ids as $property_name => $appearing_property_id) {
|
||||
if (explode('::', $appearing_property_id)[0] !== $fq_class_name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$property_class_name = self::getDeclaringClassForProperty($appearing_property_id);
|
||||
$property_class_storage = self::$storage[strtolower((string)$property_class_name)];
|
||||
$property_class_name = self::getDeclaringClassForProperty($appearing_property_id);
|
||||
@ -684,7 +676,10 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
|
||||
}
|
||||
}
|
||||
|
||||
if ($uninitialized_properties && !($this instanceof TraitChecker)) {
|
||||
if ($uninitialized_properties
|
||||
&& !($this instanceof TraitChecker)
|
||||
&& !$storage->abstract
|
||||
) {
|
||||
if ($constructor_checker) {
|
||||
$method_context = clone $class_context;
|
||||
$method_context->collect_initializations = true;
|
||||
@ -842,14 +837,6 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
|
||||
$method_name,
|
||||
Context $context
|
||||
) {
|
||||
foreach (self::$storage[strtolower($this->fq_class_name)]->properties as $property_name => $property_storage) {
|
||||
if (!isset($context->vars_in_scope['$this->' . $property_name]) &&
|
||||
$property_storage->type !== false
|
||||
) {
|
||||
$context->vars_in_scope['$this->' . $property_name] = clone $property_storage->type;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->class->stmts as $stmt) {
|
||||
if ($stmt instanceof PhpParser\Node\Stmt\ClassMethod &&
|
||||
strtolower($stmt->name) === strtolower($method_name)
|
||||
@ -1696,16 +1683,37 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
|
||||
|
||||
// register where they appear (can never be in a trait)
|
||||
foreach ($parent_storage->appearing_property_ids as $property_name => $appearing_property_id) {
|
||||
if (!$parent_storage->is_trait
|
||||
&& isset($parent_storage->properties[$property_name])
|
||||
&& $parent_storage->properties[$property_name]->visibility === self::VISIBILITY_PRIVATE
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$storage->appearing_property_ids[$property_name] = $appearing_property_id;
|
||||
}
|
||||
|
||||
// register where they're declared
|
||||
foreach ($parent_storage->declaring_property_ids as $property_name => $declaring_property_id) {
|
||||
if (!$parent_storage->is_trait
|
||||
&& isset($parent_storage->properties[$property_name])
|
||||
&& $parent_storage->properties[$property_name]->visibility === self::VISIBILITY_PRIVATE
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$storage->declaring_property_ids[$property_name] = $declaring_property_id;
|
||||
}
|
||||
|
||||
// register where they're declared
|
||||
foreach ($parent_storage->inheritable_property_ids as $property_name => $inheritable_property_id) {
|
||||
if (!$parent_storage->is_trait
|
||||
&& isset($parent_storage->properties[$property_name])
|
||||
&& $parent_storage->properties[$property_name]->visibility === self::VISIBILITY_PRIVATE
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$storage->inheritable_property_ids[$property_name] = $inheritable_property_id;
|
||||
}
|
||||
}
|
||||
|
@ -352,8 +352,25 @@ class FileChecker extends SourceChecker implements StatementsSource
|
||||
public function getMethodMutations($method_id, Context &$this_context)
|
||||
{
|
||||
list($fq_class_name, $method_name) = explode('::', $method_id);
|
||||
$call_context = new Context((string)array_values($this_context->vars_in_scope['$this']->types)[0]);
|
||||
|
||||
$class_checker_to_examine = null;
|
||||
|
||||
foreach ($this->class_checkers_to_analyze as $class_checker) {
|
||||
if (strtolower($class_checker->getFQCLN()) === strtolower($fq_class_name)) {
|
||||
$class_checker_to_examine = $class_checker;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$class_checker_to_examine) {
|
||||
$this->project_checker->getMethodMutations($method_id, $this_context);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$call_context = new Context($this_context->self);
|
||||
$call_context->collect_mutations = true;
|
||||
$call_context->include_location = $this_context->include_location;
|
||||
|
||||
foreach ($this_context->vars_possibly_in_scope as $var => $type) {
|
||||
if (strpos($var, '$this->') === 0) {
|
||||
@ -369,19 +386,7 @@ class FileChecker extends SourceChecker implements StatementsSource
|
||||
|
||||
$call_context->vars_in_scope['$this'] = $this_context->vars_in_scope['$this'];
|
||||
|
||||
$checked = false;
|
||||
|
||||
foreach ($this->class_checkers_to_analyze as $class_checker) {
|
||||
if (strtolower($class_checker->getFQCLN()) === strtolower($fq_class_name)) {
|
||||
$class_checker->getMethodMutations($method_name, $call_context);
|
||||
$checked = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$checked) {
|
||||
throw new \UnexpectedValueException('Method ' . $method_id . ' could not be checked');
|
||||
}
|
||||
$class_checker_to_examine->getMethodMutations($method_name, $call_context);
|
||||
|
||||
foreach ($call_context->vars_possibly_in_scope as $var => $_) {
|
||||
$this_context->vars_possibly_in_scope[$var] = true;
|
||||
|
@ -770,7 +770,8 @@ class AssignmentChecker
|
||||
$assignment_value_type . '\'',
|
||||
new CodeLocation(
|
||||
$statements_checker->getSource(),
|
||||
$assignment_value ?: $stmt
|
||||
$assignment_value ?: $stmt,
|
||||
$context->include_location
|
||||
)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
|
@ -1123,12 +1123,47 @@ class CallChecker
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_string($stmt->name)) {
|
||||
if ($context->collect_mutations) {
|
||||
$method_id = $fq_class_name . '::' . strtolower($stmt->name);
|
||||
$class_storage = ClassLikeChecker::$storage[strtolower($fq_class_name)];
|
||||
|
||||
$file_checker->project_checker->getMethodMutations($method_id, $context);
|
||||
if (is_string($stmt->name) && $class_storage->user_defined) {
|
||||
$method_id = $fq_class_name . '::' . strtolower($stmt->name);
|
||||
|
||||
$old_context_include_location = $context->include_location;
|
||||
$old_self = $context->self;
|
||||
$context->include_location = new CodeLocation($statements_checker->getSource(), $stmt);
|
||||
$context->self = $fq_class_name;
|
||||
|
||||
if ($context->collect_mutations) {
|
||||
$file_checker->getMethodMutations($method_id, $context);
|
||||
} elseif ($context->collect_initializations) {
|
||||
$local_vars_in_scope = [];
|
||||
$local_vars_possibly_in_scope = [];
|
||||
|
||||
foreach ($context->vars_in_scope as $var => $type) {
|
||||
if (strpos($var, '$this->') !== 0 && $var !== '$this') {
|
||||
$local_vars_in_scope[$var] = $context->vars_in_scope[$var];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($context->vars_possibly_in_scope as $var => $type) {
|
||||
if (strpos($var, '$this->') !== 0 && $var !== '$this') {
|
||||
$local_vars_possibly_in_scope[$var] = $context->vars_possibly_in_scope[$var];
|
||||
}
|
||||
}
|
||||
|
||||
$file_checker->getMethodMutations($method_id, $context);
|
||||
|
||||
foreach ($local_vars_in_scope as $var => $type) {
|
||||
$context->vars_in_scope[$var] = $type;
|
||||
}
|
||||
|
||||
foreach ($local_vars_possibly_in_scope as $var => $type) {
|
||||
$context->vars_possibly_in_scope[$var] = $type;
|
||||
}
|
||||
}
|
||||
|
||||
$context->include_location = $old_context_include_location;
|
||||
$context->self = $old_self;
|
||||
}
|
||||
} else {
|
||||
$namespace = $statements_checker->getNamespace()
|
||||
|
@ -38,6 +38,7 @@ class TraitChecker extends ClassLikeChecker
|
||||
if (!isset(self::$storage[$fq_class_name_lower])) {
|
||||
self::$storage[$fq_class_name_lower] = $storage = new ClassLikeStorage();
|
||||
$storage->name = $fq_class_name;
|
||||
$storage->is_trait = true;
|
||||
$storage->location = new CodeLocation($this->source, $class, null, true);
|
||||
|
||||
self::$file_classes[$this->source->getFilePath()][] = $fq_class_name;
|
||||
|
@ -95,6 +95,11 @@ class ClassLikeStorage
|
||||
*/
|
||||
public $used_traits = [];
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $is_trait = false;
|
||||
|
||||
/**
|
||||
* @var array<string, MethodStorage>
|
||||
*/
|
||||
|
@ -595,7 +595,7 @@ abstract class Type
|
||||
$type_key = $type->getKey();
|
||||
|
||||
if ($type instanceof TArray || $type instanceof TGenericObject) {
|
||||
for ($i = 0; $i < count($type->type_params); $i++) {
|
||||
for ($i = 0; $i < count($type->type_params); ++$i) {
|
||||
$type_param = $type->type_params[$i];
|
||||
|
||||
if (isset($combination->type_params[$type_key][$i])) {
|
||||
|
@ -230,7 +230,7 @@ class ClassScopeTest extends TestCase
|
||||
echo $this->fooFoo;
|
||||
}
|
||||
}',
|
||||
'error_message' => 'InaccessibleProperty',
|
||||
'error_message' => 'UndefinedThisPropertyFetch',
|
||||
],
|
||||
'inaccessibleStaticPrivateProperty' => [
|
||||
'<?php
|
||||
|
@ -366,6 +366,43 @@ class PropertyTypeTest extends TestCase
|
||||
use T;
|
||||
}',
|
||||
],
|
||||
'abstractClassWithNoConstructor' => [
|
||||
'<?php
|
||||
abstract class A {
|
||||
/** @var string */
|
||||
public $foo;
|
||||
}',
|
||||
],
|
||||
'abstractClassConstructorAndChildConstructor' => [
|
||||
'<?php
|
||||
abstract class A {
|
||||
/** @var string */
|
||||
public $foo;
|
||||
|
||||
public function __construct() {
|
||||
$this->foo = "";
|
||||
}
|
||||
}
|
||||
|
||||
class B extends A {
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
}
|
||||
}',
|
||||
],
|
||||
'SKIPPED-abstractClassConstructorAndImplicitChildConstructor' => [
|
||||
'<?php
|
||||
abstract class A {
|
||||
/** @var string */
|
||||
public $foo;
|
||||
|
||||
public function __construct() {
|
||||
$this->foo = "";
|
||||
}
|
||||
}
|
||||
|
||||
class B extends A {}',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@ -714,6 +751,19 @@ class PropertyTypeTest extends TestCase
|
||||
}',
|
||||
'error_message' => 'UndefinedClass',
|
||||
],
|
||||
'abstractClassWithNoConstructorButChild' => [
|
||||
'<?php
|
||||
abstract class A {
|
||||
/** @var string */
|
||||
public $foo;
|
||||
}
|
||||
|
||||
class B extends A {
|
||||
public function __construct() {
|
||||
}
|
||||
}',
|
||||
'error_message' => 'PropertyNotSetInConstructor',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user