-
-
Notifications
You must be signed in to change notification settings - Fork 932
Description
Feature request
If template is impossible to resolve, eg. because the type is T|null
, it should be possible to specify a default type
Consider this: If a value passed for $a
is null
, it's impossible to resolve TResult
's type. Then default type should be used. Currently, the default type everywhere is mixed
instead.
/**
* @template T
*/
interface I {
/**
* @template TResult
* @param (callable(T): TResult)|null $a
* @return I<TResult>
*/
public function work(callable|null $a = null): I;
}
It should be possible to say that TResult
default is T
. Therefore, when calling work(null)
on I<string>
, the return type would be string
.
https://phpstan.org/r/15309f12-13f8-4e0b-ae9f-a811e3182a94
This would finally allow us to type eg. promises. With Fibers this would be required in order to prevent using mixed everywhere.
/** @template T */
interface Promise
{
/**
* @template TResult1
* @template TResult2
* @template-default TResult1=T
* @template-default TResult2=never
*
* @param (callable(T): TResult1)|null $a
* @param (callable(mixed): TResult2)|null $b
*
* @return
*/
public function then(callable|null $a = null, callable|null $b = null) : Promise;
}
class C {
private callable $aCall = fn(int $a): string => (string) $a;
private callable $bCall = fn(bool $b): bool => $b;
/**
* @param Promise<int> $promise
*
* @return Promise<int>
*/
public function nulls(Promise $promise) : Promise
{
return $promise->then(null, null);
}
/**
* @param Promise<int> $promise
*
* @return Promise<string|bool>
*/
public function a_b(Promise $promise) : Promise
{
return $promise->then($this->aCall, $this->bCall);
}
/**
* @param Promise<int> $promise
*
* @return Promise<string>
*/
function a(Promise $promise) : Promise
{
return $promise->then($this->aCall, null);
}
/**
* @param Promise<int> $promise
*
* @return Promise<bool>
*/
function b(Promise $promise) : Promise
{
return $promise->then(null, $this->bCall);
}
}
Conditional types are not a thing for this. It's crazy complicated to write something like that using them. I guess that's also the reason why it does not work in psalm that support conditional types but only the simple ones.
/**
* @template T
*/
interface Promise
{
/**
* @template TResult1
* @template TResult2
*
* @param (callable(T): TResult1)|null $a
* @param (callable(mixed): TResult2)|null $b
*
* @return (
* $a is null
* ? (
* $b is null
* ? Promise<T>
* : Promise<TResult2>
* )
* : (
* $b is null
* ? Promise<TResult1>
* : Promise<TResult1|TResult2>
* )
* )
*/
public function then(callable|null $a = null, callable|null $b = null) : Promise;
}
This feature is already present in other languages like typescript or java
interface I<T>
{
work<TResult = T>(a: ((value: T) => TResult) | null): TResult;
}
interface Promise<T> {
then<TResult1 = T, TResult2 = never>(
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null
): Promise<TResult1 | TResult2>;
}