Skip to content

Commit dc0e55d

Browse files
authored
Merge branch 'pass_context' into copilot/fix-failing-tests
2 parents 719f086 + 02de2b6 commit dc0e55d

File tree

806 files changed

+11589
-3943
lines changed

Some content is hidden

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

806 files changed

+11589
-3943
lines changed

UPGRADING.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,78 @@
1+
# Upgrading from Psalm 6 to Psalm 7
2+
3+
## Changed
4+
5+
- [BC] Property Psalm\Type\Atomic\TCallable#$is_pure was removed and replaced with an `$allowed_mutations` property
6+
- [BC] Property Psalm\Type\Atomic\TClosure#$is_pure was removed and replaced with an `$allowed_mutations` property
7+
- [BC] Property Psalm\Type\Atomic\CallableTrait#$is_pure was removed and replaced with an `$allowed_mutations` property
8+
- [BC] Method Psalm\Type\Atomic\TCallable#setIsPure() was removed and replaced with a `setAllowedMutations` method
9+
- [BC] Method Psalm\Type\Atomic\TClosure#setIsPure() was removed and replaced with a `setAllowedMutations` method
10+
- [BC] Method Psalm\Type\Atomic\CallableTrait#setIsPure() was removed and replaced with a `setAllowedMutations` method
11+
- [BC] Property Psalm\Type\Atomic\TCallable#$value changed default value from NULL to 'callable'
12+
- [BC] The $value parameter of Psalm\Type\Atomic\TCallable#__construct() was removed
13+
- [BC] The $value parameter of Psalm\Type\Atomic\TClosure#__construct() was removed
14+
- [BC] Property $pure of `Psalm\Context`, `Psalm\Storage\FunctionLikeStorage` was removed and replaced with an `isPure()` method, reading from the new `$allowed_mutations` property.
15+
- [BC] Property $mutation_free of `Psalm\Context`, `Psalm\Storage\FunctionLikeStorage`, `Psalm\Storage\ClassLikeStorage` was removed and replaced with an `isMutationFree()` method, reading from the new `$allowed_mutations` property.
16+
- [BC] Property $external_mutation_free of of `Psalm\Context`, `Psalm\Storage\FunctionLikeStorage`, `Psalm\Storage\ClassLikeStorage` was removed and replaced with an `isExternalMutationFree()` method, reading from the new `$allowed_mutations` property.
17+
- [BC] Method signalMutationOnlyInferred() was added to interface Psalm\StatementsSource
18+
- [BC] Method signalMutation() was added to interface Psalm\StatementsSource
19+
20+
- [BC] Taints are now *internally* represented by a bitmap (an integer), instead of an array of strings. Users can still use the usual string taint identifiers (including custom ones, which will be automatically registered by Psalm), but internally, the type of `Psalm\Type\TaintKind` taint types is now an integer.
21+
22+
- [BC] The maximum number of usable taint *types* (including both native taints and custom taints) is now equal to 32 on 32-bit systems and 64 on 64-bit systems: this should be enough for the vast majority of usecases, if more taint types are needed, consider merging some taint types or using some native taint types.
23+
24+
- [BC] `Psalm\Plugin\EventHandler\AddTaintsInterface::addTaints` and `Psalm\Plugin\EventHandler\RemoveTaintsInterface::removeTaints` now must return an integer taint instead of an array of strings (see the new [taint documentation](https://psalm.dev/docs/security_analysis/custom_taint_sources/) for more info).
25+
26+
- [BC] The type of the `$taints` parameter of `Psalm\Codebase::addTaintSource` and `Psalm\Codebase::addTaintSink` was changed to an integer
27+
28+
- [BC] Type of property `Psalm\Storage\FunctionLikeParameter::$sinks` changed from `array|null` to `int`
29+
30+
- [BC] Type of property `Psalm\Storage\FunctionLikeStorage::$taint_source_types` changed from `array` to `int`
31+
32+
- [BC] Type of property `Psalm\Storage\FunctionLikeStorage::$added_taints` changed from `array` to `int`
33+
34+
- [BC] Type of property `Psalm\Storage\FunctionLikeStorage::$removed_taints` changed from `array` to `int`
35+
36+
- [BC] The `startScanningFiles`, `startAnalyzingFiles`, `startAlteringFiles` of `Psalm\Progress\Progress` and subclasses were removed and replaced with a new `startPhase` method, taking a `Psalm\Progress\Phase` enum case.
37+
38+
- [BC] The `start` method was removed, use `expand`, instead; the progress is reset to 0 when changing the current phase.
39+
40+
- [BC] Method `doesTerminalSupportUtf8` of class `Psalm\Progress\Progress` became final
41+
42+
- [BC] Method debug() of class Psalm\Progress\Progress changed from concrete to abstract
43+
44+
- [BC] Method alterFileDone() of class Psalm\Progress\Progress changed from concrete to abstract
45+
46+
- [BC] Method expand() of class Psalm\Progress\Progress changed from concrete to abstract
47+
48+
- [BC] Method taskDone() of class Psalm\Progress\Progress changed from concrete to abstract
49+
50+
- [BC] Method finish() of class Psalm\Progress\Progress changed from concrete to abstract
51+
52+
- [BC] The return type of Psalm\Type::getListAtomic() changed from Psalm\Type\Atomic\TKeyedArray to the non-covariant Psalm\Type\Atomic\TKeyedArray|Psalm\Type\Atomic\TArray
53+
54+
- [BC] The return type of Psalm\Type::getListAtomic() changed from Psalm\Type\Atomic\TKeyedArray to Psalm\Type\Atomic\TKeyedArray|Psalm\Type\Atomic\TArray
55+
56+
- [BC] The return type of Psalm\Type::getNonEmptyListAtomic() changed from Psalm\Type\Atomic\TKeyedArray to the non-covariant Psalm\Type\Atomic\TKeyedArray|Psalm\Type\Atomic\TArray
57+
58+
- [BC] The return type of Psalm\Type::getNonEmptyListAtomic() changed from Psalm\Type\Atomic\TKeyedArray to Psalm\Type\Atomic\TKeyedArray|Psalm\Type\Atomic\TArray
59+
60+
- [BC] Class Psalm\Type\Atomic\TKeyedArray became final
61+
62+
- [BC] Class Psalm\Type\Atomic\TKeyedArray can only be created using the new `make` or `makeCallable` factory methods, the constructor was rendered private.
63+
64+
- [BC] Class Psalm\Type\Atomic\TCallableKeyedArray has been deleted, and replaced with a new `is_callable` flag in Psalm\Type\Atomic\TKeyedArray
65+
66+
- [BC] Class Psalm\Type\Atomic\TCallableInterface has been deleted, use `\Psalm\Type\Atomic::isCallableType()` instead
67+
68+
## Removed
69+
70+
- [BC] Constant Psalm\Type\Atomic\TKeyedArray::NAME_ARRAY was removed
71+
72+
- [BC] Constant Psalm\Type\Atomic\TKeyedArray::NAME_LIST was removed
73+
74+
- [BC] Psalm\Type\Atomic\TKeyedArray#__construct() was made private
75+
176
# Upgrading from Psalm 5 to Psalm 6
277
## Changed
378

bin/ci/test-with-real-projects.sh

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ set -x
66
SCRIPT_DIR="$(dirname "${BASH_SOURCE[0]}")"
77
SCRIPT_DIR="$(realpath "$SCRIPT_DIR")"
88
PSALM="$(readlink -f "$SCRIPT_DIR/../../psalm")"
9-
PSALM_PHAR="$(readlink -f "$SCRIPT_DIR/../../build/psalm.phar")"
9+
PSALM_PHAR="$(readlink -f "$SCRIPT_DIR/../../build/psalm.phar" || echo "")"
1010

1111
if [ ! -f "$PSALM_PHAR" ]; then PSALM_PHAR="$PSALM"; fi
1212

13+
which gsed > /dev/null && sed=gsed || sed=sed
14+
1315
rm -Rf /tmp/testing-with-real-projects
1416
mkdir -p /tmp/testing-with-real-projects
1517
cd /tmp/testing-with-real-projects
@@ -54,7 +56,7 @@ psl)
5456
composer install --ignore-platform-reqs
5557
# Avoid conflicts with old psalm when running phar tests
5658
rm -rf vendor/vimeo/psalm
57-
sed 's/ErrorOutputBehavior::Packed, ErrorOutputBehavior::Discard/ErrorOutputBehavior::Discard/g' -i src/Psl/Shell/execute.php
59+
$sed 's/ErrorOutputBehavior::Packed, ErrorOutputBehavior::Discard/ErrorOutputBehavior::Discard/g' -i src/Psl/Shell/execute.php
5860
"$PSALM_PHAR" --monochrome -c config/psalm.xml --set-baseline=psalm-baseline.xml || FAIL=$?
5961
"$PSALM_PHAR" --monochrome -c config/psalm-static-analysis.xml tests/static-analysis --set-baseline=psalm-baseline-static-analysis.xml || FAIL=$?
6062
;;

bin/docker/Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ RUN set -eux; \
5757
# Enable linker optimization (this sorts the hash buckets to improve cache locality, and is non-default)
5858
# https://github.com/docker-library/php/issues/272
5959
# -D_LARGEFILE_SOURCE and -D_FILE_OFFSET_BITS=64 (https://www.php.net/manual/en/intro.filesystem.php)
60-
ENV PHP_CFLAGS="-fstack-protector-strong -fpic -fpie -O3 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64"
60+
ENV PHP_CFLAGS="-fstack-protector-strong -fpic -fpie -O3 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -g"
6161
ENV PHP_CPPFLAGS="$PHP_CFLAGS"
6262
ENV PHP_LDFLAGS="-Wl,-O1 -pie"
6363

@@ -185,6 +185,7 @@ RUN set -eux; \
185185
php --version
186186

187187
COPY bin/docker/docker-php-* /usr/local/bin/
188+
ADD https://raw.githubusercontent.com/php/php-src/refs/heads/PHP-8.4/.gdbinit /root/.gdbinit
188189

189190
# This line invalidates cache when master branch changes
190191
ADD https://github.com/vimeo/psalm/commits/master.atom /dev/null

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@
118118
"lint": "@php parallel-lint ./src ./tests",
119119
"phpunit": [
120120
"Composer\\Config::disableProcessTimeout",
121-
"paratest -f --runner=WrapperRunner"
121+
"@php paratest -f --runner=WrapperRunner"
122122
],
123123
"phpunit-std": [
124124
"Composer\\Config::disableProcessTimeout",

config.xsd

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@
8181
<xs:attribute name="skipChecksOnUnresolvableIncludes" type="xs:boolean" default="false" />
8282
<xs:attribute name="sealAllMethods" type="xs:boolean" default="true" />
8383
<xs:attribute name="sealAllProperties" type="xs:boolean" default="true" />
84-
<xs:attribute name="runTaintAnalysis" type="xs:boolean" default="false" />
84+
<xs:attribute name="runTaintAnalysis" type="xs:boolean" default="true" />
8585
<xs:attribute name="usePhpStormMetaPath" type="xs:boolean" default="true" />
8686
<xs:attribute name="allowInternalNamedArgumentCalls" type="xs:boolean" default="true" />
8787
<xs:attribute name="allowNamedArgumentCalls" type="xs:boolean" default="true" />
@@ -264,6 +264,7 @@
264264
<xs:element name="ImplicitToStringCast" type="IssueHandlerType" minOccurs="0" />
265265
<xs:element name="ImpureByReferenceAssignment" type="IssueHandlerType" minOccurs="0" />
266266
<xs:element name="ImpureFunctionCall" type="IssueHandlerType" minOccurs="0" />
267+
<xs:element name="ImpureGlobalVariable" type="IssueHandlerType" minOccurs="0" />
267268
<xs:element name="ImpureMethodCall" type="IssueHandlerType" minOccurs="0" />
268269
<xs:element name="ImpurePropertyAssignment" type="IssueHandlerType" minOccurs="0" />
269270
<xs:element name="ImpurePropertyFetch" type="IssueHandlerType" minOccurs="0" />
@@ -342,10 +343,13 @@
342343
<xs:element name="MissingDependency" type="IssueHandlerType" minOccurs="0" />
343344
<xs:element name="MissingDocblockType" type="IssueHandlerType" minOccurs="0" />
344345
<xs:element name="MissingFile" type="IssueHandlerType" minOccurs="0" />
346+
<xs:element name="MissingInterfaceImmutableAnnotation" type="IssueHandlerType" minOccurs="0" />
345347
<xs:element name="MissingImmutableAnnotation" type="IssueHandlerType" minOccurs="0" />
346348
<xs:element name="MissingOverrideAttribute" type="IssueHandlerType" minOccurs="0" />
347349
<xs:element name="MissingParamType" type="IssueHandlerType" minOccurs="0" />
348350
<xs:element name="MissingPropertyType" type="PropertyIssueHandlerType" minOccurs="0" />
351+
<xs:element name="MissingAbstractPureAnnotation" type="IssueHandlerType" minOccurs="0" />
352+
<xs:element name="MissingPureAnnotation" type="IssueHandlerType" minOccurs="0" />
349353
<xs:element name="MissingReturnType" type="IssueHandlerType" minOccurs="0" />
350354
<xs:element name="MissingTemplateParam" type="IssueHandlerType" minOccurs="0" />
351355
<xs:element name="MissingThrowsDocblock" type="ClassIssueHandlerType" minOccurs="0" />
@@ -368,6 +372,7 @@
368372
<xs:element name="MixedStringOffsetAssignment" type="IssueHandlerType" minOccurs="0" />
369373
<xs:element name="MoreSpecificImplementedParamType" type="IssueHandlerType" minOccurs="0" />
370374
<xs:element name="MoreSpecificReturnType" type="IssueHandlerType" minOccurs="0" />
375+
<xs:element name="ImmutableDependency" type="PropertyIssueHandlerType" minOccurs="0" />
371376
<xs:element name="MutableDependency" type="PropertyIssueHandlerType" minOccurs="0" />
372377
<xs:element name="NamedArgumentNotAllowed" type="ArgumentIssueHandlerType" minOccurs="0" />
373378
<xs:element name="NoEnumProperties" type="ClassIssueHandlerType" minOccurs="0" />

dictionaries/InternalTaintSinkMap.php

Lines changed: 53 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -5,59 +5,59 @@
55
// This maps internal function names to sink types that we don’t want to end up there
66

77
/**
8-
* @var non-empty-array<string, non-empty-list<list<TaintKind::*>>>
8+
* @var non-empty-array<string, non-empty-list<int-mask-of<TaintKind::*>>>
99
*/
1010
return [
11-
'exec' => [['shell']],
12-
'create_function' => [[], ['eval']],
13-
'file_get_contents' => [['file']],
14-
'file_put_contents' => [['file']],
15-
'fopen' => [['file']],
16-
'unlink' => [['file']],
17-
'copy' => [['file'], ['file']],
18-
'file' => [['file']],
19-
'link' => [['file'], ['file']],
20-
'mkdir' => [['file']],
21-
'move_uploaded_file' => [['file'], ['file']],
22-
'parse_ini_file' => [['file']],
23-
'chown' => [['file']],
24-
'lchown' => [['file']],
25-
'readfile' => [['file']],
26-
'rename' => [['file'], ['file']],
27-
'rmdir' => [['file']],
28-
'header' => [['header']],
29-
'symlink' => [['file']],
30-
'tempnam' => [['file']],
31-
'igbinary_unserialize' => [['unserialize']],
32-
'ldap_search' => [[], ['ldap'], ['ldap']],
33-
'mysqli_query' => [[], ['sql']],
34-
'mysqli::query' => [['sql']],
35-
'mysqli_real_query' => [[], ['sql']],
36-
'mysqli::real_query' => [['sql']],
37-
'mysqli_multi_query' => [[], ['sql']],
38-
'mysqli::multi_query' => [['sql']],
39-
'mysqli_prepare' => [[], ['sql']],
40-
'mysqli::prepare' => [['sql']],
41-
'mysqli_stmt::__construct' => [[], ['sql']],
42-
'mysqli_stmt_prepare' => [[], ['sql']],
43-
'mysqli_stmt::prepare' => [['sql']],
44-
'passthru' => [['shell']],
45-
'pcntl_exec' => [['shell']],
46-
'pg_exec' => [[], ['sql']],
47-
'pg_prepare' => [[], [], ['sql']],
48-
'pg_put_line' => [[], ['sql']],
49-
'pg_query' => [[], ['sql']],
50-
'pg_query_params' => [[], ['sql']],
51-
'pg_send_prepare' => [[], [], ['sql']],
52-
'pg_send_query' => [[], ['sql']],
53-
'pg_send_query_params' => [[], ['sql'], []],
54-
'setcookie' => [['cookie'], ['cookie']],
55-
'shell_exec' => [['shell']],
56-
'system' => [['shell']],
57-
'unserialize' => [['unserialize']],
58-
'popen' => [['shell']],
59-
'proc_open' => [['shell']],
60-
'curl_init' => [['ssrf']],
61-
'curl_setopt' => [[], [], ['ssrf']],
62-
'getimagesize' => [['ssrf']],
11+
'exec' => [TaintKind::INPUT_SHELL],
12+
'create_function' => [0, TaintKind::INPUT_EVAL],
13+
'file_get_contents' => [TaintKind::INPUT_FILE|TaintKind::INPUT_SSRF],
14+
'file_put_contents' => [TaintKind::INPUT_FILE],
15+
'fopen' => [TaintKind::INPUT_FILE],
16+
'unlink' => [TaintKind::INPUT_FILE],
17+
'copy' => [TaintKind::INPUT_FILE|TaintKind::INPUT_SSRF, TaintKind::INPUT_FILE],
18+
'file' => [TaintKind::INPUT_FILE],
19+
'link' => [TaintKind::INPUT_FILE, TaintKind::INPUT_FILE],
20+
'mkdir' => [TaintKind::INPUT_FILE],
21+
'move_uploaded_file' => [TaintKind::INPUT_FILE, TaintKind::INPUT_FILE],
22+
'parse_ini_file' => [TaintKind::INPUT_FILE],
23+
'chown' => [TaintKind::INPUT_FILE],
24+
'lchown' => [TaintKind::INPUT_FILE],
25+
'readfile' => [TaintKind::INPUT_FILE],
26+
'rename' => [TaintKind::INPUT_FILE, TaintKind::INPUT_FILE],
27+
'rmdir' => [TaintKind::INPUT_FILE],
28+
'header' => [TaintKind::INPUT_HEADER],
29+
'symlink' => [TaintKind::INPUT_FILE],
30+
'tempnam' => [TaintKind::INPUT_FILE],
31+
'igbinary_unserialize' => [TaintKind::INPUT_UNSERIALIZE],
32+
'ldap_search' => [0, TaintKind::INPUT_LDAP, TaintKind::INPUT_LDAP],
33+
'mysqli_query' => [0, TaintKind::INPUT_SQL],
34+
'mysqli::query' => [TaintKind::INPUT_SQL],
35+
'mysqli_real_query' => [0, TaintKind::INPUT_SQL],
36+
'mysqli::real_query' => [TaintKind::INPUT_SQL],
37+
'mysqli_multi_query' => [0, TaintKind::INPUT_SQL],
38+
'mysqli::multi_query' => [TaintKind::INPUT_SQL],
39+
'mysqli_prepare' => [0, TaintKind::INPUT_SQL],
40+
'mysqli::prepare' => [TaintKind::INPUT_SQL],
41+
'mysqli_stmt::__construct' => [0, TaintKind::INPUT_SQL],
42+
'mysqli_stmt_prepare' => [0, TaintKind::INPUT_SQL],
43+
'mysqli_stmt::prepare' => [TaintKind::INPUT_SQL],
44+
'passthru' => [TaintKind::INPUT_SHELL],
45+
'pcntl_exec' => [TaintKind::INPUT_SHELL],
46+
'pg_exec' => [0, TaintKind::INPUT_SQL],
47+
'pg_prepare' => [0, 0, TaintKind::INPUT_SQL],
48+
'pg_put_line' => [0, TaintKind::INPUT_SQL],
49+
'pg_query' => [0, TaintKind::INPUT_SQL],
50+
'pg_query_params' => [0, TaintKind::INPUT_SQL],
51+
'pg_send_prepare' => [0, 0, TaintKind::INPUT_SQL],
52+
'pg_send_query' => [0, TaintKind::INPUT_SQL],
53+
'pg_send_query_params' => [0, TaintKind::INPUT_SQL, 0],
54+
'setcookie' => [TaintKind::INPUT_COOKIE, TaintKind::INPUT_COOKIE],
55+
'shell_exec' => [TaintKind::INPUT_SHELL],
56+
'system' => [TaintKind::INPUT_SHELL],
57+
'unserialize' => [TaintKind::INPUT_UNSERIALIZE],
58+
'popen' => [TaintKind::INPUT_SHELL],
59+
'proc_open' => [TaintKind::INPUT_SHELL],
60+
'curl_init' => [TaintKind::INPUT_SSRF],
61+
'curl_setopt' => [0, 0, TaintKind::INPUT_SSRF],
62+
'getimagesize' => [TaintKind::INPUT_SSRF],
6363
];

docs/annotating_code/supported_annotations.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,9 @@ class D {
370370

371371
### `@psalm-external-mutation-free`
372372

373-
Used to annotate a class method that does not mutate state externally of the class's scope.
373+
Used to annotate a class method that does not mutate state externally of the class's scope.
374+
375+
Can also be used on classes to propagate the same annotation to all of its methods.
374376

375377
```php
376378
<?php
@@ -441,9 +443,15 @@ $anonymous = new /** @psalm-immutable */ class extends Foo
441443
};
442444
```
443445

446+
### `@psalm-mutable`
447+
448+
Used to annotate a class where at least one property is mutable: this is the default behavior, but it can be explicitly marked for clarity.
449+
444450
### `@psalm-pure`
445451

446-
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.
447455

448456
```php
449457
<?php
@@ -484,6 +492,15 @@ foo(
484492
);
485493
```
486494

495+
### `@psalm-impure`
496+
497+
Used to annotate a function that is not pure (nor mutation free, nor externally mutation free): this is the default, but Psalm always asks to explicitly annotate **abstract** methods with one of these four annotations:
498+
499+
- `@psalm-pure`
500+
- `@psalm-mutation-free`
501+
- `@psalm-external-mutation-free`
502+
- `@psalm-impure`
503+
487504
### `@psalm-allow-private-mutation`
488505

489506
Used to annotate readonly properties that can be mutated in a private context. With this, public properties can be read from another class but only be mutated within a method of its own class.

docs/annotating_code/type_syntax/callable_types.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,22 @@ echo $adder(true);
2828

2929
## Pure callables
3030

31-
For situations where the `callable` needs to be pure or immutable, the subtypes `pure-callable` and `pure-Closure` are also available.
31+
For situations where the `callable` needs to be pure, mutation-free or externally mutation-free, the following subtypes are available:
3232

33-
This can be useful when the `callable` is used in a function marked with `@psalm-pure` or `@psalm-mutation-free`, for example:
33+
* Pure (no mutations or even read property accesses allowed), equivalent to marking functions or methods with `@psalm-pure`
34+
* `pure-callable`
35+
* `pure-Closure`
36+
* Mutation-free (only internal property reads on `$this` are allowed for methods), equivalent to marking functions or methods with `@psalm-mutation-free`
37+
* `self-accessing-callable`
38+
* `self-accessing-Closure`
39+
* Externally mutation-free (internal property reads and writes on `$this` and `self` are allowed for methods), equivalent to marking functions or methods with `@psalm-external-mutation-free`
40+
* `self-mutating-callable`
41+
* `self-mutating-Closure`
42+
* Impure (the default behavior, all mutations allowed); functions or methods can also be explicitly marked as impure with `@psalm-impure`
43+
* `impure-callable` (an alias to `callable`)
44+
* `impure-Closure` (an alias to `Closure`)
45+
46+
This can be useful when the `callable` is used in a function marked with `@psalm-pure` or `@psalm-mutation-free` or `@psalm-external-mutation-free`, for example:
3447

3548
```php
3649
<?php

docs/running_psalm/command_line_usage.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ Currently, Shepherd tracks type coverage (the percentage of types Psalm can infe
3333

3434
## Running Psalm faster
3535

36-
Psalm has a couple of command-line options that will result in faster builds:
36+
To run Psalm up to 50% faster, use the [official docker image](https://psalm.dev/docs/running_psalm/installation/#docker-image).
37+
38+
Psalm also has a couple of command-line options that will result in faster builds:
3739

3840
- `--threads=[n]` to run Psalm’s analysis in a number of threads
3941
- `--diff` which only checks files you’ve updated since the last run (and their dependents).

0 commit comments

Comments
 (0)