mirror of
https://github.com/danog/psalm.git
synced 2024-11-30 04:39:00 +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) {
|
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_name = self::getDeclaringClassForProperty($appearing_property_id);
|
||||||
$property_class_storage = self::$storage[strtolower((string)$property_class_name)];
|
$property_class_storage = self::$storage[strtolower((string)$property_class_name)];
|
||||||
$property_class_name = self::getDeclaringClassForProperty($appearing_property_id);
|
$property_class_name = self::getDeclaringClassForProperty($appearing_property_id);
|
||||||
@ -662,10 +658,6 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
|
|||||||
$uninitialized_properties = [];
|
$uninitialized_properties = [];
|
||||||
|
|
||||||
foreach ($storage->appearing_property_ids as $property_name => $appearing_property_id) {
|
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_name = self::getDeclaringClassForProperty($appearing_property_id);
|
||||||
$property_class_storage = self::$storage[strtolower((string)$property_class_name)];
|
$property_class_storage = self::$storage[strtolower((string)$property_class_name)];
|
||||||
$property_class_name = self::getDeclaringClassForProperty($appearing_property_id);
|
$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) {
|
if ($constructor_checker) {
|
||||||
$method_context = clone $class_context;
|
$method_context = clone $class_context;
|
||||||
$method_context->collect_initializations = true;
|
$method_context->collect_initializations = true;
|
||||||
@ -842,14 +837,6 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
|
|||||||
$method_name,
|
$method_name,
|
||||||
Context $context
|
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) {
|
foreach ($this->class->stmts as $stmt) {
|
||||||
if ($stmt instanceof PhpParser\Node\Stmt\ClassMethod &&
|
if ($stmt instanceof PhpParser\Node\Stmt\ClassMethod &&
|
||||||
strtolower($stmt->name) === strtolower($method_name)
|
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)
|
// register where they appear (can never be in a trait)
|
||||||
foreach ($parent_storage->appearing_property_ids as $property_name => $appearing_property_id) {
|
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;
|
$storage->appearing_property_ids[$property_name] = $appearing_property_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// register where they're declared
|
// register where they're declared
|
||||||
foreach ($parent_storage->declaring_property_ids as $property_name => $declaring_property_id) {
|
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;
|
$storage->declaring_property_ids[$property_name] = $declaring_property_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// register where they're declared
|
// register where they're declared
|
||||||
foreach ($parent_storage->inheritable_property_ids as $property_name => $inheritable_property_id) {
|
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;
|
$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)
|
public function getMethodMutations($method_id, Context &$this_context)
|
||||||
{
|
{
|
||||||
list($fq_class_name, $method_name) = explode('::', $method_id);
|
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->collect_mutations = true;
|
||||||
|
$call_context->include_location = $this_context->include_location;
|
||||||
|
|
||||||
foreach ($this_context->vars_possibly_in_scope as $var => $type) {
|
foreach ($this_context->vars_possibly_in_scope as $var => $type) {
|
||||||
if (strpos($var, '$this->') === 0) {
|
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'];
|
$call_context->vars_in_scope['$this'] = $this_context->vars_in_scope['$this'];
|
||||||
|
|
||||||
$checked = false;
|
$class_checker_to_examine->getMethodMutations($method_name, $call_context);
|
||||||
|
|
||||||
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');
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($call_context->vars_possibly_in_scope as $var => $_) {
|
foreach ($call_context->vars_possibly_in_scope as $var => $_) {
|
||||||
$this_context->vars_possibly_in_scope[$var] = true;
|
$this_context->vars_possibly_in_scope[$var] = true;
|
||||||
|
@ -770,7 +770,8 @@ class AssignmentChecker
|
|||||||
$assignment_value_type . '\'',
|
$assignment_value_type . '\'',
|
||||||
new CodeLocation(
|
new CodeLocation(
|
||||||
$statements_checker->getSource(),
|
$statements_checker->getSource(),
|
||||||
$assignment_value ?: $stmt
|
$assignment_value ?: $stmt,
|
||||||
|
$context->include_location
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
$statements_checker->getSuppressedIssues()
|
$statements_checker->getSuppressedIssues()
|
||||||
|
@ -1123,12 +1123,47 @@ class CallChecker
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_string($stmt->name)) {
|
$class_storage = ClassLikeChecker::$storage[strtolower($fq_class_name)];
|
||||||
if ($context->collect_mutations) {
|
|
||||||
$method_id = $fq_class_name . '::' . strtolower($stmt->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 {
|
} else {
|
||||||
$namespace = $statements_checker->getNamespace()
|
$namespace = $statements_checker->getNamespace()
|
||||||
|
@ -38,6 +38,7 @@ class TraitChecker extends ClassLikeChecker
|
|||||||
if (!isset(self::$storage[$fq_class_name_lower])) {
|
if (!isset(self::$storage[$fq_class_name_lower])) {
|
||||||
self::$storage[$fq_class_name_lower] = $storage = new ClassLikeStorage();
|
self::$storage[$fq_class_name_lower] = $storage = new ClassLikeStorage();
|
||||||
$storage->name = $fq_class_name;
|
$storage->name = $fq_class_name;
|
||||||
|
$storage->is_trait = true;
|
||||||
$storage->location = new CodeLocation($this->source, $class, null, true);
|
$storage->location = new CodeLocation($this->source, $class, null, true);
|
||||||
|
|
||||||
self::$file_classes[$this->source->getFilePath()][] = $fq_class_name;
|
self::$file_classes[$this->source->getFilePath()][] = $fq_class_name;
|
||||||
|
@ -95,6 +95,11 @@ class ClassLikeStorage
|
|||||||
*/
|
*/
|
||||||
public $used_traits = [];
|
public $used_traits = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public $is_trait = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array<string, MethodStorage>
|
* @var array<string, MethodStorage>
|
||||||
*/
|
*/
|
||||||
|
@ -595,7 +595,7 @@ abstract class Type
|
|||||||
$type_key = $type->getKey();
|
$type_key = $type->getKey();
|
||||||
|
|
||||||
if ($type instanceof TArray || $type instanceof TGenericObject) {
|
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];
|
$type_param = $type->type_params[$i];
|
||||||
|
|
||||||
if (isset($combination->type_params[$type_key][$i])) {
|
if (isset($combination->type_params[$type_key][$i])) {
|
||||||
|
@ -230,7 +230,7 @@ class ClassScopeTest extends TestCase
|
|||||||
echo $this->fooFoo;
|
echo $this->fooFoo;
|
||||||
}
|
}
|
||||||
}',
|
}',
|
||||||
'error_message' => 'InaccessibleProperty',
|
'error_message' => 'UndefinedThisPropertyFetch',
|
||||||
],
|
],
|
||||||
'inaccessibleStaticPrivateProperty' => [
|
'inaccessibleStaticPrivateProperty' => [
|
||||||
'<?php
|
'<?php
|
||||||
|
@ -366,6 +366,43 @@ class PropertyTypeTest extends TestCase
|
|||||||
use T;
|
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',
|
'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