Skip to content

Commit

Permalink
Fix nullOr* non-empty-string assertions
Browse files Browse the repository at this point in the history
  • Loading branch information
herndlm committed Jan 10, 2022
1 parent 19b869e commit ab9a7db
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 71 deletions.
71 changes: 36 additions & 35 deletions src/Type/WebMozartAssert/AssertTypeSpecifyingExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,6 @@ public function isStaticMethodSupported(
TypeSpecifierContext $context
): bool
{
if (in_array($staticMethodReflection->getName(), self::ASSERTIONS_RESULTING_IN_NON_EMPTY_STRING, true)) {
return true;
}

if (substr($staticMethodReflection->getName(), 0, 6) === 'allNot') {
$methods = [
'allNotInstanceOf' => 2,
Expand All @@ -84,6 +80,10 @@ public function isStaticMethodSupported(
$trimmedName = self::trimName($staticMethodReflection->getName());
$resolvers = self::getExpressionResolvers();

if (in_array($trimmedName, self::ASSERTIONS_RESULTING_IN_NON_EMPTY_STRING, true)) {
return true;
}

if (!array_key_exists($trimmedName, $resolvers)) {
return false;
}
Expand Down Expand Up @@ -130,28 +130,43 @@ public function specifyTypes(
TypeSpecifierContext::createTruthy()
);

if (substr($staticMethodReflection->getName(), 0, 6) === 'nullOr') {
return $this->typeSpecifier->create(
$node->getArgs()[0]->value,
TypeCombinator::addNull($this->getResultingTypeFromSpecifiedTypes($specifiedTypes)),
TypeSpecifierContext::createTruthy()
);
}

if (substr($staticMethodReflection->getName(), 0, 3) === 'all') {
if (count($specifiedTypes->getSureTypes()) > 0) {
$sureTypes = $specifiedTypes->getSureTypes();
reset($sureTypes);
$exprString = key($sureTypes);
$sureType = $sureTypes[$exprString];
return $this->arrayOrIterable(
$scope,
$sureType[0],
function () use ($sureType): Type {
return $sureType[1];
}
);
}
if (count($specifiedTypes->getSureNotTypes()) > 0) {
throw new \PHPStan\ShouldNotHappenException();
}
return $this->arrayOrIterable(
$scope,
$node->getArgs()[0]->value,
function () use ($specifiedTypes): Type {
return $this->getResultingTypeFromSpecifiedTypes($specifiedTypes);
}
);
}

return $specifiedTypes;
}

private function getResultingTypeFromSpecifiedTypes(SpecifiedTypes $specifiedTypes): Type
{
if (count($specifiedTypes->getSureTypes()) > 0) {
$sureTypes = $specifiedTypes->getSureTypes();
reset($sureTypes);
$exprString = key($sureTypes);
$sureType = $sureTypes[$exprString];

$sureNotType = $specifiedTypes->getSureNotTypes()[$exprString] ?? null;

return $sureNotType !== null ? TypeCombinator::remove($sureType[1], $sureNotType[1]) : $sureType[1];
}

throw new \PHPStan\ShouldNotHappenException();
}

/**
* @param Scope $scope
* @param string $name
Expand All @@ -172,22 +187,8 @@ private static function createExpression(

$resolvers = self::getExpressionResolvers();
$resolver = $resolvers[$trimmedName];
$expression = $resolver($scope, ...$args);
if ($expression === null) {
return null;
}

if (substr($name, 0, 6) === 'nullOr') {
$expression = new \PhpParser\Node\Expr\BinaryOp\BooleanOr(
$expression,
new \PhpParser\Node\Expr\BinaryOp\Identical(
$args[0]->value,
new \PhpParser\Node\Expr\ConstFetch(new \PhpParser\Node\Name('null'))
)
);
}

return $expression;
return $resolver($scope, ...$args);
}

/**
Expand Down
10 changes: 8 additions & 2 deletions tests/Type/WebMozartAssert/data/array.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,16 @@ public function keyExists(array $a): void
\PHPStan\Testing\assertType('array{foo: string, bar?: string}', $a);
}

public function validArrayKey($a, bool $b): void
public function validArrayKey($a, bool $b, $c): void
{
Assert::validArrayKey($a);
\PHPStan\Testing\assertType('int|string', $a);

Assert::validArrayKey($b);
\PHPStan\Testing\assertType('*NEVER*', $b);

Assert::nullOrValidArrayKey($c);
\PHPStan\Testing\assertType('int|string|null', $c);
}

/**
Expand Down Expand Up @@ -94,10 +97,13 @@ public function countBetween(array $a, array $b, array $c, array $d): void
\PHPStan\Testing\assertType('*NEVER*', $d);
}

public function isList($a): void
public function isList($a, $b): void
{
Assert::isList($a);
\PHPStan\Testing\assertType('array', $a);

Assert::nullOrIsList($b);
\PHPStan\Testing\assertType('array|null', $b);
}

}
12 changes: 12 additions & 0 deletions tests/Type/WebMozartAssert/data/collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,18 @@ public function allString(array $a): void
\PHPStan\Testing\assertType('array<string>', $a);
}

public function allStringNotEmpty(array $a, iterable $b, $c): void
{
Assert::allStringNotEmpty($a);
\PHPStan\Testing\assertType('array<non-empty-string>', $a);

Assert::allStringNotEmpty($b);
\PHPStan\Testing\assertType('iterable<non-empty-string>', $b);

Assert::allStringNotEmpty($c);
\PHPStan\Testing\assertType('iterable<non-empty-string>', $c);
}

public function allInteger(array $a, iterable $b, iterable $c): void
{
Assert::allInteger($a);
Expand Down
22 changes: 17 additions & 5 deletions tests/Type/WebMozartAssert/data/comparison.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,25 @@

class ComparisonTest
{
public function true($a): void
public function true($a, $b): void
{
Assert::true($a);
\PHPStan\Testing\assertType('true', $a);

Assert::nullOrTrue($b);
\PHPStan\Testing\assertType('true|null', $b);
}

public function false($a): void
public function false($a, $b): void
{
Assert::false($a);
\PHPStan\Testing\assertType('false', $a);

Assert::nullOrFalse($b);
\PHPStan\Testing\assertType('false|null', $b);
}

public function notFalse(int $a): void
public function notFalse($a, $b): void
{
/** @var int|false $a */
Assert::notFalse($a);
Expand All @@ -37,10 +43,13 @@ public function notNull(?int $a): void
\PHPStan\Testing\assertType('int', $a);
}

public function same($a): void
public function same($a, $b): void
{
Assert::same($a, 1);
\PHPStan\Testing\assertType('1', $a);

Assert::nullOrSame($b, 1);
\PHPStan\Testing\assertType('1|null', $b);
}

/**
Expand All @@ -61,9 +70,12 @@ public function inArray($a, $b): void
\PHPStan\Testing\assertType('\'bar\'|\'foo\'|null', $b);
}

public function oneOf($a): void
public function oneOf($a, $b): void
{
Assert::oneOf($a, [1, 2]);
\PHPStan\Testing\assertType('1|2', $a);

Assert::nullOrOneOf($b, [1, 2]);
\PHPStan\Testing\assertType('1|2|null', $b);
}
}
20 changes: 16 additions & 4 deletions tests/Type/WebMozartAssert/data/object.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,40 @@
class ObjectTest
{

public function classExists($a): void
public function classExists($a, $b): void
{
Assert::classExists($a);
\PHPStan\Testing\assertType('class-string', $a);

Assert::nullOrClassExists($b);
\PHPStan\Testing\assertType('class-string|null', $b);
}

public function subclassOf($a): void
public function subclassOf($a, $b): void
{
Assert::subclassOf($a, self::class);
\PHPStan\Testing\assertType('class-string<PHPStan\Type\WebMozartAssert\ObjectTest>|PHPStan\Type\WebMozartAssert\ObjectTest', $a);

Assert::nullOrSubclassOf($b, self::class);
\PHPStan\Testing\assertType('class-string<PHPStan\Type\WebMozartAssert\ObjectTest>|PHPStan\Type\WebMozartAssert\ObjectTest|null', $b);
}

public function interfaceExists($a): void
public function interfaceExists($a, $b): void
{
Assert::interfaceExists($a);
\PHPStan\Testing\assertType('class-string', $a);

Assert::nullOrInterfaceExists($b);
\PHPStan\Testing\assertType('class-string|null', $b);
}

public function implementsInterface($a): void
public function implementsInterface($a, $b): void
{
Assert::implementsInterface($a, ObjectFoo::class);
\PHPStan\Testing\assertType('PHPStan\Type\WebMozartAssert\ObjectFoo', $a);

Assert::nullOrImplementsInterface($b, ObjectFoo::class);
\PHPStan\Testing\assertType('PHPStan\Type\WebMozartAssert\ObjectFoo|null', $b);
}

public function propertyExists(object $a): void
Expand Down
30 changes: 24 additions & 6 deletions tests/Type/WebMozartAssert/data/string.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,16 +91,22 @@ public function lengthBetween(string $a, string $b, string $c, string $d): void
\PHPStan\Testing\assertType('non-empty-string', $d);
}

public function unicodeLetters($a): void
public function unicodeLetters($a, $b): void
{
Assert::unicodeLetters($a);
\PHPStan\Testing\assertType('non-empty-string', $a);

Assert::nullOrUnicodeLetters($b);
\PHPStan\Testing\assertType('non-empty-string|null', $b);
}

public function alpha($a): void
public function alpha($a, $b): void
{
Assert::alpha($a);
\PHPStan\Testing\assertType('non-empty-string', $a);

Assert::nullOrAlpha($b);
\PHPStan\Testing\assertType('non-empty-string|null', $b);
}

public function digits(string $a): void
Expand Down Expand Up @@ -133,28 +139,40 @@ public function uuid(string $a): void
\PHPStan\Testing\assertType('non-empty-string', $a);
}

public function ip($a): void
public function ip($a, $b): void
{
Assert::ip($a);
\PHPStan\Testing\assertType('non-empty-string', $a);

Assert::nullOrIp($b);
\PHPStan\Testing\assertType('non-empty-string|null', $b);
}

public function ipv4($a): void
public function ipv4($a, $b): void
{
Assert::ipv4($a);
\PHPStan\Testing\assertType('non-empty-string', $a);

Assert::nullOrIpv4($b);
\PHPStan\Testing\assertType('non-empty-string|null', $b);
}

public function ipv6($a): void
public function ipv6($a, $b): void
{
Assert::ipv6($a);
\PHPStan\Testing\assertType('non-empty-string', $a);

Assert::nullOrIpv6($b);
\PHPStan\Testing\assertType('non-empty-string|null', $b);
}

public function email($a): void
public function email($a, $b): void
{
Assert::email($a);
\PHPStan\Testing\assertType('non-empty-string', $a);

Assert::nullOrEmail($b);
\PHPStan\Testing\assertType('non-empty-string|null', $b);
}

public function notWhitespaceOnly(string $a): void
Expand Down

0 comments on commit ab9a7db

Please sign in to comment.