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

133 lines
4.1 KiB
PHP
Raw Normal View History

<?php
2016-07-26 00:37:44 +02:00
namespace Psalm;
2016-09-17 17:57:44 +02:00
use PhpParser;
use Psalm\Checker\StatementsChecker;
class Context
{
2016-10-03 17:38:59 +02:00
/** @var array<string,Type\Union> */
public $vars_in_scope = [];
2016-10-03 17:38:59 +02:00
/** @var array<string,bool> */
public $vars_possibly_in_scope = [];
/** @var boolean */
public $in_loop = false;
/** @var string|null */
public $self;
/** @var string|null */
public $parent;
/** @var string */
public $file_name;
/**
* @param string $file_name
* @param string|null $self
*/
public function __construct($file_name, $self = null)
{
$this->file_name = $file_name;
$this->self = $self;
}
public function __clone()
{
foreach ($this->vars_in_scope as $key => &$type) {
if ($type) {
$type = clone $type;
}
}
}
/**
* 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
* @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-08-10 07:54:45 +02: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) {
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
$new_type = !$has_leaving_statements && isset($end_context->vars_in_scope[$var]) ? $end_context->vars_in_scope[$var] : null;
// 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) {
2016-08-24 05:39:43 +02:00
if (isset($new_context->vars_in_scope[$var]) && (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
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);
}
}
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-10-03 07:35:54 +02:00
if ($type->hasArray() || $type->hasObjectType() || $type->hasObjectLike() || $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]);
}
}
}
}