mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 20:34:47 +01:00
Update outer clauses in mic drop situation
This commit is contained in:
parent
0c0a30d6ea
commit
cecfe25df7
@ -177,6 +177,7 @@ class IfChecker
|
||||
$if_context,
|
||||
$old_if_context,
|
||||
$context,
|
||||
$negated_clauses,
|
||||
$pre_assignment_else_redefined_vars
|
||||
);
|
||||
|
||||
@ -272,6 +273,7 @@ class IfChecker
|
||||
* @param Context $if_context
|
||||
* @param Context $old_if_context
|
||||
* @param Context $outer_context
|
||||
* @param array<Clause> $negated_clauses
|
||||
* @param array<string,Type\Union> $pre_assignment_else_redefined_vars
|
||||
* @return false|null
|
||||
*/
|
||||
@ -282,6 +284,7 @@ class IfChecker
|
||||
Context $if_context,
|
||||
Context $old_if_context,
|
||||
Context $outer_context,
|
||||
array $negated_clauses,
|
||||
array $pre_assignment_else_redefined_vars
|
||||
) {
|
||||
$has_ending_statements = ScopeChecker::doesAlwaysReturnOrThrow($stmt->stmts);
|
||||
@ -338,28 +341,34 @@ class IfChecker
|
||||
|
||||
$if_scope->redefined_vars = Context::getRedefinedVars($outer_context, $if_context);
|
||||
$if_scope->possibly_redefined_vars = $if_scope->redefined_vars;
|
||||
} elseif (!$stmt->else && !$stmt->elseifs && $if_scope->negated_types) {
|
||||
$changed_vars = [];
|
||||
} elseif (!$stmt->else && !$stmt->elseifs) {
|
||||
if ($if_scope->negated_types) {
|
||||
$changed_vars = [];
|
||||
|
||||
$outer_context_vars_reconciled = TypeChecker::reconcileKeyedTypes(
|
||||
$if_scope->negated_types,
|
||||
$outer_context->vars_in_scope,
|
||||
$changed_vars,
|
||||
$statements_checker->getFileChecker(),
|
||||
new CodeLocation($statements_checker->getSource(), $stmt->cond),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
$outer_context_vars_reconciled = TypeChecker::reconcileKeyedTypes(
|
||||
$if_scope->negated_types,
|
||||
$outer_context->vars_in_scope,
|
||||
$changed_vars,
|
||||
$statements_checker->getFileChecker(),
|
||||
new CodeLocation($statements_checker->getSource(), $stmt->cond),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
);
|
||||
|
||||
foreach ($changed_vars as $changed_var) {
|
||||
$outer_context->removeVarFromClauses($changed_var);
|
||||
}
|
||||
|
||||
if ($outer_context_vars_reconciled === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$outer_context->vars_in_scope = $outer_context_vars_reconciled;
|
||||
$mic_drop = true;
|
||||
}
|
||||
|
||||
$outer_context->clauses = TypeChecker::simplifyCNF(
|
||||
array_merge($outer_context->clauses, $negated_clauses)
|
||||
);
|
||||
|
||||
foreach ($changed_vars as $changed_var) {
|
||||
$outer_context->removeVarFromClauses($changed_var);
|
||||
}
|
||||
|
||||
if ($outer_context_vars_reconciled === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$outer_context->vars_in_scope = $outer_context_vars_reconciled;
|
||||
$mic_drop = true;
|
||||
}
|
||||
|
||||
// update the parent context as necessary, but only if we can safely reason about type negation.
|
||||
@ -902,10 +911,6 @@ class IfChecker
|
||||
return $stmt;
|
||||
}
|
||||
|
||||
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp) {
|
||||
return self::getDefinitelyEvaluatedExpression($stmt->left);
|
||||
}
|
||||
|
||||
if ($stmt instanceof PhpParser\Node\Expr\BooleanNot) {
|
||||
return self::getDefinitelyEvaluatedExpression($stmt->expr);
|
||||
}
|
||||
|
@ -416,6 +416,8 @@ class StatementsChecker extends SourceChecker implements StatementsSource
|
||||
unset($loop_context->vars_in_scope[$var_id]);
|
||||
}
|
||||
|
||||
$loop_context->clauses = $pre_loop_context->clauses;
|
||||
|
||||
IssueBuffer::startRecording();
|
||||
$this->analyze($stmts, $loop_context, $outer_context);
|
||||
$recorded_issues = IssueBuffer::clearRecordingLevel();
|
||||
|
@ -413,121 +413,6 @@ class ScopeTest extends PHPUnit_Framework_TestCase
|
||||
$file_checker->visitAndAnalyzeMethods();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testTwoVarLogic()
|
||||
{
|
||||
$stmts = self::$parser->parse('<?php
|
||||
$a = rand(0, 10) ? "hello" : null;
|
||||
$b = rand(0, 10) ? "goodbye" : null;
|
||||
|
||||
if ($a !== null || $b !== null) {
|
||||
if ($a !== null) {
|
||||
$c = $a;
|
||||
} else {
|
||||
$c = $b;
|
||||
}
|
||||
|
||||
echo strpos($c, "e");
|
||||
}
|
||||
');
|
||||
|
||||
$file_checker = new FileChecker('somefile.php', $this->project_checker, $stmts);
|
||||
$file_checker->visitAndAnalyzeMethods();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testThreeVarLogic()
|
||||
{
|
||||
$stmts = self::$parser->parse('<?php
|
||||
$a = rand(0, 10) ? "hello" : null;
|
||||
$b = rand(0, 10) ? "goodbye" : null;
|
||||
$c = rand(0, 10) ? "hello" : null;
|
||||
|
||||
if ($a !== null || $b !== null || $c !== null) {
|
||||
if ($a !== null) {
|
||||
$d = $a;
|
||||
} elseif ($b !== null) {
|
||||
$d = $b;
|
||||
} else {
|
||||
$d = $c;
|
||||
}
|
||||
|
||||
echo strpos($d, "e");
|
||||
}
|
||||
');
|
||||
|
||||
$file_checker = new FileChecker('somefile.php', $this->project_checker, $stmts);
|
||||
$file_checker->visitAndAnalyzeMethods();
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Psalm\Exception\CodeException
|
||||
* @expectedExceptionMessage NullArgument
|
||||
* @return void
|
||||
*/
|
||||
public function testThreeVarLogicWithChange()
|
||||
{
|
||||
$stmts = self::$parser->parse('<?php
|
||||
$a = rand(0, 10) ? "hello" : null;
|
||||
$b = rand(0, 10) ? "goodbye" : null;
|
||||
$c = rand(0, 10) ? "hello" : null;
|
||||
|
||||
if ($a !== null || $b !== null || $c !== null) {
|
||||
$c = null;
|
||||
|
||||
if ($a !== null) {
|
||||
$d = $a;
|
||||
} elseif ($b !== null) {
|
||||
$d = $b;
|
||||
} else {
|
||||
$d = $c;
|
||||
}
|
||||
|
||||
echo strpos($d, "e");
|
||||
}
|
||||
');
|
||||
|
||||
$file_checker = new FileChecker('somefile.php', $this->project_checker, $stmts);
|
||||
$file_checker->visitAndAnalyzeMethods();
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Psalm\Exception\CodeException
|
||||
* @expectedExceptionMessage NullArgument
|
||||
* @return void
|
||||
*/
|
||||
public function testThreeVarLogicWithException()
|
||||
{
|
||||
$stmts = self::$parser->parse('<?php
|
||||
$a = rand(0, 10) ? "hello" : null;
|
||||
$b = rand(0, 10) ? "goodbye" : null;
|
||||
$c = rand(0, 10) ? "hello" : null;
|
||||
|
||||
if ($a !== null || $b !== null || $c !== null) {
|
||||
if ($c !== null) {
|
||||
throw new \Exception("bad");
|
||||
}
|
||||
|
||||
if ($a !== null) {
|
||||
$d = $a;
|
||||
} elseif ($b !== null) {
|
||||
$d = $b;
|
||||
} else {
|
||||
$d = $c;
|
||||
}
|
||||
|
||||
echo strpos($d, "e");
|
||||
}
|
||||
');
|
||||
|
||||
$file_checker = new FileChecker('somefile.php', $this->project_checker, $stmts);
|
||||
$file_checker->visitAndAnalyzeMethods();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
|
171
tests/TypeAlgebraTest.php
Normal file
171
tests/TypeAlgebraTest.php
Normal file
@ -0,0 +1,171 @@
|
||||
<?php
|
||||
namespace Psalm\Tests;
|
||||
|
||||
use PhpParser\ParserFactory;
|
||||
use PHPUnit_Framework_TestCase;
|
||||
use Psalm\Checker\FileChecker;
|
||||
use Psalm\Config;
|
||||
use Psalm\Context;
|
||||
|
||||
class TypeAlgebraTest extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
/** @var \PhpParser\Parser */
|
||||
protected static $parser;
|
||||
|
||||
/** @var TestConfig */
|
||||
protected static $config;
|
||||
|
||||
/** @var \Psalm\Checker\ProjectChecker */
|
||||
protected $project_checker;
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public static function setUpBeforeClass()
|
||||
{
|
||||
self::$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
|
||||
self::$config = new TestConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function setUp()
|
||||
{
|
||||
FileChecker::clearCache();
|
||||
$this->project_checker = new \Psalm\Checker\ProjectChecker();
|
||||
$this->project_checker->setConfig(self::$config);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testTwoVarLogic()
|
||||
{
|
||||
$stmts = self::$parser->parse('<?php
|
||||
$a = rand(0, 10) ? "hello" : null;
|
||||
$b = rand(0, 10) ? "goodbye" : null;
|
||||
|
||||
if ($a !== null || $b !== null) {
|
||||
if ($a !== null) {
|
||||
$c = $a;
|
||||
} else {
|
||||
$c = $b;
|
||||
}
|
||||
|
||||
echo strpos($c, "e");
|
||||
}
|
||||
');
|
||||
|
||||
$file_checker = new FileChecker('somefile.php', $this->project_checker, $stmts);
|
||||
$file_checker->visitAndAnalyzeMethods();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testThreeVarLogic()
|
||||
{
|
||||
$stmts = self::$parser->parse('<?php
|
||||
$a = rand(0, 10) ? "hello" : null;
|
||||
$b = rand(0, 10) ? "goodbye" : null;
|
||||
$c = rand(0, 10) ? "hello" : null;
|
||||
|
||||
if ($a !== null || $b !== null || $c !== null) {
|
||||
if ($a !== null) {
|
||||
$d = $a;
|
||||
} elseif ($b !== null) {
|
||||
$d = $b;
|
||||
} else {
|
||||
$d = $c;
|
||||
}
|
||||
|
||||
echo strpos($d, "e");
|
||||
}
|
||||
');
|
||||
|
||||
$file_checker = new FileChecker('somefile.php', $this->project_checker, $stmts);
|
||||
$file_checker->visitAndAnalyzeMethods();
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Psalm\Exception\CodeException
|
||||
* @expectedExceptionMessage NullArgument
|
||||
* @return void
|
||||
*/
|
||||
public function testThreeVarLogicWithChange()
|
||||
{
|
||||
$stmts = self::$parser->parse('<?php
|
||||
$a = rand(0, 10) ? "hello" : null;
|
||||
$b = rand(0, 10) ? "goodbye" : null;
|
||||
$c = rand(0, 10) ? "hello" : null;
|
||||
|
||||
if ($a !== null || $b !== null || $c !== null) {
|
||||
$c = null;
|
||||
|
||||
if ($a !== null) {
|
||||
$d = $a;
|
||||
} elseif ($b !== null) {
|
||||
$d = $b;
|
||||
} else {
|
||||
$d = $c;
|
||||
}
|
||||
|
||||
echo strpos($d, "e");
|
||||
}
|
||||
');
|
||||
|
||||
$file_checker = new FileChecker('somefile.php', $this->project_checker, $stmts);
|
||||
$file_checker->visitAndAnalyzeMethods();
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Psalm\Exception\CodeException
|
||||
* @expectedExceptionMessage NullArgument
|
||||
* @return void
|
||||
*/
|
||||
public function testThreeVarLogicWithException()
|
||||
{
|
||||
$stmts = self::$parser->parse('<?php
|
||||
$a = rand(0, 10) ? "hello" : null;
|
||||
$b = rand(0, 10) ? "goodbye" : null;
|
||||
$c = rand(0, 10) ? "hello" : null;
|
||||
|
||||
if ($a !== null || $b !== null || $c !== null) {
|
||||
if ($c !== null) {
|
||||
throw new \Exception("bad");
|
||||
}
|
||||
|
||||
if ($a !== null) {
|
||||
$d = $a;
|
||||
} elseif ($b !== null) {
|
||||
$d = $b;
|
||||
} else {
|
||||
$d = $c;
|
||||
}
|
||||
|
||||
echo strpos($d, "e");
|
||||
}
|
||||
');
|
||||
|
||||
$file_checker = new FileChecker('somefile.php', $this->project_checker, $stmts);
|
||||
$file_checker->visitAndAnalyzeMethods();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testTwoVarLogicNotNested()
|
||||
{
|
||||
$stmts = self::$parser->parse('<?php
|
||||
function foo(?string $a, ?string $b) : string {
|
||||
if (!$a && !$b) return "bad";
|
||||
if (!$a) return $b;
|
||||
return $a;
|
||||
}
|
||||
');
|
||||
|
||||
$file_checker = new FileChecker('somefile.php', $this->project_checker, $stmts);
|
||||
$file_checker->visitAndAnalyzeMethods();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user