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

Fix false positives on non-existing-offset's #3766

Open
wants to merge 3 commits into
base: 2.1.x
Choose a base branch
from

Conversation

staabm
Copy link
Contributor

@staabm staabm commented Jan 3, 2025

fixes some false positives we see with reportPossiblyNonexistentGeneralArrayOffset.

basic idea is to infer a expression type for $array[$last] after $last = array_key_last($array); based on the iterable-type of a non-empty $array.

refs phpstan/phpstan#11020


this PR fixes code like

	/**
	 * @param list<string> $hellos
	 */
	public function last(array $hellos): string
	{
		if ($hellos !== []) {
			$last = array_key_last($hellos);
			return $hellos[$last];
		}
        return '';
	}

	/**
	 * @param list<string> $hellos
	 */
	public function works(array $hellos): string
	{
		if ($hellos === []) {
			return 'nothing';
		}

		$count = count($hellos) - 1;
		return $hellos[$count];
	}

but not like (in which no intermediate variable is used)

	/**
	 * @param list<string> $hellos
	 */
	public function last(array $hellos): string
	{
		if ($hellos !== []) {
			return $hellos[array_key_last($hellos)];
		}
        return '';
	}

	/**
	 * @param list<string> $hellos
	 */
	public function works(array $hellos): string
	{
		if ($hellos === []) {
			return 'nothing';
		}

		return $hellos[count($hellos) - 1];
	}

@clxmstaab clxmstaab force-pushed the bug11020 branch 2 times, most recently from 1b0239b to e723d34 Compare January 3, 2025 13:12
@staabm staabm marked this pull request as ready for review January 3, 2025 13:17
@phpstan-bot
Copy link
Collaborator

This pull request has been marked as ready for review.

@jhoffland
Copy link

Thanks for fixing the issue I encountered. In the description are some specific cases mentioned which are not covered by this fix. What would be required to fix those cases? I could help.

@leongersen
Copy link
Contributor

leongersen commented Jan 3, 2025

It seems to me that array_key_first and array_key_last (as well as count) could add information to the array they are called with.

Currently, there is ArrayKeyLastDynamicReturnTypeExtension, which determines the return type for array_key_last, but does not modify the information about the array itself.

Would it make sense to have a FunctionTypeSpecifyingExtension for array_key_last as well, which augments the information about the array with the result determined by ArrayKeyLastDynamicReturnTypeExtension,
very similar to what ArrayKeyExistsFunctionTypeSpecifyingExtension does?

ArrayKeyExistsFunctionTypeSpecifyingExtension extends the array type with a HasOffsetType, which is also what array_key_first and array_key_last do.


This would also be an option for many more functions, such as array_search, which also proves a key exists. There is ArraySearchFunctionTypeSpecifyingExtension, which would need to specify that the array is an intersection with HasOffsetType for the type determined by ArraySearchFunctionDynamicReturnTypeExtension.

https://phpstan.org/r/a35121d2-fc59-48c9-982b-41c1850b3bf5

@staabm
Copy link
Contributor Author

staabm commented Jan 3, 2025

It seems to me that array_key_first and array_key_last (as well as count) could add information to the array they are called with.

thats exactly what this PR is doing.

Would it make sense to have a FunctionTypeSpecifyingExtension for array_key_last as well, which augments the information about the array with the result determined by ArrayKeyLastDynamicReturnTypeExtension,
very similar to what ArrayKeyExistsFunctionTypeSpecifyingExtension does?

I think thats not possible, because a FunctionTypeSpecifyingExtension can only work on the FuncCall and its arguments.
for the array_key_last, array_key_first and count the $offset we want to add information about is not part of the FuncCall, but the assignment arround it. thats why I hacked it directly into the TypeSpecifier

ArrayKeyExistsFunctionTypeSpecifyingExtension extends the array type with a HasOffsetType, which is also what array_key_first and array_key_last do.

for HasOffsetType we need a ConstantStringType or ConstantIntegerType type, which we don't know at static analysis time for array_key_first, array_key_last and count.

phpstan.org/r/a35121d2-fc59-48c9-982b-41c1850b3bf5

this array_search examples produces the same errors this PR is trying to fix for the other functions, when reportPossiblyNonexistentGeneralArrayOffset: true - so we could do the same fix for it as suggested here. you don't see them in the playground because the necessary config switch is not available there.

@leongersen
Copy link
Contributor

Thanks for your answer, I appreciate the insights!

Could you clarify the following for me?

I think thats not possible, because a FunctionTypeSpecifyingExtension can only work on the FuncCall and its arguments.

Is that not enough to ask the scope for the return type ($offset), which would be provided by the DynamicReturnTypeExtension?

@staabm
Copy link
Contributor Author

staabm commented Jan 3, 2025

Is that not enough to ask the scope for the return type ($offset), which would be provided by the DynamicReturnTypeExtension?

its enough to calculate the return-type of the FuncCall:
$last = array_key_last($array);
but to put the result of array_key_last($array); onto $array as $array[$last] we need to know the expression which the result is assigned to.

thats also the reason why my current approch doesn't work for $array[array_key_last($array)] - because there is no assignment in between. we could still support such a expression, but it needs simliar code at a different location of the TypeSpecifier. Thats why I sent this PR over for review now to check back with ondrey whether I am on the right track at all.

@staabm staabm force-pushed the bug11020 branch 3 times, most recently from 98f3ba8 to ce9e0fc Compare January 5, 2025 11:59
@staabm
Copy link
Contributor Author

staabm commented Jan 5, 2025

This would also be an option for many more functions, such as array_search, which also proves a key exists. There is ArraySearchFunctionTypeSpecifyingExtension, which would need to specify that the array is an intersection with HasOffsetType for the type determined by ArraySearchFunctionDynamicReturnTypeExtension.

@leongersen thanks to some great playground changes by ondrej, the opt-in only rule errors now show up:

https://phpstan.org/r/c519d102-52d4-41c5-8681-4b29be8b1381

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.

4 participants