mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 20:34:47 +01:00
Fix #3201 - add additional constraints on constructor initialisation
This commit is contained in:
parent
8f57d0c738
commit
1b752d06ab
@ -170,12 +170,19 @@ class Context
|
||||
public $collect_mutations = false;
|
||||
|
||||
/**
|
||||
* Whether or not to do a deep analysis and collect initializations from private methods
|
||||
* Whether or not to do a deep analysis and collect initializations from private or final methods
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $collect_initializations = false;
|
||||
|
||||
/**
|
||||
* Whether or not to do a deep analysis and collect initializations from public non-final methods
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $collect_nonprivate_initializations = false;
|
||||
|
||||
/**
|
||||
* Stored to prevent re-analysing methods when checking for initialised properties
|
||||
*
|
||||
|
@ -1147,6 +1147,7 @@ class ClassAnalyzer extends ClassLikeAnalyzer
|
||||
$uninitialized_variables = [];
|
||||
$uninitialized_properties = [];
|
||||
$uninitialized_typed_properties = [];
|
||||
$uninitialized_private_properties = false;
|
||||
|
||||
foreach ($storage->appearing_property_ids as $property_name => $appearing_property_id) {
|
||||
$property_class_name = $codebase->properties->getDeclaringClassForProperty(
|
||||
@ -1206,6 +1207,10 @@ class ClassAnalyzer extends ClassLikeAnalyzer
|
||||
strtolower($property_class_name) . '::$' . $property_name
|
||||
);
|
||||
|
||||
if ($property->visibility === ClassLikeAnalyzer::VISIBILITY_PRIVATE) {
|
||||
$uninitialized_private_properties = true;
|
||||
}
|
||||
|
||||
$uninitialized_variables[] = '$this->' . $property_name;
|
||||
$uninitialized_properties[$property_class_name . '::$' . $property_name] = $property;
|
||||
|
||||
@ -1305,6 +1310,7 @@ class ClassAnalyzer extends ClassLikeAnalyzer
|
||||
$was_collecting_initializations = $class_context->collect_initializations;
|
||||
|
||||
$class_context->collect_initializations = true;
|
||||
$class_context->collect_nonprivate_initializations = !$uninitialized_private_properties;
|
||||
|
||||
$constructor_analyzer = $this->analyzeClassMethod(
|
||||
$fake_stmt,
|
||||
@ -1324,6 +1330,7 @@ class ClassAnalyzer extends ClassLikeAnalyzer
|
||||
if ($constructor_analyzer) {
|
||||
$method_context = clone $class_context;
|
||||
$method_context->collect_initializations = true;
|
||||
$method_context->collect_nonprivate_initializations = !$uninitialized_private_properties;
|
||||
$method_context->self = $fq_class_name;
|
||||
|
||||
$this_atomic_object_type = new Type\Atomic\TNamedObject($fq_class_name);
|
||||
@ -1379,11 +1386,16 @@ class ClassAnalyzer extends ClassLikeAnalyzer
|
||||
&& $error_location
|
||||
&& (!$end_type->initialized || $property_storage !== $constructor_class_property_storage)
|
||||
) {
|
||||
$expected_visibility = $uninitialized_private_properties
|
||||
? 'private or final '
|
||||
: '';
|
||||
|
||||
if (IssueBuffer::accepts(
|
||||
new PropertyNotSetInConstructor(
|
||||
'Property ' . $class_storage->name . '::$' . $property_name
|
||||
. ' is not defined in constructor of ' .
|
||||
$this->fq_class_name . ' and in any methods called in the constructor',
|
||||
. ' is not defined in constructor of '
|
||||
. $this->fq_class_name . ' and in any ' . $expected_visibility
|
||||
. 'methods called in the constructor',
|
||||
$error_location,
|
||||
$property_id
|
||||
),
|
||||
|
@ -348,6 +348,7 @@ class FileAnalyzer extends SourceAnalyzer implements StatementsSource
|
||||
$call_context = new Context($this_context->self);
|
||||
$call_context->collect_mutations = $this_context->collect_mutations;
|
||||
$call_context->collect_initializations = $this_context->collect_initializations;
|
||||
$call_context->collect_nonprivate_initializations = $this_context->collect_nonprivate_initializations;
|
||||
$call_context->initialized_methods = $this_context->initialized_methods;
|
||||
$call_context->include_location = $this_context->include_location;
|
||||
$call_context->calling_method_id = $this_context->calling_method_id;
|
||||
|
@ -204,7 +204,12 @@ class CallAnalyzer
|
||||
|
||||
$class_analyzer = $source->getSource();
|
||||
|
||||
if ($class_analyzer instanceof ClassLikeAnalyzer && !$method_storage->is_static) {
|
||||
if ($class_analyzer instanceof ClassLikeAnalyzer
|
||||
&& !$method_storage->is_static
|
||||
&& ($context->collect_nonprivate_initializations
|
||||
|| $method_storage->visibility === ClassLikeAnalyzer::VISIBILITY_PRIVATE
|
||||
|| $method_storage->final)
|
||||
) {
|
||||
$local_vars_in_scope = [];
|
||||
$local_vars_possibly_in_scope = [];
|
||||
|
||||
|
@ -1513,7 +1513,7 @@ class PropertyTypeTest extends TestCase
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function overriddenByB(): void {
|
||||
protected final function overriddenByB(): void {
|
||||
$this->foo = 1;
|
||||
$this->bar = 1;
|
||||
}
|
||||
@ -1538,7 +1538,7 @@ class PropertyTypeTest extends TestCase
|
||||
/** @var int */
|
||||
protected $bar;
|
||||
|
||||
protected function overriddenByB(): void {
|
||||
protected final function overriddenByB(): void {
|
||||
$this->foo = 1;
|
||||
$this->bar = 1;
|
||||
}
|
||||
@ -3040,6 +3040,25 @@ class PropertyTypeTest extends TestCase
|
||||
}',
|
||||
'error_message' => 'InvalidPropertyAssignmentValue'
|
||||
],
|
||||
'overriddenConstructorCalledMethod' => [
|
||||
'<?php
|
||||
class ParentClass {
|
||||
private string $prop;
|
||||
|
||||
public function __construct() {
|
||||
$this->init();
|
||||
}
|
||||
|
||||
public function init(): void {
|
||||
$this->prop = "zxc";
|
||||
}
|
||||
}
|
||||
|
||||
class ChildClass extends ParentClass {
|
||||
public function init(): void {}
|
||||
}',
|
||||
'error_message' => 'PropertyNotSetInConstructor'
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -68,11 +68,11 @@ class PureAnnotationTest extends TestCase
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
function setOptions(array $options): void {
|
||||
public final function setOptions(array $options): void {
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
function setDefaultOptions(array $defaultOptions): void {
|
||||
public final function setDefaultOptions(array $defaultOptions): void {
|
||||
$this->defaultOptions = $defaultOptions;
|
||||
}
|
||||
}',
|
||||
|
Loading…
Reference in New Issue
Block a user