-
-
Notifications
You must be signed in to change notification settings - Fork 148
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
Improve type annotations #290
Conversation
21c3a34
to
d82f3ca
Compare
@@ -15,6 +15,8 @@ | |||
/** | |||
* Provides the base class for a generic list (items can be accessed by index). | |||
* @template T | |||
* @implements \IteratorAggregate<int, 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.
Isn't that duplicate information?
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.
It is, but just for getIterator(). Implementing IteratorAggregate also passes generic type to Traversable. Without it Traversable type is mixed. Not sure if this specific case is problem, but generally speaking, generic interface should be always implemented.
Generic type from getIterator() should be removed, as it is already defined by IteratorAggregate
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.
Thanks for the explanation. Can I ask @xificurk to remove the annotation from getIterator()
?
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 a bit more complicated...
Latest PHPStan can infer key/value types even without the @implements
annotation. Actually, the correct annotations on methods like getIterator()
are the key to get this working.
The @implements
annotations on class come into play only when checking compatibility between e.g. ArrayList<Foo>
and Traversable<string>
.
Example with method annotations only:
https://phpstan.org/r/0ecafc0e-1f6a-422b-8121-601992d34b25
Example with class annotation only:
https://phpstan.org/r/ec2e29f0-53a0-45c8-9030-dcfb5cb3ab57
@@ -225,6 +225,7 @@ public static function flatten(array $array, bool $preserveKeys = false): array | |||
|
|||
/** | |||
* Checks if the array is indexed in ascending order of numeric keys from zero, a.k.a list. | |||
* @return ($value is list ? true : false) |
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.
What's it good for?
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.
If variable is of type list it will always return true. Informs user that check is effectively useless.
But it should probably be @return ($value is list ? true : bool)
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 understand what it does, I ask what good it does :-) I just need an example.
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.
Here it is. If condition is always true.
https://phpstan.org/r/37d792a0-1fe7-488a-8818-15048810f6c0
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.
Sadly it is only half of the solution and it seems phpstan does not support assert for list. Even after isList() is given value array
https://phpstan.org/r/c26d6864-a46a-4d81-9fad-54dbc75d7e83
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.
@mabar @enumag Why do you think the return type should be @return ($value is list ? true : **bool**)
? Can you give me example of value type which is not list, but the method will return true?
This change was actually the reason, why the examples you've posted seemingly did not work. The correct conditional return annotation is enough for phpstan to correctly detect always true/false conditions & perform type narrowing.
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.
@xificurk Because $value
can be a list
without PHPStan being aware of it.
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.
@enumag Can you create example in phpstan playground?
btw, isn't it exactly the case of the last if-else block in my example? PHPStan can't know if the value is list or not, but if it is, the isList()
method will always return true, otherwise false.
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 tried a few things and yeah you're right, false is enough there. PHPStan does treat unknown array as a possible list.
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.
For completeness, here is a good example for it: https://phpstan.org/r/8f789bd9-8fbe-477c-87e1-e7c2cad3d6f2
* @property-read int $width | ||
* @property-read int $height | ||
* @property-read positive-int $width | ||
* @property-read positive-int $height |
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.
What's it good for?
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.
Mostly for compatibility with other classes that already expect width and height to be >= 0. e.g. for database entity.
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.
As any type (narrowing) hint, it's good to make code more readable and eliminate useless checks.
If you rely on static analysis and narrow down the types enough, checks like this become useless and can be safely eliminated.
Dimensions are often used in various calculations. Knowing that those are actually positive integers, implies e.g.:
- their ratio is well defined and also positive number
- the area (number of pixels in image) is positive integer
@@ -267,6 +271,8 @@ public static function detectTypeFromString(string $s, &$width = null, &$height | |||
|
|||
/** | |||
* Returns the file extension for the given `Image::XXX` constant. | |||
* @param self::JPEG|self::PNG|self::GIF|self::WEBP|self::AVIF|self::BMP $type | |||
* @return value-of<self::Formats> |
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.
What's @return
annotation good for?
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.
For me the most important thing out of this is specifying that the output extension is non-empty string, which is useful when you want to use the extension further. I guess @return non-empty-string
could cover most of the use-cases, but why shouldn't we make the return type as narrow as possible?
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.
Now, as a return annotation, this should probably be ImageType::*
, right?
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, because it returns one of the string values contained in self::Formats
, not ImageType::*
value.
55488d5
to
3678b20
Compare
d82f3ca
to
f0569a6
Compare
I would like to release version 4.0.1 soon, we can finish this somehow @xificurk ? |
@dg I'll take a look today/tomorrow. |
@dg rebased & squashed, I think it's ready from my point of view. |
Thanks! |
Add more specific type annotations to improve static analysis support.