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 support for conditional return type #3853

Closed
greg0ire opened this issue Sep 10, 2020 · 40 comments
Closed

Add support for conditional return type #3853

greg0ire opened this issue Sep 10, 2020 · 40 comments

Comments

@greg0ire
Copy link

Feature request

This is a follow up of #3849 , and of doctrine/dbal#4264

I tried your suggestion, and it seems the root cause is conditional return types are not supported: https://phpstan.org/r/bdef5abe-d824-4311-aa6e-e0cce31ede92

PHPDoc tag @phpstan-return has invalid value (($params is array{wrapperClass:mixed} ? T : Connection)): Unexpected token "$params", expected type at offset 82

Cc @pkruithof @morozov

@greg0ire
Copy link
Author

As a side note, here is my unsuccessful attempt at keeping the annotations for Psalm, and letting PHPStan fallback on a simpler version: https://phpstan.org/r/02c85102-05d0-410f-a76f-037bd4717ed3

I think that's because of the @psalm-template annotation, which PHPStan needs to resolve.

@dktapps
Copy link
Contributor

dktapps commented Sep 10, 2020

This would also be nice for dynamic return type extensions (some of the native ones would become obsolete, e.g microtime() would be able to have $get_as_float is true ? float : string or something like that).

@ondrejmirtes
Copy link
Member

FYI the annoying message about "unable to resolve template type" will no longer show up in these cases (because the template type is not present in the return type used by PHPStan): https://phpstan.org/r/3273047f-31d7-42bb-8763-357e10553eb5

Thanks to commit: phpstan/phpstan-src@a5f9f0c

@voku
Copy link
Contributor

voku commented Feb 24, 2021

Can we dynamic initialize "dynamic return type extensions" for conditional return types?

@ondrejmirtes
Copy link
Member

@voku Your question is expressed in a weird way but dynamic return type extension is what you typically implement today to achieve conditional types.

@voku
Copy link
Contributor

voku commented Feb 24, 2021

@voku Your question is expressed in a weird way but dynamic return type extension is what you typically implement today to achieve conditional types.

Sorry, yes "dynamic dynamic" 🙏 is not the best way to ask a question about dynamic initialization. 😇

I wanted to know if its possible to use the current extension code for dynamic return types via phpdoc instead of added more classes.

It would be much more easy to share this with others because they do not need to add the extra class, do not change the phpstan config and it would work out of the box.

@ondrejmirtes
Copy link
Member

That's what this open feature request asks for, it's on my roadmap to implement this.

nederdirk added a commit to nederdirk/Propel2 that referenced this issue Feb 25, 2021
Please refer to the Psalm documentation for conditional types[^0] for
more information. Although the docs specify that template parameters are
required, the reference-a-variable shorthand for templates also work,
see the [example].

Usually this project seems to prefer `@phpstan-`-prefixes, but this
construct is currently not available to PHPstan[^1].

[^0]: https://psalm.dev/docs/annotating_code/type_syntax/conditional_types/
[^1]: phpstan/phpstan#3853
[example]: https://psalm.dev/r/11cdedd7b3
@phpstan-bot
Copy link
Contributor

@greg0ire PHPStan now reports different result with your code snippet:

@@ @@
-13: PHPDoc tag @phpstan-return has invalid value (($params is array{wrapperClass:mixed} ? T : Connection)): Unexpected token "$params", expected type at offset 82
-18: Unable to resolve the template type T in call to method static method DriverManager::getConnection()
+13: PHPDoc tag @phpstan-return has invalid value (($params is array{wrapperClass:mixed} ? T : Connection)): Unexpected token "$params", expected type at offset 82
Full report
Line Error
13 PHPDoc tag @phpstan-return has invalid value (($params is array{wrapperClass:mixed} ? T : Connection)): Unexpected token "$params", expected type at offset 82

@phpstan-bot
Copy link
Contributor

@greg0ire PHPStan now reports different result with your code snippet:

