mirror of
https://github.com/danog/psalm.git
synced 2025-01-22 05:41:20 +01:00
Allow properties to be set regardless of visibility
This commit is contained in:
parent
4bbe13e28f
commit
5c76ab35c8
@ -23,6 +23,7 @@
|
|||||||
<file name="vendor/phpunit/phpunit/src/Framework/TestCase.php"/>
|
<file name="vendor/phpunit/phpunit/src/Framework/TestCase.php"/>
|
||||||
<file name="vendor/symfony/console/Command/Command.php" />
|
<file name="vendor/symfony/console/Command/Command.php" />
|
||||||
<file name="src/Psalm/Internal/Traverser/CustomTraverser.php"/>
|
<file name="src/Psalm/Internal/Traverser/CustomTraverser.php"/>
|
||||||
|
<file name="vendor/felixfbecker/advanced-json-rpc/lib/Dispatcher.php" />
|
||||||
<file name="tests/performance/a.test"/>
|
<file name="tests/performance/a.test"/>
|
||||||
<file name="tests/performance/b.test"/>
|
<file name="tests/performance/b.test"/>
|
||||||
<file name="tests/ErrorBaselineTest.php"/>
|
<file name="tests/ErrorBaselineTest.php"/>
|
||||||
|
@ -965,7 +965,7 @@ class ClassAnalyzer extends ClassLikeAnalyzer
|
|||||||
if (IssueBuffer::accepts(
|
if (IssueBuffer::accepts(
|
||||||
new PropertyNotSetInConstructor(
|
new PropertyNotSetInConstructor(
|
||||||
'Property ' . $property_id . ' is not defined in constructor of ' .
|
'Property ' . $property_id . ' is not defined in constructor of ' .
|
||||||
$this->fq_class_name . ' or in any private methods called in the constructor',
|
$this->fq_class_name . ' or in any methods called in the constructor',
|
||||||
$property_storage->location,
|
$property_storage->location,
|
||||||
$property_id
|
$property_id
|
||||||
),
|
),
|
||||||
|
@ -269,20 +269,22 @@ class FileAnalyzer extends SourceAnalyzer implements StatementsSource
|
|||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function getMethodMutations($method_id, Context $this_context)
|
public function getMethodMutations($method_id, Context $this_context, bool $from_project_analyzer = false)
|
||||||
{
|
{
|
||||||
list($fq_class_name, $method_name) = explode('::', $method_id);
|
list($fq_class_name, $method_name) = explode('::', $method_id);
|
||||||
|
|
||||||
if (isset($this->class_analyzers_to_analyze[strtolower($fq_class_name)])) {
|
if (isset($this->class_analyzers_to_analyze[strtolower($fq_class_name)])) {
|
||||||
$class_analyzer_to_examine = $this->class_analyzers_to_analyze[strtolower($fq_class_name)];
|
$class_analyzer_to_examine = $this->class_analyzers_to_analyze[strtolower($fq_class_name)];
|
||||||
} else {
|
} else {
|
||||||
$this->project_analyzer->getMethodMutations($method_id, $this_context);
|
if (!$from_project_analyzer) {
|
||||||
|
$this->project_analyzer->getMethodMutations($method_id, $this_context);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$call_context = new Context($this_context->self);
|
$call_context = new Context($this_context->self);
|
||||||
$call_context->collect_mutations = true;
|
$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->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;
|
||||||
|
@ -846,7 +846,7 @@ class ProjectAnalyzer
|
|||||||
$this_context->vars_in_scope['$this'] = Type::parseString($fq_class_name);
|
$this_context->vars_in_scope['$this'] = Type::parseString($fq_class_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
$file_analyzer->getMethodMutations($appearing_method_id, $this_context);
|
$file_analyzer->getMethodMutations($appearing_method_id, $this_context, true);
|
||||||
|
|
||||||
$file_analyzer->class_analyzers_to_analyze = [];
|
$file_analyzer->class_analyzers_to_analyze = [];
|
||||||
$file_analyzer->interface_analyzers_to_analyze = [];
|
$file_analyzer->interface_analyzers_to_analyze = [];
|
||||||
|
@ -105,43 +105,48 @@ class CallAnalyzer
|
|||||||
|
|
||||||
$declaring_method_id = $codebase->methods->getDeclaringMethodId($method_id);
|
$declaring_method_id = $codebase->methods->getDeclaringMethodId($method_id);
|
||||||
|
|
||||||
if (!$declaring_method_id) {
|
if (isset($context->vars_in_scope['$this'])) {
|
||||||
if (isset($context->vars_in_scope['$this'])) {
|
foreach ($context->vars_in_scope['$this']->getTypes() as $atomic_type) {
|
||||||
foreach ($context->vars_in_scope['$this']->getTypes() as $atomic_type) {
|
if ($atomic_type instanceof TNamedObject) {
|
||||||
if ($atomic_type instanceof TNamedObject) {
|
if ($fq_class_name === $atomic_type->value) {
|
||||||
|
$alt_declaring_method_id = $declaring_method_id;
|
||||||
|
} else {
|
||||||
$fq_class_name = $atomic_type->value;
|
$fq_class_name = $atomic_type->value;
|
||||||
|
|
||||||
$method_id = $fq_class_name . '::' . strtolower($method_name);
|
$method_id = $fq_class_name . '::' . strtolower($method_name);
|
||||||
|
|
||||||
$declaring_method_id = $codebase->methods->getDeclaringMethodId($method_id);
|
$alt_declaring_method_id = $codebase->methods->getDeclaringMethodId($method_id);
|
||||||
|
}
|
||||||
|
|
||||||
if ($declaring_method_id) {
|
if ($alt_declaring_method_id) {
|
||||||
break;
|
$declaring_method_id = $alt_declaring_method_id;
|
||||||
}
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (!$atomic_type->extra_types) {
|
if (!$atomic_type->extra_types) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($atomic_type->extra_types as $intersection_type) {
|
foreach ($atomic_type->extra_types as $intersection_type) {
|
||||||
if ($intersection_type instanceof TNamedObject) {
|
if ($intersection_type instanceof TNamedObject) {
|
||||||
$fq_class_name = $intersection_type->value;
|
$fq_class_name = $intersection_type->value;
|
||||||
$method_id = $fq_class_name . '::' . strtolower($method_name);
|
$method_id = $fq_class_name . '::' . strtolower($method_name);
|
||||||
|
|
||||||
$declaring_method_id = $codebase->methods->getDeclaringMethodId($method_id);
|
$alt_declaring_method_id = $codebase->methods->getDeclaringMethodId($method_id);
|
||||||
|
|
||||||
if ($declaring_method_id) {
|
if ($alt_declaring_method_id) {
|
||||||
break;
|
$declaring_method_id = $alt_declaring_method_id;
|
||||||
}
|
break 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!$declaring_method_id) {
|
if (!$declaring_method_id) {
|
||||||
// can happen for __call
|
// can happen for __call
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($context->initialized_methods[$declaring_method_id])) {
|
if (isset($context->initialized_methods[$declaring_method_id])) {
|
||||||
@ -158,9 +163,7 @@ class CallAnalyzer
|
|||||||
|
|
||||||
$class_analyzer = $source->getSource();
|
$class_analyzer = $source->getSource();
|
||||||
|
|
||||||
if ($class_analyzer instanceof ClassLikeAnalyzer &&
|
if ($class_analyzer instanceof ClassLikeAnalyzer && !$method_storage->is_static) {
|
||||||
($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 = [];
|
||||||
|
|
||||||
@ -176,7 +179,16 @@ class CallAnalyzer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$class_analyzer->getMethodMutations(strtolower($method_name), $context);
|
if ($fq_class_name === $source->getFQCLN()) {
|
||||||
|
$class_analyzer->getMethodMutations(strtolower($method_name), $context);
|
||||||
|
} else {
|
||||||
|
list($declaring_fq_class_name) = explode('::', $declaring_method_id);
|
||||||
|
|
||||||
|
$old_self = $context->self;
|
||||||
|
$context->self = $declaring_fq_class_name;
|
||||||
|
$project_analyzer->getMethodMutations($declaring_method_id, $context);
|
||||||
|
$context->self = $old_self;
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($local_vars_in_scope as $var => $type) {
|
foreach ($local_vars_in_scope as $var => $type) {
|
||||||
$context->vars_in_scope[$var] = $type;
|
$context->vars_in_scope[$var] = $type;
|
||||||
|
@ -92,6 +92,7 @@ class MethodMutationTest extends TestCase
|
|||||||
new FileAnalyzer($this->project_analyzer, 'somefile.php', 'somefile.php');
|
new FileAnalyzer($this->project_analyzer, 'somefile.php', 'somefile.php');
|
||||||
$this->project_analyzer->getCodebase()->scanFiles();
|
$this->project_analyzer->getCodebase()->scanFiles();
|
||||||
$method_context = new Context();
|
$method_context = new Context();
|
||||||
|
$method_context->collect_mutations = true;
|
||||||
$this->project_analyzer->getMethodMutations('FooController::barBar', $method_context);
|
$this->project_analyzer->getMethodMutations('FooController::barBar', $method_context);
|
||||||
|
|
||||||
$this->assertSame('UserViewData', (string)$method_context->vars_in_scope['$this->user_viewdata']);
|
$this->assertSame('UserViewData', (string)$method_context->vars_in_scope['$this->user_viewdata']);
|
||||||
@ -129,6 +130,7 @@ class MethodMutationTest extends TestCase
|
|||||||
new FileAnalyzer($this->project_analyzer, 'somefile.php', 'somefile.php');
|
new FileAnalyzer($this->project_analyzer, 'somefile.php', 'somefile.php');
|
||||||
$this->project_analyzer->getCodebase()->scanFiles();
|
$this->project_analyzer->getCodebase()->scanFiles();
|
||||||
$method_context = new Context();
|
$method_context = new Context();
|
||||||
|
$method_context->collect_mutations = true;
|
||||||
$this->project_analyzer->getMethodMutations('FooController::__construct', $method_context);
|
$this->project_analyzer->getMethodMutations('FooController::__construct', $method_context);
|
||||||
|
|
||||||
$this->assertSame('Foo', (string)$method_context->vars_in_scope['$this->foo']);
|
$this->assertSame('Foo', (string)$method_context->vars_in_scope['$this->foo']);
|
||||||
@ -165,6 +167,7 @@ class MethodMutationTest extends TestCase
|
|||||||
new FileAnalyzer($this->project_analyzer, 'somefile.php', 'somefile.php');
|
new FileAnalyzer($this->project_analyzer, 'somefile.php', 'somefile.php');
|
||||||
$this->project_analyzer->getCodebase()->scanFiles();
|
$this->project_analyzer->getCodebase()->scanFiles();
|
||||||
$method_context = new Context();
|
$method_context = new Context();
|
||||||
|
$method_context->collect_mutations = true;
|
||||||
$this->project_analyzer->getMethodMutations('FooController::__construct', $method_context);
|
$this->project_analyzer->getMethodMutations('FooController::__construct', $method_context);
|
||||||
|
|
||||||
$this->assertSame('Foo', (string)$method_context->vars_in_scope['$this->foo']);
|
$this->assertSame('Foo', (string)$method_context->vars_in_scope['$this->foo']);
|
||||||
|
@ -613,8 +613,7 @@ class PropertyTypeTest extends TestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
class B extends A {
|
class B extends A {
|
||||||
public function foo(): void
|
public function foo(): void {
|
||||||
{
|
|
||||||
$this->bar = "hello";
|
$this->bar = "hello";
|
||||||
}
|
}
|
||||||
}',
|
}',
|
||||||
@ -623,6 +622,29 @@ class PropertyTypeTest extends TestCase
|
|||||||
'PropertyNotSetInConstructor' => Config::REPORT_INFO,
|
'PropertyNotSetInConstructor' => Config::REPORT_INFO,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
'callsPrivateParentMethodThenUsesParentInitializedProperty' => [
|
||||||
|
'<?php
|
||||||
|
abstract class A {
|
||||||
|
/** @var string */
|
||||||
|
public $bar;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->setBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function setBar(): void {
|
||||||
|
$this->bar = "hello";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class B extends A {
|
||||||
|
public function __construct() {
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
echo $this->bar;
|
||||||
|
}
|
||||||
|
}',
|
||||||
|
],
|
||||||
'setInFinalMethod' => [
|
'setInFinalMethod' => [
|
||||||
'<?php
|
'<?php
|
||||||
class C
|
class C
|
||||||
@ -1421,6 +1443,31 @@ class PropertyTypeTest extends TestCase
|
|||||||
}
|
}
|
||||||
}'
|
}'
|
||||||
],
|
],
|
||||||
|
'propertySetInProtectedMethodWithConstant' => [
|
||||||
|
'<?php
|
||||||
|
class A {
|
||||||
|
/** @var int */
|
||||||
|
public $a;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->foo();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function foo(): void {
|
||||||
|
$this->a = 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class B extends A {
|
||||||
|
const HELLO = "HELLO";
|
||||||
|
|
||||||
|
protected function foo() : void {
|
||||||
|
$this->a = 6;
|
||||||
|
|
||||||
|
echo self::HELLO;
|
||||||
|
}
|
||||||
|
}',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1843,6 +1890,10 @@ class PropertyTypeTest extends TestCase
|
|||||||
protected function foo(): void {
|
protected function foo(): void {
|
||||||
$this->a = 5;
|
$this->a = 5;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class B extends A {
|
||||||
|
protected function foo() : void {}
|
||||||
}',
|
}',
|
||||||
'error_message' => 'PropertyNotSetInConstructor',
|
'error_message' => 'PropertyNotSetInConstructor',
|
||||||
],
|
],
|
||||||
@ -1934,7 +1985,7 @@ class PropertyTypeTest extends TestCase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}',
|
}',
|
||||||
'error_message' => 'PropertyNotSetInConstructor',
|
'error_message' => 'InaccessibleProperty',
|
||||||
],
|
],
|
||||||
'privatePropertySetInParentConstructor' => [
|
'privatePropertySetInParentConstructor' => [
|
||||||
'<?php
|
'<?php
|
||||||
|
Loading…
x
Reference in New Issue
Block a user