1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-21 21:31:13 +01:00

Fix #1309 - no PropertyNotSetInConstructor warnings for grandchild of class

This commit is contained in:
Matthew Brown 2019-02-10 15:01:10 -05:00
parent 6976528c7e
commit 62fb8035bf
9 changed files with 238 additions and 82 deletions

View File

@ -519,6 +519,27 @@ class ClassAnalyzer extends ClassLikeAnalyzer
)
: $property_type;
/**
* @psalm-suppress ReferenceConstraintViolation
*/
$class_template_params = MethodCallAnalyzer::getClassTemplateParams(
$codebase,
$property_class_storage,
$fq_class_name,
null,
new Type\Atomic\TNamedObject($fq_class_name),
'$this'
);
if ($class_template_params) {
$generic_params = [];
$fleshed_out_type->replaceTemplateTypesWithStandins(
$class_template_params,
$generic_params,
$codebase
);
}
if ($property_type_location && !$fleshed_out_type->isMixed()) {
$fleshed_out_type->check(
$this,
@ -834,11 +855,11 @@ class ClassAnalyzer extends ClassLikeAnalyzer
$fake_constructor_stmts = [
new PhpParser\Node\Stmt\Expression(
new PhpParser\Node\Expr\StaticCall(
new PhpParser\Node\Name(['parent']),
new PhpParser\Node\Name\FullyQualified($constructor_declaring_fqcln),
new PhpParser\Node\Identifier('__construct'),
$fake_constructor_stmt_args,
[
'line' => $class->extends->getLine(),
'startLine' => $class->extends->getLine(),
'startFilePos' => $class->extends->getAttribute('startFilePos'),
'endFilePos' => $class->extends->getAttribute('endFilePos'),
]
@ -852,6 +873,11 @@ class ClassAnalyzer extends ClassLikeAnalyzer
'type' => PhpParser\Node\Stmt\Class_::MODIFIER_PUBLIC,
'params' => $fake_constructor_params,
'stmts' => $fake_constructor_stmts,
],
[
'startLine' => $class->extends->getLine(),
'startFilePos' => $class->extends->getAttribute('startFilePos'),
'endFilePos' => $class->extends->getAttribute('endFilePos'),
]
);

View File

@ -8,6 +8,7 @@ use PhpParser\Node\Stmt\Function_;
use Psalm\Codebase;
use Psalm\Internal\Analyzer\FunctionLike\ReturnTypeAnalyzer;
use Psalm\Internal\Analyzer\FunctionLike\ReturnTypeCollector;
use Psalm\Internal\Analyzer\Statements\Expression\Call\MethodCallAnalyzer;
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
use Psalm\Internal\Codebase\CallMap;
use Psalm\CodeLocation;

View File

@ -434,7 +434,6 @@ class PropertyAssignmentAnalyzer
}
}
$declaring_property_class = (string) $codebase->properties->getDeclaringClassForProperty(
$property_id
);

View File

@ -248,7 +248,8 @@ class AssignmentAnalyzer
new ReferenceConstraintViolation(
'Variable ' . $var_id . ' is limited to values of type '
. $context->byref_constraints[$var_id]->type
. ' because it is passed by reference',
. ' because it is passed by reference, '
. $assign_value_type->getId() . ' type found',
new CodeLocation($statements_analyzer->getSource(), $assign_var)
),
$statements_analyzer->getSuppressedIssues()

View File

@ -1087,7 +1087,7 @@ class MethodCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\
Codebase $codebase,
ClassLikeStorage $class_storage,
string $fq_class_name,
string $method_name,
string $method_name = null,
Type\Atomic $lhs_type_part = null,
string $lhs_var_id = null
) {
@ -1097,6 +1097,7 @@ class MethodCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\
if (!$template_types) {
if ($calling_class_storage->template_type_extends
&& $method_name
&& !empty($class_storage->overridden_method_ids[$method_name])
&& !isset($class_storage->methods[$method_name]->return_type)
) {

View File

@ -84,82 +84,6 @@ class StaticCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\
$class_storage = $codebase->classlike_storage_provider->get($fq_class_name);
$fq_class_name = $class_storage->name;
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_analyzer->getSource(), $stmt),
$method_id
),
$statements_analyzer->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_analyzer->getSource(), $stmt);
$context->self = $appearing_method_class_name;
if ($context->collect_mutations) {
$file_analyzer->getMethodMutations($method_id, $context);
} else {
// collecting initializations
$local_vars_in_scope = [];
$local_vars_possibly_in_scope = [];
foreach ($context->vars_in_scope as $var => $_) {
if (strpos($var, '$this->') !== 0 && $var !== '$this') {
$local_vars_in_scope[$var] = $context->vars_in_scope[$var];
}
}
foreach ($context->vars_possibly_in_scope as $var => $_) {
if (strpos($var, '$this->') !== 0 && $var !== '$this') {
$local_vars_possibly_in_scope[$var] = $context->vars_possibly_in_scope[$var];
}
}
if (!isset($context->initialized_methods[$method_id])) {
if ($context->initialized_methods === null) {
$context->initialized_methods = [];
}
$context->initialized_methods[$method_id] = true;
$file_analyzer->getMethodMutations($method_id, $context);
foreach ($local_vars_in_scope as $var => $type) {
$context->vars_in_scope[$var] = $type;
}
foreach ($local_vars_possibly_in_scope as $var => $type) {
$context->vars_possibly_in_scope[$var] = $type;
}
}
}
$context->include_location = $old_context_include_location;
$context->self = $old_self;
if (isset($context->vars_in_scope['$this']) && $old_self) {
$context->vars_in_scope['$this'] = Type::parseString($old_self);
}
}
} elseif ($context->self) {
if ($stmt->class->parts[0] === 'static' && isset($context->vars_in_scope['$this'])) {
$fq_class_name = (string) $context->vars_in_scope['$this'];
@ -543,6 +467,82 @@ class StaticCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\
$class_storage = $codebase->classlike_storage_provider->get($fq_class_name);
if ($class_storage->user_defined
&& $context->self
&& ($context->collect_mutations || $context->collect_initializations)
) {
$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_analyzer->getSource(), $stmt),
$method_id
),
$statements_analyzer->getSuppressedIssues()
)) {
return false;
}
return;
}
list($appearing_method_class_name) = explode('::', $appearing_method_id);
if ($codebase->classExtends($context->self, $appearing_method_class_name)) {
$old_context_include_location = $context->include_location;
$old_self = $context->self;
$context->include_location = new CodeLocation($statements_analyzer->getSource(), $stmt);
$context->self = $appearing_method_class_name;
if ($context->collect_mutations) {
$file_analyzer->getMethodMutations($method_id, $context);
} else {
// collecting initializations
$local_vars_in_scope = [];
$local_vars_possibly_in_scope = [];
foreach ($context->vars_in_scope as $var => $_) {
if (strpos($var, '$this->') !== 0 && $var !== '$this') {
$local_vars_in_scope[$var] = $context->vars_in_scope[$var];
}
}
foreach ($context->vars_possibly_in_scope as $var => $_) {
if (strpos($var, '$this->') !== 0 && $var !== '$this') {
$local_vars_possibly_in_scope[$var] = $context->vars_possibly_in_scope[$var];
}
}
if (!isset($context->initialized_methods[$method_id])) {
if ($context->initialized_methods === null) {
$context->initialized_methods = [];
}
$context->initialized_methods[$method_id] = true;
$file_analyzer->getMethodMutations($method_id, $context);
foreach ($local_vars_in_scope as $var => $type) {
$context->vars_in_scope[$var] = $type;
}
foreach ($local_vars_possibly_in_scope as $var => $type) {
$context->vars_possibly_in_scope[$var] = $type;
}
}
}
$context->include_location = $old_context_include_location;
$context->self = $old_self;
if (isset($context->vars_in_scope['$this']) && $old_self) {
$context->vars_in_scope['$this'] = Type::parseString($old_self);
}
}
}
if ($class_storage->deprecated) {
if (IssueBuffer::accepts(
new DeprecatedClass(

View File

@ -97,6 +97,10 @@ class IssueBuffer
}
}
if ($e->getLocation()->getLineNumber() === -1) {
return false;
}
if (self::$recording_level > 0) {
self::$recorded_issues[self::$recording_level][] = $e;

View File

@ -1278,6 +1278,40 @@ class PropertyTypeTest extends TestCase
}
}'
],
'propertySetInGrandparentExplicitly' => [
'<?php
class A {
/**
* @var string
*/
public $s;
public function __construct(string $s) {
$this->s = $s;
}
}
class B extends A {}
class C extends B {
public function __construct(string $s) {
A::__construct($s);
}
}'
],
'propertySetInGrandparentImplicitly' => [
'<?php
class A {
/**
* @var string
*/
public $s;
public function __construct(string $s) {
$this->s = $s;
}
}
class B extends A {}
class C extends B {}'
],
];
}
@ -1655,7 +1689,7 @@ class PropertyTypeTest extends TestCase
}
class B extends A {}',
'error_message' => 'InaccessibleMethod',
'error_message' => 'InaccessibleMethod - src/somefile.php:11',
],
'classInheritsPrivateConstructorWithImplementedConstructor' => [
'<?php

View File

@ -1250,6 +1250,96 @@ class TemplateExtendsTest extends TestCase
'$b' => 'AContainer<A>',
],
],
'returnParentExtendedTemplateProperty' => [
'<?php
/**
* @template T
*/
class Container {
/**
* @var T
*/
public $t;
/**
* @param T $t
*/
public function __construct($t) {
$this->t = $t;
}
}
/**
* @template-extends Container<int>
*/
class IntContainer extends Container {
public function __construct(int $i) {
parent::__construct($i);
}
public function getValue() : int {
return $this->t;
}
}',
],
'childSetInConstructor' => [
'<?php
/**
* @template T0
*/
class Container {
/**
* @var T0
*/
public $t;
/**
* @param T0 $t
*/
public function __construct($t) {
$this->t = $t;
}
}
/**
* @template T1 as object
* @template-extends Container<T1>
*/
class ObjectContainer extends Container {}',
],
'grandChildSetInConstructor' => [
'<?php
/**
* @template T0
*/
class Container {
/**
* @var T0
*/
public $t;
/**
* @param T0 $t
*/
public function __construct($t) {
$this->t = $t;
}
}
/**
* @template T1 as object
* @template-extends Container<T1>
*/
class ObjectContainer extends Container {}
/**
* @template T2 as A
* @template-extends ObjectContainer<T2>
*/
class AContainer extends ObjectContainer {}
class A {}',
],
];
}