mirror of
https://github.com/danog/psalm.git
synced 2024-11-30 04:39:00 +01:00
parent
2bdd062400
commit
f67e92023b
@ -704,6 +704,7 @@ class CallChecker
|
||||
}
|
||||
|
||||
$config = Config::getInstance();
|
||||
$file_checker = $statements_checker->getFileChecker();
|
||||
|
||||
if ($class_type && is_string($stmt->name)) {
|
||||
$return_type = null;
|
||||
@ -751,6 +752,8 @@ class CallChecker
|
||||
|
||||
$fq_class_name = $class_type_part->value;
|
||||
|
||||
$intersection_types = $class_type_part->getIntersectionTypes();
|
||||
|
||||
$is_mock = ExpressionChecker::isMock($fq_class_name);
|
||||
|
||||
$has_mock = $has_mock || $is_mock;
|
||||
@ -771,7 +774,7 @@ class CallChecker
|
||||
} else {
|
||||
$does_class_exist = ClassLikeChecker::checkFullyQualifiedClassLikeName(
|
||||
$fq_class_name,
|
||||
$statements_checker->getFileChecker(),
|
||||
$file_checker,
|
||||
new CodeLocation($statements_checker->getSource(), $stmt),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
);
|
||||
@ -787,6 +790,18 @@ class CallChecker
|
||||
}
|
||||
|
||||
$method_id = $fq_class_name . '::' . strtolower($stmt->name);
|
||||
|
||||
if ($intersection_types && !MethodChecker::methodExists($method_id, $file_checker)) {
|
||||
foreach ($intersection_types as $intersection_type) {
|
||||
$method_id = $intersection_type->value . '::' . strtolower($stmt->name);
|
||||
$fq_class_name = $intersection_type->value;
|
||||
|
||||
if (MethodChecker::methodExists($method_id, $file_checker)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$cased_method_id = $fq_class_name . '::' . $stmt->name;
|
||||
|
||||
$does_method_exist = MethodChecker::checkMethodExists(
|
||||
|
@ -360,8 +360,36 @@ class TypeChecker
|
||||
return $new_type;
|
||||
}
|
||||
|
||||
if (!InterfaceChecker::interfaceExists($new_var_type, $file_checker) &&
|
||||
$code_location &&
|
||||
if (InterfaceChecker::interfaceExists($new_var_type, $file_checker)) {
|
||||
$new_type_part = new TNamedObject($new_var_type);
|
||||
|
||||
$acceptable_atomic_types = [];
|
||||
|
||||
foreach ($existing_var_type->types as $existing_var_type_part) {
|
||||
if (TypeChecker::isAtomicContainedBy(
|
||||
$existing_var_type_part,
|
||||
$new_type_part,
|
||||
$file_checker,
|
||||
$scalar_type_match_found,
|
||||
$type_coerced,
|
||||
$atomic_to_string_cast
|
||||
)) {
|
||||
$acceptable_atomic_types[] = $existing_var_type_part;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($existing_var_type_part instanceof TNamedObject &&
|
||||
ClassChecker::classExists($existing_var_type_part->value, $file_checker)
|
||||
) {
|
||||
$existing_var_type_part->addIntersectionType($new_type_part);
|
||||
$acceptable_atomic_types[] = $existing_var_type_part;
|
||||
}
|
||||
}
|
||||
|
||||
if ($acceptable_atomic_types) {
|
||||
return new Type\Union($acceptable_atomic_types);
|
||||
}
|
||||
} elseif ($code_location &&
|
||||
!$new_type->isMixed() &&
|
||||
!$existing_var_type->from_docblock
|
||||
) {
|
||||
@ -476,8 +504,27 @@ class TypeChecker
|
||||
$atomic_to_string_cast
|
||||
);
|
||||
|
||||
if ($is_atomic_contained_by === true) {
|
||||
if ($is_atomic_contained_by) {
|
||||
$type_match_found = true;
|
||||
} elseif (!$type_coerced &&
|
||||
$input_type_part instanceof TNamedObject &&
|
||||
$intersection_types = $input_type_part->getIntersectionTypes()
|
||||
) {
|
||||
foreach ($intersection_types as $intersection_type) {
|
||||
$is_atomic_contained_by = self::isAtomicContainedBy(
|
||||
$intersection_type,
|
||||
$container_type_part,
|
||||
$file_checker,
|
||||
$scalar_type_match_found,
|
||||
$type_coerced,
|
||||
$atomic_to_string_cast
|
||||
);
|
||||
|
||||
if ($is_atomic_contained_by) {
|
||||
$type_match_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($atomic_to_string_cast !== true) {
|
||||
|
@ -1,13 +1,21 @@
|
||||
<?php
|
||||
namespace Psalm\Type\Atomic;
|
||||
|
||||
class TNamedObject extends \Psalm\Type\Atomic
|
||||
use \Psalm\Type\Atomic;
|
||||
use \Psalm\Type\Union;
|
||||
|
||||
class TNamedObject extends Atomic
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $value;
|
||||
|
||||
/**
|
||||
* @var TNamedObject[]|null
|
||||
*/
|
||||
public $extra_types;
|
||||
|
||||
/**
|
||||
* @param string $value the name of the object
|
||||
*/
|
||||
@ -48,4 +56,21 @@ class TNamedObject extends \Psalm\Type\Atomic
|
||||
|
||||
return '\\' . $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TNamedObject $type
|
||||
* @return void
|
||||
*/
|
||||
public function addIntersectionType(TNamedObject $type)
|
||||
{
|
||||
$this->extra_types[] = $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TNamedObject[]|null
|
||||
*/
|
||||
public function getIntersectionTypes()
|
||||
{
|
||||
return $this->extra_types;
|
||||
}
|
||||
}
|
||||
|
@ -1068,7 +1068,7 @@ class TypeReconciliationTest extends PHPUnit_Framework_TestCase
|
||||
$file_checker->visitAndAnalyzeMethods($context);
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testTernaryByRefVarInConditional()
|
||||
@ -1089,4 +1089,68 @@ class TypeReconciliationTest extends PHPUnit_Framework_TestCase
|
||||
$context = new Context();
|
||||
$file_checker->visitAndAnalyzeMethods($context);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testPossibleInstanceof()
|
||||
{
|
||||
$this->markTestSkipped('Currently broken');
|
||||
$stmts = self::$parser->parse('<?php
|
||||
interface I1 {}
|
||||
interface I2 {}
|
||||
|
||||
class A
|
||||
{
|
||||
public function foo() : void {
|
||||
if ($this instanceof I1 || $this instanceof I2) {}
|
||||
}
|
||||
}
|
||||
');
|
||||
|
||||
$file_checker = new FileChecker('somefile.php', $this->project_checker, $stmts);
|
||||
$context = new Context();
|
||||
$file_checker->visitAndAnalyzeMethods($context);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testIntersection()
|
||||
{
|
||||
$stmts = self::$parser->parse('<?php
|
||||
interface I {
|
||||
public function bat() : void;
|
||||
}
|
||||
|
||||
function takesI(I $i) : void {}
|
||||
function takesA(A $a) : void {}
|
||||
|
||||
class A {
|
||||
public function foo() : void {
|
||||
if ($this instanceof I) {
|
||||
$this->bar();
|
||||
$this->bat();
|
||||
|
||||
takesA($this);
|
||||
takesI($this);
|
||||
}
|
||||
}
|
||||
|
||||
protected function bar() : void {}
|
||||
}
|
||||
|
||||
class B extends A implements I {
|
||||
public function bat() : void {}
|
||||
}
|
||||
');
|
||||
|
||||
$file_checker = new FileChecker('somefile.php', $this->project_checker, $stmts);
|
||||
$context = new Context();
|
||||
$file_checker->visitAndAnalyzeMethods($context);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user