mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 20:34:47 +01:00
* Allow plain assertions (@psalm-assert) about $this (fixes #3105) * Fix multiple assertion combining * Fix multiple assertion combining for $this again * Add test for multiple assertion combining for $this again
This commit is contained in:
parent
2a7be233bb
commit
4d1be3f0c4
@ -21,8 +21,9 @@ use Psalm\Issue\UndefinedMethod;
|
||||
use Psalm\IssueBuffer;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Atomic\TNamedObject;
|
||||
use function is_string;
|
||||
use function count;
|
||||
use function is_string;
|
||||
use function array_reduce;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -166,6 +167,7 @@ class MethodCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\
|
||||
|
||||
$result = new AtomicMethodCallAnalysisResult();
|
||||
|
||||
$possible_new_class_types = [];
|
||||
foreach ($lhs_types as $lhs_type_part) {
|
||||
AtomicMethodCallAnalyzer::analyze(
|
||||
$statements_analyzer,
|
||||
@ -181,6 +183,23 @@ class MethodCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\
|
||||
$lhs_var_id,
|
||||
$result
|
||||
);
|
||||
if (isset($context->vars_in_scope[$lhs_var_id])
|
||||
&& ($possible_new_class_type = $context->vars_in_scope[$lhs_var_id]) instanceof Type\Union
|
||||
&& !$possible_new_class_type->equals($class_type)) {
|
||||
$possible_new_class_types[] = $context->vars_in_scope[$lhs_var_id];
|
||||
}
|
||||
}
|
||||
|
||||
if (count($possible_new_class_types) > 0) {
|
||||
$class_type = array_reduce(
|
||||
$possible_new_class_types,
|
||||
function (?Type\Union $type_1, Type\Union $type_2) use ($codebase): Type\Union {
|
||||
if ($type_1 === null) {
|
||||
return $type_2;
|
||||
}
|
||||
return Type::combineUnionTypes($type_1, $type_2, $codebase);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if ($result->invalid_method_call_types) {
|
||||
|
@ -3670,6 +3670,8 @@ class CallAnalyzer
|
||||
}
|
||||
} elseif (isset($context->vars_in_scope[$assertion->var_id])) {
|
||||
$assertion_var_id = $assertion->var_id;
|
||||
} elseif ($assertion->var_id === '$this' && !is_null($thisName)) {
|
||||
$assertion_var_id = $thisName;
|
||||
} elseif (strpos($assertion->var_id, '$this->') === 0 && !is_null($thisName)) {
|
||||
$assertion_var_id = $thisName . str_replace('$this->', '->', $assertion->var_id);
|
||||
}
|
||||
|
@ -412,6 +412,30 @@ class AssertAnnotationTest extends TestCase
|
||||
|
||||
if (!is_int($a)) $a->bar();',
|
||||
],
|
||||
'assertThisType' => [
|
||||
'<?php
|
||||
class Type {
|
||||
/**
|
||||
* @psalm-assert FooType $this
|
||||
*/
|
||||
public function isFoo() : bool {
|
||||
if (!$this instanceof FooType) {
|
||||
throw new \Exception();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class FooType extends Type {
|
||||
public function bar(): void {}
|
||||
}
|
||||
|
||||
function takesType(Type $t) : void {
|
||||
$t->isFoo();
|
||||
$t->bar();
|
||||
}'
|
||||
],
|
||||
'assertThisTypeIfTrue' => [
|
||||
'<?php
|
||||
class Type {
|
||||
@ -433,6 +457,145 @@ class AssertAnnotationTest extends TestCase
|
||||
}
|
||||
}'
|
||||
],
|
||||
'assertThisTypeCombined' => [
|
||||
'<?php
|
||||
class Type {
|
||||
/**
|
||||
* @psalm-assert FooType $this
|
||||
*/
|
||||
public function assertFoo() : void {
|
||||
if (!$this instanceof FooType) {
|
||||
throw new \Exception();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-assert BarType $this
|
||||
*/
|
||||
public function assertBar() : void {
|
||||
if (!$this instanceof BarType) {
|
||||
throw new \Exception();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface FooType {
|
||||
public function foo(): void;
|
||||
}
|
||||
|
||||
interface BarType {
|
||||
public function bar(): void;
|
||||
}
|
||||
|
||||
function takesType(Type $t) : void {
|
||||
$t->assertFoo();
|
||||
$t->assertBar();
|
||||
$t->foo();
|
||||
$t->bar();
|
||||
}'
|
||||
],
|
||||
'assertThisTypeSimpleCombined' => [
|
||||
'<?php
|
||||
class Type {
|
||||
/**
|
||||
* @psalm-assert FooType $this
|
||||
*/
|
||||
public function assertFoo() : void {
|
||||
if (!$this instanceof FooType) {
|
||||
throw new \Exception();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-assert BarType $this
|
||||
*/
|
||||
public function assertBar() : void {
|
||||
if (!$this instanceof BarType) {
|
||||
throw new \Exception();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
interface FooType {
|
||||
public function foo(): void;
|
||||
}
|
||||
|
||||
interface BarType {
|
||||
public function bar(): void;
|
||||
}
|
||||
|
||||
/** @param Type&FooType $t */
|
||||
function takesType(Type $t) : void {
|
||||
$t->assertBar();
|
||||
$t->foo();
|
||||
$t->bar();
|
||||
}'
|
||||
],
|
||||
'assertThisTypeIfTrueCombined' => [
|
||||
'<?php
|
||||
class Type {
|
||||
/**
|
||||
* @psalm-assert-if-true FooType $this
|
||||
*/
|
||||
public function assertFoo() : bool {
|
||||
return $this instanceof FooType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-assert-if-true BarType $this
|
||||
*/
|
||||
public function assertBar() : bool {
|
||||
return $this instanceof BarType;
|
||||
}
|
||||
}
|
||||
|
||||
interface FooType {
|
||||
public function foo(): void;
|
||||
}
|
||||
|
||||
interface BarType {
|
||||
public function bar(): void;
|
||||
}
|
||||
|
||||
function takesType(Type $t) : void {
|
||||
if ($t->assertFoo() && $t->assertBar()) {
|
||||
$t->foo();
|
||||
$t->bar();
|
||||
}
|
||||
}'
|
||||
],
|
||||
'assertThisTypeSimpleAndIfTrueCombined' => [
|
||||
'<?php
|
||||
class Type {
|
||||
/**
|
||||
* @psalm-assert BarType $this
|
||||
* @psalm-assert-if-true FooType $this
|
||||
*/
|
||||
public function isFoo() : bool {
|
||||
if (!$this instanceof BarType) {
|
||||
throw new \Exception();
|
||||
}
|
||||
return $this instanceof FooType;
|
||||
}
|
||||
}
|
||||
|
||||
interface FooType {
|
||||
public function foo(): void;
|
||||
}
|
||||
|
||||
interface BarType {
|
||||
public function bar(): void;
|
||||
}
|
||||
|
||||
function takesType(Type $t) : void {
|
||||
if ($t->isFoo()) {
|
||||
$t->foo();
|
||||
}
|
||||
$t->bar();
|
||||
}'
|
||||
],
|
||||
'assertThisTypeSwitchTrue' => [
|
||||
'<?php
|
||||
class Type {
|
||||
@ -1116,6 +1279,31 @@ class AssertAnnotationTest extends TestCase
|
||||
}',
|
||||
'error_message' => 'DocblockTypeContradiction',
|
||||
],
|
||||
'assertThisType' => [
|
||||
'<?php
|
||||
class Type {
|
||||
/**
|
||||
* @psalm-assert FooType $this
|
||||
*/
|
||||
public function isFoo() : bool {
|
||||
if (!$this instanceof FooType) {
|
||||
throw new \Exception();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class FooType extends Type {
|
||||
public function bar(): void {}
|
||||
}
|
||||
|
||||
function takesType(Type $t) : void {
|
||||
$t->bar();
|
||||
$t->isFoo();
|
||||
}',
|
||||
'error_message' => 'UndefinedMethod',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user