Skip to content

min() and max() may return false if array size could be zero#300

Merged
ondrejmirtes merged 3 commits intophpstan:masterfrom
pmmp:min-max-false-return
Aug 16, 2020
Merged

min() and max() may return false if array size could be zero#300
ondrejmirtes merged 3 commits intophpstan:masterfrom
pmmp:min-max-false-return

Conversation

@dktapps
Copy link
Copy Markdown
Contributor

@dktapps dktapps commented Aug 16, 2020

Working on tests, just thought it would be nice to get some feedback on this 👍

https://phpstan.org/r/4d4d5332-d1ad-45b2-bbdd-43c8cb87ff61
https://3v4l.org/f0kNk

I'm not 100% sure this is the right way to address the issue since in 8.0 this starts throwing a ValueError instead. But, since most of us are still using 7, I think this should be useful anyway.

@dktapps
Copy link
Copy Markdown
Contributor Author

dktapps commented Aug 16, 2020

Having some strange behaviour with an integration test file I'm trying to add, @ondrejmirtes could you shed any light on this?

<?php

namespace MinMaxArrays;

function takesInt(int $int): void
{
}

function dummy(): void
{
	takesInt(min([1]));
	takesInt(min([]));
	takesInt(max([1]));
	takesInt(max([]));
}

/**
 * @param int[] $ints
 */
function dummy2(array $ints): void
{
	if (count($ints) > 0) {
		takesInt(min($ints));
		takesInt(max($ints));
	} else {
		takesInt(min($ints));
		takesInt(max($ints));
	}
}

/**
 * @param int[] $ints
 */
function dummy3(array $ints): void
{
	takesInt(min($ints));
	takesInt(max($ints));
}

just spews errors about takesInt not existing. The same code works fine on the playground so I don't understand the problem.

@ondrejmirtes
Copy link
Copy Markdown
Member

This is best tested in NodeScopeResolver::testFileAsserts(), it's just about type inference. We don't need to add integration test for the "accepts type" rules.

$argType = $scope->getType($functionCall->args[0]->value);
if ($argType->isArray()->yes()) {
$isIterable = $argType->isIterableAtLeastOnce();
if ($isIterable->no()) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice, this is exactly how I'd implement it 👍

@dktapps
Copy link
Copy Markdown
Contributor Author

dktapps commented Aug 16, 2020

It seems that count($array) doesn't inform the type system that $array is definitely iterable at least once (or not), so my test case isn't working out as well as I'd like it to. For example, this case ought to be OK:

/**
 * @param int[] $ints
 */
function dummy2(array $ints): void
{
	if (count($ints) > 0) {
		takesInt(min($ints)); //should be fine
		takesInt(max($ints)); //should be fine
	} else {
		takesInt(min($ints)); //definitely not ok
		takesInt(max($ints)); //definitely not ok
	}
}

@ondrejmirtes
Copy link
Copy Markdown
Member

Looks like some cases aren't covered: https://phpstan.org/r/90c6a1be-8301-4d60-9715-634814964a6a (here it works for count($ints) !== 0).

You can look into it in TypeSpecifier, and also add some type assertions. A non empty array is array<int>&nonEmpty.

@dktapps
Copy link
Copy Markdown
Contributor Author

dktapps commented Aug 16, 2020

Seems like it only works for === and !==. ==, !=, > >=, < and <= seem excluded.

@ondrejmirtes
Copy link
Copy Markdown
Member

Alright, let’s merge this after the build passes and then I’ll ask you to fix the TypeSpecifier for these cases, thanks 😊

@ondrejmirtes ondrejmirtes merged commit ad7dce2 into phpstan:master Aug 16, 2020
@ondrejmirtes
Copy link
Copy Markdown
Member

Thank you!

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants