Skip to content

Commit

Permalink
Merge pull request #10189 from boesing/feature/inherited-assertions-v2
Browse files Browse the repository at this point in the history
Inherited assertions v2
  • Loading branch information
orklah authored Sep 28, 2023
2 parents e110305 + 56b719b commit bbcf503
Show file tree
Hide file tree
Showing 3 changed files with 18 additions and 110 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,6 @@ public static function collect(
if ($static_class_storage->template_extended_params
&& $method_name
&& !empty($non_trait_class_storage->overridden_method_ids[$method_name])
&& isset($class_storage->methods[$method_name])
&& (!isset($non_trait_class_storage->methods[$method_name]->return_type)
|| $class_storage->methods[$method_name]->inherited_return_type)
) {
foreach ($non_trait_class_storage->overridden_method_ids[$method_name] as $overridden_method_id) {
$overridden_storage = $codebase->methods->getStorage($overridden_method_id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,13 @@
namespace Psalm\Internal\Codebase;

use Psalm\Codebase;
use Psalm\Storage\Assertion\IsType;
use Psalm\Storage\ClassLikeStorage;
use Psalm\Storage\MethodStorage;
use Psalm\Storage\Possibilities;
use Psalm\Type\Atomic\TTemplateParam;

use function array_filter;
use function array_map;
use function array_merge;
use function array_values;
use function reset;
use function strtolower;

/**
Expand Down Expand Up @@ -61,80 +57,9 @@ public function resolve(
* Since the inheritance does not provide its own assertions, we have to detect those
* from inherited classes
*/
$assertions += array_map(
fn(Possibilities $possibilities) => $this->modifyAssertionsForInheritance(
$possibilities,
$this->codebase,
$called_class,
$inherited_classes_and_interfaces,
),
$potential_assertion_providing_method_storage->assertions,
);
$assertions += $potential_assertion_providing_method_storage->assertions;
}

return $assertions;
}

/**
* In case the called class is either implementing or extending a class/interface which does also has the
* template we are searching for, we assume that the called method has the same assertions.
*
* @param list<class-string> $potential_assertion_providing_classes
*/
private function modifyAssertionsForInheritance(
Possibilities $possibilities,
Codebase $codebase,
ClassLikeStorage $called_class,
array $potential_assertion_providing_classes
): Possibilities {
$replacement = new Possibilities($possibilities->var_id, []);
$extended_params = $called_class->template_extended_params;
foreach ($possibilities->rule as $assertion) {
if (!$assertion instanceof IsType
|| !$assertion->type instanceof TTemplateParam) {
$replacement->rule[] = $assertion;
continue;
}

/** Called class does not extend the template parameter */
$extended_templates = $called_class->template_extended_params;
if (!isset($extended_templates[$assertion->type->defining_class][$assertion->type->param_name])) {
$replacement->rule[] = $assertion;
continue;
}

foreach ($potential_assertion_providing_classes as $potential_assertion_providing_class) {
if (!isset($extended_params[$potential_assertion_providing_class][$assertion->type->param_name])) {
continue;
}

if (!$codebase->classlike_storage_provider->has($potential_assertion_providing_class)) {
continue;
}

$potential_assertion_providing_classlike_storage = $codebase->classlike_storage_provider->get(
$potential_assertion_providing_class,
);
if (!isset(
$potential_assertion_providing_classlike_storage->template_types[$assertion->type->param_name],
)) {
continue;
}

$replacement->rule[] = new IsType(new TTemplateParam(
$assertion->type->param_name,
reset(
$potential_assertion_providing_classlike_storage->template_types[$assertion->type->param_name],
),
$potential_assertion_providing_class,
));

continue 2;
}

$replacement->rule[] = $assertion;
}

return $replacement;
}
}
48 changes: 17 additions & 31 deletions tests/AssertAnnotationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2890,9 +2890,6 @@ public static function doAssert($value): void
/** @template InstanceType */
interface PluginManagerInterface
{
/** @return InstanceType */
public function get(): mixed;
/** @psalm-assert InstanceType $value */
public function validate(mixed $value): void;
}
Expand All @@ -2903,15 +2900,6 @@ public function validate(mixed $value): void;
*/
abstract class AbstractPluginManager implements PluginManagerInterface
{
/** @param InstanceType $value */
public function __construct(private readonly mixed $value)
{}
/** {@inheritDoc} */
public function get(): mixed
{
return $this->value;
}
}
/**
Expand All @@ -2920,49 +2908,47 @@ public function get(): mixed
*/
abstract class AbstractSingleInstancePluginManager extends AbstractPluginManager
{
/**
* An object type that the created instance must be instanced of
*
* @var class-string<InstanceType>
*/
protected string $instanceOf;
/** {@inheritDoc} */
public function get(): object
{
return parent::get();
}
/** {@inheritDoc} */
public function validate(mixed $value): void
{
}
}
}
namespace Namespace2 {
use Namespace1\AbstractSingleInstancePluginManager;
use InvalidArgumentException;use Namespace1\AbstractSingleInstancePluginManager;
use Namespace1\AbstractPluginManager;
use stdClass;
/** @template-extends AbstractSingleInstancePluginManager<stdClass> */
final class Qoo extends AbstractSingleInstancePluginManager
{
/** @var class-string<stdClass> */
protected string $instanceOf = stdClass::class;
}
/** @template-extends AbstractPluginManager<callable> */
final class Ooq extends AbstractPluginManager
{
public function validate(mixed $value): void
{
}
}
}
namespace {
$baz = new \Namespace2\Qoo(new stdClass);
$baz = new \Namespace2\Qoo();
/** @var mixed $object */
$object = null;
$baz->validate($object);
$ooq = new \Namespace2\Ooq();
/** @var mixed $callable */
$callable = null;
$ooq->validate($callable);
}
',
'assertions' => [
'$object===' => 'stdClass',
'$callable===' => 'callable',
],
'ignored_issues' => [],
'php_version' => '8.1',
Expand Down

0 comments on commit bbcf503

Please sign in to comment.