1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-22 05:41:20 +01:00

Make Psalm a bit faster

This commit is contained in:
Matthew Brown 2017-07-08 21:19:16 -04:00
parent 95de80b7f1
commit c7b0f6685f
4 changed files with 108 additions and 16 deletions

View File

@ -589,7 +589,6 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
} }
} }
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 = self::getDeclaringClassForProperty($appearing_property_id); $property_class_name = self::getDeclaringClassForProperty($appearing_property_id);
$property_class_storage = self::$storage[strtolower((string)$property_class_name)]; $property_class_storage = self::$storage[strtolower((string)$property_class_name)];
@ -675,6 +674,7 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
} }
} }
} }
$class_context->include_location = $previous_context_include_location; $class_context->include_location = $previous_context_include_location;
} }
} }
@ -692,7 +692,20 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
$property = $property_class_storage->properties[$property_name]; $property = $property_class_storage->properties[$property_name];
if (!$property->has_default && $constructor_class_storage = null;
if (isset($property_class_storage->methods['__construct'])
&& $property_class_storage !== $storage
) {
$constructor_class_storage = $property_class_storage;
} elseif (!empty($property_class_storage->overridden_method_ids['__construct'])) {
list($construct_fqcln) =
explode('::', $property_class_storage->overridden_method_ids['__construct'][0]);
$constructor_class_storage = self::$storage[strtolower($construct_fqcln)];
}
if ((!$constructor_class_storage || !$constructor_class_storage->all_properties_set_in_constructor) &&
!$property->has_default &&
$property->type && $property->type &&
!$property->type->isMixed() && !$property->type->isMixed() &&
!$property->type->isNullable() && !$property->type->isNullable() &&
@ -704,10 +717,64 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
} }
} }
if ($uninitialized_properties if ($uninitialized_properties && !($this instanceof TraitChecker)) {
&& !($this instanceof TraitChecker) if (!$storage->abstract
&& !$storage->abstract && !$constructor_checker
) { && isset($storage->declaring_method_ids['__construct'])
) {
list($construct_fqcln) = explode('::', $storage->declaring_method_ids['__construct']);
$constructor_class_storage = self::$storage[strtolower($construct_fqcln)];
// ignore oldstyle constructors and classes without any declared properties
if (isset($constructor_class_storage->methods['__construct'])) {
$constructor_storage = self::$storage[strtolower($construct_fqcln)]->methods['__construct'];
$fake_constructor_params = array_map(
/** @return PhpParser\Node\Param */
function (\Psalm\FunctionLikeParameter $param) {
return (new PhpParser\Builder\Param($param->name))
->setTypehint((string)$param->signature_type)
->getNode();
},
$constructor_storage->params
);
$fake_constructor_stmt_args = array_map(
/** @return PhpParser\Node\Arg */
function (\Psalm\FunctionLikeParameter $param) {
return new PhpParser\Node\Arg(new PhpParser\Node\Expr\Variable($param->name));
},
$constructor_storage->params
);
$fake_constructor_stmts = [
new PhpParser\Node\Expr\StaticCall(
new PhpParser\Node\Name(['parent']),
'__construct',
$fake_constructor_stmt_args
),
];
$fake_stmt = new PhpParser\Node\Stmt\ClassMethod(
'__construct',
[
'type' => PhpParser\Node\Stmt\Class_::MODIFIER_PUBLIC,
'params' => $fake_constructor_params,
'stmts' => $fake_constructor_stmts,
]
);
$constructor_checker = $this->analyzeClassMethod(
$fake_stmt,
$this,
$class_context,
$global_context,
$update_docblocks
);
}
}
if ($constructor_checker) { if ($constructor_checker) {
$method_context = clone $class_context; $method_context = clone $class_context;
$method_context->collect_initializations = true; $method_context->collect_initializations = true;
@ -716,6 +783,8 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
$constructor_checker->analyze($method_context, $global_context, true); $constructor_checker->analyze($method_context, $global_context, true);
$all_properties_set_in_constructor = true;
foreach ($uninitialized_properties as $property_name => $property) { foreach ($uninitialized_properties as $property_name => $property) {
if (!isset($method_context->vars_in_scope['$this->' . $property_name])) { if (!isset($method_context->vars_in_scope['$this->' . $property_name])) {
throw new \UnexpectedValueException('$this->' . $property_name . ' should be in scope'); throw new \UnexpectedValueException('$this->' . $property_name . ' should be in scope');
@ -723,6 +792,10 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
$end_type = $method_context->vars_in_scope['$this->' . $property_name]; $end_type = $method_context->vars_in_scope['$this->' . $property_name];
if (!$end_type->initialized) {
$all_properties_set_in_constructor = false;
}
if (!$end_type->initialized && $property->location) { if (!$end_type->initialized && $property->location) {
$property_id = $this->fq_class_name . '::$' . $property_name; $property_id = $this->fq_class_name . '::$' . $property_name;
@ -738,7 +811,9 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
} }
} }
} }
} else {
$storage->all_properties_set_in_constructor = $all_properties_set_in_constructor;
} elseif (!$storage->abstract) {
$first_uninitialized_property = array_shift($uninitialized_properties); $first_uninitialized_property = array_shift($uninitialized_properties);
if ($first_uninitialized_property->location) { if ($first_uninitialized_property->location) {
@ -826,7 +901,7 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
$global_context ? clone $global_context : null $global_context ? clone $global_context : null
); );
if ($config->reportIssueInFile('InvalidReturnType', $source->getFilePath())) { if ($stmt->name !== '__construct' && $config->reportIssueInFile('InvalidReturnType', $source->getFilePath())) {
$return_type_location = null; $return_type_location = null;
$secondary_return_type_location = null; $secondary_return_type_location = null;

View File

@ -332,8 +332,11 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
$this->getMethodId() $this->getMethodId()
); );
$context->vars_in_scope['$' . $function_param->name] = $param_type;
$context->vars_possibly_in_scope['$' . $function_param->name] = true;
if (!$function_param->location) { if (!$function_param->location) {
throw new \UnexpectedValueException('We should know where this code is'); continue;
} }
$parser_param = $this->function->getParams()[$offset]; $parser_param = $this->function->getParams()[$offset];
@ -386,9 +389,6 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
} }
} }
$context->vars_in_scope['$' . $function_param->name] = $param_type;
$context->vars_possibly_in_scope['$' . $function_param->name] = true;
if ($function_param->by_ref && !$param_type->isMixed()) { if ($function_param->by_ref && !$param_type->isMixed()) {
$context->byref_constraints['$' . $function_param->name] = new \Psalm\ReferenceConstraint($param_type); $context->byref_constraints['$' . $function_param->name] = new \Psalm\ReferenceConstraint($param_type);
} }

