2016-06-20 06:38:13 +02:00
|
|
|
<?php
|
2016-07-26 00:37:44 +02:00
|
|
|
namespace Psalm;
|
2016-06-20 06:38:13 +02:00
|
|
|
|
2017-09-04 02:52:54 +02:00
|
|
|
use PhpParser;
|
2017-06-23 06:39:37 +02:00
|
|
|
use Psalm\Checker\StatementsChecker;
|
2017-09-04 02:52:54 +02:00
|
|
|
use Psalm\Storage\FunctionLikeStorage;
|
2017-12-29 16:55:41 +01:00
|
|
|
use Psalm\Type\Reconciler;
|
2017-05-19 06:48:26 +02:00
|
|
|
use Psalm\Type\Union;
|
2017-04-02 21:26:10 +02:00
|
|
|
|
2016-06-20 06:38:13 +02:00
|
|
|
class Context
|
|
|
|
{
|
2016-10-18 22:14:52 +02:00
|
|
|
/**
|
2017-01-02 06:08:35 +01:00
|
|
|
* @var array<string, Type\Union>
|
2016-10-18 22:14:52 +02:00
|
|
|
*/
|
2016-06-20 06:38:13 +02:00
|
|
|
public $vars_in_scope = [];
|
|
|
|
|
2016-10-18 22:14:52 +02:00
|
|
|
/**
|
2017-12-02 19:32:20 +01:00
|
|
|
* @var array<string, bool>
|
2016-10-18 22:14:52 +02:00
|
|
|
*/
|
2016-06-20 06:38:13 +02:00
|
|
|
public $vars_possibly_in_scope = [];
|
|
|
|
|
2017-03-13 16:23:26 +01:00
|
|
|
/**
|
|
|
|
* Whether or not we're inside the conditional of an if/where etc.
|
|
|
|
*
|
|
|
|
* This changes whether or not the context is cloned
|
|
|
|
*
|
2017-05-27 02:16:18 +02:00
|
|
|
* @var bool
|
2017-03-13 16:23:26 +01:00
|
|
|
*/
|
|
|
|
public $inside_conditional = false;
|
2016-06-20 22:30:31 +02:00
|
|
|
|
2017-04-15 05:26:58 +02:00
|
|
|
/**
|
|
|
|
* Whether or not we're inside a __construct function
|
|
|
|
*
|
2017-05-27 02:16:18 +02:00
|
|
|
* @var bool
|
2017-04-15 05:26:58 +02:00
|
|
|
*/
|
|
|
|
public $inside_constructor = false;
|
|
|
|
|
2018-01-08 05:59:17 +01:00
|
|
|
/**
|
|
|
|
* Whether or not we're inside an isset call
|
|
|
|
*
|
|
|
|
* Inside isssets Psalm is more lenient about certain things
|
|
|
|
*
|
|
|
|
* @var bool
|
|
|
|
*/
|
|
|
|
public $inside_isset = false;
|
|
|
|
|
2017-06-21 20:22:52 +02:00
|
|
|
/**
|
|
|
|
* @var ?CodeLocation
|
|
|
|
*/
|
|
|
|
public $include_location = null;
|
|
|
|
|
2016-10-18 22:14:52 +02:00
|
|
|
/**
|
|
|
|
* @var string|null
|
|
|
|
*/
|
2016-08-08 17:28:14 +02:00
|
|
|
public $self;
|
|
|
|
|
2016-10-18 22:14:52 +02:00
|
|
|
/**
|
|
|
|
* @var string|null
|
|
|
|
*/
|
2016-08-08 17:28:14 +02:00
|
|
|
public $parent;
|
|
|
|
|
2016-10-18 22:14:52 +02:00
|
|
|
/**
|
2017-05-27 02:16:18 +02:00
|
|
|
* @var bool
|
2016-10-18 22:14:52 +02:00
|
|
|
*/
|
|
|
|
public $check_classes = true;
|
|
|
|
|
|
|
|
/**
|
2017-05-27 02:16:18 +02:00
|
|
|
* @var bool
|
2016-10-18 22:14:52 +02:00
|
|
|
*/
|
|
|
|
public $check_variables = true;
|
|
|
|
|
|
|
|
/**
|
2017-05-27 02:16:18 +02:00
|
|
|
* @var bool
|
2016-10-18 22:14:52 +02:00
|
|
|
*/
|
|
|
|
public $check_methods = true;
|
|
|
|
|
|
|
|
/**
|
2017-05-27 02:16:18 +02:00
|
|
|
* @var bool
|
2016-10-18 22:14:52 +02:00
|
|
|
*/
|
|
|
|
public $check_consts = true;
|
|
|
|
|
|
|
|
/**
|
2017-05-27 02:16:18 +02:00
|
|
|
* @var bool
|
2016-10-18 22:14:52 +02:00
|
|
|
*/
|
|
|
|
public $check_functions = true;
|
|
|
|
|
2016-10-22 19:23:18 +02:00
|
|
|
/**
|
|
|
|
* A list of classes checked with class_exists
|
2016-11-02 07:29:00 +01:00
|
|
|
*
|
2016-10-22 19:23:18 +02:00
|
|
|
* @var array<string,bool>
|
|
|
|
*/
|
2017-02-12 00:56:38 +01:00
|
|
|
private $phantom_classes = [];
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2016-12-27 19:58:58 +01:00
|
|
|
/**
|
|
|
|
* A list of clauses in Conjunctive Normal Form
|
|
|
|
*
|
2017-03-18 19:04:26 +01:00
|
|
|
* @var array<int, Clause>
|
2016-12-27 19:58:58 +01:00
|
|
|
*/
|
|
|
|
public $clauses = [];
|
|
|
|
|
2017-01-12 03:37:53 +01:00
|
|
|
/**
|
|
|
|
* Whether or not to do a deep analysis and collect mutations to this context
|
|
|
|
*
|
2017-05-27 02:16:18 +02:00
|
|
|
* @var bool
|
2017-01-12 03:37:53 +01:00
|
|
|
*/
|
|
|
|
public $collect_mutations = false;
|
|
|
|
|
2017-01-27 07:23:12 +01:00
|
|
|
/**
|
|
|
|
* Whether or not to do a deep analysis and collect initializations from private methods
|
|
|
|
*
|
2017-05-27 02:16:18 +02:00
|
|
|
* @var bool
|
2017-01-27 07:23:12 +01:00
|
|
|
*/
|
|
|
|
public $collect_initializations = false;
|
|
|
|
|
2017-01-15 21:58:40 +01:00
|
|
|
/**
|
|
|
|
* @var array<string, Type\Union>
|
|
|
|
*/
|
|
|
|
public $constants = [];
|
|
|
|
|
2017-02-01 05:24:33 +01:00
|
|
|
/**
|
|
|
|
* Whether or not to track how many times a variable is used
|
|
|
|
*
|
2017-05-27 02:16:18 +02:00
|
|
|
* @var bool
|
2017-02-01 05:24:33 +01:00
|
|
|
*/
|
2017-02-27 05:09:18 +01:00
|
|
|
public $collect_references = false;
|
2017-02-01 05:24:33 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* A list of variables that have been referenced
|
|
|
|
*
|
|
|
|
* @var array<string, bool>
|
|
|
|
*/
|
2017-11-24 18:17:28 +01:00
|
|
|
public $referenced_var_ids = [];
|
2017-02-01 05:24:33 +01:00
|
|
|
|
2017-02-23 06:25:28 +01:00
|
|
|
/**
|
|
|
|
* A list of variables that have been passed by reference (where we know their type)
|
|
|
|
*
|
|
|
|
* @var array<string, \Psalm\ReferenceConstraint>|null
|
|
|
|
*/
|
|
|
|
public $byref_constraints;
|
|
|
|
|
2017-03-18 20:24:14 +01:00
|
|
|
/**
|
|
|
|
* If this context inherits from a context, it is here
|
|
|
|
*
|
|
|
|
* @var Context|null
|
|
|
|
*/
|
|
|
|
public $parent_context;
|
|
|
|
|
2017-09-03 00:15:52 +02:00
|
|
|
/**
|
|
|
|
* @var array<string, Type\Union>
|
|
|
|
*/
|
|
|
|
public $possible_param_types = [];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A list of vars that have been assigned to
|
|
|
|
*
|
|
|
|
* @var array<string, bool>
|
|
|
|
*/
|
2017-11-25 17:21:45 +01:00
|
|
|
public $assigned_var_ids = [];
|
2017-09-03 00:15:52 +02:00
|
|
|
|
2017-12-06 06:56:00 +01:00
|
|
|
/**
|
|
|
|
* @var bool
|
|
|
|
*/
|
|
|
|
public $is_global = false;
|
|
|
|
|
2017-12-17 16:58:03 +01:00
|
|
|
/**
|
|
|
|
* @var array<string, bool>
|
|
|
|
*/
|
|
|
|
public $protected_var_ids = [];
|
|
|
|
|
2016-08-14 03:14:32 +02:00
|
|
|
/**
|
|
|
|
* @param string|null $self
|
|
|
|
*/
|
2017-01-17 00:33:04 +01:00
|
|
|
public function __construct($self = null)
|
2016-08-14 03:14:32 +02:00
|
|
|
{
|
|
|
|
$this->self = $self;
|
|
|
|
}
|
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
2016-06-20 06:38:13 +02:00
|
|
|
public function __clone()
|
|
|
|
{
|
2017-02-02 06:45:23 +01:00
|
|
|
foreach ($this->vars_in_scope as &$type) {
|
2017-11-28 06:46:41 +01:00
|
|
|
$type = clone $type;
|
2016-06-20 06:38:13 +02:00
|
|
|
}
|
2016-12-27 19:58:58 +01:00
|
|
|
|
|
|
|
foreach ($this->clauses as &$clause) {
|
|
|
|
$clause = clone $clause;
|
|
|
|
}
|
2017-01-15 21:58:40 +01:00
|
|
|
|
|
|
|
foreach ($this->constants as &$constant) {
|
|
|
|
$constant = clone $constant;
|
|
|
|
}
|
2016-06-20 06:38:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2016-11-02 07:29:00 +01:00
|
|
|
* Updates the parent context, looking at the changes within a block and then applying those changes, where
|
|
|
|
* necessary, to the parent context
|
2016-06-20 06:38:13 +02:00
|
|
|
*
|
2016-10-09 23:54:58 +02:00
|
|
|
* @param Context $start_context
|
|
|
|
* @param Context $end_context
|
2016-11-02 07:29:00 +01:00
|
|
|
* @param bool $has_leaving_statements whether or not the parent scope is abandoned between
|
|
|
|
* $start_context and $end_context
|
2016-10-09 23:54:58 +02:00
|
|
|
* @param array $vars_to_update
|
|
|
|
* @param array $updated_vars
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-06-20 06:38:13 +02:00
|
|
|
* @return void
|
|
|
|
*/
|
2016-11-02 07:29:00 +01:00
|
|
|
public function update(
|
|
|
|
Context $start_context,
|
|
|
|
Context $end_context,
|
|
|
|
$has_leaving_statements,
|
|
|
|
array $vars_to_update,
|
|
|
|
array &$updated_vars
|
|
|
|
) {
|
2016-06-20 06:38:13 +02:00
|
|
|
foreach ($this->vars_in_scope as $var => &$context_type) {
|
2017-02-11 01:08:55 +01:00
|
|
|
if (isset($start_context->vars_in_scope[$var])) {
|
2016-10-02 17:08:15 +02:00
|
|
|
$old_type = $start_context->vars_in_scope[$var];
|
|
|
|
|
|
|
|
// this is only true if there was some sort of type negation
|
2017-05-27 02:05:57 +02:00
|
|
|
if (in_array($var, $vars_to_update, true)) {
|
2016-10-02 17:08:15 +02:00
|
|
|
// if we're leaving, we're effectively deleting the possibility of the if types
|
2017-02-01 05:24:33 +01:00
|
|
|
$new_type = !$has_leaving_statements && $end_context->hasVariable($var)
|
2016-11-02 07:29:00 +01:00
|
|
|
? $end_context->vars_in_scope[$var]
|
|
|
|
: null;
|
2016-10-02 17:08:15 +02:00
|
|
|
|
|
|
|
// if the type changed within the block of statements, process the replacement
|
2017-03-11 18:05:23 +01:00
|
|
|
// also never allow ourselves to remove all types from a union
|
2017-12-03 18:44:08 +01:00
|
|
|
if ((!$new_type || $old_type->getId() !== $new_type->getId())
|
2018-01-09 21:05:48 +01:00
|
|
|
&& ($new_type || count($context_type->getTypes()) > 1)
|
2017-12-03 18:44:08 +01:00
|
|
|
) {
|
2016-10-02 17:08:15 +02:00
|
|
|
$context_type->substitute($old_type, $new_type);
|
2017-12-03 07:24:47 +01:00
|
|
|
|
|
|
|
if ($new_type && $new_type->from_docblock) {
|
|
|
|
$context_type->setFromDocblock();
|
|
|
|
}
|
|
|
|
|
2016-10-02 17:08:15 +02:00
|
|
|
$updated_vars[$var] = true;
|
|
|
|
}
|
2016-08-10 07:54:45 +02:00
|
|
|
}
|
2016-06-20 06:38:13 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-09 23:54:58 +02:00
|
|
|
/**
|
2017-12-03 00:28:18 +01:00
|
|
|
* @param array<string, Type\Union> $vars_in_scope
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-10-09 23:54:58 +02:00
|
|
|
* @return array<string,Type\Union>
|
|
|
|
*/
|
2017-12-03 00:28:18 +01:00
|
|
|
public function getRedefinedVars(array $vars_in_scope)
|
2016-06-20 06:38:13 +02:00
|
|
|
{
|
|
|
|
$redefined_vars = [];
|
|
|
|
|
2017-12-03 00:28:18 +01:00
|
|
|
foreach ($vars_in_scope as $var => $context_type) {
|
2017-06-29 06:28:37 +02:00
|
|
|
if (!isset($this->vars_in_scope[$var])) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this_var = $this->vars_in_scope[$var];
|
|
|
|
|
2017-12-03 00:28:18 +01:00
|
|
|
if (!$this_var->failed_reconciliation
|
|
|
|
&& !$this_var->isEmpty()
|
|
|
|
&& !$context_type->isEmpty()
|
2017-12-03 18:44:08 +01:00
|
|
|
&& $this_var->getId() !== $context_type->getId()
|
2016-11-02 07:29:00 +01:00
|
|
|
) {
|
2017-06-29 06:28:37 +02:00
|
|
|
$redefined_vars[$var] = $this_var;
|
2016-06-20 06:38:13 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $redefined_vars;
|
|
|
|
}
|
2016-09-17 17:57:44 +02:00
|
|
|
|
2017-09-04 02:52:54 +02:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function inferType(
|
|
|
|
PhpParser\Node\Expr $expr,
|
|
|
|
FunctionLikeStorage $function_storage,
|
|
|
|
Type\Union $inferred_type
|
|
|
|
) {
|
|
|
|
if (!isset($expr->inferredType)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$expr_type = $expr->inferredType;
|
|
|
|
|
2017-12-03 18:44:08 +01:00
|
|
|
if (($expr_type->isMixed() || $expr_type->getId() === $inferred_type->getId())
|
2017-09-04 02:52:54 +02:00
|
|
|
&& $expr instanceof PhpParser\Node\Expr\Variable
|
|
|
|
&& is_string($expr->name)
|
2017-11-25 17:21:45 +01:00
|
|
|
&& !isset($this->assigned_var_ids['$' . $expr->name])
|
2017-09-04 02:52:54 +02:00
|
|
|
&& array_key_exists($expr->name, $function_storage->param_types)
|
|
|
|
&& !$function_storage->param_types[$expr->name]
|
|
|
|
) {
|
|
|
|
if (isset($this->possible_param_types[$expr->name])) {
|
|
|
|
$this->possible_param_types[$expr->name] = Type::combineUnionTypes(
|
|
|
|
$this->possible_param_types[$expr->name],
|
|
|
|
$inferred_type
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
$this->possible_param_types[$expr->name] = $inferred_type;
|
|
|
|
$this->vars_in_scope['$' . $expr->name] = clone $inferred_type;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-15 01:14:25 +01:00
|
|
|
/**
|
|
|
|
* @param Context $original_context
|
|
|
|
* @param Context $new_context
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2017-03-15 01:14:25 +01:00
|
|
|
* @return array<int, string>
|
|
|
|
*/
|
|
|
|
public static function getNewOrUpdatedVarIds(Context $original_context, Context $new_context)
|
|
|
|
{
|
|
|
|
$redefined_var_ids = [];
|
|
|
|
|
|
|
|
foreach ($new_context->vars_in_scope as $var_id => $context_type) {
|
|
|
|
if (!isset($original_context->vars_in_scope[$var_id]) ||
|
2017-12-03 18:44:08 +01:00
|
|
|
$original_context->vars_in_scope[$var_id]->getId() !== $context_type->getId()
|
2017-03-15 01:14:25 +01:00
|
|
|
) {
|
|
|
|
$redefined_var_ids[] = $var_id;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $redefined_var_ids;
|
|
|
|
}
|
|
|
|
|
2016-10-30 17:46:18 +01:00
|
|
|
/**
|
|
|
|
* @param string $remove_var_id
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-10-30 17:46:18 +01:00
|
|
|
* @return void
|
|
|
|
*/
|
2016-09-17 17:57:44 +02:00
|
|
|
public function remove($remove_var_id)
|
|
|
|
{
|
2017-05-27 00:26:14 +02:00
|
|
|
unset(
|
2017-11-24 18:17:28 +01:00
|
|
|
$this->referenced_var_ids[$remove_var_id],
|
2017-05-27 00:26:14 +02:00
|
|
|
$this->vars_possibly_in_scope[$remove_var_id]
|
|
|
|
);
|
2017-02-01 05:24:33 +01:00
|
|
|
|
2016-09-17 17:57:44 +02:00
|
|
|
if (isset($this->vars_in_scope[$remove_var_id])) {
|
2017-04-02 21:26:10 +02:00
|
|
|
$existing_type = $this->vars_in_scope[$remove_var_id];
|
2016-09-17 17:57:44 +02:00
|
|
|
unset($this->vars_in_scope[$remove_var_id]);
|
|
|
|
|
2017-04-02 21:26:10 +02:00
|
|
|
$this->removeDescendents($remove_var_id, $existing_type);
|
2016-09-17 17:57:44 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-28 20:20:16 +01:00
|
|
|
/**
|
2017-11-28 06:46:41 +01:00
|
|
|
* @param string[] $changed_var_ids
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-12-28 20:20:16 +01:00
|
|
|
* @return void
|
|
|
|
*/
|
2017-11-28 06:46:41 +01:00
|
|
|
public function removeReconciledClauses(array $changed_var_ids)
|
|
|
|
{
|
|
|
|
$this->clauses = array_filter(
|
|
|
|
$this->clauses,
|
|
|
|
/** @return bool */
|
|
|
|
function (Clause $c) use ($changed_var_ids) {
|
|
|
|
return count($c->possibilities) > 1
|
|
|
|
|| !in_array(array_keys($c->possibilities)[0], $changed_var_ids, true);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $remove_var_id
|
|
|
|
* @param Clause[] $clauses
|
|
|
|
* @param Union|null $new_type
|
|
|
|
* @param StatementsChecker|null $statements_checker
|
|
|
|
*
|
|
|
|
* @return Clause[]
|
|
|
|
*/
|
|
|
|
public static function filterClauses(
|
2017-04-02 21:26:10 +02:00
|
|
|
$remove_var_id,
|
2017-11-28 06:46:41 +01:00
|
|
|
array $clauses,
|
2017-04-02 21:26:10 +02:00
|
|
|
Union $new_type = null,
|
2017-06-23 06:39:37 +02:00
|
|
|
StatementsChecker $statements_checker = null
|
2017-04-02 21:26:10 +02:00
|
|
|
) {
|
2017-12-03 18:44:08 +01:00
|
|
|
$new_type_string = $new_type ? $new_type->getId() : '';
|
2017-04-02 21:26:10 +02:00
|
|
|
|
2017-11-28 06:46:41 +01:00
|
|
|
$clauses_to_keep = [];
|
|
|
|
|
|
|
|
foreach ($clauses as $clause) {
|
2017-04-02 21:26:10 +02:00
|
|
|
\Psalm\Checker\AlgebraChecker::calculateNegation($clause);
|
|
|
|
|
2017-12-15 23:34:21 +01:00
|
|
|
$quoted_remove_var_id = preg_quote($remove_var_id);
|
|
|
|
|
|
|
|
foreach ($clause->possibilities as $var_id => $_) {
|
|
|
|
if (preg_match('/^' . $quoted_remove_var_id . '[\[\-]/', $var_id)) {
|
|
|
|
break 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-02 21:26:10 +02:00
|
|
|
if (!isset($clause->possibilities[$remove_var_id]) ||
|
|
|
|
$clause->possibilities[$remove_var_id] === [$new_type_string]
|
|
|
|
) {
|
2016-12-28 20:20:16 +01:00
|
|
|
$clauses_to_keep[] = $clause;
|
2017-06-23 06:39:37 +02:00
|
|
|
} elseif ($statements_checker &&
|
2017-04-02 22:51:27 +02:00
|
|
|
$new_type &&
|
2017-04-03 01:06:18 +02:00
|
|
|
!$new_type->isMixed()
|
2017-04-02 22:51:27 +02:00
|
|
|
) {
|
2017-04-02 21:26:10 +02:00
|
|
|
$type_changed = false;
|
|
|
|
|
|
|
|
// if the clause contains any possibilities that would be altered
|
2017-12-02 23:57:58 +01:00
|
|
|
// by the new type
|
2017-04-03 01:06:18 +02:00
|
|
|
foreach ($clause->possibilities[$remove_var_id] as $type) {
|
|
|
|
// empty and !empty are not definitive for arrays and scalar types
|
2017-10-22 17:57:41 +02:00
|
|
|
if (($type === '!falsy' || $type === 'falsy') &&
|
2017-04-03 01:06:18 +02:00
|
|
|
($new_type->hasArray() || $new_type->hasNumericType())
|
|
|
|
) {
|
|
|
|
$type_changed = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2017-12-29 16:55:41 +01:00
|
|
|
$result_type = Reconciler::reconcileTypes(
|
2017-04-02 21:26:10 +02:00
|
|
|
$type,
|
|
|
|
clone $new_type,
|
|
|
|
null,
|
2017-06-23 06:39:37 +02:00
|
|
|
$statements_checker,
|
2017-04-02 21:26:10 +02:00
|
|
|
null,
|
|
|
|
[],
|
|
|
|
$failed_reconciliation
|
|
|
|
);
|
|
|
|
|
2017-12-03 18:44:08 +01:00
|
|
|
if ($result_type->getId() !== $new_type_string) {
|
2017-04-02 21:26:10 +02:00
|
|
|
$type_changed = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$type_changed) {
|
|
|
|
$clauses_to_keep[] = $clause;
|
|
|
|
}
|
2016-12-28 20:20:16 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-28 06:46:41 +01:00
|
|
|
return $clauses_to_keep;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $remove_var_id
|
|
|
|
* @param Union|null $new_type
|
|
|
|
* @param ?StatementsChecker $statements_checker
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function removeVarFromConflictingClauses(
|
|
|
|
$remove_var_id,
|
|
|
|
Union $new_type = null,
|
|
|
|
StatementsChecker $statements_checker = null
|
|
|
|
) {
|
|
|
|
$this->clauses = self::filterClauses($remove_var_id, $this->clauses, $new_type, $statements_checker);
|
2017-03-18 20:24:14 +01:00
|
|
|
|
|
|
|
if ($this->parent_context) {
|
2017-04-02 21:26:10 +02:00
|
|
|
$this->parent_context->removeVarFromConflictingClauses($remove_var_id);
|
2017-03-18 20:24:14 +01:00
|
|
|
}
|
2016-12-28 20:20:16 +01:00
|
|
|
}
|
|
|
|
|
2016-10-30 17:46:18 +01:00
|
|
|
/**
|
|
|
|
* @param string $remove_var_id
|
2017-04-02 21:26:10 +02:00
|
|
|
* @param \Psalm\Type\Union|null $existing_type
|
|
|
|
* @param \Psalm\Type\Union|null $new_type
|
2017-06-23 06:39:37 +02:00
|
|
|
* @param ?StatementsChecker $statements_checker
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-10-30 17:46:18 +01:00
|
|
|
* @return void
|
|
|
|
*/
|
2017-04-02 21:26:10 +02:00
|
|
|
public function removeDescendents(
|
|
|
|
$remove_var_id,
|
|
|
|
Union $existing_type = null,
|
|
|
|
Union $new_type = null,
|
2017-06-23 06:39:37 +02:00
|
|
|
StatementsChecker $statements_checker = null
|
2017-04-02 21:26:10 +02:00
|
|
|
) {
|
|
|
|
if (!$existing_type && isset($this->vars_in_scope[$remove_var_id])) {
|
|
|
|
$existing_type = $this->vars_in_scope[$remove_var_id];
|
2016-09-17 17:57:44 +02:00
|
|
|
}
|
|
|
|
|
2017-04-02 21:26:10 +02:00
|
|
|
if (!$existing_type) {
|
2016-09-17 17:57:44 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-11-28 06:46:41 +01:00
|
|
|
if ($this->clauses) {
|
|
|
|
$this->removeVarFromConflictingClauses(
|
|
|
|
$remove_var_id,
|
2017-12-02 23:57:58 +01:00
|
|
|
$existing_type->isMixed()
|
|
|
|
|| ($new_type && $existing_type->from_docblock !== $new_type->from_docblock)
|
|
|
|
? null
|
|
|
|
: $new_type,
|
2017-11-28 06:46:41 +01:00
|
|
|
$statements_checker
|
|
|
|
);
|
|
|
|
}
|
2016-12-27 19:58:58 +01:00
|
|
|
|
2017-04-02 21:26:10 +02:00
|
|
|
if ($existing_type->hasArray() || $existing_type->isMixed()) {
|
2016-09-17 17:57:44 +02:00
|
|
|
$vars_to_remove = [];
|
|
|
|
|
2017-02-02 06:45:23 +01:00
|
|
|
foreach ($this->vars_in_scope as $var_id => $_) {
|
2017-01-18 04:10:21 +01:00
|
|
|
if (preg_match('/^' . preg_quote($remove_var_id, DIRECTORY_SEPARATOR) . '[\[\-]/', $var_id)) {
|
2016-09-17 17:57:44 +02:00
|
|
|
$vars_to_remove[] = $var_id;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($vars_to_remove as $var_id) {
|
|
|
|
unset($this->vars_in_scope[$var_id]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-10-18 22:14:52 +02:00
|
|
|
|
2017-04-15 03:32:14 +02:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function removeAllObjectVars()
|
|
|
|
{
|
|
|
|
$vars_to_remove = [];
|
|
|
|
|
|
|
|
foreach ($this->vars_in_scope as $var_id => $_) {
|
|
|
|
if (strpos($var_id, '->') !== false || strpos($var_id, '::') !== false) {
|
|
|
|
$vars_to_remove[] = $var_id;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$vars_to_remove) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($vars_to_remove as $var_id) {
|
|
|
|
unset($this->vars_in_scope[$var_id]);
|
|
|
|
}
|
|
|
|
|
|
|
|
$clauses_to_keep = [];
|
|
|
|
|
|
|
|
foreach ($this->clauses as $clause) {
|
|
|
|
$abandon_clause = false;
|
|
|
|
|
|
|
|
foreach (array_keys($clause->possibilities) as $key) {
|
|
|
|
if (strpos($key, '->') !== false || strpos($key, '::') !== false) {
|
|
|
|
$abandon_clause = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$abandon_clause) {
|
|
|
|
$clauses_to_keep[] = $clause;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->clauses = $clauses_to_keep;
|
|
|
|
|
|
|
|
if ($this->parent_context) {
|
|
|
|
$this->parent_context->removeAllObjectVars();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
|
|
|
* @param Context $op_context
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-11-02 07:29:00 +01:00
|
|
|
* @return void
|
|
|
|
*/
|
2016-10-18 22:14:52 +02:00
|
|
|
public function updateChecks(Context $op_context)
|
|
|
|
{
|
|
|
|
$this->check_classes = $this->check_classes && $op_context->check_classes;
|
|
|
|
$this->check_variables = $this->check_variables && $op_context->check_variables;
|
|
|
|
$this->check_methods = $this->check_methods && $op_context->check_methods;
|
|
|
|
$this->check_functions = $this->check_functions && $op_context->check_functions;
|
|
|
|
$this->check_consts = $this->check_consts && $op_context->check_consts;
|
|
|
|
}
|
2016-10-22 19:23:18 +02:00
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
|
|
|
* @param string $class_name
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-11-02 07:29:00 +01:00
|
|
|
* @return bool
|
|
|
|
*/
|
2016-10-22 19:23:18 +02:00
|
|
|
public function isPhantomClass($class_name)
|
|
|
|
{
|
2017-03-01 17:56:36 +01:00
|
|
|
return isset($this->phantom_classes[strtolower($class_name)]);
|
2016-10-22 19:23:18 +02:00
|
|
|
}
|
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
|
|
|
* @param string $class_name
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-11-02 07:29:00 +01:00
|
|
|
* @return void
|
|
|
|
*/
|
2016-10-22 19:23:18 +02:00
|
|
|
public function addPhantomClass($class_name)
|
|
|
|
{
|
2017-03-01 17:56:36 +01:00
|
|
|
$this->phantom_classes[strtolower($class_name)] = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return array<string, bool>
|
|
|
|
*/
|
|
|
|
public function getPhantomClasses()
|
|
|
|
{
|
|
|
|
return $this->phantom_classes;
|
2016-10-22 19:23:18 +02:00
|
|
|
}
|
2017-02-01 05:24:33 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string|null $var_name
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
|
|
|
* @return bool
|
2017-02-01 05:24:33 +01:00
|
|
|
*/
|
|
|
|
public function hasVariable($var_name)
|
|
|
|
{
|
2017-11-28 06:46:41 +01:00
|
|
|
if (!$var_name ||
|
|
|
|
(!isset($this->vars_possibly_in_scope[$var_name]) &&
|
|
|
|
!isset($this->vars_in_scope[$var_name]))
|
|
|
|
) {
|
|
|
|
return false;
|
|
|
|
}
|
2017-02-01 05:24:33 +01:00
|
|
|
|
2017-11-28 06:46:41 +01:00
|
|
|
$stripped_var = preg_replace('/(->|\[).*$/', '', $var_name);
|
2017-02-01 05:24:33 +01:00
|
|
|
|
2017-11-28 06:46:41 +01:00
|
|
|
if ($stripped_var[0] === '$' && $stripped_var !== '$this') {
|
|
|
|
$this->referenced_var_ids[$var_name] = true;
|
2017-02-01 05:24:33 +01:00
|
|
|
}
|
|
|
|
|
2017-11-28 06:46:41 +01:00
|
|
|
return isset($this->vars_in_scope[$var_name]);
|
2017-02-01 05:24:33 +01:00
|
|
|
}
|
2016-06-20 06:38:13 +02:00
|
|
|
}
|