mirror of
https://github.com/danog/psalm.git
synced 2025-01-21 21:31:13 +01:00
Fix #114 - add optional Hack-like checks calls
This commit is contained in:
parent
97916c523e
commit
ce6ca58291
@ -26,6 +26,7 @@
|
||||
<xs:attribute name="requireVoidReturnType" type="xs:string" />
|
||||
<xs:attribute name="useAssertForType" type="xs:string" />
|
||||
<xs:attribute name="cacheFileContentHashes" type="xs:string" />
|
||||
<xs:attribute name="rememberPropertyAssignmentsAfterCall" type="xs:string" />
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="ProjectFilesType">
|
||||
|
@ -5,6 +5,7 @@
|
||||
useDocblockTypes="true"
|
||||
totallyTyped="true"
|
||||
strictBinaryOperands="false"
|
||||
rememberPropertyAssignmentsAfterCall="true"
|
||||
>
|
||||
<projectFiles>
|
||||
<directory name="src" />
|
||||
|
@ -49,7 +49,7 @@ class ForChecker
|
||||
}
|
||||
|
||||
foreach ($context->vars_in_scope as $var => $type) {
|
||||
if ($type->isMixed()) {
|
||||
if ($type->isMixed() || !isset($for_context->vars_in_scope[$var])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -296,6 +296,8 @@ class CallChecker
|
||||
// fall through
|
||||
}
|
||||
|
||||
$config = Config::getInstance();
|
||||
|
||||
if ($function_exists) {
|
||||
$generic_params = null;
|
||||
|
||||
@ -326,8 +328,6 @@ class CallChecker
|
||||
}
|
||||
}
|
||||
|
||||
$config = Config::getInstance();
|
||||
|
||||
if ($stmt->name instanceof PhpParser\Node\Name && $method_id) {
|
||||
if (!$in_call_map || $is_stubbed) {
|
||||
if ($function_storage && $function_storage->template_types) {
|
||||
@ -424,6 +424,10 @@ class CallChecker
|
||||
}
|
||||
}
|
||||
|
||||
if (!$config->remember_property_assignments_after_call && !$context->collect_initializations) {
|
||||
$context->removeAllObjectVars();
|
||||
}
|
||||
|
||||
if ($stmt->name instanceof PhpParser\Node\Name &&
|
||||
($stmt->name->parts === ['get_class'] || $stmt->name->parts === ['gettype']) &&
|
||||
$stmt->args
|
||||
@ -609,6 +613,12 @@ class CallChecker
|
||||
}
|
||||
}
|
||||
|
||||
$config = Config::getInstance();
|
||||
|
||||
if (!$config->remember_property_assignments_after_call && !$context->collect_initializations) {
|
||||
$context->removeAllObjectVars();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -918,6 +928,10 @@ class CallChecker
|
||||
$statements_checker
|
||||
);
|
||||
}
|
||||
|
||||
if (!$config->remember_property_assignments_after_call && !$context->collect_initializations) {
|
||||
$context->removeAllObjectVars();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1253,6 +1267,10 @@ class CallChecker
|
||||
$statements_checker
|
||||
);
|
||||
}
|
||||
|
||||
if (!$config->remember_property_assignments_after_call && !$context->collect_initializations) {
|
||||
$context->removeAllObjectVars();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -168,6 +168,11 @@ class Config
|
||||
*/
|
||||
public $use_assert_for_type = false;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
public $remember_property_assignments_after_call = true;
|
||||
|
||||
/**
|
||||
* Psalm plugins
|
||||
*
|
||||
@ -323,6 +328,11 @@ class Config
|
||||
$config->cache_file_hashes_during_run = $attribute_text === 'true' || $attribute_text === '1';
|
||||
}
|
||||
|
||||
if (isset($config_xml['rememberPropertyAssignmentsAfterCall'])) {
|
||||
$attribute_text = (string) $config_xml['rememberPropertyAssignmentsAfterCall'];
|
||||
$config->remember_property_assignments_after_call = $attribute_text === 'true' || $attribute_text === '1';
|
||||
}
|
||||
|
||||
if (isset($config_xml->projectFiles)) {
|
||||
$config->project_files = ProjectFileFilter::loadFromXMLElement($config_xml->projectFiles, true);
|
||||
}
|
||||
|
@ -361,6 +361,51 @@ class Context
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Context $op_context
|
||||
* @return void
|
||||
|
@ -1072,4 +1072,38 @@ class PropertyTypeTest extends PHPUnit_Framework_TestCase
|
||||
$file_checker = new FileChecker('somefile.php', $this->project_checker, $stmts);
|
||||
$file_checker->visitAndAnalyzeMethods();
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Psalm\Exception\CodeException
|
||||
* @expectedExceptionMessage InvalidReturnType
|
||||
* @return void
|
||||
*/
|
||||
public function testForgetPropertyAssignments()
|
||||
{
|
||||
Config::getInstance()->remember_property_assignments_after_call = false;
|
||||
|
||||
$stmts = self::$parser->parse('<?php
|
||||
class X {
|
||||
/** @var ?int **/
|
||||
private $x;
|
||||
|
||||
public function getX(): int {
|
||||
if ($this->x === null) {
|
||||
$this->x = 0;
|
||||
}
|
||||
$this->modifyX();
|
||||
return $this->x;
|
||||
}
|
||||
|
||||
private function modifyX(): void {
|
||||
$this->x = null;
|
||||
}
|
||||
}
|
||||
');
|
||||
|
||||
$file_checker = new FileChecker('somefile.php', $this->project_checker, $stmts);
|
||||
$file_checker->visitAndAnalyzeMethods();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user