2021-02-26 22:05:46 +01:00
|
|
|
<?php
|
2021-12-15 04:58:32 +01:00
|
|
|
|
2023-10-19 14:16:41 +02:00
|
|
|
declare(strict_types=1);
|
|
|
|
|
2021-02-26 22:05:46 +01:00
|
|
|
namespace Psalm\Tests\Template;
|
|
|
|
|
|
|
|
use Psalm\Tests\TestCase;
|
2021-12-04 21:55:53 +01:00
|
|
|
use Psalm\Tests\Traits\InvalidCodeAnalysisTestTrait;
|
|
|
|
use Psalm\Tests\Traits\ValidCodeAnalysisTestTrait;
|
2021-02-26 22:05:46 +01:00
|
|
|
|
2021-03-28 15:52:23 +02:00
|
|
|
class NestedTemplateTest extends TestCase
|
2021-02-26 22:05:46 +01:00
|
|
|
{
|
2021-12-04 21:55:53 +01:00
|
|
|
use InvalidCodeAnalysisTestTrait;
|
|
|
|
use ValidCodeAnalysisTestTrait;
|
2021-02-26 22:05:46 +01:00
|
|
|
|
|
|
|
public function providerValidCodeParse(): iterable
|
|
|
|
{
|
|
|
|
return [
|
|
|
|
'nestedTemplateExtends' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-02-26 22:05:46 +01:00
|
|
|
namespace Foo;
|
|
|
|
|
|
|
|
interface IBaseViewData {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @template TViewData
|
|
|
|
*/
|
|
|
|
class BaseModel {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @template TViewData as IBaseViewData
|
|
|
|
* @template TModel as BaseModel<TViewData>
|
|
|
|
*/
|
|
|
|
abstract class BaseRepository {}
|
|
|
|
|
|
|
|
class StudentViewData implements IBaseViewData {}
|
|
|
|
class TeacherViewData implements IBaseViewData {}
|
|
|
|
|
|
|
|
/** @extends BaseModel<StudentViewData> */
|
|
|
|
class StudentModel extends BaseModel {}
|
|
|
|
|
|
|
|
/** @extends BaseModel<TeacherViewData> */
|
|
|
|
class TeacherModel extends BaseModel {}
|
|
|
|
|
|
|
|
/** @extends BaseRepository<StudentViewData, StudentModel> */
|
|
|
|
class StudentRepository extends BaseRepository {}
|
|
|
|
|
|
|
|
/** @extends BaseRepository<TeacherViewData, TeacherModel> */
|
2022-12-18 17:15:15 +01:00
|
|
|
class TeacherRepository extends BaseRepository {}',
|
2021-02-26 22:05:46 +01:00
|
|
|
],
|
2021-02-27 06:22:31 +01:00
|
|
|
'unwrapIndirectGenericTemplated' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-02-27 06:22:31 +01:00
|
|
|
/**
|
|
|
|
* @template TInner
|
|
|
|
*/
|
|
|
|
interface Wrapper {
|
|
|
|
/** @return TInner */
|
|
|
|
public function unwrap() : object;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @template TInner2
|
|
|
|
* @template TWrapper2 of Wrapper<TInner2>
|
|
|
|
* @param TWrapper2 $wrapper
|
|
|
|
* @return TInner2
|
|
|
|
*/
|
|
|
|
function indirectUnwrap(Wrapper $wrapper) : object {
|
|
|
|
return unwrapGeneric($wrapper);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @template TInner1
|
|
|
|
* @template TWrapper1 of Wrapper<TInner1>
|
|
|
|
* @param TWrapper1 $wrapper
|
|
|
|
* @return TInner1
|
|
|
|
*/
|
|
|
|
function unwrapGeneric(Wrapper $wrapper) {
|
|
|
|
return $wrapper->unwrap();
|
2022-12-18 17:15:15 +01:00
|
|
|
}',
|
2021-02-27 06:22:31 +01:00
|
|
|
],
|
2021-02-27 07:00:05 +01:00
|
|
|
'unwrapFromTemplatedClassString' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-02-27 07:00:05 +01:00
|
|
|
/**
|
|
|
|
* @template TInner
|
|
|
|
*/
|
|
|
|
interface Wrapper {
|
|
|
|
/** @return TInner */
|
|
|
|
public function unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-07-17 00:38:39 +02:00
|
|
|
* @implements Wrapper<string>
|
2021-02-27 07:00:05 +01:00
|
|
|
*/
|
2021-07-17 00:38:39 +02:00
|
|
|
class StringWrapper implements Wrapper {
|
|
|
|
public function unwrap() {
|
|
|
|
return "hello";
|
|
|
|
}
|
|
|
|
}
|
2021-02-27 07:00:05 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @template TInner
|
|
|
|
* @template TWrapper of Wrapper<TInner>
|
|
|
|
*
|
|
|
|
* @param class-string<TWrapper> $class
|
|
|
|
* @return TInner
|
|
|
|
*/
|
|
|
|
function load(string $class) {
|
|
|
|
$package = new $class();
|
|
|
|
return $package->unwrap();
|
|
|
|
}
|
|
|
|
|
2022-12-18 17:15:15 +01:00
|
|
|
$result = load(StringWrapper::class);',
|
2021-02-27 07:00:05 +01:00
|
|
|
],
|
2021-03-28 15:52:23 +02:00
|
|
|
'unwrapNestedTemplateWithReset' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-03-28 15:52:23 +02:00
|
|
|
/**
|
|
|
|
* @template TValue
|
|
|
|
* @template TArray of non-empty-array<TValue>
|
|
|
|
* @param TArray $arr
|
|
|
|
* @return TValue
|
|
|
|
*/
|
|
|
|
function toList(array $arr): array {
|
|
|
|
return reset($arr);
|
2022-12-18 17:15:15 +01:00
|
|
|
}',
|
2021-03-28 15:52:23 +02:00
|
|
|
],
|
2022-11-30 17:17:38 +01:00
|
|
|
'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>
|
|
|
|
*/
|
2022-12-21 11:51:42 +01:00
|
|
|
class K1 implements J {}',
|
2022-11-30 17:17:38 +01:00
|
|
|
],
|
|
|
|
'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
|
2022-12-21 11:51:42 +01:00
|
|
|
extends DbRepositoryWrapper {}',
|
|
|
|
],
|
2023-01-11 13:17:06 +01:00
|
|
|
'4levelNestedTemplateAsFunctionParameter' => [
|
|
|
|
'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 {}
|
|
|
|
|
|
|
|
abstract class Utilities {
|
|
|
|
/**
|
|
|
|
* @template I of ObjectId
|
|
|
|
* @template O of AnObject<I>
|
|
|
|
* @template E of DbEntity<O>
|
|
|
|
* @template R of AnObjectEntityRepository<I, O, E>
|
|
|
|
* @psalm-param AnObjectDbRepositoryWrapper<I, O, E, R> $repo
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
abstract public static function doSomething(AnObjectDbRepositoryWrapper $repo): void;
|
|
|
|
}',
|
|
|
|
],
|
2021-02-26 22:05:46 +01:00
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
public function providerInvalidCodeParse(): iterable
|
|
|
|
{
|
|
|
|
return [
|
|
|
|
'nestedTemplateExtendsInvalid' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-02-26 22:05:46 +01:00
|
|
|
namespace Foo;
|
|
|
|
|
|
|
|
interface IBaseViewData {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @template TViewData
|
|
|
|
*/
|
|
|
|
class BaseModel {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @template TViewData as IBaseViewData
|
|
|
|
* @template TModel as BaseModel<TViewData>
|
|
|
|
*/
|
|
|
|
abstract class BaseRepository {}
|
|
|
|
|
|
|
|
class StudentViewData implements IBaseViewData {}
|
|
|
|
class TeacherViewData implements IBaseViewData {}
|
|
|
|
|
|
|
|
/** @extends BaseModel<StudentViewData> */
|
|
|
|
class StudentModel extends BaseModel {}
|
|
|
|
|
|
|
|
/** @extends BaseModel<TeacherViewData> */
|
|
|
|
class TeacherModel extends BaseModel {}
|
|
|
|
|
|
|
|
/** @extends BaseRepository<StudentViewData, TeacherModel> */
|
|
|
|
class StudentRepository extends BaseRepository {}',
|
2022-12-18 17:15:15 +01:00
|
|
|
'error_message' => 'InvalidTemplateParam',
|
2021-02-26 22:05:46 +01:00
|
|
|
],
|
|
|
|
];
|
|
|
|
}
|
|
|
|
}
|