-
Notifications
You must be signed in to change notification settings - Fork 660
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add @psalm-require-usage annotation and report UnusedFunctionCall for @psalm-taint-escape calls #10742
base: 5.x
Are you sure you want to change the base?
Conversation
Overall this seems useful. Am I right the proposed changes allow |
I found these snippets: https://psalm.dev/r/afb33d970e<?php
/**
* @psalm-pure
* @psalm-assert-if-true int|string $val
*/
function acceptable(mixed $val): bool {
return is_string($val) || is_int($val);
}
acceptable($argv[0]);
|
@weirdan thanks, in that case I actually got it the wrong way round then - bc atm there is a bug that your example when wrapped in a class doesn't report an error: https://psalm.dev/r/7093ba0a35 - I assumed this is correct there and just ported that part from Methods to Functions - turns out this was a bug in methods - which I fixed now. |
I found these snippets: https://psalm.dev/r/7093ba0a35<?php
class Foo {
/**
* @psalm-pure
* @psalm-assert-if-true int|string $val
*/
public function acceptable(mixed $val): bool {
return is_string($val) || is_int($val);
}
}
$foo = new Foo();
$foo->acceptable($argv[0]);
|
10a56fb
to
6125c55
Compare
@weirdan could you please review if it's alright for you like that now - then I will remove all the unused @psalm-suppress in unrelated code too, before it gets merged (just want to avoid unnecessary re/undos) |
This seems to be very useful! 👍 https://psalm.dev/r/4daa849fa9 /**
* @psalm-require-usage
*/
class AccessDeniedResponse implements ResponseInterface {} Questions concerning this PR
|
I found these snippets: https://psalm.dev/r/4daa849fa9<?php
/** PSR-7/PSR-15 stubs */
interface ResponseInterface {}
interface RequestHandlerInterface {}
interface ServerRequestInterface {}
class Response implements ResponseInterface
{
public function __construct(string $message, int $code) {}
}
/** --- actual static analysis --- */
/**
* @psalm-require-usage
*/
class AccessDeniedResponse implements ResponseInterface
{
public function __construct(string $message, int $code) {}
}
class SomeController implements RequestHandlerInterface
{
public function handle(ServerRequestInterface $request): ResponseInterface
{
$this->authorize();
// the possible `AccessDeniedResponse` of method `authorize` is not used,
// as a result the un-authorized process execution continues...
return new Response('All good... Here are the secrets...', 200);
}
private function authorize(): ?ResponseInterface
{
if ($this->anyAuthorizationCheck() === false) {
return new AccessDeniedResponse('Forbidden', 403);
}
return null;
}
private function anyAuthorizationCheck(): bool
{
return false;
}
}
|
@ohader the thing you're describing looks much more complicated then what's proposed here. While a return value usage is fairly easy to define (any read operation), usage of an atomic in a union is much less clear. E.g. would all of the following constitute usage of https://psalm.dev/r/fcb87e545b Also note that in all of those cases we don't really see |
I found these snippets: https://psalm.dev/r/fcb87e545b<?php
interface Response {}
final class AccessDenied implements Response {}
final class Ok implements Response {}
function authorize(): ?Response {
return rand(0, 1) ? null : (rand(0, 1) ? new AccessDenied : new Ok);
}
function f1(): void {
if (authorize() === null) {
// was Response used here?
echo 'secrets';
}
}
function f2(): void {
if (authorize()) {
// was it used here?
throw new Exception;
}
echo 'secrets';
}
function f3(): void {
if (!authorize() instanceof Response) {
// what about this?
echo 'secrets';
}
}
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably add one more thing here. Since void|never + require-usage
is pointless it would make sense to flag those combinations. E.g.
/**
* @psalm-require-usage
*/
function f(): never { exit; }
// InvalidDocblock(?): @require-usage is incompatible with `never` return type
@weirdan Thanks for providing the examples! I agree that my idea would make it much more complicated. Besides that, I did not want to block this change nor request any changes in particular. Sorry for hijacking this PR with my own agenda... |
d60e59a
to
5b4276c
Compare
…oring the original condition it used when added in bbde2d6
4ba1bb9
to
ecdc9a4
Compare
@weirdan added errors for invalid annotations and updated tests. It's done. |
@weirdan can you please merge this |
May I ask for a code review :) |
By default psalm considers all functions as impure and UnusedFunctionCall errors are only emitted for pure functions, since pure functions have no side-effects and therefore their return value must be used (otherwise the function call is useless)
There are some cases, where you want to force people to use the return value of a function call even if it's not pure - e.g. for escaping functions or when upgrading legacy code bases where a function returned void/never previously, but now returns a value that must get validated.
Currently, there is no valid way to do that (you could add @psalm-pure and suppress that on the function, but that will lead to other issues)
I propose a new @psalm-require-usage to get this behavior.
(I'd prefer @psalm-require-use however I think that might lead to confusion with @use/@template-use/@psalm-use)
Additionally, anything annotated with @psalm-taint-escape will also report unused function calls by default, since the escape function call only makes sense if the returned value is used.
Since phpstan doesn't offer this functionality in the first place (since by default it treats everything as pure https://phpstan.org/writing-php-code/phpdocs-basics#impure-functions), there are no PHP eco-system big picture concerns either.
Summary:
@psalm-require-usage
or has a@psalm-taint-escape
annotation