Skip to content

Commit

Permalink
Setting for rememberPossiblyImpureFunctionValues (default true)
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Jun 22, 2022
1 parent 79e18f3 commit 50ed38f
Show file tree
Hide file tree
Showing 8 changed files with 280 additions and 3 deletions.
2 changes: 2 additions & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ parameters:
propertyAlwaysReadTags: []
additionalConstructors: []
treatPhpDocTypesAsCertain: true
rememberPossiblyImpureFunctionValues: true
tipsOfTheDay: true
reportMagicMethods: false
reportMagicProperties: false
Expand Down Expand Up @@ -295,6 +296,7 @@ parametersSchema:
propertyAlwaysReadTags: listOf(string())
additionalConstructors: listOf(string())
treatPhpDocTypesAsCertain: bool()
rememberPossiblyImpureFunctionValues: bool()
reportMagicMethods: bool()
reportMagicProperties: bool()
ignoreErrors: listOf(
Expand Down
20 changes: 17 additions & 3 deletions src/Analyser/TypeSpecifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ public function __construct(
private array $functionTypeSpecifyingExtensions,
private array $methodTypeSpecifyingExtensions,
private array $staticMethodTypeSpecifyingExtensions,
private bool $rememberPossiblyImpureFunctionValues,
)
{
foreach (array_merge($functionTypeSpecifyingExtensions, $methodTypeSpecifyingExtensions, $staticMethodTypeSpecifyingExtensions) as $extension) {
Expand Down Expand Up @@ -1182,7 +1183,12 @@ public function create(
}

$functionReflection = $this->reflectionProvider->getFunction($expr->name, $scope);
if ($functionReflection->hasSideEffects()->yes()) {
$hasSideEffects = $functionReflection->hasSideEffects();
if ($hasSideEffects->yes()) {
return new SpecifiedTypes([], [], false, [], $rootExpr);
}

if (!$this->rememberPossiblyImpureFunctionValues && !$hasSideEffects->no()) {
return new SpecifiedTypes([], [], false, [], $rootExpr);
}
}
Expand All @@ -1195,7 +1201,11 @@ public function create(
$methodName = $expr->name->toString();
$calledOnType = $scope->getType($expr->var);
$methodReflection = $scope->getMethodReflection($calledOnType, $methodName);
if ($methodReflection === null || $methodReflection->hasSideEffects()->yes()) {
if (
$methodReflection === null
|| $methodReflection->hasSideEffects()->yes()
|| (!$this->rememberPossiblyImpureFunctionValues && !$methodReflection->hasSideEffects()->no())
) {
if (isset($resultType) && !TypeCombinator::containsNull($resultType)) {
return $this->createNullsafeTypes($rootExpr, $originalExpr, $scope, $context, $overwrite, $type);
}
Expand All @@ -1217,7 +1227,11 @@ public function create(
}

$methodReflection = $scope->getMethodReflection($calledOnType, $methodName);
if ($methodReflection === null || $methodReflection->hasSideEffects()->yes()) {
if (
$methodReflection === null
|| $methodReflection->hasSideEffects()->yes()
|| (!$this->rememberPossiblyImpureFunctionValues && !$methodReflection->hasSideEffects()->no())
) {
if (isset($resultType) && !TypeCombinator::containsNull($resultType)) {
return $this->createNullsafeTypes($rootExpr, $originalExpr, $scope, $context, $overwrite, $type);
}
Expand Down
1 change: 1 addition & 0 deletions src/Analyser/TypeSpecifierFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public function create(): TypeSpecifier
$this->container->getServicesByTag(self::FUNCTION_TYPE_SPECIFYING_EXTENSION_TAG),
$this->container->getServicesByTag(self::METHOD_TYPE_SPECIFYING_EXTENSION_TAG),
$this->container->getServicesByTag(self::STATIC_METHOD_TYPE_SPECIFYING_EXTENSION_TAG),
$this->container->getParameter('rememberPossiblyImpureFunctionValues'),
);

foreach (array_merge(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php declare(strict_types = 1);

namespace PHPStan\Analyser;

use PHPStan\Testing\TypeInferenceTestCase;

class DoNotRememberPossiblyImpureFunctionValuesTest extends TypeInferenceTestCase
{

public function dataAsserts(): iterable
{
yield from $this->gatherAssertTypes(__DIR__ . '/data/do-not-remember-possibly-impure-function-values.php');
}

/**
* @dataProvider dataAsserts
* @param mixed ...$args
*/
public function testAsserts(
string $assertType,
string $file,
...$args,
): void
{
$this->assertFileAsserts($assertType, $file, ...$args);
}

public static function getAdditionalConfigFiles(): array
{
return [
__DIR__ . '/do-not-remember-possibly-impure-function-values.neon',
];
}

}
1 change: 1 addition & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -917,6 +917,7 @@ public function dataFileAsserts(): iterable
yield from $this->gatherAssertTypes(__DIR__ . '/data/in-array-non-empty.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4117.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7490.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/remember-possibly-impure-function-values.php');
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php

namespace DoNotRememberPossiblyImpureFunctionValues;

use function PHPStan\Testing\assertType;

class Foo
{

/** @phpstan-pure */
public function pure(): int
{
return 1;
}

public function maybePure(): int
{
return 1;
}

/** @phpstan-impure */
public function impure(): int
{
return rand(0, 1);
}

public function test(): void
{
if ($this->pure() === 1) {
assertType('1', $this->pure());
}

if ($this->maybePure() === 1) {
assertType('int', $this->maybePure());
}

if ($this->impure() === 1) {
assertType('int', $this->impure());
}
}

}

class FooStatic
{

/** @phpstan-pure */
public static function pure(): int
{
return 1;
}

public static function maybePure(): int
{
return 1;
}

/** @phpstan-impure */
public static function impure(): int
{
return rand(0, 1);
}

public function test(): void
{
if (self::pure() === 1) {
assertType('1', self::pure());
}

if (self::maybePure() === 1) {
assertType('int', self::maybePure());
}

if (self::impure() === 1) {
assertType('int', self::impure());
}
}

}

/** @phpstan-pure */
function pure(): int
{
return 1;
}

function maybePure(): int
{
return 1;
}

/** @phpstan-impure */
function impure(): int
{
return rand(0, 1);
}

function test(): void
{
if (pure() === 1) {
assertType('1', pure());
}

if (maybePure() === 1) {
assertType('int', maybePure());
}

if (impure() === 1) {
assertType('int', impure());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php

namespace RememberPossiblyImpureFunctionValues;

use function PHPStan\Testing\assertType;

class Foo
{

/** @phpstan-pure */
public function pure(): int
{
return 1;
}

public function maybePure(): int
{
return 1;
}

/** @phpstan-impure */
public function impure(): int
{
return rand(0, 1);
}

public function test(): void
{
if ($this->pure() === 1) {
assertType('1', $this->pure());
}

if ($this->maybePure() === 1) {
assertType('1', $this->maybePure());
}

if ($this->impure() === 1) {
assertType('int', $this->impure());
}
}

}

class FooStatic
{

/** @phpstan-pure */
public static function pure(): int
{
return 1;
}

public static function maybePure(): int
{
return 1;
}

/** @phpstan-impure */
public static function impure(): int
{
return rand(0, 1);
}

public function test(): void
{
if (self::pure() === 1) {
assertType('1', self::pure());
}

if (self::maybePure() === 1) {
assertType('1', self::maybePure());
}

if (self::impure() === 1) {
assertType('int', self::impure());
}
}

}

/** @phpstan-pure */
function pure(): int
{
return 1;
}

function maybePure(): int
{
return 1;
}

/** @phpstan-impure */
function impure(): int
{
return rand(0, 1);
}

function test(): void
{
if (pure() === 1) {
assertType('1', pure());
}

if (maybePure() === 1) {
assertType('1', maybePure());
}

if (impure() === 1) {
assertType('int', impure());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
parameters:
rememberPossiblyImpureFunctionValues: false

0 comments on commit 50ed38f

Please sign in to comment.