packagist vimeo/psalm 3.15
Automatically add pure/immutable annotations

latest releases: 5.x-dev, 6.x-dev, dev-master...
4 years ago

Features

This release brings a couple of features that are useful to fans of functional programming.

If you're unfamiliar with these concepts, or want to know how Psalm uses them, read this article first.

Automatic addition of @psalm-pure/@psalm-immutable annotations (#4036):

Running vendor/bin/psalm --alter --issues=MissingPureAnnotation,MissingImmutableAnnotation will add those annotations to any function, method or class that deserves it.

So

function sayHello(string $s): string {
    return 'Hello ' . $s;
}

is transformed into

/**
 * @psalm-pure
 */
function sayHello(string $s): string {
    return 'Hello ' . $s;
}

Note: running this command will not recursively add annotations, so if you have a chain of callers like

function one(string $s) { return two($s); }

function two(string $s) { return $s; }

running the command once will produce

function one(string $s) { return two($s); }

/**
 * @psalm-pure
 */
function two(string $s) { return $s; }

and running it a second time will produce

/**
 * @psalm-pure
 */
function one(string $s) { return two($s); }

/**
 * @psalm-pure
 */
function two(string $s) { return $s; }

Pure callables and closures

Thanks to @azjezz, Psalm now understands the annotation pure-callable, which allows you to guarantee the purity of a pure function that executes a callable.

Let's say we want to return the longest string in an array of strings ["a", "bbb", "cc"]. We could write that function pretty simply, but let's suppose we want to make it a bit more generic: given an array of items, and a callable that returns a score for the each entry, return the highest-scoring value.

We can define that function in PHP like

/**
 * @psalm-param non-empty-array $values
 * @psalm-pure
 */
function get_max(array $values, callable $score_func) {
    $max = reset($values);
    $max_num = null;
    foreach ($values as $value) {
        $value_num = $score_func($value);
        if (null === $max_num || $value_num >= $max_num) {
            $max = $value;
            $max_num = $value_num;
        }
    }

    return $max;
}

echo get_max(['a', 'bbb', 'cc'], 'strlen'); // outputs "bbb"

This function is only pure, though, if the callable that we're passing is pure.

With this latest version the full, pure type signature of the function can be written:

/**
 * @template T
 * @psalm-param non-empty-array<T> $values
 * @psalm-param pure-callable(T):int $score_func
 * @psalm-return T
 *
 * @psalm-pure
 */
function get_max(array $values, callable $score_func) { ... }

Bugfixes

  • preserve intersections when expanding templated types (#4043)
  • don't remove null types unnecessarily in mixed union, and refine iterable keys after is_array check (#4038)
  • assume most iterators are impure (#4064)
  • process indirect comparisons to null in assertions (#4061)
  • allow pure functions to return impure closures (#4077)
  • allow literal reconciliation against positive-int types (#4081, #4093)
  • allow ParamNameMismatch to be suppressed locally (#4012)
  • fix parsing of union param types inside docblock @method annotations(#4083)
  • @staabm added a slightly-improved return type for strpos that precludes negative numbers
  • @weirdan fixed #3869 by creating a per-user cache directory
  • MissingPropertyType can now be refined on a per-property basis (#2200)
  • @lhchavez broadened the param type for strval to allow null

Don't miss a new psalm release

NewReleases is sending notifications on new releases.