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;
|
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
|
* @var bool
|
||||||
*/
|
*/
|
||||||
public $collect_initializations = false;
|
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
|
* Stored to prevent re-analysing methods when checking for initialised properties
|
||||||
*
|
*
|
||||||
|
@ -1147,6 +1147,7 @@ class ClassAnalyzer extends ClassLikeAnalyzer
|
|||||||
$uninitialized_variables = [];
|
$uninitialized_variables = [];
|
||||||
$uninitialized_properties = [];
|
$uninitialized_properties = [];
|
||||||
$uninitialized_typed_properties = [];
|
$uninitialized_typed_properties = [];
|
||||||
|
$uninitialized_private_properties = false;
|
||||||
|
|
||||||
foreach ($storage->appearing_property_ids as $property_name => $appearing_property_id) {
|
foreach ($storage->appearing_property_ids as $property_name => $appearing_property_id) {
|
||||||
$property_class_name = $codebase->properties->getDeclaringClassForProperty(
|
$property_class_name = $codebase->properties->getDeclaringClassForProperty(
|
||||||
@ -1206,6 +1207,10 @@ class ClassAnalyzer extends ClassLikeAnalyzer
|
|||||||
strtolower($property_class_name) . '::$' . $property_name
|
strtolower($property_class_name) . '::$' . $property_name
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if ($property->visibility === ClassLikeAnalyzer::VISIBILITY_PRIVATE) {
|
||||||
|
$uninitialized_private_properties = true;
|
||||||
|
}
|
||||||
|
|
||||||
$uninitialized_variables[] = '$this->' . $property_name;
|
$uninitialized_variables[] = '$this->' . $property_name;
|
||||||
$uninitialized_properties[$property_class_name . '::$' . $property_name] = $property;
|
$uninitialized_properties[$property_class_name . '::$' . $property_name] = $property;
|
||||||
|
|
||||||
@ -1305,6 +1310,7 @@ class ClassAnalyzer extends ClassLikeAnalyzer
|
|||||||
$was_collecting_initializations = $class_context->collect_initializations;
|
$was_collecting_initializations = $class_context->collect_initializations;
|
||||||
|
|
||||||
$class_context->collect_initializations = true;
|
$class_context->collect_initializations = true;
|
||||||
|
$class_context->collect_nonprivate_initializations = !$uninitialized_private_properties;
|
||||||
|
|
||||||
$constructor_analyzer = $this->analyzeClassMethod(
|
$constructor_analyzer = $this->analyzeClassMethod(
|
||||||
$fake_stmt,
|
$fake_stmt,
|
||||||
@ -1324,6 +1330,7 @@ class ClassAnalyzer extends ClassLikeAnalyzer
|
|||||||
if ($constructor_analyzer) {
|
if ($constructor_analyzer) {
|
||||||
$method_context = clone $class_context;
|
$method_context = clone $class_context;
|
||||||
$method_context->collect_initializations = true;
|
$method_context->collect_initializations = true;
|
||||||
|
$method_context->collect_nonprivate_initializations = !$uninitialized_private_properties;
|
||||||
$method_context->self = $fq_class_name;
|
$method_context->self = $fq_class_name;
|
||||||
|
|
||||||
$this_atomic_object_type = new Type\Atomic\TNamedObject($fq_class_name);
|
$this_atomic_object_type = new Type\Atomic\TNamedObject($fq_class_name);
|
||||||
@ -1379,11 +1386,16 @@ class ClassAnalyzer extends ClassLikeAnalyzer
|
|||||||
&& $error_location
|
&& $error_location
|
||||||
&& (!$end_type->initialized || $property_storage !== $constructor_class_property_storage)
|
&& (!$end_type->initialized || $property_storage !== $constructor_class_property_storage)
|
||||||
) {
|
) {
|
||||||
|
$expected_visibility = $uninitialized_private_properties
|
||||||
|
? 'private or final '
|
||||||
|
: '';
|
||||||
|
|
||||||
if (IssueBuffer::accepts(
|
if (IssueBuffer::accepts(
|
||||||
new PropertyNotSetInConstructor(
|
new PropertyNotSetInConstructor(
|
||||||
'Property ' . $class_storage->name . '::$' . $property_name
|
'Property ' . $class_storage->name . '::$' . $property_name
|
||||||
. ' is not defined in constructor of ' .
|
. ' is not defined in constructor of '
|
||||||
$this->fq_class_name . ' and in any methods called in the constructor',
|
. $this->fq_class_name . ' and in any ' . $expected_visibility
|
||||||
|
. 'methods called in the constructor',
|
||||||
$error_location,
|
$error_location,
|
||||||
$property_id
|
$property_id
|
||||||
),
|
),
|
||||||
|
@ -348,6 +348,7 @@ class FileAnalyzer extends SourceAnalyzer implements StatementsSource
|
|||||||
$call_context = new Context($this_context->self);
|
$call_context = new Context($this_context->self);
|
||||||
$call_context->collect_mutations = $this_context->collect_mutations;
|
$call_context->collect_mutations = $this_context->collect_mutations;
|
||||||
$call_context->collect_initializations = $this_context->collect_initializations;
|
$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->initialized_methods = $this_context->initialized_methods;
|
||||||
$call_context->include_location = $this_context->include_location;
|
$call_context->include_location = $this_context->include_location;
|
||||||
$call_context->calling_method_id = $this_context->calling_method_id;
|
$call_context->calling_method_id = $this_context->calling_method_id;
|
||||||
|
@ -204,7 +204,12 @@ class CallAnalyzer
|
|||||||
|
|
||||||
$class_analyzer = $source->getSource();
|
$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_in_scope = [];
|
||||||
$local_vars_possibly_in_scope = [];
|
$local_vars_possibly_in_scope = [];
|
||||||
|
|
||||||
|
@ -1513,7 +1513,7 @@ class PropertyTypeTest extends TestCase
|
|||||||
parent::__construct();
|
parent::__construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function overriddenByB(): void {
|
protected final function overriddenByB(): void {
|
||||||
$this->foo = 1;
|
$this->foo = 1;
|
||||||
$this->bar = 1;
|
$this->bar = 1;
|
||||||
}
|
}
|
||||||
@ -1538,7 +1538,7 @@ class PropertyTypeTest extends TestCase
|
|||||||
/** @var int */
|
/** @var int */
|
||||||
protected $bar;
|
protected $bar;
|
||||||
|
|
||||||
protected function overriddenByB(): void {
|
protected final function overriddenByB(): void {
|
||||||
$this->foo = 1;
|
$this->foo = 1;
|
||||||
$this->bar = 1;
|
$this->bar = 1;
|
||||||
}
|
}
|
||||||
@ -3040,6 +3040,25 @@ class PropertyTypeTest extends TestCase
|
|||||||
}',
|
}',
|
||||||
'error_message' => 'InvalidPropertyAssignmentValue'
|
'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;
|
return $this->options;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setOptions(array $options): void {
|
public final function setOptions(array $options): void {
|
||||||
$this->options = $options;
|
$this->options = $options;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setDefaultOptions(array $defaultOptions): void {
|
public final function setDefaultOptions(array $defaultOptions): void {
|
||||||
$this->defaultOptions = $defaultOptions;
|
$this->defaultOptions = $defaultOptions;
|
||||||
}
|
}
|
||||||
}',
|
}',
|
||||||
|
Loading…
Reference in New Issue
Block a user