1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-12 01:09:38 +01:00
psalm/src/Psalm/Context.php

314 lines
8.0 KiB
PHP
Raw Normal View History

<?php
2016-07-26 00:37:44 +02:00
namespace Psalm;
class Context
{
/**
* @var array<string, Type\Union>
*/
public $vars_in_scope = [];
/**
* @var array<string, bool|string>
*/
public $vars_possibly_in_scope = [];
/**
* @var boolean
*/
public $in_loop = false;
/**
* @var string|null
*/
public $self;
/**
* @var string|null
*/
public $parent;
/**
* @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 = [];
/**
* A list of clauses in Conjunctive Normal Form
*
* @var array<Clause>
*/
public $clauses = [];
2017-01-12 03:37:53 +01:00
/**
* Whether or not to do a deep analysis and collect mutations to this context
*
* @var boolean
*/
public $collect_mutations = false;
/**
* Whether or not to do a deep analysis and collect initializations from private methods
*
* @var boolean
*/
public $collect_initializations = false;
/**
* @var array<string, Type\Union>
*/
public $constants = [];
/**
* Whether or not to track how many times a variable is used
*
* @var boolean
*/
public $count_references = false;
/**
* A list of variables that have been referenced
*
* @var array<string, bool>
*/
public $referenced_vars = [];
/**
* @param string|null $self
*/
public function __construct($self = null)
{
$this->self = $self;
}
2016-11-02 07:29:00 +01:00
/**
* @return void
*/
public function __clone()
{
2017-02-02 06:45:23 +01:00
foreach ($this->vars_in_scope as &$type) {
if ($type) {
$type = clone $type;
}
}
foreach ($this->clauses as &$clause) {
$clause = clone $clause;
}
foreach ($this->constants as &$constant) {
$constant = clone $constant;
}
}
/**
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
*
* @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
* @param array $vars_to_update
* @param array $updated_vars
* @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
) {
foreach ($this->vars_in_scope as $var => &$context_type) {
if ($start_context->hasVariable($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
if (in_array($var, $vars_to_update)) {
// if we're leaving, we're effectively deleting the possibility of the if types
$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
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
}
}
}
}
/**
* @param Context $original_context
* @param Context $new_context
* @return array<string,Type\Union>
*/
public static function getRedefinedVars(Context $original_context, Context $new_context)
{
$redefined_vars = [];
foreach ($original_context->vars_in_scope as $var => $context_type) {
if ($new_context->hasVariable($var) &&
2016-11-02 07:29:00 +01:00
(string)$new_context->vars_in_scope[$var] !== (string)$context_type
) {
$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)
{
unset($this->referenced_vars[$remove_var_id]);
2017-02-08 17:27:06 +01:00
unset($this->vars_possibly_in_scope[$remove_var_id]);
2016-09-17 17:57:44 +02:00
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);
}
}
/**
* @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;
}
$this->removeVarFromClauses($remove_var_id);
if ($type->hasArray() || $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-11-02 07:29:00 +01:00
/**
* @param Context $op_context
* @return void
*/
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;
}
/**
* @param string|null $var_name
* @return boolean
*/
public function hasVariable($var_name)
{
if ($this->count_references) {
if (!$var_name ||
(!isset($this->vars_possibly_in_scope[$var_name]) &&
!isset($this->vars_in_scope[$var_name]))
) {
return false;
}
$stripped_var = preg_replace('/(->|\[).*$/', '', $var_name);
if ($stripped_var[0] === '$' && $stripped_var !== '$this') {
$this->referenced_vars[$var_name] = true;
}
return isset($this->vars_in_scope[$var_name]);
}
return $var_name && isset($this->vars_in_scope[$var_name]);
}
}