-
-
Notifications
You must be signed in to change notification settings - Fork 146
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
[2.x] Add basic template annotations #227
[2.x] Add basic template annotations #227
Conversation
* @param callable|null $onRejected | ||
* @param callable|null $onProgress This argument is deprecated and should not be used anymore. | ||
* @return PromiseInterface | ||
* @return (TReturn is PromiseInterface ? TReturn : PromiseInterface<TReturn>) |
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.
How should we handle cases such as this?
$in = resolve(mt_rand(0, 100));
$out = $in->then(function (int $n): float {
if ($n === 0) {
throw new \UnexpectedValueException();
}
return 1 / $n;
});
I see you did some work regarding type assertions in phpstan/phpstan#7371, perhaps this is something we should include as part of this PR? 👍
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.
Could do that, but then just the basics, and not the more complicated cases 👍 .
src/PromiseInterface.php
Outdated
@@ -2,6 +2,7 @@ | |||
|
|||
namespace React\Promise; | |||
|
|||
/** @template T */ |
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.
Should we have a TFulfilled
and a TRejected
as discussed in phpstan/phpstan#7371?
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.
Playing with that locally at the moment to get the types checking in CI as you mentioned above.
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.
After the discussion below, perhaps we should indeed disregard my comment and focus solely on TFulfilled
instead? (#227 (comment))
Sorry for the confusion!
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.
After the discussion below, perhaps we should indeed disregard my comment and focus solely on
TFulfilled
instead? (#227 (comment))
Done!
Sorry for the confusion!
No worries, we needed that discussion 😃
* @param callable|null $onRejected | ||
* @param callable|null $onProgress This argument is deprecated and should not be used anymore. | ||
* @return PromiseInterface | ||
* @return (TReturn is PromiseInterface ? TReturn : PromiseInterface<TReturn>) | ||
*/ | ||
public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null); |
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.
I see this currently only affects the then()
method, should we also add this for the methods defined in ExtendedPromiseInterface
? (Despite being removed in Promise v3 via #75.)
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.
This is purposely only covering then
and not the other methods. In #223 I want to expand that to other methods. But the primary goal is to get this ready for reactphp/async#40 and the minimum we need for that is support only on the then
method. If we can add more methods before that release of that I'm all up for it. But if we don't I propose we leave it out.
da8e9b4
to
9459def
Compare
@clue Updated the PR with a types directory for PHPStan type checking, and expanded the annotations a bit |
9459def
to
8566b8d
Compare
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.
I don't think the proposed typing is correct. I tried to point out some of the problems in this PHPStan playground: https://phpstan.org/r/af4e982e-7118-4572-b180-3769009a906f
In general, the problem is that the promise is declared like PromiseInterface<T, R>
but then is used everywhere like PromiseInterface<T>
. That doesn't work because PHPStan (unsure about Psalm) doesn't support default template types (@template R = never
).
I'm not convinced that it's worth trying to type promise with two generic types, nor I'm sure that's actually possible to do it properly. Having a single template type for the fulfilled value like TypeScript has would be much easier: https://github.com/microsoft/TypeScript/blob/2ecde2718722d6643773d43390aa57c3e3199365/lib/lib.es5.d.ts#L1446-L1461
Anyway, should it be two generic templates, then I believe it should be like this (using a pseudocode):
function resolve(T $value): PromiseInterface<T, never> {}
function reject(R $reason): PromiseInterface<never, R> {}
When resolving or rejecting, the created promise will have one type based on the provided value or reason, the other is always never
.
then(onFulfilled(...), onRejected(...)) => PromiseInterface<ReturnTypeOfOnFulfilled|ReturnTypeOfOnRejected, never>
then(onFulfilled(...)) => PromiseInterface<ReturnTypeOfOnFulfilled, R>
then(null, onRejected(...)) => PromiseInterface<T|ReturnTypeOfOnRejected, never>
then() => PromiseInterface<T, R>
The presence of onRejected
turns a potentially rejected promise into a fulfilled promise; therefore the returned promise will be <?, never>
.
If onFulfilled
or onRejected
is missing, the original respective T
and R
is propagated to the returned type.
--
For context, I got here from webonyx/graphql-php#1170 where we also discussed generics promises.
Another thing is that I believe the promise type is covariant on |
@vhenzl The main goal with this PR is to make it work well enough to make reactphp/async#40 possible and then make the rest fully work in #223. I'll take a good read of your comment and playgrounds tonight, and do a longer response. But if you have a way to reach the goal I mentioned above then please feel free to suggest it as we realised this is a bigger undertaking than just this PR. And we want to get fibers out in the short term with typing. |
@vhenzl Excellent input and indeed something we've considered before. I agree that having the After the discussion so far, it's my understanding focusing solely on |
8566b8d
to
faa006c
Compare
38e0443
to
4abe816
Compare
This adds basic type safety annotations for static analyzers like PHPStan and Psalm. This will cover around 80% of the use cases and a follow-up PR for all supported versions will be proposed later to get it to a 100% of close to a 100%. By adding these annotations methods returning a promise can hint their resolving type by adding `@return PromiseInterface<bool>` when they for example resolve to a boolean. By doing that Psalm and PHPStan will understand that the following bit of code will not become an issue because the method's contract promised a boolean through the promise: ```php $promise->then(static function (bool $isEnabled) {}); ``` However, the following will yield errors: ```php $promise->then(static function (string $isEnabled) {}); ``` This PR is a requirement for reactphp/async#40
4abe816
to
f4cc8ae
Compare
@@ -32,10 +35,11 @@ interface PromiseInterface | |||
* than once. | |||
* 3. `$onProgress` (deprecated) may be called multiple times. | |||
* | |||
* @param callable|null $onFulfilled | |||
* @template TReturn of mixed | |||
* @param callable(T): TReturn $onFulfilled |
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.
No longer marked as nullable?
Closing this in favour of #223 |
This adds basic type safety annotations for static analyzers like PHPStan and Psalm. This will cover around 80% of the use cases and a follow-up PR for all supported versions will be proposed later to get it to a 100% of close to a 100%.
By adding these annotations methods returning a promise can hint their resolving type by adding
@return PromiseInterface<bool>
when they for example resolve to a boolean. By doing that Psalm and PHPStan will understand that the following bit of code will not become an issue because the method's contract promised a boolean through the promise:However, the following will yield errors:
This PR is a requirement for reactphp/async#40