Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow assertions on mutable object properties. #7252

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 10 additions & 8 deletions docs/annotating_code/supported_annotations.md
Expand Up @@ -240,6 +240,8 @@ $b->s = "boo"; // disallowed
### `@psalm-mutation-free`

Used to annotate a class method that does not mutate state, either internally or externally of the class's scope.
This requires that the return value depend only on the instance's properties. For example, `random_int` is considered
mutating here because it mutates the random number generator's internal state.

```php
<?php
Expand Down Expand Up @@ -309,7 +311,7 @@ Used to annotate a class where every property is treated by consumers as `@psalm
abstract class Foo
{
public string $baz;

abstract public function bar(): int;
}

Expand All @@ -322,7 +324,7 @@ final class ChildClass extends Foo
{
$this->baz = $baz;
}

public function bar(): int
{
return 0;
Expand All @@ -332,7 +334,7 @@ final class ChildClass extends Foo
$anonymous = new /** @psalm-immutable */ class extends Foo
{
public string $baz = "B";

public function bar(): int
{
return 1;
Expand Down Expand Up @@ -393,9 +395,9 @@ class Counter {
/**
* @readonly
* @psalm-allow-private-mutation
*/
*/
public int $count = 0;

public function increment() : void {
$this->count++;
}
Expand All @@ -417,9 +419,9 @@ This is a shorthand for the property annotations `@readonly` and `@psalm-allow-p
class Counter {
/**
* @psalm-readonly-allow-private-mutation
*/
*/
public int $count = 0;

public function increment() : void {
$this->count++;
}
Expand All @@ -439,7 +441,7 @@ You can use this annotation to trace inferred type (applied to the *next* statem
```php
<?php

/** @psalm-trace $username */
/** @psalm-trace $username */
$username = $_GET['username']; // prints something like "test.php:4 $username: mixed"

```
Expand Down
Expand Up @@ -4106,26 +4106,26 @@ public static function isPropertyImmutableOnArgument(

foreach ($type->getAtomicTypes() as $type) {
if (!$type instanceof TNamedObject) {
return 'Variable ' . $name . ' is not an object so assertion cannot be applied';
return 'Variable ' . $name . ' is not an object so the assertion cannot be applied';
}

$class_definition = $class_provider->get($type->value);
$property_definition = $class_definition->properties[$property] ?? null;

if (!$property_definition instanceof PropertyStorage) {
return sprintf(
'Property %s is not defined on variable %s so assertion cannot be applied',
$property,
$name
);
}
$magic_type = $class_definition->pseudo_property_get_types['$' . $property] ?? null;
if ($magic_type === null) {
return sprintf(
'Property %s is not defined on variable %s so the assertion cannot be applied',
$property,
$name
);
}

if (!$property_definition->readonly) {
return sprintf(
'Property %s of variable %s is not read-only/immutable so assertion cannot be applied',
$property,
$name
);
$magic_getter = $class_definition->methods['__get'] ?? null;
if ($magic_getter === null || !$magic_getter->mutation_free) {
return "{$class_definition->name}::__get is not mutation-free, so the assertion cannot be applied";
}
}
}

Expand Down