Skip to content

Commit

Permalink
Fix #2242 - warn when using mutable dependencies
Browse files Browse the repository at this point in the history
  • Loading branch information
muglug committed Feb 22, 2020
1 parent a50826d commit a706f4d
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 0 deletions.
1 change: 1 addition & 0 deletions config.xsd
Expand Up @@ -271,6 +271,7 @@
<xs:element name="MixedPropertyTypeCoercion" type="PropertyIssueHandlerType" minOccurs="0" />
<xs:element name="MoreSpecificReturnType" type="IssueHandlerType" minOccurs="0" />
<xs:element name="MoreSpecificImplementedParamType" type="IssueHandlerType" minOccurs="0" />
<xs:element name="MutableDependency" type="PropertyIssueHandlerType" minOccurs="0" />
<xs:element name="NoInterfaceProperties" type="ClassIssueHandlerType" minOccurs="0" />
<xs:element name="NonStaticSelfCall" type="IssueHandlerType" minOccurs="0" />
<xs:element name="NullableReturnStatement" type="IssueHandlerType" minOccurs="0" />
Expand Down
19 changes: 19 additions & 0 deletions docs/running_psalm/issues.md
Expand Up @@ -1433,6 +1433,25 @@ function foo() : B {
}
```

### MutableDependency

Emitted when an immutable class inherits from a class or trait not marked immutable

```php
class MutableParent {
public int $i = 0;

public function increment() : void {
$this->i++;
}
}

/**
* @psalm-immutable
*/
final class NotReallyImmutableClass extends MutableParent {}
```

### NoValue

Emitted when using the result of a function that never returns.
Expand Down
27 changes: 27 additions & 0 deletions src/Psalm/Internal/Analyzer/ClassAnalyzer.php
Expand Up @@ -22,6 +22,7 @@
use Psalm\Issue\MissingImmutableAnnotation;
use Psalm\Issue\MissingPropertyType;
use Psalm\Issue\MissingTemplateParam;
use Psalm\Issue\MutableDependency;
use Psalm\Issue\OverriddenPropertyAccess;
use Psalm\Issue\PropertyNotSetInConstructor;
use Psalm\Issue\ReservedWord;
Expand Down Expand Up @@ -329,6 +330,20 @@ public function analyze(
}
}

if ($storage->mutation_free
&& !$parent_class_storage->mutation_free
) {
if (IssueBuffer::accepts(
new MutableDependency(
$fq_class_name . ' is marked immutable but ' . $parent_fq_class_name . ' is not',
$code_location
),
$storage->suppressed_issues + $this->getSuppressedIssues()
)) {
// fall through
}
}

if ($codebase->store_node_types) {
$codebase->analyzer->addNodeReference(
$this->getFilePath(),
Expand Down Expand Up @@ -1433,6 +1448,18 @@ private function analyzeTraitUse(
}
}

if ($storage->mutation_free && !$trait_storage->mutation_free) {
if (IssueBuffer::accepts(
new MutableDependency(
$storage->name . ' is marked immutable but ' . $fq_trait_name . ' is not',
new CodeLocation($this, $trait_name)
),
$storage->suppressed_issues + $this->getSuppressedIssues()
)) {
// fall through
}
}

$trait_file_analyzer = $project_analyzer->getFileAnalyzerForClassLike($fq_trait_name_resolved);
$trait_node = $codebase->classlikes->getTraitNode($fq_trait_name_resolved);
$trait_aliases = $codebase->classlikes->getTraitAliases($fq_trait_name_resolved);
Expand Down
7 changes: 7 additions & 0 deletions src/Psalm/Issue/MutableDependency.php
@@ -0,0 +1,7 @@
<?php
namespace Psalm\Issue;

class MutableDependency extends CodeIssue
{
const ERROR_LEVEL = 1;
}
72 changes: 72 additions & 0 deletions tests/ImmutableAnnotationTest.php
Expand Up @@ -333,6 +333,44 @@ public function get(): int {
$item = new Item(5);
new Immutable($item);',
],
'preventNonImmutableTraitInImmutableClass' => [
'<?php
/**
* @psalm-immutable
*/
trait ImmutableTrait {
public int $i = 0;
public function __construct(int $i) {
$this->i = $i;
}
}
/**
* @psalm-immutable
*/
final class NotReallyImmutableClass {
use ImmutableTrait;
}',
],
'preventImmutableClassInheritingMutableParent' => [
'<?php
/**
* @psalm-immutable
*/
class ImmutableParent {
public int $i = 0;
public function __construct(int $i) {
$this->i = $i;
}
}
/**
* @psalm-immutable
*/
final class ImmutableClass extends ImmutableParent {}',
],
];
}

Expand Down Expand Up @@ -529,6 +567,40 @@ public function get(): int {
new Immutable($item);',
'error_message' => 'ImpureArgument',
],
'preventNonImmutableTraitInImmutableClass' => [
'<?php
trait MutableTrait {
public int $i = 0;
public function increment() : void {
$this->i++;
}
}
/**
* @psalm-immutable
*/
final class NotReallyImmutableClass {
use MutableTrait;
}',
'error_message' => 'MutableDependency'
],
'preventImmutableClassInheritingMutableParent' => [
'<?php
class MutableParent {
public int $i = 0;
public function increment() : void {
$this->i++;
}
}
/**
* @psalm-immutable
*/
final class NotReallyImmutableClass extends MutableParent {}',
'error_message' => 'MutableDependency'
],
];
}
}

0 comments on commit a706f4d

Please sign in to comment.