Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes vimeo#8112 #8792

Merged
merged 6 commits into from Dec 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 25 additions & 0 deletions src/Psalm/Internal/Type/Comparator/ObjectComparator.php
Expand Up @@ -6,11 +6,14 @@
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
use Psalm\Type\Atomic;
use Psalm\Type\Atomic\TIterable;
use Psalm\Type\Atomic\TMixed;
use Psalm\Type\Atomic\TNamedObject;
use Psalm\Type\Atomic\TObjectWithProperties;
use Psalm\Type\Atomic\TTemplateParam;
use Psalm\Type\Union;

use function count;
use function current;
use function in_array;
use function strpos;
use function strtolower;
Expand All @@ -31,6 +34,28 @@ public static function isShallowlyContainedBy(
bool $allow_interface_equality,
?TypeComparisonResult $atomic_comparison_result
): bool {
if ($container_type_part instanceof TTemplateParam
&& $input_type_part instanceof TTemplateParam
&& $container_type_part->defining_class != $input_type_part->defining_class
&& strpos($container_type_part->defining_class, 'fn-') !== 0
&& strpos($input_type_part->defining_class, 'fn-') !== 0
&& 1 == count($container_type_part->as->getAtomicTypes())
&& 1 == count($input_type_part->as->getAtomicTypes())) {
$containerAs = current($container_type_part->as->getAtomicTypes());
$inputAs = current($input_type_part->as->getAtomicTypes());
if ($containerAs instanceof TNamedObject && $inputAs instanceof TNamedObject) {
return self::isShallowlyContainedBy(
$codebase,
$inputAs,
$containerAs,
$allow_interface_equality,
$atomic_comparison_result,
);
} elseif ($containerAs instanceof TMixed && $inputAs instanceof TMixed) {
return true;
}
}

$intersection_input_types = self::getIntersectionTypes($input_type_part);
$intersection_container_types = self::getIntersectionTypes($container_type_part);

Expand Down
3 changes: 0 additions & 3 deletions tests/Template/ClassTemplateExtendsTest.php
Expand Up @@ -1635,9 +1635,6 @@ public function __construct(array $elements = [])

public function getIterator()
{
/**
* @psalm-suppress InvalidReturnStatement
*/
return new ArrayIterator($this->elements);
}

Expand Down
123 changes: 123 additions & 0 deletions tests/Template/NestedTemplateTest.php
Expand Up @@ -121,6 +121,129 @@ function toList(array $arr): array {
return reset($arr);
}',
],
'3levelNestedTemplatesOfMixed' => [
'code' => '<?php
/** @template T */
interface A {}

/**
* @template T
* @template U of A<T>
*/
interface B {}

/** @template T */
interface J {}

/**
* @template T
* @template U of A<T>
* @implements J<U>
*/
class K2 implements J {}

/**
* @template T
* @template U of A<T>
* @template V of B<T, U>
* @extends J<V>
*/
interface K3 extends J {}

/**
* @template T
* @template U of A<T>
* @template V of B<T, U>
* @implements J<V>
*/
class K1 implements J {}',
],
'4levelNestedTemplatesOfObjects' => [
'code' => '<?php
/**
* Interface for all DB entities that map to some data-model object.
*
* @template T
*/
interface DbEntity
{
/**
* Maps this entity to a data-model entity
*
* @return T Data-model entity to which this DB entity maps.
*/
public function toCore();
}

/**
* @template T of object
*/
abstract class EntityRepository {}

/**
* Base entity repository with common tooling.
*
* @template T of object
* @extends EntityRepository<T>
*/
abstract class DbEntityRepository
extends EntityRepository {}

interface ObjectId {}

/**
* @template I of ObjectId
*/
interface AnObject {}

/**
* Base entity repository with common tooling.
*
* @template I of ObjectId
* @template O of AnObject<I>
* @template E of DbEntity<O>
* @extends DbEntityRepository<E>
*/
abstract class AnObjectEntityRepository
extends DbEntityRepository
{}

/**
* Base repository implementation backed by a Db repository.
*
* @template T
* @template E of DbEntity<T>
* @template R of DbEntityRepository<E>
*/
abstract class DbRepositoryWrapper
{
/** @var R $repo Db repository */
private DbEntityRepository $repo;

/**
* Getter for the Db repository.
*
* @return DbEntityRepository The Db repository.
* @psalm-return R
*/
protected function getDbRepo(): DbEntityRepository
{
return $this->repo;
}
}

/**
* Base implementation for all custom repositories that map to Core objects.
*
* @template I of ObjectId
* @template O of AnObject<I>
* @template E of DbEntity<O>
* @template R of AnObjectEntityRepository<I, O, E>
* @extends DbRepositoryWrapper<O, E, R>
*/
abstract class AnObjectDbRepositoryWrapper
extends DbRepositoryWrapper {}',
],
];
}

Expand Down