,error_levels?:string[]}> */ public function providerValidCodeParse(): iterable { return [ 'assertTemplatedType' => [ ' $type * @template T * @psalm-assert T $value */ function assertInstanceOf($value, string $type): void { // some code } // Returns concreate implementation of Foo, which in this case is Bar function getImplementationOfFoo(): Foo { return new Bar(); } $bar = getImplementationOfFoo(); assertInstanceOf($bar, Bar::class); $bar->sayHello();', ], 'assertInstanceofTemplatedClassMethodUnknownClass' => [ ' $expected * @param mixed $actual * @psalm-assert T $actual */ public function assertInstanceOf($expected, $actual) : void {} /** * @param class-string $c */ function bar(string $c, object $e) : void { $this->assertInstanceOf($c, $e); echo $e->getCode(); } }', [], ['MixedArgument', 'MixedMethodCall'], ], 'assertInstanceofTemplatedClassMethodUnknownStringClass' => [ ' $expected * @param mixed $actual * @psalm-assert T $actual */ public function assertInstanceOf($expected, $actual) : void {} function bar(string $c, object $e) : void { $this->assertInstanceOf($c, $e); echo $e->getCode(); } }', [], ['MixedArgument', 'MixedMethodCall', 'ArgumentTypeCoercion'], ], 'assertInstanceofTemplatedFunctionUnknownClass' => [ ' $expected * @param mixed $actual * @psalm-assert T $actual */ function assertInstanceOf($expected, $actual) : void {} /** * @param class-string $c */ function bar(string $c, object $e) : void { assertInstanceOf($c, $e); echo $e->getCode(); }', [], ['MixedArgument', 'MixedMethodCall'], ], 'assertInstanceofTemplatedFunctionUnknownStringClass' => [ ' $expected * @param mixed $actual * @psalm-assert T $actual */ function assertInstanceOf($expected, $actual) : void {} function bar(string $c, object $e) : void { assertInstanceOf($c, $e); echo $e->getCode(); }', [], ['MixedArgument', 'MixedMethodCall', 'ArgumentTypeCoercion'], ], 'assertTypedArray' => [ ' $expected * @param mixed $actual * @psalm-assert T[] $actual */ function assertArrayOf($expected, $actual) : void {} function bar(array $arr) : void { assertArrayOf(A::class, $arr); foreach ($arr as $a) { $a->foo(); } }', ], 'assertTemplatedTypeString' => [ ' $type * @template T * @psalm-assert T $value */ function assertInstanceOf($value, string $type): void { // some code } function getFoo() : Foo { return new class implements Foo { public function bat(): void { echo "Hello"; } }; } $f = getFoo(); /** * @var mixed */ $class = "hello"; /** @psalm-suppress MixedArgument */ assertInstanceOf($f, $class); $f->bat();', [ '$f' => 'Foo', ], ], 'suppressRedundantCondition' => [ ' $expected * @param mixed $actual * @param string $message * * @template T * @psalm-assert T $actual */ function assertInstanceOf($expected, $actual) : void { } /** * @psalm-suppress RedundantCondition */ function takesA(A $a) : void { assertInstanceOf(A::class, $a); }', ], 'allowCanBeSameAfterAssertion' => [ ' [ ' [ ' [ ' [ ' $i * * @param iterable $i * @param class-string $type */ function assertAllInstanceOf(iterable $i, string $type): void { /** @psalm-suppress MixedAssignment */ foreach ($i as $elt) { if (!$elt instanceof $type) { throw new \UnexpectedValueException(""); } } } class A {} function getArray(): array { return []; } $array = getArray(); assertAllInstanceOf($array, A::class);', [ '$array' => 'array', ], ], 'assertAllIterableOfClass' => [ ' $i * * @param iterable $i * @param class-string $type */ function assertAllInstanceOf(iterable $i, string $type): void { /** @psalm-suppress MixedAssignment */ foreach ($i as $elt) { if (!$elt instanceof $type) { throw new \UnexpectedValueException(""); } } } class A {} function getIterable(): iterable { return []; } $iterable = getIterable(); assertAllInstanceOf($iterable, A::class);', [ '$iterable' => 'iterable', ], ], 'complicatedAssertAllInstanceOf' => [ ' $i * * @param iterable $i * @param class-string $type */ function allInstanceOf(iterable $i, string $type): bool { /** @psalm-suppress MixedAssignment */ foreach ($i as $elt) { if (!$elt instanceof $type) { return false; } } return true; } interface IBlogPost { public function getId(): int; } function getData(): iterable { return []; } $data = getData(); assert(allInstanceOf($data, IBlogPost::class)); foreach ($data as $post) { echo $post->getId(); }', ], 'assertUnionInNamespace' => [ ' $interface * @psalm-assert ExpectedType|class-string $value */ function implementsInterface($value, $interface, string $message = ""): void {} /** * @psalm-template ExpectedType of object * @param mixed $value * @psalm-param class-string $interface * @psalm-assert null|ExpectedType|class-string $value */ function nullOrImplementsInterface(?object $value, $interface, string $message = ""): void {} interface A { } /** * @param mixed $value * * @psalm-return A|class-string */ function consume($value) { implementsInterface($value, A::class); return $value; } /** * @param mixed $value * * @psalm-return A|class-string|null */ function consume2($value) { nullOrImplementsInterface($value, A::class); return $value; }' ], 'assertTemplatedTemplateSimple' => [ ' $c * * @return T2 */ function example(Clazz $c) { /** @var mixed */ $x = 0; $c->is($x); return $x; }' ], 'assertTemplatedTemplateIfTrue' => [ ' $c * * @return T2|false */ function example(Clazz $c) { /** @var mixed */ $x = 0; return $c->is($x) ? $x : false; }' ], 'assertOnClass' => [ 'matches($value)); return $value; } }' ], 'noCrashWhenAsserting' => [ ' $expectedType * @psalm-assert class-string $actualType */ function assertIsA(string $expectedType, string $actualType): void { \assert(\is_a($actualType, $expectedType, true)); } class Foo { /** * @psalm-template OriginalClass of object * @psalm-param class-string $originalClass * @psalm-return class-string|null */ private function generateProxy(string $originalClass) : ?string { $generatedClassName = self::class . \'\\\\\' . $originalClass; if (class_exists($generatedClassName)) { assertIsA($originalClass, $generatedClassName); return $generatedClassName; } return null; } }', ], 'castClassStringWithIsA' => [ ' $expectedType * @psalm-return class-string */ function castStringToClassString(string $expectedType, string $anyString): string { \assert(\is_a($anyString, $expectedType, true)); return $anyString; }' ], 'classTemplateAssert' => [ 'value = $value; } } /** * @template FieldDefinitionType * * @param string|bool|int|null $value * @param FieldDefinition $definition * * @return FieldValue */ function fromScalarAndDefinition($value, FieldDefinition $definition) : FieldValue { $definition->assertAppliesToValue($value); return new FieldValue($value); } /** * @template ExpectedFieldType */ final class FieldDefinition { /** * @param mixed $value * @psalm-assert ExpectedFieldType $value */ public function assertAppliesToValue($value): void { throw new \Exception("bad"); } }' ], 'assertThrowsInstanceOfFunction' => [ ' $exceptionType * @psalm-assert T $outerEx */ function assertThrowsInstanceOf(\Throwable $outerEx, string $exceptionType) : void { if (!($outerEx instanceof $exceptionType)) { throw new \Exception("thrown instance of wrong type"); } }' ], 'dontBleedTemplateTypeInArray' => [ ' $class * @psalm-assert array> $value * * @param array $value * @param string $class */ function allIsAOf($value, $class): void {} /** * @psalm-template T of object * * @param array $value * @param class-string $class * * @return array> */ function f($value, $class) { allIsAOf($value, $class); return $value; }' ], 'noCrashOnListKeyAssertion' => [ ' $list */ function takesList(array $list) : void { foreach ($list as $i => $l) { assertSame($i, $l); } }' ], 'assertSameOnMemoizedMethodCall' => [ 'getMessage()); } try { validateUsername("invalid#1"); } catch (Exception $e) { assertSame("b", $e->getMessage()); } } /** * @psalm-template ExpectedType * @psalm-param ExpectedType $expected * @psalm-param mixed $actual * @psalm-assert =ExpectedType $actual */ function assertSame($expected, $actual): void { if ($actual !== $expected) { throw new Exception("Bad"); } } function validateUsername(string $username): void { if (strlen($username) < 5) { throw new Exception("Username must be at least 5 characters long"); } }' ], 'noCrashWhenAssertingTemplatedKeyOf' => [ ' $key */ function keyExists(array $arr, $key) : void {}' ], ]; } /** * @return iterable */ public function providerInvalidCodeParse(): iterable { return [ 'detectRedundantCondition' => [ ' $expected * @param mixed $actual * @param string $message * * @template T * @psalm-assert T $actual */ function assertInstanceOf($expected, $actual) : void { } function takesA(A $a) : void { assertInstanceOf(A::class, $a); }', 'error_message' => 'RedundantCondition', ], 'detectAssertSameTypeDoesNotContainType' => [ ' 'TypeDoesNotContainType', ], 'detectAssertAlwaysSame' => [ ' 'RedundantCondition', ], 'detectNeverCanBeSameAfterAssertion' => [ ' 'TypeDoesNotContainType', ], 'detectNeverCanBeNotSameAfterAssertion' => [ ' 'RedundantCondition', ], 'detectNeverCanBeEqualAfterAssertion' => [ ' 'TypeDoesNotContainType', ], 'detectIntFloatNeverCanBeEqualAfterAssertion' => [ ' 'TypeDoesNotContainType', ], 'detectFloatIntNeverCanBeEqualAfterAssertion' => [ ' 'TypeDoesNotContainType', ], 'assertNotSameDifferentTypes' => [ ' 'RedundantCondition', ], 'assertNotSameDifferentTypesExplicitString' => [ ' 'RedundantCondition', ], 'dontBleedTemplateTypeInArrayAgain' => [ ' $array * @psalm-assert array $array */ function isMap(array $array) : void {} /** * @param array $arr */ function bar(array $arr): void { isMap($arr); /** @psalm-trace $arr */ $arr; }', 'error_message' => 'string, string', ], ]; } }