Skip to content
Permalink
Browse files

Fix #2483 - treat intersecting PHPDocs from interfaces better

  • Loading branch information
muglug committed Dec 20, 2019
1 parent d7b9914 commit a4191171bfadec2dfdc062fbb3303265ac558af1
@@ -1703,7 +1703,11 @@ public static function analyzeClassMethodReturnType(

$original_fq_classlike_name = $fq_classlike_name;

$return_type = $codebase->methods->getMethodReturnType($analyzed_method_id, $fq_classlike_name);
$return_type = $codebase->methods->getMethodReturnType(
$analyzed_method_id,
$fq_classlike_name,
$method_analyzer
);

if ($return_type && $class_storage->template_type_extends) {
$declaring_method_id = $codebase->methods->getDeclaringMethodId($analyzed_method_id);
@@ -1769,13 +1773,15 @@ public static function analyzeClassMethodReturnType(

if (!$return_type
&& !$class_storage->is_interface
&& isset($class_storage->interface_method_ids[strtolower($stmt->name->name)])
&& $overridden_method_ids
) {
$interface_method_ids = $class_storage->interface_method_ids[strtolower($stmt->name->name)];

foreach ($interface_method_ids as $interface_method_id) {
foreach ($overridden_method_ids as $interface_method_id) {
list($interface_class) = explode('::', $interface_method_id);

if (!$codebase->classlikes->interfaceExists($interface_class)) {
continue;
}

$interface_return_type = $codebase->methods->getMethodReturnType(
$interface_method_id,
$interface_class
@@ -523,7 +523,7 @@ public static function checkIteratorType(
$intersection_value_type = Type::intersectUnionTypes(
$intersection_value_type,
$value_type_part
);
) ?: Type::getMixed();
}

if (!$intersection_key_type) {
@@ -532,7 +532,7 @@ public static function checkIteratorType(
$intersection_key_type = Type::intersectUnionTypes(
$intersection_key_type,
$key_type_part
);
) ?: Type::getMixed();
}
}

@@ -672,7 +672,7 @@ private static function analyzeAtomicCall(
$all_intersection_return_type = Type::intersectUnionTypes(
$all_intersection_return_type,
$intersection_return_type
);
) ?: Type::getMixed();
}
}
}
@@ -782,7 +782,7 @@ private static function analyzeAtomicCall(
$return_type_candidate = Type::intersectUnionTypes(
$all_intersection_return_type,
$return_type_candidate
);
) ?: Type::getMixed();
}

if (!$return_type) {
@@ -913,7 +913,7 @@ function (PhpParser\Node\Arg $arg) {
$return_type_candidate = Type::intersectUnionTypes(
$all_intersection_return_type,
$return_type_candidate
);
) ?: Type::getMixed();
}

if (!$return_type) {
@@ -1491,7 +1491,7 @@ function (Assertion $assertion) use ($class_template_params) : Assertion {
$return_type_candidate = Type::intersectUnionTypes(
$all_intersection_return_type,
$return_type_candidate
);
) ?: Type::getMixed();
}

if (!$return_type) {
@@ -10,6 +10,7 @@
use Psalm\Codebase;
use Psalm\CodeLocation;
use Psalm\Context;
use Psalm\Internal\Analyzer\TypeAnalyzer;
use Psalm\Internal\Provider\ClassLikeStorageProvider;
use Psalm\Internal\Provider\FileReferenceProvider;
use Psalm\Internal\Provider\MethodExistenceProvider;
@@ -394,6 +395,7 @@ public function getMethodParams(
}

$overridden_method_id = $class_storage->documenting_method_ids[$appearing_method_name];

$overridden_storage = $this->getStorage($overridden_method_id);

list($overriding_fq_class_name) = explode('::', $overridden_method_id);
@@ -554,7 +556,7 @@ public function isVariadic($method_id)
public function getMethodReturnType(
$method_id,
&$self_class,
\Psalm\Internal\Analyzer\StatementsAnalyzer $statements_analyzer = null,
\Psalm\Internal\Analyzer\SourceAnalyzer $source_analyzer = null,
array $args = null
) {
list($original_fq_class_name, $original_method_name) = explode('::', $method_id);
@@ -599,8 +601,8 @@ public function getMethodReturnType(
) {
if ($appearing_method_id === 'Closure::fromcallable'
&& isset($args[0])
&& $statements_analyzer
&& ($first_arg_type = $statements_analyzer->node_data->getType($args[0]->value))
&& $source_analyzer
&& ($first_arg_type = $source_analyzer->getNodeTypeProvider()->getType($args[0]->value))
&& $first_arg_type->isSingle()
) {
foreach ($first_arg_type->getTypes() as $atomic_type) {
@@ -659,12 +661,19 @@ public function getMethodReturnType(
return null;
}

$candidate_type = null;

foreach ($class_storage->overridden_method_ids[$appearing_method_name] as $overridden_method_id) {
$overridden_storage = $this->getStorage($overridden_method_id);

if ($overridden_storage->return_type) {
if ($overridden_storage->return_type->isNull()) {
return Type::getVoid();
if ($candidate_type && !$candidate_type->isVoid()) {
return null;
}

$candidate_type = Type::getVoid();
continue;
}

list($fq_overridden_class) = explode('::', $overridden_method_id);
@@ -676,11 +685,45 @@ public function getMethodReturnType(

$self_class = $overridden_class_storage->name;

return $overridden_return_type;
if ($candidate_type
&& $source_analyzer
) {
$old_contained_by_new = TypeAnalyzer::isContainedBy(
$source_analyzer->getCodebase(),
$candidate_type,
$overridden_return_type
);

$new_contained_by_old = TypeAnalyzer::isContainedBy(
$source_analyzer->getCodebase(),
$overridden_return_type,
$candidate_type
);

if (!$old_contained_by_new && !$new_contained_by_old) {
$attempted_intersection = Type::intersectUnionTypes(
$candidate_type,
$overridden_return_type
);

if ($attempted_intersection) {
$candidate_type = $attempted_intersection;
continue;
}

return null;
}

if ($old_contained_by_new) {
continue;
}
}

$candidate_type = $overridden_return_type;
}
}

return null;
return $candidate_type;
}

/**
@@ -805,9 +805,10 @@ private function populateDataFromImplementedInterfaces(
}
}
}
$storage->overridden_method_ids[$method_name][$interface_method_ids[0]] = $interface_method_ids[0];
} else {
$storage->interface_method_ids[$method_name] = $interface_method_ids;
}

foreach ($interface_method_ids as $interface_method_id) {
$storage->overridden_method_ids[$method_name][$interface_method_id] = $interface_method_id;
}
}
}
@@ -261,11 +261,6 @@ class ClassLikeStorage
*/
public $documenting_method_ids = [];

/**
* @var array<string, array<string>>
*/
public $interface_method_ids = [];

/**
* @var array<string, string>
*/
@@ -1547,12 +1547,14 @@ public static function combineUnionTypes(
* @param Union $type_1
* @param Union $type_2
*
* @return Union
* @return ?Union
*/
public static function intersectUnionTypes(
Union $type_1,
Union $type_2
) {
$intersection_performed = false;

if ($type_1->isMixed() && $type_2->isMixed()) {
$combined_type = Type::getMixed();
} else {
@@ -1570,13 +1572,15 @@ public static function intersectUnionTypes(

if ($type_1->isMixed() && !$type_2->isMixed()) {
$combined_type = clone $type_2;
$intersection_performed = true;
} elseif (!$type_1->isMixed() && $type_2->isMixed()) {
$combined_type = clone $type_1;
$intersection_performed = true;
} else {
$combined_type = clone $type_1;

foreach ($combined_type->getTypes() as $t1_key => $type_1_atomic) {
foreach ($type_2->getTypes() as $type_2_atomic) {
foreach ($type_2->getTypes() as $t2_key => $type_2_atomic) {
if (($type_1_atomic instanceof TIterable
|| $type_1_atomic instanceof TNamedObject
|| $type_1_atomic instanceof TTemplateParam
@@ -1590,6 +1594,8 @@ public static function intersectUnionTypes(
$type_1_atomic->extra_types = [];
}

$intersection_performed = true;

$type_2_atomic_clone = clone $type_2_atomic;

$type_2_atomic_clone->extra_types = [];
@@ -1609,6 +1615,11 @@ public static function intersectUnionTypes(
if ($type_1_atomic instanceof TObject && $type_2_atomic instanceof TNamedObject) {
$combined_type->removeType($t1_key);
$combined_type->addType(clone $type_2_atomic);
$intersection_performed = true;
} elseif ($type_2_atomic instanceof TObject && $type_1_atomic instanceof TNamedObject) {
$combined_type->removeType($t2_key);
$combined_type->addType(clone $type_1_atomic);
$intersection_performed = true;
}
}
}
@@ -1643,6 +1654,10 @@ public static function intersectUnionTypes(
}
}

if (!$intersection_performed && $type_1->getId() !== $type_2->getId()) {
return null;
}

if ($type_1->possibly_undefined && $type_2->possibly_undefined) {
$combined_type->possibly_undefined = true;
}
@@ -639,6 +639,26 @@ function takesIterableOfIntersections(iterable $i): void {
}
}',
],
'inheritDocFromObviousInterface' => [
'<?php
interface I1 {
/**
* @param string $type
* @return bool
*/
public function takesString($type);
}
interface I2 extends I1 {
public function takesString($type);
}
class C implements I2 {
public function takesString($type) {
return true;
}
}',
],
];
}

@@ -1724,6 +1724,9 @@ interface Collection extends \IteratorAggregate
public function getIterator(): \Iterator;
}
/**
* @template-implements Collection<string>
*/
abstract class Set implements Collection {
public function forEach(callable $action): void {
$i = $this->getIterator();

0 comments on commit a419117

Please sign in to comment.
You can’t perform that action at this time.