@@ @@
-21: Unable to resolve the template type T in call to method static method DriverManager::getConnection()
+No errors

@greg0ire
Copy link
Author

greg0ire commented Mar 9, 2021

Awesome!

@greg0ire greg0ire closed this as completed Mar 9, 2021
@ondrejmirtes ondrejmirtes reopened this Mar 9, 2021
@ondrejmirtes
Copy link
Member

Conditional types aren't yet implemented. The "unable to resolve" error disappeared because in PHPStan-read @return tag T is not present. I've talked about it in a previous comment: #3853 (comment)

@greg0ire
Copy link
Author

greg0ire commented Mar 9, 2021

Oh right, the message did not appear before because phpstan-bot was not implemented when the the release containing what you commented on was created? Anyway, that bot is great!

@ondrejmirtes
Copy link
Member

Exactly :) Thank you!

mringler pushed a commit to mringler/Propel2 that referenced this issue Mar 15, 2021
Please refer to the Psalm documentation for conditional types[^0] for
more information. Although the docs specify that template parameters are
required, the reference-a-variable shorthand for templates also work,
see the [example].

Usually this project seems to prefer `@phpstan-`-prefixes, but this
construct is currently not available to PHPstan[^1].

[^0]: https://psalm.dev/docs/annotating_code/type_syntax/conditional_types/
[^1]: phpstan/phpstan#3853
[example]: https://psalm.dev/r/11cdedd7b3
@phpstan-bot
Copy link
Contributor

@greg0ire After the latest commit in dev-master, PHPStan now reports different result with your code snippet:

@@ @@
-21: Unable to resolve the template type T in call to method static method DriverManager::getConnection()
+16: Template type T of method DriverManager::getConnection() is not referenced in a parameter.
Full report
Line Error
16 Template type T of method DriverManager::getConnection() is not referenced in a parameter.

@phpstan-bot
Copy link
Contributor

@ondrejmirtes After the latest commit in dev-master, PHPStan now reports different result with your code snippet:

@@ @@
-No errors
+16: Template type T of method DriverManager::getConnection() is not referenced in a parameter.
Full report
Line Error
16 Template type T of method DriverManager::getConnection() is not referenced in a parameter.

@ondrejmirtes ondrejmirtes pinned this issue Mar 30, 2022
@phpstan-bot
Copy link
Contributor

@greg0ire After the latest commit in 1.6.x, PHPStan now reports different result with your code snippet:

@@ @@
-13: PHPDoc tag @phpstan-return has invalid value (($params is array{wrapperClass:mixed} ? T : Connection)): Unexpected token "$params", expected type at offset 82
 18: Unable to resolve the template type T in call to method static method DriverManager::getConnection()
Full report
Line Error
18 Unable to resolve the template type T in call to method static method DriverManager::getConnection()

@ondrejmirtes
Copy link
Member

About:

 Identify which dynamic return type extensions in PHPStan could be replaced with a stub that has conditional return type

I looked into some and maybe there won't be too many. Sometimes a function looks like a good candidate, like current, but then I saw that the parameter is array|object, and I don't want to have "unable to resolve template type" error when someone calls current($object).

But for example it looks like it could work for HrtimeFunctionReturnTypeExtension. But there's also a catch - the extension currently returns a "benevolent union type" - we have a special syntax that works in PHPDocs for that - it's __benevolent<Foo|Bar>, but again, it's not going to be as nice as I expected.

Also MicrotimeFunctionReturnTypeExtension is a good candidate.

@staabm
Copy link
Contributor

staabm commented Apr 1, 2022

Identify which dynamic return type extensions in PHPStan could be replaced with a stub that has conditional return type

an alternative approach could be checking the conditional types psalm defines and phpstan does not have a Extension for, so we could be inspired by their types

grafik

@ondrejmirtes
Copy link
Member

Feel free to go through them and submit them here to 1.6.x branch along with some tests, that would be helpful!

