Skip to content

Commit

Permalink
More precise return type for trigger_error
Browse files Browse the repository at this point in the history
  • Loading branch information
paulbalandan authored and ondrejmirtes committed Jul 10, 2023
1 parent c761188 commit 45276eb
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 11 deletions.
5 changes: 0 additions & 5 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -1497,11 +1497,6 @@ parameters:
count: 1
path: src/Type/Php/StrlenFunctionReturnTypeExtension.php

-
message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#"
count: 1
path: src/Type/Php/TriggerErrorDynamicReturnTypeExtension.php

-
message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#"
count: 2
Expand Down
39 changes: 34 additions & 5 deletions src/Type/Php/TriggerErrorDynamicReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,63 @@

use PhpParser\Node\Expr\FuncCall;
use PHPStan\Analyser\Scope;
use PHPStan\Php\PhpVersion;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Type\ConstantScalarType;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
use PHPStan\Type\NeverType;
use PHPStan\Type\Type;
use function count;
use function in_array;
use const E_USER_DEPRECATED;
use const E_USER_ERROR;
use const E_USER_NOTICE;
use const E_USER_WARNING;

class TriggerErrorDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension
{

public function __construct(private PhpVersion $phpVersion)
{
}

public function isFunctionSupported(FunctionReflection $functionReflection): bool
{
return $functionReflection->getName() === 'trigger_error';
}

public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type
{
if (count($functionCall->getArgs()) < 2) {
$args = $functionCall->getArgs();

if (count($args) === 0) {
return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType();
}

$errorType = $scope->getType($functionCall->getArgs()[1]->value);
if ($errorType instanceof ConstantScalarType) {
if ($errorType->getValue() === E_USER_ERROR) {
if (count($args) === 1) {
return new ConstantBooleanType(true);
}

$errorType = $scope->getType($args[1]->value);

if ($errorType instanceof ConstantIntegerType) {
$errorLevel = $errorType->getValue();

if ($errorLevel === E_USER_ERROR) {
return new NeverType(true);
}

if (!in_array($errorLevel, [E_USER_WARNING, E_USER_NOTICE, E_USER_DEPRECATED], true)) {
if ($this->phpVersion->throwsValueErrorForInternalFunctions()) {
return new NeverType(true);
}

return new ConstantBooleanType(false);
}

return new ConstantBooleanType(true);
}

return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType();
Expand Down
6 changes: 6 additions & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1309,6 +1309,12 @@ public function dataFileAsserts(): iterable
yield from $this->gatherAssertTypes(__DIR__ . '/data/gettype.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/array_splice.php');
yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-9542.php');

if (PHP_VERSION_ID >= 80000) {
yield from $this->gatherAssertTypes(__DIR__ . '/data/trigger-error-php8.php');
} else {
yield from $this->gatherAssertTypes(__DIR__ . '/data/trigger-error-php7.php');
}
}

/**
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/data/bug-5992.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ function StringValue(): string
$values = [1, 'one', true, null, []];
$value = $values[rand(0, 4)];
if (!is_string($value)) {
assertType('bool', trigger_error("just a soft warning", E_USER_WARNING));
assertType('true', trigger_error("just a soft warning", E_USER_WARNING));
assertType('*NEVER*', trigger_error("error which halts the script", E_USER_ERROR));
}
return $value;
Expand Down
15 changes: 15 additions & 0 deletions tests/PHPStan/Analyser/data/trigger-error-php7.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php declare(strict_types = 1);

namespace TriggerErrorPhp7;

use function PHPStan\Testing\assertType;

$errorLevels = [E_USER_DEPRECATED, E_USER_ERROR, E_USER_NOTICE, E_USER_WARNING, E_NOTICE, E_WARNING];

assertType('true', trigger_error('bar'));
assertType('true', trigger_error('bar', $errorLevels[0]));
assertType('*NEVER*', trigger_error('bar', $errorLevels[1]));
assertType('true', trigger_error('bar', $errorLevels[2]));
assertType('true', trigger_error('bar', $errorLevels[3]));
assertType('false', trigger_error('bar', $errorLevels[4]));
assertType('false', trigger_error('bar', $errorLevels[5]));
15 changes: 15 additions & 0 deletions tests/PHPStan/Analyser/data/trigger-error-php8.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php declare(strict_types = 1);

namespace TriggerErrorPhp8;

use function PHPStan\Testing\assertType;

$errorLevels = [E_USER_DEPRECATED, E_USER_ERROR, E_USER_NOTICE, E_USER_WARNING, E_NOTICE, E_WARNING];

assertType('true', trigger_error('bar'));
assertType('true', trigger_error('bar', $errorLevels[0]));
assertType('*NEVER*', trigger_error('bar', $errorLevels[1]));
assertType('true', trigger_error('bar', $errorLevels[2]));
assertType('true', trigger_error('bar', $errorLevels[3]));
assertType('*NEVER*', trigger_error('bar', $errorLevels[4]));
assertType('*NEVER*', trigger_error('bar', $errorLevels[5]));

0 comments on commit 45276eb

Please sign in to comment.