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

Add more generic phpdocs #306

Closed
wants to merge 1 commit into from

Conversation

dantleech
Copy link

@dantleech dantleech commented Mar 28, 2020

See #305

This allows:

interface WatcherProcess
{
    public function stop(): void;

    /**
     * @return Promise<?ModifiedFile>
     */
    public function wait(): Promise;
}

lib/Promise.php Outdated
@@ -23,7 +24,7 @@ interface Promise
* Note: You shouldn't implement this interface yourself. Instead, provide a method that returns a promise for the
* operation you're implementing. Objects other than pure placeholders implementing it are a very bad idea.
*
* @param callable(\Throwable|null, TValue|null) $onResolved The first argument shall
* @param callable(\Throwable|null, TValue|null): void $onResolved The first argument shall
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Phpstan wouldn't parse this without :void (which is in the Psalm stub)

Copy link
Contributor

@enumag enumag left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add @template for the call function too. It's one of the most frequently used ones so I think it deserves it.
https://github.com/vimeo/psalm/blob/master/src/Psalm/Internal/Stubs/Amp.php#L15-L24

@dantleech
Copy link
Author

Please add @template for the call function too

I tried this, but at least with PHPStan it gives an error, and I'm not sure how to add the type information:

   /**
     * @template TValue
     *
     * @param callable():(\Generator<mixed, mixed, mixed, TValue>|TValue) $gen
     *
     * @return Promise<TValue>
     */
    function call(callable $callback, ...$args): Promise
 ------ ---------------------------------------------------------------- 
  Line   SystemDetector/CommandDetector.php                                     
 ------ ---------------------------------------------------------------- 
  16     Unable to resolve the template type TValue in call to function  
         Amp\call                                                        
 ------ ----------------------------------------------------------------

Untitled

*
* @return callable(mixed ...$args): \Amp\Promise
* @param callable():\Generator<mixed, mixed, mixed, TValue> $callback
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$callback is passed to call() which accepts many different types as return types from the callback, not just Generator.
See https://github.com/amphp/amp/blob/master/lib/functions.php#L65-L77

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated to

     * @param callable(mixed ...$args): (\Generator<mixed, mixed, mixed, TValue>|\Amp\Promise<TValue>|mixed) $callback

If that makes sense.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to use TValue instead of the last mixed but I'm guessing that runs into the same issue you mentioned above, right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Second mixed of the generator should possibly be Promise<mixed>. Afaik yielding anything else than a Promise would cause an error.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, that works. However not sure about the ReactPromise? Should we add that too?

     * @param callable(mixed ...$args): (\Generator<mixed, mixed, mixed, TValue>|\Amp\Promise<TValue>|\React\Promise\PromiseInterface<TValue>|TValue) $callback

Copy link
Contributor

@enumag enumag Mar 28, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess... Personally I'm kinda against using React promises in Amphp code and would always call adapt() anyway but it works so I guess it's correct to add it here.

By the way. technically the return value of the Generator is Promise<TValue>|ReactPromise<TValue>|TValue.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool, updated to use local names

@enumag
Copy link
Contributor

enumag commented Mar 28, 2020

I tried this, but at least with PHPStan it gives an error, and I'm not sure how to add the type information:

I see... yeah that complicates things. Maybe we should report it to PHPStan then.

@dantleech dantleech changed the title [wip] Add more generic docs Add more generic phpdocs Mar 28, 2020
lib/Promise.php Outdated Show resolved Hide resolved
@enumag
Copy link
Contributor

enumag commented Mar 28, 2020 via email

@kelunik
Copy link
Member

kelunik commented Mar 28, 2020

I've added quite a lot of annotations I had locally already, so there are some conflicts now, sorry about that.

