Skip to content

Commit 491dd74

Browse files
authored
Merge pull request #11676 from vimeo/psalm_pure_on_classes
Allow @psalm-pure on classes
2 parents 541892e + 3c947b7 commit 491dd74

File tree

380 files changed

+2130
-167
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

380 files changed

+2130
-167
lines changed

config.xsd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,7 @@
372372
<xs:element name="MixedStringOffsetAssignment" type="IssueHandlerType" minOccurs="0" />
373373
<xs:element name="MoreSpecificImplementedParamType" type="IssueHandlerType" minOccurs="0" />
374374
<xs:element name="MoreSpecificReturnType" type="IssueHandlerType" minOccurs="0" />
375+
<xs:element name="ImmutableDependency" type="PropertyIssueHandlerType" minOccurs="0" />
375376
<xs:element name="MutableDependency" type="PropertyIssueHandlerType" minOccurs="0" />
376377
<xs:element name="NamedArgumentNotAllowed" type="ArgumentIssueHandlerType" minOccurs="0" />
377378
<xs:element name="NoEnumProperties" type="ClassIssueHandlerType" minOccurs="0" />

docs/annotating_code/supported_annotations.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,9 @@ Used to annotate a class where at least one property is mutable: this is the def
449449

450450
### `@psalm-pure`
451451

