Skip to content

Conversation

@rajyan
Copy link
Contributor

@rajyan rajyan commented Mar 17, 2022

Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure about this fix. What should be done instead is that these situations should be caught by the if ($exprType instanceof NeverType && $exprType->isExplicit()) { instead.

"Implicit" NeverType is for example when you try to intersect two incompatible types (string&int). "Explicit" mixed is when you have a throw expression, function with @return never and similar.

This means that the explicitness of "NeverType" gets lost when we have a match expression with "explicit never" arms, and that's something that should be fixed instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you for the review and informative examples. I'll try fixing in that way!

@rajyan rajyan marked this pull request as draft March 17, 2022 13:54
@ondrejmirtes ondrejmirtes force-pushed the 1.5.x branch 3 times, most recently from ddd20b4 to 95d480b Compare March 18, 2022 19:53
@rajyan
Copy link
Contributor Author

rajyan commented Mar 20, 2022

The cause is here,

if ($types[$i] instanceof NeverType) {
unset($types[$i]);
continue;
}

return new NeverType();

but not sure how to fix yet.

Also, the comment says that it's transforming A | never to A

// transform A | never to A

but NeverType is not handled there.

I think it can be handled like the comment says, and never | never can be NeverType with $type->isExplicit() && $type->isExplicit().

But if I fix like that, test fails here with array{Never, Never}

<?php
namespace Bug1516;
use function PHPStan\Testing\assertType;
class FlowNodeManager
{
public function _test() : void
{
$out = [];
$a =
[
'foof' => 'barr',
'ftt' => []
];
foreach ($a as $k => $b) {
$str = 'toto';
assertType('\'toto\'|array{}', $out[$k]);
if (is_array($b)) {
// $out[$k] is redefined there before the array_merge
assertType('\'toto\'|array{}', $out[$k]);
$out[$k] = [];
assertType('array{}', $out[$k]);
$out[$k] = array_merge($out[$k], []);
assertType('array{}', $out[$k]);
} else {
// I think phpstan takes this definition as a string and takes no account of the foreach
$out[$k] = $str;
assertType('\'toto\'', $out[$k]);
}
}
}
}
.

@rajyan
Copy link
Contributor Author

rajyan commented Mar 20, 2022

Have to take more time breaking and fixing TypeCombinator to understand it deeper...

@ondrejmirtes
Copy link
Member

Hi, I've took the liberty to push a change here. Some tests still fail but I like it beter. Before TypeCombinator removed NeverType from $types so the fact the it's an explicit NeverType got lost.

Right now we leave NeverType in there so if TypeCombinator::union() is called with multiple explicit NeverType instances, the explicitness is preserved.

But some tests currently fail and I don't know why. Please debug that, thanks :)

Please read more about what's going on in TypeCombinator here: https://phpstan.org/developing-extensions/type-system (especially https://phpstan.org/developing-extensions/type-system#type-normalization).

@rajyan
Copy link
Contributor Author

rajyan commented Mar 22, 2022

@ondrejmirtes
Hi, thank you for looking into this PR.
The failing test were from NeverType handling, which TypeCombinator says that

// transform A | never to A

but not transformed actually.

By adding NeverType union handling c4ac24b, all tests passed except this one.

<?php
namespace Bug1516;
use function PHPStan\Testing\assertType;
class FlowNodeManager
{
public function _test() : void
{
$out = [];
$a =
[
'foof' => 'barr',
'ftt' => []
];
foreach ($a as $k => $b) {
$str = 'toto';
assertType('\'toto\'|array{}', $out[$k]);
if (is_array($b)) {
// $out[$k] is redefined there before the array_merge
assertType('\'toto\'|array{}', $out[$k]);
$out[$k] = [];
assertType('array{}', $out[$k]);
$out[$k] = array_merge($out[$k], []);
assertType('array{}', $out[$k]);
} else {
// I think phpstan takes this definition as a string and takes no account of the foreach
$out[$k] = $str;
assertType('\'toto\'', $out[$k]);
}
}
}
}

This one failed because it has a different reason related to array_merge (the test is green if array_merge is removed).
Empty array for ConstantArray was changed to and explicit NeverType in this commit c2b9e71

but the handling for ArrayMergeFunctionDynamicReturnTypeExtension

if ($keyType instanceof NeverType && !$keyType->isExplicit()) {
return new ConstantArrayType([], []);
}

is expecting a implicit NeverType

I hope this fix is in the right direction. I'll add a regression test for NeverType (and maybe for ArrayMergeFunctionDynamicReturnTypeExtension ?) if it looks good to you!

@ondrejmirtes
Copy link
Member

The thing is - NeverType is already handled by TypeCombinator::compareTypesInUnion() by these lines:

		if ($b->isSuperTypeOf($a)->yes()) {
			return [null, $b];
		}

		if ($a->isSuperTypeOf($b)->yes()) {
			return [$a, null];
		}

The root issue was that some types wrongly responded to isSuperTypeOf with NeverType. So I addressed the root issue and merged it: 99951ac

Thank you.

@rajyan rajyan deleted the fix/issue-6251 branch March 22, 2022 09:43
@rajyan
Copy link
Contributor Author

rajyan commented Mar 22, 2022

Thank you.
Sorry I couldn't get to the root issue...

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.

Match interpretation doesn't identify return type correctly Match statements throwing exceptions not interpreted correctly

2 participants