mirror of
https://github.com/danog/psalm.git
synced 2025-01-22 22:01:48 +01:00
This commit is contained in:
parent
b38530ed0d
commit
1ff8518888
@ -7,6 +7,7 @@ use Psalm\CodeLocation;
|
|||||||
use Psalm\Codebase;
|
use Psalm\Codebase;
|
||||||
use Psalm\Context;
|
use Psalm\Context;
|
||||||
use Psalm\Internal\Analyzer\AttributesAnalyzer;
|
use Psalm\Internal\Analyzer\AttributesAnalyzer;
|
||||||
|
use Psalm\Internal\Analyzer\Statements\Expression\Assignment\InstancePropertyAssignmentAnalyzer;
|
||||||
use Psalm\Internal\Analyzer\Statements\Expression\AssignmentAnalyzer;
|
use Psalm\Internal\Analyzer\Statements\Expression\AssignmentAnalyzer;
|
||||||
use Psalm\Internal\Analyzer\Statements\Expression\CallAnalyzer;
|
use Psalm\Internal\Analyzer\Statements\Expression\CallAnalyzer;
|
||||||
use Psalm\Internal\Analyzer\Statements\Expression\ExpressionIdentifier;
|
use Psalm\Internal\Analyzer\Statements\Expression\ExpressionIdentifier;
|
||||||
@ -45,6 +46,7 @@ use Psalm\Type\Atomic\TClosure;
|
|||||||
use Psalm\Type\Atomic\TKeyedArray;
|
use Psalm\Type\Atomic\TKeyedArray;
|
||||||
use Psalm\Type\Atomic\TList;
|
use Psalm\Type\Atomic\TList;
|
||||||
use Psalm\Type\Atomic\TLiteralString;
|
use Psalm\Type\Atomic\TLiteralString;
|
||||||
|
use Psalm\Type\Atomic\TNamedObject;
|
||||||
use Psalm\Type\Atomic\TNonEmptyArray;
|
use Psalm\Type\Atomic\TNonEmptyArray;
|
||||||
use Psalm\Type\Atomic\TTemplateParam;
|
use Psalm\Type\Atomic\TTemplateParam;
|
||||||
use Psalm\Type\Union;
|
use Psalm\Type\Union;
|
||||||
@ -1253,6 +1255,37 @@ final class ArgumentsAnalyzer
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static function handleByRefReadonlyArg(
|
||||||
|
StatementsAnalyzer $statements_analyzer,
|
||||||
|
Context $context,
|
||||||
|
PhpParser\Node\Expr\PropertyFetch $stmt,
|
||||||
|
string $fq_class_name,
|
||||||
|
string $prop_name
|
||||||
|
): void {
|
||||||
|
$property_id = $fq_class_name . '::$' . $prop_name;
|
||||||
|
|
||||||
|
$codebase = $statements_analyzer->getCodebase();
|
||||||
|
$declaring_property_class = (string) $codebase->properties->getDeclaringClassForProperty(
|
||||||
|
$property_id,
|
||||||
|
false, # what does this do? @todo
|
||||||
|
$statements_analyzer,
|
||||||
|
);
|
||||||
|
$declaring_class_storage = $codebase->classlike_storage_provider->get($declaring_property_class);
|
||||||
|
|
||||||
|
if (isset($declaring_class_storage->properties[$prop_name])) {
|
||||||
|
$property_storage = $declaring_class_storage->properties[$prop_name];
|
||||||
|
|
||||||
|
InstancePropertyAssignmentAnalyzer::trackPropertyImpurity(
|
||||||
|
$statements_analyzer,
|
||||||
|
$stmt,
|
||||||
|
$property_id,
|
||||||
|
$property_storage,
|
||||||
|
$declaring_class_storage,
|
||||||
|
$context,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return false|null
|
* @return false|null
|
||||||
*/
|
*/
|
||||||
@ -1274,6 +1307,46 @@ final class ArgumentsAnalyzer
|
|||||||
'reset', 'end', 'next', 'prev', 'array_pop', 'array_shift',
|
'reset', 'end', 'next', 'prev', 'array_pop', 'array_shift',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if ($arg->value instanceof PhpParser\Node\Expr\PropertyFetch
|
||||||
|
&& $arg->value->name instanceof PhpParser\Node\Identifier) {
|
||||||
|
$prop_name = $arg->value->name->name;
|
||||||
|
if ($statements_analyzer->getFQCLN()) {
|
||||||
|
$fq_class_name = $statements_analyzer->getFQCLN();
|
||||||
|
|
||||||
|
self::handleByRefReadonlyArg(
|
||||||
|
$statements_analyzer,
|
||||||
|
$context,
|
||||||
|
$arg->value,
|
||||||
|
$fq_class_name,
|
||||||
|
$prop_name,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// @todo atm only works for simple fetch, $a->foo, not $a->foo->bar
|
||||||
|
// I guess there's a function to do this, but I couldn't locate it
|
||||||
|
$var_id = ExpressionIdentifier::getVarId(
|
||||||
|
$arg->value->var,
|
||||||
|
$statements_analyzer->getFQCLN(),
|
||||||
|
$statements_analyzer,
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($var_id && isset($context->vars_in_scope[$var_id])) {
|
||||||
|
foreach ($context->vars_in_scope[$var_id]->getAtomicTypes() as $atomic_type) {
|
||||||
|
if ($atomic_type instanceof TNamedObject) {
|
||||||
|
$fq_class_name = $atomic_type->value;
|
||||||
|
|
||||||
|
self::handleByRefReadonlyArg(
|
||||||
|
$statements_analyzer,
|
||||||
|
$context,
|
||||||
|
$arg->value,
|
||||||
|
$fq_class_name,
|
||||||
|
$prop_name,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (($var_id && isset($context->vars_in_scope[$var_id]))
|
if (($var_id && isset($context->vars_in_scope[$var_id]))
|
||||||
|| ($method_id
|
|| ($method_id
|
||||||
&& in_array(
|
&& in_array(
|
||||||
|
@ -764,6 +764,69 @@ class ImmutableAnnotationTest extends TestCase
|
|||||||
}',
|
}',
|
||||||
'error_message' => 'ImpurePropertyAssignment',
|
'error_message' => 'ImpurePropertyAssignment',
|
||||||
],
|
],
|
||||||
|
'readonlyByRefInClass' => [
|
||||||
|
'code' => '<?php
|
||||||
|
namespace World;
|
||||||
|
|
||||||
|
final readonly class Foo
|
||||||
|
{
|
||||||
|
public array $values;
|
||||||
|
|
||||||
|
public function __construct(array $values)
|
||||||
|
{
|
||||||
|
$this->values = $values;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function bar(): mixed
|
||||||
|
{
|
||||||
|
return reset($this->values);
|
||||||
|
}
|
||||||
|
}',
|
||||||
|
'error_message' => 'InaccessibleProperty',
|
||||||
|
],
|
||||||
|
'readonlyByRef' => [
|
||||||
|
'code' => '<?php
|
||||||
|
namespace World;
|
||||||
|
|
||||||
|
final readonly class Foo
|
||||||
|
{
|
||||||
|
public array $values;
|
||||||
|
|
||||||
|
public function __construct(array $values)
|
||||||
|
{
|
||||||
|
$this->values = $values;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$x = new Foo([]);
|
||||||
|
reset($x->values);',
|
||||||
|
'error_message' => 'InaccessibleProperty',
|
||||||
|
],
|
||||||
|
'readonlyByRefCustomFunction' => [
|
||||||
|
'code' => '<?php
|
||||||
|
namespace World;
|
||||||
|
|
||||||
|
final readonly class Foo
|
||||||
|
{
|
||||||
|
public array $values;
|
||||||
|
|
||||||
|
public function __construct(array $values)
|
||||||
|
{
|
||||||
|
$this->values = $values;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $a
|
||||||
|
* @param array $b
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
function bar($a, &$b) {}
|
||||||
|
|
||||||
|
$x = new Foo([]);
|
||||||
|
bar("hello", $x->values);',
|
||||||
|
'error_message' => 'InaccessibleProperty',
|
||||||
|
],
|
||||||
'preventUnset' => [
|
'preventUnset' => [
|
||||||
'code' => '<?php
|
'code' => '<?php
|
||||||
/**
|
/**
|
||||||
|
Loading…
x
Reference in New Issue
Block a user