@dantleech dantleech force-pushed the gh-305-amp-sa-coroutine branch from d884c50 to 7585d05 Compare March 28, 2020 14:28
@@ -14,8 +14,7 @@
*
* @template TReturn
*
* @param callable(mixed ...$args):(\Generator<mixed,Promise|ReactPromise|array<array-key, Promise|ReactPromise>,mixed,Promise<TReturn>|ReactPromise|TReturn>|Promise<TReturn>|ReactPromise|TReturn)
* $callback
* @param callable(mixed ...$args):(\Generator<mixed,Promise|ReactPromise|array<array-key, Promise|ReactPromise>,mixed,Promise<TReturn>|ReactPromise|TReturn>|Promise<TReturn>|ReactPromise|TReturn) $callback
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The phpstan docblock parser didn't like $callback being ona new line for some reason.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please open an phpstan issue for that? Phpstorm automatically formats it like that for me.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, it works now it seems.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PhpStorm doesn't understand this syntax at all so I'm not surprised it breaks on that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ondrejmirtes: PhpStorm doesn't break, it just reformats too long lines like that.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kelunik Yeah but it won't be able to understand the method accepts callable. Maybe in this case yes, because the type starts with callable, but definitely not in some more complex cases.

@dantleech
Copy link
Author

dantleech commented Mar 28, 2020

Updated most of it seems to be implemented now anyway, although it does fix a parsing issue with Phpstan. The new call / asyncCall annotations don't work with Phpstan for the reason given above - I'd report a bug but will need to think about it quite hard 👀

@dantleech dantleech force-pushed the gh-305-amp-sa-coroutine branch from 3fa7ede to 9f73668 Compare March 28, 2020 18:13
@dantleech
Copy link
Author

ok - so I think everything is covered by your changes and the original stuff in this PR can be discarded.

I've updated the PR to replace @psalm-param with @param as these seem to work with Phpstan.

@ondrejmirtes
Copy link

I wouldn't use these in @param because you'll break IDE autocompletion. Better to use analyser-prefixed tags like @phpstan-param and @psalm-param.

@dantleech
Copy link
Author

dantleech commented Mar 28, 2020

Then, I'll close this. Thanks for implementing the types @kelunik

@ondrejmirtes I would create an issue for what seems like a Phpstan bug above , but I'm not quite sure yet how to reproduce, hopefully can do it later.

@dantleech dantleech closed this Mar 28, 2020
@dantleech dantleech deleted the gh-305-amp-sa-coroutine branch March 28, 2020 18:27
@dantleech
Copy link
Author

dantleech commented Mar 28, 2020

I just tried running Psalm with the lastest annotations in master:

            while (null !== $chunk = yield $stream->read()) {
                foreach (str_split($chunk) as $char) {

gives:

INFO: InvalidArgument - src/Parser/LineParser.php:18:36 - Argument 1 of str_split expects string, Amp\Promise<null
|string> provided (see https://psalm.dev/004)                                                                     
                foreach (str_split($chunk) as $char) {                                                             

(after adding the psalm-return to ProcessInputStream:

     * @psalm-return Promise<string|null>
     */
    public function read(): Promise

🤔

@enumag
Copy link
Contributor

enumag commented Mar 28, 2020

Even with the annotations both Psalm and PHPStan are very unlikely to recognize that
yield Promise<TValue> will produce TValue.

@ondrejmirtes
Copy link

I havent checked the actual annotations but Generator stubs in PHPStan/Psalm support this in TSend.

@kelunik
Copy link
Member

kelunik commented Mar 28, 2020

@ondrejmirtes They just support one type per generator, but not a type based on the yielded value.

@ondrejmirtes
Copy link

@kelunik I don't understand. See this example: https://phpstan.org/r/0eaa8d12-2237-489a-9165-eb243d637329

What do you think wouldn't work in regards to Amp?

@enumag
Copy link
Contributor

enumag commented Mar 28, 2020

@ondrejmirtes With Amphp the Generators are usually immediately called using the \Amp\call() function. They do not have an annotation with a TSend type. Also they often yield promises for different things. See the code example here.

$http->request("https://example.com/") returns a Promise<Response> which means that $response instanceof Response. Then $response->getBody(); returns a Promise<string> so $body is a string.

@enumag
Copy link
Contributor

enumag commented Mar 28, 2020

In other words this code:

/**
 * @return Promise<string>
 */
function getExampleCom(HttpClient $http): Promise {
    return Amp\call(function () use ($http) {
        $response = yield $http->request("https://example.com/");
        $body = yield $response->getBody();
        return $body;
    });
}

would look like this with async-await:

async function getExampleCom(HttpClient $http): string {
    $response = await $http->request("https://example.com/");
    $body = await $response->getBody();
    return $body;
}

@dantleech
Copy link
Author

The issue seems similar to: phpstan/phpstan-doctrine#101

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

4 participants