8000 Add support for template default types · Issue #4801 · phpstan/phpstan · GitHub
[go: up one dir, main page]

Skip to content
Add support for template default types #4801
@simPod

Description

@simPod

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>;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      0