1
0
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:
Matt Brown 2017-03-16 11:46:07 -04:00
parent 0c0a30d6ea
commit cecfe25df7
4 changed files with 202 additions and 139 deletions

View File

@ -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);
}

View File

@ -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();

View File

@ -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
View 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();
}
}