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
|
|
|
|
|
|
|
class Context
|
|
|
|
{
|
2016-10-18 22:14:52 +02:00
|
|
|
/**
|
|
|
|
* @var array<string,Type\Union>
|
|
|
|
*/
|
2016-06-20 06:38:13 +02:00
|
|
|
public $vars_in_scope = [];
|
|
|
|
|
2016-10-18 22:14:52 +02:00
|
|
|
/**
|
|
|
|
* @var array<string,bool>
|
|
|
|
*/
|
2016-06-20 06:38:13 +02:00
|
|
|
public $vars_possibly_in_scope = [];
|
|
|
|
|
2016-10-18 22:14:52 +02:00
|
|
|
/**
|
|
|
|
* @var boolean
|
|
|
|
*/
|
2016-06-20 22:30:31 +02:00
|
|
|
public $in_loop = false;
|
|
|
|
|
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
|
|
|
/**
|
|
|
|
* @var string
|
|
|
|
*/
|
2016-08-14 03:14:32 +02:00
|
|
|
public $file_name;
|
|
|
|
|
2016-10-18 22:14:52 +02:00
|
|
|
/**
|
|
|
|
* @var boolean
|
|
|
|
*/
|
|
|
|
public $check_classes = true;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var boolean
|
|
|
|
*/
|
|
|
|
public $check_variables = true;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var boolean
|
|
|
|
*/
|
|
|
|
public $check_methods = true;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var boolean
|
|
|
|
*/
|
|
|
|
public $check_consts = true;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var boolean
|
|
|
|
*/
|
|
|
|
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>
|
|
|
|
*/
|
|
|
|
protected $phantom_classes = [];
|
|
|
|
|
2016-12-27 19:58:58 +01:00
|
|
|
/**
|
|
|
|
* A list of clauses in Conjunctive Normal Form
|
|
|
|
*
|
|
|
|
* @var array<Clause>
|
|
|
|
*/
|
|
|
|
public $clauses = [];
|
|
|
|
|
2016-08-14 03:14:32 +02:00
|
|
|
/**
|
|
|
|
* @param string $file_name
|
|
|
|
* @param string|null $self
|
|
|
|
*/
|
|
|
|
public function __construct($file_name, $self = null)
|
|
|
|
{
|
|
|
|
$this->file_name = $file_name;
|
|
|
|
$this->self = $self;
|
|
|
|
}
|
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
2016-06-20 06:38:13 +02:00
|
|
|
public function __clone()
|
|
|
|
{
|
|
|
|
foreach ($this->vars_in_scope as $key => &$type) {
|
2016-10-09 23:54:58 +02:00
|
|
|
if ($type) {
|
|
|
|
$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;
|
|
|
|
}
|
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
|
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) {
|
2016-10-02 17:08:15 +02:00
|
|
|
if (isset($start_context->vars_in_scope[$var])) {
|
|
|
|
$old_type = $start_context->vars_in_scope[$var];
|
|
|
|
|
|
|
|
// this is only true if there was some sort of type negation
|
|
|
|
if (in_array($var, $vars_to_update)) {
|
|
|
|
// if we're leaving, we're effectively deleting the possibility of the if types
|
2016-11-02 07:29:00 +01:00
|
|
|
$new_type = !$has_leaving_statements && isset($end_context->vars_in_scope[$var])
|
|
|
|
? $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
|
|
|
|
if ((string)$old_type !== (string)$new_type) {
|
|
|
|
$context_type->substitute($old_type, $new_type);
|
|
|
|
$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
|
|
|
/**
|
|
|
|
* @param Context $original_context
|
|
|
|
* @param Context $new_context
|
|
|
|
* @return array<string,Type\Union>
|
|
|
|
*/
|
2016-06-20 06:38:13 +02:00
|
|
|
public static function getRedefinedVars(Context $original_context, Context $new_context)
|
|
|
|
{
|
|
|
|
$redefined_vars = [];
|
|
|
|
|
|
|
|
foreach ($original_context->vars_in_scope as $var => $context_type) {
|
2016-11-02 07:29:00 +01:00
|
|
|
if (isset($new_context->vars_in_scope[$var]) &&
|
|
|
|
(string)$new_context->vars_in_scope[$var] !== (string)$context_type
|
|
|
|
) {
|
2016-06-20 06:38:13 +02:00
|
|
|
$redefined_vars[$var] = $new_context->vars_in_scope[$var];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $redefined_vars;
|
|
|
|
}
|
2016-09-17 17:57:44 +02:00
|
|
|
|
2016-10-30 17:46:18 +01:00
|
|
|
/**
|
|
|
|
* @param string $remove_var_id
|
|
|
|
* @return void
|
|
|
|
*/
|
2016-09-17 17:57:44 +02:00
|
|
|
public function remove($remove_var_id)
|
|
|
|
{
|
|
|
|
if (isset($this->vars_in_scope[$remove_var_id])) {
|
|
|
|
$type = $this->vars_in_scope[$remove_var_id];
|
|
|
|
unset($this->vars_in_scope[$remove_var_id]);
|
|
|
|
|
|
|
|
$this->removeDescendents($remove_var_id, $type);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-28 20:20:16 +01:00
|
|
|
/**
|
|
|
|
* @param string $remove_var_id
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function removeVarFromClauses($remove_var_id)
|
|
|
|
{
|
|
|
|
$clauses_to_keep = [];
|
|
|
|
|
|
|
|
foreach ($this->clauses as $clause) {
|
|
|
|
if (!isset($clause->possibilities[$remove_var_id])) {
|
|
|
|
$clauses_to_keep[] = $clause;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->clauses = $clauses_to_keep;
|
|
|
|
}
|
|
|
|
|
2016-10-30 17:46:18 +01:00
|
|
|
/**
|
|
|
|
* @param string $remove_var_id
|
|
|
|
* @param \Psalm\Type\Union|null $type
|
|
|
|
* @return void
|
|
|
|
*/
|
2016-09-17 17:57:44 +02:00
|
|
|
public function removeDescendents($remove_var_id, \Psalm\Type\Union $type = null)
|
|
|
|
{
|
|
|
|
if (!$type && isset($this->vars_in_scope[$remove_var_id])) {
|
|
|
|
$type = $this->vars_in_scope[$remove_var_id];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$type) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-12-28 20:20:16 +01:00
|
|
|
$this->removeVarFromClauses($remove_var_id);
|
2016-12-27 19:58:58 +01:00
|
|
|
|
2016-10-28 19:24:06 +02:00
|
|
|
if ($type->hasArray() || $type->isMixed()) {
|
2016-09-17 17:57:44 +02:00
|
|
|
$vars_to_remove = [];
|
|
|
|
|
|
|
|
foreach ($this->vars_in_scope as $var_id => $context_type) {
|
2016-10-02 05:10:15 +02:00
|
|
|
if (preg_match('/^' . preg_quote($remove_var_id, '/') . '[\[\-]/', $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
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
|
|
|
* @param Context $op_context
|
|
|
|
* @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
|
|
|
|
* @return bool
|
|
|
|
*/
|
2016-10-22 19:23:18 +02:00
|
|
|
public function isPhantomClass($class_name)
|
|
|
|
{
|
|
|
|
return isset($this->phantom_classes[$class_name]);
|
|
|
|
}
|
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
|
|
|
* @param string $class_name
|
|
|
|
* @return void
|
|
|
|
*/
|
2016-10-22 19:23:18 +02:00
|
|
|
public function addPhantomClass($class_name)
|
|
|
|
{
|
|
|
|
$this->phantom_classes[$class_name] = true;
|
|
|
|
}
|
2016-06-20 06:38:13 +02:00
|
|
|
}
|