[ 'code' => ' */ abstract function elements() : array; /** * @return T|null */ public function first() { return $this->elements()[0] ?? null; } } class Service { /** * @use CollectionTrait */ use CollectionTrait; /** * @return array */ public function elements(): array { return [1, 2, 3, 4]; } }', ], 'extendedTraitUse' => [ 'code' => ' */ abstract function elements() : array; /** * @return T|null */ public function first() { return $this->elements()[0] ?? null; } } /** * @template TValue */ trait BridgeTrait { /** * @use CollectionTrait */ use CollectionTrait; } class Service { /** * @use BridgeTrait */ use BridgeTrait; /** * @return array */ public function elements(): array { return [1, 2, 3, 4]; } }', ], 'extendedTraitUseAlreadyBound' => [ 'code' => ' */ abstract function elements() : array; /** * @return T|null */ public function first() { return $this->elements()[0] ?? null; } } trait BridgeTrait { /** * @use CollectionTrait */ use CollectionTrait; } class Service { use BridgeTrait; /** * @return array */ public function elements(): array { return [1, 2, 3, 4]; } }', ], 'badTemplateUseUnionType' => [ 'code' => 't = $t; } } /** * @template TT */ class B { /** * @template-use T */ use T; }', ], 'allowTraitExtendAndImplementWithExplicitParamType' => [ 'code' => 'validate($value); $this->value = $value; } /** * @psalm-param T $value * * @param $value */ abstract protected function validate($value): void; } final class StringValidator { /** * @template-use ValueObjectTrait */ use ValueObjectTrait; /** * @param string $value */ protected function validate($value): void { if (strlen($value) > 30) { throw new \Exception("bad"); } } }', ], 'allowTraitExtendAndImplementWithoutExplicitParamType' => [ 'code' => 'validate($value); $this->value = $value; } /** * @psalm-param T $value * * @param $value */ abstract protected function validate($value): void; } final class StringValidator { /** * @template-use ValueObjectTrait */ use ValueObjectTrait; protected function validate($value): void { if (strlen($value) > 30) { throw new \Exception("bad"); } } }', ], 'traitInImplicitExtendedClass' => [ 'code' => ' */ class Bar implements Foo { use FooTrait; }', ], 'useTraitReturnTypeForInheritedInterface' => [ 'code' => 'doNormalize($v); } /** * @param TTraitValue $v * @return TTraitNormalizedValue */ abstract protected function doNormalize($v); } /** @implements Normalizer */ class StringNormalizer implements Normalizer { /** @use NormalizerTrait */ use NormalizerTrait; protected function doNormalize($v): string { return trim($v); } }', ], 'useTraitReturnTypeForInheritedClass' => [ 'code' => 'doNormalize($v); } /** * @param TTraitValue $v * @return TTraitNormalizedValue */ abstract protected function doNormalize($v); } /** @extends Normalizer */ class StringNormalizer extends Normalizer { /** @use NormalizerTrait */ use NormalizerTrait; protected function doNormalize($v): string { return trim($v); } }', ], 'inheritTraitPropertyTKeyedArray' => [ 'code' => 'foo = $foo; } } /** @template TValue */ class B { /** @use A */ use A; }', ], 'inheritTraitPropertyArray' => [ 'code' => ' */ private $foo; /** @psalm-param array $foo */ public function __construct(array $foo) { $this->foo = $foo; } } /** @template TValue */ class B { /** @use A */ use A; }', ], 'applyTemplatedValueInTraitProperty' => [ 'code' => 'value = $value; } } class C { /** @use ValueTrait */ use ValueTrait; /** @var string */ private $value; public function __construct(string $value) { $this->value = $value; } }', ], 'traitSelfAsParam' => [ 'code' => ' [ 'code' => 'get()); } } /** * @template B */ class Bar { /** * @use Foo */ use Foo; /** * @param B $value */ public function __construct(public mixed $value) { } /** * @return B */ public function get() { return $this->value; } }', ], ]; } public function providerInvalidCodeParse(): iterable { return [ 'badTemplateUse' => [ 'code' => 't = $t; } } /** * @template TT */ class B { /** * @template-use T */ use T; }', 'error_message' => 'UndefinedDocblockClass', ], 'badTemplateUseBadFormat' => [ 'code' => 't = $t; } } /** * @template TT */ class B { /** * @template-use T< > */ use T; }', 'error_message' => 'InvalidDocblock', ], 'badTemplateUseInt' => [ 'code' => 't = $t; } } /** * @template TT */ class B { /** * @template-use int */ use T; }', 'error_message' => 'InvalidDocblock', ], 'badTemplateExtendsShouldBeUse' => [ 'code' => 't = $t; } } /** * @template TT */ class B { /** * @template-extends T */ use T; }', 'error_message' => 'InvalidDocblock', ], 'possiblyNullReferenceOnTraitDefinedMethod' => [ 'code' => ' */ protected $mocks = []; /** * @param TKey $offset * @return TValue|null * @psalm-suppress LessSpecificImplementedReturnType * @psalm-suppress ImplementedParamTypeMismatch */ public function offsetGet($offset) { return $this->mocks[$offset] ?? null; } } /** * @template TKey as array-key * @template TValue */ interface Arr { /** * @param TKey $offset * @return TValue|null */ public function offsetGet($offset); } /** * @template TKey as array-key * @template TValue * @implements Arr */ class C implements Arr { /** @use T1 */ use T1; /** * @param TKey $offset * @psalm-suppress MixedMethodCall */ public function foo($offset) : void { $this->offsetGet($offset)->bar(); } }', 'error_message' => 'PossiblyNullReference', ], 'possiblyNullReferenceOnTraitDefinedMethodExtended' => [ 'code' => ' */ protected $mocks = []; /** * @param TKey $offset * @return TValue|null * @psalm-suppress LessSpecificImplementedReturnType * @psalm-suppress ImplementedParamTypeMismatch */ public function offsetGet($offset) { return $this->mocks[$offset] ?? null; } } /** * @template TKey as array-key * @template TValue */ interface Arr { /** * @param TKey $offset * @return TValue|null */ public function offsetGet($offset); } /** * @template TKey as array-key * @template TValue * @implements Arr */ class C implements Arr { /** @use T1 */ use T1; } /** * @psalm-suppress MissingTemplateParam */ class D extends C { /** * @param mixed $offset * @psalm-suppress MixedArgument */ public function foo($offset) : void { $this->offsetGet($offset)->bar(); } }', 'error_message' => 'MixedMethodCall', ], ]; } }