Install psalm as dev, sync scripts updates

This commit is contained in:
Clemens Schwaighofer
2023-03-09 16:27:10 +09:00
parent 6bec59e387
commit feba79a2e8
2099 changed files with 283333 additions and 32 deletions

72
vendor/vimeo/psalm/docs/README.md vendored Normal file
View File

@@ -0,0 +1,72 @@
# About Psalm
Psalm is a static analysis tool that attempts to dig into your program and find as many type-related bugs as possible.
It has a few features that go further than other similar tools:
- **Mixed type warnings**<br />
If Psalm cannot infer a type for an expression then it uses a `mixed` placeholder type. `mixed` types can sometimes mask bugs, so keeping track of them helps you avoid a number of common pitfalls.
- **Intelligent logic checks**<br />
Psalm keeps track of logical assertions made about your code, so `if ($a && $a) {}` and `if ($a && !$a) {}` are both treated as issues. Psalm also keeps track of logical assertions made in prior code paths, preventing issues like `if ($a) {} elseif ($a) {}`.
- **Property initialisation checks**<br />
Psalm checks that all properties of a given object have values after the constructor is called.
- **Taint analysis**<br />
Psalm can [detect security vulnerabilities](https://psalm.dev/articles/detect-security-vulnerabilities-with-psalm) in your code.
- **Language Server**<br />
Psalm has a Language Server thats [compatible with a range of different IDEs](https://psalm.dev/docs/running_psalm/language_server/).
- **Automatic fixes**<br />
Psalm can [fix many of the issues it finds automatically](https://psalm.dev/docs/manipulating_code/fixing/).
- **Automatic refactoring**<br />
Psalm can also [perform simple refactors](https://psalm.dev/docs/manipulating_code/refactoring/) from the command line.
## Example output
Given a file `implode_strings.php`:
```php
<?php
$a = ['foo', 'bar'];
echo implode($a, ' ');
```
```bash
> ./vendor/bin/psalm implode_strings.php
ERROR: InvalidArgument - somefile.php:3:14 - Argument 1 of implode expects `string`, `array` provided (see https://psalm.dev/004)
```
## Inspirations
There are two main inspirations for Psalm:
- Etsy's [Phan](https://github.com/etsy/phan), which uses nikic's [php-ast](https://github.com/nikic/php-ast) extension to create an abstract syntax tree
- Facebook's [Hack](http://hacklang.org/), a PHP-like language that supports many advanced typing features natively, so docblocks aren't necessary.
## Index
- Running Psalm:
- [Installation](running_psalm/installation.md)
- [Configuration](running_psalm/configuration.md)
- Plugins
- [Using plugins](running_psalm/plugins/using_plugins.md)
- [Authoring plugins](running_psalm/plugins/authoring_plugins.md)
- [How Psalm represents types](running_psalm/plugins/plugins_type_system.md)
- [Command line usage](running_psalm/command_line_usage.md)
- [IDE support](running_psalm/language_server.md)
- Handling errors:
- [Dealing with code issues](running_psalm/dealing_with_code_issues.md)
- [Issue Types](running_psalm/issues.md)
- [Checking non-PHP files](running_psalm/checking_non_php_files.md)
- Annotating code:
- [Typing in Psalm](annotating_code/typing_in_psalm.md)
- [Supported Annotations](annotating_code/supported_annotations.md)
- [Template Annotations](annotating_code/templated_annotations.md)
- Manipulating code:
- [Fixing code](manipulating_code/fixing.md)
- [Refactoring code](manipulating_code/refactoring.md)

View File

@@ -0,0 +1,223 @@
# Adding assertions
Psalm has five docblock annotations that allow you to specify that a function verifies facts about variables and properties:
- `@psalm-assert` (used when throwing an exception)
- `@psalm-assert-if-true`/`@psalm-assert-if-false` (used when returning a `bool`)
- `@psalm-if-this-is`/`@psalm-this-out` (used when calling a method)
A list of acceptable assertions [can be found here](assertion_syntax.md).
## Examples
If you have a class that verified its input is an array of strings, you can make that clear to Psalm:
```php
<?php
/** @psalm-assert string[] $arr */
function validateStringArray(array $arr) : void {
foreach ($arr as $s) {
if (!is_string($s)) {
throw new UnexpectedValueException('Invalid value ' . gettype($s));
}
}
}
```
This enables you to call the `validateStringArray` function on some data and have Psalm understand that the given data *must* be an array of strings:
```php
<?php
function takesString(string $s) : void {}
function takesInt(int $s) : void {}
function takesArray(array $arr) : void {
takesInt($arr[0]); // this is fine
validateStringArray($arr);
takesInt($arr[0]); // this is an error
foreach ($arr as $a) {
takesString($a); // this is fine
}
}
```
Similarly, `@psalm-assert-if-true` and `@psalm-assert-if-false` will filter input if the function/method returns `true` and `false` respectively:
```php
<?php
class A {
public function isValid() : bool {
return (bool) rand(0, 1);
}
}
class B extends A {
public function bar() : void {}
}
/**
* @psalm-assert-if-true B $a
*/
function isValidB(A $a) : bool {
return $a instanceof B && $a->isValid();
}
/**
* @psalm-assert-if-false B $a
*/
function isInvalidB(A $a) : bool {
return !$a instanceof B || !$a->isValid();
}
function takesA(A $a) : void {
if (isValidB($a)) {
$a->bar();
}
if (isInvalidB($a)) {
// do something
} else {
$a->bar();
}
$a->bar(); //error
}
```
As well as getting Psalm to understand that the given data must be a certain type, you can also show that a variable must be not null:
```php
<?php
/**
* @psalm-assert !null $value
*/
function assertNotNull($value): void {
// Some check that will mean the method will only complete if $value is not null.
}
```
And you can check on null values:
```php
<?php
/**
* @psalm-assert-if-true null $value
*/
function isNull($value): bool {
return ($value === null);
}
```
### Asserting return values of methods
You can also use the `@psalm-assert-if-true` and `@psalm-assert-if-false` annotations to assert return values of
methods inside classes. As you can see, Psalm even allows you to specify multiple annotations in the same DocBlock:
```php
<?php
class Result {
/**
* @var ?Exception
*/
private $exception;
/**
* @psalm-assert-if-true Exception $this->exception
* @psalm-assert-if-true Exception $this->getException()
*/
public function hasException(): bool {
return $this->exception !== null;
}
public function getException(): ?Exception {
return $this->exception;
}
public function foo(): void {
if( $this->hasException() ) {
// Psalm now knows that $this->exception is an instance of Exception
echo $this->exception->getMessage();
}
}
}
$result = new Result;
if( $result->hasException() ) {
// Psalm now knows that $result->getException() will return an instance of Exception
echo $result->getException()->getMessage();
}
```
Please note that the example above only works if you enable [method call memoization](https://psalm.dev/docs/running_psalm/configuration/#memoizemethodcallresults)
in the config file or annotate the class as [immutable](https://psalm.dev/docs/annotating_code/supported_annotations/#psalm-immutable).
You can use `@psalm-this-out` to change the template arguments of a method after a method call, to reflect changes to the object's internal state.
You can also make assertions on the object's template arguments using `@psalm-if-this-is`.
```php
<?php
/**
* @template T
*/
class a {
/**
* @var list<T>
*/
private array $data;
/**
* @param T $data
*/
public function __construct($data) {
$this->data = [$data];
}
/**
* @template NewT
*
* @param NewT $data
*
* @psalm-this-out self<T|NewT>
*
* @return void
*/
public function addData($data) {
/** @var self<T|NewT> $this */
$this->data []= $data;
}
/**
* @template NewT
*
* @param NewT $data
*
* @psalm-this-out self<NewT>
*
* @return void
*/
public function setData($data) {
/** @var self<NewT> $this */
$this->data = [$data];
}
/**
* @psalm-if-this-is a<int>
*/
public function test(): void {
}
}
$i = new a(123);
// OK - $i is a<123>
$i->test();
$i->addData(321);
// OK - $i is a<123|321>
$i->test();
$i->setData("test");
// IfThisIsMismatch - Class is not a<int> as required by psalm-if-this-is
$i->test();
```

View File

@@ -0,0 +1,127 @@
# Assertion syntax
Psalms [assertion annotation](adding_assertions.md) supports a number of different assertions.
Psalm assertions are of the form
`@psalm-assert(-if-true|-if-false)? (Assertion) (Variable or Property)`
`Assertion` here can have many forms:
## Regular assertions
### is_xxx assertions
Most `is_xxx` PHP functions have companion assertions e.g. `int` for `is_int`. Here's the full list:
- `int`
- `float`
- `string`
- `bool`
- `scalar`
- `callable`
- `countable`
- `array`
- `iterable`
- `numeric`
- `resource`
- `object`
- `null`
So a custom version `is_int` could be annotated in Psalm as
```php
<?php
/** @psalm-assert-if-true int $x */
function custom_is_int($x) {
return is_int($x);
}
```
### Object type assertions
Any class can be used as an assertion e.g.
`@psalm-assert SomeObjectType $foo`
### Generic assertions
Generic type parameters can also now be asserted e.g.
`@psalm-assert array<int, string> $foo`
## Negated assertions
Any assertion above can be negated:
This asserts that `$foo` is not an `int`:
```php
<?php
/** @psalm-assert !int $foo */
```
This asserts that `$bar` is not an object of type `SomeObjectType`:
```php
<?php
/** @psalm-assert !SomeObjectType $bar */
```
## Bool assertions
This asserts that `$bar` is `true`:
```php
<?php
/** @psalm-assert true $bar */
```
This asserts that `$bar` is not `false`:
```php
<?php
/** @psalm-assert !false $bar */
```
## Equality assertions
Psalm also supports the equivalent of `assert($some_int === $other_int)` in the form
```php
<?php
/** @psalm-assert =int $some_int */
```
There are two differences between the above assertion and
```php
<?php
/** @psalm-assert int $some_int */
```
Firstly, the negation of `=int` has no meaning:
```php
<?php
/** @psalm-assert-if-true =int $x */
function equalsFive($x) {
return is_int($x) && $x === 5;
}
function foo($y) : void {
if (equalsFive($y)) {
// $y is definitely an int
} else {
// $y might be an int, but it might not
}
}
function bar($y) : void {
if (is_int($y)) {
// $y is definitely an int
} else {
// $y is definitely not an int
}
}
```
Secondly, calling `equalsFive($some_int)` is not a `RedundantCondition` in Psalm, whereas calling `is_int($some_int)` is.

View File

@@ -0,0 +1,664 @@
# Supported docblock annotations
Psalm supports a wide range of docblock annotations.
## PHPDoc tags
Psalm uses the following PHPDoc tags to understand your code:
- [`@var`](https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/var.html)
Used for specifying the types of properties and variables@
- [`@return`](https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/return.html)
Used for specifying the return types of functions, methods and closures
- [`@param`](https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/param.html)
Used for specifying types of parameters passed to functions, methods and closures
- [`@property`](https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/property.html)
Used to specify what properties can be accessed on an object that uses `__get` and `__set`
- [`@property-read`](https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/property-read.html)
Used to specify what properties can be read on object that uses `__get`
- [`@property-write`](https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/property-write.html)
Used to specify what properties can be written on object that uses `__set`
- [`@method`](https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/method.html)
Used to specify which magic methods are available on object that uses `__call`.
- [`@deprecated`](https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/deprecated.html)
Used to mark functions, methods, classes and interfaces as being deprecated
- [`@internal`](https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/internal.html)
used to mark classes, functions and properties that are internal to an application or library.
### Off-label usage of the `@var` tag
The `@var` tag is supposed to only be used for properties. Psalm, taking a lead from PHPStorm and other static analysis tools, allows its use inline in the form `@var Type [VariableReference]`.
If `VariableReference` is provided, it should be of the form `$variable` or `$variable->property`. If used above an assignment, Psalm checks whether the `VariableReference` matches the variable being assigned. If they differ, Psalm will assign the `Type` to `VariableReference` and use it in the expression below.
If no `VariableReference` is given, the annotation tells Psalm that the right-hand side of the expression, whether an assignment or a return, is of type `Type`.
```php
<?php
/** @var string */
$a = $_GET['foo'];
/** @var string $b */
$b = $_GET['bar'];
function bat(): string {
/** @var string */
return $_GET['bat'];
}
```
## Psalm-specific tags
There are a number of custom tags that determine how Psalm treats your code.
### `@psalm-consistent-constructor`
See [UnsafeInstantiation](../running_psalm/issues/UnsafeInstantiation.md)
### `@psalm-consistent-templates`
See [UnsafeGenericInstantiation](../running_psalm/issues/UnsafeGenericInstantiation.md)
### `@param-out`, `@psalm-param-out`
This is used to specify that a by-ref type is different from the one that entered. In the function below the first param can be null, but once the function has executed the by-ref value is not null.
```php
<?php
/**
* @param-out string $s
*/
function addFoo(?string &$s) : void {
if ($s === null) {
$s = "hello";
}
$s .= "foo";
}
```
### `@psalm-var`, `@psalm-param`, `@psalm-return`, `@psalm-property`, `@psalm-property-read`, `@psalm-property-write`, `@psalm-method`
When specifying types in a format not supported by phpDocumentor ([but supported by Psalm](#type-syntax)) you may wish to prepend `@psalm-` to the PHPDoc tag, so as to avoid confusing your IDE. If a `@psalm`-prefixed tag is given, Psalm will use it in place of its non-prefixed counterpart.
### `@psalm-ignore-var`
This annotation is used to ignore the `@var` annotation written in the same docblock. Some IDEs don't fully understand complex types like generics. To take advantage of such IDE's auto-completion, you may sometimes want to use explicit `@var` annotations even when psalm can infer the type just fine. This weakens the effectiveness of type checking in many cases since the explicit `@var` annotation overrides the types inferred by psalm. As psalm ignores the `@var` annotation which is co-located with `@psalm-ignore-var`, IDEs can use the type specified by the `@var` for auto-completion, while psalm can still use its own inferred type for type checking.
```php
<?php
/** @return iterable<array-key,\DateTime> $f */
function getTimes(int $n): iterable {
while ($n--) {
yield new \DateTime();
}
};
/**
* @var \Datetime[] $times
* @psalm-ignore-var
*/
$times = getTimes(3);
// this trace shows "iterable<array-key, DateTime>" instead of "array<array-key, Datetime>"
/** @psalm-trace $times */
foreach ($times as $time) {
echo $time->format('Y-m-d H:i:s.u') . PHP_EOL;
}
```
### `@psalm-suppress SomeIssueName`
This annotation is used to suppress issues. It can be used in function docblocks, class docblocks and also inline, applying to the following statement.
Function docblock example:
```php
<?php
/**
* @psalm-suppress PossiblyNullOperand
*/
function addString(?string $s) {
echo "hello " . $s;
}
```
Inline example:
```php
<?php
function addString(?string $s) {
/** @psalm-suppress PossiblyNullOperand */
echo "hello " . $s;
}
```
`@psalm-suppress all` can be used to suppress all issues instead of listing them individually.
### `@psalm-assert`, `@psalm-assert-if-true`, `@psalm-assert-if-false`, `@psalm-if-this-is` and `@psalm-this-out`
See [Adding assertions](adding_assertions.md).
### `@psalm-ignore-nullable-return`
This can be used to tell Psalm not to worry if a function/method returns null. Its a bit of a hack, but occasionally useful for scenarios where you either have a very high confidence of a non-null value, or some other function guarantees a non-null value for that particular code path.
```php
<?php
class Foo {}
function takesFoo(Foo $f): void {}
/** @psalm-ignore-nullable-return */
function getFoo(): ?Foo {
return rand(0, 10000) > 1 ? new Foo() : null;
}
takesFoo(getFoo());
```
### `@psalm-ignore-falsable-return`
This provides the same, but for `false`. Psalm uses this internally for functions like `preg_replace`, which can return false if the given input has encoding errors, but where 99.9% of the time the function operates as expected.
### `@psalm-seal-properties`
If you have a magic property getter/setter, you can use `@psalm-seal-properties` to instruct Psalm to disallow getting and setting any properties not contained in a list of `@property` (or `@property-read`/`@property-write`) annotations.
```php
<?php
/**
* @property string $foo
* @psalm-seal-properties
*/
class A {
public function __get(string $name): ?string {
if ($name === "foo") {
return "hello";
}
}
public function __set(string $name, $value): void {}
}
$a = new A();
$a->bar = 5; // this call fails
```
### `@psalm-internal`
Used to mark a class, property or function as internal to a given namespace. Psalm treats this slightly differently to
the PHPDoc `@internal` tag. For `@internal`, an issue is raised if the calling code is in a namespace completely
unrelated to the namespace of the calling code, i.e. not sharing the first element of the namespace.
In contrast for `@psalm-internal`, the docblock line must specify a namespace. An issue is raised if the calling code
is not within the given namespace.
```php
<?php
namespace A\B {
/**
* @internal
* @psalm-internal A\B
*/
class Foo { }
}
namespace A\B\C {
class Bat {
public function batBat(): void {
$a = new \A\B\Foo(); // this is fine
}
}
}
namespace A\C {
class Bat {
public function batBat(): void {
$a = new \A\B\Foo(); // error
}
}
}
```
### `@psalm-readonly` and `@readonly`
Used to annotate a property that can only be written to in its defining class's constructor.
```php
<?php
class B {
/** @readonly */
public string $s;
public function __construct(string $s) {
$this->s = $s;
}
}
$b = new B("hello");
echo $b->s;
$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
class D {
private string $s;
public function __construct(string $s) {
$this->s = $s;
}
/**
* @psalm-mutation-free
*/
public function getShort() : string {
return substr($this->s, 0, 5);
}
/**
* @psalm-mutation-free
*/
public function getShortMutating() : string {
$this->s .= "hello"; // this is a bug
return substr($this->s, 0, 5);
}
}
```
### `@psalm-external-mutation-free`
Used to annotate a class method that does not mutate state externally of the class's scope.
```php
<?php
class E {
private string $s;
public function __construct(string $s) {
$this->s = $s;
}
/**
* @psalm-external-mutation-free
*/
public function getShortMutating() : string {
$this->s .= "hello"; // this is fine
return substr($this->s, 0, 5);
}
/**
* @psalm-external-mutation-free
*/
public function save() : void {
file_put_contents("foo.txt", $this->s); // this is a bug
}
}
```
### `@psalm-immutable`
Used to annotate a class where every property is treated by consumers as `@psalm-readonly` and every instance method is treated as `@psalm-mutation-free`.
```php
<?php
/**
* @psalm-immutable
*/
abstract class Foo
{
public string $baz;
abstract public function bar(): int;
}
/**
* @psalm-immutable
*/
final class ChildClass extends Foo
{
public function __construct(string $baz)
{
$this->baz = $baz;
}
public function bar(): int
{
return 0;
}
}
$anonymous = new /** @psalm-immutable */ class extends Foo
{
public string $baz = "B";
public function bar(): int
{
return 1;
}
};
```
### `@psalm-pure`
Used to annotate a [pure function](https://en.wikipedia.org/wiki/Pure_function) - one whose output is just a function of its input.
```php
<?php
class Arithmetic {
/** @psalm-pure */
public static function add(int $left, int $right) : int {
return $left + $right;
}
/** @psalm-pure - this is wrong */
public static function addCumulative(int $left) : int {
/** @var int */
static $i = 0; // this is a side effect, and thus a bug
$i += $left;
return $i;
}
}
echo Arithmetic::add(40, 2);
echo Arithmetic::add(40, 2); // same value is emitted
echo Arithmetic::addCumulative(3); // outputs 3
echo Arithmetic::addCumulative(3); // outputs 6
```
On the other hand, `pure-callable` can be used to denote a callable which needs to be pure.
```php
/**
* @param pure-callable(mixed): int $callback
*/
function foo(callable $callback) {...}
// this fails since random_int is not pure
foo(
/** @param mixed $p */
fn($p) => random_int(1, 2)
);
```
### `@psalm-allow-private-mutation`
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.
```php
<?php
class Counter {
/**
* @readonly
* @psalm-allow-private-mutation
*/
public int $count = 0;
public function increment() : void {
$this->count++;
}
}
$counter = new Counter();
echo $counter->count; // outputs 0
$counter->increment(); // Method can mutate property
echo $counter->count; // outputs 1
$counter->count = 5; // This will fail, as it's mutating a property directly
```
### `@psalm-readonly-allow-private-mutation`
This is a shorthand for the property annotations `@readonly` and `@psalm-allow-private-mutation`.
```php
<?php
class Counter {
/**
* @psalm-readonly-allow-private-mutation
*/
public int $count = 0;
public function increment() : void {
$this->count++;
}
}
$counter = new Counter();
echo $counter->count; // outputs 0
$counter->increment(); // Method can mutate property
echo $counter->count; // outputs 1
$counter->count = 5; // This will fail, as it's mutating a property directly
```
### `@psalm-trace`
You can use this annotation to trace inferred type (applied to the *next* statement).
```php
<?php
/** @psalm-trace $username */
$username = $_GET['username']; // prints something like "test.php:4 $username: mixed"
```
*Note*: it throws [special low-level issue](../running_psalm/issues/Trace.md), so you have to set errorLevel to 1, override it in config or invoke Psalm with `--show-info=true`.
### `@psalm-check-type`
You can use this annotation to ensure the inferred type matches what you expect.
```php
<?php
/** @psalm-check-type $foo = int */
$foo = 1; // No issue
/** @psalm-check-type $bar = int */
$bar = "not-an-int"; // Checked variable $bar = int does not match $bar = 'not-an-int'
```
### `@psalm-check-type-exact`
Like `@psalm-check-type`, but checks the exact type of the variable without allowing subtypes.
```php
<?php
/** @psalm-check-type-exact $foo = int */
$foo = 1; // Checked variable $foo = int does not match $foo = 1
```
### `@psalm-taint-*`
See [Security Analysis annotations](../security_analysis/annotations.md).
### `@psalm-type`
This allows you to define an alias for another type.
```php
<?php
/**
* @psalm-type PhoneType = array{phone: string}
*/
class Phone {
/**
* @psalm-return PhoneType
*/
public function toArray(): array {
return ["phone" => "Nokia"];
}
}
```
### `@psalm-import-type`
You can use this annotation to import a type defined with [`@psalm-type`](#psalm-type) if it was defined somewhere else.
```php
<?php
/**
* @psalm-import-type PhoneType from Phone
*/
class User {
/**
* @psalm-return PhoneType
*/
public function toArray(): array {
return array_merge([], (new Phone())->toArray());
}
}
```
You can also alias a type when you import it:
```php
<?php
/**
* @psalm-import-type PhoneType from Phone as MyPhoneTypeAlias
*/
class User {
/**
* @psalm-return MyPhoneTypeAlias
*/
public function toArray(): array {
return array_merge([], (new Phone())->toArray());
}
}
```
### `@psalm-require-extends`
The `@psalm-require-extends` annotation allows you to define the requirements that a trait imposes on the using class.
```php
<?php
abstract class DatabaseModel {
// methods, properties, etc.
}
/**
* @psalm-require-extends DatabaseModel
*/
trait SoftDeletingTrait {
// useful but scoped functionality, that depends on methods/properties from DatabaseModel
}
class MyModel extends DatabaseModel {
// valid
use SoftDeletingTrait;
}
class NormalClass {
// triggers an error
use SoftDeletingTrait;
}
```
### `@psalm-require-implements`
Behaves the same way as `@psalm-require-extends`, but for interfaces.
### `@no-named-arguments`
This will prevent access to the function or method tagged with named parameters (by emitting a `NamedArgumentNotAllowed` issue).
Incidentally, it will change the inferred type for the following code:
```php
<?php
function a(int ...$a){
var_dump($a);
}
```
The type of `$a` is `array<array-key, int>` without `@no-named-arguments` but becomes `list<int>` with it, because it excludes the case where the offset would be a string with the name of the parameter
### `@psalm-ignore-variable-property` and `@psalm-ignore-variable-method`
Instructs Psalm to ignore variable property fetch / variable method call when looking for dead code.
```php
class Foo
{
// this property can be deleted by Psalter,
// as potential reference in get() is ignored
public string $bar = 'bar';
public function get(string $name): mixed
{
/** @psalm-ignore-variable-property */
return $this->{$name};
}
}
```
When Psalm encounters variable property, it treats all properties in given class as potentially referenced.
With `@psalm-ignore-variable-property` annotation, this reference is ignored.
While `PossiblyUnusedProperty` would be emitted in both cases, using `@psalm-ignore-variable-property`
would allow [Psalter](../manipulating_code/fixing.md) to delete `Foo::$bar`.
`@psalm-ignore-variable-method` behaves the same way, but for variable method calls.
### `@psalm-yield`
Used to specify the type of value which will be sent back to a generator when an annotated object instance is yielded.
```php
<?php
/**
* @template-covariant TValue
* @psalm-yield TValue
*/
interface Promise {}
/**
* @template-covariant TValue
* @template-implements Promise<TValue>
*/
class Success implements Promise {
/**
* @psalm-param TValue $value
*/
public function __construct($value) {}
}
/**
* @return Promise<string>
*/
function fetch(): Promise {
return new Success('{"data":[]}');
}
function (): Generator {
$data = yield fetch();
// this is fine, Psalm knows that $data is a string
return json_decode($data);
};
```
This annotation supports only generic types, meaning that e.g. `@psalm-yield string` would be ignored.
### `@psalm-api`
Used to tell Psalm that a class is used, even if no references to it can be
found. Unused issues will be suppressed.
For example, in frameworks, controllers are often invoked "magically" without
any explicit references to them in your code. You should mark these classes with
`@psalm-api`.
```php
/**
* @psalm-api
*/
class UnreferencedClass {}
```
## Type Syntax
Psalm supports PHPDocs [type syntax](https://docs.phpdoc.org/latest/guide/guides/types.html), and also the [proposed PHPDoc PSR type syntax](https://github.com/php-fig/fig-standards/blob/master/proposed/phpdoc.md#appendix-a-types).
A detailed write-up is found in [Typing in Psalm](typing_in_psalm.md)

View File

@@ -0,0 +1,454 @@
# Templating
Docblocks allow you to tell Psalm some simple information about how your code works. For example `@return int` in a function return type tells Psalm that a function should return an `int` and `@return MyContainer` tells Psalm that a function should return an instance of a user-defined class `MyContainer`. In either case, Psalm can check that the function actually returns those types _and_ that anything calling that function uses its returned value properly.
Templated types allow you to tell Psalm even more information about how your code works.
Let's look at a simple class `MyContainer`:
```php
<?php
class MyContainer {
private $value;
public function __construct($value) {
$this->value = $value;
}
public function getValue() {
return $this->value;
}
}
```
When Psalm handles the return type of `$my_container->getValue()` it doesn't know what it's getting out, because the value can be arbitrary.
Templated annotations provide us with a workaround - we can define a generic/templated param `T` that is a placeholder for the value inside `MyContainer`:
```php
<?php
/**
* @template T
*/
class MyContainer {
/** @var T */
private $value;
/** @param T $value */
public function __construct($value) {
$this->value = $value;
}
/** @return T */
public function getValue() {
return $this->value;
}
}
```
Now we can substitute values for that templated param when we reference `MyContainer` in docblocks e.g. `@return MyContainer<int>`. This tells Psalm to substitute `T` for `int` when evaluating that return type, effectively treating it as a class that looks like
```php
<?php
class One_off_instance_of_MyContainer {
/** @var int */
private $value;
/** @param int $value */
public function __construct($value) {
$this->value = $value;
}
/** @return int */
public function getValue() {
return $this->value;
}
}
```
This pattern can be used in a large number of different situations like mocking, collections, iterators and loading arbitrary objects. Psalm has a large number of annotations to make it easy to use templated types in your codebase.
## `@template`, `@psalm-template`
The `@template`/`@psalm-template` tag allows classes and functions to declare a generic type parameter.
As a very simple example, this function returns whatever is passed in:
```php
<?php
/**
* @template T
* @psalm-param T $t
* @return T
*/
function mirror($t) {
return $t;
}
$a = 5;
$b = mirror($a); // Psalm knows the result is an int
$c = "foo";
$d = mirror($c); // Psalm knows the result is string
```
Psalm also uses `@template` annotations in its stubbed versions of PHP array functions e.g.
```php
<?php
/**
* Takes one array with keys and another with values and combines them
*
* @template TKey
* @template TValue
*
* @param array<mixed, TKey> $arr
* @param array<mixed, TValue> $arr2
* @return array<TKey, TValue>
*/
function array_combine(array $arr, array $arr2) {}
```
### Notes
- `@template` tag order matters for class docblocks, as they dictate the order in which those generic parameters are referenced in docblocks.
- The names of your templated types (e.g. `TKey`, `TValue`) don't matter outside the scope of the class or function in which they're declared.
## @param class-string&lt;T&gt;
Psalm also allows you to parameterize class types
```php
<?php
/**
* @template T of Foo
* @psalm-param class-string<T> $class
* @return T
*/
function instantiator(string $class) {
return new $class();
}
class Foo {
public final function __construct() {}
}
class FooChild extends Foo {}
$r = instantiator(FooChild::class);
// Psalm knows $r is an object of type FooChild
```
## Template inheritance
Psalm allows you to extend templated classes with `@extends`/`@template-extends`:
```php
<?php
/**
* @template T
*/
class ParentClass {}
/**
* @extends ParentClass<int>
*/
class ChildClass extends ParentClass {}
```
similarly you can implement interfaces with `@implements`/`@template-implements`
```php
<?php
/**
* @template T
*/
interface IFoo {}
/**
* @implements IFoo<int>
*/
class Foo implements IFoo {}
```
and import traits with `@use`/`@template-use`
```php
<?php
/**
* @template T
*/
trait MyTrait {}
class Foo {
/**
* @use MyTrait<int>
*/
use MyTrait;
}
```
You can also extend one templated class with another, e.g.
```php
<?php
/**
* @template T1
*/
class ParentClass {}
/**
* @template T2
* @extends ParentClass<T2>
*/
class ChildClass extends ParentClass {}
```
## Template constraints
You can use `@template of <type>` to restrict input. For example, to restrict to a given class you can use
```php
<?php
class Foo {}
class FooChild extends Foo {}
/**
* @template T of Foo
* @psalm-param T $t
* @return array<int, T>
*/
function makeArray($t) {
return [$t];
}
$a = makeArray(new Foo()); // typed as array<int, Foo>
$b = makeArray(new FooChild()); // typed as array<int, FooChild>
$c = makeArray(new stdClass()); // type error
```
Templated types aren't limited to key-value pairs, and you can re-use templates across multiple arguments of a template-supporting type:
```php
<?php
/**
* @template T0 of array-key
*
* @template-implements IteratorAggregate<T0, int>
*/
abstract class Foo implements IteratorAggregate {
/**
* @var int
*/
protected $rand_min;
/**
* @var int
*/
protected $rand_max;
public function __construct(int $rand_min, int $rand_max) {
$this->rand_min = $rand_min;
$this->rand_max = $rand_max;
}
/**
* @return Generator<T0, int, mixed, T0>
*/
public function getIterator() : Generator {
$j = random_int($this->rand_min, $this->rand_max);
for($i = $this->rand_min; $i <= $j; $i += 1) {
yield $this->getFuzzyType($i) => $i ** $i;
}
return $this->getFuzzyType($j);
}
/**
* @return T0
*/
abstract protected function getFuzzyType(int $i);
}
/**
* @template-extends Foo<int>
*/
class Bar extends Foo {
protected function getFuzzyType(int $i) : int {
return $i;
}
}
/**
* @template-extends Foo<string>
*/
class Baz extends Foo {
protected function getFuzzyType(int $i) : string {
return static::class . '[' . $i . ']';
}
}
```
## Template covariance
Imagine you have code like this:
```php
<?php
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
/**
* @template T
*/
class Collection {
/**
* @var array<int, T>
*/
public array $list;
/**
* @param array<int, T> $list
*/
public function __construct(array $list) {
$this->list = $list;
}
/**
* @param T $t
*/
public function add($t) : void {
$this->list[] = $t;
}
}
/**
* @param Collection<Animal> $collection
*/
function addAnimal(Collection $collection) : void {
$collection->add(new Cat());
}
/**
* @param Collection<Dog> $dog_collection
*/
function takesDogList(Collection $dog_collection) : void {
addAnimal($dog_collection);
}
```
That last call `addAnimal($dog_collection)` breaks the type of the collection suddenly a collection of dogs becomes a collection of dogs _or_ cats. That is bad.
To prevent this, Psalm emits an error when calling `addAnimal($dog_collection)` saying "addAnimal expects a `Collection<Animal>`, but `Collection<Dog>` was passed". If you haven't encountered this rule before it's probably confusing to you any function that accepted an `Animal` would be happy to accept a subtype thereof. But as we see in the example above, doing so can lead to problems.
But there are also times where it's perfectly safe to pass template param subtypes:
```php
<?php
abstract class Animal {
abstract public function getNoise() : string;
}
class Dog extends Animal {
public function getNoise() : string { return "woof"; }
}
class Cat extends Animal {
public function getNoise() : string { return "miaow"; }
}
/**
* @template T
*/
class Collection {
/** @var array<int, T> */
public array $list = [];
}
/**
* @param Collection<Animal> $collection
*/
function getNoises(Collection $collection) : void {
foreach ($collection->list as $animal) {
echo $animal->getNoise();
}
}
/**
* @param Collection<Dog> $dog_collection
*/
function takesDogList(Collection $dog_collection) : void {
getNoises($dog_collection);
}
```
Here we're not doing anything bad we're just iterating over an array of objects. But Psalm still gives that same basic error "getNoises expects a `Collection<Animal>`, but `Collection<Dog>` was passed".
We can tell Psalm that it's safe to pass subtypes for the templated param `T` by using the annotation `@template-covariant T` (or `@psalm-template-covariant T`):
```php
<?php
/**
* @template-covariant T
*/
class Collection {
/** @var array<int, T> */
public array $list = [];
}
```
Doing this for the above example produces no errors: [https://psalm.dev/r/5254af7a8b](https://psalm.dev/r/5254af7a8b)
But `@template-covariant` doesn't get rid of _all_ errors if you add it to the first example, you get a new error [https://psalm.dev/r/0fcd699231](https://psalm.dev/r/0fcd699231) complaining that you're attempting to use a covariant template parameter for function input. Thats no good, as it means you're likely altering the collection somehow (which is, again, a violation).
### But what about immutability?
Psalm has [comprehensive support for declaring functional immutability](https://psalm.dev/articles/immutability-and-beyond).
If we make sure that the class is immutable, we can declare a class with an `add` method that still takes a covariant param as input, but which does not modify the collection at all, instead returning a new one:
```php
<?php
/**
* @template-covariant T
* @psalm-immutable
*/
class Collection {
/**
* @var array<int, T>
*/
public array $list = [];
/**
* @param array<int, T> $list
*/
public function __construct(array $list) {
$this->list = $list;
}
/**
* @param T $t
* @return Collection<T>
*/
public function add($t) : Collection {
return new Collection(array_merge($this->list, [$t]));
}
}
```
This is perfectly valid, and Psalm won't complain.
## Builtin templated classes and interfaces
Psalm has support for a number of builtin classes and interfaces that you can extend/implement in your own code.
- `interface Traversable<TKey, TValue>`
- `interface ArrayAccess<TKey, TValue>`
- `interface IteratorAggregate<TKey, TValue> extends Traversable<TKey, TValue>`
- `interface Iterator<TKey, TValue> extends Traversable<TKey, TValue>`
- `interface SeekableIterator<TKey, TValue> extends Iterator<TKey, TValue>`
- `class Generator<TKey, TValue, TSend, TReturn> extends Traversable<TKey, TValue>`
- `class ArrayObject<TKey, TValue> implements IteratorAggregate<TKey, TValue>, ArrayAccess<TKey, TValue>`
- `class ArrayIterator<TKey of array-key, TValue> implements SeekableIterator<TKey, TValue>, ArrayAccess<TKey, TValue>`
- `class DOMNodeList<TNode of DOMNode> implements Traversable<int, TNode>`
- `class SplDoublyLinkedList<TValue> implements Iterator<TKey, TValue>, ArrayAccess<TKey, TValue>`
- `class SplQueue<TValue> extends SplDoublyLinkedList<TValue>`
- `abstract class FilterIterator<TKey, TValue, TIterator>`

View File

@@ -0,0 +1,220 @@
# Array types
In PHP, the `array` type is commonly used to represent three different data structures:
[List](https://en.wikipedia.org/wiki/List_(abstract_data_type)):
```php
<?php
$a = [1, 2, 3, 4, 5];
```
[Associative array](https://en.wikipedia.org/wiki/Associative_array):
```php
<?php
$a = [0 => 'hello', 5 => 'goodbye'];
$b = ['a' => 'AA', 'b' => 'BB', 'c' => 'CC']
```
Makeshift [Structs](https://en.wikipedia.org/wiki/Struct_(C_programming_language)):
```php
<?php
$a = ['name' => 'Psalm', 'type' => 'tool'];
```
PHP treats all these arrays the same, essentially (though there are some optimisations under the hood for the first case).
Psalm has a few different ways to represent arrays in its type system:
## Generic arrays
Psalm uses a syntax [borrowed from Java](https://en.wikipedia.org/wiki/Generics_in_Java) that allows you to denote the types of both keys *and* values:
```php
/** @return array<TKey, TValue> */
```
You can also specify that an array is non-empty with the special type `non-empty-array<TKey, TValue>`.
### PHPDoc syntax
PHPDoc [allows you to specify](https://docs.phpdoc.org/latest/guide/references/phpdoc/types.html#arrays) the type of values a generic array holds with the annotation:
```php
/** @return ValueType[] */
```
In Psalm this annotation is equivalent to `@psalm-return array<array-key, ValueType>`.
Generic arrays encompass both _associative arrays_ and _lists_.
## Lists
(Psalm 3.6+)
Psalm supports a `list` type that represents continuous, integer-indexed arrays like `["red", "yellow", "blue"]`.
A frequent way to create a list is with the `$arr[] =` notation.
These arrays will return true to `array_is_list($arr)`(PHP 8.1+) and represent a large percentage of all array usage in PHP applications.
A `list` type is of the form `list<SomeType>`, where `SomeType` is any permitted [union type](union_types.md) supported by Psalm.
- `list` is a subtype of `array<int, mixed>`
- `list<Foo>` is a subtype of `array<int, Foo>`.
List types show their value in a few ways:
```php
<?php
/**
* @param array<int, string> $arr
*/
function takesArray(array $arr) : void {
if ($arr) {
// this index may not be set
echo $arr[0];
}
}
/**
* @psalm-param list<string> $arr
*/
function takesList(array $arr) : void {
if ($arr) {
// list indexes always start from zero,
// so a non-empty list will have an element here
echo $arr[0];
}
}
takesArray(["hello"]); // this is fine
takesArray([1 => "hello"]); // would trigger bug, without warning
takesList(["hello"]); // this is fine
takesList([1 => "hello"]); // triggers warning in Psalm
```
## Array shapes
Psalm supports a special format for arrays where the key offsets are known: array shapes, also known as "object-like arrays".
Given an array
```php
<?php
["hello", "world", "foo" => new stdClass, 28 => false];
```
Psalm will type it internally as:
```
array{0: string, 1: string, foo: stdClass, 28: false}
```
You can specify types in that format yourself, e.g.
```php
/** @return array{foo: string, bar: int} */
```
Optional keys can be denoted by a trailing `?`, e.g.:
```php
/** @return array{optional?: string, bar: int} */
```
Tip: if you find yourself copying the same complex array shape over and over again to avoid `InvalidArgument` issues, try using [type aliases](utility_types.md#type-aliases), instead.
### Validating array shapes
Use [Valinor](https://github.com/CuyZ/Valinor) in strict mode to easily assert array shapes at runtime using Psalm array shape syntax (instead of manually asserting keys with isset):
```php
try {
$array = (new \CuyZ\Valinor\MapperBuilder())
->mapper()
->map(
'array{a: string, b: int}',
json_decode(file_get_contents('https://.../'), true)
);
/** @psalm-trace $array */; // array{a: string, b: int}
echo $array['a'];
echo $array['b'];
} catch (\CuyZ\Valinor\Mapper\MappingError $error) {
// Do something…
}
```
Valinor provides both runtime and static Psalm assertions with full Psalm syntax support and many other features, check out the [Valinor documentation](https://valinor.cuyz.io/latest/) for more info!
## List shapes
Starting in Psalm 5, Psalm also supports a special format for list arrays where the key offsets are known.
Given a list array
```php
<?php
["hello", "world", new stdClass, false];
```
Psalm will type it internally as:
```
list{string, string, stdClass, false}
```
You can specify types in that format yourself, e.g.
```php
/** @return list{string, int} */
/** @return list{0: string, 1: int} */
```
Optional keys can be denoted by a specifying keys for all elements and specifying a trailing `?` for optional keys, e.g.:
```php
/** @return list{0: string, 1?: int} */
```
List shapes are essentially n-tuples [from a type theory perspective](https://en.wikipedia.org/wiki/Tuple#Type_theory).
## Unsealed array and list shapes
Starting from Psalm v5, array shapes and list shapes can be marked as open by adding `...` as their last element.
Here we have a function `handleOptions` that takes an array of options. The type tells us it has a single known key with type `string`, and potentially many other keys of unknown types.
```php
/** @param array{verbose: string, ...} $options */
function handleOptions(array $options): float {
if ($options['verbose']) {
var_dump($options);
}
}
$options = get_opt(/* some code */);
$options['verbose'] = isset($options['verbose']);
handleOptions($options);
```
## Callable arrays
An array holding a callable, like PHP's native `call_user_func()` and friends supports it:
```php
<?php
$callable = ['myClass', 'aMethod'];
$callable = [$object, 'aMethod'];
```
## non-empty-array
An array which is not allowed to be empty.
[Generic syntax](#generic-arrays) is also supported: `non-empty-array<string, int>`.

View File

@@ -0,0 +1,60 @@
# Atomic types
Atomic types are the basic building block of all type information used in Psalm. Multiple atomic types can be combined, either with [union types](union_types.md) or [intersection types](intersection_types.md). Psalm allows many different sorts of atomic types to be expressed in docblock syntax:
**Note**: you can view detailed documentation and usage examples for all atomic types by clicking on each type in the following list.
* [Scalar types](scalar_types.md)
* [bool](scalar_types.md#scalar)
* [int](scalar_types.md#scalar)
* [float](scalar_types.md#scalar)
* [string](scalar_types.md#scalar)
* [`int-range<x, y>`](scalar_types.md#int-range)
* [`int-mask<1, 2, 4>`](scalar_types.md#int-mask1-2-4)
* [`int-mask-of<MyClass::CLASS_CONSTANT_*>`](scalar_types.md#int-mask-ofmyclassclass_constant_)
* [class-string and class-string&lt;Foo&gt;](scalar_types.md#class-string-interface-string)
* [trait-string](scalar_types.md#trait-string)
* [enum-string](scalar_types.md#enum-string)
* [callable-string](scalar_types.md#callable-string)
* [numeric-string](scalar_types.md#numeric-string)
* [literal-string](scalar_types.md#literal-string)
* [literal-int](scalar_types.md#literal-int)
* [array-key](scalar_types.md#array-key)
* [numeric](scalar_types.md#numeric)
* [scalar](scalar_types.md#scalar)
* [Object types](object_types.md)
* [object](object_types.md#unnamed-objects)
* [object{foo: string}](object_types.md#object-properties)
* [Exception, Foo\MyClass and `Foo\MyClass<Bar>`](object_types.md#named-objectsmd)
* [Generator](object_types.md#generators)
* [Array types](array_types.md)
* [array&lt;int, string&gt;](array_types.md#generic-arrays)
* [non-empty-array](array_types.md#non-empty-array)
* [string\[\]](array_types.md#phpdoc-syntax)
* [list & non-empty-list](array_types.md#lists)
* [list&lt;string&gt;](array_types.md#lists)
* [array{foo: int, bar: string} and list{int, string}](array_types.md#object-like-arrays)
* [callable-array](array_types.md#callable-arrays)
* [Callable types](callable_types.md)
* [Value types](value_types.md)
* [null](value_types.md#null)
* [true, false](value_types.md#true-false)
* [6, 7.0, "forty-two" and 'forty two'](value_types.md#some_string-4-314)
* [Foo\Bar::MY_SCALAR_CONST](value_types.md#regular-class-constants)
* [Utility types](utility_types.md)
* [(T is true ? string : bool)](conditional_types.md)
* [`key-of<T>`](utility_types.md#key-oft)
* [`value-of<T>`](utility_types.md#value-oft)
* [`properties-of<T>`](utility_types.md#properties-oft)
* [`class-string-map<T as Foo, T>`](utility_types.md#class-string-mapt-as-foo-t)
* [`T[K]`](utility_types.md#tk)
* [Type aliases](utility_types.md#type-aliases)
* [Variable templates](utility_types.md#variable-templates)
* [Other types](other_types.md)
* [`iterable<TKey, TValue>`](other_types.md)
* [void](other_types.md)
* [resource](other_types.md)
* [closed-resource](other_types.md)
* [Top and bottom types](top_bottom_types.md)
* [mixed](top_bottom_types.md#mixed)
* [never](top_bottom_types.md#never)

View File

@@ -0,0 +1,58 @@
# Callable types
Psalm supports a special format for `callable`s of the form. It can also be used for annotating `Closure`.
```
callable(Type1, OptionalType2=, SpreadType3...):ReturnType
```
Adding `=` after the type implies it is optional, and suffixing with `...` implies the use of the spread operator.
Using this annotation you can specify that a given function return a `Closure` e.g.
```php
<?php
/**
* @return Closure(bool):int
*/
function delayedAdd(int $x, int $y) : Closure {
return function(bool $debug) use ($x, $y) {
if ($debug) echo "got here" . PHP_EOL;
return $x + $y;
};
}
$adder = delayedAdd(3, 4);
echo $adder(true);
```
## Pure callables
For situations where the `callable` needs to be pure or immutable, the subtypes `pure-callable` and `pure-Closure` are also available.
This can be useful when the `callable` is used in a function marked with `@psalm-pure` or `@psalm-mutation-free`, for example:
```php
<?php
/** @psalm-immutable */
class intList {
/** @param list<int> $items */
public function __construct(private array $items) {}
/**
* @param pure-callable(int, int): int $callback
* @psalm-mutation-free
*/
public function walk(callable $callback): int {
return array_reduce($this->items, $callback, 0);
}
}
$list = new intList([1,2,3]);
// This is ok, as the callable is pure
echo $list->walk(fn (int $c, int $v): int => $c + $v);
// This will cause an InvalidArgument error, as the closure calls an impure function
echo $list->walk(fn (int $c, int $v): int => $c + random_int(1, $v));
```

View File

@@ -0,0 +1,82 @@
# Conditional types
Psalm supports the equivalent of TypeScripts [conditional types](https://www.typescriptlang.org/docs/handbook/advanced-types.html#conditional-types).
Conditional types have the form:
`(<template param> is <union type> ? <union type> : <union type>)`
All conditional types must be wrapped inside brackets e.g. `(...)`
Conditional types are dependent on [template parameters](../templated_annotations.md), so you can only use them in a function where template parameters are defined.
## Example application
Let's suppose we want to make a userland implementation of PHP's numeric addition (but please never do this). You could type this with a conditional return type:
```php
<?php
/**
* @template T of int|float
* @param T $a
* @param T $b
* @return int|float
* @psalm-return (T is int ? int : float)
*/
function add($a, $b) {
return $a + $b;
}
```
When figuring out the result of `add($x, $y)` Psalm tries to infer the value `T` for that particular call. When calling `add(1, 2)`, `T` can be trivially inferred as an `int`. Then Psalm takes the provided conditional return type
`(T is int ? int : float)`
and substitutes in the known value of `T`, `int`, so that expression becomes
`(int is int ? int : float)`
which simplifies to `(true ? int : float)`, which simplifies to `int`.
Calling `add(1, 2.1)` means `T` would instead be inferred as `int|float`, which means the expression `(T is int ? int : float)` would instead have the substitution
`(int|float is int ? int : float)`
The union `int|float` is clearly not an `int`, so the expression is simplified to `(false ? int : float)`, which simplifies to `float`.
## Nested conditionals
You can also nest conditionals just as you could ternary expressions:
```php
<?php
class A {
const TYPE_STRING = 0;
const TYPE_INT = 1;
/**
* @template T of int
* @param T $i
* @psalm-return (
* T is self::TYPE_STRING
* ? string
* : (T is self::TYPE_INT ? int : bool)
* )
*/
public static function getDifferentType(int $i) {
if ($i === self::TYPE_STRING) {
return "hello";
}
if ($i === self::TYPE_INT) {
return 5;
}
return true;
}
}
```

View File

@@ -0,0 +1,36 @@
# Intersection types
An annotation of the form `Type1&Type2&Type3` is an _Intersection Type_. Any value must satisfy `Type1`, `Type2` and `Type3` simultaneously. `Type1`, `Type2` and `Type3` are all [atomic types](atomic_types.md).
For example, after this statement in a PHPUnit test:
```php
<?php
$hare = $this->createMock(Hare::class);
```
`$hare` will be an instance of a class that extends `Hare`, and implements `\PHPUnit\Framework\MockObject\MockObject`. So
`$hare` is typed as `Hare&\PHPUnit\Framework\MockObject\MockObject`. You can use this syntax whenever a value is
required to implement multiple interfaces.
Another use case is being able to merge object-like arrays:
```php
/**
* @psalm-type A=array{a: int}
* @psalm-type B=array{b: int}
*
* @param A $a
* @param B $b
*
* @return A&B
*/
function foo($a, $b) {
return $a + $b;
}
```
The returned type will contain the properties of both `A` and `B`. In other words, it will be `{a: int, b: int}`.
Intersections are only valid for lists of only *object types* and lists of only *object-like arrays*.

View File

@@ -0,0 +1,45 @@
# Object types
## Unnamed objects
`object` are examples of unnamed object types. This type is also a valid type in PHP.
## Named objects
`stdClass`, `Foo`, `Bar\Baz` etc. are examples of named object types. These types are also valid types in PHP.
## Object properties
Psalm supports specifying the properties of an object and their expected types, e.g.:
```php
/** @param object{foo: string} $obj */
function takesObject(object $obj) : string {
return $obj->foo;
}
takesObject((object) ["foo" => "hello"]);
```
Optional properties can be denoted by a trailing `?`, e.g.:
```php
/** @param object{optional?: string} */
```
## Generic object types
Psalm supports using generic object types like `ArrayObject<int, string>`. Any generic object should be typehinted with appropriate [`@template` tags](../templated_annotations.md).
## Generators
Generator types support up to four parameters, e.g. `Generator<int, string, mixed, void>`:
1. `TKey`, the type of the `yield` key - default: `mixed`
2. `TValue`, the type of the `yield` value - default: `mixed`
3. `TSend`, the type of the `send()` method's parameter - default: `mixed`
4. `TReturn`, the return type of the `getReturn()` method - default: `mixed`
`Generator<int>` is a shorthand for `Generator<mixed, int, mixed, mixed>`.
`Generator<int, string>` is a shorthand for `Generator<int, string, mixed, mixed>`.

View File

@@ -0,0 +1,6 @@
# Other types
- `iterable` - represents the [iterable pseudo-type](https://php.net/manual/en/language.types.iterable.php). Like arrays, iterables can have type parameters e.g. `iterable<string, Foo>`.
- `void` - can be used in a return type when a function does not return a value.
- `resource` represents a [PHP resource](https://www.php.net/manual/en/language.types.resource.php).
- `closed-resource` represents a [PHP resource](https://www.php.net/manual/en/language.types.resource.php) that was closed (using `fclose` or another closing function).

View File

@@ -0,0 +1,115 @@
# Scalar types
## scalar
`int`, `bool`, `float`, `string` are examples of scalar types. Scalar types represent scalar values in PHP. These types are also valid types in PHP 7.
The type `scalar` is the supertype of all scalar types.
## int-range
Integer ranges indicate an integer within a range, specified using generic syntax: `int<x, y>`.
`x` and `y` must be integer numbers.
`x` can also be `min` to indicate PHP_INT_MIN, and `y` can be `max` to indicate PHP_INT_MAX.
Examples:
* `int<-1, 3>`
* `int<min, 0>`
* `int<1, max>` (equivalent to `positive-int`)
* `int<0, max>` (equivalent to `non-negative-int`)
* `int<min, -1>` (equivalent to `negative-int`)
* `int<min, 0>` (equivalent to `non-positive-int`)
* `int<min, max>` (equivalent to `int`)
## int-mask&lt;1, 2, 4&gt;
Represents the type that is the result of a bitmask combination of its parameters.
`int-mask<1, 2, 4>` corresponds to `0|1|2|3|4|5|6|7`.
## int-mask-of&lt;MyClass::CLASS_CONSTANT_*&gt;
Represents the type that is the result of a bitmask combination of its parameters.
This is the same concept as [`int-mask`](#int-mask1-2-4) but this type is used with a reference to constants in code: `int-mask-of<MyClass::CLASS_CONSTANT_*>` will correspond to `0|1|2|3|4|5|6|7` if there are three constants called `CLASS_CONSTANT_{A,B,C}` with values 1, 2 and 4.
## array-key
`array-key` is the supertype (but not a union) of `int` and `string`.
## numeric
`numeric` is a supertype of `int` or `float` and [`numeric-string`](#numeric-string).
## class-string, interface-string
Psalm supports a special meta-type for `MyClass::class` constants, `class-string`, which can be used everywhere `string` can.
For example, given a function with a `string` parameter `$class_name`, you can use the annotation `@param class-string $class_name` to tell Psalm make sure that the function is always called with a `::class` constant in that position:
```php
<?php
class A {}
/**
* @param class-string $s
*/
function takesClassName(string $s) : void {}
```
`takesClassName("A");` would trigger a `TypeCoercion` issue, whereas `takesClassName(A::class)` is fine.
You can also parameterize `class-string` with an object name e.g. [`class-string<Foo>`](value_types.md#regular-class-constants). This tells Psalm that any matching type must either be a class string of `Foo` or one of its descendants.
## trait-string
Psalm also supports a `trait-string` annotation denoting a trait that exists.
## enum-string
Psalm also supports a `enum-string` annotation denote an enum that exists.
## callable-string
`callable-string` denotes a string value that has passed an `is_callable` check.
## numeric-string
`numeric-string` denotes a string value that has passed an `is_numeric` check.
## literal-string
`literal-string` denotes a string value that is entirely composed of strings in your application.
Examples:
- `"hello " . "world"`
- `"hello " . Person::DEFAULT_NAME`
- `implode(', ', ["one", "two"])`
- `implode(', ', [1, 2, 3])`
- `"hello " . <another literal-string>`
Strings that don't pass this type check:
- `file_get_contents("foo.txt")`
- `$_GET["foo"]`
- `"hello " . $_GET["foo"]`
## literal-int
`literal-int` denotes an int value that is entirely composed of literal integers in your application.
Examples:
- `12`
- `12+42`
Integers that don't pass this type check:
- `(int) file_get_contents("foo.txt")`
- `(int) $_GET["foo"]`
- `((int)$_GET["foo"]) + 2`
## lowercase-string, non-empty-string, non-empty-lowercase-string
A non empty string, lowercased or both at once.
`empty` here is defined as all strings except the empty string `''`. Another type `non-falsy-string` is effectively a subtype of `non-empty-string`, and also precludes the string value `'0'`.

View File

@@ -0,0 +1,17 @@
# Top types, bottom types
## `mixed`
This is the _top type_ in PHP's type system, and represents a lack of type information. Psalm warns about `mixed` types when the `reportMixedIssues` flag is turned on, or when you're on level 1.
## `never`
It can be aliased to `no-return` or `never-return` in docblocks. Note: it replaced the old `empty` type that used to exist in Psalm
This is the _bottom type_ in PHP's type system. It's used to describe a type that has no possible value. It can happen in multiple cases:
- the actual `never` type from PHP 8.1 (can be used in docblocks for older versions). This type can be used as a return type for functions that will never return, either because they always throw exceptions or always exit()
- an union type that have been stripped for all its possible types. (For example, if a variable is `string|int` and we perform a is_bool() check in a condition, the type of the variable in the condition will be `never` as the condition will never be entered)
- it can represent a placeholder for types yet to come — a good example is the type of the empty array `[]`, which Psalm types as `array<never, never>`, the content of the array is void so it can accept any content
- it can also happen in the same context as the line above for templates that have yet to be defined

View File

@@ -0,0 +1,16 @@
# Union Types
An annotation of the form `Type1|Type2|Type3` is a _Union Type_. `Type1`, `Type2` and `Type3` are all acceptable possible types of that union type.
`Type1`, `Type2` and `Type3` are each [atomic types](atomic_types.md).
Union types can be generated in a number of different ways, for example in ternary expressions:
```php
<?php
$rabbit = rand(0, 10) === 4 ? 'rabbit' : ['rabbit'];
```
`$rabbit` will be either a `string` or an `array`. We can represent that idea with Union Types so `$rabbit` is typed as `string|array`. Union types represent *all* the possible types a given variable can have.
PHP builtin functions also have union-type returns - `strpos` can return `false` in some situations, `int` in others. We represent that union type with `int|false`.

View File

@@ -0,0 +1,364 @@
# Utility types
Psalm supports some _magical_ utility types that brings superpower to the PHP type system.
## key-of&lt;T&gt;
(Psalm 5.0+)
The `key-of` utility returns the offset-type for any [array type](array_types.md).
Some examples:
- `key-of<Foo\Bar::ARRAY_CONST>` evaluates to offset-type of `ARRAY_CONST` (Psalm 3.3+)
- `key-of<list<mixed>>` evaluates to `int`
- `key-of<array{a: mixed, b: mixed}|array{c: mixed}>` evaluates to `'a'|'b'|'c'`
- `key-of<string[]>` evaluates to `array-key`
- `key-of<T>` evaluates to the template param's offset-type (ensure `@template T of array`)
### Notes on template usage
If you use `key-of` with a template param, you can fulfill the type check only with these allowed methods:
- `array_keys($t)`
- `array_key_first($t)`
- `array_key_last($t)`
Currently `array_key_exists($key, $t)` **does not** infer that `$key` is of `key-of<T>`.
```php
/**
* @template T of array
* @param T $array
* @return list<key-of<T>>
*/
function getKeys($array) {
return array_keys($array);
}
```
## value-of&lt;T&gt;
(Psalm 5.0+)
The `value-of` utility returns the value-type for any [array type](array_types.md).
Some examples:
- `value-of<Foo\Bar::ARRAY_CONST>` evaluates to value-type of `ARRAY_CONST` (Psalm 3.3+)
- `value-of<list<float>>` evaluates to `float`
- `value-of<array{a: bool, b: int}|array{c: string}>` evaluates to `bool|int|string`
- `value-of<string[]>` evaluates to `string`
- `value-of<T>` evaluates to the template param's value-type (ensure `@template T of array`)
### Use with enumerations
In addition to array-types, `value-of` can also be used to specify an `int` or `string` that contains one of the possible values of a `BackedEnum`:
- `value-of<Suit>` evaluates to `'H'|'D'|'C'|'S'` (see [Backed enumerations](https://www.php.net/manual/en/language.enumerations.backed.php))
- `value-of<BinaryDigits>` evaluates to `0|1`
### Notes on template usage
If you use `value-of` with a template param, you can fulfill the type check only with these allowed methods:
- `array_values`
```php
/**
* @template T of array
* @param T $array
* @return value-of<T>[]
*/
function getValues($array) {
return array_values($array);
}
```
Currently `in_array($value, $t)` **does not** infer that `$value` is of `value-of<T>`.
## properties-of&lt;T&gt;
(Psalm 5.0+)
This collection of _utility types_ construct a keyed-array type, with the names of non-static properties of a class as
keys, and their respective types as values. This can be useful if you need to convert objects into arrays.
```php
class A {
public string $foo = 'foo!';
public int $bar = 42;
/**
* @return properties-of<self>
*/
public function asArray(): array {
return [
'foo' => $this->foo,
'bar' => $this->bar,
];
}
/**
* @return list<key-of<properties-of<self>>>
*/
public function attributeNames(): array {
return ['foo', 'bar']
}
}
```
### Variants
Note that `properties-of<T>` will return **all non-static** properties. There are the following subtypes to pick only
properties with a certain visibility:
- `public-properties-of<T>`
- `protected-properties-of<T>`
- `private-properties-of<T>`
### Sealed array support
Use final classes if you want to properties-of and get_object_vars to return sealed arrays:
```php
/**
* @template T
* @param T $object
* @return properties-of<T>
*/
function asArray($object): array {
return get_object_vars($object);
}
class A {
public string $foo = 'foo!';
public int $bar = 42;
}
final class B extends A {
public float $baz = 2.1;
}
$a = asArray(new A);
/** @psalm-trace $a */; // array{foo: string, bar: int, ...}
$b = asArray(new B);
/** @psalm-trace $b */; // array{foo: string, bar: int, baz: float}
```
## class-string-map&lt;T as Foo, T&gt;
Used to indicate an array where each value is equal an instance of the class string contained in the key:
```php
<?php
/**
* @psalm-consistent-constructor
*/
class Foo {}
/**
* @psalm-consistent-constructor
*/
class Bar extends Foo {}
class A {
/** @var class-string-map<T as Foo, T> */
private static array $map = [];
/**
* @template U as Foo
* @param class-string<U> $class
* @return U
*/
public static function get(string $class) : Foo {
if (isset(self::$map[$class])) {
return self::$map[$class];
}
self::$map[$class] = new $class();
return self::$map[$class];
}
}
$foo = A::get(Foo::class);
$bar = A::get(Bar::class);
/** @psalm-trace $foo */; // Foo
/** @psalm-trace $bar */; // Bar
```
If we had used an `array<class-string<Foo>, Foo>` instead of a `class-string-map<T as Foo, T>` in the above example, we would've gotten some false positive `InvalidReturnStatement` issues, caused by the lack of a type assertion inside the `isset`.
On the other hand, when using `class-string-map`, Psalm assumes that the value obtained by using a key `class-string<T>` is always equal to `T`.
Unbounded templates can also be used for unrelated classes:
```php
<?php
/**
* @psalm-consistent-constructor
*/
class Foo {}
/**
* @psalm-consistent-constructor
*/
class Bar {}
/**
* @psalm-consistent-constructor
*/
class Baz {}
class A {
/** @var class-string-map<T, T> */
private static array $map = [];
/**
* @template U
* @param class-string<U> $class
* @return U
*/
public static function get(string $class) : object {
if (isset(self::$map[$class])) {
return self::$map[$class];
}
self::$map[$class] = new $class();
return self::$map[$class];
}
}
$foo = A::get(Foo::class);
$bar = A::get(Bar::class);
$baz = A::get(Baz::class);
/** @psalm-trace $foo */; // Foo
/** @psalm-trace $bar */; // Bar
/** @psalm-trace $baz */; // Baz
```
## `T[K]`
Used to get the value corresponding to the specified key:
```php
<?php
/**
* @template T as array
* @template TKey as string
* @param T $arr
* @param TKey $k
* @return T[TKey]
*/
function a(array $arr, string $k): mixed {
assert(isset($arr[$k]));
return $arr[$k];
}
$a = a(['test' => 123], 'test');
/** @psalm-trace $a */; // 123
```
## Type aliases
Psalm allows defining type aliases for complex types (like array shapes) which must be reused often:
```php
/**
* @psalm-type PhoneType = array{phone: string}
*/
class Phone {
/**
* @psalm-return PhoneType
*/
public function toArray(): array {
return ["phone" => "Nokia"];
}
}
```
You can use the [`@psalm-import-type`](../supported_annotations.md#psalm-import-type) annotation to import a type defined with [`@psalm-type`](../supported_annotations.md#psalm-type) if it was defined somewhere else.
```php
<?php
/**
* @psalm-import-type PhoneType from Phone
*/
class User {
/**
* @psalm-return PhoneType
*/
public function toArray(): array {
return array_merge([], (new Phone())->toArray());
}
}
```
You can also alias a type when you import it:
```php
<?php
/**
* @psalm-import-type PhoneType from Phone as MyPhoneTypeAlias
*/
class User {
/**
* @psalm-return MyPhoneTypeAlias
*/
public function toArray(): array {
return array_merge([], (new Phone())->toArray());
}
}
```
## Variable templates
Variable templates allow directly using variables instead of template types, for example instead of the following verbose example:
```php
<?php
/**
* @template TA as string
* @template TB as string
* @template TChoose as bool
* @param TA $a
* @param TB $b
* @param TChoose $choose
* @return (TChoose is true ? TA : TB)
*/
function pick(string $a, string $b, bool $choose): string {
return $choose ? $a : $b;
}
$a = pick('a', 'b', true);
/** @psalm-trace $a */; // 'a'
$a = pick('a', 'b', false);
/** @psalm-trace $a */; // 'b'
```
We can instead use variable templates like so:
```php
<?php
/**
* @return ($choose is true ? $a : $b)
*/
function pick(string $a, string $b, bool $choose): string {
return $choose ? $a : $b;
}
$a = pick('a', 'b', true);
/** @psalm-trace $a */; // 'a'
$a = pick('a', 'b', false);
/** @psalm-trace $a */; // 'b'
```

View File

@@ -0,0 +1,48 @@
# Value types
Psalm also allows you to specify values in types.
## null
This is the `null` value, destroyer of worlds. Use it sparingly. Psalm supports you writing `?Foo` to mean `null|Foo`.
## true, false
Use of `true` and `false` is also PHPDoc-compatible
## "some_string", 4, 3.14
Psalm also allows you specify literal values in types, e.g. `@return "good"|"bad"`
## Regular class constants
Psalm allows you to include class constants in types, e.g. `@return Foo::GOOD|Foo::BAD`. You can also specify explicit class strings e.g. `Foo::class|Bar::class`
If you want to specify that a parameter should only take class strings that are, or extend, a given class, you can use the annotation `@param class-string<Foo> $foo_class`. If you only want the param to accept that exact class string, you can use the annotation `Foo::class`:
```php
<?php
class A {}
class AChild extends A {}
class B {}
class BChild extends B {}
/**
* @param class-string<A>|class-string<B> $s
*/
function foo(string $s) : void {}
/**
* @param A::class|B::class $s
*/
function bar(string $s) : void {}
foo(A::class); // works
foo(AChild::class); // works
foo(B::class); // works
foo(BChild::class); // works
bar(A::class); // works
bar(AChild::class); // fails
bar(B::class); // works
bar(BChild::class); // fails
```

View File

@@ -0,0 +1,173 @@
# Typing in Psalm
Psalm is able to interpret all PHPDoc type annotations, and use them to further understand the codebase.
Types are used to describe acceptable values for properties, variables, function parameters and `return $x`.
## Docblock Type Syntax
Psalm allows you to express a lot of complicated type information in docblocks.
All docblock types are either [atomic types](type_syntax/atomic_types.md), [union types](type_syntax/union_types.md) or [intersection types](type_syntax/intersection_types.md).
Additionally, Psalm supports PHPDocs [type syntax](https://docs.phpdoc.org/latest/guide/guides/types.html), and also the [proposed PHPDoc PSR type syntax](https://github.com/php-fig/fig-standards/blob/master/proposed/phpdoc.md#appendix-a-types).
## Property declaration types vs Assignment typehints
You can use the `/** @var Type */` docblock to annotate both [property declarations](http://php.net/manual/en/language.oop5.properties.php) and to help Psalm understand variable assignment.
### Property declaration types
You can specify a particular type for a class property declaration in Psalm by using the `@var` declaration:
```php
<?php
/** @var string|null */
public $foo;
```
When checking `$this->foo = $some_variable;`, Psalm will check to see whether `$some_variable` is either `string` or `null` and, if neither, emit an issue.
If you leave off the property type docblock, Psalm will emit a `MissingPropertyType` issue.
### Assignment typehints
Consider the following code:
```php
<?php
namespace YourCode {
function bar() : int {
$a = \ThirdParty\foo();
return $a;
}
}
namespace ThirdParty {
function foo() {
return mt_rand(0, 100);
}
}
```
Psalm does not know what the third-party function `ThirdParty\foo` returns, because the author has not added any return types. If you know that the function returns a given value you can use an assignment typehint like so:
```php
<?php
namespace YourCode {
function bar() : int {
/** @var int */
$a = \ThirdParty\foo();
return $a;
}
}
namespace ThirdParty {
function foo() {
return mt_rand(0, 100);
}
}
```
This tells Psalm that `int` is a possible type for `$a`, and allows it to infer that `return $a;` produces an integer.
Unlike property types, however, assignment typehints are not binding they can be overridden by a new assignment without Psalm emitting an issue e.g.
```php
<?php
/** @var string|null */
$a = foo();
$a = 6; // $a is now typed as an int
```
You can also use typehints on specific variables e.g.
```php
<?php
/** @var string $a */
echo strpos($a, 'hello');
```
This tells Psalm to assume that `$a` is a string (though it will still throw an error if `$a` is undefined).
## Specifying string/int options (aka enums)
Psalm allows you to specify a specific set of allowed string/int values for a given function or method.
Whereas this would cause Psalm to [complain that not all paths return a value](https://getpsalm.org/r/9f6f1ceab6):
```php
<?php
function foo(string $s) : string {
switch ($s) {
case 'a':
return 'hello';
case 'b':
return 'goodbye';
}
}
```
If you specify the param type of `$s` as `'a'|'b'` Psalm will know that all paths return a value:
```php
<?php
/**
* @param 'a'|'b' $s
*/
function foo(string $s) : string {
switch ($s) {
case 'a':
return 'hello';
case 'b':
return 'goodbye';
}
}
```
If the values are in class constants, you can use those too:
```php
<?php
class A {
const FOO = 'foo';
const BAR = 'bar';
}
/**
* @param A::FOO | A::BAR $s
*/
function foo(string $s) : string {
switch ($s) {
case A::FOO:
return 'hello';
case A::BAR:
return 'goodbye';
}
}
```
If the class constants share a common prefix, you can specify them all using a wildcard:
```php
<?php
class A {
const STATUS_FOO = 'foo';
const STATUS_BAR = 'bar';
}
/**
* @param A::STATUS_* $s
*/
function foo(string $s) : string {
switch ($s) {
case A::STATUS_FOO:
return 'hello';
default:
// any other status
return 'goodbye';
}
}
```

View File

@@ -0,0 +1,62 @@
# Adding a new issue type
To add a new issue type there are a number of required steps, listed below.
## Generating a new shortcode
Run `bin/max_used_shortcode.php` and note the value it printed (`$max_shortcode`)
## Create issue class
Create a class in `Psalm\Issue` namespace like this:
```php
<?php
namespace Psalm\Issue;
final class MyNewIssue extends CodeIssue
{
public const SHORTCODE = 123;
public const ERROR_LEVEL = 2;
}
```
For `SHORTCODE` value use `$max_shortcode + 1`. To choose appropriate error level see [Error levels](../running_psalm/error_levels.md).
There a number of abstract classes you can extend:
* `CodeIssue` - non specific, default issue. It's a base class for all issues.
* `ClassIssue` - issue related to a specific class (also interface, trait, enum). These issues can be suppressed for specific classes in `psalm.xml` by using `referencedClass` attribute
* `PropertyIssue` - issue related to a specific property. Can be targeted by using `referencedProperty` in `psalm.xml`
* `FunctionIssue` - issue related to a specific function. Can be suppressed with `referencedFunction` attribute.
* `ArgumentIssue` - issue related to a specific argument. Can be targeted with `referencedFunction` attribute.
* `MethodIssue` - issue related to a specific method. Can be targeted with `referencedMethod` attribute.
* `ClassConstantIssue` - issue related ot a specific class constant. Can be targeted with `referencedConstant`.
* `VariableIssue` - issue for a specific variable. Targeted with `referencedVariable`
## Add a `config.xsd` entry
All issue types needs to be listed in `config.xsd`, which is used to validate `psalm.xml`. Choose appropriate `type` attribute. E.g. for issues extending `PropertyIssue` use `type="PropertyIssueHandlerType"`.
## Add a doc page for your new issue
Every issue needs to be documented. Create a markdown file in `docs/running_psalm/issues` folder. Make sure to include a snippet of code illustrating your issue. Important: snippets must use fenced php code block and must include opening PHP tag (`<?php`). The snippet must actually produce the issue you're documenting. It's checked by our test suite.
## Add links to the doc page
Add links to the doc page you created to `docs/running_psalm/error_levels.md` and `docs/running_psalm/issues.md`
## Run documentation tests
```
$ vendor/bin/phpunit tests/DocumentationTest.php
```
It will check whether you did all (or at least most) of the steps above.
## Use your new issue type in Psalm core
```php
IssueBuffer::maybeAdd(new MyNewIssue(...))
```

View File

@@ -0,0 +1,81 @@
# Altering callmaps
## Intro
One of the first things most contributors start with is proposing changes to
callmaps.
Callmap is a data file (formatted as a PHP file returning an array) that tells
Psalm what arguments function/method takes and what it returns.
There are two full callmaps (`CallMap.php` and `CallMap_historical.php`) in
`dictionaries` folder, and a number of delta files that provide information on
how signatures changed in various PHP versions. `CallMap_historical` has
signatures as they were in PHP 7.0, `CallMap.php` contains current signatures
(for PHP 8.1 at the time of writing).
## Full callmap format
Full callmaps (`CallMap.php` and `CallMap_historical.php`) have function/method
names as keys and an array representing the corresponding signature as a value.
First element of that value is a return type (it also doesn't have a key), and
subsequent elements represent function/method parameters. Parameter name for an
optional parameter is postfixed with `=`.
## Delta file format
Delta files (named `CallMap_<PHP major version><PHP minor version>_delta.php`)
list changes that happened in the corresponding PHP version. There are
three section with self-explanatory names: `added` (for functions/methods that
were added in that PHP version), `removed` (for those that were removed) and
`changed`.
Entry format for `removed` and `added` section matches that of a full callmap,
while `changed` entries list `old` and `new` signatures.
## How Psalm uses delta files
When the current PHP version is set to something other than the latest PHP
version supported by Psalm, it needs to process delta files to arrive at a
version of callmap matching the one that is used during analysis. Psalm uses
the following process to do that:
1. Read `CallMap.php` (Note: it's the one having the latest signatures).
2. If it matches configured PHP version, use it.
3. If the callmap delta for previous PHP version exists, read that.
4. Take previous callmap delta and apply it in reverse order. That is, entries
in `removed` section are added, those in `added` section are removed and
`changed.new` signatures in the current callamp are replaced with
`changed.old`.
5. Goto 2
## Consistent histories
To make sure there are no mismatches in deltas and the callmap, CI validates
that all function/method entries have consistent histories. E.g. that the
signature in `changed.new` matches the one in `CallMap.php`, the `removed`
entries are actually absent from `CallMap.php` and so on.
## Typical changes
To put that into practical perspective, let's see how a couple of typical
callmap changes may look like.
### Adding a new function
Say, there's a function added in PHP 8.1, e.g. `array_is_list()`. Add it to the
`CallMap_81_delta.php` (as it was introduced in PHP 8.1), and `CallMap.php` (as
it exists in the latest PHP version). Here's [the PR that does it](https://github.com/vimeo/psalm/pull/6398/files).
### Correcting the function signature
Assume you found an incorrect signature, the one that was always different to what
we currently have in Psalm. This will need a change to `CallMap_historical.php`
(as the signature was always that way) and `CallMap.php` (as the signature is
still valid). Here's [the PR that does it](https://github.com/vimeo/psalm/pull/6359/files).
If function signature is correct for an older version but has changed since you
will need to edit the delta for PHP version where signature changed and
`CallMap.php` (as this new signature is still valid). Here's
[the PR that does it (makes `timestamp` nullable)](https://github.com/vimeo/psalm/pull/6244/files).

View File

@@ -0,0 +1,73 @@
# How Psalm works
The entry point for all analysis is [`ProjectAnalyzer`](https://github.com/vimeo/psalm/blob/master/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php)
`ProjectAnalyzer` is in charge of two things: Scanning and Analysis
## Scanning
For any file or set of files, Psalm needs to determine all the possible dependencies and get their function signatures and constants, so that the analysis phase can be done multi-threaded.
Scanning happens in `Psalm\Internal\Codebase\Scanner`.
The first task is to convert a file into a set of [PHP Parser](https://github.com/nikic/PHP-Parser) statements. PHP Parser converts PHP code into an abstract syntax tree that Psalm uses for all its analysis.
### Deep scanning vs shallow scanning
Psalm then uses a custom PHP Parser `NodeVisitor` called [`ReflectorVisitor`](https://github.com/vimeo/psalm/blob/master/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php) which has two modes when scanning a given file: a shallow scan, where it just gets function signatures, return types, constants, and inheritance, or a deep scan, where it drills into every function statement to get those dependencies too (e.g. the class names instantiated by a function). It only does a deep scan on files that it knows will be analysed later (so the vast majority of the vendor directory, for example, just gets a shallow scan).
So, when analysing the `src` directory, Psalm will deep scan the following file:
src/A.php
```php
<?php
use Vendor\VendorClass;
use Vendor\OtherVendorClass;
class A extends VendorClass
{
public function foo(OtherVendorClass $c): void {}
}
```
And will also deep scan the file belonging to `Vendor\VendorClass`, because it may have to check instantiations of properties at some point.
It will do a shallow scan of `Vendor\OtherVendorClass` (and any dependents) because all it cares about are the method signatures and return types of the variable `$c`.
### Finding files from class names
To figure out the `ClassName` => `src/FileName.php` mapping it uses reflection for project files and the Composer classmap for vendor files.
### Storing data from scanning
For each file that `ReflectorVisitor` visits, Psalm creates a [`FileStorage`](https://github.com/vimeo/psalm/blob/master/src/Psalm/Storage/FileStorage.php) instance, along with [`ClassLikeStorage`](https://github.com/vimeo/psalm/blob/master/src/Psalm/Storage/ClassLikeStorage.php) and [`FunctionLikeStorage`](https://github.com/vimeo/psalm/blob/master/src/Psalm/Storage/FunctionLikeStorage.php) instances depending on the file contents.
Once we have a set of all files and their classes and function signatures, we calculate inheritance for everything in the [`Populator`](https://github.com/vimeo/psalm/blob/master/src/Psalm/Internal/Codebase/Populator.php) class and then move onto analysis.
At the end of the scanning step we have populated all the necessary information in [`ClassLikes`](https://github.com/vimeo/psalm/blob/master/src/Psalm/Internal/Codebase/ClassLikes.php), [`Functions`](https://github.com/vimeo/psalm/blob/master/src/Psalm/Internal/Codebase/Functions.php) and [`Methods`](https://github.com/vimeo/psalm/blob/master/src/Psalm/Internal/Codebase/Methods.php) classes, and created a complete list of `FileStorage` and `ClassLikeStorage` objects (in [`FileStorageProvider`](https://github.com/vimeo/psalm/blob/master/src/Psalm/Internal/Provider/FileStorageProvider.php) and [`ClassLikeStorageProvider`](https://github.com/vimeo/psalm/blob/master/src/Psalm/Internal/Provider/ClassLikeStorageProvider.php) respectively) for all classes and files used in our project.
## Analysis
We analyse files in [`FileAnalyzer`](https://github.com/vimeo/psalm/blob/master/src/Psalm/Internal/Analyzer/FileAnalyzer.php)
The `FileAnalyzer` takes a given file and looks for a set of top-level components: classes, traits, interfaces, functions. It can look inside namespaces and extract the classes, interfaces, traits and functions in them as well.
It delegates the analysis of those components to [`ClassAnalyzer`](https://github.com/vimeo/psalm/blob/master/src/Psalm/Internal/Analyzer/ClassAnalyzer.php), [`InterfaceAnalyzer`](https://github.com/vimeo/psalm/blob/master/src/Psalm/Internal/Analyzer/InterfaceAnalyzer.php) and [`FunctionAnalyzer`](https://github.com/vimeo/psalm/blob/master/src/Psalm/Internal/Analyzer/FunctionAnalyzer.php).
Because its the most basic use case for the line-by-line analysis (no class inheritance to worry about), lets drill down into `FunctionAnalyzer`.
### Function Analysis
`FunctionAnalyzer::analyze` is defined in [`FunctionLikeAnalyzer`](https://github.com/vimeo/psalm/blob/master/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php). That method first gets the `FunctionLikeStorage` object for the given function that we created in our scanning step. That `FunctionLikeStorage` object has information about function parameters, which we then feed into a [`Context`](https://github.com/vimeo/psalm/blob/master/src/Psalm/Context.php) object. The `Context` contains all the type information we know about variables & properties (stored in `Context::$vars_in_scope`) and also a whole bunch of other information that can change depending on assignments and assertions.
Somewhere in `FunctionLikeAnalyzer::analyze` we create a new [`StatementsAnalyzer`](https://github.com/vimeo/psalm/blob/master/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php) and then call its `analyze()` method, passing in a set of PhpParser nodes. `StatementAnalyzer::analyze` passes off to a number of different checkers (`IfAnalyzer`, `ForeachAnalyzer`, `ExpressionAnalyzer` etc.) for more thorough analysis.
At each line the `Context` object may or may not be manipulated. At branching points (if statements, loops, ternary etc) the `Context` object is cloned and then, at the end of the branch, Psalm figures out how to resolve the changes and update the uncloned `Context` object.
The `NodeDataProvider` stores a type for each PhpParser node.
After all the statements have been analysed we gather up all the return types and compare them to the given return type.
### Type Reconciliation
While some updates to the `Context` object are straightforward, others are not. Updating the `Context` object in the light of new type information happens in [`Reconciler`](https://github.com/vimeo/psalm/blob/master/src/Psalm/Type/Reconciler.php), which takes an array assertions e.g. `[“$a” => “!null”]` and a list of existing type information e.g. `$a => string|null` and return a set of updated information e.g. `$a => string`

View File

@@ -0,0 +1,40 @@
# Contributing to Psalm
Psalm is made possible through the contributions of [hundreds of developers](https://github.com/vimeo/psalm/graphs/contributors).
Hopefully you can be one of them?
## Getting started
[Heres a rough guide to the codebase](how_psalm_works.md).
[Here's the philosophy underpinning the Psalms development](philosophy.md).
I've also put together [a list of Psalms complexities](what_makes_psalm_complicated.md).
Are you looking for low-hanging fruit? Here are some [GitHub issues](https://github.com/vimeo/psalm/issues?q=is%3Aissue+is%3Aopen+label%3A%22easy+problems%22) that shouldn't be too difficult to resolve.
### Dont be afraid!
One great thing about working on Psalm is that its _very_ hard to introduce any sort of type error in Psalms codebase. There are almost 5,000 PHPUnit tests, so the risk of you messing up (without the CI system noticing) is very small.
### Why static analysis is cool
Day-to-day PHP programming involves solving concrete problems, but they're rarely very complex. Psalm, on the other hand, attempts to solve a pretty hard collection of problems, which then allows it to detect a ton of bugs in PHP code without actually executing that code.
There's a lot of interesting theory behind the things Psalm does, too. If you want you can go very deep, though you don't need to know really any theory to improve Psalm.
Lastly, working to improve static analysis tools will also make you a better PHP developer it'll help you think more about how values flow through your program.
### Guides
* [Editing callmaps](editing_callmaps.md)
* [Adding a new issue type](adding_issues.md)
## Pull Requests
Before you send a pull request, make sure you follow these guidelines:
Run integration checks locally: `composer tests`
If you're adding new features or fixing bugs, dont forget to add tests!

View File

@@ -0,0 +1,70 @@
# Philosophy
This is about why Psalm is the way it is. This is a living document!
## Psalm is a tool for PHP written in PHP
### Advantages
- PHP is fast enough for most use-cases
- Writing the tool in PHP guarantees that PHP community members can contribute to it without too much difficulty
### Drawbacks
- Psalm is slow in very large monorepos (over 5 million lines of checked-in code).
- Psalms language server is more limited in what it can provide than comparable compiled-language tools. For example, it's infeasible to find/change all occurrences of a symbol, in all files that use it, as you type.
### Comparison to other languages & tools
Many languages have typecheckers/compilers written in the same language. Popular examples include Go, Rust, and TypeScript. Python is a special case where the semi-official typechecker [MyPy](https://github.com/python/mypy) (written in Python) can also [compile to a C Python extension](https://github.com/python/mypy/blame/master/mypyc/README.md#L6-L10), which runs 4x faster than the interpreted equivalent.
Some interpreted languages have unofficial open-source typecheckers written in faster compiled languages. In all cases a single mid-to-large company is behind each effort, with a small number of contributors not employed by that company:
- PHP
- [NoVerify](https://github.com/VKCOM/noverify) written in Go. Runs much faster than Psalm (but does not support many modern PHP features)
- Ruby
- [Sorbet](https://sorbet.org/) - written in C
- Python
- [Pyre](https://github.com/facebook/pyre-check) - written in OCaml
- [Hack](https://github.com/facebook/hhvm) - the typechecker is written in OCaml and Rust
## Psalm's primary purpose is finding bugs
Psalm does a lot, but people mainly use it to find potential bugs in their code.
All other functionality the language server, security analysis, manipulating/fixing code is a secondary concern.
## It's designed to be run on syntactically-correct code
Psalm is almost always run on PHP code that parses a lint check (`php -l <filename>`) i.e. syntactically-correct code. Psalm is not a replacement for that syntax check.
Given Psalm is almost always used on syntatically-correct code it should use a parser built for that purpose, and `nikic/php-parser` is the gold-standard.
Where Psalm needs to run on syntactically-incorrect code (e.g. in language server mode) Psalm should still use the same parser (and work around any issues that it produces).
## Docblock annotations are better than type-providing plugins
Psalm offers a plugin API that allows you to tell it what about your program's property types, return types and parameter types.
Psalm aims to operate in a space with other static analysis tools. The more users rely on plugins, the less chance those other tools have to understand the user's intent.
Psalm should encourage developers to use docblock annotations rather than type-providing plugins. This was a driving force in the adoption of [Conditional Types](../annotating_code/type_syntax/conditional_types.md) which allowed Psalm to replace some of its own internal type-providing plugins.
The other benefit to docblock annotations is verifiability for the most part Psalm is able to verify that docblock annotations are correct, but it cannot provide many assurances when plugins are used.
This doesnt mean that plugins as a whole are bad, or that they cant provide useful types. A great adjacent use of plugins is to provide stubs with Psalm type annotations for libraries that dont have them. This helps the PHP ecosystem because those stubs may eventually make their way into the project currently being stubbed.
## Docblock annotations that can be verified are better than those that cannot
Psalm currently supports a number of function/class docblock annotations that it's unable to verify:
- `@psalm-assert`, `@psalm-assert-if-true`, `@psalm-assert-if-false`
- `@property`, `@method`, `@mixin`
Whenever new docblock annotations are added, effort should be made to allow Psalm to verify their correctness.
## In certain circumstances docblock annotations are better than PHP 8 attributes
For information that's just designed to be consumed by static analysis tools, docblocks are a better home than PHP 8 attributes.
A rationale is provided in [this article](https://psalm.dev/articles/php-8-attributes).

View File

@@ -0,0 +1,82 @@
# Things that make developing Psalm complicated
This is a somewhat informal list that might aid others.
## Type inference
Type inference is one of the big things Psalm does. It tries to figure out what different PHP elements (function calls, if/for/foreach statements etc.) mean for the data in your code.
Within type inference there are a number of tricky areas:
#### Loops
Loops are hard to reason about - break and continue are a pain. This analysis mainly takes place in `LoopAnalyzer`
#### Combining types
There are lots of edge-cases when combining types together, given the many types Psalm supports. Type combining occurs in `TypeCombiner`.
#### Logical assertions
What effect do different PHP elements have on user-asserted logic in if conditionals, ternarys etc. This logic is spread between a number of different classes.
#### Generics & Templated code
Figuring out how templated code should work (`@template` tags) and how much it should work like it does in other languages (Hack, TypeScript etc.) is tricky. Psalm also supports things like nested templates (`@template T1 of T2`) which makes things trickier
## Detecting dead code
Detecting unused variables requires some fun [data-flow analysis](https://psalm.dev/articles/better-unused-variable-detection).
Detecting unused classes and methods between different runs requires maintaining references to those classes in cache (see below).
## Supporting the community
- **Supporting formal PHPDoc annotations**
- **Supporting informal PHPDoc annotations**
e.g. `ArrayIterator|string[]` to denote an `ArrayIterator` over strings
- **non-Composer projects**
e.g. WordPress
## Making Psalm fast
#### Parser-based reflection
Requires scanning everything necessary for analysis
#### Forking processes** (non-windows)
Mostly handled by code borrowed from Phan, but can introduce subtle issues, also requires to think about how to make work happen in processes
#### Caching thing
see below
## Cache invalidation
#### Invalidating analysis results
Requires tracking what methods/properties are used in what other files, and invalidating those results when linked methods change
#### Partial parsing
Reparsing bits of files that have changed, which is hard
## Language Server Support
#### Handling temporary file changes
When files change Psalm figures out what's changed within them to avoid re-analysing things unnecessarily
#### Dealing with malformed PHP code
When people write code, it's not always pretty as they write it. A language server needs to deal with that bad code somehow
## Fixing code with Psalter
#### Adding/replacing code
Figuring out what changed, making edits that could have been made by a human
#### Minimal diffs
hard to change more than you need

View File

@@ -0,0 +1,615 @@
# Fixing Code
Psalm is good at finding potential issues in large codebases, but once found, it can be something of a gargantuan task to fix all the issues.
It comes with a tool, called Psalter, that helps you fix code.
You can either run it via its binary
```
vendor/bin/psalter [args]
```
or via Psalm's binary:
```
vendor/bin/psalm --alter [args]
```
## Safety features
Updating code is inherently risky, doing so automatically is even more so. I've added a few features to make it a little more reassuring:
- To see what changes Psalter will make ahead of time, you can run it with `--dry-run`.
- You can target particular versions of PHP via `--php-version`, so that (for example) you don't add nullable typehints to PHP 7.0 code, or any typehints at all to PHP 5.6 code. `--php-version` defaults to your current version.
- it has a `--safe-types` mode that will only update PHP 7 return typehints with information Psalm has gathered from non-docblock sources of type information (e.g. typehinted params, `instanceof` checks, other return typehints etc.)
- using `--allow-backwards-incompatible-changes=false` you can make sure to not create backwards incompatible changes
## Plugins
You can pass in your own manipulation plugins e.g.
```bash
vendor/bin/psalter --plugin=vendor/vimeo/psalm/examples/plugins/ClassUnqualifier.php --dry-run
```
The above example plugin converts all unnecessarily qualified classnames in your code to shorter aliased versions.
## Supported fixes
This initial release provides support for the following alterations, corresponding to the names of issues Psalm finds.
To fix all of these at once, run `vendor/bin/psalter --issues=all`
### MissingReturnType
Running `vendor/bin/psalter --issues=MissingReturnType --php-version=7.0` on
```php
<?php
function foo() {
return "hello";
}
```
gives
```php
<?php
function foo() : string {
return "hello";
}
```
and running `vendor/bin/psalter --issues=MissingReturnType --php-version=5.6` on
```php
<?php
function foo() {
return "hello";
}
```
gives
```php
<?php
/**
* @return string
*/
function foo() {
return "hello";
}
```
### MissingClosureReturnType
As above, except for closures
### InvalidReturnType
Running `vendor/bin/psalter --issues=InvalidReturnType` on
```php
<?php
/**
* @return int
*/
function foo() {
return "hello";
}
```
gives
```php
<?php
/**
* @return string
*/
function foo() {
return "hello";
}
```
There's also support for return typehints, so running `vendor/bin/psalter --issues=InvalidReturnType` on
```php
<?php
function foo() : int {
return "hello";
}
```
gives
```php
<?php
function foo() : string {
return "hello";
}
```
### InvalidNullableReturnType
Running `vendor/bin/psalter --issues=InvalidNullableReturnType --php-version=7.1` on
```php
<?php
function foo() : string {
return rand(0, 1) ? "hello" : null;
}
```
gives
```php
<?php
function foo() : ?string {
return rand(0, 1) ? "hello" : null;
}
```
and running `vendor/bin/psalter --issues=InvalidNullableReturnType --php-version=7.0` on
```php
<?php
function foo() : string {
return rand(0, 1) ? "hello" : null;
}
```
gives
```php
<?php
/**
* @return string|null
*/
function foo() {
return rand(0, 1) ? "hello" : null;
}
```
### InvalidFalsableReturnType
Running `vendor/bin/psalter --issues=InvalidFalsableReturnType` on
```php
<?php
function foo() : string {
return rand(0, 1) ? "hello" : false;
}
```
gives
```php
<?php
/**
* @return string|false
*/
function foo() {
return rand(0, 1) ? "hello" : false;
}
```
### MissingParamType
Running `vendor/bin/psalter --issues=MissingParamType` on
```php
<?php
class C {
public static function foo($s) : void {
echo $s;
}
}
C::foo("hello");
```
gives
```php
<?php
class C {
/**
* @param string $s
*/
public static function foo($s) : void {
echo $s;
}
}
C::foo("hello");
```
### MissingPropertyType
Running `vendor/bin/psalter --issues=MissingPropertyType` on
```php
<?php
class A {
public $foo;
public $bar;
public $baz;
public function __construct()
{
if (rand(0, 1)) {
$this->foo = 5;
} else {
$this->foo = "hello";
}
$this->bar = "baz";
}
public function setBaz() {
$this->baz = [1, 2, 3];
}
}
```
gives
```php
<?php
class A {
/**
* @var string|int
*/
public $foo;
public string $bar;
/**
* @var array<int, int>|null
* @psalm-var non-empty-list<int>|null
*/
public $baz;
public function __construct()
{
if (rand(0, 1)) {
$this->foo = 5;
} else {
$this->foo = "hello";
}
$this->bar = "baz";
}
public function setBaz() {
$this->baz = [1, 2, 3];
}
}
```
### MismatchingDocblockParamType
Given
```php
<?php
class A {}
class B extends A {}
class C extends A {}
class D {}
```
running `vendor/bin/psalter --issues=MismatchingDocblockParamType` on
```php
<?php
/**
* @param B|C $first
* @param D $second
*/
function foo(A $first, A $second) : void {}
```
gives
```php
<?php
/**
* @param B|C $first
* @param A $second
*/
function foo(A $first, A $second) : void {}
```
### MismatchingDocblockReturnType
Running `vendor/bin/psalter --issues=MismatchingDocblockReturnType` on
```php
<?php
/**
* @return int
*/
function foo() : string {
return "hello";
}
```
gives
```php
<?php
/**
* @return string
*/
function foo() : string {
return "hello";
}
```
### LessSpecificReturnType
Running `vendor/bin/psalter --issues=LessSpecificReturnType` on
```php
<?php
function foo() : ?string {
return "hello";
}
```
gives
```php
<?php
function foo() : string {
return "hello";
}
```
### PossiblyUndefinedVariable
Running `vendor/bin/psalter --issues=PossiblyUndefinedVariable` on
```php
<?php
function foo()
{
if (rand(0, 1)) {
$a = 5;
}
echo $a;
}
```
gives
```php
<?php
function foo()
{
$a = null;
if (rand(0, 1)) {
$a = 5;
}
echo $a;
}
```
### PossiblyUndefinedGlobalVariable
Running `vendor/bin/psalter --issues=PossiblyUndefinedGlobalVariable` on
```php
<?php
if (rand(0, 1)) {
$a = 5;
}
echo $a;
```
gives
```php
<?php
$a = null;
if (rand(0, 1)) {
$a = 5;
}
echo $a;
```
### UnusedMethod
This removes private unused methods.
Running `vendor/bin/psalter --issues=UnusedMethod` on
```php
<?php
class A {
private function foo() : void {}
}
new A();
```
gives
```php
<?php
class A {
}
new A();
```
### PossiblyUnusedMethod
This removes protected/public unused methods.
Running `vendor/bin/psalter --issues=PossiblyUnusedMethod` on
```php
<?php
class A {
protected function foo() : void {}
public function bar() : void {}
}
new A();
```
gives
```php
<?php
class A {
}
new A();
```
### UnusedProperty
This removes private unused properties.
Running `vendor/bin/psalter --issues=UnusedProperty` on
```php
<?php
class A {
/** @var string */
private $foo;
}
new A();
```
gives
```php
<?php
class A {
}
new A();
```
### PossiblyUnusedProperty
This removes protected/public unused properties.
Running `vendor/bin/psalter --issues=PossiblyUnusedProperty` on
```php
<?php
class A {
/** @var string */
public $foo;
/** @var string */
protected $bar;
}
new A();
```
gives
```php
<?php
class A {
}
new A();
```
### UnusedVariable
This removes unused variables.
Running `vendor/bin/psalter --issues=UnusedVariable` on
```php
<?php
function foo(string $s) : void {
$a = 5;
$b = 6;
$c = $b += $a -= intval($s);
echo "foo";
}
```
gives
```php
<?php
function foo(string $s) : void {
echo "foo";
}
```
### UnnecessaryVarAnnotation
This removes unused `@var` annotations
Running `vendor/bin/psalter --issues=UnnecessaryVarAnnotation` on
```php
<?php
function foo() : string {
return "hello";
}
/** @var string */
$a = foo();
```
gives
```php
<?php
function foo() : string {
return "hello";
}
$a = foo();
```
### ParamNameMismatch
This aligns child class param names with their parent.
Running `vendor/bin/psalter --issues=ParamNameMismatch` on
```php
<?php
class A {
public function foo(string $str, bool $b = false) : void {}
}
class AChild extends A {
public function foo(string $string, bool $b = false) : void {
echo $string;
}
}
```
gives
```php
<?php
class A {
public function foo(string $str, bool $b = false) : void {}
}
class AChild extends A {
public function foo(string $str, bool $b = false) : void {
echo $str;
}
}
```

View File

@@ -0,0 +1,45 @@
# Refactoring Code
Sometimes you want to make big changes to your codebase like moving methods or classes.
Psalm has a refactoring tool you can access with either `vendor/bin/psalm-refactor` or `vendor/bin/psalm --refactor`, followed by appropriate commands.
## Moving all classes from one namespace to another
```
vendor/bin/psalm-refactor --move "Ns1\*" --into "Some\Other\Namespace"
```
This moves all classes in `Ns1` (e.g. `Ns1\Foo`, `Ns1\Baz`) into the specified namespace. Files are moved as necessary.
## Moving classes between namespaces
```
vendor/bin/psalm-refactor --move "Ns1\Foo" --into "Ns2"
```
This tells Psalm to move class `Ns1\Foo` into the namespace `Ns2`, so any reference to `Ns1\Foo` becomes `Ns2\Foo`). Files are moved as necessary.
## Moving and renaming classes
```
vendor/bin/psalm-refactor --rename "Ns1\Foo" --to "Ns2\Bar\Baz"
```
This tells Psalm to move class `Ns1\Foo` into the namespace `Ns2\Bar` and rename it to `Baz`, so any reference to `Ns1\Foo` becomes `Ns2\Bar\Baz`). Files are moved as necessary.
## Moving methods between classes
```
vendor/bin/psalm-refactor --move "Ns1\Foo::bar" --into "Ns2\Baz"
```
This tells Psalm to move a method named `bar` inside `Ns1\Foo` into the class `Ns2\Baz`, so any reference to `Ns1\Foo::bar` becomes `Ns2\Baz::bar`). Psalm currently allows you to move static methods between arbitrary classes, and instance methods into child classes of that instance.
## Moving and renaming methods
```
vendor/bin/psalm-refactor --rename "Ns1\Foo::bar" --to "Ns2\Baz::bat"
```
This tells Psalm to move method `Ns1\Foo::bar` into the class `Ns2\Baz` and rename it to `bat`, so any reference to `Ns1\Foo::bar` becomes `Ns2\Baz::bat`).

View File

@@ -0,0 +1,43 @@
# Checking non-PHP files
Psalm supports the ability to check various PHPish files by extending the `FileChecker` class. For example, if you have a template where the variables are set elsewhere, Psalm can scrape those variables and check the template with those variables pre-populated.
An example TemplateChecker is provided [here](https://github.com/vimeo/psalm/blob/master/examples/TemplateChecker.php).
## Using `psalm.xml`
To ensure your custom `FileChecker` is used, you must update the Psalm `fileExtensions` config in psalm.xml:
```xml
<fileExtensions>
<extension name=".php" />
<extension name=".phpt" checker="path/to/TemplateChecker.php" />
</fileExtensions>
```
## Using custom plugin
Plugins can register their own custom scanner and analyzer implementations for particular file extensions.
```php
<?php
namespace Psalm\Example;
use Psalm\Plugin\PluginEntryPointInterface;
use Psalm\Plugin\PluginFileExtensionsInterface;
use Psalm\Plugin\FileExtensionsInterface;
use Psalm\Plugin\RegistrationInterface;
class CustomPlugin implements PluginEntryPointInterface, PluginFileExtensionsInterface
{
public function __invoke(RegistrationInterface $registration, ?\SimpleXMLElement $config = null): void
{
// ... regular plugin processes, stub registration, hook registration
}
public function processFileExtensions(FileExtensionsInterface $fileExtensions, ?SimpleXMLElement $config = null): void
{
$fileExtensions->addFileTypeScanner('phpt', TemplateScanner::class);
$fileExtensions->addFileTypeAnalyzer('phpt', TemplateAnalyzer::class);
}
}
```

View File

@@ -0,0 +1,47 @@
# Running Psalm
Once you've set up your config file, you can run Psalm from your project's root directory with
```bash
./vendor/bin/psalm
```
and Psalm will scan all files in the project referenced by `<projectFiles>`.
If you want to run on specific files, use
```bash
./vendor/bin/psalm file1.php [file2.php...]
```
## Command-line options
Run with `--help` to see a list of options that Psalm supports.
## Exit status
Psalm exits with status `0` when it successfully completed and found no issues,
`1` when there was a problem running Psalm and `2` when it completed
successfully but found some issues. Any exit status apart from those indicate
some internal problem.
## Shepherd
Psalm currently offers some GitHub integration with public projects.
Add `--shepherd` to send information about your build to https://shepherd.dev.
Currently, Shepherd tracks type coverage (the percentage of types Psalm can infer) on `master` branches.
## Running Psalm faster
Psalm has a couple of command-line options that will result in faster builds:
- `--threads=[n]` to run Psalms analysis in a number of threads
- `--diff` which only checks files youve updated since the last run (and their dependents).
In Psalm 4 `--diff` is turned on by default (you can disable it with `--no-diff`).
Data from the last run is stored in the *cache directory*, which may be set in [configuration](./configuration.md).
If you are running Psalm on a build server, you may want to configure the server to ensure that the cache directory
is preserved between runs.
Running them together (e.g. `--threads=8 --diff`) will result in the fastest possible Psalm run.

View File

@@ -0,0 +1,621 @@
# Configuration
Psalm uses an XML config file (by default, `psalm.xml`). A barebones example looks like this:
```xml
<?xml version="1.0"?>
<psalm>
<projectFiles>
<directory name="src" />
</projectFiles>
</psalm>
```
Configuration file may be split into several files using [XInclude](https://www.w3.org/TR/xinclude/) tags (c.f. previous example):
#### psalm.xml
```xml
<?xml version="1.0"?>
<psalm
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
xmlns:xi="http://www.w3.org/2001/XInclude"
>
<xi:include href="files.xml"/>
</psalm>
```
#### files.xml
```xml
<?xml version="1.0" encoding="UTF-8"?>
<projectFiles xmlns="https://getpsalm.org/schema/config">
<file name="Bar.php" />
<file name="Bat.php" />
</projectFiles>
```
## Optional &lt;psalm /&gt; attributes
### Coding style
#### errorLevel
```xml
<psalm
errorLevel="[int]"
/>
```
This corresponds to Psalms [error-detection level](error_levels.md).
#### reportMixedIssues
```xml
<psalm
reportMixedIssues="[bool]"
/>
```
Setting this to `"false"` hides all issues with `Mixed` types in Psalms output. If not given, this defaults to `"false"` when `errorLevel` is 3 or higher, and `"true"` when the error level is 1 or 2.
#### totallyTyped
\(Deprecated\) This setting has been replaced by `reportMixedIssues` which is automatically enabled when `errorLevel` is 1.
#### resolveFromConfigFile
```xml
<psalm
resolveFromConfigFile="[bool]"
/>
```
If this is enabled, relative directories mentioned in the config file will be resolved relative to the location
of the config file. If it is disabled, or absent they will be resolved relative to the working directory of the Psalm process.
New versions of Psalm enable this option when generating config files. Older versions did not include it.
#### useDocblockTypes
```xml
<psalm
useDocblockTypes="[bool]"
>
```
Whether or not to use types as defined in docblocks. Defaults to `true`.
#### useDocblockPropertyTypes
```xml
<psalm
useDocblockPropertyTypes="[bool]"
>
```
If not using all docblock types, you can still use docblock property types. Defaults to `false` (though only relevant if `useDocblockTypes` is `false`).
#### usePhpDocMethodsWithoutMagicCall
```xml
<psalm
usePhpDocMethodsWithoutMagicCall="[bool]"
>
```
The PHPDoc `@method` annotation normally only applies to classes with a `__call` method. Setting this to `true` allows you to use the `@method` annotation to override inherited method return types. Defaults to `false`.
#### usePhpDocPropertiesWithoutMagicCall
```xml
<psalm
usePhpDocPropertiesWithoutMagicCall="[bool]"
>
```
The PHPDoc `@property`, `@property-read` and `@property-write` annotations normally only apply to classes with `__get`/`__set` methods. Setting this to `true` allows you to use the `@property`, `@property-read` and `@property-write` annotations to override property existence checks and resulting property types. Defaults to `false`.
#### disableVarParsing
```xml
<psalm
disableVarParsing="[bool]"
/>
```
Disables parsing of `@var` PHPDocs everywhere except for properties. Setting this to `true` can remove many false positives due to outdated `@var` annotations, used before integrations of Psalm generics and proper typing, enforcing Single Source Of Truth principles. Defaults to `false`.
#### strictBinaryOperands
```xml
<psalm
strictBinaryOperands="[bool]"
>
```
If true we force strict typing on numerical and string operations (see https://github.com/vimeo/psalm/issues/24). Defaults to `false`.
#### rememberPropertyAssignmentsAfterCall
```xml
<psalm
rememberPropertyAssignmentsAfterCall="[bool]"
>
```
Setting this to `false` means that any function calls will cause Psalm to forget anything it knew about object properties within the scope of the function it's currently analysing. This duplicates functionality that Hack has. Defaults to `true`.
#### allowStringToStandInForClass
```xml
<psalm
allowStringToStandInForClass="[bool]"
>
```
When `true`, strings can be used as classes, meaning `$some_string::someMethod()` is allowed. If `false`, only class constant strings (of the form `Foo\Bar::class`) can stand in for classes, otherwise an `InvalidStringClass` issue is emitted. Defaults to `false`.
#### disableSuppressAll
```xml
<psalm
disableSuppressAll="[bool]"
>
```
When `true`, disables wildcard suppression of all issues with `@psalm-suppress all`. Defaults to `false`.
#### memoizeMethodCallResults
```xml
<psalm
memoizeMethodCallResults="[bool]"
>
```
When `true`, the results of method calls without arguments passed are remembered between repeated calls of that method on a given object. Defaults to `false`.
#### hoistConstants
```xml
<psalm
hoistConstants="[bool]"
>
```
When `true`, constants defined in a function in a file are assumed to be available when requiring that file, and not just when calling that function. Defaults to `false` (i.e. constants defined in functions will *only* be available for use when that function is called)
#### addParamDefaultToDocblockType
```xml
<psalm
addParamDefaultToDocblockType="[bool]"
>
```
Occasionally a param default will not match up with the docblock type. By default, Psalm emits an issue. Setting this flag to `true` causes it to expand the param type to include the param default. Defaults to `false`.
#### checkForThrowsDocblock
```xml
<psalm
checkForThrowsDocblock="[bool]"
>
```
When `true`, Psalm will check that the developer has supplied `@throws` docblocks for every exception thrown in a given function or method. Defaults to `false`.
#### checkForThrowsInGlobalScope
```xml
<psalm
checkForThrowsInGlobalScope="[bool]"
>
```
When `true`, Psalm will check that the developer has caught every exception in global scope. Defaults to `false`.
#### ignoreInternalFunctionFalseReturn
```xml
<psalm
ignoreInternalFunctionFalseReturn="[bool]"
>
```
When `true`, Psalm ignores possibly-false issues stemming from return values of internal functions (like `preg_split`) that may return false, but do so rarely. Defaults to `true`.
#### ignoreInternalFunctionNullReturn
```xml
<psalm
ignoreInternalFunctionNullReturn="[bool]"
>
```
When `true`, Psalm ignores possibly-null issues stemming from return values of internal array functions (like `current`) that may return null, but do so rarely. Defaults to `true`.
#### inferPropertyTypesFromConstructor
```xml
<psalm
inferPropertyTypesFromConstructor="[bool]"
>
```
When `true`, Psalm infers property types from assignments seen in straightforward constructors. Defaults to `true`.
#### findUnusedVariablesAndParams
```xml
<psalm
findUnusedVariablesAndParams="[bool]"
>
```
When `true`, Psalm will attempt to find all unused variables, the equivalent of running with `--find-unused-variables`. Defaults to `false`.
#### findUnusedCode
```xml
<psalm
findUnusedCode="[bool]"
>
```
When `true`, Psalm will attempt to find all unused code (including unused variables), the equivalent of running with `--find-unused-code`. Defaults to `false`.
#### findUnusedPsalmSuppress
```xml
<psalm
findUnusedPsalmSuppress="[bool]"
>
```
When `true`, Psalm will report all `@psalm-suppress` annotations that aren't used, the equivalent of running with `--find-unused-psalm-suppress`. Defaults to `false`.
#### ensureArrayStringOffsetsExist
```xml
<psalm
ensureArrayStringOffsetsExist="[bool]"
>
```
When `true`, Psalm will complain when referencing an explicit string offset on an array e.g. `$arr['foo']` without a user first asserting that it exists (either via an `isset` check or via an object-like array). Defaults to `false`.
#### ensureArrayIntOffsetsExist
```xml
<psalm
ensureArrayIntOffsetsExist="[bool]"
>
```
When `true`, Psalm will complain when referencing an explicit integer offset on an array e.g. `$arr[7]` without a user first asserting that it exists (either via an `isset` check or via an object-like array). Defaults to `false`.
#### phpVersion
```xml
<psalm
phpVersion="[string]"
>
```
Set the php version Psalm should assume when checking and/or fixing the project. If this attribute is not set, Psalm uses the declaration in `composer.json` if one is present. It will check against the earliest version of PHP that satisfies the declared `php` dependency
This can be overridden on the command-line using the `--php-version=` flag which takes the highest precedence over both the `phpVersion` setting and the version derived from `composer.json`.
#### skipChecksOnUnresolvableIncludes
```xml
<psalm
skipChecksOnUnresolvableIncludes="[bool]"
>
```
When `true`, Psalm will skip checking classes, variables and functions after it comes across an `include` or `require` it cannot resolve. This allows code to reference functions and classes unknown to Psalm.
This defaults to `false`.
#### sealAllMethods
```xml
<psalm
sealAllMethods="[bool]"
>
```
When `true`, Psalm will treat all classes as if they had sealed methods, meaning that if you implement the magic method `__call`, you also have to add `@method` for each magic method. Defaults to false.
#### sealAllProperties
```xml
<psalm
sealAllProperties="[bool]"
>
```
When `true`, Psalm will treat all classes as if they had sealed properties, meaning that Psalm will disallow getting and setting any properties not contained in a list of `@property` (or `@property-read`/`@property-write`) annotations and not explicitly defined as a `property`. Defaults to false.
#### runTaintAnalysis
```xml
<psalm
runTaintAnalysis="[bool]"
>
```
When `true`, Psalm will run [Taint Analysis](../security_analysis/index.md) on your codebase. This config is the same as if you were running Psalm with `--taint-analysis`.
#### reportInfo
```xml
<psalm
reportInfo="[bool]"
>
```
When `false`, Psalm will not consider issue at lower level than `errorLevel` as `info` (they will be suppressed instead). This can be a big improvement in analysis time for big projects. However, this config will prevent Psalm to count or suggest fixes for suppressed issue
#### allowNamedArgumentCalls
```xml
<psalm
allowNamedArgumentCalls="[bool]"
>
```
When `false`, Psalm will not report `ParamNameMismatch` issues in your code anymore. This does not replace the use of individual `@no-named-arguments` to prevent external access to a library's method or to reduce the type to a `list` when using variadics
#### triggerErrorExits
```xml
<psalm
triggerErrorExits="[string]"
>
```
Describe the behavior of trigger_error. `always` means it always exits, `never` means it never exits, `default` means it exits only for `E_USER_ERROR`. Default is `default`
### Running Psalm
#### autoloader
```xml
<psalm
autoloader="[string]"
>
```
If your application registers one or more custom autoloaders, and/or declares universal constants/functions, this autoloader script will be executed by Psalm before scanning starts. Psalm always registers composer's autoloader by default.
#### throwExceptionOnError
```xml
<psalm
throwExceptionOnError="[bool]"
>
```
Useful in testing, this makes Psalm throw a regular-old exception when it encounters an error. Defaults to `false`.
#### hideExternalErrors
```xml
<psalm
hideExternalErrors="[bool]"
>
```
Whether or not to show issues in files that are used by your project files, but which are not included in `<projectFiles>`. Defaults to `false`.
#### hideAllErrorsExceptPassedFiles
```xml
<psalm
hideAllErrorsExceptPassedFiles="[bool]"
>
```
Whether or not to report issues only for files that were passed explicitly as arguments in CLI. This means any files that are loaded with require/include will not report either, if not set in CLI. Useful if you want to only check errors in a single or selected files. Defaults to `false`.
#### cacheDirectory
```xml
<psalm
cacheDirectory="[string]"
>
```
The directory used to store Psalm's cache data - if you specify one (and it does not already exist), its parent directory must already exist, otherwise Psalm will throw an error.
Defaults to `$XDG_CACHE_HOME/psalm`. If `$XDG_CACHE_HOME` is either not set or empty, a default equal to `$HOME/.cache/psalm` is used or `sys_get_temp_dir() . '/psalm'` when not defined.
#### allowFileIncludes
```xml
<psalm
allowFileIncludes="[bool]"
>
```
Whether or not to allow `require`/`include` calls in your PHP. Defaults to `true`.
#### serializer
```xml
<psalm
serializer="['igbinary'|'default']"
>
```
Allows you to hard-code a serializer for Psalm to use when caching data. By default, Psalm uses `ext-igbinary` *if* the version is greater than or equal to 2.0.5, otherwise it defaults to PHP's built-in serializer.
#### threads
```xml
<psalm
threads="[int]"
>
```
Allows you to hard-code the number of threads Psalm will use (similar to `--threads` on the command line). This value will be used in place of detecting threads from the host machine, but will be overridden by using `--threads` or `--debug` (which sets threads to 1) on the command line
#### maxStringLength
```xml
<psalm
maxStringLength="1000"
>
```
This setting controls the maximum length of literal strings that will be transformed into a literal string type during Psalm analysis.
Strings longer than this value (by default 1000 bytes) will be transformed in a generic `non-empty-string` type, instead.
Please note that changing this setting might introduce unwanted side effects and those side effects won't be considered as bugs.
#### maxShapedArraySize
```xml
<psalm
maxShapedArraySize="100"
>
```
This setting controls the maximum size of shaped arrays that will be transformed into a shaped `array{key1: "value", key2: T}` type during Psalm analysis.
Arrays bigger than this value (100 by default) will be transformed in a generic `non-empty-array` type, instead.
Please note that changing this setting might introduce unwanted side effects and those side effects won't be considered as bugs.
#### restrictReturnTypes
```xml
<psalm
restrictReturnTypes="true"
>
```
Emits `LessSpecificReturnType` when the declared return type is not as tight as
the inferred return type.
This code:
```php
function getOne(): int // declared type: int
{
return 1; // inferred type: 1 (int literal)
}
```
Will give this error: `LessSpecificReturnType - The inferred return type '1' for
a is more specific than the declared return type 'int'`
To fix the error, you should specify the more specific type in the doc-block:
```php
/**
* @return 1
*/
function getOne(): int
{
return 1;
}
```
**Warning**: Forcing a tighter type is not always the best course of action and
may cause unexpected results. The following code is invalid with
`restrictReturnTypes="true"`:
```php
class StandardCar {
/**
* @return list{'motor', 'brakes', 'wheels'}
*/
public function getSystems(): array {
return ['motor', 'brakes', 'wheels'];
}
}
class PremiumCar extends StandardCar {
/**
* @return list{'motor', 'brakes', 'wheels', 'rear parking sensor'}
*/
public function getSystems(): array {
return ['motor', 'brakes', 'wheels', 'rear parking sensor'];
}
}
```
`ImplementedReturnTypeMismatch - The inherited return type 'list{'motor', 'brakes', 'wheels'}' for StandardCar::getSystems is different to the implemented return type for PremiumCar::getsystems 'list{'motor', 'brakes', 'wheels', 'rear parking sensor'}'`
#### findUnusedBaselineEntry
Emits [UnusedBaselineEntry](issues/UnusedBaselineEntry.md) when a baseline entry
is not being used to suppress an issue.
## Project settings
#### &lt;projectFiles&gt;
Contains a list of all the directories that Psalm should inspect. You can also specify a set of files and folders to ignore with the `<ignoreFiles>` directive. By default, ignored files/folders are required to exist. An `allowMissingFiles` attribute can be added for ignored files/folders than may or may not exist.
```xml
<projectFiles>
<directory name="src" />
<ignoreFiles>
<directory name="src/Stubs" />
</ignoreFiles>
<ignoreFiles allowMissingFiles="true">
<directory name="path-that-may-not-exist" />
</ignoreFiles>
</projectFiles>
```
#### &lt;extraFiles&gt;
Optional. Same format as `<projectFiles>`. Directories Psalm should load but not inspect.
#### &lt;fileExtensions&gt;
Optional. A list of extensions to search over. See [Checking non-PHP files](checking_non_php_files.md) to understand how to extend this.
#### &lt;enableExtensions&gt;
Optional. A list of extensions to enable. By default, only extensions required by your composer.json will be enabled.
```xml
<enableExtensions>
<extension name="decimal"/>
<extension name="pdo"/>
</enableExtensions>
```
#### &lt;disableExtensions&gt;
Optional. A list of extensions to disable. By default, only extensions required by your composer.json will be enabled.
```xml
<disableExtensions>
<extension name="gmp"/>
</disableExtensions>
```
#### &lt;plugins&gt;
Optional. A list of `<plugin filename="path_to_plugin.php" />` entries. See the [Plugins](plugins/using_plugins.md) section for more information.
#### &lt;issueHandlers&gt;
Optional. If you don't want Psalm to complain about every single issue it finds, the issueHandler tag allows you to configure that. [Dealing with code issues](dealing_with_code_issues.md) tells you more.
#### &lt;mockClasses&gt;
Optional. Do you use mock classes in your tests? If you want Psalm to ignore them when checking files, include a fully-qualified path to the class with `<class name="Your\Namespace\ClassName" />`
#### &lt;universalObjectCrates&gt;
Optional. Do you have objects with properties that cannot be determined statically? If you want Psalm to treat all properties on a given classlike as mixed, include a fully-qualified path to the class with `<class name="Your\Namespace\ClassName" />`. By default, `stdClass` and `SimpleXMLElement` are configured to be universal object crates.
#### &lt;stubs&gt;
Optional. If your codebase uses classes and functions that are not visible to Psalm via reflection
(e.g. if there are internal packages that your codebase relies on that are not available on the machine running Psalm),
you can use stub files. Used by PhpStorm (a popular IDE) and others, stubs provide a description of classes and
functions without the implementations.
You can find a list of stubs for common classes [here](https://github.com/JetBrains/phpstorm-stubs).
List out each file with `<file name="path/to/file.php" />`. In case classes to be tested use parent classes
or interfaces defined in a stub file, this stub should be configured with attribute `preloadClasses="true"`.
```xml
<stubs>
<file name="path/to/file.php" />
<file name="path/to/abstract-class.php" preloadClasses="true" />
</stubs>
```
#### &lt;ignoreExceptions&gt;
Optional. A list of exceptions to not report for `checkForThrowsDocblock` or `checkForThrowsInGlobalScope`. If an exception has `onlyGlobalScope` set to `true`, only `checkForThrowsInGlobalScope` is ignored for that exception, e.g.
```xml
<ignoreExceptions>
<class name="fully\qualified\path\Exc" onlyGlobalScope="true" />
</ignoreExceptions>
```
#### &lt;globals&gt;
Optional. If your codebase uses global variables that are accessed with the `global` keyword, you can declare their type. e.g.
```xml
<globals>
<var name="globalVariableName" type="type" />
</globals>
```
Some frameworks and libraries expose functionalities through e.g. `$GLOBALS[DB]->query($query)`.
The following configuration declares custom types for super-globals (`$GLOBALS`, `$_GET`, ...).
```xml
<globals>
<var name="GLOBALS" type="array{DB: MyVendor\DatabaseConnection, VIEW: MyVendor\TemplateView}" />
<var name="_GET" type="array{data: array<string, string>}" />
</globals>
```
The example above declares global variables as shown below
- `$GLOBALS`
- `DB` of type `MyVendor\DatabaseConnection`
- `VIEW` of type `MyVendor\TemplateView`
- `$_GET`
- `data` e.g. like `["id" => "123", "title" => "Nice"]`
#### &lt;forbiddenFunctions&gt;
Optional. Allows you to specify a list of functions that should emit the [`ForbiddenCode`](issues/ForbiddenCode.md) issue type.
```xml
<forbiddenFunctions>
<function name="var_dump" />
<function name="dd" />
</forbiddenFunctions>
```
## Accessing Psalm configuration in plugins
Plugins can access or modify the global configuration in plugins using
[singleton Psalm\Config](https://github.com/vimeo/psalm/blob/master/src/Psalm/Config.php).
```php
$config = \Psalm\Config::getInstance();
if (!isset($config->globals['$GLOBALS'])) {
$config->globals['$GLOBALS'] = 'array{data: array<string, string>}';
}
```

View File

@@ -0,0 +1,167 @@
# Dealing with code issues
Psalm has a large number of [code issues](issues.md). Each project can specify its own reporting level for a given issue.
Code issue levels in Psalm fall into three categories:
- `error`<br>
This will cause Psalm to print a message, and to ultimately terminate with a non-zero exit status
- `info`<br>
This will cause Psalm to print a message
- `suppress`<br>
This will cause Psalm to ignore the code issue entirely
The third category, `suppress`, is the one you will probably be most interested in, especially when introducing Psalm to a large codebase.
## Suppressing issues
There are two ways to suppress an issue via the Psalm config or via a function docblock.
### Config suppression
You can use the `<issueHandlers>` tag in the config file to influence how issues are treated.
Some issue types allow the use of `referencedMethod`, `referencedClass` or `referencedVariable` to isolate known trouble spots.
```xml
<issueHandlers>
<MissingPropertyType errorLevel="suppress" />
<InvalidReturnType>
<errorLevel type="suppress">
<directory name="some_bad_directory" /> <!-- all InvalidReturnType issues in this directory are suppressed -->
<file name="some_bad_file.php" /> <!-- all InvalidReturnType issues in this file are suppressed -->
</errorLevel>
</InvalidReturnType>
<UndefinedMethod>
<errorLevel type="suppress">
<referencedMethod name="Bar\Bat::bar" /> <!-- not supported for all types of errors -->
<file name="some_bad_file.php" />
</errorLevel>
</UndefinedMethod>
<UndefinedClass>
<errorLevel type="suppress">
<referencedClass name="Bar\Bat\Baz" />
</errorLevel>
</UndefinedClass>
<PropertyNotSetInConstructor>
<errorLevel type="suppress">
<referencedProperty name="Symfony\Component\Validator\ConstraintValidator::$context" />
</errorLevel>
</PropertyNotSetInConstructor>
<UndefinedGlobalVariable>
<errorLevel type="suppress">
<referencedVariable name="$fooBar" /> <!-- if your variable is "$fooBar" -->
</errorLevel>
</UndefinedGlobalVariable>
<PluginIssue name="IssueNameEmittedByPlugin" errorLevel="info" /> <!-- this is a special case to handle issues emitted by plugins -->
</issueHandlers>
```
### Docblock suppression
You can also use `@psalm-suppress IssueName` on a function's docblock to suppress Psalm issues e.g.
```php
<?php
/**
* @psalm-suppress InvalidReturnType
*/
function (int $a) : string {
return $a;
}
```
You can also suppress issues at the line level e.g.
```php
<?php
/**
* @psalm-suppress InvalidReturnType
*/
function (int $a) : string {
/**
* @psalm-suppress InvalidReturnStatement
*/
return $a;
}
```
If you wish to suppress all issues, you can use `@psalm-suppress all` instead of multiple annotations.
## Using a baseline file
If you have a bunch of errors and you don't want to fix them all at once, Psalm can grandfather-in errors in existing code, while ensuring that new code doesn't have those same sorts of errors.
```
vendor/bin/psalm --set-baseline=your-baseline.xml
```
will generate a file containing the current errors. You should commit that generated file so that Psalm can use it when running in other places (e.g. CI). It won't complain about those errors either.
You have two options to use the generated baseline when running psalm:
```
vendor/bin/psalm --use-baseline=your-baseline.xml
```
or using the configuration:
```xml
<?xml version="1.0"?>
<psalm
...
errorBaseline="./path/to/your-baseline.xml"
>
...
</psalm>
```
To update that baseline file, use
```
vendor/bin/psalm --update-baseline
```
This will remove fixed issues, but will _not_ add new issues. To add new issues, use `--set-baseline=...`.
In case you want to run psalm without the baseline, run
```
vendor/bin/psalm --ignore-baseline
```
Baseline files are a great way to gradually improve a codebase.
## Using a plugin
If you want something more custom, like suppressing a certain type of error on classes that implement a particular interface, you can use a plugin that implements `AfterClassLikeVisitInterface`
```php
<?php
namespace Foo\Bar;
use Psalm\Plugin\EventHandler\AfterClassLikeVisitInterface;
use Psalm\Plugin\EventHandler\Event\AfterClassLikeVisitEvent;
use ReflectionClass;
/**
* Suppress issues dynamically based on interface implementation
*/
class DynamicallySuppressClassIssueBasedOnInterface implements AfterClassLikeVisitInterface
{
public static function afterClassLikeVisit(AfterClassLikeVisitEvent $event)
{
$storage = $event->getStorage();
if ($storage->user_defined
&& !$storage->is_interface
&& \class_exists($storage->name)
&& (new ReflectionClass($storage->name))->implementsInterface(\Your\Interface::class)
) {
$storage->suppressed_issues[-1] = 'PropertyNotSetInConstructor';
}
}
}
```

View File

@@ -0,0 +1,311 @@
# Error levels
You can run Psalm in at different levels of strictness from 1 to 8.
Level 1 is the most strict, level 8 is the most lenient.
When no level is explicitly defined, psalm defaults to level 2.
Some issues are always treated as errors. These are issues with a very low probability of false-positives.
At level 1 all issues (except those emitted for opt-in features) that Psalm can find are treated as errors. Those issues include any situation where Psalm cannot infer the type of a given expression.
At level 2 Psalm ignores those `Mixed*` issues, but treats most other issues as errors.
At level 3 Psalm starts to be a little more lenient. For example Psalm allows missing param types, return types and property types.
At level 4 Psalm ignores issues for _possible_ problems. These are more likely to be false positives where the application code may guarantee behaviour that Psalm isn't able to infer.
Level 5 and above allows a more non-verifiable code, and higher levels are even more permissive.
## Always treated as errors
- [AbstractMethodCall](issues/AbstractMethodCall.md)
- [ComplexFunction](issues/ComplexFunction.md)
- [ComplexMethod](issues/ComplexMethod.md)
- [ConfigIssue](issues/ConfigIssue.md)
- [DuplicateArrayKey](issues/DuplicateArrayKey.md)
- [DuplicateClass](issues/DuplicateClass.md)
- [DuplicateFunction](issues/DuplicateFunction.md)
- [DuplicateMethod](issues/DuplicateMethod.md)
- [DuplicateParam](issues/DuplicateParam.md)
- [EmptyArrayAccess](issues/EmptyArrayAccess.md)
- [ExtensionRequirementViolation](issues/ExtensionRequirementViolation.md)
- [ImplementationRequirementViolation](issues/ImplementationRequirementViolation.md)
- [ImpureByReferenceAssignment](issues/ImpureByReferenceAssignment.md)
- [ImpureFunctionCall](issues/ImpureFunctionCall.md)
- [ImpureMethodCall](issues/ImpureMethodCall.md)
- [ImpurePropertyAssignment](issues/ImpurePropertyAssignment.md)
- [ImpurePropertyFetch](issues/ImpurePropertyFetch.md)
- [ImpureStaticProperty](issues/ImpureStaticProperty.md)
- [ImpureStaticVariable](issues/ImpureStaticVariable.md)
- [ImpureVariable](issues/ImpureVariable.md)
- [InaccessibleClassConstant](issues/InaccessibleClassConstant.md)
- [InaccessibleMethod](issues/InaccessibleMethod.md)
- [InaccessibleProperty](issues/InaccessibleProperty.md)
- [InterfaceInstantiation](issues/InterfaceInstantiation.md)
- [InvalidAttribute](issues/InvalidAttribute.md)
- [InvalidEnumMethod](issues/InvalidEnumMethod.md)
- [InvalidExtendClass](issues/InvalidExtendClass.md)
- [InvalidGlobal](issues/InvalidGlobal.md)
- [InvalidInterfaceImplementation](issues/InvalidInterfaceImplementation.md)
- [InvalidParamDefault](issues/InvalidParamDefault.md)
- [InvalidParent](issues/InvalidParent.md)
- [InvalidPassByReference](issues/InvalidPassByReference.md)
- [InvalidScope](issues/InvalidScope.md)
- [InvalidStaticInvocation](issues/InvalidStaticInvocation.md)
- [InvalidThrow](issues/InvalidThrow.md)
- [LoopInvalidation](issues/LoopInvalidation.md)
- [MethodSignatureMustOmitReturnType](issues/MethodSignatureMustOmitReturnType.md)
- [MethodSignatureMustProvideReturnType](issues/MethodSignatureMustProvideReturnType.md)
- [MissingDependency](issues/MissingDependency.md)
- [MissingFile](issues/MissingFile.md)
- [MissingImmutableAnnotation](issues/MissingImmutableAnnotation.md)
- [MissingTemplateParam](issues/MissingTemplateParam.md)
- [MissingThrowsDocblock](issues/MissingThrowsDocblock.md)
- [NonStaticSelfCall](issues/NonStaticSelfCall.md)
- [NoValue](issues/NoValue.md)
- [NullArrayAccess](issues/NullArrayAccess.md)
- [NullFunctionCall](issues/NullFunctionCall.md)
- [NullIterator](issues/NullIterator.md)
- [NullPropertyAssignment](issues/NullPropertyAssignment.md)
- [NullPropertyFetch](issues/NullPropertyFetch.md)
- [NullReference](issues/NullReference.md)
- [OverriddenPropertyAccess](issues/OverriddenPropertyAccess.md)
- [ParadoxicalCondition](issues/ParadoxicalCondition.md)
- [ParentNotFound](issues/ParentNotFound.md)
- [TooFewArguments](issues/TooFewArguments.md)
- [UndefinedAttributeClass](issues/UndefinedAttributeClass.md)
- [UndefinedClass](issues/UndefinedClass.md)
- [UndefinedConstant](issues/UndefinedConstant.md)
- [UndefinedDocblockClass](issues/UndefinedDocblockClass.md)
- [UndefinedFunction](issues/UndefinedFunction.md)
- [UndefinedGlobalVariable](issues/UndefinedGlobalVariable.md)
- [UndefinedInterface](issues/UndefinedInterface.md)
- [UndefinedTrait](issues/UndefinedTrait.md)
- [UndefinedVariable](issues/UndefinedVariable.md)
- [UnimplementedAbstractMethod](issues/UnimplementedAbstractMethod.md)
- [UnimplementedInterfaceMethod](issues/UnimplementedInterfaceMethod.md)
- [UnrecognizedExpression](issues/UnrecognizedExpression.md)
- [UnrecognizedStatement](issues/UnrecognizedStatement.md)
- [UnusedFunctionCall](issues/UnusedFunctionCall.md)
- [UnusedMethodCall](issues/UnusedMethodCall.md)
## Errors that appear at level 7 and below
- [AbstractInstantiation](issues/AbstractInstantiation.md)
- [AssignmentToVoid](issues/AssignmentToVoid.md)
- [CircularReference](issues/CircularReference.md)
- [ConflictingReferenceConstraint](issues/ConflictingReferenceConstraint.md)
- [ContinueOutsideLoop](issues/ContinueOutsideLoop.md)
- [InvalidTypeImport](issues/InvalidTypeImport.md)
- [MethodSignatureMismatch](issues/MethodSignatureMismatch.md)
- [OverriddenMethodAccess](issues/OverriddenMethodAccess.md)
- [ParamNameMismatch](issues/ParamNameMismatch.md)
- [ReservedWord](issues/ReservedWord.md)
- [UnhandledMatchCondition](issues/UnhandledMatchCondition.md)
- [UninitializedProperty](issues/UninitializedProperty.md)
## Errors that appear at level 6 and below
- [InvalidArgument](issues/InvalidArgument.md)
- [InvalidArrayAccess](issues/InvalidArrayAccess.md)
- [InvalidArrayAssignment](issues/InvalidArrayAssignment.md)
- [InvalidArrayOffset](issues/InvalidArrayOffset.md)
- [InvalidCast](issues/InvalidCast.md)
- [InvalidCatch](issues/InvalidCatch.md)
- [InvalidClass](issues/InvalidClass.md)
- [InvalidClone](issues/InvalidClone.md)
- [InvalidFunctionCall](issues/InvalidFunctionCall.md)
- [InvalidIterator](issues/InvalidIterator.md)
- [InvalidMethodCall](issues/InvalidMethodCall.md)
- [InvalidNamedArgument](issues/InvalidNamedArgument.md)
- [InvalidPropertyAssignment](issues/InvalidPropertyAssignment.md)
- [InvalidPropertyAssignmentValue](issues/InvalidPropertyAssignmentValue.md)
- [InvalidPropertyFetch](issues/InvalidPropertyFetch.md)
- [InvalidReturnStatement](issues/InvalidReturnStatement.md)
- [InvalidReturnType](issues/InvalidReturnType.md)
- [InvalidTemplateParam](issues/InvalidTemplateParam.md)
- [NullArgument](issues/NullArgument.md)
- [NullArrayOffset](issues/NullArrayOffset.md)
- [TooManyTemplateParams](issues/TooManyTemplateParams.md)
- [TraitMethodSignatureMismatch](issues/TraitMethodSignatureMismatch.md)
- [UndefinedMethod](issues/UndefinedMethod.md)
- [UndefinedPropertyAssignment](issues/UndefinedPropertyAssignment.md)
- [UndefinedPropertyFetch](issues/UndefinedPropertyFetch.md)
- [UndefinedThisPropertyFetch](issues/UndefinedThisPropertyFetch.md)
## Errors that appear at level 5 and below
- [ConstructorSignatureMismatch](issues/ConstructorSignatureMismatch.md)
- [FalsableReturnStatement](issues/FalsableReturnStatement.md)
- [InvalidNullableReturnType](issues/InvalidNullableReturnType.md)
- [LessSpecificImplementedReturnType](issues/LessSpecificImplementedReturnType.md)
- [MoreSpecificImplementedParamType](issues/MoreSpecificImplementedParamType.md)
- [NullableReturnStatement](issues/NullableReturnStatement.md)
- [UndefinedInterfaceMethod](issues/UndefinedInterfaceMethod.md)
- [UndefinedThisPropertyAssignment](issues/UndefinedThisPropertyAssignment.md)
## Errors that appear at level 4 and below
- [FalseOperand](issues/FalseOperand.md)
- [ForbiddenCode](issues/ForbiddenCode.md)
- [ImplementedParamTypeMismatch](issues/ImplementedParamTypeMismatch.md)
- [ImplementedReturnTypeMismatch](issues/ImplementedReturnTypeMismatch.md)
- [ImplicitToStringCast](issues/ImplicitToStringCast.md)
- [InternalClass](issues/InternalClass.md)
- [InternalMethod](issues/InternalMethod.md)
- [InternalProperty](issues/InternalProperty.md)
- [InvalidDocblock](issues/InvalidDocblock.md)
- [InvalidLiteralArgument](issues/InvalidLiteralArgument.md)
- [InvalidOperand](issues/InvalidOperand.md)
- [InvalidScalarArgument](issues/InvalidScalarArgument.md)
- [InvalidToString](issues/InvalidToString.md)
- [MismatchingDocblockParamType](issues/MismatchingDocblockParamType.md)
- [MismatchingDocblockReturnType](issues/MismatchingDocblockReturnType.md)
- [MissingDocblockType](issues/MissingDocblockType.md)
- [NoInterfaceProperties](issues/NoInterfaceProperties.md)
- [PossibleRawObjectIteration](issues/PossibleRawObjectIteration.md)
- [RedundantCondition](issues/RedundantCondition.md)
- [RedundantFunctionCall](issues/RedundantFunctionCall.md)
- [RedundantPropertyInitializationCheck](issues/RedundantPropertyInitializationCheck.md)
- [StringIncrement](issues/StringIncrement.md)
- [TooManyArguments](issues/TooManyArguments.md)
- [TypeDoesNotContainNull](issues/TypeDoesNotContainNull.md)
- [TypeDoesNotContainType](issues/TypeDoesNotContainType.md)
- [UndefinedMagicMethod](issues/UndefinedMagicMethod.md)
- [UndefinedMagicPropertyAssignment](issues/UndefinedMagicPropertyAssignment.md)
- [UndefinedMagicPropertyFetch](issues/UndefinedMagicPropertyFetch.md)
## Errors that appear at level 3 and below
- [ArgumentTypeCoercion](issues/ArgumentTypeCoercion.md)
- [LessSpecificReturnStatement](issues/LessSpecificReturnStatement.md)
- [MoreSpecificReturnType](issues/MoreSpecificReturnType.md)
- [PossiblyFalseArgument](issues/PossiblyFalseArgument.md)
- [PossiblyFalseIterator](issues/PossiblyFalseIterator.md)
- [PossiblyFalseOperand](issues/PossiblyFalseOperand.md)
- [PossiblyFalsePropertyAssignmentValue](issues/PossiblyFalsePropertyAssignmentValue.md)
- [PossiblyFalseReference](issues/PossiblyFalseReference.md)
- [PossiblyInvalidArgument](issues/PossiblyInvalidArgument.md)
- [PossiblyInvalidArrayAccess](issues/PossiblyInvalidArrayAccess.md)
- [PossiblyInvalidArrayAssignment](issues/PossiblyInvalidArrayAssignment.md)
- [PossiblyInvalidArrayOffset](issues/PossiblyInvalidArrayOffset.md)
- [PossiblyInvalidCast](issues/PossiblyInvalidCast.md)
- [PossiblyInvalidClone](issues/PossiblyInvalidClone.md)
- [PossiblyInvalidFunctionCall](issues/PossiblyInvalidFunctionCall.md)
- [PossiblyInvalidIterator](issues/PossiblyInvalidIterator.md)
- [PossiblyInvalidMethodCall](issues/PossiblyInvalidMethodCall.md)
- [PossiblyInvalidOperand](issues/PossiblyInvalidOperand.md)
- [PossiblyInvalidPropertyAssignment](issues/PossiblyInvalidPropertyAssignment.md)
- [PossiblyInvalidPropertyAssignmentValue](issues/PossiblyInvalidPropertyAssignmentValue.md)
- [PossiblyInvalidPropertyFetch](issues/PossiblyInvalidPropertyFetch.md)
- [PossiblyNullArgument](issues/PossiblyNullArgument.md)
- [PossiblyNullArrayAccess](issues/PossiblyNullArrayAccess.md)
- [PossiblyNullArrayAssignment](issues/PossiblyNullArrayAssignment.md)
- [PossiblyNullArrayOffset](issues/PossiblyNullArrayOffset.md)
- [PossiblyNullFunctionCall](issues/PossiblyNullFunctionCall.md)
- [PossiblyNullIterator](issues/PossiblyNullIterator.md)
- [PossiblyNullPropertyAssignment](issues/PossiblyNullPropertyAssignment.md)
- [PossiblyNullPropertyAssignmentValue](issues/PossiblyNullPropertyAssignmentValue.md)
- [PossiblyNullPropertyFetch](issues/PossiblyNullPropertyFetch.md)
- [PossiblyNullReference](issues/PossiblyNullReference.md)
- [PossiblyUndefinedArrayOffset](issues/PossiblyUndefinedArrayOffset.md)
- [PossiblyUndefinedGlobalVariable](issues/PossiblyUndefinedGlobalVariable.md)
- [PossiblyUndefinedMethod](issues/PossiblyUndefinedMethod.md)
- [PossiblyUndefinedVariable](issues/PossiblyUndefinedVariable.md)
- [PropertyTypeCoercion](issues/PropertyTypeCoercion.md)
- [RiskyCast](issues/RiskyCast.md)
## Errors that appear at level 2 and below
- [DeprecatedClass](issues/DeprecatedClass.md)
- [DeprecatedConstant](issues/DeprecatedConstant.md)
- [DeprecatedFunction](issues/DeprecatedFunction.md)
- [DeprecatedInterface](issues/DeprecatedInterface.md)
- [DeprecatedMethod](issues/DeprecatedMethod.md)
- [DeprecatedProperty](issues/DeprecatedProperty.md)
- [DeprecatedTrait](issues/DeprecatedTrait.md)
- [DirectConstructorCall](issues/DirectConstructorCall.md)
- [DocblockTypeContradiction](issues/DocblockTypeContradiction.md)
- [InvalidDocblockParamName](issues/InvalidDocblockParamName.md)
- [InvalidFalsableReturnType](issues/InvalidFalsableReturnType.md)
- [InvalidStringClass](issues/InvalidStringClass.md)
- [MissingClosureParamType](issues/MissingClosureParamType.md)
- [MissingClosureReturnType](issues/MissingClosureReturnType.md)
- [MissingConstructor](issues/MissingConstructor.md)
- [MissingParamType](issues/MissingParamType.md)
- [MissingPropertyType](issues/MissingPropertyType.md)
- [MissingReturnType](issues/MissingReturnType.md)
- [NullOperand](issues/NullOperand.md)
- [PropertyNotSetInConstructor](issues/PropertyNotSetInConstructor.md)
- [RawObjectIteration](issues/RawObjectIteration.md)
- [RedundantConditionGivenDocblockType](issues/RedundantConditionGivenDocblockType.md)
- [RedundantFunctionCallGivenDocblockType](issues/RedundantFunctionCallGivenDocblockType.md)
- [ReferenceConstraintViolation](issues/ReferenceConstraintViolation.md)
- [UndefinedTrace](issues/UndefinedTrace.md)
- [UnresolvableInclude](issues/UnresolvableInclude.md)
- [UnsafeInstantiation](issues/UnsafeInstantiation.md)
## Errors that only appear at level 1
- [LessSpecificReturnType](issues/LessSpecificReturnType.md)
- [MixedArgument](issues/MixedArgument.md)
- [MixedArgumentTypeCoercion](issues/MixedArgumentTypeCoercion.md)
- [MixedArrayAccess](issues/MixedArrayAccess.md)
- [MixedArrayAssignment](issues/MixedArrayAssignment.md)
- [MixedArrayOffset](issues/MixedArrayOffset.md)
- [MixedArrayTypeCoercion](issues/MixedArrayTypeCoercion.md)
- [MixedAssignment](issues/MixedAssignment.md)
- [MixedClone](issues/MixedClone.md)
- [MixedFunctionCall](issues/MixedFunctionCall.md)
- [MixedInferredReturnType](issues/MixedInferredReturnType.md)
- [MixedMethodCall](issues/MixedMethodCall.md)
- [MixedOperand](issues/MixedOperand.md)
- [MixedPropertyAssignment](issues/MixedPropertyAssignment.md)
- [MixedPropertyFetch](issues/MixedPropertyFetch.md)
- [MixedPropertyTypeCoercion](issues/MixedPropertyTypeCoercion.md)
- [MixedReturnStatement](issues/MixedReturnStatement.md)
- [MixedReturnTypeCoercion](issues/MixedReturnTypeCoercion.md)
- [MixedStringOffsetAssignment](issues/MixedStringOffsetAssignment.md)
- [MutableDependency](issues/MutableDependency.md)
- [PossiblyNullOperand](issues/PossiblyNullOperand.md)
- [RedundantIdentityWithTrue](issues/RedundantIdentityWithTrue.md)
- [Trace](issues/Trace.md)
## Feature-specific errors
- [PossiblyUndefinedIntArrayOffset](issues/PossiblyUndefinedIntArrayOffset.md)
- [PossiblyUndefinedStringArrayOffset](issues/PossiblyUndefinedStringArrayOffset.md)
- [PossiblyUnusedMethod](issues/PossiblyUnusedMethod.md)
- [PossiblyUnusedParam](issues/PossiblyUnusedParam.md)
- [PossiblyUnusedProperty](issues/PossiblyUnusedProperty.md)
- [TaintedCallable](issues/TaintedCallable.md)
- [TaintedCookie](issues/TaintedCookie.md)
- [TaintedCustom](issues/TaintedCustom.md)
- [TaintedEval](issues/TaintedEval.md)
- [TaintedFile](issues/TaintedFile.md)
- [TaintedHeader](issues/TaintedHeader.md)
- [TaintedHtml](issues/TaintedHtml.md)
- [TaintedInclude](issues/TaintedInclude.md)
- [TaintedInput](issues/TaintedInput.md)
- [TaintedLdap](issues/TaintedLdap.md)
- [TaintedShell](issues/TaintedShell.md)
- [TaintedSql](issues/TaintedSql.md)
- [TaintedSSRF](issues/TaintedSSRF.md)
- [TaintedSystemSecret](issues/TaintedSystemSecret.md)
- [TaintedUnserialize](issues/TaintedUnserialize.md)
- [TaintedUserSecret](issues/TaintedUserSecret.md)
- [UncaughtThrowInGlobalScope](issues/UncaughtThrowInGlobalScope.md)
- [UnevaluatedCode](issues/UnevaluatedCode.md)
- [UnnecessaryVarAnnotation](issues/UnnecessaryVarAnnotation.md)
- [UnusedClass](issues/UnusedClass.md)
- [UnusedClosureParam](issues/UnusedClosureParam.md)
- [UnusedConstructor](issues/UnusedConstructor.md)
- [UnusedDocblockParam](issues/UnusedDocblockParam.md)
- [UnusedForeachValue](issues/UnusedForeachValue.md)
- [UnusedMethod](issues/UnusedMethod.md)
- [UnusedParam](issues/UnusedParam.md)
- [UnusedProperty](issues/UnusedProperty.md)
- [UnusedPsalmSuppress](issues/UnusedPsalmSuppress.md)
- [UnusedVariable](issues/UnusedVariable.md)

View File

@@ -0,0 +1,53 @@
# Installation
The latest version of Psalm requires PHP >= 7.4 and [Composer](https://getcomposer.org/).
```bash
composer require --dev vimeo/psalm
```
Generate a config file:
```bash
./vendor/bin/psalm --init
```
Psalm will scan your project and figure out an appropriate [error level](error_levels.md) for your codebase.
Then run Psalm:
```bash
./vendor/bin/psalm
```
Psalm will probably find a number of issues - find out how to deal with them in [Dealing with code issues](dealing_with_code_issues.md).
## Installing plugins
While Psalm can figure out the types used by various libraries based on
their source code and docblocks, it works even better with custom-tailored types
provided by Psalm plugins.
Check out the [list of existing plugins on Packagist](https://packagist.org/?type=psalm-plugin).
Install them with `composer require --dev <plugin/package> && vendor/bin/psalm-plugin enable <plugin/package>`
Read more about plugins in [Using Plugins chapter](plugins/using_plugins.md).
## Using the Phar
Sometimes your project can conflict with one or more of Psalms dependencies. In
that case you may find the Phar (a self-contained PHP executable) useful.
The Phar can be downloaded from Github:
```bash
wget https://github.com/vimeo/psalm/releases/latest/download/psalm.phar
chmod +x psalm.phar
./psalm.phar --version
```
Alternatively, you can use Composer to install the Phar:
```bash
composer require --dev psalm/phar
```

View File

@@ -0,0 +1,300 @@
# Issue types
- [AbstractInstantiation](issues/AbstractInstantiation.md)
- [AbstractMethodCall](issues/AbstractMethodCall.md)
- [AmbiguousConstantInheritance](issues/AmbiguousConstantInheritance.md)
- [ArgumentTypeCoercion](issues/ArgumentTypeCoercion.md)
- [AssignmentToVoid](issues/AssignmentToVoid.md)
- [CheckType](issues/CheckType.md)
- [CircularReference](issues/CircularReference.md)
- [ComplexFunction](issues/ComplexFunction.md)
- [ComplexMethod](issues/ComplexMethod.md)
- [ConfigIssue](issues/ConfigIssue.md)
- [ConflictingReferenceConstraint](issues/ConflictingReferenceConstraint.md)
- [ConstantDeclarationInTrait](issues/ConstantDeclarationInTrait.md)
- [ConstructorSignatureMismatch](issues/ConstructorSignatureMismatch.md)
- [ContinueOutsideLoop](issues/ContinueOutsideLoop.md)
- [DeprecatedClass](issues/DeprecatedClass.md)
- [DeprecatedConstant](issues/DeprecatedConstant.md)
- [DeprecatedFunction](issues/DeprecatedFunction.md)
- [DeprecatedInterface](issues/DeprecatedInterface.md)
- [DeprecatedMethod](issues/DeprecatedMethod.md)
- [DeprecatedProperty](issues/DeprecatedProperty.md)
- [DeprecatedTrait](issues/DeprecatedTrait.md)
- [DirectConstructorCall](issues/DirectConstructorCall.md)
- [DocblockTypeContradiction](issues/DocblockTypeContradiction.md)
- [DuplicateArrayKey](issues/DuplicateArrayKey.md)
- [DuplicateClass](issues/DuplicateClass.md)
- [DuplicateConstant](issues/DuplicateConstant.md)
- [DuplicateEnumCase](issues/DuplicateEnumCase.md)
- [DuplicateEnumCaseValue](issues/DuplicateEnumCaseValue.md)
- [DuplicateFunction](issues/DuplicateFunction.md)
- [DuplicateMethod](issues/DuplicateMethod.md)
- [DuplicateParam](issues/DuplicateParam.md)
- [EmptyArrayAccess](issues/EmptyArrayAccess.md)
- [ExtensionRequirementViolation](issues/ExtensionRequirementViolation.md)
- [FalsableReturnStatement](issues/FalsableReturnStatement.md)
- [FalseOperand](issues/FalseOperand.md)
- [ForbiddenCode](issues/ForbiddenCode.md)
- [IfThisIsMismatch](issues/IfThisIsMismatch.md)
- [ImplementationRequirementViolation](issues/ImplementationRequirementViolation.md)
- [ImplementedParamTypeMismatch](issues/ImplementedParamTypeMismatch.md)
- [ImplementedReturnTypeMismatch](issues/ImplementedReturnTypeMismatch.md)
- [ImplicitToStringCast](issues/ImplicitToStringCast.md)
- [ImpureByReferenceAssignment](issues/ImpureByReferenceAssignment.md)
- [ImpureFunctionCall](issues/ImpureFunctionCall.md)
- [ImpureMethodCall](issues/ImpureMethodCall.md)
- [ImpurePropertyAssignment](issues/ImpurePropertyAssignment.md)
- [ImpurePropertyFetch](issues/ImpurePropertyFetch.md)
- [ImpureStaticProperty](issues/ImpureStaticProperty.md)
- [ImpureStaticVariable](issues/ImpureStaticVariable.md)
- [ImpureVariable](issues/ImpureVariable.md)
- [InaccessibleClassConstant](issues/InaccessibleClassConstant.md)
- [InaccessibleMethod](issues/InaccessibleMethod.md)
- [InaccessibleProperty](issues/InaccessibleProperty.md)
- [InterfaceInstantiation](issues/InterfaceInstantiation.md)
- [InternalClass](issues/InternalClass.md)
- [InternalMethod](issues/InternalMethod.md)
- [InternalProperty](issues/InternalProperty.md)
- [InvalidArgument](issues/InvalidArgument.md)
- [InvalidArrayAccess](issues/InvalidArrayAccess.md)
- [InvalidArrayAssignment](issues/InvalidArrayAssignment.md)
- [InvalidArrayOffset](issues/InvalidArrayOffset.md)
- [InvalidAttribute](issues/InvalidAttribute.md)
- [InvalidCast](issues/InvalidCast.md)
- [InvalidCatch](issues/InvalidCatch.md)
- [InvalidClass](issues/InvalidClass.md)
- [InvalidClassConstantType](issues/InvalidClassConstantType.md)
- [InvalidClone](issues/InvalidClone.md)
- [InvalidConstantAssignmentValue](issues/InvalidConstantAssignmentValue.md)
- [InvalidDocblock](issues/InvalidDocblock.md)
- [InvalidDocblockParamName](issues/InvalidDocblockParamName.md)
- [InvalidEnumBackingType](issues/InvalidEnumBackingType.md)
- [InvalidEnumCaseValue](issues/InvalidEnumCaseValue.md)
- [InvalidEnumMethod](issues/InvalidEnumMethod.md)
- [InvalidExtendClass](issues/InvalidExtendClass.md)
- [InvalidFalsableReturnType](issues/InvalidFalsableReturnType.md)
- [InvalidFunctionCall](issues/InvalidFunctionCall.md)
- [InvalidGlobal](issues/InvalidGlobal.md)
- [InvalidInterfaceImplementation](issues/InvalidInterfaceImplementation.md)
- [InvalidIterator](issues/InvalidIterator.md)
- [InvalidLiteralArgument](issues/InvalidLiteralArgument.md)
- [InvalidMethodCall](issues/InvalidMethodCall.md)
- [InvalidNamedArgument](issues/InvalidNamedArgument.md)
- [InvalidNullableReturnType](issues/InvalidNullableReturnType.md)
- [InvalidOperand](issues/InvalidOperand.md)
- [InvalidParamDefault](issues/InvalidParamDefault.md)
- [InvalidParent](issues/InvalidParent.md)
- [InvalidPassByReference](issues/InvalidPassByReference.md)
- [InvalidPropertyAssignment](issues/InvalidPropertyAssignment.md)
- [InvalidPropertyAssignmentValue](issues/InvalidPropertyAssignmentValue.md)
- [InvalidPropertyFetch](issues/InvalidPropertyFetch.md)
- [InvalidReturnStatement](issues/InvalidReturnStatement.md)
- [InvalidReturnType](issues/InvalidReturnType.md)
- [InvalidScalarArgument](issues/InvalidScalarArgument.md)
- [InvalidScope](issues/InvalidScope.md)
- [InvalidStaticInvocation](issues/InvalidStaticInvocation.md)
- [InvalidStringClass](issues/InvalidStringClass.md)
- [InvalidTemplateParam](issues/InvalidTemplateParam.md)
- [InvalidThrow](issues/InvalidThrow.md)
- [InvalidToString](issues/InvalidToString.md)
- [InvalidTraversableImplementation](issues/InvalidTraversableImplementation.md)
- [InvalidTypeImport](issues/InvalidTypeImport.md)
- [LessSpecificClassConstantType](issues/LessSpecificClassConstantType.md)
- [LessSpecificImplementedReturnType](issues/LessSpecificImplementedReturnType.md)
- [LessSpecificReturnStatement](issues/LessSpecificReturnStatement.md)
- [LessSpecificReturnType](issues/LessSpecificReturnType.md)
- [LoopInvalidation](issues/LoopInvalidation.md)
- [MethodSignatureMismatch](issues/MethodSignatureMismatch.md)
- [MethodSignatureMustOmitReturnType](issues/MethodSignatureMustOmitReturnType.md)
- [MethodSignatureMustProvideReturnType](issues/MethodSignatureMustProvideReturnType.md)
- [MismatchingDocblockParamType](issues/MismatchingDocblockParamType.md)
- [MismatchingDocblockPropertyType](issues/MismatchingDocblockPropertyType.md)
- [MismatchingDocblockReturnType](issues/MismatchingDocblockReturnType.md)
- [MissingClosureParamType](issues/MissingClosureParamType.md)
- [MissingClosureReturnType](issues/MissingClosureReturnType.md)
- [MissingConstructor](issues/MissingConstructor.md)
- [MissingDependency](issues/MissingDependency.md)
- [MissingDocblockType](issues/MissingDocblockType.md)
- [MissingFile](issues/MissingFile.md)
- [MissingImmutableAnnotation](issues/MissingImmutableAnnotation.md)
- [MissingParamType](issues/MissingParamType.md)
- [MissingPropertyType](issues/MissingPropertyType.md)
- [MissingReturnType](issues/MissingReturnType.md)
- [MissingTemplateParam](issues/MissingTemplateParam.md)
- [MissingThrowsDocblock](issues/MissingThrowsDocblock.md)
- [MixedArgument](issues/MixedArgument.md)
- [MixedArgumentTypeCoercion](issues/MixedArgumentTypeCoercion.md)
- [MixedArrayAccess](issues/MixedArrayAccess.md)
- [MixedArrayAssignment](issues/MixedArrayAssignment.md)
- [MixedArrayOffset](issues/MixedArrayOffset.md)
- [MixedArrayTypeCoercion](issues/MixedArrayTypeCoercion.md)
- [MixedAssignment](issues/MixedAssignment.md)
- [MixedClone](issues/MixedClone.md)
- [MixedFunctionCall](issues/MixedFunctionCall.md)
- [MixedInferredReturnType](issues/MixedInferredReturnType.md)
- [MixedMethodCall](issues/MixedMethodCall.md)
- [MixedOperand](issues/MixedOperand.md)
- [MixedPropertyAssignment](issues/MixedPropertyAssignment.md)
- [MixedPropertyFetch](issues/MixedPropertyFetch.md)
- [MixedPropertyTypeCoercion](issues/MixedPropertyTypeCoercion.md)
- [MixedReturnStatement](issues/MixedReturnStatement.md)
- [MixedReturnTypeCoercion](issues/MixedReturnTypeCoercion.md)
- [MixedStringOffsetAssignment](issues/MixedStringOffsetAssignment.md)
- [MoreSpecificImplementedParamType](issues/MoreSpecificImplementedParamType.md)
- [MoreSpecificReturnType](issues/MoreSpecificReturnType.md)
- [MutableDependency](issues/MutableDependency.md)
- [NamedArgumentNotAllowed](issues/NamedArgumentNotAllowed.md)
- [NoEnumProperties](issues/NoEnumProperties.md)
- [NoInterfaceProperties](issues/NoInterfaceProperties.md)
- [NonInvariantDocblockPropertyType](issues/NonInvariantDocblockPropertyType.md)
- [NonInvariantPropertyType](issues/NonInvariantPropertyType.md)
- [NonStaticSelfCall](issues/NonStaticSelfCall.md)
- [NoValue](issues/NoValue.md)
- [NullableReturnStatement](issues/NullableReturnStatement.md)
- [NullArgument](issues/NullArgument.md)
- [NullArrayAccess](issues/NullArrayAccess.md)
- [NullArrayOffset](issues/NullArrayOffset.md)
- [NullFunctionCall](issues/NullFunctionCall.md)
- [NullIterator](issues/NullIterator.md)
- [NullOperand](issues/NullOperand.md)
- [NullPropertyAssignment](issues/NullPropertyAssignment.md)
- [NullPropertyFetch](issues/NullPropertyFetch.md)
- [NullReference](issues/NullReference.md)
- [OverriddenFinalConstant](issues/OverriddenFinalConstant.md)
- [OverriddenInterfaceConstant](issues/OverriddenInterfaceConstant.md)
- [OverriddenMethodAccess](issues/OverriddenMethodAccess.md)
- [OverriddenPropertyAccess](issues/OverriddenPropertyAccess.md)
- [ParadoxicalCondition](issues/ParadoxicalCondition.md)
- [ParamNameMismatch](issues/ParamNameMismatch.md)
- [ParentNotFound](issues/ParentNotFound.md)
- [ParseError](issues/ParseError.md)
- [PluginIssue](issues/PluginIssue.md)
- [PossibleRawObjectIteration](issues/PossibleRawObjectIteration.md)
- [PossiblyFalseArgument](issues/PossiblyFalseArgument.md)
- [PossiblyFalseIterator](issues/PossiblyFalseIterator.md)
- [PossiblyFalseOperand](issues/PossiblyFalseOperand.md)
- [PossiblyFalsePropertyAssignmentValue](issues/PossiblyFalsePropertyAssignmentValue.md)
- [PossiblyFalseReference](issues/PossiblyFalseReference.md)
- [PossiblyInvalidArgument](issues/PossiblyInvalidArgument.md)
- [PossiblyInvalidArrayAccess](issues/PossiblyInvalidArrayAccess.md)
- [PossiblyInvalidArrayAssignment](issues/PossiblyInvalidArrayAssignment.md)
- [PossiblyInvalidArrayOffset](issues/PossiblyInvalidArrayOffset.md)
- [PossiblyInvalidCast](issues/PossiblyInvalidCast.md)
- [PossiblyInvalidClone](issues/PossiblyInvalidClone.md)
- [PossiblyInvalidDocblockTag](issues/PossiblyInvalidDocblockTag.md)
- [PossiblyInvalidFunctionCall](issues/PossiblyInvalidFunctionCall.md)
- [PossiblyInvalidIterator](issues/PossiblyInvalidIterator.md)
- [PossiblyInvalidMethodCall](issues/PossiblyInvalidMethodCall.md)
- [PossiblyInvalidOperand](issues/PossiblyInvalidOperand.md)
- [PossiblyInvalidPropertyAssignment](issues/PossiblyInvalidPropertyAssignment.md)
- [PossiblyInvalidPropertyAssignmentValue](issues/PossiblyInvalidPropertyAssignmentValue.md)
- [PossiblyInvalidPropertyFetch](issues/PossiblyInvalidPropertyFetch.md)
- [PossiblyNullArgument](issues/PossiblyNullArgument.md)
- [PossiblyNullArrayAccess](issues/PossiblyNullArrayAccess.md)
- [PossiblyNullArrayAssignment](issues/PossiblyNullArrayAssignment.md)
- [PossiblyNullArrayOffset](issues/PossiblyNullArrayOffset.md)
- [PossiblyNullFunctionCall](issues/PossiblyNullFunctionCall.md)
- [PossiblyNullIterator](issues/PossiblyNullIterator.md)
- [PossiblyNullOperand](issues/PossiblyNullOperand.md)
- [PossiblyNullPropertyAssignment](issues/PossiblyNullPropertyAssignment.md)
- [PossiblyNullPropertyAssignmentValue](issues/PossiblyNullPropertyAssignmentValue.md)
- [PossiblyNullPropertyFetch](issues/PossiblyNullPropertyFetch.md)
- [PossiblyNullReference](issues/PossiblyNullReference.md)
- [PossiblyUndefinedArrayOffset](issues/PossiblyUndefinedArrayOffset.md)
- [PossiblyUndefinedGlobalVariable](issues/PossiblyUndefinedGlobalVariable.md)
- [PossiblyUndefinedIntArrayOffset](issues/PossiblyUndefinedIntArrayOffset.md)
- [PossiblyUndefinedMethod](issues/PossiblyUndefinedMethod.md)
- [PossiblyUndefinedStringArrayOffset](issues/PossiblyUndefinedStringArrayOffset.md)
- [PossiblyUndefinedVariable](issues/PossiblyUndefinedVariable.md)
- [PossiblyUnusedMethod](issues/PossiblyUnusedMethod.md)
- [PossiblyUnusedParam](issues/PossiblyUnusedParam.md)
- [PossiblyUnusedProperty](issues/PossiblyUnusedProperty.md)
- [PossiblyUnusedReturnValue](issues/PossiblyUnusedReturnValue.md)
- [PropertyNotSetInConstructor](issues/PropertyNotSetInConstructor.md)
- [PropertyTypeCoercion](issues/PropertyTypeCoercion.md)
- [RawObjectIteration](issues/RawObjectIteration.md)
- [RedundantCast](issues/RedundantCast.md)
- [RedundantCastGivenDocblockType](issues/RedundantCastGivenDocblockType.md)
- [RedundantCondition](issues/RedundantCondition.md)
- [RedundantConditionGivenDocblockType](issues/RedundantConditionGivenDocblockType.md)
- [RedundantFunctionCall](issues/RedundantFunctionCall.md)
- [RedundantFunctionCallGivenDocblockType](issues/RedundantFunctionCallGivenDocblockType.md)
- [RedundantIdentityWithTrue](issues/RedundantIdentityWithTrue.md)
- [RedundantPropertyInitializationCheck](issues/RedundantPropertyInitializationCheck.md)
- [ReferenceConstraintViolation](issues/ReferenceConstraintViolation.md)
- [ReferenceReusedFromConfusingScope](issues/ReferenceReusedFromConfusingScope.md)
- [ReservedWord](issues/ReservedWord.md)
- [RiskyCast](issues/RiskyCast.md)
- [StringIncrement](issues/StringIncrement.md)
- [TaintedCallable](issues/TaintedCallable.md)
- [TaintedCookie](issues/TaintedCookie.md)
- [TaintedCustom](issues/TaintedCustom.md)
- [TaintedEval](issues/TaintedEval.md)
- [TaintedFile](issues/TaintedFile.md)
- [TaintedHeader](issues/TaintedHeader.md)
- [TaintedHtml](issues/TaintedHtml.md)
- [TaintedInclude](issues/TaintedInclude.md)
- [TaintedInput](issues/TaintedInput.md)
- [TaintedLdap](issues/TaintedLdap.md)
- [TaintedShell](issues/TaintedShell.md)
- [TaintedSql](issues/TaintedSql.md)
- [TaintedSSRF](issues/TaintedSSRF.md)
- [TaintedSystemSecret](issues/TaintedSystemSecret.md)
- [TaintedTextWithQuotes](issues/TaintedTextWithQuotes.md)
- [TaintedUnserialize](issues/TaintedUnserialize.md)
- [TaintedUserSecret](issues/TaintedUserSecret.md)
- [TooFewArguments](issues/TooFewArguments.md)
- [TooManyArguments](issues/TooManyArguments.md)
- [TooManyTemplateParams](issues/TooManyTemplateParams.md)
- [Trace](issues/Trace.md)
- [TraitMethodSignatureMismatch](issues/TraitMethodSignatureMismatch.md)
- [TypeDoesNotContainNull](issues/TypeDoesNotContainNull.md)
- [TypeDoesNotContainType](issues/TypeDoesNotContainType.md)
- [UncaughtThrowInGlobalScope](issues/UncaughtThrowInGlobalScope.md)
- [UndefinedAttributeClass](issues/UndefinedAttributeClass.md)
- [UndefinedClass](issues/UndefinedClass.md)
- [UndefinedConstant](issues/UndefinedConstant.md)
- [UndefinedDocblockClass](issues/UndefinedDocblockClass.md)
- [UndefinedFunction](issues/UndefinedFunction.md)
- [UndefinedGlobalVariable](issues/UndefinedGlobalVariable.md)
- [UndefinedInterface](issues/UndefinedInterface.md)
- [UndefinedInterfaceMethod](issues/UndefinedInterfaceMethod.md)
- [UndefinedMagicMethod](issues/UndefinedMagicMethod.md)
- [UndefinedMagicPropertyAssignment](issues/UndefinedMagicPropertyAssignment.md)
- [UndefinedMagicPropertyFetch](issues/UndefinedMagicPropertyFetch.md)
- [UndefinedMethod](issues/UndefinedMethod.md)
- [UndefinedPropertyAssignment](issues/UndefinedPropertyAssignment.md)
- [UndefinedPropertyFetch](issues/UndefinedPropertyFetch.md)
- [UndefinedThisPropertyAssignment](issues/UndefinedThisPropertyAssignment.md)
- [UndefinedThisPropertyFetch](issues/UndefinedThisPropertyFetch.md)
- [UndefinedTrace](issues/UndefinedTrace.md)
- [UndefinedTrait](issues/UndefinedTrait.md)
- [UndefinedVariable](issues/UndefinedVariable.md)
- [UnevaluatedCode](issues/UnevaluatedCode.md)
- [UnhandledMatchCondition](issues/UnhandledMatchCondition.md)
- [UnimplementedAbstractMethod](issues/UnimplementedAbstractMethod.md)
- [UnimplementedInterfaceMethod](issues/UnimplementedInterfaceMethod.md)
- [UninitializedProperty](issues/UninitializedProperty.md)
- [UnnecessaryVarAnnotation](issues/UnnecessaryVarAnnotation.md)
- [UnrecognizedExpression](issues/UnrecognizedExpression.md)
- [UnrecognizedStatement](issues/UnrecognizedStatement.md)
- [UnresolvableConstant](issues/UnresolvableConstant.md)
- [UnresolvableInclude](issues/UnresolvableInclude.md)
- [UnsafeGenericInstantiation](issues/UnsafeGenericInstantiation.md)
- [UnsafeInstantiation](issues/UnsafeInstantiation.md)
- [UnsupportedReferenceUsage](issues/UnsupportedReferenceUsage.md)
- [UnusedBaselineEntry](issues/UnusedBaselineEntry.md)
- [UnusedClass](issues/UnusedClass.md)
- [UnusedClosureParam](issues/UnusedClosureParam.md)
- [UnusedConstructor](issues/UnusedConstructor.md)
- [UnusedDocblockParam](issues/UnusedDocblockParam.md)
- [UnusedForeachValue](issues/UnusedForeachValue.md)
- [UnusedFunctionCall](issues/UnusedFunctionCall.md)
- [UnusedMethod](issues/UnusedMethod.md)
- [UnusedMethodCall](issues/UnusedMethodCall.md)
- [UnusedParam](issues/UnusedParam.md)
- [UnusedProperty](issues/UnusedProperty.md)
- [UnusedPsalmSuppress](issues/UnusedPsalmSuppress.md)
- [UnusedReturnValue](issues/UnusedReturnValue.md)
- [UnusedVariable](issues/UnusedVariable.md)

View File

@@ -0,0 +1,10 @@
# AbstractInstantiation
Emitted when an attempt is made to instantiate an abstract class:
```php
<?php
abstract class A {}
new A();
```

View File

@@ -0,0 +1,17 @@
# AbstractMethodCall
Emitted when an attempt is made to call an abstract static method directly
```php
<?php
abstract class Base {
abstract static function bar() : void;
}
Base::bar();
```
## Why this is bad
It's not allowed by PHP.

View File

@@ -0,0 +1,41 @@
# AmbiguousConstantInheritance
Emitted when a constant is inherited from multiple sources.
```php
<?php
interface Foo
{
/** @var non-empty-string */
public const CONSTANT='foo';
}
interface Bar
{
/**
* @var non-empty-string
*/
public const CONSTANT='bar';
}
interface Baz extends Foo, Bar {}
```
```php
<?php
interface Foo
{
/** @var non-empty-string */
public const CONSTANT='foo';
}
class Bar
{
/** @var non-empty-string */
public const CONSTANT='bar';
}
class Baz extends Bar implements Foo {}
```

View File

@@ -0,0 +1,39 @@
# ArgumentTypeCoercion
Emitted when calling a function with an argument which has a less specific type than the function expects
```php
<?php
class A {}
class B extends A {}
function takesA(A $a) : void {
takesB($a);
}
function takesB(B $b) : void {}
```
## How to fix
You could add a typecheck before the call to `takesB`:
```php
<?php
function takesA(A $a) : void {
if ($a instanceof B) {
takesB($a);
}
}
```
Or, if you have control over the function signature of `takesA` you can change it to expect `B`:
```php
<?php
function takesA(B $a) : void {
takesB($a);
}
```

View File

@@ -0,0 +1,25 @@
# AssignmentToVoid
Emitted when assigning from a function that returns `void`:
```php
<?php
function foo() : void {}
$a = foo();
```
## Why this is bad
Though `void`-returning functions are treated by PHP as returning `null` (so this on its own does not lead to runtime errors), `void` is a concept more broadly in programming languages which is not designed for assignment purposes.
## How to fix
You should just be able to remove the assignment entirely:
```php
<?php
function foo() : void {}
foo();
```

View File

@@ -0,0 +1,19 @@
# CheckType
Checks if a variable matches a specific type.
Similar to [Trace](./Trace.md), but only shows if the type does not match the expected type.
```php
<?php
/** @psalm-check-type $x = 1 */
$x = 2; // Checked variable $x = 1 does not match $x = 2
```
```php
<?php
/** @psalm-check-type-exact $x = int */
$x = 2; // Checked variable $x = int does not match $x = 2
```

View File

@@ -0,0 +1,14 @@
# CircularReference
Emitted when a class references itself as one of its parents
```php
<?php
class A extends B {}
class B extends A {}
```
## Why this is bad
The code above will not compile

View File

@@ -0,0 +1,3 @@
# ComplexFunction
Emitted when a function is too complicated. Complicated functions should be split up.

View File

@@ -0,0 +1,3 @@
# ComplexMethod
Emitted when a method is too complicated. Complicated methods should be split up.

View File

@@ -0,0 +1,7 @@
# ConfigIssue
Emitted for non-fatal configuration issues, e.g. deprecated configuration switches.
## Why this is bad
Your config file may be incompatible with future Psalm versions.

View File

@@ -0,0 +1,39 @@
# ConflictingReferenceConstraint
Emitted when a by-ref variable is set in two different branches of an if to different types.
```php
<?php
class A {
/** @var int */
private $foo;
public function __construct(int &$foo) {
$this->foo = &$foo;
}
}
class B {
/** @var string */
private $bar;
public function __construct(string &$bar) {
$this->bar = &$bar;
}
}
if (rand(0, 1)) {
$v = 5;
$c = (new A($v)); // $v is constrained to an int
} else {
$v = "hello";
$c = (new B($v)); // $v is constrained to a string
}
$v = 8;
```
## Why this is bad
Psalm doesn't understand what the type of `$c` should be

View File

@@ -0,0 +1,15 @@
# ConstantDeclarationInTrait
Emitted when a trait declares a constant in PHP <8.2.0
```php
<?php
trait A {
const B = 0;
}
```
## Why this is bad
A fatal error will be thrown.

View File

@@ -0,0 +1,17 @@
# ConstructorSignatureMismatch
Emitted when a constructor parameter differs from a parent constructor parameter, or if there are fewer parameters than the parent constructor AND where the parent class has a `@psalm-consistent-constructor` annotation.
```php
<?php
/**
* @psalm-consistent-constructor
*/
class A {
public function __construct(int $i) {}
}
class B extends A {
public function __construct(string $s) {}
}
```

View File

@@ -0,0 +1,14 @@
# ContinueOutsideLoop
Emitted when encountering a `continue` statement outside a loop context.
```php
<?php
$a = 5;
continue;
```
## Why this is bad
The code won't compile in PHP 5.6 and above.

View File

@@ -0,0 +1,19 @@
# DeprecatedClass
Emitted when referring to a deprecated class:
```php
<?php
/** @deprecated */
class A {}
new A();
```
## Why this is bad
The `@deprecated` tag is normally indicative of code that will stop working in the near future.
## How to fix
Dont use the deprecated class.

View File

@@ -0,0 +1,29 @@
# DeprecatedConstant
Emitted when referring to a deprecated constant or enum case:
```php
<?php
class A {
/** @deprecated */
const FOO = 'foo';
}
echo A::FOO;
enum B {
/** @deprecated */
case B;
}
echo B::B;
```
## Why this is bad
The `@deprecated` tag is normally indicative of code that will stop working in the near future.
## How to fix
Dont use the deprecated constant or enum case

View File

@@ -0,0 +1,19 @@
# DeprecatedFunction
Emitted when calling a deprecated function:
```php
<?php
/** @deprecated */
function foo() : void {}
foo();
```
## Why this is bad
The `@deprecated` tag is normally indicative of code that will stop working in the near future.
## How to fix
Dont use the deprecated function.

View File

@@ -0,0 +1,20 @@
# DeprecatedInterface
Emitted when referring to a deprecated interface
```php
<?php
/** @deprecated */
interface I {}
class A implements I {}
```
## Why this is bad
The `@deprecated` tag is normally indicative of code that will stop working in the near future.
## How to fix
Dont use the deprecated interface.

View File

@@ -0,0 +1,21 @@
# DeprecatedMethod
Emitted when calling a deprecated method on a given class:
```php
<?php
class A {
/** @deprecated */
public function foo() : void {}
}
(new A())->foo();
```
## Why this is bad
The `@deprecated` tag is normally indicative of code that will stop working in the near future.
## How to fix
Dont use the deprecated method.

View File

@@ -0,0 +1,24 @@
# DeprecatedProperty
Emitted when getting/setting a deprecated property of a given class
```php
<?php
class A {
/**
* @deprecated
* @var ?string
*/
public $foo;
}
(new A())->foo = 5;
```
## Why this is bad
The `@deprecated` tag is normally indicative of code that will stop working in the near future.
## How to fix
Dont use the deprecated property.

View File

@@ -0,0 +1,21 @@
# DeprecatedTrait
Emitted when referring to a deprecated trait:
```php
<?php
/** @deprecated */
trait T {}
class A {
use T;
}
```
## Why this is bad
The `@deprecated` tag is normally indicative of code that will stop working in the near future.
## How to fix
Dont use the deprecated trait.

View File

@@ -0,0 +1,12 @@
# DirectConstructorCall
Emitted when `__construct()` is called directly as a method. Constructors are supposed to be called implicitely, as a result of `new ClassName` statement.
```php
<?php
class A {
public function __construct() {}
}
$a = new A;
$a->__construct(); // wrong
```

View File

@@ -0,0 +1,40 @@
# DocblockTypeContradiction
Emitted when conditional doesn't make sense given the docblock types supplied.
```php
<?php
/**
* @param string $s
*
* @return void
*/
function foo($s) {
if ($s === null) {
throw new \Exception('Bad input');
}
echo $s;
}
```
## Why this is bad
This can sometimes point to a flaw in either your docblock types, or some unnecessary runtime checks in an environment where all types can be checked by Psalm, without the need for additional runtime checks.
## How to fix
A lot of old PHP code is set up to prevent unexpected errors with checks like the one above.
As you migrate your code to newer versions of PHP you can also use stricter type-hints:
```php
<?php
function foo(string $s) : void {
echo $s;
}
```
Since it's impossible for `$s` to be anything but a `string` inside the function, the null check can be removed.

View File

@@ -0,0 +1,40 @@
# DuplicateArrayKey
Emitted when an array has a key more than once
```php
<?php
$arr = [
'a' => 'one',
'b' => 'two',
'c' => 'this text will be overwritten by the next line',
'c' => 'three',
];
```
This can be caused by variadic arguments if `@no-named-arguments` is not specified:
```php
<?php
function foo($bar, ...$baz): array
{
return [$bar, ...$baz]; // $baz is array<array-key, mixed> since it can have named arguments
}
```
## How to fix
Remove the offending duplicates:
```php
<?php
$arr = [
'a' => 'one',
'b' => 'two',
'c' => 'three',
];
```
The first matching `'c'` key was removed to prevent a change in behaviour (any new duplicate keys overwrite the values of previous ones).

View File

@@ -0,0 +1,32 @@
# DuplicateClass
Emitted when a class is defined twice
```php
<?php
class A {}
class A {}
```
## Why this is bad
The above code wont compile.
PHP does allow you to define a class conditionally:
```php
<?php
if (rand(0, 1)) {
class A {
public function __construct(string $s) {}
}
} else {
class A {
public function __construct(object $o) {}
}
}
```
But Psalm _really_ doesn't want you to use this pattern it's impossible for Psalm to know (without using reflection) which class is getting used.

View File

@@ -0,0 +1,16 @@
# DuplicateConstant
Emitted when a constant is defined twice in a single class or when there's a
clash between a constant and an enum case.
```php
<?php
class C {
public const A = 1;
public const A = 2;
}
```
## Why this is bad
The above code wont compile.

View File

@@ -0,0 +1,27 @@
# DuplicateEnumCase
Emitted when enum has duplicate cases.
```php
<?php
enum Status
{
case Open;
case Open;
}
```
## How to fix
Remove or rename the offending duplicates.
```php
<?php
enum Status
{
case Open;
case Closed;
}
```

View File

@@ -0,0 +1,27 @@
# DuplicateEnumCaseValue
Emitted when a backed enum has duplicate case values.
```php
<?php
enum Status: string
{
case Open = "open";
case Closed = "open";
}
```
## How to fix
Change case values so that there are no duplicates.
```php
<?php
enum Status: string
{
case Open = "open";
case Closed = "closed";
}
```

View File

@@ -0,0 +1,11 @@
# DuplicateFunction
Emitted when a function is defined twice
```php
<?php
function foo() : void {}
function bar() : void {}
function foo() : void {}
```

View File

@@ -0,0 +1,12 @@
# DuplicateMethod
Emitted when a method is defined twice
```php
<?php
class A {
public function foo() {}
public function foo() {}
}
```

View File

@@ -0,0 +1,13 @@
# DuplicateParam
Emitted when a function has a param defined twice
```php
<?php
function foo(int $b, string $b) {}
```
## Why this is bad
The above code produces a fatal error in PHP.

View File

@@ -0,0 +1,10 @@
# EmptyArrayAccess
Emitted when attempting to access a value on an empty array
```php
<?php
$a = [];
$b = $a[0];
```

View File

@@ -0,0 +1,20 @@
# ExtensionRequirementViolation
Emitted when a using class of a trait does not extend the class specified using `@psalm-require-extends`.
```php
<?php
class A { }
/**
* @psalm-require-extends A
*/
trait T { }
class B {
// ExtensionRequirementViolation is emitted, as T requires
// the using class B to extend A, which is not the case
use T;
}
```

View File

@@ -0,0 +1,45 @@
# FalsableReturnStatement
Emitted if a return statement contains a false value, but the function return type does not allow false
```php
<?php
function getCommaPosition(string $a) : int {
return strpos($a, ',');
}
```
## How to fix
You can add a specific check for false:
```php
<?php
function getCommaPosition(string $a) : int {
$pos = strpos($a, ',');
if ($pos === false) {
return -1;
}
return $pos;
}
```
Alternatively you may choose to throw an exception:
```php
<?php
function getCommaPosition(string $a) : int {
$pos = strpos($a, ',');
if ($pos === false) {
throw new Exception('This is unexpected');
}
return $pos;
}
```

View File

@@ -0,0 +1,13 @@
# FalseOperand
Emitted when using `false` as part of an operation (e.g. `+`, `.`, `^` etc.)
```php
<?php
echo 5 . false;
```
## Why this is bad
`false` does not make sense in these operations

View File

@@ -0,0 +1,23 @@
# ForbiddenCode
Emitted when Psalm encounters a var_dump, exec or similar expression that may make your code more vulnerable
```php
<?php
var_dump("bah");
```
This functions list can be extended by configuring `forbiddenFunctions` in `psalm.xml`
```xml
<?xml version="1.0"?>
<psalm>
<!-- other configs -->
<forbiddenFunctions>
<function name="dd"/>
<function name="var_dump"/>
</forbiddenFunctions>
</psalm>
```

View File

@@ -0,0 +1,35 @@
# IfThisIsMismatch
Emitted when the type in `@psalm-if-this-is` annotation cannot be contained by the object's actual type.
```php
<?php
/**
* @template T
*/
class a {
/**
* @var T
*/
private $data;
/**
* @param T $data
*/
public function __construct($data) {
$this->data = $data;
}
/**
* @psalm-if-this-is a<int>
*/
public function test(): void {
}
}
$i = new a(123);
$i->test();
$i = new a("test");
// IfThisIsMismatch - Class is not a<int> as required by psalm-if-this-is
$i->test();
```

View File

@@ -0,0 +1,22 @@
# ImplementationRequirementViolation
Emitted when a using class of a trait does not implement all interfaces specified using `@psalm-require-implements`.
```php
<?php
interface A { }
interface B { }
/**
* @psalm-require-implements A
* @psalm-require-implements B
*/
trait T { }
class C {
// ImplementationRequirementViolation is emitted, as T requires
// the using class C to implement A and B, which is not the case
use T;
}
```

View File

@@ -0,0 +1,35 @@
# ImplementedParamTypeMismatch
Emitted when a class that inherits another, or implements an interface, has a docblock param type that's entirely different to the parent.
```php
<?php
class D {
/** @param string $a */
public function foo($a): void {}
}
class E extends D {
/** @param int $a */
public function foo($a): void {}
}
```
## How to fix
Make sure to respect the [Liskov substitution principle](https://en.wikipedia.org/wiki/Liskov_substitution_principle) any method that overrides a parent method must accept all the same arguments as its parent method.
```php
<?php
class D {
/** @param string $a */
public function foo($a): void {}
}
class E extends D {
/** @param string|int $a */
public function foo($a): void {}
}
```

View File

@@ -0,0 +1,43 @@
# ImplementedReturnTypeMismatch
Emitted when a class that inherits another, or implements an interface, has a docblock return type that's entirely different to the parent.
```php
<?php
class A {
/** @return bool */
public function foo() {
return true;
}
}
class B extends A {
/** @return string */
public function foo() {
return "hello";
}
}
```
## How to fix
Make sure to respect the [Liskov substitution principle](https://en.wikipedia.org/wiki/Liskov_substitution_principle) any method that overrides a parent method must return a subtype of the parent method.
In the above case, that means adding the child return type to the parent one.
```php
<?php
class A {
/** @return bool|string */
public function foo() {
return true;
}
}
class B extends A {
/** @return string */
public function foo() {
return "hello";
}
}
```

View File

@@ -0,0 +1,29 @@
# ImplicitToStringCast
Emitted when implicitly converting an object with a `__toString` method to a string
```php
<?php
class A {
public function __toString() {
return "foo";
}
}
function takesString(string $s) : void {}
takesString(new A);
```
## How to fix
You can add an explicit string cast:
```php
<?php
...
takesString((string) new A);
```

View File

@@ -0,0 +1,30 @@
# ImpureByReferenceAssignment
Emitted when assigning a passed-by-reference variable inside a function or method marked as mutation-free.
```php
<?php
/**
* @psalm-pure
*/
function foo(string &$a): string {
$a = "B";
return $a;
}
```
## How to fix
Just remove the mutating assignment:
```php
<?php
/**
* @psalm-pure
*/
function foo(string &$a): string {
return $a;
}
```

View File

@@ -0,0 +1,23 @@
# ImpureFunctionCall
Emitted when calling an impure function from a function or method marked as pure.
```php
<?php
function impure(array $a) : array {
/** @var int */
static $i = 0;
++$i;
$a[$i] = 1;
return $a;
}
/** @psalm-pure */
function filterOdd(array $a) : void {
impure($a);
}
```

View File

@@ -0,0 +1,26 @@
# ImpureMethodCall
Emitted when calling an impure method from a function or method marked as pure.
```php
<?php
class A {
public int $a = 5;
public function foo() : void {
$this->a++;
}
}
/** @psalm-pure */
function filterOdd(int $i, A $a) : ?int {
$a->foo();
if ($i % 2 === 0 || $a->a === 2) {
return $i;
}
return null;
}
```

View File

@@ -0,0 +1,18 @@
# ImpurePropertyAssignment
Emitted when updating a property value from a function or method marked as pure.
```php
<?php
class A {
public int $a = 5;
}
/** @psalm-pure */
function foo(int $i, A $a) : int {
$a->a = $i;
return $i;
}
```

View File

@@ -0,0 +1,16 @@
# ImpurePropertyFetch
Emitted when fetching a property value inside a function or method marked as pure.
```php
<?php
class A {
public int $a = 5;
}
/** @psalm-pure */
function foo(int $i, A $a) : int {
return $i + $a->a;
}
```

View File

@@ -0,0 +1,18 @@
# ImpureStaticProperty
Emitted when attempting to use a static property from a function or method marked as pure
```php
<?php
class ValueHolder {
public static ?string $value = null;
/**
* @psalm-pure
*/
public static function get(): ?string {
return self::$value;
}
}
```

View File

@@ -0,0 +1,15 @@
# ImpureStaticVariable
Emitted when attempting to use a static variable from a function or method marked as pure
```php
<?php
/** @psalm-pure */
function addCumulative(int $left) : int {
/** @var int */
static $i = 0;
$i += $left;
return $left;
}
```

View File

@@ -0,0 +1,18 @@
# ImpureVariable
Emitted when referencing an impure or possibly-impure variable from a pure context.
```php
<?php
class A {
public int $a = 5;
/**
* @psalm-pure
*/
public function foo() : self {
return $this;
}
}
```

View File

@@ -0,0 +1,12 @@
# InaccessibleClassConstant
Emitted when a public/private class constant is not accessible from the calling context
```php
<?php
class A {
protected const FOO = 'FOO';
}
echo A::FOO;
```

View File

@@ -0,0 +1,12 @@
# InaccessibleMethod
Emitted when attempting to access a protected/private method from outside its available scope
```php
<?php
class A {
protected function foo() : void {}
}
echo (new A)->foo();
```

View File

@@ -0,0 +1,13 @@
# InaccessibleProperty
Emitted when attempting to access a protected/private property from outside its available scope
```php
<?php
class A {
/** @return string */
protected $foo;
}
echo (new A)->foo;
```

View File

@@ -0,0 +1,10 @@
# InterfaceInstantiation
Emitted when an attempt is made to instantiate an interface:
```php
<?php
interface I {}
new I();
```

View File

@@ -0,0 +1,23 @@
# InternalClass
Emitted when attempting to access a class marked as internal an unrelated namespace or class, or attempting
to access a class marked as psalm-internal to a different namespace.
```php
<?php
namespace A {
/**
* @internal
*/
class Foo { }
}
namespace B {
class Bat {
public function batBat(): void {
$a = new \A\Foo();
}
}
}
```

View File

@@ -0,0 +1,25 @@
# InternalMethod
Emitted when attempting to access a method marked as internal an unrelated namespace or class, or attempting
to access a method marked as psalm-internal to a different namespace.
```php
<?php
namespace A {
class Foo {
/**
* @internal
*/
public static function barBar(): void {
}
}
}
namespace B {
class Bat {
public function batBat(): void {
\A\Foo::barBar();
}
}
}
```

View File

@@ -0,0 +1,26 @@
# InternalProperty
Emitted when attempting to access a property marked as internal from an unrelated namespace or class, or attempting
to access a property marked as psalm-internal to a different namespace.
```php
<?php
namespace A {
class Foo {
/**
* @internal
* @var ?int
*/
public $foo;
}
}
namespace B {
class Bat {
public function batBat() : void {
echo (new \A\Foo)->foo;
}
}
}
```

View File

@@ -0,0 +1,40 @@
# InvalidArgument
Emitted when a supplied function/method argument is incompatible with the method signature or docblock one.
```php
<?php
class A {}
function foo(A $a) : void {}
/**
* @param string $s
*/
function callFoo($s) : void {
foo($s);
}
```
## Why its bad
Calling functions with incorrect values will cause a fatal error at runtime.
## How to fix
Sometimes this message can just be the result of an incorrect docblock.
You can fix by correcting the docblock, or converting to a function signature:
```php
<?php
class A {}
function foo(A $a) : void {}
function callFoo(A $a) : void {
foo($a);
}
```

View File

@@ -0,0 +1,10 @@
# InvalidArrayAccess
Emitted when attempting to access an array offset on a value that does not permit it
```php
<?php
$arr = 5;
echo $arr[0];
```

View File

@@ -0,0 +1,10 @@
# InvalidArrayAssignment
Emitted when attempting to assign a value on a non-array
```php
<?php
$arr = 5;
$arr[0] = 3;
```

View File

@@ -0,0 +1,10 @@
# InvalidArrayOffset
Emitted when attempting to access an array using a value that's not a valid offset for that array
```php
<?php
$a = [5, 20, 18];
echo $a["hello"];
```

View File

@@ -0,0 +1,16 @@
# InvalidAttribute
Emitted when using an attribute on an element that doesn't match the attribute's target
```php
<?php
namespace Foo;
#[\Attribute(\Attribute::TARGET_CLASS)]
class Table {
public function __construct(public string $name) {}
}
#[Table("videos")]
function foo() : void {}
```

View File

@@ -0,0 +1,11 @@
# InvalidCast
Emitted when attempting to cast a value that's not castable
```php
<?php
class A {}
$a = new A();
$b = (string)$a;
```

View File

@@ -0,0 +1,13 @@
# InvalidCatch
Emitted when trying to catch a class/interface that doesn't extend `Exception` or implement `Throwable`
```php
<?php
class A {}
try {
$worked = true;
}
catch (A $e) {}
```

View File

@@ -0,0 +1,26 @@
# InvalidClass
Emitted when referencing a class with the wrong casing
```php
<?php
class Foo {}
(new foo());
```
Could also be an issue in the namespace even if the class has the correct casing
```php
<?php
namespace OneTwo {
class Three {}
}
namespace {
use Onetwo\Three;
// ^ ("t" instead of "T")
$three = new Three();
}
```

View File

@@ -0,0 +1,28 @@
# InvalidClassConstantType
Emitted when a constant type in a child does not satisfy the type in the parent.
```php
<?php
class Foo
{
/** @var int<1,max> */
public const CONSTANT = 3;
public static function bar(): array
{
return str_split("foobar", static::CONSTANT);
}
}
class Bar extends Foo
{
/** @var int<min,-1> */
public const CONSTANT = -1;
}
Bar::bar(); // Error: str_split argument 2 must be greater than 0
```
This issue will always show up when overriding a constant that doesn't have a docblock type. Psalm will infer the most specific type for the constant that it can, you have to add a type annotation to tell it what type constraint you wish to be applied. Otherwise Psalm has no way of telling if you mean for the constant to be a literal `1`, `int<1, max>`, `int`, `numeric`, etc.

View File

@@ -0,0 +1,10 @@
# InvalidClone
Emitted when trying to clone a value that's not cloneable
```php
<?php
$a = "hello";
$b = clone $a;
```

View File

@@ -0,0 +1,12 @@
# InvalidConstantAssignmentValue
Emitted when attempting to assign a value to a class constant that cannot contain that type.
```php
<?php
class Foo {
/** @var int */
public const BAR = "bar";
}
```

Some files were not shown because too many files have changed in this diff Show More