Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 46 additions & 69 deletions src/Type/WebMozartAssert/AssertTypeSpecifyingExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@
);
}

[$expr, $rootExpr] = self::createExpression($scope, $staticMethodReflection->getName(), $node->getArgs());
[$expr, $specifyOnly] = self::createExpression($scope, $staticMethodReflection->getName(), $node->getArgs());
if ($expr === null) {
return new SpecifiedTypes([], []);
}
Expand All @@ -165,14 +165,17 @@
$scope,
$expr,
TypeSpecifierContext::createTruthy(),
)->setRootExpr($rootExpr ?? $expr);
);
if ($specifyOnly) {
$specifiedTypes = $specifiedTypes->setSpecifyOnly();

Check failure on line 170 in src/Type/WebMozartAssert/AssertTypeSpecifyingExtension.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.2, lowest)

Call to an undefined method PHPStan\Analyser\SpecifiedTypes::setSpecifyOnly().

Check failure on line 170 in src/Type/WebMozartAssert/AssertTypeSpecifyingExtension.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.5, lowest)

Call to an undefined method PHPStan\Analyser\SpecifiedTypes::setSpecifyOnly().

Check failure on line 170 in src/Type/WebMozartAssert/AssertTypeSpecifyingExtension.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.5, highest)

Call to an undefined method PHPStan\Analyser\SpecifiedTypes::setSpecifyOnly().

Check failure on line 170 in src/Type/WebMozartAssert/AssertTypeSpecifyingExtension.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.3, lowest)

Call to an undefined method PHPStan\Analyser\SpecifiedTypes::setSpecifyOnly().

Check failure on line 170 in src/Type/WebMozartAssert/AssertTypeSpecifyingExtension.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.0, highest)

Call to an undefined method PHPStan\Analyser\SpecifiedTypes::setSpecifyOnly().

Check failure on line 170 in src/Type/WebMozartAssert/AssertTypeSpecifyingExtension.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.3, highest)

Call to an undefined method PHPStan\Analyser\SpecifiedTypes::setSpecifyOnly().

Check failure on line 170 in src/Type/WebMozartAssert/AssertTypeSpecifyingExtension.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.1, highest)

Call to an undefined method PHPStan\Analyser\SpecifiedTypes::setSpecifyOnly().

Check failure on line 170 in src/Type/WebMozartAssert/AssertTypeSpecifyingExtension.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.0, lowest)

Call to an undefined method PHPStan\Analyser\SpecifiedTypes::setSpecifyOnly().

Check failure on line 170 in src/Type/WebMozartAssert/AssertTypeSpecifyingExtension.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.4, lowest)

Call to an undefined method PHPStan\Analyser\SpecifiedTypes::setSpecifyOnly().

Check failure on line 170 in src/Type/WebMozartAssert/AssertTypeSpecifyingExtension.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.1, lowest)

Call to an undefined method PHPStan\Analyser\SpecifiedTypes::setSpecifyOnly().

Check failure on line 170 in src/Type/WebMozartAssert/AssertTypeSpecifyingExtension.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.4, highest)

Call to an undefined method PHPStan\Analyser\SpecifiedTypes::setSpecifyOnly().

Check failure on line 170 in src/Type/WebMozartAssert/AssertTypeSpecifyingExtension.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.2, highest)

Call to an undefined method PHPStan\Analyser\SpecifiedTypes::setSpecifyOnly().
}

return $this->specifyRootExprIfSet($rootExpr, $scope, $specifiedTypes);
return $specifiedTypes;
}

/**
* @param Arg[] $args
* @return array{?Expr, ?Expr}
* @return array{?Expr, bool}
*/
private function createExpression(
Scope $scope,
Expand All @@ -186,14 +189,14 @@

$resolverResult = $resolver($scope, ...$args);
if (is_array($resolverResult)) {
[$expr, $rootExpr] = $resolverResult;
[$expr, $specifyOnly] = $resolverResult;
} else {
$expr = $resolverResult;
$rootExpr = null;
$specifyOnly = false;
}

if ($expr === null) {
return [null, null];
return [null, false];
}

if (substr($name, 0, 6) === 'nullOr') {
Expand All @@ -206,11 +209,11 @@
);
}

return [$expr, $rootExpr];
return [$expr, $specifyOnly];
}

/**
* @return array<string, callable(Scope, Arg...): (Expr|array{?Expr, ?Expr}|null)>
* @return array<string, callable(Scope, Arg...): (Expr|array{?Expr, bool}|null)>
*/
private function getExpressionResolvers(): array
{
Expand Down Expand Up @@ -634,22 +637,23 @@
];

