Laravel has a response() helper function:
response() returns a ResponseFactory objectresponse('Hello world') returns a Response objectDoes Psalm currently support annotating this @return type depending on the argument count? Conditional types don't seem to support this.
Here's a derived example:
https://psalm.dev/r/927336a62d
I found these snippets:
https://psalm.dev/r/927336a62d
<?php
/**
* @param string $content
* @return string|bool
*/
function mirror($content = '') {
if (func_num_args() === 0) {
return false;
}
return $content;
}
$str = mirror('x');
echo strlen($str);
Psalm output (using commit 21f4dee):
ERROR: InvalidScalarArgument - 16:13 - Argument 1 of strlen expects string, bool|string provided
Here's a closer example:
I found these snippets:
https://psalm.dev/r/bd04a5ea2a
<?php
class Response {
private string $content;
public function __construct(string $content) {
$this->content = $content;
}
public function getContent(): string
{
return $this->content;
}
}
class ResponseFactory {
public function json(array $data): Response {
$content = json_encode($data);
return new Response($content);
}
}
/**
* @return Response|ResponseFactory
*/
function response(string $content = '') {
if (func_num_args() === 0) {
return new ResponseFactory();
}
return new Response($content);
}
echo response()
->json(['foo' => 'bar'])
->getContent();
Psalm output (using commit 21f4dee):
ERROR: PossiblyUndefinedMethod - 35:7 - Method Response::json does not exist
INFO: MixedMethodCall - 36:7 - Cannot determine the type of the object on the left hand side of this expression
INFO: MixedArgument - 34:6 - Argument 1 of echo cannot be mixed, expecting string
You don't need argument count if you can ignore the case of return('') not returning the factory as return type then may depend on the parameter type (literal empty string in this case): https://psalm.dev/r/306cc1e212
I found these snippets:
https://psalm.dev/r/306cc1e212
<?php
class Response {
private string $content;
public function __construct(string $content) {
$this->content = $content;
}
public function getContent(): string
{
return $this->content;
}
}
class ResponseFactory {
public function json(array $data): Response {
$content = json_encode($data);
return new Response($content);
}
}
/**
* @return Response|ResponseFactory
* @template T of string
* @psalm-param T $content
* @psalm-return (T is '' ? ResponseFactory : Response)
*/
function response(string $content = '') {
if (func_num_args() === 0) {
return new ResponseFactory();
}
return new Response($content);
}
echo response()
->json(['foo' => 'bar'])
->getContent();
echo response('html')->getContent();
Psalm output (using commit 21f4dee):
No issues!
You don't need argument count if you can ignore the case of
return('')not returning the factory as return type then may depend on the parameter type (literal empty string in this case): https://psalm.dev/r/306cc1e212
Good idea, but unfortunately that workaround only works for explicit strings, not for string parameters:
https://psalm.dev/r/cd6d14ee01
I found these snippets:
https://psalm.dev/r/306cc1e212
<?php
class Response {
private string $content;
public function __construct(string $content) {
$this->content = $content;
}
public function getContent(): string
{
return $this->content;
}
}
class ResponseFactory {
public function json(array $data): Response {
$content = json_encode($data);
return new Response($content);
}
}
/**
* @return Response|ResponseFactory
* @template T of string
* @psalm-param T $content
* @psalm-return (T is '' ? ResponseFactory : Response)
*/
function response(string $content = '') {
if (func_num_args() === 0) {
return new ResponseFactory();
}
return new Response($content);
}
echo response()
->json(['foo' => 'bar'])
->getContent();
echo response('html')->getContent();
Psalm output (using commit 21f4dee):
No issues!
https://psalm.dev/r/cd6d14ee01
<?php
class Response {
private string $content;
public function __construct(string $content) {
$this->content = $content;
}
public function getContent(): string
{
return $this->content;
}
}
class ResponseFactory {
public function json(array $data): Response {
$content = json_encode($data);
return new Response($content);
}
}
/**
* @return Response|ResponseFactory
* @template T of string
* @psalm-param T $content
* @psalm-return (T is '' ? ResponseFactory : Response)
*/
function response(string $content = '') {
if (func_num_args() === 0) {
return new ResponseFactory();
}
return new Response($content);
}
function stringResponse(string $content): Response {
return response($content);
}
Psalm output (using commit 21f4dee):
ERROR: InvalidReturnStatement - 38:12 - The inferred type 'Response|ResponseFactory' does not match the declared return type 'Response' for stringResponse
ERROR: InvalidReturnType - 37:43 - The declared return type 'Response' for stringResponse is incorrect, got 'Response|ResponseFactory'
@caugner I assume this is in a stub file then yeah? I think you might be able to get away with declaring the parameter string|null, and then checking if it's a string.
I did something similar for laravel factories https://github.com/mr-feek/psalm-plugin-laravel/pull/1/files
Would love to have you PR whatever you have so far against the laravel plugin and we can get it over the finish line!
HMMMMMM
What if conditional return types supported
($argc > 1 ? string : int)
That wouldn't be too hard to add to the parser, I don't think
HMMMMMM
What if conditional return types supported
($argc > 1 ? string : int)That wouldn't be too hard to add to the parser, I don't think
Sounds like a good solution to me!
I made the solution a little more conservative – $argc already has a meaning, so I used func_num_args() instead:
(func_num_args() is 0 ? int : string)
@muglug Thank you for this great feature and the rapid implementation!
Awesome, this will be helpful for me too! Thanks @muglug
Most helpful comment
I made the solution a little more conservative –
$argcalready has a meaning, so I usedfunc_num_args()instead:(func_num_args() is 0 ? int : string)