,error_levels?:string[]}>
*/
public function providerValidCodeParse()
{
return [
'classTemplate' => [
'T = $T;
}
/**
* @return T
*/
public function bar() {
$t = $this->T;
return new $t();
}
}
$at = "A";
/** @var Foo */
$afoo = new Foo($at);
$afoo_bar = $afoo->bar();
$bfoo = new Foo(B::class);
$bfoo_bar = $bfoo->bar();
// this shouldn’t cause a problem as it’s a docbblock type
if (!($bfoo_bar instanceof B)) {}
$c = C::class;
$cfoo = new Foo($c);
$cfoo_bar = $cfoo->bar();',
'assertions' => [
'$afoo' => 'Foo',
'$afoo_bar' => 'A',
'$bfoo' => 'Foo',
'$bfoo_bar' => 'B',
'$cfoo' => 'Foo',
'$cfoo_bar' => 'C',
],
'error_levels' => [
'MixedReturnStatement',
'LessSpecificReturnStatement',
'DocblockTypeContradiction',
'TypeCoercion',
],
],
'classTemplateSelfs' => [
'T = $T;
}
/**
* @return T
*/
public function bar() {
$t = $this->T;
return new $t();
}
}
class E {
/**
* @return Foo
*/
public static function getFoo() {
return new Foo(__CLASS__);
}
/**
* @return Foo
*/
public static function getFoo2() {
return new Foo(self::class);
}
/**
* @return Foo
*/
public static function getFoo3() {
return new Foo(static::class);
}
}
class G extends E {}
$efoo = E::getFoo();
$efoo2 = E::getFoo2();
$efoo3 = E::getFoo3();
$gfoo = G::getFoo();
$gfoo2 = G::getFoo2();
$gfoo3 = G::getFoo3();',
'assertions' => [
'$efoo' => 'Foo',
'$efoo2' => 'Foo',
'$efoo3' => 'Foo',
'$gfoo' => 'Foo',
'$gfoo2' => 'Foo',
'$gfoo3' => 'Foo',
],
'error_levels' => [
'LessSpecificReturnStatement',
'RedundantConditionGivenDocblockType',
],
],
'classTemplateExternalClasses' => [
'T = $T;
}
/**
* @return T
*/
public function bar() {
$t = $this->T;
return new $t();
}
}
$efoo = new Foo(\Exception::class);
$efoo_bar = $efoo->bar();
$ffoo = new Foo(\LogicException::class);
$ffoo_bar = $ffoo->bar();',
'assertions' => [
'$efoo' => 'Foo',
'$efoo_bar' => 'Exception',
'$ffoo' => 'Foo',
'$ffoo_bar' => 'LogicException',
],
'error_levels' => ['LessSpecificReturnStatement'],
],
'classTemplateContainer' => [
'obj = $obj;
}
/**
* @return T
*/
public function bar() {
return $this->obj;
}
/**
* @return T
*/
public function bat() {
return $this->bar();
}
public function __toString(): string {
return "hello " . $this->obj;
}
}
$afoo = new Foo(new A());
$afoo_bar = $afoo->bar();',
'assertions' => [
'$afoo' => 'Foo',
'$afoo_bar' => 'A',
],
'error_levels' => ['MixedOperand'],
],
'validTemplatedType' => [
' [
' [
'bar(new A());',
],
'validTemplatedStaticMethodType' => [
' [
'foo("string"));',
],
'genericArrayKeys' => [
' $arr
* @return array
*/
function my_array_keys($arr) {
return array_keys($arr);
}
$a = my_array_keys(["hello" => 5, "goodbye" => new \Exception()]);',
'assertions' => [
'$a' => 'array',
],
],
'genericArrayFlip' => [
' $arr
* @return array
*/
function my_array_flip($arr) {
return array_flip($arr);
}
$b = my_array_flip(["hello" => 5, "goodbye" => 6]);',
'assertions' => [
'$b' => 'array',
],
],
'byRefKeyValueArray' => [
' $arr
*/
function byRef(array &$arr) : void {}
$b = ["a" => 5, "c" => 6];
byRef($b);',
'assertions' => [
'$b' => 'array',
],
],
'byRefMixedKeyArray' => [
' $arr
*/
function byRef(array &$arr) : void {}
$b = ["a" => 5, "c" => 6];
byRef($b);',
'assertions' => [
'$b' => 'array',
],
],
'mixedArrayPop' => [
' $arr
* @return TValue|null
*/
function my_array_pop(array &$arr) {
return array_pop($arr);
}
/** @var mixed */
$b = ["a" => 5, "c" => 6];
$a = my_array_pop($b);',
'assertions' => [
'$a' => 'mixed',
'$b' => 'array',
],
'error_levels' => ['MixedAssignment', 'MixedArgument'],
],
'genericArrayPop' => [
' $arr
* @return TValue|null
*/
function my_array_pop(array &$arr) {
return array_pop($arr);
}
$b = ["a" => 5, "c" => 6];
$a = my_array_pop($b);',
'assertions' => [
'$a' => 'null|int',
'$b' => 'array',
],
],
'intersectionTemplatedTypes' => [
' */
private $data;
/** @psalm-param iterable $data */
public function __construct(iterable $data) {
$this->data = $data;
}
}
class Item {}
/** @psalm-param Collection- $c */
function takesCollectionOfItems(Collection $c): void {}
/** @psalm-var iterable
- $data2 */
$data2 = [];
takesCollectionOfItems(new Collection($data2));
/** @psalm-var iterable
- &Countable $data */
$data = [];
takesCollectionOfItems(new Collection($data));',
],
'templateCallableReturnType' => [
' [
' [
' */
private $data;
/** @param array $data */
public function __construct(array $data) {
$this->data = $data;
}
/**
* @template T
* @param Closure(TValue):T $func
* @return ArrayCollection
*/
public function map(Closure $func) {
return new static(array_map($func, $this->data));
}
}
class Item {}
/**
* @param ArrayCollection $i
*/
function takesCollectionOfItems(ArrayCollection $i): void {}
$c = new ArrayCollection([ new Item ]);
takesCollectionOfItems($c);
takesCollectionOfItems($c->map(function(Item $i): Item { return $i;}));
takesCollectionOfItems($c->map(function(Item $i): Item { return $i;}));',
],
'replaceChildTypeWithGenerator' => [
' $t
* @return array
*/
function f(Traversable $t): array {
$ret = [];
foreach ($t as $k => $v) $ret[$k] = $v;
return $ret;
}
/** @return Generator */
function g():Generator { yield new stdClass; }
takesArrayOfStdClass(f(g()));
/** @param array $p */
function takesArrayOfStdClass(array $p): void {}',
],
'noRepeatedTypeException' => [
' */
private $items;
/**
* @param class-string $type
* @template-typeof T $type
*/
public function __construct(string $type)
{
if (!in_array($type, [A::class, B::class], true)) {
throw new \InvalidArgumentException;
}
$this->type = $type;
$this->items = [];
}
/** @param T $item */
public function add($item): void
{
$this->items[] = $item;
}
}
class FooFacade
{
/**
* @template T
* @param T $item
*/
public function add($item): void
{
$foo = $this->ensureFoo([$item]);
$foo->add($item);
}
/**
* @template T
* @param array $items
* @return Foo
*/
private function ensureFoo(array $items): Foo
{
$type = $items[0] instanceof A ? A::class : B::class;
return new Foo($type);
}
}
class A {}
class B {}',
],
'collectionOfClosure' => [
'
* @psalm-suppress MixedTypeCoercion
*/
public function filter(Closure $p) {
return $this;
}
}
class I {}
/** @var Collection> $c */
$c = new Collection;
$c->filter(
/** @param Collection $elt */
function(Collection $elt): bool { return (bool) rand(0,1); }
);
$c->filter(
/** @param Collection $elt */
function(Collection $elt): bool { return true; }
);',
],
'splatTemplateParam' => [
' $arr
* @param array $arr2
* @return array
*/
function splat_proof(array $arr, array $arr2) {
return $arr;
}
$foo = [
[1, 2, 3],
[1, 2],
];
$a = splat_proof(... $foo);',
'assertions' => [
'$a' => 'array',
],
],
'passArrayByRef' => [
' $_arr
* @return null|TValue
* @psalm-ignore-nullable-return
*/
function fRef(array &$_arr) {
return array_shift($_arr);
}
/**
* @template TKey as array-key
* @template TValue
*
* @param array $_arr
* @return null|TValue
* @psalm-ignore-nullable-return
*/
function fNoRef(array $_arr) {
return array_shift($_arr);
}',
],
'templatedInterfaceIteration' => [
' */
public function getIterator();
}
class Collection implements ICollection {
/** @var array */
private $data;
public function __construct(array $data) {
$this->data = $data;
}
/** @psalm-suppress LessSpecificImplementedReturnType */
public function getIterator(): \Traversable {
return new \ArrayIterator($this->data);
}
}
/** @var ICollection */
$c = new Collection(["a" => 1]);
foreach ($c as $k => $v) { atan($v); strlen($k); }',
],
'templatedInterfaceGetIteratorIteration' => [
' */
public function getIterator();
}
class Collection implements ICollection {
/** @var array */
private $data;
public function __construct(array $data) {
$this->data = $data;
}
/** @psalm-suppress LessSpecificImplementedReturnType */
public function getIterator(): \Traversable {
return new \ArrayIterator($this->data);
}
}
/** @var ICollection */
$c = new Collection(["a" => 1]);
foreach ($c->getIterator() as $k => $v) { atan($v); strlen($k); }',
],
'implictIteratorTemplating' => [
'
*/
class SomeIterator implements IteratorAggregate
{
function getIterator()
{
yield 1;
}
}
/** @param \IteratorAggregate $i */
function takesIteratorOfInts(\IteratorAggregate $i) : void {
foreach ($i as $j) {
echo $j;
}
}
takesIteratorOfInts(new SomeIterator());',
],
'allowTemplatedIntersectionToExtend' => [
'bar = $bar;
}
/**
* @return T&Foo
*/
public function makeFoo()
{
return $this->bar;
}
}',
],
'restrictTemplateInputWithTClassGoodInput' => [
' */
private $items;
/**
* @param T::class $type
*/
public function __construct(string $type)
{
if (!in_array($type, [A::class, B::class], true)) {
throw new \InvalidArgumentException;
}
$this->type = $type;
$this->items = [];
}
/** @param T $item */
public function add($item): void
{
$this->items[] = $item;
}
}
class A {}
class B {}
$foo = new Foo(A::class);
$foo->add(new A);',
],
'classTemplateAsCorrect' => [
' [
' [
' [
't = $t;
}
/**
* @return T
*/
public function getFoo()
{
return $this->t;
}
}
function passFoo(Foo $f) : Foo {
return (new FooGetter($f))->getFoo();
}',
],
'templateFunctionVar' => [
'bar();
/** @var T&D */
$b = $some_t;
$b->bar();
/** @var D&T */
$b = $some_t;
$b->bar();
return $a;
}',
'assertions' => [],
'error_levels' => ['MixedAssignment', 'MissingParamType'],
],
'returnClassString' => [
' [
' [],
'error_levels' => ['MixedMethodCall'],
],
'returnTemplatedClassClassName' => [
'loader(FooChild::class);',
'assertions' => [
'$a' => 'null|FooChild',
],
],
'upcastIterableToTraversable' => [
' [],
'error_levels' => ['MixedAssignment'],
],
'upcastGenericIterableToGenericTraversable' => [
'
* @param T::class $class
*/
function foo(string $class) : void {
$a = new $class();
foreach ($a as $b) {}
}',
'assertions' => [],
'error_levels' => [],
],
'bindFirstTemplatedClosureParameter' => [
' [
'
*/
private $type;
/**
* @param class-string $type
*/
public function __construct(string $type) {
$this->type = $type;
}
/**
* @return class-string
*/
public function getType()
{
return $this->type;
}
/**
* @param T $object
*/
public function bar(Foo $object) : void
{
if ($this->getType() !== get_class($object)) {
return;
}
echo $object->id;
}
}
class FooChild extends Foo {}
/** @param Collection $c */
function handleCollectionOfFoo(Collection $c) : void {
if ($c->getType() === FooChild::class) {}
}',
],
'getEquateClass' => [
'obj = $obj;
}
/**
* @param T $object
*/
public function bar(Foo $object) : void
{
if ($this->obj === $object) {}
}
}',
],
'allowComparisonGetTypeResult' => [
'
*/
private $type;
/**
* @param class-string $type
*/
public function __construct(string $type) {
$this->type = $type;
}
/**
* @return class-string|null
*/
public function getType()
{
return $this->type;
}
}
function foo(Collection $c) : void {
$val = $c->getType();
if (!$val) {}
if ($val) {}
}',
],
'mixedTemplatedParamOutWithNoExtendedTemplate' => [
'v = $v;
}
/**
* @return TValue
*/
public function getValue()
{
return $this->v;
}
}
/**
* @template TKey
* @template TValue
*/
class KeyValueContainer extends ValueContainer
{
/**
* @var TKey
*/
private $k;
/**
* @param TKey $k
* @param TValue $v
*/
public function __construct($k, $v)
{
$this->k = $k;
parent::__construct($v);
}
/**
* @return TKey
*/
public function getKey()
{
return $this->k;
}
}
$a = new KeyValueContainer("hello", 15);
$b = $a->getValue();',
[
'$a' => 'KeyValueContainer',
'$b' => 'mixed',
],
'error_levels' => ['MixedAssignment'],
],
'mixedTemplatedParamOutDifferentParamName' => [
'v = $v;
}
/**
* @return TValue
*/
public function getValue()
{
return $this->v;
}
}
/**
* @template TKey
* @template Tv
*/
class KeyValueContainer extends ValueContainer
{
/**
* @var TKey
*/
private $k;
/**
* @param TKey $k
* @param Tv $v
*/
public function __construct($k, $v)
{
$this->k = $k;
parent::__construct($v);
}
/**
* @return TKey
*/
public function getKey()
{
return $this->k;
}
}
$a = new KeyValueContainer("hello", 15);
$b = $a->getValue();',
[
'$a' => 'KeyValueContainer',
'$b' => 'mixed',
],
'error_levels' => ['MixedAssignment'],
],
'doesntExtendTemplateAndDoesNotOverride' => [
'id = $id;
}
/**
* @return T
*/
public function getID()
{
return $this->id;
}
}
class AppUser extends User {}
$au = new AppUser(-1);
$id = $au->getId();',
[
'$au' => 'AppUser',
'$id' => 'array-key',
],
],
'callableReturnsItself' => [
' [
' [
'getIterator();',
[
'$i' => 'Traversable',
],
],
'upcastArrayToIterable' => [
' $collection
* @return V
* @psalm-suppress InvalidReturnType
*/
function first($collection) {}
$one = first([1,2,3]);',
[
'$one' => 'int',
],
],
'templateObjectLikeValues' => [
',1:Collection}
* @psalm-suppress InvalidReturnType
*/
public function partition() {}
}
/** @var Collection $c */
$c = new Collection;
[$partA, $partB] = $c->partition();',
[
'$partA' => 'Collection',
'$partB' => 'Collection',
],
],
'understandTemplatedCalculationInOtherFunction' => [
' [
'add(5, "hello");
$list->add("hello", 5);
/** @var SplDoublyLinkedList */
$templated_list = new SplDoublyLinkedList();
$templated_list->add(5, "hello");
$a = $templated_list->bottom();',
[
'$a' => 'string',
],
],
'objectReturn' => [
' $foo
*
* @return T
*/
function Foo(string $foo) : object {
return new $foo;
}
echo Foo(DateTime::class)->format("c");',
],
'genericInterface' => [
' $t
* @return T
*/
function generic(string $t) {
return f($t)->get();
}
/** @template T as object */
interface I {
/** @return T */
public function get() {}
}
/**
* @template T as object
* @template-implements I
*/
class C implements I {
/**
* @var T
*/
public $t;
/**
* @param T $t
*/
public function __construct(object $t) {
$this->t = $t;
}
/**
* @return T
*/
public function get() {
return $this->t;
}
}
/**
* @template T as object
* @param class-string $t
* @return I
*/
function f(string $t) {
return new C(new $t);
}',
],
'templateIntersectionLeft' => [
' [
' [
'|TReturn) $gen
* @return array
*/
function call(callable $gen) : array {
$return = $gen();
if ($return instanceof Generator) {
return [$gen->getReturn()];
}
return [$gen];
}
$arr = call(
/**
* @return Generator
*/
function() {
yield 1;
return "hello";
}
);',
[
'$arr' => 'array',
],
],
'templatedClassStringParam' => [
' $c_class
*
* @return C
* @psalm-return T
*/
public static function get(string $c_class) : C {
$c = new $c_class;
$c->foo();
return $c;
}
}
/**
* @param class-string $c_class
*/
function bar(string $c_class) : void {
$c = E::get($c_class);
$c->foo();
}
/**
* @psalm-suppress TypeCoercion
*/
function bat(string $c_class) : void {
$c = E::get($c_class);
$c->foo();
}',
],
'templatedClassStringParamMoreSpecific' => [
' $c_class
*
* @return C
* @psalm-return T
*/
public static function get(string $c_class) : C {
$c = new $c_class;
$c->foo();
return $c;
}
}
/**
* @param class-string $d_class
*/
function moreSpecific(string $d_class) : void {
$d = E::get($d_class);
$d->foo();
$d->faa();
}',
],
'templateOfWithSpace' => [
'
*/
class Foo
{
}
/**
* @param Foo> $a
*/
function bar(Foo $a) : void {}',
],
'templateDefaultSimpleString' => [
't = $t;
}
}
$c = new C();',
'assertions' => [
'$c===' => 'C',
],
],
'SKIPPED-templateDefaultConstant' => [
't = $t;
}
}
$e = new E();',
'assertions' => [
'$e===' => 'E',
],
],
'SKIPPED-templateDefaultClassConstant' => [
't = $t;
}
}
$e = new E();',
'assertions' => [
'$e===' => 'E',
],
],
'allowNullablePropertyAssignment' => [
' $foo
*/
public function __construct(I $foo) {
$this->bar = $foo->get();
}
}',
],
'allowUnionTypeParam' => [
' $y
*/
function example($x, $y): void {}
example(
/**
* @param int|false $x
*/
function($x): void {},
[strpos("str", "str")]
);',
],
'reflectionClass' => [
' $name
*/
class CustomReflectionClass {
/**
* @var class-string
*/
public $name;
/**
* @param T|class-string $argument
*/
public function __construct($argument) {
if (is_object($argument)) {
$this->name = get_class($argument);
} else {
$this->name = $argument;
}
}
}
/**
* @template T as object
* @param class-string $className
* @return CustomReflectionClass
*/
function getTypeOf(string $className) {
return new CustomReflectionClass($className);
}',
],
'ignoreTooManyArrayArgs' => [
'
*/
$b = [1, 2, 3];
takesArray($b);',
],
'ignoreTooManyGenericObjectArgs' => [
't = $t;
}
}
/** @param C $c */
function takesC(C $c) : void {}
/**
* @psalm-suppress TooManyTemplateParams
* @var C
*/
$c = new C(5);
takesC($c);',
],
'classTemplateUnionType' => [
' $c */
function foo(C $c) : void {}
/** @param C $c */
function bar(C $c) : void {}',
],
'functionTemplateUnionType' => [
' [
'$s' => 'string',
'$i' => 'int',
],
],
'unionAsTypeReturnType' => [
'
*/
public function filter(Closure $p);
}',
],
'converterObject' => [
' $records
*/
public $records;
/**
* @param array $records
*/
public function __construct(array $records) {
$this->records = $records;
}
/**
* @template Q2 as object
*
* @param Q2 $obj2
*
* @return array
*/
private function appender(object $obj2): array
{
$arr = [];
foreach ($this->records as $key => $obj) {
if (rand(0, 1)) {
$obj = $obj2;
}
$arr[$key] = $obj;
}
return $arr;
}
/**
* @template Q1 as object
*
* @param Q1 $obj
*
* @return array
*/
public function appendProperty(object $obj): array
{
return $this->appender($obj);
}
}',
],
'converterClassString' => [
' $records
*/
public $records;
/**
* @param array $records
*/
public function __construct(array $records) {
$this->records = $records;
}
/**
* @template Q as object
*
* @param class-string
$obj
*
* @return array
*/
public function appendProperty(string $obj): array
{
return $this->appender($obj);
}
/**
* @template Q as object
*
* @param class-string $obj2
*
* @return array
*/
private function appender(string $obj2): array
{
$arr = [];
foreach ($this->records as $key => $obj) {
if (rand(0, 1)) {
$obj = new $obj2;
}
$arr[$key] = $obj;
}
return $arr;
}
}',
],
'allowTemplateReconciliation' => [
'
*/
public function providerInvalidCodeParse()
{
return [
'invalidTemplatedType' => [
' 'InvalidScalarArgument',
],
'invalidTemplatedStaticMethodType' => [
' 'InvalidScalarArgument',
],
'invalidTemplatedInstanceMethodType' => [
'foo(4));',
'error_message' => 'InvalidScalarArgument',
],
'replaceChildTypeNoHint' => [
' $t
* @return array
*/
function f(Traversable $t): array {
$ret = [];
foreach ($t as $k => $v) $ret[$k] = $v;
return $ret;
}
function g():Generator { yield new stdClass; }
takesArrayOfStdClass(f(g()));
/** @param array $p */
function takesArrayOfStdClass(array $p): void {}',
'error_message' => 'MixedTypeCoercion',
],
'restrictTemplateInputWithClassString' => [
' */
private $items;
/**
* @param T::class $type
*/
public function __construct(string $type)
{
if (!in_array($type, [A::class, B::class], true)) {
throw new \InvalidArgumentException;
}
$this->type = $type;
$this->items = [];
}
/** @param T $item */
public function add($item): void
{
$this->items[] = $item;
}
}
class A {}
class B {}
$foo = new Foo(A::class);
$foo->add(new B);',
'error_message' => 'InvalidArgument',
],
'restrictTemplateInputWithTClassBadInput' => [
' */
private $items;
/**
* @param T::class $type
*/
public function __construct(string $type)
{
if (!in_array($type, [A::class, B::class], true)) {
throw new \InvalidArgumentException;
}
$this->type = $type;
$this->items = [];
}
/** @param T $item */
public function add($item): void
{
$this->items[] = $item;
}
}
class A {}
class B {}
$foo = new Foo(A::class);
$foo->add(new B);',
'error_message' => 'InvalidArgument',
],
'templatedClosureProperty' => [
' 'InvalidArgument - src' . DIRECTORY_SEPARATOR . 'somefile.php:20:34 - Argument 1 of type expects string, callable(State):(T as mixed)&Foo provided',
],
'classTemplateAsIncorrectClass' => [
' 'InvalidArgument',
],
'classTemplateAsIncorrectInterface' => [
' 'InvalidArgument',
],
'templateFunctionMethodCallWithoutMethod' => [
'bar();
}',
'error_message' => 'PossiblyUndefinedMethod',
],
'templateFunctionMethodCallWithoutAsType' => [
'bar();
}',
'error_message' => 'MixedMethodCall',
],
'forbidLossOfInformationWhenCoercing' => [
'
* @param T::class $class
*/
function foo(string $class) : void {}
function bar(Traversable $t) : void {
foo(get_class($t));
}',
'error_message' => 'MixedTypeCoercion',
],
'bindFirstTemplatedClosureParameter' => [
' 'InvalidScalarArgument',
],
'bindFirstTemplatedClosureParameterTypeCoercion' => [
' 'TypeCoercion',
],
'callableDoesNotReturnItself' => [
' 'InvalidScalarArgument',
],
'multipleArgConstraintWithMoreRestrictiveFirstArg' => [
' 'TypeCoercion',
],
'multipleArgConstraintWithMoreRestrictiveSecondArg' => [
' 'TypeCoercion',
],
'multipleArgConstraintWithLessRestrictiveThirdArg' => [
' 'TypeCoercion',
],
'possiblyInvalidArgumentWithUnionFirstArg' => [
' 'PossiblyInvalidArgument',
],
'possiblyInvalidArgumentWithUnionSecondArg' => [
' 'PossiblyInvalidArgument',
],
'templateWithNoReturn' => [
' 'InvalidReturnType',
],
'templateInvalidDocblockArgument' => [
'
* @psalm-suppress InvalidReturnType
*/
function violate($p) {}',
'error_message' => 'InvalidTemplateParam',
],
'doublyLinkedListBadParam' => [
' */
$templated_list = new SplDoublyLinkedList();
$templated_list->add(5, []);',
'error_message' => 'InvalidArgument',
],
'copyScopedClassInFunction' => [
' $foo
*/
function Foo(string $foo) : string {
return $foo;
}',
'error_message' => 'ReservedWord',
],
'copyScopedClassInNamespacedFunction' => [
' $foo
*/
function Foo(string $foo) : string {
return $foo;
}',
'error_message' => 'ReservedWord',
],
'copyScopedClassInNamespacedClass' => [
' 'ReservedWord',
],
'duplicateTemplateFunction' => [
'
*/
static function of($value): self
{
return new self($value);
}
/**
* @param T $value
*/
private function __construct($value)
{
$this->value = $value;
}
}',
'error_message' => 'InvalidDocblock',
],
];
}
}