Skip to content

Commit

Permalink
Merge pull request #7252 from AndrolGenhald/feature/allow-assertions-…
Browse files Browse the repository at this point in the history
…on-mutable-object-properties
  • Loading branch information
weirdan committed Jan 2, 2022
2 parents 047859b + 1b9f57f commit 697db76
Show file tree
Hide file tree
Showing 3 changed files with 356 additions and 61 deletions.
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

0 comments on commit 697db76

Please sign in to comment.