452-
Used to annotate a [pure function](https://en.wikipedia.org/wiki/Pure_function) - one whose output is just a function of its input.
452+
Used to annotate a [pure function](https://en.wikipedia.org/wiki/Pure_function) - one whose output is just a function of its input.
453+
454+
Can also be used on classes to auotmatically annotate all of its methods as pure and ban the usage of properties.
453455

454456
```php
455457
<?php

docs/running_psalm/error_levels.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ Level 5 and above allows a more non-verifiable code, and higher levels are even
109109
- [UnusedMethodCall](issues/UnusedMethodCall.md)
110110
## Errors that only appear at level 1
111111

112+
- [ImmutableDependency](issues/ImmutableDependency.md)
112113
- [InvalidClassConstantType](issues/InvalidClassConstantType.md)
113114
- [LessSpecificClassConstantType](issues/LessSpecificClassConstantType.md)
114115
- [LessSpecificReturnType](issues/LessSpecificReturnType.md)

docs/running_psalm/issues.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
- [FalseOperand](issues/FalseOperand.md)
4040
- [ForbiddenCode](issues/ForbiddenCode.md)
4141
- [IfThisIsMismatch](issues/IfThisIsMismatch.md)
42+
- [ImmutableDependency](issues/ImmutableDependency.md)
4243
- [ImplementationRequirementViolation](issues/ImplementationRequirementViolation.md)
4344
- [ImplementedParamTypeMismatch](issues/ImplementedParamTypeMismatch.md)
4445
- [ImplementedReturnTypeMismatch](issues/ImplementedReturnTypeMismatch.md)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# ImmutableDependency
2+
3+
Emitted when an mutable class inherits from an immutable class, trait or interface.
4+
5+
```php
6+
<?php
7+
8+
/** @psalm-immutable */
9+
class ImmutableParent {
10+
public int $i = 0;
11+
12+
public function getI(): int {
13+
return $this->i;
14+
}
15+
}
16+
17+
final class MutableChild extends ImmutableParent {
18+
public function setI(int $i): void {
19+
$this->i = 123;
20+
}
21+
}
22+
23+
// This is bad because when passing around an ImmutableParent instance,
24+
// we might actually be passing around a MutableChild.
25+
```
26+
27+
Will also be emitted for classes marked `@psalm-pure`, `@psalm-mutation-free`, `@psalm-external-mutation-free`.
28+
29+
To fix, make the child have the same mutability level of the parent, or vice versa.

docs/running_psalm/issues/MissingImmutableAnnotation.md

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
# MissingImmutableAnnotation
22

3-
Emitted when a class inheriting from an immutable interface or class does not also have a `@psalm-immutable` declaration.
4-
5-
Also emitted when a potentially immutable interface or class does not have a `@psalm-immutable` declaration.
3+
Emitted when a potentially immutable interface or class does not have a `@psalm-pure`, `@psalm-immutable` or `@psalm-external-mutation-free` declaration.
64

75
To automatically add immutable annotations where needed, run Psalm with `--alter --issues=MissingImmutableAnnotation`.
86

@@ -11,18 +9,17 @@ This issue is emitted to aid [security analysis](https://psalm.dev/docs/security
119
```php
1210
<?php
1311

14-
/** @psalm-immutable */
15-
interface SomethingImmutable {
16-
public function someInteger() : int;
17-
}
18-
19-
class MutableImplementation implements SomethingImmutable {
12+
/** @api */
13+
final class CouldBeExternallyMutationFree {
2014
private int $counter = 0;
15+
16+
/** @psalm-external-mutation-free */
2117
public function someInteger() : int {
2218
return ++$this->counter;
2319
}
2420
}
2521

22+
/** @api */
2623
final class CouldBeImmutable {
2724
}
2825

docs/running_psalm/issues/MissingInterfaceImmutableAnnotation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# MissingInterfaceImmutableAnnotation
22

3-
Emitted when an interface is not annotated with `@psalm-immutable` nor `@psalm-mutable`: to fix, mark the interface with one of the two annotations, enforcing immutability (or mutability) for all properties and methods of implementing classes.
3+
Emitted when an interface is not annotated with `@psalm-pure`, `@psalm-immutable`, `@psalm-external-mutation-free` or `@psalm-mutable`: to fix, mark the interface with one of the two annotations, enforcing immutability (or mutability) for all properties and methods of implementing classes.
44

55
This issue is emitted to aid [security analysis](https://psalm.dev/docs/security_analysis/), which works best when all explicitly immutable interfaces and classes are marked as immutable.
66

psalm-baseline.xml

Lines changed: 30 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<files psalm-version="6.x-dev@bbd217fc98c0daa0a13aea2a7f119d03ba3fc9a0">
2+
<files psalm-version="dev-master@cf404b51daf81d79198a029f7b2c7f64d28ae68e">
33
<file src="examples/TemplateChecker.php">
44
<PossiblyUndefinedIntArrayOffset>
55
<code><![CDATA[$comment_block->tags['variablesfrom'][0]]]></code>
@@ -131,11 +131,6 @@
131131
<code><![CDATA[glob($parts[0], GLOB_ONLYDIR | GLOB_NOSORT)]]></code>
132132
</RiskyTruthyFalsyComparison>
133133
</file>
134-
<file src="src/Psalm/Config/ProjectFileFilter.php">
135-
<MutableDependency>
136-
<code><![CDATA[#[Override]]]></code>
137-
</MutableDependency>
138-
</file>
139134
<file src="src/Psalm/Context.php">
140135
<RiskyTruthyFalsyComparison>
141136
<code><![CDATA[strpos($key, '()')]]></code>
@@ -221,9 +216,6 @@
221216
</RiskyTruthyFalsyComparison>
222217
</file>
223218
<file src="src/Psalm/Internal/Analyzer/ClassLikeAnalyzer.php">
224-
<MutableDependency>
225-
<code><![CDATA[#[Override]]]></code>
226-
</MutableDependency>
227219
<PossiblyUndefinedIntArrayOffset>
228220
<code><![CDATA[$property_name]]></code>
229221
</PossiblyUndefinedIntArrayOffset>
@@ -1558,12 +1550,6 @@
15581550
</RiskyTruthyFalsyComparison>
15591551
</file>
15601552
<file src="src/Psalm/Internal/Codebase/CombinedFlowGraph.php">
1561-
<MutableDependency>
1562-
<code><![CDATA[#[Override]]]></code>
1563-
<code><![CDATA[#[Override]]]></code>
1564-
<code><![CDATA[#[Override]]]></code>
1565-
<code><![CDATA[DataFlowGraph]]></code>
1566-
</MutableDependency>
15671553
<PossiblyUnusedMethod>
15681554
<code><![CDATA[addSink]]></code>
15691555
</PossiblyUnusedMethod>
@@ -1665,10 +1651,9 @@
16651651
</RiskyTruthyFalsyComparison>
16661652
</file>
16671653
<file src="src/Psalm/Internal/Codebase/TaintFlowGraph.php">
1668-
<MutableDependency>
1669-
<code><![CDATA[#[Override]]]></code>
1654+
<ImmutableDependency>
16701655
<code><![CDATA[DataFlowGraph]]></code>
1671-
</MutableDependency>
1656+
</ImmutableDependency>
16721657
<RiskyTruthyFalsyComparison>
16731658
<code><![CDATA[end($path_types)]]></code>
16741659
</RiskyTruthyFalsyComparison>
@@ -1677,10 +1662,9 @@
16771662
</UnnecessaryVarAnnotation>
16781663
</file>
16791664
<file src="src/Psalm/Internal/Codebase/VariableUseGraph.php">
1680-
<MutableDependency>
1681-
<code><![CDATA[#[Override]]]></code>
1665+
<ImmutableDependency>
16821666
<code><![CDATA[DataFlowGraph]]></code>
1683-
</MutableDependency>
1667+
</ImmutableDependency>
16841668
</file>
16851669
<file src="src/Psalm/Internal/Composer.php">
16861670
<RiskyTruthyFalsyComparison>
@@ -2123,11 +2107,6 @@
21232107
<code><![CDATA[new Cache($config, 'classlike_cache', $dependencies, $persistent)]]></code>
21242108
</InvalidPropertyAssignmentValue>
21252109
</file>
2126-
<file src="src/Psalm/Internal/Provider/FakeFileProvider.php">
2127-
<MutableDependency>
2128-
<code><![CDATA[#[Override]]]></code>
2129-
</MutableDependency>
2130-
</file>
21312110
<file src="src/Psalm/Internal/Provider/FileReferenceCacheProvider.php">
21322111
<InvalidPropertyAssignmentValue>
21332112
<code><![CDATA[new Cache($config, 'file_reference', [$composerLock], $persistent)]]></code>
@@ -2534,30 +2513,10 @@
25342513
<code><![CDATA[strpos($string_type_token[0], '$')]]></code>
25352514
</RiskyTruthyFalsyComparison>
25362515
</file>
2537-
<file src="src/Psalm/Internal/TypeVisitor/CanContainObjectTypeVisitor.php">
2538-
<MutableDependency>
2539-
<code><![CDATA[#[Override]]]></code>
2540-
</MutableDependency>
2541-
</file>
2542-
<file src="src/Psalm/Internal/TypeVisitor/ContainsLiteralVisitor.php">
2543-
<MutableDependency>
2544-
<code><![CDATA[#[Override]]]></code>
2545-
</MutableDependency>
2546-
</file>
2547-
<file src="src/Psalm/Internal/TypeVisitor/ContainsStaticVisitor.php">
2548-
<MutableDependency>
2549-
<code><![CDATA[#[Override]]]></code>
2550-
</MutableDependency>
2551-
</file>
2552-
<file src="src/Psalm/Internal/TypeVisitor/TemplateTypeCollector.php">
2553-
<MutableDependency>
2554-
<code><![CDATA[#[Override]]]></code>
2555-
</MutableDependency>
2556-
</file>
25572516
<file src="src/Psalm/Internal/TypeVisitor/TypeChecker.php">
2558-
<MutableDependency>
2517+
<ImmutableDependency>
25592518
<code><![CDATA[#[Override]]]></code>
2560-
</MutableDependency>
2519+
</ImmutableDependency>
25612520
<RiskyTruthyFalsyComparison>
25622521
<code><![CDATA[!$fq_classlike_name]]></code>
25632522
<code><![CDATA[$class_storage->template_types]]></code>
@@ -2566,9 +2525,9 @@
25662525
</RiskyTruthyFalsyComparison>
25672526
</file>
25682527
<file src="src/Psalm/Internal/TypeVisitor/TypeScanner.php">
2569-
<MutableDependency>
2528+
<ImmutableDependency>
25702529
<code><![CDATA[#[Override]]]></code>
2571-
</MutableDependency>
2530+
</ImmutableDependency>
25722531
</file>
25732532
<file src="src/Psalm/Issue/ArgumentIssue.php">
25742533
<RiskyTruthyFalsyComparison>
@@ -2828,10 +2787,10 @@
28282787
</RiskyTruthyFalsyComparison>
28292788
</file>
28302789
<file src="src/Psalm/Report/CompactReport.php">
2831-
<MutableDependency>
2790+
<ImmutableDependency>
28322791
<code><![CDATA[#[Override]]]></code>
28332792
<code><![CDATA[Report]]></code>
2834-
</MutableDependency>
2793+
</ImmutableDependency>
28352794
</file>
28362795
<file src="src/Psalm/Report/ConsoleReport.php">
28372796
<PossiblyInvalidArgument>
@@ -2853,27 +2812,27 @@
28532812
</RiskyTruthyFalsyComparison>
28542813
</file>
28552814
<file src="src/Psalm/Report/JunitReport.php">
2856-
<MutableDependency>
2815+
<ImmutableDependency>
28572816
<code><![CDATA[#[Override]]]></code>
28582817
<code><![CDATA[Report]]></code>
2859-
</MutableDependency>
2818+
</ImmutableDependency>
28602819
</file>
28612820
<file src="src/Psalm/Report/PhpStormReport.php">
28622821
<RiskyTruthyFalsyComparison>
28632822
<code><![CDATA[$issue_data->taint_trace]]></code>
28642823
</RiskyTruthyFalsyComparison>
28652824
</file>
28662825
<file src="src/Psalm/Report/SarifReport.php">
2867-
<MutableDependency>
2826+
<ImmutableDependency>
28682827
<code><![CDATA[#[Override]]]></code>
28692828
<code><![CDATA[Report]]></code>
2870-
</MutableDependency>
2829+
</ImmutableDependency>
28712830
</file>
28722831
<file src="src/Psalm/Report/XmlReport.php">
2873-
<MutableDependency>
2832+
<ImmutableDependency>
28742833
<code><![CDATA[#[Override]]]></code>
28752834
<code><![CDATA[Report]]></code>
2876-
</MutableDependency>
2835+
</ImmutableDependency>
28772836
</file>
28782837
<file src="src/Psalm/StatementsSource.php">
28792838
<MissingInterfaceImmutableAnnotation>
@@ -3409,6 +3368,16 @@
34093368
<code><![CDATA[$expected_value_type]]></code>
34103369
</ImplicitToStringCast>
34113370
</file>
3371+
<file src="tests/Config/ConfigFileTest.php">
3372+
<ImmutableDependency>
3373+
<code><![CDATA[#[Override]]]></code>
3374+
</ImmutableDependency>
3375+
</file>
3376+
<file src="tests/Config/ConfigTest.php">
3377+
<ImmutableDependency>
3378+
<code><![CDATA[#[Override]]]></code>
3379+
</ImmutableDependency>
3380+
</file>
34123381
<file src="tests/Config/Plugin/Hook/AfterAnalysis.php">
34133382
<UnusedMethodCall>
34143383
<code><![CDATA[toArray]]></code>
@@ -3546,6 +3515,9 @@
35463515
</PossiblyFalseArgument>
35473516
</file>
35483517
<file src="tests/TestConfig.php">
3518+
<ImmutableDependency>
3519+
<code><![CDATA[public function __construct()]]></code>
3520+
</ImmutableDependency>
35493521
<InvalidExtendClass>
35503522
<code><![CDATA[Config]]></code>
35513523
</InvalidExtendClass>
@@ -3554,10 +3526,6 @@
35543526
<code><![CDATA[#[Override]]]></code>
35553527
<code><![CDATA[public function __construct()]]></code>
35563528
</MethodSignatureMismatch>
3557-
<MutableDependency>
3558-
<code><![CDATA[#[Override]]]></code>
3559-
<code><![CDATA[public function __construct()]]></code>
3560-
</MutableDependency>
35613529
</file>
35623530
<file src="tests/TypeParseTest.php">
35633531
<ImplicitToStringCast>

src/Psalm/Config.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1882,16 +1882,25 @@ public function reportIssueInFile(string $issue_type, string $file_path): bool
18821882
return true;
18831883
}
18841884

1885+
/**
1886+
* @psalm-mutation-free
1887+
*/
18851888
public function isInProjectDirs(string $file_path): bool
18861889
{
18871890
return $this->project_files && $this->project_files->allows($file_path);
18881891
}
18891892

1893+
/**
1894+
* @psalm-mutation-free
1895+
*/
18901896
public function isInExtraDirs(string $file_path): bool
18911897
{
18921898
return $this->extra_files && $this->extra_files->allows($file_path);
18931899
}
18941900

1901+
/**
1902+
* @psalm-mutation-free
1903+
*/
18951904
public function mustBeIgnored(string $file_path): bool
18961905
{
18971906
return $this->project_files && $this->project_files->forbids($file_path);
@@ -2232,6 +2241,9 @@ public function getExtraDirectories(): array
22322241
return $this->extra_files->getDirectories();
22332242
}
22342243

2244+
/**
2245+
* @psalm-mutation-free
2246+
*/
22352247
public function reportTypeStatsForFile(string $file_path): bool
22362248
{
22372249
return $this->project_files

src/Psalm/Config/ProjectFileFilter.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ public static function loadFromXMLElement(
3535
return $filter;
3636
}
3737

38+
/**
39+
* @psalm-mutation-free
40+
*/
3841
#[Override]
3942
public function allows(string $file_name, bool $case_sensitive = false): bool
4043
{
@@ -47,6 +50,9 @@ public function allows(string $file_name, bool $case_sensitive = false): bool
4750
return parent::allows($file_name, $case_sensitive);
4851
}
4952

53+
/**
54+
* @psalm-mutation-free
55+
*/
5056
public function forbids(string $file_name, bool $case_sensitive = false): bool
5157
{
5258
if ($this->inclusive && $this->file_filter) {

0 commit comments

Comments
 (0)