foreach (['contains', 'startsWith', 'endsWith'] as $name) {
$this->resolvers[$name] = static function (Scope $scope, Arg $value, Arg $subString) use ($name): array {
if ($scope->getType($subString->value)->isNonEmptyString()->yes()) {
return self::createIsNonEmptyStringAndSomethingExprPair($name, [$value, $subString]);
}

$this->resolvers[$name] = static function (Scope $scope, Arg $value, Arg $subString): array {
$expr = new FuncCall(
new Name('is_string'),
[$value],
);

$rootExpr = new BooleanAnd(
$expr,
new FuncCall(new Name('FAUX_FUNCTION_ ' . $name), [$value, $subString]),
);
if ($scope->getType($subString->value)->isNonEmptyString()->yes()) {
$expr = new BooleanAnd(
$expr,
new NotIdentical(
$value->value,
new String_(''),
),
);
}

return [$expr, $rootExpr];
return [$expr, true];
};
}

Expand All @@ -669,7 +673,19 @@
'notWhitespaceOnly',
];
foreach ($assertionsResultingAtLeastInNonEmptyString as $name) {
$this->resolvers[$name] = static fn (Scope $scope, Arg $value): array => self::createIsNonEmptyStringAndSomethingExprPair($name, [$value]);
$this->resolvers[$name] = static fn (Scope $scope, Arg $value): array => [
new BooleanAnd(
new FuncCall(
new Name('is_string'),
[$value],
),
new NotIdentical(
$value->value,
new String_(''),
),
),
true,
];
}
}

Expand All @@ -687,7 +703,6 @@
$scope,
$node->getArgs()[0]->value,
static fn (Type $type): Type => TypeCombinator::removeNull($type),
null,
);
}

Expand All @@ -703,7 +718,6 @@
$scope,
$node->getArgs()[0]->value,
static fn (Type $type): Type => TypeCombinator::remove($type, $classNameType),
null,
);
}

Expand All @@ -713,7 +727,6 @@
$scope,
$node->getArgs()[0]->value,
static fn (Type $type): Type => TypeCombinator::remove($type, $valueType),
null,
);
}

Expand All @@ -732,7 +745,7 @@
{
$args = $node->getArgs();
$args[0] = new Arg(new ArrayDimFetch($args[0]->value, new LNumber(0)));
[$expr, $rootExpr] = self::createExpression($scope, $methodName, $args);
[$expr, $specifyOnly] = self::createExpression($scope, $methodName, $args);
if ($expr === null) {
return new SpecifiedTypes();
}
Expand All @@ -741,7 +754,7 @@
$scope,
$expr,
TypeSpecifierContext::createTruthy(),
)->setRootExpr($rootExpr ?? $expr);
);

$sureNotTypes = $specifiedTypes->getSureNotTypes();
foreach ($specifiedTypes->getSureTypes() as $exprStr => [$exprNode, $type]) {
Expand All @@ -754,12 +767,16 @@
$type = $typeModifier($type);
}

return $this->allArrayOrIterable(
$specifiedTypes = $this->allArrayOrIterable(
$scope,
$node->getArgs()[0]->value,
static fn (): Type => $type,
$rootExpr,
);
break;
}

if ($specifyOnly) {
$specifiedTypes = $specifiedTypes->setSpecifyOnly();

Check failure on line 779 in src/Type/WebMozartAssert/AssertTypeSpecifyingExtension.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.2, lowest)

Call to an undefined method PHPStan\Analyser\SpecifiedTypes::setSpecifyOnly().

Check failure on line 779 in src/Type/WebMozartAssert/AssertTypeSpecifyingExtension.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.5, lowest)

Call to an undefined method PHPStan\Analyser\SpecifiedTypes::setSpecifyOnly().

Check failure on line 779 in src/Type/WebMozartAssert/AssertTypeSpecifyingExtension.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.5, highest)

Call to an undefined method PHPStan\Analyser\SpecifiedTypes::setSpecifyOnly().

Check failure on line 779 in src/Type/WebMozartAssert/AssertTypeSpecifyingExtension.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.3, lowest)

Call to an undefined method PHPStan\Analyser\SpecifiedTypes::setSpecifyOnly().

Check failure on line 779 in src/Type/WebMozartAssert/AssertTypeSpecifyingExtension.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.0, highest)

Call to an undefined method PHPStan\Analyser\SpecifiedTypes::setSpecifyOnly().

Check failure on line 779 in src/Type/WebMozartAssert/AssertTypeSpecifyingExtension.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.3, highest)

Call to an undefined method PHPStan\Analyser\SpecifiedTypes::setSpecifyOnly().

Check failure on line 779 in src/Type/WebMozartAssert/AssertTypeSpecifyingExtension.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.1, highest)

Call to an undefined method PHPStan\Analyser\SpecifiedTypes::setSpecifyOnly().

Check failure on line 779 in src/Type/WebMozartAssert/AssertTypeSpecifyingExtension.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.0, lowest)

Call to an undefined method PHPStan\Analyser\SpecifiedTypes::setSpecifyOnly().

Check failure on line 779 in src/Type/WebMozartAssert/AssertTypeSpecifyingExtension.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.4, lowest)

Call to an undefined method PHPStan\Analyser\SpecifiedTypes::setSpecifyOnly().

