1
0
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:
Matthew Brown 2019-03-01 17:30:55 -05:00
parent 4bbe13e28f
commit 5c76ab35c8
7 changed files with 104 additions and 35 deletions

View File

@ -23,6 +23,7 @@
<file name="vendor/phpunit/phpunit/src/Framework/TestCase.php"/>
<file name="vendor/symfony/console/Command/Command.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/b.test"/>
<file name="tests/ErrorBaselineTest.php"/>

View File

@ -965,7 +965,7 @@ class ClassAnalyzer extends ClassLikeAnalyzer
if (IssueBuffer::accepts(
new PropertyNotSetInConstructor(
'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_id
),

View File

@ -269,20 +269,22 @@ class FileAnalyzer extends SourceAnalyzer implements StatementsSource
*
* @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);
if (isset($this->class_analyzers_to_analyze[strtolower($fq_class_name)])) {
$class_analyzer_to_examine = $this->class_analyzers_to_analyze[strtolower($fq_class_name)];
} else {
$this->project_analyzer->getMethodMutations($method_id, $this_context);
if (!$from_project_analyzer) {
$this->project_analyzer->getMethodMutations($method_id, $this_context);
}
return;
}
$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->initialized_methods = $this_context->initialized_methods;
$call_context->include_location = $this_context->include_location;

View File

@ -846,7 +846,7 @@ class ProjectAnalyzer
$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->interface_analyzers_to_analyze = [];

View File

@ -105,43 +105,48 @@ class CallAnalyzer
$declaring_method_id = $codebase->methods->getDeclaringMethodId($method_id);
if (!$declaring_method_id) {
if (isset($context->vars_in_scope['$this'])) {
foreach ($context->vars_in_scope['$this']->getTypes() as $atomic_type) {
if ($atomic_type instanceof TNamedObject) {
if (isset($context->vars_in_scope['$this'])) {
foreach ($context->vars_in_scope['$this']->getTypes() as $atomic_type) {
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;
$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) {
break;
}
if ($alt_declaring_method_id) {
$declaring_method_id = $alt_declaring_method_id;
break;
}
if (!$atomic_type->extra_types) {
continue;
}
if (!$atomic_type->extra_types) {
continue;
}
foreach ($atomic_type->extra_types as $intersection_type) {
if ($intersection_type instanceof TNamedObject) {
$fq_class_name = $intersection_type->value;
$method_id = $fq_class_name . '::' . strtolower($method_name);
foreach ($atomic_type->extra_types as $intersection_type) {
if ($intersection_type instanceof TNamedObject) {
$fq_class_name = $intersection_type->value;
$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) {
break;
}
if ($alt_declaring_method_id) {
$declaring_method_id = $alt_declaring_method_id;
break 2;
}
}
}
}
}
}
if (!$declaring_method_id) {
// can happen for __call
return;
}
if (!$declaring_method_id) {
// can happen for __call
return;
}
if (isset($context->initialized_methods[$declaring_method_id])) {
@ -158,9 +163,7 @@ class CallAnalyzer
$class_analyzer = $source->getSource();
if ($class_analyzer instanceof ClassLikeAnalyzer &&
($method_storage->visibility === ClassLikeAnalyzer::VISIBILITY_PRIVATE || $method_storage->final)
) {
if ($class_analyzer instanceof ClassLikeAnalyzer && !$method_storage->is_static) {
$local_vars_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) {
$context->vars_in_scope[$var] = $type;

View File

@ -92,6 +92,7 @@ class MethodMutationTest extends TestCase
new FileAnalyzer($this->project_analyzer, 'somefile.php', 'somefile.php');
$this->project_analyzer->getCodebase()->scanFiles();
$method_context = new Context();
$method_context->collect_mutations = true;
$this->project_analyzer->getMethodMutations('FooController::barBar', $method_context);
$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');
$this->project_analyzer->getCodebase()->scanFiles();
$method_context = new Context();
$method_context->collect_mutations = true;
$this->project_analyzer->getMethodMutations('FooController::__construct', $method_context);
$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');
$this->project_analyzer->getCodebase()->scanFiles();
$method_context = new Context();
$method_context->collect_mutations = true;
$this->project_analyzer->getMethodMutations('FooController::__construct', $method_context);
$this->assertSame('Foo', (string)$method_context->vars_in_scope['$this->foo']);

View File

@ -613,8 +613,7 @@ class PropertyTypeTest extends TestCase
}
class B extends A {
public function foo(): void
{
public function foo(): void {
$this->bar = "hello";
}
}',
@ -623,6 +622,29 @@ class PropertyTypeTest extends TestCase
'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' => [
'<?php
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 {
$this->a = 5;
}
}
class B extends A {
protected function foo() : void {}
}',
'error_message' => 'PropertyNotSetInConstructor',
],
@ -1934,7 +1985,7 @@ class PropertyTypeTest extends TestCase
}
}
}',
'error_message' => 'PropertyNotSetInConstructor',
'error_message' => 'InaccessibleProperty',
],
'privatePropertySetInParentConstructor' => [
'<?php