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

Fix #903 - make sure parent::foo is executed in context of method’s class

and not immediate parent
This commit is contained in:
Matthew Brown 2018-07-21 19:56:26 -04:00
parent ef1c1995a3
commit c97329da06
8 changed files with 140 additions and 57 deletions

View File

@ -383,6 +383,7 @@ class ClassChecker extends ClassLikeChecker
}
$constructor_checker = null;
$constructor_appearing_fqcln = $fq_class_name;
$member_stmts = [];
foreach ($class->stmts as $stmt) {
@ -447,27 +448,6 @@ class ClassChecker extends ClassLikeChecker
continue;
}
$constructor_class_storage = null;
if (isset($storage->methods['__construct'])) {
$constructor_class_storage = $storage;
} elseif (isset($property_class_storage->methods['__construct'])
&& $property_class_storage !== $storage
) {
$constructor_class_storage = $property_class_storage;
} elseif (!empty($property_class_storage->overridden_method_ids['__construct'])) {
list($construct_fqcln) =
explode('::', $property_class_storage->overridden_method_ids['__construct'][0]);
$constructor_class_storage = $classlike_storage_provider->get($construct_fqcln);
}
if ($constructor_class_storage
&& $constructor_class_storage->all_properties_set_in_constructor
&& $constructor_class_storage->methods['__construct']->visibility !== self::VISIBILITY_PRIVATE
) {
continue;
}
$uninitialized_variables[] = '$this->' . $property_name;
$uninitialized_properties[$property_name] = $property;
}
@ -478,9 +458,10 @@ class ClassChecker extends ClassLikeChecker
&& isset($storage->declaring_method_ids['__construct'])
&& $class->extends
) {
list($construct_fqcln) = explode('::', $storage->declaring_method_ids['__construct']);
list($constructor_declaring_fqcln) = explode('::', $storage->declaring_method_ids['__construct']);
list($constructor_appearing_fqcln) = explode('::', $storage->appearing_method_ids['__construct']);
$constructor_class_storage = $classlike_storage_provider->get($construct_fqcln);
$constructor_class_storage = $classlike_storage_provider->get($constructor_declaring_fqcln);
// ignore oldstyle constructors and classes without any declared properties
if ($constructor_class_storage->user_defined
@ -550,35 +531,47 @@ class ClassChecker extends ClassLikeChecker
if ($constructor_checker) {
$method_context = clone $class_context;
$method_context->collect_initializations = true;
$method_context->self = $fq_class_name;
$method_context->vars_in_scope['$this'] = Type::parseString($fq_class_name);
$method_context->vars_possibly_in_scope['$this'] = true;
$constructor_checker->analyze($method_context, $global_context, true);
$all_properties_set_in_constructor = true;
foreach ($uninitialized_properties as $property_name => $property) {
foreach ($uninitialized_properties as $property_name => $property_storage) {
if (!isset($method_context->vars_in_scope['$this->' . $property_name])) {
throw new \UnexpectedValueException('$this->' . $property_name . ' should be in scope');
}
$end_type = $method_context->vars_in_scope['$this->' . $property_name];
if (!$end_type->initialized) {
$all_properties_set_in_constructor = false;
$property_id = $constructor_appearing_fqcln . '::$' . $property_name;
$constructor_class_property_storage = $property_storage;
if ($fq_class_name !== $constructor_appearing_fqcln) {
$a_class_storage = $classlike_storage_provider->get($constructor_appearing_fqcln);
if (!isset($a_class_storage->declaring_property_ids[$property_name])) {
$constructor_class_property_storage = null;
} else {
$declaring_property_class = $a_class_storage->declaring_property_ids[$property_name];
$constructor_class_property_storage = $classlike_storage_provider
->get($declaring_property_class)
->properties[$property_name];
}
}
if (!$end_type->initialized && $property->location) {
$property_id = $this->fq_class_name . '::$' . $property_name;
if ($property_storage->location
&& (!$end_type->initialized || $property_storage !== $constructor_class_property_storage)
) {
if (!$config->reportIssueInFile(
'PropertyNotSetInConstructor',
$property->location->file_path
$property_storage->location->file_path
) && $class->extends
) {
$error_location = new CodeLocation($this, $class->extends);
} else {
$error_location = $property->location;
$error_location = $property_storage->location;
}
if (IssueBuffer::accepts(
@ -593,8 +586,6 @@ class ClassChecker extends ClassLikeChecker
}
}
}
$storage->all_properties_set_in_constructor = $all_properties_set_in_constructor;
} elseif (!$storage->abstract) {
$first_uninitialized_property = array_shift($uninitialized_properties);

View File

@ -15,6 +15,7 @@ use Psalm\Issue\DeprecatedClass;
use Psalm\Issue\InvalidStringClass;
use Psalm\Issue\ParentNotFound;
use Psalm\Issue\UndefinedClass;
use Psalm\Issue\UndefinedMethod;
use Psalm\IssueBuffer;
use Psalm\Type;
use Psalm\Type\Atomic\TNamedObject;
@ -79,17 +80,40 @@ class StaticCallChecker extends \Psalm\Checker\Statements\Expression\CallChecker
$fq_class_name = $class_storage->name;
if ($stmt->name instanceof PhpParser\Node\Identifier && $class_storage->user_defined) {
if ($stmt->name instanceof PhpParser\Node\Identifier
&& $class_storage->user_defined
&& ($context->collect_mutations || $context->collect_initializations)
) {
$method_id = $fq_class_name . '::' . strtolower($stmt->name->name);
$appearing_method_id = $codebase->getAppearingMethodId($method_id);
if (!$appearing_method_id) {
if (IssueBuffer::accepts(
new UndefinedMethod(
'Method ' . $method_id . ' does not exist',
new CodeLocation($statements_checker->getSource(), $stmt),
$method_id
),
$statements_checker->getSuppressedIssues()
)) {
return false;
}
return;
}
list($appearing_method_class_name) = explode('::', $appearing_method_id);
$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;
$context->self = $appearing_method_class_name;
if ($context->collect_mutations) {
$file_checker->getMethodMutations($method_id, $context);
} elseif ($context->collect_initializations) {
} else {
// collecting initalisations
$local_vars_in_scope = [];
$local_vars_possibly_in_scope = [];

View File

@ -652,7 +652,7 @@ class Populator
}
// 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_class) {
if (isset($storage->declaring_property_ids[$property_name])) {
continue;
}
@ -664,7 +664,7 @@ class Populator
continue;
}
$storage->declaring_property_ids[$property_name] = $declaring_property_id;
$storage->declaring_property_ids[$property_name] = $declaring_property_class;
}
// register where they're declared

View File

@ -47,11 +47,10 @@ class Properties
if (isset($class_storage->declaring_property_ids[$property_name])) {
if ($this->collect_references && $code_location) {
$declaring_property_id = $class_storage->declaring_property_ids[$property_name];
list($declaring_property_class, $declaring_property_name) = explode('::$', $declaring_property_id);
$declaring_property_class = $class_storage->declaring_property_ids[$property_name];
$declaring_class_storage = $this->classlike_storage_provider->get($declaring_property_class);
$declaring_property_storage = $declaring_class_storage->properties[$declaring_property_name];
$declaring_property_storage = $declaring_class_storage->properties[$property_name];
if ($declaring_property_storage->referencing_locations === null) {
$declaring_property_storage->referencing_locations = [];
@ -80,9 +79,7 @@ class Properties
$class_storage = $this->classlike_storage_provider->get($fq_class_name);
if (isset($class_storage->declaring_property_ids[$property_name])) {
$declaring_property_id = $class_storage->declaring_property_ids[$property_name];
return explode('::$', $declaring_property_id)[0];
return $class_storage->declaring_property_ids[$property_name];
}
}

View File

@ -115,7 +115,7 @@ class Reflection
$property_id = (string)$class_property->class . '::$' . $property_name;
$storage->declaring_property_ids[$property_name] = $property_id;
$storage->declaring_property_ids[$property_name] = (string)$class_property->class;
$storage->appearing_property_ids[$property_name] = $property_id;
if (!$class_property->isPrivate()) {
@ -131,7 +131,7 @@ class Reflection
$property_id = $class_name . '::$' . $property_name;
$storage->declaring_property_ids[$property_name] = $property_id;
$storage->declaring_property_ids[$property_name] = $class_name;
$storage->appearing_property_ids[$property_name] = $property_id;
$storage->inheritable_property_ids[$property_name] = $property_id;
}
@ -430,7 +430,7 @@ class Reflection
}
// 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_class) {
if (!$parent_storage->is_trait
&& isset($parent_storage->properties[$property_name])
&& $parent_storage->properties[$property_name]->visibility === ClassLikeChecker::VISIBILITY_PRIVATE
@ -438,7 +438,7 @@ class Reflection
continue;
}
$storage->declaring_property_ids[$property_name] = $declaring_property_id;
$storage->declaring_property_ids[$property_name] = $declaring_property_class;
}
// register where they're declared

View File

@ -65,11 +65,6 @@ class ClassLikeStorage
*/
public $stubbed = false;
/**
* @var bool
*/
public $all_properties_set_in_constructor = false;
/**
* @var bool
*/

View File

@ -691,7 +691,7 @@ class DependencyFinderVisitor extends PhpParser\NodeVisitorAbstract implements P
$property_id = $fq_classlike_name . '::$' . $property_name;
$storage->declaring_property_ids[$property_name] = $property_id;
$storage->declaring_property_ids[$property_name] = $fq_classlike_name;
$storage->appearing_property_ids[$property_name] = $property_id;
}
}
@ -1737,7 +1737,7 @@ class DependencyFinderVisitor extends PhpParser\NodeVisitorAbstract implements P
$property_id = $fq_classlike_name . '::$' . $property->name->name;
$storage->declaring_property_ids[$property->name->name] = $property_id;
$storage->declaring_property_ids[$property->name->name] = $fq_classlike_name;
$storage->appearing_property_ids[$property->name->name] = $property_id;
if ($property_is_initialized) {

View File

@ -1484,6 +1484,82 @@ class PropertyTypeTest extends TestCase
}',
'error_message' => 'PropertyNotSetInConstructor',
],
'privatePropertySameNameNotSetInConstructor' => [
'<?php
class A {
/** @var string */
private $b;
public function __construct() {
$this->b = "foo";
}
}
class B extends A {
/** @var string */
private $b;
}',
'error_message' => 'PropertyNotSetInConstructor',
],
'privateMethodCalledInParentConstructor' => [
'<?php
class C extends B {}
abstract class B extends A {
/** @var string */
private $b;
/** @var string */
protected $c;
}
class A {
public function __construct() {
$this->publicMethod();
}
publiic function publicMethod() : void {
$this->privateMethod();
}
private function privateMethod() : void {}
}',
'error_message' => 'MissingConstructor',
],
'privatePropertySetInParentConstructorReversedOrder' => [
'<?php
class B extends A {
/** @var string */
private $b;
}
class A {
public function __construct() {
if ($this instanceof B) {
$this->b = "foo";
}
}
}',
'error_message' => 'PropertyNotSetInConstructor',
],
'privatePropertySetInParentConstructor' => [
'<?php
class A {
public function __construct() {
if ($this instanceof B) {
$this->b = "foo";
}
}
}
class B extends A {
/** @var string */
private $b;
}
',
'error_message' => 'InaccessibleProperty',
],
'undefinedPropertyClass' => [
'<?php
class A {