Check failure on line 779 in src/Type/WebMozartAssert/AssertTypeSpecifyingExtension.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.1, lowest)

Call to an undefined method PHPStan\Analyser\SpecifiedTypes::setSpecifyOnly().

Check failure on line 779 in src/Type/WebMozartAssert/AssertTypeSpecifyingExtension.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.4, highest)

Call to an undefined method PHPStan\Analyser\SpecifiedTypes::setSpecifyOnly().

Check failure on line 779 in src/Type/WebMozartAssert/AssertTypeSpecifyingExtension.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.2, highest)

Call to an undefined method PHPStan\Analyser\SpecifiedTypes::setSpecifyOnly().
}

return $specifiedTypes;
Expand All @@ -769,7 +786,6 @@
Scope $scope,
Expr $expr,
Closure $typeCallback,
?Expr $rootExpr
): SpecifiedTypes
{
$currentType = TypeCombinator::intersect($scope->getType($expr), new IterableType(new MixedType(), new MixedType()));
Expand Down Expand Up @@ -809,14 +825,12 @@
return new SpecifiedTypes([], []);
}

$specifiedTypes = $this->typeSpecifier->create(
return $this->typeSpecifier->create(
$expr,
$specifiedType,
TypeSpecifierContext::createTruthy(),
$scope,
)->setRootExpr($rootExpr);

return $this->specifyRootExprIfSet($rootExpr, $scope, $specifiedTypes);
);
}

/**
Expand Down Expand Up @@ -856,41 +870,4 @@
return self::implodeExpr($resolvers, BooleanOr::class);
}

/**
* @param Arg[] $args
* @return array{Expr, Expr}
*/
private static function createIsNonEmptyStringAndSomethingExprPair(string $name, array $args): array
{
$expr = new BooleanAnd(
new FuncCall(
new Name('is_string'),
[$args[0]],
),
new NotIdentical(
$args[0]->value,
new String_(''),
),
);

$rootExpr = new BooleanAnd(
$expr,
new FuncCall(new Name('FAUX_FUNCTION_ ' . $name), $args),
);

return [$expr, $rootExpr];
}

private function specifyRootExprIfSet(?Expr $rootExpr, Scope $scope, SpecifiedTypes $specifiedTypes): SpecifiedTypes
{
if ($rootExpr === null) {
return $specifiedTypes;
}

// Makes consecutive calls with a rootExpr adding unknown info via FAUX_FUNCTION evaluate to true
return $specifiedTypes->unionWith(
$this->typeSpecifier->create($rootExpr, new ConstantBooleanType(true), TypeSpecifierContext::createTruthy(), $scope),
);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ protected function getRule(): Rule

public function testExtension(): void
{
$tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting <fg=cyan>treatPhpDocTypesAsCertain: false</> in your <fg=cyan>%configurationFile%</>.';

$this->analyse([__DIR__ . '/data/impossible-check.php'], [
[
'Call to static method Webmozart\Assert\Assert::stringNotEmpty() with \'\' will always evaluate to false.',
Expand Down Expand Up @@ -110,7 +108,6 @@ public function testExtension(): void
[
'Call to static method Webmozart\Assert\Assert::isInstanceOf() with Exception and class-string<Exception> will always evaluate to true.',
119,
$tipText,
],
[
'Call to static method Webmozart\Assert\Assert::startsWith() with \'value\' and string will always evaluate to true.',
Expand All @@ -119,7 +116,6 @@ public function testExtension(): void
[
'Call to static method Webmozart\Assert\Assert::implementsInterface() with \'WebmozartAssertImpossibleCheck\\\\Bar\' and \'WebmozartAssertImpossibleCheck\\\\Bar\' will always evaluate to false.',
134,
$tipText,
],
]);
}
Expand Down Expand Up @@ -196,34 +192,28 @@ public function testEqNotEq(): void

public function testBug8(): void
{
$tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting <fg=cyan>treatPhpDocTypesAsCertain: false</> in your <fg=cyan>%configurationFile%</>.';
$this->analyse([__DIR__ . '/data/bug-8.php'], [
[
'Call to static method Webmozart\Assert\Assert::numeric() with numeric-string will always evaluate to true.',
15,
$tipText,
],
[
'Call to static method Webmozart\Assert\Assert::numeric() with \'foo\' will always evaluate to false.',
16,
$tipText,
],
[
'Call to static method Webmozart\Assert\Assert::numeric() with \'17.19\' will always evaluate to true.',
17,
$tipText,
],
]);
}

public function testBug17(): void
{
$tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting <fg=cyan>treatPhpDocTypesAsCertain: false</> in your <fg=cyan>%configurationFile%</>.';
$this->analyse([__DIR__ . '/data/bug-17.php'], [
[
'Call to static method Webmozart\Assert\Assert::implementsInterface() with \'DateTime\' and \'DateTimeInterface\' will always evaluate to true.',
9,
$tipText,
],
]);
}
Expand Down
Loading