mirror of
https://github.com/danog/psalm.git
synced 2025-01-21 21:31:13 +01:00
Mutation checks should not care about return type
This commit is contained in:
parent
ae69695f89
commit
9442805763
@ -62,7 +62,7 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer implements Statements
|
||||
protected $source;
|
||||
|
||||
/**
|
||||
* @var array<string, array<string, Type\Union>>
|
||||
* @var ?array<string, Type\Union>
|
||||
*/
|
||||
protected $return_vars_in_scope = [];
|
||||
|
||||
@ -72,7 +72,7 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer implements Statements
|
||||
protected $possible_param_types = [];
|
||||
|
||||
/**
|
||||
* @var array<string, array<string, bool>>
|
||||
* @var ?array<string, bool>
|
||||
*/
|
||||
protected $return_vars_possibly_in_scope = [];
|
||||
|
||||
@ -582,7 +582,7 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer implements Statements
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($context->collect_initializations) {
|
||||
if ($context->collect_initializations || $context->collect_mutations) {
|
||||
$statements_analyzer->addSuppressedIssues([
|
||||
'DocblockTypeContradiction',
|
||||
'InvalidReturnStatement',
|
||||
@ -854,17 +854,17 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer implements Statements
|
||||
}
|
||||
|
||||
if ($add_mutations) {
|
||||
if (isset($this->return_vars_in_scope[''])) {
|
||||
if ($this->return_vars_in_scope !== null) {
|
||||
$context->vars_in_scope = TypeAnalyzer::combineKeyedTypes(
|
||||
$context->vars_in_scope,
|
||||
$this->return_vars_in_scope['']
|
||||
$this->return_vars_in_scope
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($this->return_vars_possibly_in_scope[''])) {
|
||||
if ($this->return_vars_possibly_in_scope !== null) {
|
||||
$context->vars_possibly_in_scope = array_merge(
|
||||
$context->vars_possibly_in_scope,
|
||||
$this->return_vars_possibly_in_scope['']
|
||||
$this->return_vars_possibly_in_scope
|
||||
);
|
||||
}
|
||||
|
||||
@ -967,24 +967,24 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer implements Statements
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addReturnTypes($return_type, Context $context)
|
||||
public function addReturnTypes(Context $context)
|
||||
{
|
||||
if (isset($this->return_vars_in_scope[$return_type])) {
|
||||
$this->return_vars_in_scope[$return_type] = TypeAnalyzer::combineKeyedTypes(
|
||||
if ($this->return_vars_in_scope !== null) {
|
||||
$this->return_vars_in_scope = TypeAnalyzer::combineKeyedTypes(
|
||||
$context->vars_in_scope,
|
||||
$this->return_vars_in_scope[$return_type]
|
||||
$this->return_vars_in_scope
|
||||
);
|
||||
} else {
|
||||
$this->return_vars_in_scope[$return_type] = $context->vars_in_scope;
|
||||
$this->return_vars_in_scope = $context->vars_in_scope;
|
||||
}
|
||||
|
||||
if (isset($this->return_vars_possibly_in_scope[$return_type])) {
|
||||
$this->return_vars_possibly_in_scope[$return_type] = array_merge(
|
||||
if ($this->return_vars_possibly_in_scope !== null) {
|
||||
$this->return_vars_possibly_in_scope = array_merge(
|
||||
$context->vars_possibly_in_scope,
|
||||
$this->return_vars_possibly_in_scope[$return_type]
|
||||
$this->return_vars_possibly_in_scope
|
||||
);
|
||||
} else {
|
||||
$this->return_vars_possibly_in_scope[$return_type] = $context->vars_possibly_in_scope;
|
||||
$this->return_vars_possibly_in_scope = $context->vars_possibly_in_scope;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -119,10 +119,7 @@ class ReturnAnalyzer
|
||||
if ($source instanceof FunctionLikeAnalyzer
|
||||
&& !($source->getSource() instanceof TraitAnalyzer)
|
||||
) {
|
||||
$source->addReturnTypes(
|
||||
$stmt->expr ? (string) $stmt->inferredType : '',
|
||||
$context
|
||||
);
|
||||
$source->addReturnTypes($context);
|
||||
|
||||
$source->examineParamTypes($statements_analyzer, $context, $codebase, $stmt);
|
||||
|
||||
|
@ -14,79 +14,79 @@ class MethodMutationTest extends TestCase
|
||||
$this->addFile(
|
||||
'somefile.php',
|
||||
'<?php
|
||||
class User {
|
||||
/** @var string */
|
||||
public $name;
|
||||
class User {
|
||||
/** @var string */
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*/
|
||||
protected function __construct($name) {
|
||||
$this->name = $name;
|
||||
}
|
||||
/**
|
||||
* @param string $name
|
||||
*/
|
||||
protected function __construct($name) {
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/** @return User|null */
|
||||
public static function loadUser(int $id) {
|
||||
if ($id === 3) {
|
||||
$user = new User("bob");
|
||||
return $user;
|
||||
/** @return User|null */
|
||||
public static function loadUser(int $id) {
|
||||
if ($id === 3) {
|
||||
$user = new User("bob");
|
||||
return $user;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class UserViewData {
|
||||
/** @var string|null */
|
||||
public $name;
|
||||
}
|
||||
|
||||
class Response {
|
||||
public function __construct (UserViewData $viewdata) {}
|
||||
}
|
||||
|
||||
class UnauthorizedException extends Exception { }
|
||||
|
||||
class Controller {
|
||||
/** @var UserViewData */
|
||||
public $user_viewdata;
|
||||
|
||||
/** @var string|null */
|
||||
public $title;
|
||||
|
||||
public function __construct() {
|
||||
$this->user_viewdata = new UserViewData();
|
||||
}
|
||||
|
||||
public function setUser(): void
|
||||
{
|
||||
$user_id = (int)$_GET["id"];
|
||||
|
||||
if (!$user_id) {
|
||||
throw new UnauthorizedException("No user id supplied");
|
||||
class UserViewData {
|
||||
/** @var string|null */
|
||||
public $name;
|
||||
}
|
||||
|
||||
$user = User::loadUser($user_id);
|
||||
|
||||
if (!$user) {
|
||||
throw new UnauthorizedException("User not found");
|
||||
class Response {
|
||||
public function __construct (UserViewData $viewdata) {}
|
||||
}
|
||||
|
||||
$this->user_viewdata->name = $user->name;
|
||||
}
|
||||
}
|
||||
class UnauthorizedException extends Exception { }
|
||||
|
||||
class FooController extends Controller {
|
||||
public function barBar(): Response {
|
||||
$this->setUser();
|
||||
class Controller {
|
||||
/** @var UserViewData */
|
||||
public $user_viewdata;
|
||||
|
||||
if (rand(0, 1)) {
|
||||
$this->title = "hello";
|
||||
/** @var string|null */
|
||||
public $title;
|
||||
|
||||
public function __construct() {
|
||||
$this->user_viewdata = new UserViewData();
|
||||
}
|
||||
|
||||
public function setUser(): void
|
||||
{
|
||||
$user_id = (int)$_GET["id"];
|
||||
|
||||
if (!$user_id) {
|
||||
throw new UnauthorizedException("No user id supplied");
|
||||
}
|
||||
|
||||
$user = User::loadUser($user_id);
|
||||
|
||||
if (!$user) {
|
||||
throw new UnauthorizedException("User not found");
|
||||
}
|
||||
|
||||
$this->user_viewdata->name = $user->name;
|
||||
}
|
||||
}
|
||||
|
||||
return new Response($this->user_viewdata);
|
||||
}
|
||||
}'
|
||||
class FooController extends Controller {
|
||||
public function barBar(): Response {
|
||||
$this->setUser();
|
||||
|
||||
if (rand(0, 1)) {
|
||||
$this->title = "hello";
|
||||
}
|
||||
|
||||
return new Response($this->user_viewdata);
|
||||
}
|
||||
}'
|
||||
);
|
||||
|
||||
new FileAnalyzer($this->project_analyzer, 'somefile.php', 'somefile.php');
|
||||
@ -106,6 +106,51 @@ class MethodMutationTest extends TestCase
|
||||
$this->assertTrue($method_context->vars_possibly_in_scope['$this->title']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testNotSettingUser()
|
||||
{
|
||||
$this->addFile(
|
||||
'somefile.php',
|
||||
'<?php
|
||||
class User {}
|
||||
|
||||
class FooController {
|
||||
/** @var User|null */
|
||||
public $user;
|
||||
|
||||
public function doThingWithUser(): array
|
||||
{
|
||||
if (!$this->user) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return ["hello"];
|
||||
}
|
||||
|
||||
public function barBar(): void {
|
||||
$this->user = rand(0, 1) ? new User() : null;
|
||||
|
||||
$this->doThingWithUser();
|
||||
}
|
||||
}'
|
||||
);
|
||||
|
||||
new FileAnalyzer($this->project_analyzer, 'somefile.php', 'somefile.php');
|
||||
$this->project_analyzer->getCodebase()->scanFiles();
|
||||
$method_context = new Context();
|
||||
$method_context->collect_mutations = true;
|
||||
$this->project_analyzer->getMethodMutations(
|
||||
'FooController::barBar',
|
||||
$method_context,
|
||||
'somefile.php',
|
||||
'somefile.php'
|
||||
);
|
||||
|
||||
$this->assertSame('null|User', (string)$method_context->vars_in_scope['$this->user']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
@ -114,22 +159,22 @@ class MethodMutationTest extends TestCase
|
||||
$this->addFile(
|
||||
'somefile.php',
|
||||
'<?php
|
||||
class Foo { }
|
||||
class Foo { }
|
||||
|
||||
class Controller {
|
||||
/** @var Foo|null */
|
||||
public $foo;
|
||||
class Controller {
|
||||
/** @var Foo|null */
|
||||
public $foo;
|
||||
|
||||
public function __construct() {
|
||||
$this->foo = new Foo();
|
||||
}
|
||||
}
|
||||
public function __construct() {
|
||||
$this->foo = new Foo();
|
||||
}
|
||||
}
|
||||
|
||||
class FooController extends Controller {
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
}
|
||||
}'
|
||||
class FooController extends Controller {
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
}
|
||||
}'
|
||||
);
|
||||
|
||||
new FileAnalyzer($this->project_analyzer, 'somefile.php', 'somefile.php');
|
||||
@ -154,24 +199,24 @@ class MethodMutationTest extends TestCase
|
||||
$this->addFile(
|
||||
'somefile.php',
|
||||
'<?php
|
||||
class Foo { }
|
||||
class Foo { }
|
||||
|
||||
trait T {
|
||||
private function setFoo(): void {
|
||||
$this->foo = new Foo();
|
||||
}
|
||||
}
|
||||
trait T {
|
||||
private function setFoo(): void {
|
||||
$this->foo = new Foo();
|
||||
}
|
||||
}
|
||||
|
||||
class FooController {
|
||||
use T;
|
||||
class FooController {
|
||||
use T;
|
||||
|
||||
/** @var Foo|null */
|
||||
public $foo;
|
||||
/** @var Foo|null */
|
||||
public $foo;
|
||||
|
||||
public function __construct() {
|
||||
$this->setFoo();
|
||||
}
|
||||
}'
|
||||
public function __construct() {
|
||||
$this->setFoo();
|
||||
}
|
||||
}'
|
||||
);
|
||||
|
||||
new FileAnalyzer($this->project_analyzer, 'somefile.php', 'somefile.php');
|
||||
|
Loading…
x
Reference in New Issue
Block a user