mirror of
https://github.com/danog/psalm.git
synced 2024-11-30 04:39:00 +01:00
Prohibit property fetches from pure contexts except when they’re on immutable objects
This commit is contained in:
parent
1cf5153700
commit
c8ea4b4e8b
@ -13,6 +13,7 @@ use Psalm\Internal\Type\TemplateResult;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Context;
|
||||
use Psalm\Issue\DeprecatedProperty;
|
||||
use Psalm\Issue\ImpurePropertyFetch;
|
||||
use Psalm\Issue\InvalidPropertyFetch;
|
||||
use Psalm\Issue\InternalProperty;
|
||||
use Psalm\Issue\MissingPropertyType;
|
||||
@ -179,12 +180,14 @@ class InstancePropertyFetchAnalyzer
|
||||
|
||||
$property_id = $lhs_type_part->value . '::$' . $stmt->name->name;
|
||||
|
||||
$class_storage = $codebase->classlike_storage_provider->get($lhs_type_part->value);
|
||||
|
||||
self::processTaints(
|
||||
$statements_analyzer,
|
||||
$stmt,
|
||||
$stmt_type,
|
||||
$property_id,
|
||||
$codebase->classlike_storage_provider->get($lhs_type_part->value),
|
||||
$class_storage,
|
||||
$in_assignment
|
||||
);
|
||||
|
||||
@ -208,6 +211,32 @@ class InstancePropertyFetchAnalyzer
|
||||
$property_id
|
||||
);
|
||||
}
|
||||
|
||||
if (!$context->collect_mutations
|
||||
&& !$context->collect_initializations
|
||||
&& !($class_storage->external_mutation_free
|
||||
&& $stmt_type->allow_mutations)
|
||||
) {
|
||||
$project_analyzer = $statements_analyzer->getProjectAnalyzer();
|
||||
|
||||
if ($context->pure) {
|
||||
if (IssueBuffer::accepts(
|
||||
new ImpurePropertyFetch(
|
||||
'Cannot access a property on a mutable object from a pure context',
|
||||
new CodeLocation($statements_analyzer, $stmt)
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
} elseif ($codebase->alter_code
|
||||
&& isset($project_analyzer->getIssuesToFix()['MissingPureAnnotation'])
|
||||
&& $statements_analyzer->getSource()
|
||||
instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer
|
||||
) {
|
||||
$statements_analyzer->getSource()->inferred_impure = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -956,6 +985,32 @@ class InstancePropertyFetchAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
if (!$context->collect_mutations
|
||||
&& !$context->collect_initializations
|
||||
&& !($class_storage->external_mutation_free
|
||||
&& $class_property_type->allow_mutations)
|
||||
) {
|
||||
$project_analyzer = $statements_analyzer->getProjectAnalyzer();
|
||||
|
||||
if ($context->pure) {
|
||||
if (IssueBuffer::accepts(
|
||||
new ImpurePropertyFetch(
|
||||
'Cannot access a property on a mutable object from a pure context',
|
||||
new CodeLocation($statements_analyzer, $stmt)
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
} elseif ($codebase->alter_code
|
||||
&& isset($project_analyzer->getIssuesToFix()['MissingPureAnnotation'])
|
||||
&& $statements_analyzer->getSource()
|
||||
instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer
|
||||
) {
|
||||
$statements_analyzer->getSource()->inferred_impure = true;
|
||||
}
|
||||
}
|
||||
|
||||
self::processTaints(
|
||||
$statements_analyzer,
|
||||
$stmt,
|
||||
|
8
src/Psalm/Issue/ImpurePropertyFetch.php
Normal file
8
src/Psalm/Issue/ImpurePropertyFetch.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
namespace Psalm\Issue;
|
||||
|
||||
class ImpurePropertyFetch extends CodeIssue
|
||||
{
|
||||
const ERROR_LEVEL = -1;
|
||||
const SHORTCODE = 234;
|
||||
}
|
@ -40,6 +40,27 @@ class PureAnnotationAdditionTest extends FileManipulationTest
|
||||
['MissingPureAnnotation'],
|
||||
true,
|
||||
],
|
||||
'dontAddPureAnnotationToMutationFreeMethod' => [
|
||||
'<?php
|
||||
class A {
|
||||
public string $foo = "hello";
|
||||
|
||||
public function getFoo() : string {
|
||||
return $this->foo;
|
||||
}
|
||||
}',
|
||||
'<?php
|
||||
class A {
|
||||
public string $foo = "hello";
|
||||
|
||||
public function getFoo() : string {
|
||||
return $this->foo;
|
||||
}
|
||||
}',
|
||||
'7.4',
|
||||
['MissingPureAnnotation'],
|
||||
true,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -18,18 +18,12 @@ class PureAnnotationTest extends TestCase
|
||||
'<?php
|
||||
namespace Bar;
|
||||
|
||||
class A {
|
||||
public int $a = 5;
|
||||
}
|
||||
|
||||
/** @psalm-pure */
|
||||
function filterOdd(int $i, A $a) : ?int {
|
||||
if ($i % 2 === 0 || $a->a === 2) {
|
||||
function filterOdd(int $i) : ?int {
|
||||
if ($i % 2 === 0) {
|
||||
return $i;
|
||||
}
|
||||
|
||||
$a = new A();
|
||||
|
||||
return null;
|
||||
}',
|
||||
],
|
||||
@ -345,6 +339,28 @@ class PureAnnotationTest extends TestCase
|
||||
}
|
||||
}'
|
||||
],
|
||||
'allowPropertyAccessOnImmutableClass' => [
|
||||
'<?php
|
||||
namespace Bar;
|
||||
|
||||
/** @psalm-immutable */
|
||||
class A {
|
||||
public int $a;
|
||||
|
||||
public function __construct(int $a) {
|
||||
$this->a = $a;
|
||||
}
|
||||
}
|
||||
|
||||
/** @psalm-pure */
|
||||
function filterOdd(A $a) : bool {
|
||||
if ($a->a % 2 === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@ -364,7 +380,7 @@ class PureAnnotationTest extends TestCase
|
||||
|
||||
/** @psalm-pure */
|
||||
function filterOdd(int $i, A $a) : ?int {
|
||||
$a->a++;
|
||||
$a->a = $i;
|
||||
|
||||
if ($i % 2 === 0 || $a->a === 2) {
|
||||
return $i;
|
||||
@ -598,6 +614,40 @@ class PureAnnotationTest extends TestCase
|
||||
}',
|
||||
'error_message' => 'ImpureFunctionCall',
|
||||
],
|
||||
'propertyFetchIsNotPure' => [
|
||||
'<?php
|
||||
class A {
|
||||
public string $foo = "hello";
|
||||
|
||||
/** @psalm-pure */
|
||||
public function getFoo() : string {
|
||||
return $this->foo;
|
||||
}
|
||||
}',
|
||||
'error_message' => 'ImpurePropertyFetch',
|
||||
],
|
||||
'preventPropertyAccessOnMutableClass' => [
|
||||
'<?php
|
||||
namespace Bar;
|
||||
|
||||
class A {
|
||||
public int $a;
|
||||
|
||||
public function __construct(int $a) {
|
||||
$this->a = $a;
|
||||
}
|
||||
}
|
||||
|
||||
/** @psalm-pure */
|
||||
function filterOdd(A $a) : bool {
|
||||
if ($a->a % 2 === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}',
|
||||
'error_message' => 'ImpurePropertyFetch',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user