View File

@ -37,6 +37,11 @@ class ClassLikeStorage
*/ */
public $reflected = false; public $reflected = false;
/**
* @var bool
*/
public $all_properties_set_in_constructor = false;
/** /**
* @var bool * @var bool
*/ */

View File

@ -390,18 +390,20 @@ class PropertyTypeTest extends TestCase
} }
}', }',
], ],
'SKIPPED-abstractClassConstructorAndImplicitChildConstructor' => [ 'abstractClassConstructorAndImplicitChildConstructor' => [
'<?php '<?php
abstract class A { abstract class A {
/** @var string */ /** @var string */
public $foo; public $foo;
public function __construct() { public function __construct(int $bar) {
$this->foo = ""; $this->foo = (string)$bar;
} }
} }
class B extends A {}', class B extends A {}
class E extends \Exception{}',
], ],
]; ];
} }
@ -657,6 +659,16 @@ class PropertyTypeTest extends TestCase
}', }',
'error_message' => 'MissingConstructor', 'error_message' => 'MissingConstructor',
], ],
'abstractClassInheritsNoConstructor' => [
'<?php
abstract class A {
/** @var string */
public $foo;
}
class B extends A {}',
'error_message' => 'MissingConstructor',
],
'notSetInAllBranchesOfIf' => [ 'notSetInAllBranchesOfIf' => [
'<?php '<?php
class A { class A {