Skip to content
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

A way to use signature of another method as a type #8716

Open
weirdan opened this issue Nov 18, 2022 · 2 comments
Open

A way to use signature of another method as a type #8716

weirdan opened this issue Nov 18, 2022 · 2 comments

Comments

@weirdan
Copy link
Collaborator

weirdan commented Nov 18, 2022

Rationale

Some methods may have signatures based on other methods. One particular example is Laravel's Dispatchable trait, where methods have signatures based on constructor signature.

It would be great if we had a way to specify something like this:

<?php declare(strict_types=1);

trait Creatable 
{
   /** @param method-args<static::__construct> $args */
   public static function create(...$args): static
   {
      return new static(...$args);
   }
   
   /** @param method-args<static::__construct> $args */
   public static function createIf(bool $condition, ...$args): static|null
   {
      return $condition ? new static(...$args) : null;
   }
}

final class A
{
   use Creatable;
   public function __construct(private int $param) {}
}

final class B
{
   use Creatable; 
   public function __construct(private string $param) {}
}

$a = A::create(2); // valid
$b = B::createIf(true === true, "42"); // valid

$aa = A::createIf(true, "42"); // invalid
$bb = B::create(2); // invalid

https://psalm.dev/r/bd50e7e47e

Prior art

Parameters<T>, ConstructorParameters<T> and ReturnType<T> in Typescript.

Typescript, however, builds those types on function signature types + generics + infer: https://github.com/microsoft/TypeScript/blob/a3092c798ad9f165b0f7cba964a2c7b976cd30d0/lib/lib.es5.d.ts#L1601

@psalm-github-bot
Copy link

I found these snippets:

https://psalm.dev/r/bd50e7e47e
<?php

trait Creatable 
{
   /** @param method-args<static::__construct> $args */
   public static function create(...$args): static
   {
      return new static(...$args);
   }
   
   /** @param method-args<static::__construct> $args */
   public static function createIf(bool $condition, ...$args): static|null
   {
      return $condition ? new static(...$args) : null;
   }
}

final class A
{
   use Creatable;
   public function __construct(private int $param) {}
}

final class B
{
   use Creatable; 
   public function __construct(private string $param) {}
}

$a = A::create(2); // valid
$b = B::createIf(true === true, "42"); // valid

$aa = A::createIf(true, "42"); // invalid
$bb = B::create(2); // invalid
Psalm output (using commit 12f33fa):

ERROR: InvalidArgument - 30:16 - Argument 1 of A::create expects method-args<A::__construct>, but 2 provided

ERROR: InvalidArgument - 31:33 - Argument 2 of B::createIf expects method-args<B::__construct>, but '42' provided

ERROR: InvalidArgument - 33:25 - Argument 2 of A::createIf expects method-args<A::__construct>, but '42' provided

ERROR: InvalidArgument - 34:17 - Argument 1 of B::create expects method-args<B::__construct>, but 2 provided

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

INFO: UnusedVariable - 31:1 - $b is never referenced or the value is not used

INFO: UnusedVariable - 33:1 - $aa is never referenced or the value is not used

INFO: UnusedVariable - 34:1 - $bb is never referenced or the value is not used

ERROR: UndefinedDocblockClass - 5:15 - Docblock-defined class, interface or enum named method-args does not exist

ERROR: UndefinedConstant - 5:15 - Constant A::__construct is not defined

INFO: MixedInferredReturnType - 6:45 - Could not verify return type 'A' for Creatable::create

ERROR: UndefinedDocblockClass - 11:15 - Docblock-defined class, interface or enum named method-args does not exist

ERROR: UndefinedConstant - 11:15 - Constant A::__construct is not defined

INFO: MixedInferredReturnType - 12:64 - Could not verify return type 'A|null' for Creatable::createIf

ERROR: UndefinedConstant - 5:15 - Constant B::__construct is not defined

INFO: MixedInferredReturnType - 6:45 - Could not verify return type 'B' for Creatable::create

ERROR: UndefinedConstant - 11:15 - Constant B::__construct is not defined

INFO: MixedInferredReturnType - 12:64 - Could not verify return type 'B|null' for Creatable::createIf

@weirdan
Copy link
Collaborator Author

weirdan commented Nov 18, 2022

Could be extended to functions and callable, and include return types:

/**
 * @param function-args<$func> $args
 * @return function-return<$func>
 */
function call_user_func(callable $func, ...$args) { ... }

@weirdan weirdan changed the title A way to copy signature of another method as a type A way to use signature of another method as a type Nov 18, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant