,error_levels?:string[]}> */ public function providerValidCodeParse(): iterable { return [ 'implictAssertInstanceOfB' => [ 'foo(); }', ], 'implicitAssertEqualsNull' => [ ' [ ' [ ' [ ' [ 'bar(); $a->foo(); }', ], 'implicitAssertInstanceOfMultipleInterfaces' => [ 'bar(); $a->foo1(); }', ], 'implicitAssertInstanceOfBInClassMethod' => [ 'assertInstanceOfB($a); $a->foo(); } }', ], 'implicitAssertPropertyNotNull' => [ 'a) { throw new \Exception(); } } public function takesA(A $a): void { $this->assertNotNullProperty(); $a->foo(); } }', ], 'implicitAssertWithoutRedundantCondition' => [ ' [ 'foo(); }', ], 'assertIfTrueAnnotation' => [ ' [ ' [ ' [ 'foo()); assertFalse($a->bar()); }', ], 'assertAllStrings' => [ ' $i * * @param iterable $i */ function assertAllStrings(iterable $i): void { /** @psalm-suppress MixedAssignment */ foreach ($i as $s) { if (!is_string($s)) { throw new \UnexpectedValueException(""); } } } function getArray(): array { return []; } function getIterable(): iterable { return []; } $array = getArray(); assertAllStrings($array); $iterable = getIterable(); assertAllStrings($iterable);', [ '$array' => 'array', '$iterable' => 'iterable', ], ], 'assertStaticMethodIfFalse' => [ ' [ ' [ 'bar();', ], 'assertThisType' => [ 'isFoo(); $t->bar(); }' ], 'assertThisTypeIfTrue' => [ 'isFoo()) { $t->bar(); } }' ], 'assertThisTypeCombined' => [ 'assertFoo(); $t->assertBar(); $t->foo(); $t->bar(); }' ], 'assertThisTypeCombinedInsideMethod' => [ 'assertFoo(); $t->assertBar(); $t->foo(); $t->bar(); } } interface FooType { public function foo(): void; } interface BarType { public function bar(): void; } ' ], 'assertThisTypeSimpleCombined' => [ 'assertBar(); $t->foo(); $t->bar(); }' ], 'assertThisTypeIfTrueCombined' => [ 'assertFoo() && $t->assertBar()) { $t->foo(); $t->bar(); } }' ], 'assertThisTypeSimpleAndIfTrueCombined' => [ 'isFoo()) { $t->foo(); } $t->bar(); }' ], 'assertThisTypeSwitchTrue' => [ 'isFoo(): $t->bar(); } }' ], 'assertNotArray' => [ ' [ 'assertProperty()) { $this->a->foo(); } } /** * @psalm-assert-if-true !null $this->a */ public function assertProperty() : bool { return $this->a !== null; } }' ], 'assertIfFalseOnProperty' => [ 'assertProperty()) { $this->a->foo(); } } /** * @psalm-assert-if-false null $this->a */ public function assertProperty() : bool { return $this->a !== null; } }' ], 'assertIfTrueOnPropertyNegated' => [ 'assertProperty()) { $this->a->foo(); } } /** * @psalm-assert-if-true null $this->a */ public function assertProperty() : bool { return $this->a !== null; } }' ], 'assertIfFalseOnPropertyNegated' => [ 'assertProperty()) { $this->a->foo(); } } /** * @psalm-assert-if-false !null $this->a */ public function assertProperty() : bool { return $this->a !== null; } }' ], 'assertPropertyVisibleOutside' => [ 'x = 0; } } /** * @psalm-assert !null $this->x */ public function assertProperty() : void { if (is_null($this->x)) { throw new RuntimeException(); } } } $a = new A(); $a->maybeAssignX(); $a->assertProperty(); echo (2 * $a->x);', ], 'parseAssertion' => [ ' $data * @param mixed $data */ function isArrayOfStrings($data): void {} function foo(array $arr) : void { isArrayOfStrings($arr); foreach ($arr as $a) { foreach ($a as $b) { echo $b; } } }' ], 'noExceptionOnShortArrayAssertion' => [ ' [ ' $arr * @return array */ function foo(iterable $arr) : array { isArray($arr); return $arr; }' ], 'listAssertion' => [ ' $arr * @return list */ function foo(array $arr) : array { isList($arr); return $arr; }' ], 'scanAssertionTypes' => [ ' [ ' [ ' [ ' [ 'arr = $arr; } } /** @psalm-immutable */ class A { public B $b; public function __construct(B $b) { $this->b = $b; } /** @psalm-assert-if-true !null $this->b->arr */ public function hasArray() : bool { return $this->b->arr !== null; } } function foo(A $a) : void { if ($a->hasArray()) { echo count($a->b->arr); } }' ], 'assertOnNestedMethod' => [ 'arr = $arr; } public function getArray() : ?array { return $this->arr; } } /** @psalm-immutable */ class A { public B $b; public function __construct(B $b) { $this->b = $b; } /** @psalm-assert-if-true !null $this->b->getarray() */ public function hasArray() : bool { return $this->b->getArray() !== null; } } function foo(A $a) : void { if ($a->hasArray()) { echo count($a->b->getArray()); } }' ], 'assertOnThisMethod' => [ 'arr = $arr; } /** @psalm-assert-if-true !null $this->getarray() */ public function hasArray() : bool { return $this->arr !== null; } public function getArray() : ?array { return $this->arr; } } function foo(A $a) : void { if (!$a->hasArray()) { return; } echo count($a->getArray()); }' ], 'preventErrorWhenAssertingOnArrayUnion' => [ ' $data */ function validate(array $data): void {}' ], 'nonEmptyList' => [ ' */ function consume1($value): array { isNonEmptyList($value); return $value; } /** * @psalm-param list $values */ function consume2(array $values): void { isNonEmptyList($values); foreach ($values as $str) {} echo $str; }' ], 'nonEmptyListOfStrings' => [ ' $array * * @param mixed $array */ function isNonEmptyListOfStrings($array): void {} /** * @psalm-param list $values */ function consume2(array $values): void { isNonEmptyListOfStrings($values); foreach ($values as $str) {} echo $str; }' ], 'assertResource' => [ ' [ ', * env?: array, * buildDeps?: list, * configure?: string * }> * }> * } $data * * @param mixed $data */ function assertStructure($data): void {}' ], 'intersectArraysAfterAssertion' => [ ' [ ' $value * * @param mixed $value * * @throws InvalidArgumentException */ function allString($value): void {} function takesAnArray(array $a): void { $keys = array_keys($a); allString($keys); }', ], 'assertListIsListOfStrings' => [ ' $value * * @param mixed $value * * @throws InvalidArgumentException */ function allString($value): void {} function takesAnArray(array $a): void { $keys = array_keys($a); allString($keys); }', ], 'multipleAssertIfTrue' => [ ' [ ' [ ' [ '' ], 'assertIfTrueStaticSelf' => [ '' ], 'assertIfFalseStaticSelf' => [ '' ], 'assertStaticByInheritedMethod' => [ '' ], 'assertInheritedStatic' => [ '' ], 'assertStaticOnUnrelatedClass' => [ '' ], 'implicitComplexAssertionNoCrash' => [ 'status && "complete" === $status) || ("canceled" === $this->status && "pending" === $status) || ("complete" === $this->status && "canceled" === $status) || ("complete" === $this->status && "pending" === $status) ) { throw new \LogicException(); } } }' ], 'assertArrayIteratorIsIterableOfStrings' => [ ' $value * @param mixed $value * * @return void */ function assertAllString($value) : void { throw new \Exception(\var_export($value, true)); } /** * @param ArrayIterator $value * * @return ArrayIterator */ function preserveContainerAllArrayIterator($value) { assertAllString($value); return $value; }' ], 'implicitReflectionParameterAssertion' => [ 'getParameters(); foreach ($parameters as $parameter) { if ($parameter->hasType()) { $parameter->getType()->__toString(); } }', ], 'reflectionNameTypeClassStringIfNotBuiltin' => [ 'getType(); return ($type instanceof \ReflectionNamedType) && !$type->isBuiltin() ? $type->getName() : null; }', [], [], '7.4', ], 'withHasTypeCall' => [ 'getType() */ public function hasType() : bool { return true; } public function getType() : ?ReflectionType { return null; } } function takesParam(Param $p) : void { if ($p->hasType()) { echo $p->getType()->__toString(); } }', ], 'assertTemplatedIterable' => [ ' $foos * @return array */ function foo(array $foos) : array { allIsInstanceOf($foos, Foo::class); return $foos; } /** * @template ExpectedType of object * * @param mixed $value * @param class-string $class * @psalm-assert iterable $value */ function allIsInstanceOf($value, $class): void {}' ], 'implicitReflectionPropertyAssertion' => [ 'getProperties(); foreach ($properties as $property) { if ($property->hasType()) { $property->getType()->allowsNull(); } }', [], [], '7.4' ], 'onPropertyOfImmutableArgument' => [ 'b = $b; } } /** @psalm-assert !null $item->b */ function c(\Aclass $item): void { if (null === $item->b) { throw new \InvalidArgumentException(""); } } /** @var \Aclass $a */ c($a); echo strlen($a->b);', ], 'inTrueOnPropertyOfImmutableArgument' => [ 'b = $b; } } /** @psalm-assert-if-true !null $item->b */ function c(A $item): bool { return null !== $item->b; } function check(int $a): void {} /** @var A $a */ if (c($a)) { check($a->b); }', ], 'inFalseOnPropertyOfAImmutableArgument' => [ 'b = $b; } } /** @psalm-assert-if-false !null $item->b */ function c(A $item): bool { return null === $item->b; } function check(int $a): void {} /** @var A $a */ if (!c($a)) { check($a->b); }', ], 'ifTrueOnNestedPropertyOfArgument' => [ 'c = $c; } } /** @psalm-immutable */ class Aclass { public B $b; public function __construct(B $b) { $this->b = $b; } } /** @psalm-assert-if-true !null $item->b->c */ function c(\Aclass $item): bool { return null !== $item->b->c; } $a = new \Aclass(new \B(null)); if (c($a)) { echo strlen($a->b->c); }', ], 'ifFalseOnNestedPropertyOfArgument' => [ 'c = $c; } } /** @psalm-immutable */ class Aclass { public B $b; public function __construct(B $b) { $this->b = $b; } } /** @psalm-assert-if-false !null $item->b->c */ function c(\Aclass $item): bool { return null !== $item->b->c; } $a = new \Aclass(new \B(null)); if (!c($a)) { echo strlen($a->b->c); }', ], 'assertOnKeyedArrayWithClassStringOffset' => [ ' ""]; /** @var array $b */ $b = []; $this->assertSame($a, $b); } /** * @template T * @param T $expected * @param mixed $actual * @psalm-assert =T $actual */ public function assertSame($expected, $actual): void { return; } }', ], ]; } /** * @return iterable */ public function providerInvalidCodeParse(): iterable { return [ 'assertInstanceOfMultipleInterfaces' => [ 'bar(); $a->foo1(); }', 'error_message' => 'UndefinedMethod', ], 'assertIfTrueNoAnnotation' => [ ' 'PossiblyNullOperand', ], 'assertIfFalseNoAnnotation' => [ ' 'PossiblyNullOperand', ], 'assertIfTrueMethodCall' => [ 'isInt($p)) { strlen($p); } } }', 'error_message' => 'InvalidScalarArgument', ], 'assertIfStaticTrueMethodCall' => [ 'isInt($p)) { strlen($p); } } }', 'error_message' => 'InvalidScalarArgument', ], 'noFatalForUnknownAssertClass' => [ 'sayHello();', 'error_message' => 'UndefinedDocblockClass', ], 'assertValueImpossible' => [ ' 'TypeDoesNotContainType', ], 'sortOfReplacementForAssert' => [ ' 'TypeDoesNotContainType', ], 'assertScalarAndEmpty' => [ ' 'RedundantConditionGivenDocblockType - src' . DIRECTORY_SEPARATOR . 'somefile.php:19:29', ], 'assertOneOfStrings' => [ ' 'DocblockTypeContradiction', ], 'assertThisType' => [ 'bar(); $t->isFoo(); }', 'error_message' => 'UndefinedMethod', ], 'invalidUnionAssertion' => [ ' 'InvalidDocblock', ], 'assertNotEmptyOnBool' => [ ' 'RedundantConditionGivenDocblockType', ], 'withoutHasTypeCall' => [ 'getParameters(); foreach ($parameters as $parameter) { $parameter->getType()->__toString(); }', 'error_message' => 'PossiblyNullReference', ], 'onPropertyOfMutableArgument' => [ 'b = $b; } } /** @psalm-assert !null $item->b */ function c(\Aclass $item): void { if (null === $item->b) { throw new \InvalidArgumentException(""); } } /** @var \Aclass $a */ c($a); echo strlen($a->b);', 'error_message' => 'InvalidDocblock', ], 'ifTrueOnPropertyOfMutableArgument' => [ 'b = $b; } } /** @psalm-assert-if-true !null $item->b */ function c(\Aclass $item): bool { return null !== $item->b; } /** @var \Aclass $a */ if (c($a)) { echo strlen($a->b); }', 'error_message' => 'InvalidDocblock', ], 'ifFalseOnPropertyOfMutableArgument' => [ 'b = $b; } } /** @psalm-assert-if-false !null $item->b */ function c(\Aclass $item): bool { return null === $item->b; } /** @var \Aclass $a */ if (!c($a)) { echo strlen($a->b); }', 'error_message' => 'InvalidDocblock', ], ]; } }