psalm icon indicating copy to clipboard operation
psalm copied to clipboard

Code comment that asserts a variable satisfies a type (@psalm-satisfies)

Open annervisser opened this issue 10 months ago • 2 comments

Feature request: @psalm-satisfies comment annotation

Problem

It can be useful for multiple reasons to annotate the type of a variable that psalm already knows the type of:

  • Readability for other developers without them grokking complex return types
  • Helping IDEs understand the types of variables (for cases where PHPStorm is not as smart as psalm)
  • As a replacement for runtime checks that prevent bugs from code changes elsewhere

Currently this can only be done by using @var or @psalm-var comments. But these tags are unsafe: Psalm treats those as gospel, and assumes whatever you put in them to be true.

Solution

I propose a new tag that specifies the type of a variable, but that Psalm actually verifies is correct. This would allow expressing type expectations in code.

Example:

/**
 * @psalm-return ($asList is true ? list<string> : array<string, string>)
 */
function getItems(bool $asList): array
{
    $arr = ['a' => 'item', 'another' => 'item'];
    return $asList ? array_values($arr) : $arr;
}

$a = getItems(true);
/** @psalm-satisfies list<string> $a */
// From here on out anyone reading can safely assume that $a is list<string>
// And with a bit of luck PhpStorm could start interpreting psalm-satisfies the same as a @var tag
Problems
  • For this to be useful for IDEs, they would have to explicitly add support for this tag.
    • PHPStorm already has support for a lot of Psalm's annotations, so it might add support if this is implemented.
    • An alternative syntax re-using the @var annotation could also be considered, see Alternatives below

Alternatives

  • It might be more consistent to allow inline @psalm-assert $variable instead of introducing a new tag.
  • To work with IDEs out of the box, the @var tag could be re-used:
    • Perhaps a differentiation between @var and @psalm-var
    • Or a way to mark a @var as an assert: /** @var list<string> $myVar !assert */

annervisser avatar Apr 10 '24 07:04 annervisser

I found these snippets:

https://psalm.dev/r/721c3dae1b
<?php
	/**
	 * @psalm-return ($asList is true ? list<string> : array<string, string>)
	 */
 function getItems(bool $asList): array
	{
		$arr = ['a' => 'item', 'another' => 'item'];
		return $asList ? array_values($arr) : $arr;
	}

$a = getItems(true);
/** @psalm-satisfies list<string> $a */
// From here on out I know that $a is list<string>
// And with a bit of luck PhpStorm could start interpreting psalm-satisfies the same as a @var tag
Psalm output (using commit ef3b018):

ERROR: InvalidDocblock - 14:99 - Unrecognised annotation @psalm-satisfies

INFO: UnusedVariable - 11:1 - $a is never referenced or the value is not used

psalm-github-bot[bot] avatar Apr 10 '24 07:04 psalm-github-bot[bot]

See https://github.com/vimeo/psalm/pull/10577 for the WIP implementation

danog avatar Apr 11 '24 18:04 danog