rvanvelzen added a commit to rvanvelzen/phpstan-src that referenced this issue Apr 1, 2022
@staabm
Copy link
Contributor

staabm commented Apr 1, 2022

Feel free to go through them and submit them here to 1.6.x branch along with some tests, that would be helpful!

I started doing so. will send PRs as i see fit.

most interessting observation atm regarding conditional types: I did not yet find a case, where the conditional types are "useful" for phpstan-core.

all I scanned right now are either already implemented as an Extension (and phpstan is most of the time more precise then the psalm conditional types), or can be aliased on existing logic.

I am sure though, that the conditional types are usefull for libraries and application code, for cases one does not want to go full into phpstan internals/extensions right now.


here a list of "special" cases psalm conditional types understand.. I don't say we should support them, but as I work thru the list right now, I will give you a summary:

I will update this list, in case I find more


there is also a news article with some references to builtin methods utilizing conditional return types

@ondrejmirtes
Copy link
Member

I'll look into this myself:

What to do about conditional return type when inheriting PHPDoc? Parameter name needs to be remapped based on position.

@phpstan-bot
Copy link
Contributor

@ondrejmirtes After the latest commit in 1.6.x, PHPStan now reports different result with your code snippet:

@@ @@
-18: Unable to resolve the template type T in call to method static method DriverManager::getConnection()
+No errors

@ondrejmirtes
Copy link
Member

I'm gonna do these two next:

Test that OverridingMethodRuleTest works with conditional type as expected (verify covariance with parent method)
Test that IncompatiblePhpDocTypeRule works with conditional type as expected

@ondrejmirtes
Copy link
Member

Tomorrow I want to work on this one:

New rule - always true/always false conditional type condition - like $a is int when there's already @param int $a

I think PHPStan 1.6.0 is on the right track to be released next week, but I don't want to promise anything :)

@ondrejmirtes
Copy link
Member

I realized that all of these would have some overlap/duplications so this is going to be taken care of by a single rule:

New rule - is the type in ConditionalTypeNode part of the function's @template type map?
New rule - does the parameter in ConditionalTypeForParameterNode exist?
New rule - always true/always false conditional type condition - like $a is int when there's already @param int $a

@canvural
Copy link
Contributor

I was playing with the conditional types and came up with this example from Psalm: https://phpstan.org/r/560549e5-71b5-47be-8da2-74d5d87089e1 I know it's still in work in progress, so I didn't want to open a new issue.

Shouldn't it dump float in the second case? Because int|float is not int, it should take the else branch

@ondrejmirtes
Copy link
Member

@canvural Generics don't work like that. If you have T referenced multiple times in parameters, T is inferred as a union of all parameters. So in case of (T is int ? int : float), it's a matter of (int|float is int ? int : float), so both if and else are used in the result.

@canvural
Copy link
Contributor

I know it is inferred as union. But the union int|float is not int So I thought only else branch would be considered.

@ondrejmirtes
Copy link
Member

int|float can be int (IntegerType->isSuperTypeOf(UnionType(IntegerType, FloatType)) is maybe.

@ondrejmirtes
Copy link
Member

Done: phpstan/phpstan-src@a8d9628

@rvanvelzen Please is there anything you want to add? I'd like to release PHPStan 1.6.0 later this week :)

@rvanvelzen
Copy link
Contributor

Very nice! The conditional never type was the only thing I ran into, so as far as I'm concerned it's good to go :)

@ondrejmirtes
Copy link
Member

Awesome! I need to write an article about the flagship things in PHPStan 1.6.0, compile the release notes, and it's good to go :)

@ondrejmirtes
Copy link
Member

@canvural Your example can be solved with:

/**
 * @param int|float $a
 * @param int|float $b
 * @return ($a is int ? ($b is int ? int : float) : float)
 */
function add($a, $b) {
    return $a + $b;
}

@ondrejmirtes ondrejmirtes unpinned this issue May 9, 2022
@github-actions
Copy link

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 27, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

8 participants